]> BookStack Code Mirror - bookstack/commitdiff
Comments: Fixed a range of TS errors + other
authorDan Brown <redacted>
Mon, 12 May 2025 14:31:55 +0000 (15:31 +0100)
committerDan Brown <redacted>
Mon, 12 May 2025 14:31:55 +0000 (15:31 +0100)
- Migrated toolbox component to TS
- Aligned how custom event types are managed
- Fixed PHP use of content_ref where not provided

app/Activity/CommentRepo.php
app/Activity/Controllers/CommentController.php
resources/js/components/editor-toolbox.ts [moved from resources/js/components/editor-toolbox.js with 61% similarity]
resources/js/components/page-comment-reference.ts
resources/js/components/page-comment.ts
resources/js/components/page-comments.ts
resources/js/components/pointer.ts
resources/js/components/tabs.ts
resources/js/services/dom.ts
resources/js/services/events.ts

index c194e72168ca09d2f52601fdd8ac7c9ee38cc343..7005f8fcf83d9dc788262e32cf76bb6f153ea1b5 100644 (file)
@@ -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);
index 7f16c17ffced39b3b0c534954dcd8eec9a77cd6a..479d57c4db941455bf34d8a158c6e186b12a686c 100644 (file)
@@ -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,
similarity index 61%
rename from resources/js/components/editor-toolbox.js
rename to resources/js/components/editor-toolbox.ts
index 95339328542a6a9946ee226ad49c42f608811c92..60bdde05efbc6ac598e1d17bc421acb7a0ae58a0 100644 (file)
@@ -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);
     }
 
 }
index 48fb8ee0a492a47380ad7b3432ec81eda675dc46..009e806c104d429cfa2451f4af68b20b26f803db 100644 (file)
@@ -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() {
index ce35cdc4a243e90e058217f5c45178b037dd3e1e..0c3e19f4b1a8b2b2996272be5ec1c4f55c9667a1 100644 (file)
@@ -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 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 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;
 
     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');
index 04c8125808cb8ffe00186073eb8176e0beea86d5..94f5ab3bb8e73ead67de167cb1381623e14fa260 100644 (file)
@@ -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}`;
index d84186d872d02ac317a74ed35fc96e1af669b392..4b927045aaedc4980101a30b1864d28c00f19037 100644 (file)
@@ -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;
         }
index 56405b8c78e2b409c509990d55aeac5362a033cb..a03d37cd48c9f36ee38c55b9eb52345d9503c0d6 100644 (file)
@@ -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 {
index 77c19a761058aa2aa9d2f08f8ba4f55e996a5298..c3817536c85422c8d0e480cbd05f267be3f6633f 100644 (file)
@@ -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;
index 7dae6dc29d96da23801f4c71d480d033e8612de1..6045d51f823846fae39803befb9efb1dc0d7cca3 100644 (file)
@@ -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);
Morty Proxy This is a proxified and sanitized view of the page, visit original site.