app: path.join(__dirname, '../../resources/js/app.ts'),
code: path.join(__dirname, '../../resources/js/code/index.mjs'),
'legacy-modes': path.join(__dirname, '../../resources/js/code/legacy-modes.mjs'),
- markdown: path.join(__dirname, '../../resources/js/markdown/index.mjs'),
+ markdown: path.join(__dirname, '../../resources/js/markdown/index.mts'),
wysiwyg: path.join(__dirname, '../../resources/js/wysiwyg/index.ts'),
};
'pages_md_insert_drawing' => 'Insert Drawing',
'pages_md_show_preview' => 'Show preview',
'pages_md_sync_scroll' => 'Sync preview scroll',
+ 'pages_md_plain_editor' => 'Plaintext editor',
'pages_drawing_unsaved' => 'Unsaved Drawing Found',
'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?',
'pages_not_in_chapter' => 'Page is not in a chapter',
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));
/**
* 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();
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);
-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
- */
-
-/**
- * Entity Selector
- */
+export interface EntitySelectorSearchOptions {
+ entityTypes: string;
+ entityPermission: string;
+ searchEndpoint: string;
+ initialValue: string;
+}
+
+export type EntitySelectorEntity = {
+ id: number,
+ name: string,
+ link: string,
+};
+
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;
initialValue: this.searchInput.value || '',
};
- this.search = '';
- this.lastClick = 0;
-
this.setupListeners();
this.showLoading();
}
}
- /**
- * @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;
});
// 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) {
if (event.code === 'ArrowUp') {
this.focusAdjacent(false);
}
- });
+ }) as (event: Event) => void);
this.searchInput.addEventListener('keydown', event => {
if (event.code === 'ArrowDown') {
}
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();
}
}
}
window.$http.get(this.searchUrl()).then(resp => {
- this.resultsContainer.innerHTML = resp.data;
+ this.resultsContainer.innerHTML = resp.data as string;
this.hideLoading();
});
}
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');
}
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();
});
}
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');
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;
}
}
- confirmSelection(data) {
+ confirmSelection(data: EntitySelectorEntity) {
window.$events.emit('entity-select-confirm', data);
}
});
}
+ /**
+ * @param {({ thumbs: { display: string; }; url: string; name: string; }) => void} callback
+ * @param {String} type
+ */
show(callback, type = 'gallery') {
this.resetAll();
-import * as DrawIO from '../services/drawio.ts';
+import * as DrawIO from '../services/drawio';
+import {MarkdownEditor} from "./index.mjs";
+import {EntitySelectorPopup, ImageManager} from "../components";
+import {ChangeSpec, SelectionRange, TransactionSpec} from "@codemirror/state";
+
+interface ImageManagerImage {
+ id: number;
+ name: string;
+ thumbs: { display: string; };
+ url: string;
+}
export class Actions {
- /**
- * @param {MarkdownEditor} editor
- */
- constructor(editor) {
+ protected readonly editor: MarkdownEditor;
+ protected lastContent: { html: string; markdown: string } = {
+ html: '',
+ markdown: '',
+ };
+
+ constructor(editor: MarkdownEditor) {
this.editor = editor;
- this.lastContent = {
- html: '',
- markdown: '',
- };
}
updateAndRender() {
}
showImageInsert() {
- /** @type {ImageManager} * */
- const imageManager = window.$components.first('image-manager');
+ const imageManager = window.$components.first('image-manager') as ImageManager;
- imageManager.show(image => {
+ imageManager.show((image: ImageManagerImage) => {
const imageUrl = image.thumbs?.display || image.url;
const selectedText = this.#getSelectionText();
const newText = `[](${image.url})`;
showImageManager() {
const selectionRange = this.#getSelectionRange();
- /** @type {ImageManager} * */
- const imageManager = window.$components.first('image-manager');
- imageManager.show(image => {
+ const imageManager = window.$components.first('image-manager') as ImageManager;
+ imageManager.show((image: ImageManagerImage) => {
this.#insertDrawing(image, selectionRange);
}, 'drawio');
}
showLinkSelector() {
const selectionRange = this.#getSelectionRange();
- /** @type {EntitySelectorPopup} * */
- const selector = window.$components.first('entity-selector-popup');
+ const selector = window.$components.first('entity-selector-popup') as EntitySelectorPopup;
const selectionText = this.#getSelectionText(selectionRange);
selector.show(entity => {
const selectedText = selectionText || entity.name;
try {
const resp = await window.$http.post('/images/drawio', data);
- this.#insertDrawing(resp.data, selectionRange);
+ this.#insertDrawing(resp.data as ImageManagerImage, selectionRange);
DrawIO.close();
} catch (err) {
this.handleDrawingUploadError(err);
});
}
- #insertDrawing(image, originalSelectionRange) {
+ #insertDrawing(image: ImageManagerImage, originalSelectionRange: SelectionRange) {
const newText = `<div drawio-diagram="${image.id}"><img src="${image.url}"></div>`;
this.#replaceSelection(newText, newText.length, originalSelectionRange);
}
// Show draw.io if enabled and handle save.
- editDrawing(imgContainer) {
+ editDrawing(imgContainer: HTMLElement) {
const {drawioUrl} = this.editor.config;
if (!drawioUrl) {
return;
}
const selectionRange = this.#getSelectionRange();
- const drawingId = imgContainer.getAttribute('drawio-diagram');
+ const drawingId = imgContainer.getAttribute('drawio-diagram') || '';
+ if (!drawingId) {
+ return;
+ }
DrawIO.show(drawioUrl, () => DrawIO.load(drawingId), async pngData => {
const data = {
try {
const resp = await window.$http.post('/images/drawio', data);
- const newText = `<div drawio-diagram="${resp.data.id}"><img src="${resp.data.url}"></div>`;
+ const image = resp.data as ImageManagerImage;
+ const newText = `<div drawio-diagram="${image.id}"><img src="${image.url}"></div>`;
const newContent = this.#getText().split('\n').map(line => {
if (line.indexOf(`drawio-diagram="${drawingId}"`) !== -1) {
return newText;
});
}
- handleDrawingUploadError(error) {
+ handleDrawingUploadError(error: any): void {
if (error.status === 413) {
window.$events.emit('error', this.editor.config.text.serverUploadLimit);
} else {
}
// Scroll to a specified text
- scrollToText(searchText) {
+ scrollToText(searchText: string): void {
if (!searchText) {
return;
}
/**
* Insert content into the editor.
- * @param {String} content
*/
- insertContent(content) {
+ insertContent(content: string) {
this.#replaceSelection(content, content.length);
}
/**
* Prepend content to the editor.
- * @param {String} content
*/
- prependContent(content) {
+ prependContent(content: string): void {
content = this.#cleanTextForEditor(content);
const selectionRange = this.#getSelectionRange();
const selectFrom = selectionRange.from + content.length + 1;
/**
* Append content to the editor.
- * @param {String} content
*/
- appendContent(content) {
+ appendContent(content: string): void {
content = this.#cleanTextForEditor(content);
- this.#dispatchChange(this.editor.cm.state.doc.length, `\n${content}`);
+ const end = this.editor.cm.state.doc.length;
+ this.#dispatchChange(end, end, `\n${content}`);
this.focus();
}
/**
* Replace the editor's contents
- * @param {String} content
*/
- replaceContent(content) {
+ replaceContent(content: string): void {
this.#setText(content);
}
* Replace the start of the line
* @param {String} newStart
*/
- replaceLineStart(newStart) {
+ replaceLineStart(newStart: string): void {
const selectionRange = this.#getSelectionRange();
const line = this.editor.cm.state.doc.lineAt(selectionRange.from);
/**
* Wrap the selection in the given contents start and end contents.
- * @param {String} start
- * @param {String} end
*/
- wrapSelection(start, end) {
+ wrapSelection(start: string, end: string): void {
const selectRange = this.#getSelectionRange();
const selectionText = this.#getSelectionText(selectRange);
if (!selectionText) {
const formats = ['info', 'success', 'warning', 'danger'];
const joint = formats.join('|');
const regex = new RegExp(`class="((${joint})\\s+callout|callout\\s+(${joint}))"`, 'i');
- const matches = regex.exec(line.text);
+ const matches = regex.exec(line.text) || [''];
const format = (matches ? (matches[2] || matches[3]) : '').toLowerCase();
if (format === formats[formats.length - 1]) {
}
}
- syncDisplayPosition(event) {
+ syncDisplayPosition(event: Event): void {
// Thanks to http://liuhao.im/english/2015/11/10/the-sync-scroll-of-markdown-editor-in-javascript.html
- const scrollEl = event.target;
+ const scrollEl = event.target as HTMLElement;
const atEnd = Math.abs(scrollEl.scrollHeight - scrollEl.clientHeight - scrollEl.scrollTop) < 1;
if (atEnd) {
this.editor.display.scrollToIndex(-1);
/**
* Fetch and insert the template of the given ID.
* The page-relative position provided can be used to determine insert location if possible.
- * @param {String} templateId
- * @param {Number} posX
- * @param {Number} posY
*/
- async insertTemplate(templateId, posX, posY) {
+ async insertTemplate(templateId: string, posX: number, posY: number): Promise<void> {
const cursorPos = this.editor.cm.posAtCoords({x: posX, y: posY}, false);
- const {data} = await window.$http.get(`/templates/${templateId}`);
- const content = data.markdown || data.html;
+ const responseData = (await window.$http.get(`/templates/${templateId}`)).data as {markdown: string, html: string};
+ const content = responseData.markdown || responseData.html;
this.#dispatchChange(cursorPos, cursorPos, content, cursorPos);
}
/**
* Insert multiple images from the clipboard from an event at the provided
* screen coordinates (Typically form a paste event).
- * @param {File[]} images
- * @param {Number} posX
- * @param {Number} posY
*/
- insertClipboardImages(images, posX, posY) {
+ insertClipboardImages(images: File[], posX: number, posY: number): void {
const cursorPos = this.editor.cm.posAtCoords({x: posX, y: posY}, false);
for (const image of images) {
this.uploadImage(image, cursorPos);
/**
* Handle image upload and add image into markdown content
- * @param {File} file
- * @param {?Number} position
*/
- async uploadImage(file, position = null) {
+ async uploadImage(file: File, position: number|null = null): Promise<void> {
if (file === null || file.type.indexOf('image') !== 0) return;
let ext = 'png';
if (file.name) {
const fileNameMatches = file.name.match(/\.(.+)$/);
- if (fileNameMatches.length > 1) ext = fileNameMatches[1];
+ if (fileNameMatches && fileNameMatches.length > 1) {
+ ext = fileNameMatches[1];
+ }
}
// Insert image into markdown
formData.append('uploaded_to', this.editor.config.pageId);
try {
- const {data} = await window.$http.post('/images/gallery', formData);
- const newContent = `[](${data.url})`;
+ const image = (await window.$http.post('/images/gallery', formData)).data as ImageManagerImage;
+ const newContent = `[](${image.url})`;
this.#findAndReplaceContent(placeHolderText, newContent);
- } catch (err) {
+ } catch (err: any) {
window.$events.error(err?.data?.message || this.editor.config.text.imageUploadError);
this.#findAndReplaceContent(placeHolderText, '');
console.error(err);
/**
* Set the text of the current editor instance.
- * @param {String} text
- * @param {?SelectionRange} selectionRange
*/
- #setText(text, selectionRange = null) {
+ #setText(text: string, selectionRange: SelectionRange|null = null) {
selectionRange = selectionRange || this.#getSelectionRange();
const newDoc = this.editor.cm.state.toText(text);
const newSelectFrom = Math.min(selectionRange.from, newDoc.length);
* Replace the current selection and focus the editor.
* Takes an offset for the cursor, after the change, relative to the start of the provided string.
* Can be provided a selection range to use instead of the current selection range.
- * @param {String} newContent
- * @param {Number} cursorOffset
- * @param {?SelectionRange} selectionRange
*/
- #replaceSelection(newContent, cursorOffset = 0, selectionRange = null) {
- selectionRange = selectionRange || this.editor.cm.state.selection.main;
+ #replaceSelection(newContent: string, cursorOffset: number = 0, selectionRange: SelectionRange|null = null) {
+ selectionRange = selectionRange || this.#getSelectionRange();
const selectFrom = selectionRange.from + cursorOffset;
this.#dispatchChange(selectionRange.from, selectionRange.to, newContent, selectFrom);
this.focus();
/**
* Get the text content of the main current selection.
- * @param {SelectionRange} selectionRange
- * @return {string}
*/
- #getSelectionText(selectionRange = null) {
+ #getSelectionText(selectionRange: SelectionRange|null = null): string {
selectionRange = selectionRange || this.#getSelectionRange();
return this.editor.cm.state.sliceDoc(selectionRange.from, selectionRange.to);
}
/**
* Get the range of the current main selection.
- * @return {SelectionRange}
*/
- #getSelectionRange() {
+ #getSelectionRange(): SelectionRange {
return this.editor.cm.state.selection.main;
}
/**
* Cleans the given text to work with the editor.
* Standardises line endings to what's expected.
- * @param {String} text
- * @return {String}
*/
- #cleanTextForEditor(text) {
+ #cleanTextForEditor(text: string): string {
return text.replace(/\r\n|\r/g, '\n');
}
/**
* Find and replace the first occurrence of [search] with [replace]
- * @param {String} search
- * @param {String} replace
*/
- #findAndReplaceContent(search, replace) {
+ #findAndReplaceContent(search: string, replace: string): void {
const newText = this.#getText().replace(search, replace);
this.#setText(newText);
}
/**
* Wrap the line in the given start and end contents.
- * @param {String} start
- * @param {String} end
*/
- #wrapLine(start, end) {
+ #wrapLine(start: string, end: string): void {
const selectionRange = this.#getSelectionRange();
const line = this.editor.cm.state.doc.lineAt(selectionRange.from);
const lineContent = line.text;
/**
* Dispatch changes to the editor.
- * @param {Number} from
- * @param {?Number} to
- * @param {?String} text
- * @param {?Number} selectFrom
- * @param {?Number} selectTo
*/
- #dispatchChange(from, to = null, text = null, selectFrom = null, selectTo = null) {
- const tr = {changes: {from, to, insert: text}};
+ #dispatchChange(from: number, to: number|null = null, text: string|null = null, selectFrom: number|null = null, selectTo: number|null = null): void {
+ const change: ChangeSpec = {from};
+ if (to) {
+ change.to = to;
+ }
+ if (text) {
+ change.insert = text;
+ }
+ const tr: TransactionSpec = {changes: change};
if (selectFrom) {
tr.selection = {anchor: selectFrom};
* @param {Number} to
* @param {Boolean} scrollIntoView
*/
- #setSelection(from, to, scrollIntoView = false) {
+ #setSelection(from: number, to: number, scrollIntoView = false) {
this.editor.cm.dispatch({
selection: {anchor: from, head: to},
scrollIntoView,
/**
* Initiate the codemirror instance for the markdown editor.
* @param {MarkdownEditor} editor
- * @returns {Promise<void>}
+ * @returns {Promise<EditorView>}
*/
export async function init(editor) {
const Code = await window.importVersioned('code');
+++ /dev/null
-function getContentToInsert({html, markdown}) {
- return markdown || html;
-}
-
-/**
- * @param {MarkdownEditor} editor
- */
-export function listen(editor) {
- window.$events.listen('editor::replace', eventContent => {
- const markdown = getContentToInsert(eventContent);
- editor.actions.replaceContent(markdown);
- });
-
- window.$events.listen('editor::append', eventContent => {
- const markdown = getContentToInsert(eventContent);
- editor.actions.appendContent(markdown);
- });
-
- window.$events.listen('editor::prepend', eventContent => {
- const markdown = getContentToInsert(eventContent);
- editor.actions.prependContent(markdown);
- });
-
- window.$events.listen('editor::insert', eventContent => {
- const markdown = getContentToInsert(eventContent);
- editor.actions.insertContent(markdown);
- });
-
- window.$events.listen('editor::focus', () => {
- editor.actions.focus();
- });
-}
--- /dev/null
+import {MarkdownEditor} from "./index.mjs";
+
+export interface HtmlOrMarkdown {
+ html: string;
+ markdown: string;
+}
+
+function getContentToInsert({html, markdown}: {html: string, markdown: string}): string {
+ return markdown || html;
+}
+
+export function listenToCommonEvents(editor: MarkdownEditor): void {
+ window.$events.listen('editor::replace', (eventContent: HtmlOrMarkdown) => {
+ const markdown = getContentToInsert(eventContent);
+ editor.actions.replaceContent(markdown);
+ });
+
+ window.$events.listen('editor::append', (eventContent: HtmlOrMarkdown) => {
+ const markdown = getContentToInsert(eventContent);
+ editor.actions.appendContent(markdown);
+ });
+
+ window.$events.listen('editor::prepend', (eventContent: HtmlOrMarkdown) => {
+ const markdown = getContentToInsert(eventContent);
+ editor.actions.prependContent(markdown);
+ });
+
+ window.$events.listen('editor::insert', (eventContent: HtmlOrMarkdown) => {
+ const markdown = getContentToInsert(eventContent);
+ editor.actions.insertContent(markdown);
+ });
+
+ window.$events.listen('editor::focus', () => {
+ editor.actions.focus();
+ });
+}
+++ /dev/null
-import {Markdown} from './markdown';
-import {Display} from './display';
-import {Actions} from './actions';
-import {Settings} from './settings';
-import {listen} from './common-events';
-import {init as initCodemirror} from './codemirror';
-
-/**
- * Initiate a new markdown editor instance.
- * @param {MarkdownEditorConfig} config
- * @returns {Promise<MarkdownEditor>}
- */
-export async function init(config) {
- /**
- * @type {MarkdownEditor}
- */
- const editor = {
- config,
- markdown: new Markdown(),
- settings: new Settings(config.settingInputs),
- };
-
- editor.actions = new Actions(editor);
- editor.display = new Display(editor);
- editor.cm = await initCodemirror(editor);
-
- listen(editor);
-
- return editor;
-}
-
-/**
- * @typedef MarkdownEditorConfig
- * @property {String} pageId
- * @property {Element} container
- * @property {Element} displayEl
- * @property {HTMLTextAreaElement} inputEl
- * @property {String} drawioUrl
- * @property {HTMLInputElement[]} settingInputs
- * @property {Object<String, String>} text
- */
-
-/**
- * @typedef MarkdownEditor
- * @property {MarkdownEditorConfig} config
- * @property {Display} display
- * @property {Markdown} markdown
- * @property {Actions} actions
- * @property {EditorView} cm
- * @property {Settings} settings
- */
--- /dev/null
+import {Markdown} from './markdown';
+import {Display} from './display';
+import {Actions} from './actions';
+import {Settings} from './settings';
+import {listenToCommonEvents} from './common-events';
+import {init as initCodemirror} from './codemirror';
+import {EditorView} from "@codemirror/view";
+
+export interface MarkdownEditorConfig {
+ pageId: string;
+ container: Element;
+ displayEl: Element;
+ inputEl: HTMLTextAreaElement;
+ drawioUrl: string;
+ settingInputs: HTMLInputElement[];
+ text: Record<string, string>;
+}
+
+export interface MarkdownEditor {
+ config: MarkdownEditorConfig;
+ display: Display;
+ markdown: Markdown;
+ actions: Actions;
+ cm: EditorView;
+ settings: Settings;
+}
+
+/**
+ * Initiate a new Markdown editor instance.
+ * @param {MarkdownEditorConfig} config
+ * @returns {Promise<MarkdownEditor>}
+ */
+export async function init(config) {
+ /**
+ * @type {MarkdownEditor}
+ */
+ const editor: MarkdownEditor = {
+ config,
+ markdown: new Markdown(),
+ settings: new Settings(config.settingInputs),
+ };
+
+ editor.actions = new Actions(editor);
+ editor.display = new Display(editor);
+ editor.cm = await initCodemirror(editor);
+
+ listenToCommonEvents(editor);
+
+ return editor;
+}
+
+
scrollSync: true,
showPreview: true,
editorWidth: 50,
+ plainEditor: false,
};
this.changeListeners = {};
this.loadFromLocalStorage();
<div class="px-m">
@include('form.custom-checkbox', ['name' => 'md-scrollSync', 'label' => trans('entities.pages_md_sync_scroll'), 'value' => true, 'checked' => true])
</div>
+ <hr class="m-none">
+ <div class="px-m">
+ @include('form.custom-checkbox', ['name' => 'md-plainEditor', 'label' => trans('entities.pages_md_plain_editor'), 'value' => true, 'checked' => false])
+ </div>
</div>
</div>
</div>