]> BookStack Code Mirror - bookstack/commitdiff
Comments: Started inline comment display windows
authorDan Brown <redacted>
Mon, 21 Apr 2025 13:04:41 +0000 (14:04 +0100)
committerDan Brown <redacted>
Mon, 21 Apr 2025 13:04:41 +0000 (14:04 +0100)
lang/en/entities.php
resources/js/components/page-comment.ts
resources/sass/_animations.scss
resources/sass/_pages.scss
resources/views/comments/comment.blade.php

index a74785eaacde3ca5316d7153e1e6b139dc603829..9ce684ac71b2ac229f88c59b855ab06ecdb77eb1 100644 (file)
@@ -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.',
index f4d295b95a3d304c44d9193e8862ff38972211fc..5a148c258856f59aa708f06daa79f883a6db3e7f 100644 (file)
@@ -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 = <string>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?
     }
 }
index f1aa3139b8ea7aff9f01088d8a680584b483e9c1..ccbe36161b67c0a4d0781b16283fc6d50f0f94ca 100644 (file)
   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
index 1fe22b9c4e266bcc56f7b25f52c3ad3194d40a42..ac2d195b4e8b2953a3d6d5d9cbe4ec671e67e17d 100755 (executable)
@@ -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 {
index c3578293a5a610320d9b842df7bfbb5b1ab0ede1..1886dad51e4df4bd7b635f7bc97412b4d659d383 100644 (file)
@@ -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}}"
Morty Proxy This is a proxified and sanitized view of the page, visit original site.