1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-08-07 23:03:00 +03:00

MD Editor: Starting conversion to typescript

This commit is contained in:
Dan Brown
2025-07-20 12:33:22 +01:00
parent 61f8d18af5
commit 7bbf591a7f
13 changed files with 237 additions and 229 deletions

View File

@@ -1,15 +1,23 @@
import {Component} from './component';
import {EntitySelector, EntitySelectorEntity, EntitySelectorSearchOptions} from "./entity-selector";
import {Popup} from "./popup";
export type EntitySelectorPopupCallback = (entity: EntitySelectorEntity) => void;
export class EntitySelectorPopup extends Component {
protected container!: HTMLElement;
protected selectButton!: HTMLElement;
protected selectorEl!: HTMLElement;
protected callback: EntitySelectorPopupCallback|null = null;
protected selection: EntitySelectorEntity|null = null;
setup() {
this.container = this.$el;
this.selectButton = this.$refs.select;
this.selectorEl = this.$refs.selector;
this.callback = null;
this.selection = null;
this.selectButton.addEventListener('click', this.onSelectButtonClick.bind(this));
window.$events.listen('entity-select-change', this.onSelectionChange.bind(this));
window.$events.listen('entity-select-confirm', this.handleConfirmedSelection.bind(this));
@@ -17,10 +25,8 @@ export class EntitySelectorPopup extends Component {
/**
* Show the selector popup.
* @param {Function} callback
* @param {EntitySelectorSearchOptions} searchOptions
*/
show(callback, searchOptions = {}) {
show(callback: EntitySelectorPopupCallback, searchOptions: Partial<EntitySelectorSearchOptions> = {}) {
this.callback = callback;
this.getSelector().configureSearchOptions(searchOptions);
this.getPopup().show();
@@ -32,34 +38,28 @@ export class EntitySelectorPopup extends Component {
this.getPopup().hide();
}
/**
* @returns {Popup}
*/
getPopup() {
return window.$components.firstOnElement(this.container, 'popup');
getPopup(): Popup {
return window.$components.firstOnElement(this.container, 'popup') as Popup;
}
/**
* @returns {EntitySelector}
*/
getSelector() {
return window.$components.firstOnElement(this.selectorEl, 'entity-selector');
getSelector(): EntitySelector {
return window.$components.firstOnElement(this.selectorEl, 'entity-selector') as EntitySelector;
}
onSelectButtonClick() {
this.handleConfirmedSelection(this.selection);
}
onSelectionChange(entity) {
this.selection = entity;
if (entity === null) {
onSelectionChange(entity: EntitySelectorEntity|{}) {
this.selection = (entity.hasOwnProperty('id') ? entity : null) as EntitySelectorEntity|null;
if (!this.selection) {
this.selectButton.setAttribute('disabled', 'true');
} else {
this.selectButton.removeAttribute('disabled');
}
}
handleConfirmedSelection(entity) {
handleConfirmedSelection(entity: EntitySelectorEntity|null): void {
this.hide();
this.getSelector().reset();
if (this.callback && entity) this.callback(entity);

View File

@@ -1,24 +1,36 @@
import {onChildEvent} from '../services/dom.ts';
import {onChildEvent} from '../services/dom';
import {Component} from './component';
/**
* @typedef EntitySelectorSearchOptions
* @property entityTypes string
* @property entityPermission string
* @property searchEndpoint string
* @property initialValue string
*/
export interface EntitySelectorSearchOptions {
entityTypes: string;
entityPermission: string;
searchEndpoint: string;
initialValue: string;
}
export type EntitySelectorEntity = {
id: number,
name: string,
link: string,
};
/**
* Entity Selector
*/
export class EntitySelector extends Component {
protected elem!: HTMLElement;
protected input!: HTMLInputElement;
protected searchInput!: HTMLInputElement;
protected loading!: HTMLElement;
protected resultsContainer!: HTMLElement;
protected searchOptions!: EntitySelectorSearchOptions;
protected search = '';
protected lastClick = 0;
setup() {
this.elem = this.$el;
this.input = this.$refs.input;
this.searchInput = this.$refs.search;
this.input = this.$refs.input as HTMLInputElement;
this.searchInput = this.$refs.search as HTMLInputElement;
this.loading = this.$refs.loading;
this.resultsContainer = this.$refs.results;
@@ -29,9 +41,6 @@ export class EntitySelector extends Component {
initialValue: this.searchInput.value || '',
};
this.search = '';
this.lastClick = 0;
this.setupListeners();
this.showLoading();
@@ -40,16 +49,13 @@ export class EntitySelector extends Component {
}
}
/**
* @param {EntitySelectorSearchOptions} options
*/
configureSearchOptions(options) {
configureSearchOptions(options: Partial<EntitySelectorSearchOptions>): void {
Object.assign(this.searchOptions, options);
this.reset();
this.searchInput.value = this.searchOptions.initialValue;
}
setupListeners() {
setupListeners(): void {
this.elem.addEventListener('click', this.onClick.bind(this));
let lastSearch = 0;
@@ -67,7 +73,7 @@ export class EntitySelector extends Component {
});
// Keyboard navigation
onChildEvent(this.$el, '[data-entity-type]', 'keydown', event => {
onChildEvent(this.$el, '[data-entity-type]', 'keydown', ((event: KeyboardEvent) => {
if (event.ctrlKey && event.code === 'Enter') {
const form = this.$el.closest('form');
if (form) {
@@ -83,7 +89,7 @@ export class EntitySelector extends Component {
if (event.code === 'ArrowUp') {
this.focusAdjacent(false);
}
});
}) as (event: Event) => void);
this.searchInput.addEventListener('keydown', event => {
if (event.code === 'ArrowDown') {
@@ -93,10 +99,10 @@ export class EntitySelector extends Component {
}
focusAdjacent(forward = true) {
const items = Array.from(this.resultsContainer.querySelectorAll('[data-entity-type]'));
const items: (Element|null)[] = Array.from(this.resultsContainer.querySelectorAll('[data-entity-type]'));
const selectedIndex = items.indexOf(document.activeElement);
const newItem = items[selectedIndex + (forward ? 1 : -1)] || items[0];
if (newItem) {
if (newItem instanceof HTMLElement) {
newItem.focus();
}
}
@@ -132,7 +138,7 @@ export class EntitySelector extends Component {
}
window.$http.get(this.searchUrl()).then(resp => {
this.resultsContainer.innerHTML = resp.data;
this.resultsContainer.innerHTML = resp.data as string;
this.hideLoading();
});
}
@@ -142,7 +148,7 @@ export class EntitySelector extends Component {
return `${this.searchOptions.searchEndpoint}?${query}`;
}
searchEntities(searchTerm) {
searchEntities(searchTerm: string) {
if (!this.searchOptions.searchEndpoint) {
throw new Error('Search endpoint not set for entity-selector load');
}
@@ -150,7 +156,7 @@ export class EntitySelector extends Component {
this.input.value = '';
const url = `${this.searchUrl()}&term=${encodeURIComponent(searchTerm)}`;
window.$http.get(url).then(resp => {
this.resultsContainer.innerHTML = resp.data;
this.resultsContainer.innerHTML = resp.data as string;
this.hideLoading();
});
}
@@ -162,16 +168,16 @@ export class EntitySelector extends Component {
return answer;
}
onClick(event) {
const listItem = event.target.closest('[data-entity-type]');
if (listItem) {
onClick(event: MouseEvent) {
const listItem = (event.target as HTMLElement).closest('[data-entity-type]');
if (listItem instanceof HTMLElement) {
event.preventDefault();
event.stopPropagation();
this.selectItem(listItem);
}
}
selectItem(item) {
selectItem(item: HTMLElement): void {
const isDblClick = this.isDoubleClick();
const type = item.getAttribute('data-entity-type');
const id = item.getAttribute('data-entity-id');
@@ -180,14 +186,14 @@ export class EntitySelector extends Component {
this.unselectAll();
this.input.value = isSelected ? `${type}:${id}` : '';
const link = item.getAttribute('href');
const name = item.querySelector('.entity-list-item-name').textContent;
const data = {id: Number(id), name, link};
const link = item.getAttribute('href') || '';
const name = item.querySelector('.entity-list-item-name')?.textContent || '';
const data: EntitySelectorEntity = {id: Number(id), name, link};
if (isSelected) {
item.classList.add('selected');
} else {
window.$events.emit('entity-select-change', null);
window.$events.emit('entity-select-change');
}
if (!isDblClick && !isSelected) return;
@@ -200,7 +206,7 @@ export class EntitySelector extends Component {
}
}
confirmSelection(data) {
confirmSelection(data: EntitySelectorEntity) {
window.$events.emit('entity-select-confirm', data);
}

View File

@@ -127,6 +127,10 @@ export class ImageManager extends Component {
});
}
/**
* @param {({ thumbs: { display: string; }; url: string; name: string; }) => void} callback
* @param {String} type
*/
show(callback, type = 'gallery') {
this.resetAll();