diff --git a/lang/en/entities.php b/lang/en/entities.php index 9ce684ac7..f9fab8ebf 100644 --- a/lang/en/entities.php +++ b/lang/en/entities.php @@ -403,6 +403,7 @@ return [ 'comment_created_success' => 'Comment added', 'comment_updated_success' => 'Comment updated', 'comment_view' => 'View comment', + 'comment_jump_to_thread' => 'Jump to thread', 'comment_delete_confirm' => 'Are you sure you want to delete this comment?', 'comment_in_reply_to' => 'In reply to :commentId', 'comment_editor_explain' => 'Here are the comments that have been left on this page. Comments can be added & managed when viewing the saved page.', diff --git a/resources/js/components/page-comment.ts b/resources/js/components/page-comment.ts index 5a148c258..9192c7c56 100644 --- a/resources/js/components/page-comment.ts +++ b/resources/js/components/page-comment.ts @@ -4,6 +4,13 @@ import {buildForInput} from '../wysiwyg-tinymce/config'; import {el} from "../wysiwyg/utils/dom"; import commentIcon from "@icons/comment.svg" +import closeIcon from "@icons/close.svg" + +/** + * Track the close function for the current open marker so it can be closed + * when another is opened so we only show one marker comment thread at one time. + */ +let openMarkerClose: Function|null = null; export class PageComment extends Component { @@ -13,6 +20,8 @@ export class PageComment extends Component { protected deletedText: string; protected updatedText: string; protected viewCommentText: string; + protected jumpToThreadText: string; + protected closeText: string; protected wysiwygEditor: any = null; protected wysiwygLanguage: string; @@ -35,6 +44,8 @@ export class PageComment extends Component { this.deletedText = this.$opts.deletedText; this.updatedText = this.$opts.updatedText; this.viewCommentText = this.$opts.viewCommentText; + this.jumpToThreadText = this.$opts.jumpToThreadText; + this.closeText = this.$opts.closeText; // Editor reference and text options this.wysiwygLanguage = this.$opts.wysiwygLanguage; @@ -130,7 +141,7 @@ export class PageComment extends Component { await window.$http.delete(`/comment/${this.commentId}`); this.$emit('delete'); - this.container.closest('.comment-branch').remove(); + this.container.closest('.comment-branch')?.remove(); window.$events.success(this.deletedText); } @@ -196,16 +207,22 @@ export class PageComment extends Component { } protected showCommentAtMarker(marker: HTMLElement): void { - + // Hide marker and close existing marker windows + if (openMarkerClose) { + openMarkerClose(); + } marker.hidden = true; - const readClone = this.container.closest('.comment-branch').cloneNode(true) as HTMLElement; + + // Build comment window + const readClone = (this.container.closest('.comment-branch') as HTMLElement).cloneNode(true) as HTMLElement; const toRemove = readClone.querySelectorAll('.actions, form'); for (const el of toRemove) { el.remove(); } - const close = el('button', {type: 'button'}, ['x']); - const jump = el('button', {type: 'button'}, ['Jump to thread']); + const close = el('button', {type: 'button', title: this.closeText}); + close.innerHTML = (closeIcon as string); + const jump = el('button', {type: 'button', 'data-action': 'jump'}, [this.jumpToThreadText]); const commentWindow = el('div', { class: 'content-comment-window' @@ -214,19 +231,29 @@ export class PageComment extends Component { class: 'content-comment-window-actions', }, [jump, close]), el('div', { - class: 'content-comment-window-content', + class: 'content-comment-window-content comment-container-compact comment-container-super-compact', }, [readClone]), ]); - marker.parentElement.append(commentWindow); + marker.parentElement?.append(commentWindow); + // Handle interaction within window const closeAction = () => { commentWindow.remove(); marker.hidden = false; + window.removeEventListener('click', windowCloseAction); + openMarkerClose = null; }; - close.addEventListener('click', closeAction.bind(this)); + const windowCloseAction = (event: MouseEvent) => { + if (!(marker.parentElement as HTMLElement).contains(event.target as HTMLElement)) { + closeAction(); + } + }; + window.addEventListener('click', windowCloseAction); + openMarkerClose = closeAction; + close.addEventListener('click', closeAction.bind(this)); jump.addEventListener('click', () => { closeAction(); this.container.scrollIntoView({behavior: 'smooth'}); @@ -235,7 +262,12 @@ export class PageComment extends Component { highlightTarget.addEventListener('animationend', () => highlightTarget.classList.remove('anim-highlight')) }); - // TODO - Position wrapper sensibly - // TODO - Movement control? + // Position window within bounds + const commentWindowBounds = commentWindow.getBoundingClientRect(); + const contentBounds = document.querySelector('.page-content')?.getBoundingClientRect(); + if (contentBounds && commentWindowBounds.right > contentBounds.right) { + const diff = commentWindowBounds.right - contentBounds.right; + commentWindow.style.left = `-${diff}px`; + } } } diff --git a/resources/sass/_components.scss b/resources/sass/_components.scss index 58d39d3ee..26b051827 100644 --- a/resources/sass/_components.scss +++ b/resources/sass/_components.scss @@ -746,6 +746,10 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { height: calc(100% - vars.$m); } +.comment-branch .comment-box { + margin-bottom: vars.$m; +} + .comment-branch .comment-branch .comment-branch .comment-branch .comment-thread-indicator { display: none; } @@ -761,6 +765,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { } .comment-container-compact .comment-box { + margin-bottom: vars.$xs; .meta { font-size: 0.8rem; } @@ -778,6 +783,28 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { width: vars.$m; } +.comment-container-super-compact .comment-box { + .meta { + font-size: 12px; + } + .avatar { + width: 18px; + margin-inline-end: 2px !important; + } + .content { + padding: vars.$xxs vars.$s; + line-height: 1.2; + } + .content p { + font-size: 12px; + } +} + +.comment-container-super-compact .comment-thread-indicator { + width: (vars.$xs + 3px); + margin-inline-start: 3px; +} + #tag-manager .drag-card { max-width: 500px; } diff --git a/resources/sass/_pages.scss b/resources/sass/_pages.scss index ac2d195b4..be5a0f7c3 100755 --- a/resources/sass/_pages.scss +++ b/resources/sass/_pages.scss @@ -242,12 +242,13 @@ body.tox-fullscreen, body.markdown-fullscreen { .content-comment-window { font-size: vars.$fs-m; line-height: 1.4; - position: relative; - z-index: 90; + position: absolute; + top: calc(100% + 3px); + left: 0; + z-index: 92; pointer-events: all; min-width: min(340px, 80vw); background-color: #FFF; - //border: 1px solid var(--color-primary); box-shadow: vars.$bs-hover; border-radius: 4px; overflow: hidden; @@ -258,9 +259,24 @@ body.tox-fullscreen, body.markdown-fullscreen { display: flex; align-items: center; justify-content: end; + gap: vars.$xs; + button { + color: #FFF; + font-size: 12px; + padding: vars.$xs; + line-height: 1; + cursor: pointer; + } + button[data-action="jump"] { + text-decoration: underline; + } + svg { + fill: currentColor; + width: 12px; + } } .content-comment-window-content { - padding: vars.$xs; + padding: vars.$xs vars.$s vars.$xs vars.$xs; max-height: 200px; overflow-y: scroll; } @@ -280,11 +296,16 @@ body.tox-fullscreen, body.markdown-fullscreen { color: #FFF; cursor: pointer; z-index: 90; + transform: scale(1); + transition: transform ease-in-out 120ms; svg { fill: #FFF; width: 80%; } } +.page-content [id^="bkmrk-"]:hover .content-comment-marker { + transform: scale(1.15); +} // Page editor sidebar toolbox .floating-toolbox { diff --git a/resources/views/comments/comment-branch.blade.php b/resources/views/comments/comment-branch.blade.php index 78d19ac3e..83fa4b5c5 100644 --- a/resources/views/comments/comment-branch.blade.php +++ b/resources/views/comments/comment-branch.blade.php @@ -1,5 +1,5 @@
-
+
@include('comments.comment', ['comment' => $branch['comment']])
diff --git a/resources/views/comments/comment.blade.php b/resources/views/comments/comment.blade.php index 1886dad51..5b79da4ac 100644 --- a/resources/views/comments/comment.blade.php +++ b/resources/views/comments/comment.blade.php @@ -8,6 +8,8 @@ option:page-comment:updated-text="{{ trans('entities.comment_updated_success') }}" option:page-comment:deleted-text="{{ trans('entities.comment_deleted_success') }}" option:page-comment:view-comment-text="{{ trans('entities.comment_view') }}" + option:page-comment:jump-to-thread-text="{{ trans('entities.comment_jump_to_thread') }}" + option:page-comment:close-text="{{ trans('common.close') }}" option:page-comment:wysiwyg-language="{{ $locale->htmlLang() }}" option:page-comment:wysiwyg-text-direction="{{ $locale->htmlDirection() }}" id="comment{{$comment->local_id}}"