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