Comments: Fixed a range of TS errors + other
- Migrated toolbox component to TS - Aligned how custom event types are managed - Fixed PHP use of content_ref where not provided
This commit is contained in:
		
							parent
							
								
									62f78f1c6d
								
							
						
					
					
						commit
						8f92b6f21b
					
				| 
						 | 
				
			
			@ -22,7 +22,7 @@ class CommentRepo
 | 
			
		|||
    /**
 | 
			
		||||
     * Create a new comment on an entity.
 | 
			
		||||
     */
 | 
			
		||||
    public function create(Entity $entity, string $html, ?int $parent_id, string $content_ref): Comment
 | 
			
		||||
    public function create(Entity $entity, string $html, ?int $parentId, string $contentRef): Comment
 | 
			
		||||
    {
 | 
			
		||||
        $userId = user()->id;
 | 
			
		||||
        $comment = new Comment();
 | 
			
		||||
| 
						 | 
				
			
			@ -31,8 +31,8 @@ class CommentRepo
 | 
			
		|||
        $comment->created_by = $userId;
 | 
			
		||||
        $comment->updated_by = $userId;
 | 
			
		||||
        $comment->local_id = $this->getNextLocalId($entity);
 | 
			
		||||
        $comment->parent_id = $parent_id;
 | 
			
		||||
        $comment->content_ref = preg_match('/^bkmrk-(.*?):\d+:(\d*-\d*)?$/', $content_ref) === 1 ? $content_ref : '';
 | 
			
		||||
        $comment->parent_id = $parentId;
 | 
			
		||||
        $comment->content_ref = preg_match('/^bkmrk-(.*?):\d+:(\d*-\d*)?$/', $contentRef) === 1 ? $contentRef : '';
 | 
			
		||||
 | 
			
		||||
        $entity->comments()->save($comment);
 | 
			
		||||
        ActivityService::add(ActivityType::COMMENT_CREATE, $comment);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,7 +43,8 @@ class CommentController extends Controller
 | 
			
		|||
 | 
			
		||||
        // Create a new comment.
 | 
			
		||||
        $this->checkPermission('comment-create-all');
 | 
			
		||||
        $comment = $this->commentRepo->create($page, $input['html'], $input['parent_id'] ?? null, $input['content_ref']);
 | 
			
		||||
        $contentRef = $input['content_ref'] ?? '';
 | 
			
		||||
        $comment = $this->commentRepo->create($page, $input['html'], $input['parent_id'] ?? null, $contentRef);
 | 
			
		||||
 | 
			
		||||
        return view('comments.comment-branch', [
 | 
			
		||||
            'readOnly' => false,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,39 +1,49 @@
 | 
			
		|||
import {Component} from './component';
 | 
			
		||||
 | 
			
		||||
export interface EditorToolboxChangeEventData {
 | 
			
		||||
    tab: string;
 | 
			
		||||
    open: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class EditorToolbox extends Component {
 | 
			
		||||
 | 
			
		||||
    protected container!: HTMLElement;
 | 
			
		||||
    protected buttons!: HTMLButtonElement[];
 | 
			
		||||
    protected contentElements!: HTMLElement[];
 | 
			
		||||
    protected toggleButton!: HTMLElement;
 | 
			
		||||
    protected editorWrapEl!: HTMLElement;
 | 
			
		||||
 | 
			
		||||
    protected open: boolean = false;
 | 
			
		||||
    protected tab: string = '';
 | 
			
		||||
 | 
			
		||||
    setup() {
 | 
			
		||||
        // Elements
 | 
			
		||||
        this.container = this.$el;
 | 
			
		||||
        this.buttons = this.$manyRefs.tabButton;
 | 
			
		||||
        this.buttons = this.$manyRefs.tabButton as HTMLButtonElement[];
 | 
			
		||||
        this.contentElements = this.$manyRefs.tabContent;
 | 
			
		||||
        this.toggleButton = this.$refs.toggle;
 | 
			
		||||
        this.editorWrapEl = this.container.closest('.page-editor');
 | 
			
		||||
 | 
			
		||||
        // State
 | 
			
		||||
        this.open = false;
 | 
			
		||||
        this.tab = '';
 | 
			
		||||
        this.editorWrapEl = this.container.closest('.page-editor') as HTMLElement;
 | 
			
		||||
 | 
			
		||||
        this.setupListeners();
 | 
			
		||||
 | 
			
		||||
        // Set the first tab as active on load
 | 
			
		||||
        this.setActiveTab(this.contentElements[0].dataset.tabContent);
 | 
			
		||||
        this.setActiveTab(this.contentElements[0].dataset.tabContent || '');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setupListeners() {
 | 
			
		||||
    protected setupListeners(): void {
 | 
			
		||||
        // Toolbox toggle button click
 | 
			
		||||
        this.toggleButton.addEventListener('click', () => this.toggle());
 | 
			
		||||
        // Tab button click
 | 
			
		||||
        this.container.addEventListener('click', event => {
 | 
			
		||||
            const button = event.target.closest('button');
 | 
			
		||||
            if (this.buttons.includes(button)) {
 | 
			
		||||
                const name = button.dataset.tab;
 | 
			
		||||
        this.container.addEventListener('click', (event: MouseEvent) => {
 | 
			
		||||
            const button = (event.target as HTMLElement).closest('button');
 | 
			
		||||
            if (button instanceof HTMLButtonElement && this.buttons.includes(button)) {
 | 
			
		||||
                const name = button.dataset.tab || '';
 | 
			
		||||
                this.setActiveTab(name, true);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    toggle() {
 | 
			
		||||
    protected toggle(): void {
 | 
			
		||||
        this.container.classList.toggle('open');
 | 
			
		||||
        const isOpen = this.container.classList.contains('open');
 | 
			
		||||
        this.toggleButton.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +52,7 @@ export class EditorToolbox extends Component {
 | 
			
		|||
        this.emitState();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setActiveTab(tabName, openToolbox = false) {
 | 
			
		||||
    protected setActiveTab(tabName: string, openToolbox: boolean = false): void {
 | 
			
		||||
        // Set button visibility
 | 
			
		||||
        for (const button of this.buttons) {
 | 
			
		||||
            button.classList.remove('active');
 | 
			
		||||
| 
						 | 
				
			
			@ -65,8 +75,9 @@ export class EditorToolbox extends Component {
 | 
			
		|||
        this.emitState();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    emitState() {
 | 
			
		||||
        this.$emit('change', {tab: this.tab, open: this.open});
 | 
			
		||||
    protected emitState(): void {
 | 
			
		||||
        const data: EditorToolboxChangeEventData = {tab: this.tab, open: this.open};
 | 
			
		||||
        this.$emit('change', data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -4,6 +4,8 @@ import {el} from "../wysiwyg/utils/dom";
 | 
			
		|||
import commentIcon from "@icons/comment.svg";
 | 
			
		||||
import closeIcon from "@icons/close.svg";
 | 
			
		||||
import {debounce, scrollAndHighlightElement} from "../services/util";
 | 
			
		||||
import {EditorToolboxChangeEventData} from "./editor-toolbox";
 | 
			
		||||
import {TabsChangeEvent} from "./tabs";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Track the close function for the current open marker so it can be closed
 | 
			
		||||
| 
						 | 
				
			
			@ -12,13 +14,13 @@ import {debounce, scrollAndHighlightElement} from "../services/util";
 | 
			
		|||
let openMarkerClose: Function|null = null;
 | 
			
		||||
 | 
			
		||||
export class PageCommentReference extends Component {
 | 
			
		||||
    protected link: HTMLLinkElement;
 | 
			
		||||
    protected reference: string;
 | 
			
		||||
    protected link!: HTMLLinkElement;
 | 
			
		||||
    protected reference!: string;
 | 
			
		||||
    protected markerWrap: HTMLElement|null = null;
 | 
			
		||||
 | 
			
		||||
    protected viewCommentText: string;
 | 
			
		||||
    protected jumpToThreadText: string;
 | 
			
		||||
    protected closeText: string;
 | 
			
		||||
    protected viewCommentText!: string;
 | 
			
		||||
    protected jumpToThreadText!: string;
 | 
			
		||||
    protected closeText!: string;
 | 
			
		||||
 | 
			
		||||
    setup() {
 | 
			
		||||
        this.link = this.$el as HTMLLinkElement;
 | 
			
		||||
| 
						 | 
				
			
			@ -31,15 +33,15 @@ export class PageCommentReference extends Component {
 | 
			
		|||
        this.showForDisplay();
 | 
			
		||||
 | 
			
		||||
        // Handle editor view to show on comments toolbox view
 | 
			
		||||
        window.addEventListener('editor-toolbox-change', (event) => {
 | 
			
		||||
             const tabName: string = (event as {detail: {tab: string, open: boolean}}).detail.tab;
 | 
			
		||||
             const isOpen = (event as {detail: {tab: string, open: boolean}}).detail.open;
 | 
			
		||||
             if (tabName === 'comments' && isOpen && this.link.checkVisibility()) {
 | 
			
		||||
                 this.showForEditor();
 | 
			
		||||
             } else {
 | 
			
		||||
                 this.hideMarker();
 | 
			
		||||
             }
 | 
			
		||||
        });
 | 
			
		||||
        window.addEventListener('editor-toolbox-change', ((event: CustomEvent<EditorToolboxChangeEventData>) => {
 | 
			
		||||
            const tabName: string = event.detail.tab;
 | 
			
		||||
            const isOpen = event.detail.open;
 | 
			
		||||
            if (tabName === 'comments' && isOpen && this.link.checkVisibility()) {
 | 
			
		||||
                this.showForEditor();
 | 
			
		||||
            } else {
 | 
			
		||||
                this.hideMarker();
 | 
			
		||||
            }
 | 
			
		||||
        }) as EventListener);
 | 
			
		||||
 | 
			
		||||
        // Handle visibility changes within editor toolbox archived details dropdown
 | 
			
		||||
        window.addEventListener('toggle', event => {
 | 
			
		||||
| 
						 | 
				
			
			@ -55,8 +57,8 @@ export class PageCommentReference extends Component {
 | 
			
		|||
        }, {capture: true});
 | 
			
		||||
 | 
			
		||||
        // Handle comments tab changes to hide/show markers & indicators
 | 
			
		||||
        window.addEventListener('tabs-change', event => {
 | 
			
		||||
            const sectionId = (event as {detail: {showing: string}}).detail.showing;
 | 
			
		||||
        window.addEventListener('tabs-change', ((event: CustomEvent<TabsChangeEvent>) => {
 | 
			
		||||
            const sectionId = event.detail.showing;
 | 
			
		||||
            if (!sectionId.startsWith('comment-tab-panel')) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -67,7 +69,7 @@ export class PageCommentReference extends Component {
 | 
			
		|||
            } else {
 | 
			
		||||
                this.hideMarker();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        }) as EventListener);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public showForDisplay() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,29 +1,39 @@
 | 
			
		|||
import {Component} from './component';
 | 
			
		||||
import {getLoading, htmlToDom} from '../services/dom.ts';
 | 
			
		||||
import {getLoading, htmlToDom} from '../services/dom';
 | 
			
		||||
import {buildForInput} from '../wysiwyg-tinymce/config';
 | 
			
		||||
import {PageCommentReference} from "./page-comment-reference";
 | 
			
		||||
import {HttpError} from "../services/http";
 | 
			
		||||
 | 
			
		||||
export interface PageCommentReplyEventData {
 | 
			
		||||
    id: string; // ID of comment being replied to
 | 
			
		||||
    element: HTMLElement; // Container for comment replied to
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface PageCommentArchiveEventData {
 | 
			
		||||
    new_thread_dom: HTMLElement;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class PageComment extends Component {
 | 
			
		||||
 | 
			
		||||
    protected commentId: string;
 | 
			
		||||
    protected commentLocalId: string;
 | 
			
		||||
    protected deletedText: string;
 | 
			
		||||
    protected updatedText: string;
 | 
			
		||||
    protected archiveText: string;
 | 
			
		||||
    protected commentId!: string;
 | 
			
		||||
    protected commentLocalId!: string;
 | 
			
		||||
    protected deletedText!: string;
 | 
			
		||||
    protected updatedText!: string;
 | 
			
		||||
    protected archiveText!: string;
 | 
			
		||||
 | 
			
		||||
    protected wysiwygEditor: any = null;
 | 
			
		||||
    protected wysiwygLanguage: string;
 | 
			
		||||
    protected wysiwygTextDirection: string;
 | 
			
		||||
    protected wysiwygLanguage!: string;
 | 
			
		||||
    protected wysiwygTextDirection!: string;
 | 
			
		||||
 | 
			
		||||
    protected container: HTMLElement;
 | 
			
		||||
    protected contentContainer: HTMLElement;
 | 
			
		||||
    protected form: HTMLFormElement;
 | 
			
		||||
    protected formCancel: HTMLElement;
 | 
			
		||||
    protected editButton: HTMLElement;
 | 
			
		||||
    protected deleteButton: HTMLElement;
 | 
			
		||||
    protected replyButton: HTMLElement;
 | 
			
		||||
    protected archiveButton: HTMLElement;
 | 
			
		||||
    protected input: HTMLInputElement;
 | 
			
		||||
    protected container!: HTMLElement;
 | 
			
		||||
    protected contentContainer!: HTMLElement;
 | 
			
		||||
    protected form!: HTMLFormElement;
 | 
			
		||||
    protected formCancel!: HTMLElement;
 | 
			
		||||
    protected editButton!: HTMLElement;
 | 
			
		||||
    protected deleteButton!: HTMLElement;
 | 
			
		||||
    protected replyButton!: HTMLElement;
 | 
			
		||||
    protected archiveButton!: HTMLElement;
 | 
			
		||||
    protected input!: HTMLInputElement;
 | 
			
		||||
 | 
			
		||||
    setup() {
 | 
			
		||||
        // Options
 | 
			
		||||
| 
						 | 
				
			
			@ -53,10 +63,11 @@ export class PageComment extends Component {
 | 
			
		|||
 | 
			
		||||
    protected setupListeners(): void {
 | 
			
		||||
        if (this.replyButton) {
 | 
			
		||||
            this.replyButton.addEventListener('click', () => this.$emit('reply', {
 | 
			
		||||
            const data: PageCommentReplyEventData = {
 | 
			
		||||
                id: this.commentLocalId,
 | 
			
		||||
                element: this.container,
 | 
			
		||||
            }));
 | 
			
		||||
            };
 | 
			
		||||
            this.replyButton.addEventListener('click', () => this.$emit('reply', data));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.editButton) {
 | 
			
		||||
| 
						 | 
				
			
			@ -95,10 +106,10 @@ export class PageComment extends Component {
 | 
			
		|||
            drawioUrl: '',
 | 
			
		||||
            pageId: 0,
 | 
			
		||||
            translations: {},
 | 
			
		||||
            translationMap: (window as Record<string, Object>).editor_translations,
 | 
			
		||||
            translationMap: (window as unknown as Record<string, Object>).editor_translations,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        (window as {tinymce: {init: (Object) => Promise<any>}}).tinymce.init(config).then(editors => {
 | 
			
		||||
        (window as unknown as {tinymce: {init: (arg0: Object) => Promise<any>}}).tinymce.init(config).then(editors => {
 | 
			
		||||
            this.wysiwygEditor = editors[0];
 | 
			
		||||
            setTimeout(() => this.wysiwygEditor.focus(), 50);
 | 
			
		||||
        });
 | 
			
		||||
| 
						 | 
				
			
			@ -120,7 +131,9 @@ export class PageComment extends Component {
 | 
			
		|||
            window.$events.success(this.updatedText);
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
            console.error(err);
 | 
			
		||||
            window.$events.showValidationErrors(err);
 | 
			
		||||
            if (err instanceof HttpError) {
 | 
			
		||||
                window.$events.showValidationErrors(err);
 | 
			
		||||
            }
 | 
			
		||||
            this.form.toggleAttribute('hidden', false);
 | 
			
		||||
            loading.remove();
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -151,7 +164,8 @@ export class PageComment extends Component {
 | 
			
		|||
 | 
			
		||||
        const response = await window.$http.put(`/comment/${this.commentId}/${action}`);
 | 
			
		||||
        window.$events.success(this.archiveText);
 | 
			
		||||
        this.$emit(action, {new_thread_dom: htmlToDom(response.data as string)});
 | 
			
		||||
        const eventData: PageCommentArchiveEventData = {new_thread_dom: htmlToDom(response.data as string)};
 | 
			
		||||
        this.$emit(action, eventData);
 | 
			
		||||
 | 
			
		||||
        const branch = this.container.closest('.comment-branch') as HTMLElement;
 | 
			
		||||
        const references = window.$components.allWithinElement<PageCommentReference>(branch, 'page-comment-reference');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,50 +1,38 @@
 | 
			
		|||
import {Component} from './component';
 | 
			
		||||
import {getLoading, htmlToDom} from '../services/dom.ts';
 | 
			
		||||
import {getLoading, htmlToDom} from '../services/dom';
 | 
			
		||||
import {buildForInput} from '../wysiwyg-tinymce/config';
 | 
			
		||||
import {Tabs} from "./tabs";
 | 
			
		||||
import {PageCommentReference} from "./page-comment-reference";
 | 
			
		||||
import {scrollAndHighlightElement} from "../services/util";
 | 
			
		||||
 | 
			
		||||
export interface CommentReplyEvent extends Event {
 | 
			
		||||
    detail: {
 | 
			
		||||
        id: string; // ID of comment being replied to
 | 
			
		||||
        element: HTMLElement; // Container for comment replied to
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ArchiveEvent extends Event {
 | 
			
		||||
    detail: {
 | 
			
		||||
        new_thread_dom: HTMLElement;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
import {PageCommentArchiveEventData, PageCommentReplyEventData} from "./page-comment";
 | 
			
		||||
 | 
			
		||||
export class PageComments extends Component {
 | 
			
		||||
 | 
			
		||||
    private elem: HTMLElement;
 | 
			
		||||
    private pageId: number;
 | 
			
		||||
    private container: HTMLElement;
 | 
			
		||||
    private commentCountBar: HTMLElement;
 | 
			
		||||
    private activeTab: HTMLElement;
 | 
			
		||||
    private archivedTab: HTMLElement;
 | 
			
		||||
    private addButtonContainer: HTMLElement;
 | 
			
		||||
    private archiveContainer: HTMLElement;
 | 
			
		||||
    private replyToRow: HTMLElement;
 | 
			
		||||
    private referenceRow: HTMLElement;
 | 
			
		||||
    private formContainer: HTMLElement;
 | 
			
		||||
    private form: HTMLFormElement;
 | 
			
		||||
    private formInput: HTMLInputElement;
 | 
			
		||||
    private formReplyLink: HTMLAnchorElement;
 | 
			
		||||
    private formReferenceLink: HTMLAnchorElement;
 | 
			
		||||
    private addCommentButton: HTMLElement;
 | 
			
		||||
    private hideFormButton: HTMLElement;
 | 
			
		||||
    private removeReplyToButton: HTMLElement;
 | 
			
		||||
    private removeReferenceButton: HTMLElement;
 | 
			
		||||
    private wysiwygLanguage: string;
 | 
			
		||||
    private wysiwygTextDirection: string;
 | 
			
		||||
    private elem!: HTMLElement;
 | 
			
		||||
    private pageId!: number;
 | 
			
		||||
    private container!: HTMLElement;
 | 
			
		||||
    private commentCountBar!: HTMLElement;
 | 
			
		||||
    private activeTab!: HTMLElement;
 | 
			
		||||
    private archivedTab!: HTMLElement;
 | 
			
		||||
    private addButtonContainer!: HTMLElement;
 | 
			
		||||
    private archiveContainer!: HTMLElement;
 | 
			
		||||
    private replyToRow!: HTMLElement;
 | 
			
		||||
    private referenceRow!: HTMLElement;
 | 
			
		||||
    private formContainer!: HTMLElement;
 | 
			
		||||
    private form!: HTMLFormElement;
 | 
			
		||||
    private formInput!: HTMLInputElement;
 | 
			
		||||
    private formReplyLink!: HTMLAnchorElement;
 | 
			
		||||
    private formReferenceLink!: HTMLAnchorElement;
 | 
			
		||||
    private addCommentButton!: HTMLElement;
 | 
			
		||||
    private hideFormButton!: HTMLElement;
 | 
			
		||||
    private removeReplyToButton!: HTMLElement;
 | 
			
		||||
    private removeReferenceButton!: HTMLElement;
 | 
			
		||||
    private wysiwygLanguage!: string;
 | 
			
		||||
    private wysiwygTextDirection!: string;
 | 
			
		||||
    private wysiwygEditor: any = null;
 | 
			
		||||
    private createdText: string;
 | 
			
		||||
    private countText: string;
 | 
			
		||||
    private archivedCountText: string;
 | 
			
		||||
    private createdText!: string;
 | 
			
		||||
    private countText!: string;
 | 
			
		||||
    private archivedCountText!: string;
 | 
			
		||||
    private parentId: number | null = null;
 | 
			
		||||
    private contentReference: string = '';
 | 
			
		||||
    private formReplyText: string = '';
 | 
			
		||||
| 
						 | 
				
			
			@ -92,19 +80,19 @@ export class PageComments extends Component {
 | 
			
		|||
            this.hideForm();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.elem.addEventListener('page-comment-reply', (event: CommentReplyEvent) => {
 | 
			
		||||
        this.elem.addEventListener('page-comment-reply', ((event: CustomEvent<PageCommentReplyEventData>) => {
 | 
			
		||||
            this.setReply(event.detail.id, event.detail.element);
 | 
			
		||||
        });
 | 
			
		||||
        }) as EventListener);
 | 
			
		||||
 | 
			
		||||
        this.elem.addEventListener('page-comment-archive', (event: ArchiveEvent) => {
 | 
			
		||||
        this.elem.addEventListener('page-comment-archive', ((event: CustomEvent<PageCommentArchiveEventData>) => {
 | 
			
		||||
            this.archiveContainer.append(event.detail.new_thread_dom);
 | 
			
		||||
            setTimeout(() => this.updateCount(), 1);
 | 
			
		||||
        });
 | 
			
		||||
        }) as EventListener);
 | 
			
		||||
 | 
			
		||||
        this.elem.addEventListener('page-comment-unarchive', (event: ArchiveEvent) => {
 | 
			
		||||
        this.elem.addEventListener('page-comment-unarchive', ((event: CustomEvent<PageCommentArchiveEventData>) => {
 | 
			
		||||
            this.container.append(event.detail.new_thread_dom);
 | 
			
		||||
            setTimeout(() => this.updateCount(), 1);
 | 
			
		||||
        });
 | 
			
		||||
        }) as EventListener);
 | 
			
		||||
 | 
			
		||||
        if (this.form) {
 | 
			
		||||
            this.removeReplyToButton.addEventListener('click', this.removeReplyTo.bind(this));
 | 
			
		||||
| 
						 | 
				
			
			@ -115,7 +103,7 @@ export class PageComments extends Component {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected saveComment(event): void {
 | 
			
		||||
    protected saveComment(event: SubmitEvent): void {
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
        event.stopPropagation();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -209,10 +197,10 @@ export class PageComments extends Component {
 | 
			
		|||
            drawioUrl: '',
 | 
			
		||||
            pageId: 0,
 | 
			
		||||
            translations: {},
 | 
			
		||||
            translationMap: (window as Record<string, Object>).editor_translations,
 | 
			
		||||
            translationMap: (window as unknown as Record<string, Object>).editor_translations,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        (window as {tinymce: {init: (Object) => Promise<any>}}).tinymce.init(config).then(editors => {
 | 
			
		||||
        (window as unknown as {tinymce: {init: (arg0: Object) => Promise<any>}}).tinymce.init(config).then(editors => {
 | 
			
		||||
            this.wysiwygEditor = editors[0];
 | 
			
		||||
            setTimeout(() => this.wysiwygEditor.focus(), 50);
 | 
			
		||||
        });
 | 
			
		||||
| 
						 | 
				
			
			@ -233,11 +221,11 @@ export class PageComments extends Component {
 | 
			
		|||
        return this.archiveContainer.querySelectorAll(':scope > .comment-branch').length;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected setReply(commentLocalId, commentElement): void {
 | 
			
		||||
        const targetFormLocation = commentElement.closest('.comment-branch').querySelector('.comment-branch-children');
 | 
			
		||||
    protected setReply(commentLocalId: string, commentElement: HTMLElement): void {
 | 
			
		||||
        const targetFormLocation = (commentElement.closest('.comment-branch') as HTMLElement).querySelector('.comment-branch-children') as HTMLElement;
 | 
			
		||||
        targetFormLocation.append(this.formContainer);
 | 
			
		||||
        this.showForm();
 | 
			
		||||
        this.parentId = commentLocalId;
 | 
			
		||||
        this.parentId = Number(commentLocalId);
 | 
			
		||||
        this.replyToRow.toggleAttribute('hidden', false);
 | 
			
		||||
        this.formReplyLink.textContent = this.formReplyText.replace('1234', String(this.parentId));
 | 
			
		||||
        this.formReplyLink.href = `#comment${this.parentId}`;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
import * as DOM from '../services/dom.ts';
 | 
			
		||||
import * as DOM from '../services/dom';
 | 
			
		||||
import {Component} from './component';
 | 
			
		||||
import {copyTextToClipboard} from '../services/clipboard.ts';
 | 
			
		||||
import {hashElement, normalizeNodeTextOffsetToParent} from "../services/dom.ts";
 | 
			
		||||
import {copyTextToClipboard} from '../services/clipboard';
 | 
			
		||||
import {hashElement, normalizeNodeTextOffsetToParent} from "../services/dom";
 | 
			
		||||
import {PageComments} from "./page-comments";
 | 
			
		||||
 | 
			
		||||
export class Pointer extends Component {
 | 
			
		||||
| 
						 | 
				
			
			@ -11,16 +11,16 @@ export class Pointer extends Component {
 | 
			
		|||
    protected targetElement: HTMLElement|null = null;
 | 
			
		||||
    protected targetSelectionRange: Range|null = null;
 | 
			
		||||
 | 
			
		||||
    protected pointer: HTMLElement;
 | 
			
		||||
    protected linkInput: HTMLInputElement;
 | 
			
		||||
    protected linkButton: HTMLElement;
 | 
			
		||||
    protected includeInput: HTMLInputElement;
 | 
			
		||||
    protected includeButton: HTMLElement;
 | 
			
		||||
    protected sectionModeButton: HTMLElement;
 | 
			
		||||
    protected commentButton: HTMLElement;
 | 
			
		||||
    protected modeToggles: HTMLElement[];
 | 
			
		||||
    protected modeSections: HTMLElement[];
 | 
			
		||||
    protected pageId: string;
 | 
			
		||||
    protected pointer!: HTMLElement;
 | 
			
		||||
    protected linkInput!: HTMLInputElement;
 | 
			
		||||
    protected linkButton!: HTMLElement;
 | 
			
		||||
    protected includeInput!: HTMLInputElement;
 | 
			
		||||
    protected includeButton!: HTMLElement;
 | 
			
		||||
    protected sectionModeButton!: HTMLElement;
 | 
			
		||||
    protected commentButton!: HTMLElement;
 | 
			
		||||
    protected modeToggles!: HTMLElement[];
 | 
			
		||||
    protected modeSections!: HTMLElement[];
 | 
			
		||||
    protected pageId!: string;
 | 
			
		||||
 | 
			
		||||
    setup() {
 | 
			
		||||
        this.pointer = this.$refs.pointer;
 | 
			
		||||
| 
						 | 
				
			
			@ -67,7 +67,7 @@ export class Pointer extends Component {
 | 
			
		|||
        DOM.onEvents(pageContent, ['mouseup', 'keyup'], event => {
 | 
			
		||||
            event.stopPropagation();
 | 
			
		||||
            const targetEl = (event.target as HTMLElement).closest('[id^="bkmrk"]');
 | 
			
		||||
            if (targetEl && window.getSelection().toString().length > 0) {
 | 
			
		||||
            if (targetEl instanceof HTMLElement && (window.getSelection() || '').toString().length > 0) {
 | 
			
		||||
                const xPos = (event instanceof MouseEvent) ? event.pageX : 0;
 | 
			
		||||
                this.showPointerAtTarget(targetEl, xPos, false);
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -102,11 +102,8 @@ export class Pointer extends Component {
 | 
			
		|||
 | 
			
		||||
    /**
 | 
			
		||||
     * Move and display the pointer at the given element, targeting the given screen x-position if possible.
 | 
			
		||||
     * @param {Element} element
 | 
			
		||||
     * @param {Number} xPosition
 | 
			
		||||
     * @param {Boolean} keyboardMode
 | 
			
		||||
     */
 | 
			
		||||
    showPointerAtTarget(element, xPosition, keyboardMode) {
 | 
			
		||||
    showPointerAtTarget(element: HTMLElement, xPosition: number, keyboardMode: boolean) {
 | 
			
		||||
        this.targetElement = element;
 | 
			
		||||
        this.targetSelectionRange = window.getSelection()?.getRangeAt(0) || null;
 | 
			
		||||
        this.updateDomForTarget(element);
 | 
			
		||||
| 
						 | 
				
			
			@ -134,7 +131,7 @@ export class Pointer extends Component {
 | 
			
		|||
            window.removeEventListener('scroll', scrollListener);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        element.parentElement.insertBefore(this.pointer, element);
 | 
			
		||||
        element.parentElement?.insertBefore(this.pointer, element);
 | 
			
		||||
        if (!keyboardMode) {
 | 
			
		||||
            window.addEventListener('scroll', scrollListener, {passive: true});
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -142,9 +139,8 @@ export class Pointer extends Component {
 | 
			
		|||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the pointer inputs/content for the given target element.
 | 
			
		||||
     * @param {?Element} element
 | 
			
		||||
     */
 | 
			
		||||
    updateDomForTarget(element) {
 | 
			
		||||
    updateDomForTarget(element: HTMLElement) {
 | 
			
		||||
        const permaLink = window.baseUrl(`/link/${this.pageId}#${element.id}`);
 | 
			
		||||
        const includeTag = `{{@${this.pageId}#${element.id}}}`;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -158,13 +154,13 @@ export class Pointer extends Component {
 | 
			
		|||
            const elementId = element.id;
 | 
			
		||||
 | 
			
		||||
            // Get the first 50 characters.
 | 
			
		||||
            const queryContent = element.textContent && element.textContent.substring(0, 50);
 | 
			
		||||
            const queryContent = (element.textContent || '').substring(0, 50);
 | 
			
		||||
            editAnchor.href = `${editHref}?content-id=${elementId}&content-text=${encodeURIComponent(queryContent)}`;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    enterSectionSelectMode() {
 | 
			
		||||
        const sections = Array.from(document.querySelectorAll('.page-content [id^="bkmrk"]'));
 | 
			
		||||
        const sections = Array.from(document.querySelectorAll('.page-content [id^="bkmrk"]')) as HTMLElement[];
 | 
			
		||||
        for (const section of sections) {
 | 
			
		||||
            section.setAttribute('tabindex', '0');
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -172,12 +168,12 @@ export class Pointer extends Component {
 | 
			
		|||
        sections[0].focus();
 | 
			
		||||
 | 
			
		||||
        DOM.onEnterPress(sections, event => {
 | 
			
		||||
            this.showPointerAtTarget(event.target, 0, true);
 | 
			
		||||
            this.showPointerAtTarget(event.target as HTMLElement, 0, true);
 | 
			
		||||
            this.pointer.focus();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    createCommentAtPointer(event) {
 | 
			
		||||
    createCommentAtPointer() {
 | 
			
		||||
        if (!this.targetElement) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,9 @@
 | 
			
		|||
import {Component} from './component';
 | 
			
		||||
 | 
			
		||||
export interface TabsChangeEvent {
 | 
			
		||||
    showing: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tabs
 | 
			
		||||
 * Uses accessible attributes to drive its functionality.
 | 
			
		||||
| 
						 | 
				
			
			@ -19,12 +23,12 @@ import {Component} from './component';
 | 
			
		|||
 */
 | 
			
		||||
export class Tabs extends Component {
 | 
			
		||||
 | 
			
		||||
    protected container: HTMLElement;
 | 
			
		||||
    protected tabList: HTMLElement;
 | 
			
		||||
    protected tabs: HTMLElement[];
 | 
			
		||||
    protected panels: HTMLElement[];
 | 
			
		||||
    protected container!: HTMLElement;
 | 
			
		||||
    protected tabList!: HTMLElement;
 | 
			
		||||
    protected tabs!: HTMLElement[];
 | 
			
		||||
    protected panels!: HTMLElement[];
 | 
			
		||||
 | 
			
		||||
    protected activeUnder: number;
 | 
			
		||||
    protected activeUnder!: number;
 | 
			
		||||
    protected active: null|boolean = null;
 | 
			
		||||
 | 
			
		||||
    setup() {
 | 
			
		||||
| 
						 | 
				
			
			@ -58,7 +62,8 @@ export class Tabs extends Component {
 | 
			
		|||
            tab.setAttribute('aria-selected', selected ? 'true' : 'false');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.$emit('change', {showing: sectionId});
 | 
			
		||||
        const data: TabsChangeEvent = {showing: sectionId};
 | 
			
		||||
        this.$emit('change', data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected updateActiveState(): void {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -225,7 +225,7 @@ export function findTargetNodeAndOffset(parentNode: HTMLElement, offset: number)
 | 
			
		|||
        if (currentNode.nodeType === Node.TEXT_NODE) {
 | 
			
		||||
            // For text nodes, count the length of their content
 | 
			
		||||
            // Returns if within range
 | 
			
		||||
            const textLength = currentNode.textContent.length;
 | 
			
		||||
            const textLength = (currentNode.textContent || '').length;
 | 
			
		||||
            if (currentOffset + textLength >= offset) {
 | 
			
		||||
                return {
 | 
			
		||||
                    node: currentNode,
 | 
			
		||||
| 
						 | 
				
			
			@ -237,9 +237,9 @@ export function findTargetNodeAndOffset(parentNode: HTMLElement, offset: number)
 | 
			
		|||
        } else if (currentNode.nodeType === Node.ELEMENT_NODE) {
 | 
			
		||||
            // Otherwise, if an element, track the text length and search within
 | 
			
		||||
            // if in range for the target offset
 | 
			
		||||
            const elementTextLength = currentNode.textContent.length;
 | 
			
		||||
            const elementTextLength = (currentNode.textContent || '').length;
 | 
			
		||||
            if (currentOffset + elementTextLength >= offset) {
 | 
			
		||||
                return findTargetNodeAndOffset(currentNode, offset - currentOffset);
 | 
			
		||||
                return findTargetNodeAndOffset(currentNode as HTMLElement, offset - currentOffset);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            currentOffset += elementTextLength;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,9 @@
 | 
			
		|||
import {HttpError} from "./http";
 | 
			
		||||
 | 
			
		||||
type Listener = (data: any) => void;
 | 
			
		||||
 | 
			
		||||
export class EventManager {
 | 
			
		||||
    protected listeners: Record<string, ((data: any) => void)[]> = {};
 | 
			
		||||
    protected listeners: Record<string, Listener[]> = {};
 | 
			
		||||
    protected stack: {name: string, data: {}}[] = [];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			@ -27,7 +29,7 @@ export class EventManager {
 | 
			
		|||
    /**
 | 
			
		||||
     * Remove an event listener which is using the given callback for the given event name.
 | 
			
		||||
     */
 | 
			
		||||
    remove(eventName: string, callback: Function): void {
 | 
			
		||||
    remove(eventName: string, callback: Listener): void {
 | 
			
		||||
        const listeners = this.listeners[eventName] || [];
 | 
			
		||||
        const index = listeners.indexOf(callback);
 | 
			
		||||
        if (index !== -1) {
 | 
			
		||||
| 
						 | 
				
			
			@ -64,8 +66,7 @@ export class EventManager {
 | 
			
		|||
    /**
 | 
			
		||||
     * Notify of standard server-provided validation errors.
 | 
			
		||||
     */
 | 
			
		||||
    showValidationErrors(responseErr: {status?: number, data?: object}): void {
 | 
			
		||||
        if (!responseErr.status) return;
 | 
			
		||||
    showValidationErrors(responseErr: HttpError): void {
 | 
			
		||||
        if (responseErr.status === 422 && responseErr.data) {
 | 
			
		||||
            const message = Object.values(responseErr.data).flat().join('\n');
 | 
			
		||||
            this.error(message);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue