From 5bfba281fc0c57556d65092013b5663efd350b3e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 21 Apr 2025 14:04:41 +0100 Subject: [PATCH] Comments: Started inline comment display windows --- lang/en/entities.php | 1 + resources/js/components/page-comment.ts | 66 ++++++++++++++++++++-- resources/sass/_animations.scss | 22 ++++++++ resources/sass/_pages.scss | 46 +++++++++++++++ resources/views/comments/comment.blade.php | 1 + 5 files changed, 132 insertions(+), 4 deletions(-) diff --git a/lang/en/entities.php b/lang/en/entities.php index a74785eaa..9ce684ac7 100644 --- a/lang/en/entities.php +++ b/lang/en/entities.php @@ -402,6 +402,7 @@ return [ 'comment_deleted_success' => 'Comment deleted', 'comment_created_success' => 'Comment added', 'comment_updated_success' => 'Comment updated', + 'comment_view' => 'View comment', '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 f4d295b95..5a148c258 100644 --- a/resources/js/components/page-comment.ts +++ b/resources/js/components/page-comment.ts @@ -3,6 +3,8 @@ import {findTargetNodeAndOffset, getLoading, hashElement, htmlToDom} from '../se import {buildForInput} from '../wysiwyg-tinymce/config'; import {el} from "../wysiwyg/utils/dom"; +import commentIcon from "@icons/comment.svg" + export class PageComment extends Component { protected commentId: string; @@ -10,6 +12,7 @@ export class PageComment extends Component { protected commentContentRef: string; protected deletedText: string; protected updatedText: string; + protected viewCommentText: string; protected wysiwygEditor: any = null; protected wysiwygLanguage: string; @@ -31,6 +34,7 @@ export class PageComment extends Component { this.commentContentRef = this.$opts.commentContentRef; this.deletedText = this.$opts.deletedText; this.updatedText = this.$opts.updatedText; + this.viewCommentText = this.$opts.viewCommentText; // Editor reference and text options this.wysiwygLanguage = this.$opts.wysiwygLanguage; @@ -171,13 +175,67 @@ export class PageComment extends Component { const relLeft = bounds.left - refElBounds.left; const relTop = bounds.top - refElBounds.top; - // TODO - Extract to class, Use theme color - const marker = el('div', { + + const marker = el('button', { + type: 'button', + class: 'content-comment-marker', + title: this.viewCommentText, + }); + marker.innerHTML = commentIcon; + marker.addEventListener('click', event => { + this.showCommentAtMarker(marker); + }); + + const markerWrap = el('div', { class: 'content-comment-highlight', style: `left: ${relLeft}px; top: ${relTop}px; width: ${bounds.width}px; height: ${bounds.height}px;` - }, ['']); + }, [marker]); refEl.style.position = 'relative'; - refEl.append(marker); + refEl.append(markerWrap); + } + + protected showCommentAtMarker(marker: HTMLElement): void { + + marker.hidden = true; + const readClone = this.container.closest('.comment-branch').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 commentWindow = el('div', { + class: 'content-comment-window' + }, [ + el('div', { + class: 'content-comment-window-actions', + }, [jump, close]), + el('div', { + class: 'content-comment-window-content', + }, [readClone]), + ]); + + marker.parentElement.append(commentWindow); + + const closeAction = () => { + commentWindow.remove(); + marker.hidden = false; + }; + + close.addEventListener('click', closeAction.bind(this)); + + jump.addEventListener('click', () => { + closeAction(); + this.container.scrollIntoView({behavior: 'smooth'}); + const highlightTarget = this.container.querySelector('.header') as HTMLElement; + highlightTarget.classList.add('anim-highlight'); + highlightTarget.addEventListener('animationend', () => highlightTarget.classList.remove('anim-highlight')) + }); + + // TODO - Position wrapper sensibly + // TODO - Movement control? } } diff --git a/resources/sass/_animations.scss b/resources/sass/_animations.scss index f1aa3139b..ccbe36161 100644 --- a/resources/sass/_animations.scss +++ b/resources/sass/_animations.scss @@ -67,4 +67,26 @@ animation-duration: 180ms; animation-delay: 0s; animation-timing-function: cubic-bezier(.62, .28, .23, .99); +} + +@keyframes highlight { + 0% { + background-color: var(--color-primary-light); + } + 33% { + background-color: transparent; + } + 66% { + background-color: var(--color-primary-light); + } + 100% { + background-color: transparent; + } +} + +.anim-highlight { + animation-name: highlight; + animation-duration: 2s; + animation-delay: 0s; + animation-timing-function: linear; } \ No newline at end of file diff --git a/resources/sass/_pages.scss b/resources/sass/_pages.scss index 1fe22b9c4..ac2d195b4 100755 --- a/resources/sass/_pages.scss +++ b/resources/sass/_pages.scss @@ -239,6 +239,52 @@ body.tox-fullscreen, body.markdown-fullscreen { opacity: 0.25; } } +.content-comment-window { + font-size: vars.$fs-m; + line-height: 1.4; + position: relative; + z-index: 90; + 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; +} +.content-comment-window-actions { + background-color: var(--color-primary); + color: #FFF; + display: flex; + align-items: center; + justify-content: end; +} +.content-comment-window-content { + padding: vars.$xs; + max-height: 200px; + overflow-y: scroll; +} +.content-comment-marker { + position: absolute; + right: -16px; + top: -16px; + pointer-events: all; + width: min(1.5em, 32px); + height: min(1.5em, 32px); + border-radius: min(calc(1.5em / 2), 32px); + display: flex; + align-items: center; + justify-content: center; + background-color: var(--color-primary); + box-shadow: vars.$bs-hover; + color: #FFF; + cursor: pointer; + z-index: 90; + svg { + fill: #FFF; + width: 80%; + } +} // Page editor sidebar toolbox .floating-toolbox { diff --git a/resources/views/comments/comment.blade.php b/resources/views/comments/comment.blade.php index c3578293a..1886dad51 100644 --- a/resources/views/comments/comment.blade.php +++ b/resources/views/comments/comment.blade.php @@ -7,6 +7,7 @@ option:page-comment:comment-content-ref="{{ $comment->content_ref }}" 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:wysiwyg-language="{{ $locale->htmlLang() }}" option:page-comment:wysiwyg-text-direction="{{ $locale->htmlDirection() }}" id="comment{{$comment->local_id}}"