feat(comments): various (back + front)
updates `updatedAt` of parent comment on reply; shuffles events; finalises various interactions
This commit is contained in:
@@ -30,7 +30,7 @@ import 'vue2-perfect-scrollbar/dist/vue2-perfect-scrollbar.css'
|
||||
Vue.use(PerfectScrollbar)
|
||||
|
||||
import VTooltip from 'v-tooltip'
|
||||
Vue.use(VTooltip, { defaultDelay: 300 })
|
||||
Vue.use(VTooltip, { defaultDelay: 300, defaultBoundariesElement: document.body })
|
||||
|
||||
import VueMatomo from 'vue-matomo'
|
||||
|
||||
|
||||
@@ -12,8 +12,13 @@
|
||||
>
|
||||
<v-icon>mdi-minus</v-icon>
|
||||
</v-btn> -->
|
||||
<v-btn icon class="primary dark ml-2 elevation-10" @click="$emit('close', comment)">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
<v-btn
|
||||
icon
|
||||
small
|
||||
class="primary dark white--text ml-2 elevation-10"
|
||||
@click="$emit('close', comment)"
|
||||
>
|
||||
<v-icon small>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<div v-show="!minimise" style="width: 100%">
|
||||
@@ -51,17 +56,19 @@
|
||||
@click:append="addReply"
|
||||
@keydown.enter.shift.exact.prevent="addReply()"
|
||||
></v-textarea>
|
||||
<v-btn
|
||||
v-tooltip="'Marks this thread as archived.'"
|
||||
class="float-right"
|
||||
x-small
|
||||
rounded
|
||||
depressed
|
||||
color="error"
|
||||
@click="showArchiveDialog = true"
|
||||
>
|
||||
Archive
|
||||
</v-btn>
|
||||
<div class="text-right">
|
||||
<v-btn
|
||||
v-tooltip="'Marks this thread as archived.'"
|
||||
class="white--text mt-2 mr-2"
|
||||
small
|
||||
icon
|
||||
depressed
|
||||
color="error"
|
||||
@click="showArchiveDialog = true"
|
||||
>
|
||||
<v-icon small>mdi-delete-outline</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-dialog v-model="showArchiveDialog" max-width="500">
|
||||
<v-card>
|
||||
<v-toolbar color="error" dark flat>
|
||||
@@ -229,6 +236,11 @@ export default {
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
// Shhh.
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
this.comment.replies.totalCount++
|
||||
// eslint-disable-next-line vue/no-mutating-props
|
||||
this.comment.updatedAt = Date.now()
|
||||
this.$emit('refresh-layout') // needed for layout reshuffle in parent
|
||||
}, 100)
|
||||
},
|
||||
|
||||
@@ -5,50 +5,71 @@
|
||||
$store.state.selectedComment || $store.state.addingComment ? '0.2' : '1'
|
||||
};`"
|
||||
>
|
||||
<div
|
||||
v-for="user in users"
|
||||
:ref="`user-target-${user.uuid}`"
|
||||
:key="user.uuid + 'target'"
|
||||
:class="`absolute-pos rounded-pill primary`"
|
||||
:style="` opacity: ${
|
||||
user.hidden ? '0.2' : 1
|
||||
}; transform-origin:center; width: 10px; height:10px; pointer-events:none`"
|
||||
></div>
|
||||
<div
|
||||
v-for="user in users"
|
||||
:ref="`user-arrow-${user.uuid}`"
|
||||
:key="user.uuid + 'arrow'"
|
||||
:class="`absolute-pos d-flex align-center justify-center`"
|
||||
:style="`opacity: ${
|
||||
user.hidden ? '0.2' : 1
|
||||
}; pointer-events:none; transform-origin:center; width: 32px; height:32px; transform: rotateY(0) rotate(90deg)`"
|
||||
>
|
||||
<!-- <v-icon class="primary--text" style="position: relative; right: -90%">mdi-arrow-right</v-icon> -->
|
||||
<!-- <v-icon class="primary--text" style="position: relative; right: -90%">mdi-pan-right</v-icon> -->
|
||||
<v-icon class="primary--text" large style="position: relative; right: -60%; font-size: 4.2em">
|
||||
mdi-menu-right
|
||||
</v-icon>
|
||||
</div>
|
||||
<div
|
||||
v-for="sessionUser in users"
|
||||
:ref="`user-bubble-${sessionUser.uuid}`"
|
||||
:key="sessionUser.uuid"
|
||||
class="absolute-pos rounded-pill user-bubble elevation-5"
|
||||
:style="`opacity: ${sessionUser.hidden ? '0.2' : 1}; border: 4px solid ${
|
||||
$vuetify.theme.dark ? '#047EFB' : '#047EFB'
|
||||
}`"
|
||||
>
|
||||
<div @click="setUserPow(sessionUser)">
|
||||
<user-avatar
|
||||
:id="sessionUser.id"
|
||||
v-tooltip="sessionUser.name"
|
||||
:show-hover="false"
|
||||
:size="30"
|
||||
:margin="false"
|
||||
></user-avatar>
|
||||
<text-dots-typing v-if="sessionUser.status === 'writing'" />
|
||||
<div v-show="showBubbles">
|
||||
<div
|
||||
v-for="user in users"
|
||||
:ref="`user-target-${user.uuid}`"
|
||||
:key="user.uuid + 'target'"
|
||||
:class="`absolute-pos rounded-pill primary`"
|
||||
:style="` opacity: ${
|
||||
user.hidden ? '0.2' : 1
|
||||
}; transform-origin:center; width: 10px; height:10px; pointer-events:none`"
|
||||
></div>
|
||||
<div
|
||||
v-for="user in users"
|
||||
:ref="`user-arrow-${user.uuid}`"
|
||||
:key="user.uuid + 'arrow'"
|
||||
:class="`absolute-pos d-flex align-center justify-center`"
|
||||
:style="`opacity: ${
|
||||
user.hidden ? '0.2' : 1
|
||||
}; pointer-events:none; transform-origin:center; width: 32px; height:32px; transform: rotateY(0) rotate(90deg)`"
|
||||
>
|
||||
<!-- <v-icon class="primary--text" style="position: relative; right: -90%">mdi-arrow-right</v-icon> -->
|
||||
<!-- <v-icon class="primary--text" style="position: relative; right: -90%">mdi-pan-right</v-icon> -->
|
||||
<v-icon
|
||||
class="primary--text"
|
||||
large
|
||||
style="position: relative; right: -60%; font-size: 4.2em"
|
||||
>
|
||||
mdi-menu-right
|
||||
</v-icon>
|
||||
</div>
|
||||
<div
|
||||
v-for="sessionUser in users"
|
||||
:ref="`user-bubble-${sessionUser.uuid}`"
|
||||
:key="sessionUser.uuid"
|
||||
class="absolute-pos rounded-pill user-bubble elevation-5"
|
||||
:style="`opacity: ${sessionUser.hidden ? '0.2' : 1}; border: 4px solid ${
|
||||
$vuetify.theme.dark ? '#047EFB' : '#047EFB'
|
||||
}`"
|
||||
>
|
||||
<div @click="setUserPow(sessionUser)">
|
||||
<user-avatar
|
||||
:id="sessionUser.id"
|
||||
v-tooltip="sessionUser.name"
|
||||
:show-hover="false"
|
||||
:size="30"
|
||||
:margin="false"
|
||||
></user-avatar>
|
||||
<text-dots-typing v-if="sessionUser.status === 'writing'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<portal to="viewercontrols" :order="4">
|
||||
<v-btn
|
||||
key="bubbles-toggle-button"
|
||||
v-show="users.length !== 0"
|
||||
v-tooltip="`Toggle real time user bubbles`"
|
||||
small
|
||||
rounded
|
||||
icon
|
||||
class="mr-2"
|
||||
@click="showBubbles = !showBubbles"
|
||||
>
|
||||
<v-icon v-if="showBubbles" small>mdi-account</v-icon>
|
||||
<v-icon v-else small>mdi-account-off</v-icon>
|
||||
</v-btn>
|
||||
</portal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
@@ -137,7 +158,8 @@ export default {
|
||||
selectedIds: [],
|
||||
selectionLocation: null,
|
||||
selectionCenter: null,
|
||||
users: []
|
||||
users: [],
|
||||
showBubbles: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
>
|
||||
<div
|
||||
class="d-flex align-center"
|
||||
:style="`height: 48px; width: ${$vuetify.breakpoint.xs ? '100vw' : '320px'}`"
|
||||
:style="`height: 48px; width: ${$vuetify.breakpoint.xs ? '90vw' : '320px'}`"
|
||||
>
|
||||
<v-btn
|
||||
v-tooltip="!expand ? 'Add a comment (ctrl + shift + c)' : 'Cancel'"
|
||||
@@ -37,7 +37,7 @@
|
||||
<v-icon v-else dark x-small>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
<v-slide-x-transition>
|
||||
<div v-if="expand" style="width: 100%" class="d-flex">
|
||||
<div v-if="expand && !$vuetify.breakpoint.xs" style="width: 100%" class="d-flex">
|
||||
<v-textarea
|
||||
v-model="commentText"
|
||||
solo
|
||||
@@ -64,6 +64,38 @@
|
||||
</div>
|
||||
</v-slide-x-transition>
|
||||
</div>
|
||||
<v-dialog
|
||||
v-if="$vuetify.breakpoint.xs"
|
||||
v-model="expand"
|
||||
class="elevation-0 flat"
|
||||
@input="toggleExpand()"
|
||||
>
|
||||
<div class="d-flex justify-center" style="position: relative; left: 24px">
|
||||
<v-textarea
|
||||
v-model="commentText"
|
||||
solo
|
||||
hide-details
|
||||
autofocus
|
||||
auto-grow
|
||||
rows="1"
|
||||
placeholder="Your comment..."
|
||||
class="mouse rounded-xl caption elevation-15"
|
||||
append-icon="mdi-send"
|
||||
@keydown.enter.shift.exact.prevent="addComment()"
|
||||
></v-textarea>
|
||||
<v-btn
|
||||
v-tooltip="'Send comment (shift + enter)'"
|
||||
icon
|
||||
dark
|
||||
large
|
||||
class="mouse elevation-0 primary pa-0 ma-o"
|
||||
style="left: -47px; top: 1px; height: 48px; width: 48px"
|
||||
@click="addComment()"
|
||||
>
|
||||
<v-icon dark small>mdi-send</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</v-slide-x-transition>
|
||||
<portal to="viewercontrols" :order="100">
|
||||
@@ -238,6 +270,11 @@ export default {
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
::v-deep .v-dialog {
|
||||
box-shadow: none;
|
||||
overflow-y: hidden;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.no-mouse {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
:ref="`comment-${comment.id}`"
|
||||
:class="`absolute-pos rounded-xl no-mouse`"
|
||||
:style="`transition: opacity 0.2s ease; z-index:${comment.expanded ? '20' : '10'}; ${
|
||||
hasExpandedComment && !comment.expanded && !comment.hovered
|
||||
hasExpandedComment && !comment.expanded && !comment.hovered && !comment.bouncing
|
||||
? 'opacity: 0.1;'
|
||||
: 'opacity: 1;'
|
||||
}`"
|
||||
@@ -29,8 +29,8 @@
|
||||
<div class="" style="pointer-events: none">
|
||||
<div class="d-flex align-center" style="pointer-events: none">
|
||||
<v-btn
|
||||
v-show="!($vuetify.breakpoint.xs && comment.expanded)"
|
||||
:ref="`comment-button-${comment.id}`"
|
||||
v-tooltip="comment.expanded ? 'Close comment thread' : 'Open comment thread'"
|
||||
small
|
||||
icon
|
||||
:class="`elevation-5 pa-0 ma-0 mouse ${
|
||||
@@ -38,14 +38,26 @@
|
||||
}`"
|
||||
@click="comment.expanded ? collapseComment(comment) : expandComment(comment)"
|
||||
>
|
||||
<!-- @click="comment.expanded ? (comment.expanded = false) : expandComment(comment)" -->
|
||||
<!-- <span v-if="!comment.expanded" class="primary--text">
|
||||
<v-icon small>mdi-comment</v-icon>
|
||||
1
|
||||
</span> -->
|
||||
<v-icon v-if="!comment.expanded" x-small class="">mdi-comment</v-icon>
|
||||
<v-icon v-if="comment.expanded" x-small class="">mdi-close</v-icon>
|
||||
</v-btn>
|
||||
<v-slide-x-transition>
|
||||
<div
|
||||
v-if="comment.hovered && !comment.expanded"
|
||||
style="position: absolute; left: 30px; width: max-content"
|
||||
class="rounded-xl primary white--text px-2 ml-1 caption"
|
||||
>
|
||||
<timeago :datetime="comment.updatedAt" class="font-italic mr-2"></timeago>
|
||||
<v-icon x-small class="white--text">mdi-comment-outline</v-icon>
|
||||
{{ comment.replies.totalCount + 1 }}
|
||||
<v-icon v-if="comment.data.filters" x-small class="white--text">
|
||||
mdi-filter-variant
|
||||
</v-icon>
|
||||
<v-icon v-if="comment.data.sectionBox" x-small class="white--text">
|
||||
mdi-scissors-cutting
|
||||
</v-icon>
|
||||
</div>
|
||||
</v-slide-x-transition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -78,6 +90,7 @@
|
||||
</div>
|
||||
<portal to="viewercontrols" :order="5">
|
||||
<v-btn
|
||||
key="comment-toggle-button"
|
||||
v-tooltip="`Toggle comments (${localComments.length})`"
|
||||
rounded
|
||||
icon
|
||||
@@ -110,7 +123,11 @@ export default {
|
||||
authorId
|
||||
text
|
||||
createdAt
|
||||
updatedAt
|
||||
data
|
||||
replies {
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,7 +182,7 @@ export default {
|
||||
result({ data }) {
|
||||
if (!data.commentActivity) return
|
||||
// Creation
|
||||
if (data.commentActivity.action === 'created') {
|
||||
if (data.commentActivity.eventType === 'comment-added') {
|
||||
data.commentActivity.expanded = false
|
||||
data.commentActivity.hovered = false
|
||||
data.commentActivity.bouncing = false
|
||||
@@ -175,10 +192,6 @@ export default {
|
||||
this.bounceComment(data.commentActivity.id)
|
||||
}, 10)
|
||||
}
|
||||
// Deletion
|
||||
if (data.commentActivity.action === 'deleted') {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<v-card
|
||||
class="elevation-5 rounded-xl pl-3 py-0 d-flex align-center"
|
||||
height="44"
|
||||
style="max-width: 100vw; overflow-x: scroll"
|
||||
style="max-width: 90vw; overflow-x: scroll;"
|
||||
>
|
||||
<v-btn
|
||||
v-show="showVisReset"
|
||||
@@ -16,7 +16,8 @@
|
||||
<v-icon small class="mr-2">mdi-eye</v-icon>
|
||||
Reset Filters
|
||||
</v-btn>
|
||||
<v-btn
|
||||
<!-- disabling ortho mode because comment intersection are f*ed. -->
|
||||
<!-- <v-btn
|
||||
v-tooltip="`Toggle between perspective or ortho camera.`"
|
||||
:small="small"
|
||||
rounded
|
||||
@@ -25,7 +26,7 @@
|
||||
@click="toggleCamera()"
|
||||
>
|
||||
<v-icon small>mdi-perspective-less</v-icon>
|
||||
</v-btn>
|
||||
</v-btn> -->
|
||||
<canonical-views :small="small" />
|
||||
<v-btn v-tooltip="'Zoom extents'" :small="small" rounded icon class="mr-2" @click="zoomEx()">
|
||||
<v-icon small>mdi-arrow-expand</v-icon>
|
||||
@@ -40,18 +41,8 @@
|
||||
>
|
||||
<v-icon small>mdi-scissors-cutting</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-tooltip="`Toggle real time user bubbles`"
|
||||
:small="small"
|
||||
rounded
|
||||
icon
|
||||
class="mr-2"
|
||||
@click="sectionToggle()"
|
||||
>
|
||||
<v-icon small>mdi-account</v-icon>
|
||||
</v-btn>
|
||||
<!-- Other components teleport extra controls in here -->
|
||||
<portal-target name="viewercontrols" multiple></portal-target>
|
||||
<portal-target name="viewercontrols" class="d-flex align-center" multiple></portal-target>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -100,6 +100,7 @@ $primary-gradient: linear-gradient(0deg, $primary-darken 0%, $primary-base 40%);
|
||||
z-index: 10000;
|
||||
font-family: 'Roboto', sans-serif !important;
|
||||
font-size: 0.75rem !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tooltip .tooltip-inner {
|
||||
|
||||
@@ -56,7 +56,7 @@ module.exports = {
|
||||
let id = await createComment( { userId: context.userId, input: args.input } )
|
||||
// console.log( args.input )
|
||||
await pubsub.publish( 'COMMENT_ACTIVITY', {
|
||||
commentActivity: { ...args.input, authorId: context.userId, id, createdAt: Date.now(), action: 'created' },
|
||||
commentActivity: { ...args.input, authorId: context.userId, id, replies: { totalCount: 0 }, updatedAt: Date.now(), createdAt: Date.now(), eventType: 'comment-added' },
|
||||
streamId: args.input.streamId,
|
||||
resourceId: args.input.resources[1].resourceId // TODO: hack for now
|
||||
} )
|
||||
@@ -66,6 +66,7 @@ module.exports = {
|
||||
// TODO
|
||||
},
|
||||
async commentArchive( parent, args, context, info ) {
|
||||
await authorizeStreamAccess( { streamId: args.streamId, userId: context.userId, auth: context.auth } )
|
||||
await archiveComment( { ...args } )
|
||||
await pubsub.publish( 'COMMENT_THREAD_ACTIVITY', {
|
||||
commentThreadActivity: { eventType: 'comment-archived' },
|
||||
@@ -85,7 +86,7 @@ module.exports = {
|
||||
// console.log(input.resources)
|
||||
let id = await createComment( { userId: context.userId, input } )
|
||||
await pubsub.publish( 'COMMENT_THREAD_ACTIVITY', {
|
||||
commentThreadActivity: { eventType: 'reply-added', ...args.input, id, authorId: context.userId, createdAt: Date.now() },
|
||||
commentThreadActivity: { eventType: 'reply-added', ...args.input, id, authorId: context.userId, updatedAt: Date.now(), createdAt: Date.now() },
|
||||
streamId: args.input.streamId,
|
||||
commentId: args.input.parentComment
|
||||
} )
|
||||
|
||||
@@ -73,10 +73,12 @@ module.exports = {
|
||||
|
||||
const streamResources = input.resources.filter( r => r.resourceType === 'stream' )
|
||||
if ( streamResources.length > 1 ) throw Error( 'Commenting on multiple streams is not supported' )
|
||||
|
||||
|
||||
const [ stream ] = streamResources
|
||||
if ( stream.resourceId !== input.streamId ) throw Error( 'Input streamId doesn\'t match the stream resource.resourceId' )
|
||||
|
||||
const commentResource = input.resources.find( r => r.resourceType === 'comment' )
|
||||
|
||||
let comment = { ...input }
|
||||
|
||||
delete comment.resources
|
||||
@@ -88,6 +90,10 @@ module.exports = {
|
||||
await Comments().insert( comment )
|
||||
await persistResourceLinks( comment.id, input.resources )
|
||||
|
||||
if ( commentResource ) {
|
||||
await Comments().where( { id: commentResource.resourceId } ).update( { updatedAt: knex.fn.now( ) } )
|
||||
}
|
||||
|
||||
return comment.id
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user