From 579809a4f4afaa88ede0cdddd6511e5a94801019 Mon Sep 17 00:00:00 2001 From: Jumana Almahmoud Date: Wed, 2 Mar 2022 01:04:14 -0500 Subject: [PATCH 01/41] Update environments.js --- src/environments.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/environments.js b/src/environments.js index e9138d4..5a54a10 100644 --- a/src/environments.js +++ b/src/environments.js @@ -2,7 +2,7 @@ export const Environments = { prod: { baseURL: "https://nb2.csail.mit.edu", pluginURL: "https://nb2.csail.mit.edu/client" }, feature: { baseURL: "https://feature.nb2.csail.mit.edu", pluginURL: "https://feature.nb2.csail.mit.edu/client" }, dev: { baseURL: "https://127.0.0.1:3000", pluginURL: "https://127.0.0.1:3001" }, - testJumana: { baseURL: "https://jumana-nb.csail.mit.edu", pluginURL: "https://jumana-nb.csail.mit.edu/client" }, + test: { baseURL: "https://test.nb2.csail.mit.edu", pluginURL: "https://test.nb2.csail.mit.edu/client" }, testHelen: { baseURL: "https://helen-nb.csail.mit.edu", pluginURL: "https://helen-nb.csail.mit.edu/client" }, ngrok: { baseURL:"https://nb2.ngrok.io", pluginURL: "https://nb2.ngrok.io/client"} } From 1d4033d76d3ada51968f99db31817fb5bf466a81 Mon Sep 17 00:00:00 2001 From: Jumana Almahmoud Date: Wed, 2 Mar 2022 14:54:09 -0500 Subject: [PATCH 02/41] Update package.json --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index d47d7fb..534a31f 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,6 @@ "@fortawesome/free-regular-svg-icons": "^5.8.1", "@fortawesome/free-solid-svg-icons": "^5.8.1", "@fortawesome/vue-fontawesome": "^0.1.6", - "@sentry/tracing": "^6.2.5", - "@sentry/vue": "^6.2.5", "axios": ">=0.21.1", "bootstrap-vue": "^2.21.2", "express": "^4.17.0", @@ -61,4 +59,4 @@ "vue-sweetalert2": "^4.2.0", "webpack-dev-server": "^3.11.2" } -} \ No newline at end of file +} From f4560bc91edb46b730211fee373b0b15842edb98 Mon Sep 17 00:00:00 2001 From: Jumana Almahmoud Date: Wed, 2 Mar 2022 14:54:33 -0500 Subject: [PATCH 03/41] Update app.js --- src/app.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/app.js b/src/app.js index 38760c8..e37daa9 100644 --- a/src/app.js +++ b/src/app.js @@ -25,20 +25,6 @@ import axios from 'axios' import VueJwtDecode from "vue-jwt-decode"; import io from "socket.io-client"; import { Environments } from './environments' -// import * as Sentry from "@sentry/vue"; -// import { Integrations } from "@sentry/tracing"; - -//prodcution log -// Sentry.init({ -// Vue, -// dsn: "https://1a47ffe142234c9cb942cf7ddd6d4ec3@o564291.ingest.sentry.io/5722075", -// integrations: [new Integrations.BrowserTracing()], - -// // Set tracesSampleRate to 1.0 to capture 100% -// // of transactions for performance monitoring. -// // We recommend adjusting this value in production -// tracesSampleRate: 1.0, -// }); const currentEnv = Environments.dev From 87c1ccb523090ba5b61db136d7c139af608fac48 Mon Sep 17 00:00:00 2001 From: Jumana Almahmoud Date: Wed, 30 Mar 2022 22:48:30 -0400 Subject: [PATCH 04/41] Add configuration to control what events to be logged --- src/app.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app.js b/src/app.js index e37daa9..65914c1 100644 --- a/src/app.js +++ b/src/app.js @@ -311,8 +311,8 @@ function embedNbApp() { isSyncNotificationPopup: false, isSyncSpotlightNewThread: false, isNbLog: false, + nbLogEventsEnabled: [], syncSpotlightNewThreadConfig: {}, - isNbLogScroll: false, nbLogScrollSpoConfig: 2000, isShowQuickEditor: false, sortByConfig: 'init', @@ -554,8 +554,8 @@ function embedNbApp() { this.currentConfigs.isSyncNotificationPopup = configs['SYNC_NOTIFICATION_POPUP'] === 'true' ? true : false this.currentConfigs.isSyncSpotlightNewThread = configs['SYNC_SPOTLIGHT_NEW_THREAD'] === 'true' ? true : false this.currentConfigs.isNbLog = configs['NB_LOG'] === 'true' ? true : false + this.currentConfigs.nbLogEventsEnabled = configs['CONFIG_NB_LOG_EVENTS_ENABLED'] ? JSON.parse(configs['CONFIG_NB_LOG_EVENTS_ENABLED']) : [] this.currentConfigs.syncSpotlightNewThreadConfig = configs['CONFIG_SYNC_SPOTLIGHT_NEW_THREAD'] ? JSON.parse(configs['CONFIG_SYNC_SPOTLIGHT_NEW_THREAD']) : {} - this.currentConfigs.isNbLogScroll = configs['NB_LOG_SCROLL'] === 'true' ? true : false this.currentConfigs.nbLogScrollSpoConfig = configs['CONFIG_NB_LOG_SCROLL'] ? Number(configs['CONFIG_NB_LOG_SCROLL']) : 2000 this.currentConfigs.isShowQuickEditor = configs['SHOW_QUICK_EDITOR'] === 'true' ? true : false this.currentConfigs.sortByConfig = configs['CONFIG_SORT_BY'] ? configs['CONFIG_SORT_BY'] : 'recent' @@ -1277,7 +1277,7 @@ function embedNbApp() { window.removeEventListener('scroll', this.handleScroll) }, onLogNb: async function (event = 'NONE', initiator = 'NONE', spotlightType = 'NONE', isSyncAnnotation = false, hasSyncAnnotation = false, notificationTrigger = 'NONE', annotationId = null, countAnnotationReplies = 0) { - if (this.currentConfigs.isNbLog) { + if (this.currentConfigs.isNbLog && this.currentConfigs.nbLogEventsEnabled.includes(event)) { // console.log(`onLogNb \nevent: ${event} \ninitiator: ${initiator} \nspotlightType: ${spotlightType} \nisSyncAnnotation: ${isSyncAnnotation} \nhasSyncAnnotation: ${hasSyncAnnotation} \nnotificationTrigger: ${notificationTrigger} \nannotationId: ${annotationId} \nannotation_replies_count: ${countAnnotationReplies}`) const token = localStorage.getItem("nb.user"); const config = { headers: { Authorization: 'Bearer ' + token }, params: { url: this.sourceURL } } @@ -1327,7 +1327,7 @@ function embedNbApp() { return new Promise(resolve => setTimeout(resolve, ms)) }, handleScroll: function (e) { - if (this.currentConfigs.isNbLogScroll) { + if (this.currentConfigs.nbLogEventsEnabled.includes('SCROLL')) { clearTimeout(this.scrollLogTimer) this.scrollLogTimer = setTimeout(() => this.onLogNb('SCROLL'), this.currentConfigs.nbLogScrollSpoConfig) } From 684bef230d3b83a41e8f04d0023d011c660da5de Mon Sep 17 00:00:00 2001 From: Jumana Almahmoud Date: Thu, 31 Mar 2022 00:33:21 -0400 Subject: [PATCH 05/41] Improve UI to show if threads are filtered --- public/style/plugin.css | 2 +- src/components/list/ListView.vue | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/public/style/plugin.css b/public/style/plugin.css index a25365a..1980a26 100644 --- a/public/style/plugin.css +++ b/public/style/plugin.css @@ -142,7 +142,7 @@ top: 0; right: 0; line-height: normal; - font-size: 16px; + font-size: 12px; font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; background: #fff; z-index: 99999; diff --git a/src/components/list/ListView.vue b/src/components/list/ListView.vue index 3bdafaa..e2f25fe 100644 --- a/src/components/list/ListView.vue +++ b/src/components/list/ListView.vue @@ -10,7 +10,7 @@
- {{ threads.length }} of {{ totalLabel }} + {{ currentThreadsCount }} of {{ totalLabel }} @@ -146,6 +146,9 @@ export default { this.sortBy = this.currentConfigs.sortByConfig }, computed: { + currentThreadsCount: function () { + return this.threads.length + }, totalLabel: function () { if (this.totalCount === 1) { return '1 thread' @@ -216,3 +219,11 @@ export default { } } + From 202865bf6156d677e5782e2b9ca25af1f85cd1eb Mon Sep 17 00:00:00 2001 From: Jumana Almahmoud Date: Wed, 28 Dec 2022 20:18:33 -0500 Subject: [PATCH 06/41] Add following feature --- app.js | 1 + public/style/plugin.css | 27 ++++++++++- src/app.js | 54 ++++++++++++++++++++-- src/components/NbSidebar.vue | 16 ++++++- src/components/filters/FilterView.vue | 27 +++++++---- src/components/list/ListRow.vue | 39 +++++++++++++++- src/components/list/ListView.vue | 5 +++ src/components/thread/ThreadComment.vue | 60 ++++++++++++++++++++++--- src/components/thread/ThreadView.vue | 15 ++++++- src/models/nbcomment.js | 22 +++++++++ 10 files changed, 244 insertions(+), 22 deletions(-) diff --git a/app.js b/app.js index d31e98c..04b69a8 100644 --- a/app.js +++ b/app.js @@ -12,4 +12,5 @@ app.use(express.static('public')) app.get('/', (req, res) => res.send('Hello World!')) const server = https.createServer({key: key, cert: cert }, app); +console.log(`Running on ${port}`); server.listen(port) \ No newline at end of file diff --git a/public/style/plugin.css b/public/style/plugin.css index 1980a26..6c69093 100644 --- a/public/style/plugin.css +++ b/public/style/plugin.css @@ -416,6 +416,12 @@ font-size: 14px; font-family: 'Verdana', 'Geneva', sans-serif; } +#nb-app .nb-sidebar .list-view .list-row .flags .icon-wrapper.follow { + width: 13px; + color: #4a2270; + margin-left: 4px; + margin-right: 4px; +} #nb-app .nb-sidebar .list-view .list-row .flags .icon-wrapper.question { width: 13px; color: #aaa; @@ -424,6 +430,10 @@ width: 16px; height: 16px; } +#nb-app .nb-sidebar .list-view .list-row .flags .placeholder.follow { + width: 20px; + height: 16px; +} #nb-app .nb-sidebar .list-view .list-row .flags .placeholder.question { width: 16px; height: 16px; @@ -587,14 +597,27 @@ #nb-app .nb-sidebar .thread-view .thread-row .header .author { font-size: 12px; } +#nb-app .nb-sidebar .thread-view .thread-row .header .author .follow-icon { + vertical-align: baseline; + color: #aaa; + cursor: pointer; + font-size: 1.1em; +} + +#nb-app .nb-sidebar .thread-view .thread-row .header .author .unfollow-icon { + vertical-align: baseline; + color: #4a2270; + cursor: pointer; + font-size: 1.1em; +} #nb-app .nb-sidebar .thread-view .thread-row .header .author .instr-icon { display: inline-block; height: 16px; - padding: 0 3px; + padding: 0 6px; border-radius: 3px; background-color: #f4ad3e; color: #fff; - font-size: 11px; + font-size: 14px; vertical-align: bottom; font-family: 'Verdana', 'Geneva', sans-serif; } diff --git a/src/app.js b/src/app.js index 65914c1..6bd2394 100644 --- a/src/app.js +++ b/src/app.js @@ -8,7 +8,7 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' import { library } from '@fortawesome/fontawesome-svg-core' import { fas } from '@fortawesome/free-solid-svg-icons' import { far } from '@fortawesome/free-regular-svg-icons' -import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons' +import { faChevronDown, faChevronUp, faUserCheck, faUserPlus, faCheckSquare } from '@fortawesome/free-solid-svg-icons' import { createNbRange, deserializeNbRange } from './models/nbrange.js' import NbComment from './models/nbcomment.js' import NbNotification from './models/nbnotification.js' @@ -33,7 +33,7 @@ Vue.use(VTooltip) Vue.use(Notifications) Vue.use(VueSweetalert2); Vue.component('font-awesome-icon', FontAwesomeIcon) -library.add(fas, far, faChevronDown, faChevronUp) +library.add(fas, far, faChevronDown, faChevronUp, faUserCheck, faUserPlus, faCheckSquare) const socket = io(currentEnv.baseURL, { reconnect: true }) axios.defaults.baseURL = `${currentEnv.baseURL}/` export const PLUGIN_HOST_URL = currentEnv.pluginURL @@ -214,7 +214,10 @@ function embedNbApp() { :notifications-muted="notificationsMuted" :show-sync-features="showSyncFeatures" :sync-config="syncConfig" + :myfollowing="myfollowing" :filter="filter" + @follow-author="onFollowAuthor" + @unfollow-author="onUnfollowAuthor" @log-nb="onLogNb" @switch-class="onSwitchClass" @show-sync-features="onShowSyncFeatures" @@ -263,6 +266,7 @@ function embedNbApp() { data: { user: null, myClasses: [], + myfollowing: [], activeClass: {}, users: {}, hashtags: {}, @@ -303,6 +307,7 @@ function embedNbApp() { isShowNumberOfReplies: true, isShowIndicatorForUnseenThread: true, isShowIndicatorForInstructorComment: true, + isShowIndicatorForFollowComment: true, isShowIndicatorForSpotlitThread: true, isShowIndicatorForNotifiedThread: false, isShowIndicatorForQuestionedThread: true, @@ -399,6 +404,13 @@ function embedNbApp() { if (filterComments.includes('me') && item.hasUserPost(this.user.id)) { return true } + if(filterComments.includes('following') && item.anonymity != 'ANONYMOUS'){ + for(let i = 0; i < this.myfollowing.length; i++){ + if (item.hasUserPost(this.myfollowing[i].follower_id)){ + return true + } + } + } return false }) } @@ -513,6 +525,12 @@ function embedNbApp() { const config = { headers: { Authorization: 'Bearer ' + token }, params: { url: source } } const myClasses = await axios.get('/api/annotations/myClasses', config) + axios.get(`/api/follow/user`, {headers: { Authorization: 'Bearer ' + token }}).then((res) => { + console.log(`user`); + console.log(res.data); + this.myfollowing = res.data + }) + if (myClasses.data.length > 0) { this.myClasses = myClasses.data if (this.myClasses.length === 1) { @@ -870,7 +888,8 @@ function embedNbApp() { getAllAnnotations: async function (source, newActiveClass) { this.stillGatheringThreads = true const token = localStorage.getItem("nb.user"); - const config = { headers: { Authorization: 'Bearer ' + token }, params: { url: source, class: newActiveClass.id, sectioned: !this.currentConfigs.isIgnoreSectionsInClass } } + const headers = { Authorization: 'Bearer ' + token } + const config = { headers, params: { url: source, class: newActiveClass.id, sectioned: !this.currentConfigs.isIgnoreSectionsInClass } } axios.get('/api/annotations/annotation', config).then(async res => { this.threads = [] @@ -993,6 +1012,13 @@ function embedNbApp() { if (filters.includes('me') && this.threadSelected.hasUserPost(this.user.id)) { filtered = false } + if (filters.includes('following') && this.threadSelected.anonymity != 'ANONYMOUS'){ + for(let i = 0; i < this.myfollowing.length; i++){ + if (this.threadSelected.hasUserPost(this.myfollowing[i].follower_id)){ + filtered = false + } + } + } if (filtered) { this.threadSelected = null // reset selection if filtered } @@ -1228,6 +1254,28 @@ function embedNbApp() { onOpenSidebarNotifications: function () { this.sidebarNotificationsOpened = true; }, + onFollowAuthor: async function(comment){ + const token = localStorage.getItem("nb.user"); + const headers = { headers: { Authorization: 'Bearer ' + token }} + try { + const res = await axios.get(`/api/users/user/${comment.author}`, headers) + const res2 = await axios.post(`/api/follow/user`, {username: res.data.username}, headers) + this.myfollowing = res2.data + } catch(e) { + console.error(e); + } + }, + onUnfollowAuthor: async function(comment){ + const token = localStorage.getItem("nb.user"); + const headers = { headers: { Authorization: 'Bearer ' + token }} + try { + const res = await axios.get(`/api/users/user/${comment.author}`, headers) + const res2 = await axios.delete(`/api/follow/user`, {headers: { Authorization: 'Bearer ' + token }, data: {username: res.data.username}}) + this.myfollowing = res2.data + } catch(e) { + console.error(e); + } + }, onSessionEnd: async function () { if (this.activeClass.id) { this.onLogNb('SESSION_END') diff --git a/src/components/NbSidebar.vue b/src/components/NbSidebar.vue index 565d210..4873872 100644 --- a/src/components/NbSidebar.vue +++ b/src/components/NbSidebar.vue @@ -54,6 +54,7 @@ :activeClass="activeClass" :user="user" :show-sync-features="showSyncFeatures" + :myfollowing="myfollowing" :filter="filter" @log-nb="onLogNb" @toggle-highlights="onToggleHighlights" @@ -90,13 +91,16 @@ :current-configs="currentConfigs" :activeClass="activeClass" :thread-view-initiator="threadViewInitiator" + :myfollowing="myfollowing" @log-nb="onLogNb" @edit-comment="onEditComment" @delete-comment="onDeleteComment" @draft-reply="onDraftReply" @submit-small-comment="onSubmitSmallComment" @prev-comment="onPrevComment" - @next-comment="onNextComment"> + @next-comment="onNextComment" + @follow-author="followAuthor" + @unfollow-author="unfollowAuthor"> [] + }, filter: { type: Object, default: () => {} @@ -380,6 +388,12 @@ export default { onUnhoverThread: function (thread) { this.$emit('unhover-thread', thread) }, + followAuthor: function(comment){ + this.$emit('follow-author', comment) + }, + unfollowAuthor: function(comment){ + this.$emit('unfollow-author', comment) + }, onEditComment: function (comment) { if (this.draftRange || this.replyToComment) { alert("You're already working on another comment. Please save or cancel it first.") diff --git a/src/components/filters/FilterView.vue b/src/components/filters/FilterView.vue index 5bf22f8..2e6b73f 100644 --- a/src/components/filters/FilterView.vue +++ b/src/components/filters/FilterView.vue @@ -99,23 +99,34 @@
-
-
+
+ +
diff --git a/src/components/list/ListRow.vue b/src/components/list/ListRow.vue index 767e029..63bd9d0 100644 --- a/src/components/list/ListRow.vue +++ b/src/components/list/ListRow.vue @@ -1,6 +1,7 @@ @@ -77,6 +80,10 @@ export default { }, activeClass: Object, threadViewInitiator: String, + myfollowing:{ + type: Object, + default: () => [] + } }, computed: { numComments: function () { @@ -120,6 +127,12 @@ export default { onNextComment: function () { this.$emit('next-comment') }, + followAuthor: function(comment){ + this.$emit('follow-author', comment) + }, + unfollowAuthor: function(comment){ + this.$emit('unfollow-author', comment) + }, onLogNb: async function (event='NONE', initiator='NONE', spotlightType='NONE', isSyncAnnotation=false, hasSyncAnnotation=false, notificationTrigger='NONE', annotationId=null, countAnnotationReplies=0) { this.$emit('log-nb', event, initiator, spotlightType, isSyncAnnotation, hasSyncAnnotation, notificationTrigger, annotationId, countAnnotationReplies) } diff --git a/src/models/nbcomment.js b/src/models/nbcomment.js index cc0662c..85765c6 100644 --- a/src/models/nbcomment.js +++ b/src/models/nbcomment.js @@ -138,6 +138,13 @@ class NbComment { */ this.anonymity = data.anonymity + /** + * Flag for followed user. True if the current user's follows author of this comment. + * @name NbComment#followed + * @type Boolean + */ + this.followed = data.followed + /** * Flag for the current user's reply request. * True if the current user requested reply for this comment. @@ -565,6 +572,17 @@ class NbComment { return false } + isFollowed() { + if (!this.followed) { return true } + + for (let child of this.children) { + if (child.isFollowed()) { + return true + } + } + return false + } + /** * Check recursively if this comment (or descendant) hasn't been seen by the current user. * @return {Boolean} True if this comment (or descendant) hasn't been seen by the current user @@ -695,6 +713,10 @@ class NbComment { * Toggle the upvote for this comment by the current user. */ toggleUpvote(threadViewInitiator = 'NONE', thread = {}, activeClass = {}, user = {}, onLogNb = () => { }) { + if(!this.upvoteCount){ + this.upvoteCount = 0 + } + if (this.upvotedByMe) { this.upvoteCount -= 1 this.upvotedByMe = false From 700a8485a488683a477896796d2c363670152169 Mon Sep 17 00:00:00 2001 From: Jumana Almahmoud Date: Thu, 29 Dec 2022 23:58:45 -0500 Subject: [PATCH 07/41] Add instructor endorsement --- public/style/plugin.css | 72 +++++++++++++++++++++++++ src/app.js | 4 +- src/components/filters/FilterView.vue | 2 +- src/components/list/ListRow.vue | 7 +-- src/components/thread/ThreadComment.vue | 6 +++ src/models/nbcomment.js | 60 +++++++++++++++++++++ 6 files changed, 145 insertions(+), 6 deletions(-) diff --git a/public/style/plugin.css b/public/style/plugin.css index 6c69093..feda79d 100644 --- a/public/style/plugin.css +++ b/public/style/plugin.css @@ -416,6 +416,16 @@ font-size: 14px; font-family: 'Verdana', 'Geneva', sans-serif; } +#nb-app .nb-sidebar .list-view .list-row .flags .icon-wrapper.instr-endorsed { + color: #f4ad3e; + background-color: #fff; + border-color: #f4ad3e; + font-size: 12px; + font-family: 'Verdana', 'Geneva', sans-serif; + border: 2px solid; + width: 12px; + height: 12px; +} #nb-app .nb-sidebar .list-view .list-row .flags .icon-wrapper.follow { width: 13px; color: #4a2270; @@ -568,6 +578,8 @@ padding: 0 0 10px 0; color: #444; border-bottom: 1px solid #666; + user-select: none; + cursor: default; } #nb-app .nb-sidebar .thread-view .thread-header-arrows { @@ -620,6 +632,8 @@ font-size: 14px; vertical-align: bottom; font-family: 'Verdana', 'Geneva', sans-serif; + user-select: none; + cursor: default; } #nb-app .nb-sidebar .thread-view .thread-row .smallCommentHeader .author .instr-icon { display: inline-block; @@ -637,14 +651,54 @@ font-size: 10px; color: #444; cursor: default; + user-select: none; + } #nb-app .nb-sidebar .thread-view .thread-row .header .options { position: absolute; right: 0; top: 0; + display: flex; +} +#nb-app .nb-sidebar .thread-view .thread-row .header .options .icon-wrapper.instr { + display: flex; + align-items: center; + justify-content: center; + width: 10px; + height: 16px; + border-radius: 3px; + background-color: #f4ad3e; + color: #fff; + font-size: 14px; + font-family: 'Verdana', 'Geneva', sans-serif; + padding: 0 3px; + margin-right: 3px; + margin-top: 3px; + user-select: none; + cursor: default; +} +#nb-app .nb-sidebar .thread-view .thread-row .header .options .icon-wrapper.instr-endorsed { + display: flex; + align-items: center; + justify-content: center; + width: 8px; + height: 14px; + border-radius: 3px; + background-color: #fff; + color: #f4ad3e; + font-size: 14px; + font-family: 'Verdana', 'Geneva', sans-serif; + padding: 0 3px; + margin-right: 3px; + margin-top: 0px; + border-color: #f4ad3e; + border: 2px solid; + user-select: none; + cursor: default; } #nb-app .nb-sidebar .thread-view .thread-row .header .options .bookmark { cursor: pointer; + margin: 1px 10px 0 5px; } #nb-app .nb-sidebar .thread-view .thread-row .header .options .bookmark .icon { height: 16px; @@ -659,6 +713,22 @@ #nb-app .nb-sidebar .thread-view .thread-row .header .options .bookmark:hover { color: #70a0f0; } +#nb-app .nb-sidebar .thread-view .thread-row .header .options .endorsed .check-square { + margin-right: 10px; +} +#nb-app .nb-sidebar .thread-view .thread-row .header .options .endorsed .check-square .icon { + height: 16px; + vertical-align: middle; +} +#nb-app .nb-sidebar .thread-view .thread-row .header .options .endorsed .check-square .fas { + color: #70a0f0; +} +#nb-app .nb-sidebar .thread-view .thread-row .header .options .endorsed .check-square .far { + color: #444; +} +#nb-app .nb-sidebar .thread-view .thread-row .header .options .endorsed .check-square:hover { + color: #70a0f0; +} #nb-app .nb-sidebar .thread-view .thread-row .header .overflow-menu { margin: 0 3px 0 6px; vertical-align: middle; @@ -676,6 +746,7 @@ font-size: 14px; color: #444; margin-top: 8px; + user-select: none; } #nb-app .nb-sidebar .thread-view .thread-row .footer > span { cursor: pointer; @@ -730,6 +801,7 @@ display: flex; padding: 5px 0; font-size: 12px; + align-items: center; } #nb-app .editor-view .footer .checkbox-buttons .buttons { margin-left: auto; diff --git a/src/app.js b/src/app.js index 6bd2394..78733a7 100644 --- a/src/app.js +++ b/src/app.js @@ -398,7 +398,7 @@ function embedNbApp() { let filterComments = this.filter.comments if (filterComments.length > 0) { items = items.filter(item => { - if (filterComments.includes('instructor') && item.hasInstructorPost()) { + if (filterComments.includes('instructor') && item.hasInstructorPost() || item.isEndorsed()) { return true } if (filterComments.includes('me') && item.hasUserPost(this.user.id)) { @@ -1006,7 +1006,7 @@ function embedNbApp() { onFilterComments: function (filters) { if (this.threadSelected && filters.length > 0) { let filtered = true - if (filters.includes('instructor') && this.threadSelected.hasInstructorPost()) { + if (filters.includes('instructor') && this.threadSelected.hasInstructorPost() || this.threadSelected.isEndorsed()) { filtered = false } if (filters.includes('me') && this.threadSelected.hasUserPost(this.user.id)) { diff --git a/src/components/filters/FilterView.vue b/src/components/filters/FilterView.vue index 2e6b73f..ee9c9b6 100644 --- a/src/components/filters/FilterView.vue +++ b/src/components/filters/FilterView.vue @@ -126,7 +126,7 @@ v-model="filterComments" @change="onFilterChange('comments')">
diff --git a/src/components/list/ListRow.vue b/src/components/list/ListRow.vue index 63bd9d0..4196518 100644 --- a/src/components/list/ListRow.vue +++ b/src/components/list/ListRow.vue @@ -27,9 +27,10 @@
-
+
+ i +
+
i
diff --git a/src/components/thread/ThreadComment.vue b/src/components/thread/ThreadComment.vue index f76c469..327d101 100644 --- a/src/components/thread/ThreadComment.vue +++ b/src/components/thread/ThreadComment.vue @@ -25,6 +25,9 @@
{{ timeString }}
+
+ i +
{ }) { const headComment = this.getHeadComment(comment) onLogNb(event, threadViewInitiator, headComment.spotlight ? headComment.spotlight.type.toUpperCase() : 'NONE', comment.isSync, headComment.hasSync, headComment.associatedNotification ? headComment.associatedNotification.trigger : 'NONE', headComment.id, headComment.countAllReplies()) @@ -813,6 +871,7 @@ class NbComment { this.people = data.mentions.users this.visibility = data.visibility this.anonymity = data.anonymity + this.endorsed = data.endorsed if (this.replyRequestedByMe !== data.replyRequested) { this.replyRequestedByMe = data.replyRequested this.replyRequestCount += data.replyRequested ? 1 : -1 @@ -826,6 +885,7 @@ class NbComment { userTags: this.people, visibility: CommentVisibility[this.visibility], anonymity: CommentAnonymity[this.anonymity], + endorsed: this.endorsed, replyRequest: this.replyRequestedByMe }, headers) } From 31435c0a97444bb5a2e227982f8e903e41fb4b68 Mon Sep 17 00:00:00 2001 From: Jumana Almahmoud Date: Mon, 2 Jan 2023 22:10:53 -0500 Subject: [PATCH 08/41] Improve DB logging and UI/UX --- public/style/plugin.css | 144 +++++++++----- src/app.js | 176 ++++++++++++++---- src/components/NavBar.vue | 2 +- src/components/NbOnline.vue | 4 +- src/components/NbSidebar.vue | 55 +++--- src/components/highlights/NbHighlight.vue | 31 +-- src/components/highlights/NbHighlights.vue | 9 +- src/components/list/ListRow.vue | 24 +-- src/components/list/ListView.vue | 90 ++++++++- src/components/list/NotificationView.vue | 2 +- .../innotations/NbInnotationBlock.vue | 2 +- .../innotations/NbInnotationInline.vue | 2 +- .../spotlights/innotations/NbInnotations.vue | 8 +- .../spotlights/marginalias/NbMarginalia.vue | 2 +- .../spotlights/marginalias/NbMarginalias.vue | 8 +- src/components/thread/ThreadComment.vue | 6 +- src/components/thread/ThreadView.vue | 4 +- src/models/nbcomment.js | 13 +- src/models/nbnotification.js | 4 +- 19 files changed, 416 insertions(+), 170 deletions(-) diff --git a/public/style/plugin.css b/public/style/plugin.css index feda79d..039134f 100644 --- a/public/style/plugin.css +++ b/public/style/plugin.css @@ -105,9 +105,21 @@ background-color: #0069d9; } -#nb-app .nb-sync{ +#nb-app .nb-sync { display: flex; justify-content: space-between; + user-select: none; + padding: 4px 10px; + border: 1px solid #ccc; + align-items: center; + background: #eeee; +} +#nb-app .nb-sync > div > span { + display: flex; + align-items: center; + justify-content: space-evenly; + height: 100%; + width: 50px; } #nb-app .nb-online-left-parent{ @@ -187,6 +199,10 @@ justify-content: space-between; background: #eee; font-size: 13px; + user-select: none; +} +#nb-app .nb-sidebar .nb-nav-bar span { + font-size: 12px; } #nb-app .nb-sidebar .nb-nav-bar .overflow-menu { display: inline-block; @@ -337,26 +353,40 @@ #nb-app .nb-sidebar .list-view { margin-bottom: 10px; /* margin-top: 10px; */ + border: 1px solid #ccc; + padding: 2px; + user-select: none; } #nb-app .nb-sidebar .list-view .list-header { display: flex; align-items: center; - margin-bottom: 2px; + height: 30px; color: #444; } #nb-app .nb-sidebar .list-view .list-header .count { margin-right: 5px; - font-size: 13px; + font-size: 12px; } #nb-app .nb-sidebar .list-view .list-header .toggle-highlights { color: #444; cursor: pointer; + margin: 0 2px; } #nb-app .nb-sidebar .list-view .list-header .toggle-highlights .icon { vertical-align: bottom; transform: initial; font-size: initial; } +#nb-app .nb-sidebar .list-view .list-header .toggle-spotlights { + color: #444; + cursor: pointer; + margin: 0 2px; +} +#nb-app .nb-sidebar .list-view .list-header .toggle-spotlights .icon { + vertical-align: bottom; + transform: initial; + font-size: initial; +} #nb-app .icon{ transform: initial; font-size: initial; @@ -366,7 +396,7 @@ font-size: initial; } #nb-app .nb-sidebar .list-view .list-header .sort { - font-size: 13px; + font-size: 11px; margin-left: auto; } #nb-app .nb-sidebar .list-view .list-table { @@ -383,6 +413,7 @@ overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + align-items: center; } #nb-app .nb-sidebar .list-view .list-row #notification-row-flags{ /*only for notification row style*/ justify-content: flex-start; @@ -391,19 +422,24 @@ background-color: #f0f0f0; } #nb-app .nb-sidebar .list-view .list-row .flags { - /* width: 60px; */ display: flex; align-items: center; justify-content: space-between; margin-right: 5px; + border-right: 1px dashed #ddd; + padding-right: 4px; } +#nb-app .nb-sidebar .list-view .list-row .flags .placeholder, #nb-app .nb-sidebar .list-view .list-row .flags .icon-wrapper { display: flex; align-items: center; justify-content: center; - width: 16px; - height: 16px; - border-radius: 3px; + width: 15px; + height: 15px; + border: 1px solid #eee; + border-radius: 4px; + background: #fff; + margin-right: 1px; } #nb-app .nb-sidebar .list-view .list-row .flags .icon-wrapper.counter { background-color: #eee; @@ -423,56 +459,50 @@ font-size: 12px; font-family: 'Verdana', 'Geneva', sans-serif; border: 2px solid; - width: 12px; - height: 12px; -} -#nb-app .nb-sidebar .list-view .list-row .flags .icon-wrapper.follow { - width: 13px; - color: #4a2270; - margin-left: 4px; - margin-right: 4px; -} -#nb-app .nb-sidebar .list-view .list-row .flags .icon-wrapper.question { - width: 13px; - color: #aaa; + width: 14px; + height: 14px; } + #nb-app .nb-sidebar .list-view .list-row .flags .placeholder.instr { width: 16px; height: 16px; } -#nb-app .nb-sidebar .list-view .list-row .flags .placeholder.follow { - width: 20px; - height: 16px; +#nb-app .nb-sidebar .list-view .list-row .flags svg { + width: 12px; + height: 12px; } -#nb-app .nb-sidebar .list-view .list-row .flags .placeholder.question { - width: 16px; - height: 16px; +#nb-app .nb-sidebar .list-view .list-row .flags .icon-wrapper.question { + color: #eee; + background-color: purple; +} +#nb-app .nb-sidebar .list-view .list-row .flags .icon-wrapper.question svg { + width: 11px; + height: 11px; } - #nb-app .nb-sidebar .list-view .list-row .flags .icon-wrapper.inno { background-color: #4a2270; color: #fff; - font-size: 14px; + font-size: 12px; font-family: 'Verdana', 'Geneva', sans-serif; + align-items: center; } #nb-app .nb-sidebar .list-view .list-row .flags .icon-wrapper.notification { background-color: #b096ee; color: #fff; - font-size: 14px; - font-family: 'Verdana', 'Geneva', sans-serif; } -#nb-app .nb-sidebar .list-view .list-row .flags .placeholder.inno { - width: 16px; - height: 16px; +#nb-app .nb-sidebar .list-view .list-row .flags .icon-wrapper.notification svg { + width: 11px; + height: 11px; +} +#nb-app .nb-sidebar .list-view .list-row .flags .icon-wrapper.follow { + background-color: #fff; + color: #4a2270; } - #nb-app .nb-spotlight-control { display: inline-flex; vertical-align: middle; } - - #nb-app .nb-spotlight-control span { background-color: #a67cce; color: #fff; @@ -487,16 +517,10 @@ margin: 0 2px; cursor: pointer; } - #nb-app .nb-spotlight-control span.active { background-color: #4a2270; cursor: not-allowed; } - -#nb-app .nb-sidebar .list-view .list-row .flags .placeholder.notification { - width: 16px; - height: 16px; -} #nb-app .nb-notification-sidebar .list-view .list-row.timestamp { font-size: 13px; color: #444; @@ -567,7 +591,6 @@ height: 120px; border: solid 1px #ddd; overflow-y: scroll; - margin-left: 5px; } #nb-app .nb-sidebar .thread-view { min-height: 100px; @@ -1336,11 +1359,14 @@ nb-innotation-controller { #nb-app .card-header { display: flex; align-items: center; - padding: 0 15px 0 5px; + padding: 0 5px; background: #efefef; border: solid 1px #aaa; cursor: pointer; - height: 30px; + height: 20px; + margin: -3px -3px 0 -3px; + font-size: 12px; + font-weight: bold; } #nb-app .card-header-title { @@ -1348,7 +1374,8 @@ nb-innotation-controller { } #nb-app .collapse-button { - margin-left: auto + margin-left: auto; + color: #444; } .icons-left-parent { @@ -1422,3 +1449,28 @@ nb-innotation-controller { background: #f0f3f4; border: 1px #d5d8d8 solid; } + +/* ===== Scrollbar CSS ===== */ +/* Firefox */ +* { + scrollbar-width: thin; + scrollbar-color: #333 #c9c9c9; +} + +/* Chrome, Edge, and Safari */ +*::-webkit-scrollbar { + width: 5px; +} + +*::-webkit-scrollbar-track { + background: #c9c9c9; +} + +*::-webkit-scrollbar-thumb { + background-color:#999; + border-radius: 1px; + border: 0px none #ffffff; +} +.swal2-container { + z-index: 100000; +} diff --git a/src/app.js b/src/app.js index 78733a7..4985779 100644 --- a/src/app.js +++ b/src/app.js @@ -120,7 +120,7 @@ function embedNbApp() { v-if="currentConfigs.isInnotation" :innotationsBlock="innotationsBlock" :innotationsInline="innotationsInline" - :show-highlights="showHighlights" + :show-spotlights="showSpotlights" :thread-selected="threadSelected" :user="user" :activeClass="activeClass" @@ -133,7 +133,7 @@ function embedNbApp() { { - console.log(`user`); - console.log(res.data); this.myfollowing = res.data }) @@ -560,10 +566,13 @@ function embedNbApp() { const configs = req.data this.nbConfigs = configs + localStorage.setItem("nbc.current.class", JSON.stringify({class: newActiveClass.id, url: this.sourceURL})) + this.currentConfigs.isEmphasize = configs['SPOTLIGHT_EM'] === 'true' ? true : false this.currentConfigs.isShowNumberOfReplies = configs['SHOW_NUMBER_OF_REPLIES'] === 'false' ? false : true this.currentConfigs.isShowIndicatorForUnseenThread = configs['SHOW_INDICATOR_FOR_UNSEEN_THREAD'] === 'false' ? false : true this.currentConfigs.isShowIndicatorForInstructorComment = configs['SHOW_INDICATOR_FOR_INSTRUCTOR_COMMENT'] === 'false' ? false : true + this.currentConfigs.isShowIndicatorForFollowComment = configs['SHOW_INDICATOR_FOR_FOLLOW_COMMENT'] === 'false' ? false : true this.currentConfigs.isShowIndicatorForSpotlitThread = configs['SHOW_INDICATOR_FOR_SPOTLIT_THREAD'] === 'false' ? false : true this.currentConfigs.isShowIndicatorForNotifiedThread = configs['SHOW_INDICATOR_FOR_NOTIFIED_THREAD'] === 'false' ? false : true this.currentConfigs.isShowIndicatorForQuestionedThread = configs['SHOW_INDICATOR_FOR_QUESTIONED_THREAD'] === 'false' ? false : true @@ -582,6 +591,10 @@ function embedNbApp() { this.currentConfigs.isShowSpotlightControls = configs['SHOW_SPOTLIGHT_CONTROLS'] === 'false' ? false : true this.currentConfigs.syncNotificationPopupTimerConfig = configs['CONFIG_SYNC_NOTIFICATION_POPUP_TIMER'] ? configs['CONFIG_SYNC_NOTIFICATION_POPUP_TIMER'] : 60000 this.currentConfigs.isCommentMediaAudio = configs['COMMENT_MEDIA_AUDIO_STUDENT'] === 'true' ? true : false + this.currentConfigs.isSpotlightFollowThread = configs['SPOTLIGHT_FOLLOW_THREAD'] === 'true' ? true : false + this.currentConfigs.spotlightFollowThreadConfig = configs['CONFIG_SPOTLIGHT_FOLLOW_THREAD'] ? JSON.parse(configs['CONFIG_SPOTLIGHT_FOLLOW_THREAD']) : {} + this.currentConfigs.isSpotlightEndorsThread = configs['SPOTLIGHT_ENDORS_THREAD'] === 'true' ? true : false + this.currentConfigs.spotlightEndorsThreadConfig = configs['CONFIG_SPOTLIGHT_ENDORS_THREAD'] ? JSON.parse(configs['CONFIG_SPOTLIGHT_ENDORS_THREAD']) : {} if (document.location.href.includes('/nb_viewer.html')) { this.currentConfigs.isMarginalia = configs['SPOTLIGHT_MARGIN'] === 'true' ? true : false @@ -593,6 +606,15 @@ function embedNbApp() { this.currentConfigs.syncSpotlightNewThreadConfig.type = 'MARGIN' } + if (this.currentConfigs.spotlightFollowThreadConfig.type && ['IN', 'ABOVE', 'BELLOW', 'LEFT', 'RIGHT'].includes(this.currentConfigs.spotlightFollowThreadConfig.type)) { + this.currentConfigs.spotlightFollowThreadConfig.type = 'MARGIN' + } + + if (this.currentConfigs.spotlightEndorsThreadConfig.type && ['IN', 'ABOVE', 'BELLOW', 'LEFT', 'RIGHT'].includes(this.currentConfigs.spotlightEndorsThreadConfig.type)) { + this.currentConfigs.spotlightEndorsThreadConfig.type = 'MARGIN' + } + + } else { this.currentConfigs.isMarginalia = false this.currentConfigs.isInnotation = configs['SPOTLIGHT_INNOTATION'] === 'true' ? true : false @@ -603,6 +625,14 @@ function embedNbApp() { this.currentConfigs.syncSpotlightNewThreadConfig.type = 'LEFT' } + if (this.currentConfigs.spotlightFollowThreadConfig.type && ['MARGIN'].includes(this.currentConfigs.spotlightFollowThreadConfig.type)) { + this.currentConfigs.spotlightFollowThreadConfig.type = 'LEFT' + } + + if (this.currentConfigs.spotlightEndorsThreadConfig.type && ['MARGIN'].includes(this.currentConfigs.spotlightEndorsThreadConfig.type)) { + this.currentConfigs.spotlightEndorsThreadConfig.type = 'LEFT' + } + } if (this.currentConfigs.isFilterMaxThreads) { @@ -665,7 +695,6 @@ function embedNbApp() { socket.on('connections', (data) => { console.log(`NB: Socket.IO connections`) - // console.log(data); let isInitConnection = this.onlineUsers.ids.length === 0 this.onlineUsers = data.users @@ -694,7 +723,6 @@ function embedNbApp() { socket.on("new_thread", (data) => { console.log(`NB: Socket.IO new_thread`) - // console.log(data); let userIdsSet = new Set(data.userIds) if (data.authorId !== this.user.id && userIdsSet.has(this.user.id)) { // find if we are one of the target audiences w/ visibility + section permissions for this new_thread if current user, we already added new thread to their list if (this.activeClass && this.activeClass.id == data.classId && this.sourceURL === data.sourceUrl) { @@ -704,8 +732,6 @@ function embedNbApp() { }) socket.on('thread-typing', (data) => { - // console.log("***typing***"); - // console.log(data); let thread = this.threads.find(x => x.id === data.threadId) if (thread !== undefined) { thread.usersTyping = data.usersTyping @@ -714,7 +740,6 @@ function embedNbApp() { socket.on('new_reply', (data) => { console.log(`NB: Socket.IO new_reply`) - // console.log(data); if (data.authorId !== this.user.id) { // if current user, we already added new reply to their list if (this.activeClass && this.activeClass.id == data.classId && this.sourceURL === data.sourceUrl) { const canISeeIt = this.threads.filter(t => t.id === data.headAnnotationId).length > 0 @@ -724,6 +749,17 @@ function embedNbApp() { } } }) + + socket.on('update_thread', (data) => { + console.log(`NB: Socket.IO update_thread`) + if (this.activeClass && this.activeClass.id == data.classId && this.sourceURL === data.sourceUrl) { + const canISeeIt = this.threads.filter(t => t.id === data.headAnnotationId).length > 0 + if (canISeeIt) { + this.getSingleThread(data.sourceUrl, data.classId, data.threadId, data.authorId, data.taggedUsers, false, null, data.headAnnotationId, true) + } + } + }) + }, destroyed: function () { window.removeEventListener('scroll', this.handleScroll) @@ -756,7 +792,15 @@ function embedNbApp() { onUserLeft: function () { socket.emit('left', { username: this.user.username, classId: this.activeClass.id, sectionId: this.currentSectionId, sourceURL: this.sourceURL }) }, - getSingleThread: function (sourceUrl, classId, threadId, authorId, taggedUsers, isNewThread, replyAnnotationId = null, oldHeadAnnotationId = null) { // get single thread and add it to the list + iFollowThisUser: function(userId) { + for(let i = 0; i < this.myfollowing.length; i++){ + if (this.myfollowing[i].follower_id === userId){ + return true + } + } + return false + }, + getSingleThread: function (sourceUrl, classId, threadId, authorId, taggedUsers, isNewThread, replyAnnotationId = null, oldHeadAnnotationId = null, isUpdatedComment = false) { // get single thread and add it to the list const token = localStorage.getItem("nb.user"); const config = { headers: { Authorization: 'Bearer ' + token }, params: { source_url: sourceUrl, class_id: classId, thread_id: threadId } } axios.get('/api/annotations/specific_thread', config) @@ -780,6 +824,16 @@ function embedNbApp() { comment.spotlight = this.currentConfigs.syncSpotlightNewThreadConfig } + // if spotlight endorsed thread + if (this.currentConfigs.isSpotlightEndorsThread && isUpdatedComment && comment.endorsed) { + comment.spotlight = this.currentConfigs.spotlightEndorsThreadConfig + } + + // if spotlight follow thread + if (isNewThread && this.currentConfigs.isSpotlightFollowThread && this.iFollowThisUser(authorId)) { + comment.spotlight = this.currentConfigs.spotlightFollowThreadConfig + } + // get the specific annotation that was recently posted let specificAnnotation = null if (replyAnnotationId !== null) { @@ -797,16 +851,25 @@ function embedNbApp() { // set any type of notification let notification = null - if (taggedUsers.includes(this.user.id)) { // user tagged in post - notification = new NbNotification(comment, "tag", true, specificAnnotation, false) - } else if ((isNewThread && comment.hasReplyRequests()) || (specificAnnotation !== null && specificAnnotation.hasReplyRequests())) { // new thread with reply request or the reply had a reply request - notification = new NbNotification(comment, "question", true, specificAnnotation, false) - } else if (specificAnnotation && specificAnnotation.parent && specificAnnotation.parent.author === this.user.id) { // if this new comment is a reply to the user - notification = new NbNotification(comment, "reply", true, specificAnnotation, false) - } else if (this.users[authorId].role === "instructor") { // instructor comment - notification = new NbNotification(comment, "instructor", true, specificAnnotation, false) - } else if (this.user.role === 'instructor' && isNewThread) { // instructors will get all new threads and posts - notification = new NbNotification(comment, "recent", true, specificAnnotation, false) + + if (!isUpdatedComment) { + if (taggedUsers && taggedUsers.includes(this.user.id)) { // user tagged in post + notification = new NbNotification(comment, "tag", true, specificAnnotation, false) + } else if ((isNewThread && comment.hasReplyRequests()) || (specificAnnotation !== null && specificAnnotation.hasReplyRequests())) { // new thread with reply request or the reply had a reply request + notification = new NbNotification(comment, "question", true, specificAnnotation, false) + } else if (specificAnnotation && specificAnnotation.parent && specificAnnotation.parent.author === this.user.id) { // if this new comment is a reply to the user + notification = new NbNotification(comment, "reply", true, specificAnnotation, false) + } else if (this.iFollowThisUser(authorId)) { // if this new comment of author i follow + notification = new NbNotification(comment, "follow", true, specificAnnotation, false) + } else if (replyAnnotationId && this.users[authorId].role === "instructor") { // instructor comment + notification = new NbNotification(comment, "instructor", true, specificAnnotation, false) + } else if (this.user.role === 'instructor' && isNewThread) { // instructors will get all new threads and posts + notification = new NbNotification(comment, "recent", true, specificAnnotation, false) + } + } else { + if (comment.endorsed && this.user.role !== 'instructor') { // instructor endorsed + notification = new NbNotification(comment, "endorsed", true, specificAnnotation, false) + } } if (notification !== null) { @@ -823,7 +886,7 @@ function embedNbApp() { this.threads.push(comment) let syncLogEvent = isNewThread ? 'SYNC_RECEIVED_ANNOTATION' : 'SYNC_RECEIVED_REPLY' - this.onLogNb(syncLogEvent, 'NONE', 'NONE', true, true, comment.associatedNotification ? comment.associatedNotification.trigger : 'NONE', replyAnnotationId || comment.id, comment.countAllReplies()) + this.onLogNb(syncLogEvent, 'NONE', 'NONE', comment) }) }, triggerPopupNotification: function (notification) { @@ -833,10 +896,10 @@ function embedNbApp() { if (relevantComment.text.length > 20) { text += "..." } - this.$swal({ + this.$swal.fire({ title: '', text: notification.readableType + ": " + text, - type: 'success', + icon: 'info', showCancelButton: true, confirmButtonColor: '#3085d6', cancelButtonColor: '#d33', @@ -845,7 +908,10 @@ function embedNbApp() { position: 'top-start', timer: this.currentConfigs.syncNotificationPopupTimerConfig, }).then((result) => { + console.log('after swal'); + console.log(result); if (result.value) { + console.log('here'); this.swalClicked = true this.onSelectNotification(notification) } @@ -907,6 +973,21 @@ function embedNbApp() { let comment = new NbComment(item, res.data.annotationsData) this.threads.push(comment) + // check if i follow this user's comments + if (this.iFollowThisUser(comment.author)) { + comment.followed = true + } + + // if spotlight endorsed thread + if (this.currentConfigs.isSpotlightEndorsThread && comment.endorsed) { + comment.spotlight = this.currentConfigs.spotlightEndorsThreadConfig + } + + // if spotlight follow thread + if (this.currentConfigs.isSpotlightFollowThread && comment.followed) { + comment.spotlight = this.currentConfigs.spotlightFollowThreadConfig + } + // TODO: check this code let offlineNotification = this.newOfflineNotification(comment) // Either get back a notification to add or null if (offlineNotification !== null) { @@ -934,7 +1015,12 @@ function embedNbApp() { }) }, draftThread: function (range) { + this.draftRange = null if (this.user) { // only if selection was after user log in + if (this.threadSelected) { + socket.emit('thread-stop-typing', { threadId: this.threadSelected.id, username: this.user.username }) // drafting new thread so stop typing on this thread + this.threadSelected = null + } this.draftRange = createNbRange(range) } }, @@ -1111,6 +1197,7 @@ function embedNbApp() { if (thread.associatedNotification !== null) { thread.associatedNotification.setIsUnseen(false) } + console.log(thread); this.threadSelected = thread thread.markSeenAll() }, @@ -1188,6 +1275,9 @@ function embedNbApp() { onToggleHighlights: function (show) { this.showHighlights = show }, + onToggleSpotlights: function (show) { + this.showSpotlights = show + }, handleRedrawHighlights: function () { // if (this.canRedrawHighlightsTimeout) { // clearTimeout(this.canRedrawHighlightsTimeout) @@ -1252,15 +1342,16 @@ function embedNbApp() { this.sidebarNotificationsOpened = false; }, onOpenSidebarNotifications: function () { - this.sidebarNotificationsOpened = true; + this.sidebarNotificationsOpened = !this.sidebarNotificationsOpened; }, onFollowAuthor: async function(comment){ const token = localStorage.getItem("nb.user"); const headers = { headers: { Authorization: 'Bearer ' + token }} try { - const res = await axios.get(`/api/users/user/${comment.author}`, headers) - const res2 = await axios.post(`/api/follow/user`, {username: res.data.username}, headers) - this.myfollowing = res2.data + const author = await axios.get(`/api/users/user/${comment.author}`, headers) + const following = await axios.post(`/api/follow/user`, {username: author.data.username}, headers) + this.myfollowing = following.data + this.onLogNb('FOLLOW', this.threadViewInitiator, comment) } catch(e) { console.error(e); } @@ -1269,9 +1360,10 @@ function embedNbApp() { const token = localStorage.getItem("nb.user"); const headers = { headers: { Authorization: 'Bearer ' + token }} try { - const res = await axios.get(`/api/users/user/${comment.author}`, headers) - const res2 = await axios.delete(`/api/follow/user`, {headers: { Authorization: 'Bearer ' + token }, data: {username: res.data.username}}) - this.myfollowing = res2.data + const author = await axios.get(`/api/users/user/${comment.author}`, headers) + const following = await axios.delete(`/api/follow/user`, {headers: { Authorization: 'Bearer ' + token }, data: {username: author.data.username}}) + this.myfollowing = following.data + this.onLogNb('UNFOLLOW', this.threadViewInitiator, comment) } catch(e) { console.error(e); } @@ -1324,27 +1416,31 @@ function embedNbApp() { this.showHighlights = true window.removeEventListener('scroll', this.handleScroll) }, - onLogNb: async function (event = 'NONE', initiator = 'NONE', spotlightType = 'NONE', isSyncAnnotation = false, hasSyncAnnotation = false, notificationTrigger = 'NONE', annotationId = null, countAnnotationReplies = 0) { + + onLogNb: async function (event = 'NONE', initiator = 'NONE', comment = undefined) { + if (this.currentConfigs.isNbLog && this.currentConfigs.nbLogEventsEnabled.includes(event)) { - // console.log(`onLogNb \nevent: ${event} \ninitiator: ${initiator} \nspotlightType: ${spotlightType} \nisSyncAnnotation: ${isSyncAnnotation} \nhasSyncAnnotation: ${hasSyncAnnotation} \nnotificationTrigger: ${notificationTrigger} \nannotationId: ${annotationId} \nannotation_replies_count: ${countAnnotationReplies}`) + // console.log(`onLogNb \nevent: ${event} \ninitiator: ${initiator} \ncomment: ${JSON.stringify(comment)}`) + + const headComment = comment ? comment.getHeadComment(comment) : undefined const token = localStorage.getItem("nb.user"); const config = { headers: { Authorization: 'Bearer ' + token }, params: { url: this.sourceURL } } - const pageYOffset = (window.pageYOffset || document.documentElement.scrollTop) - (document.documentElement.clientTop || 0) const pageHeight = (document.documentElement.scrollHeight - document.documentElement.clientHeight) axios.post(`/api/log/nb`, { class_id: this.activeClass.id, - annotation_id: annotationId, + annotation_id: comment ? comment.id : null, + head_annotation_id: headComment ? headComment.id : null, event: event.toUpperCase(), - spotlight_type: spotlightType.toUpperCase(), + spotlight_type: (headComment && headComment.spotlight) ? headComment.spotlight.type.toUpperCase() : 'NONE', order: this.nbLogEventsOrder, initiator: initiator, - is_sync_annotation: isSyncAnnotation, - has_sync_annotation: hasSyncAnnotation, - notification_trigger: notificationTrigger, + is_sync_annotation: comment ? comment.isSync : null, + has_sync_annotation: headComment ? headComment.hasSync : null, + notification_trigger: (headComment && headComment.associatedNotification) ? headComment.associatedNotification.trigger : 'NONE', count_source_annotations: this.threads.length, - count_annotation_replies: countAnnotationReplies, + count_annotation_replies: headComment ? headComment.countAllReplies() : null, count_online_students: this.onlineUsers.students.length, count_online_instructors: this.onlineUsers.instructors.length, page_position: this.calculatePagePosition(pageYOffset, pageHeight).toUpperCase(), @@ -1353,6 +1449,8 @@ function embedNbApp() { role: this.users[this.user.id].role.toUpperCase(), applied_filter: JSON.stringify(this.filter), applied_sort: this.currentConfigs.sortByConfig, + comment_endorsed: headComment ? headComment.isEndorsed() : null, + comment_followed: headComment ? headComment.isFollowed() : null, }, config) this.nbLogEventsOrder = this.nbLogEventsOrder + 1 diff --git a/src/components/NavBar.vue b/src/components/NavBar.vue index 0b2f6be..fa65497 100644 --- a/src/components/NavBar.vue +++ b/src/components/NavBar.vue @@ -1,7 +1,7 @@