mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-05-29 03:22:00 +08:00
Converted the page editor from vue to component
This commit is contained in:
@ -129,7 +129,7 @@ function parseOpts(name, element) {
|
||||
function kebabToCamel(kebab) {
|
||||
const ucFirst = (word) => word.slice(0,1).toUpperCase() + word.slice(1);
|
||||
const words = kebab.split('-');
|
||||
return words[0] + words.slice(1).map(ucFirst).join();
|
||||
return words[0] + words.slice(1).map(ucFirst).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,12 +8,11 @@ import DrawIO from "../services/drawio";
|
||||
|
||||
class MarkdownEditor {
|
||||
|
||||
constructor(elem) {
|
||||
this.elem = elem;
|
||||
setup() {
|
||||
this.elem = this.$el;
|
||||
|
||||
const pageEditor = document.getElementById('page-editor');
|
||||
this.pageId = pageEditor.getAttribute('page-id');
|
||||
this.textDirection = pageEditor.getAttribute('text-direction');
|
||||
this.pageId = this.$opts.pageId;
|
||||
this.textDirection = this.$opts.textDirection;
|
||||
|
||||
this.markdown = new MarkdownIt({html: true});
|
||||
this.markdown.use(mdTasksLists, {label: true});
|
||||
@ -27,12 +26,18 @@ class MarkdownEditor {
|
||||
|
||||
this.onMarkdownScroll = this.onMarkdownScroll.bind(this);
|
||||
|
||||
this.display.addEventListener('load', () => {
|
||||
const displayLoad = () => {
|
||||
this.displayDoc = this.display.contentDocument;
|
||||
this.init();
|
||||
});
|
||||
};
|
||||
|
||||
window.$events.emitPublic(elem, 'editor-markdown::setup', {
|
||||
if (this.display.contentDocument.readyState === 'complete') {
|
||||
displayLoad();
|
||||
} else {
|
||||
this.display.addEventListener('load', displayLoad.bind(this));
|
||||
}
|
||||
|
||||
window.$events.emitPublic(this.elem, 'editor-markdown::setup', {
|
||||
markdownIt: this.markdown,
|
||||
displayEl: this.display,
|
||||
codeMirrorInstance: this.cm,
|
||||
|
182
resources/js/components/page-editor.js
Normal file
182
resources/js/components/page-editor.js
Normal file
@ -0,0 +1,182 @@
|
||||
import * as Dates from "../services/dates";
|
||||
import {onSelect} from "../services/dom";
|
||||
|
||||
/**
|
||||
* Page Editor
|
||||
* @extends {Component}
|
||||
*/
|
||||
class PageEditor {
|
||||
setup() {
|
||||
// Options
|
||||
this.draftsEnabled = this.$opts.draftsEnabled === 'true';
|
||||
this.editorType = this.$opts.editorType;
|
||||
this.pageId = Number(this.$opts.pageId);
|
||||
this.isNewDraft = this.$opts.pageNewDraft === 'true';
|
||||
this.hasDefaultTitle = this.$opts.isDefaultTitle || false;
|
||||
|
||||
// Elements
|
||||
this.container = this.$el;
|
||||
this.titleElem = this.$refs.titleContainer.querySelector('input');
|
||||
this.saveDraftButton = this.$refs.saveDraft;
|
||||
this.discardDraftButton = this.$refs.discardDraft;
|
||||
this.discardDraftWrap = this.$refs.discardDraftWrap;
|
||||
this.draftDisplay = this.$refs.draftDisplay;
|
||||
this.draftDisplayIcon = this.$refs.draftDisplayIcon;
|
||||
this.changelogInput = this.$refs.changelogInput;
|
||||
this.changelogDisplay = this.$refs.changelogDisplay;
|
||||
|
||||
// Translations
|
||||
this.draftText = this.$opts.draftText;
|
||||
this.autosaveFailText = this.$opts.autosaveFailText;
|
||||
this.editingPageText = this.$opts.editingPageText;
|
||||
this.draftDiscardedText = this.$opts.draftDiscardedText;
|
||||
this.setChangelogText = this.$opts.setChangelogText;
|
||||
|
||||
// State data
|
||||
this.editorHTML = '';
|
||||
this.editorMarkdown = '';
|
||||
this.autoSave = {
|
||||
interval: null,
|
||||
frequency: 30000,
|
||||
last: 0,
|
||||
};
|
||||
this.draftHasError = false;
|
||||
|
||||
if (this.pageId !== 0 && this.draftsEnabled) {
|
||||
window.setTimeout(() => {
|
||||
this.startAutoSave();
|
||||
}, 1000);
|
||||
}
|
||||
this.draftDisplay.innerHTML = this.draftText;
|
||||
|
||||
this.setupListeners();
|
||||
this.setInitialFocus();
|
||||
}
|
||||
|
||||
setupListeners() {
|
||||
// Listen to save events from editor
|
||||
window.$events.listen('editor-save-draft', this.saveDraft.bind(this));
|
||||
window.$events.listen('editor-save-page', this.savePage.bind(this));
|
||||
|
||||
// Listen to content changes from the editor
|
||||
window.$events.listen('editor-html-change', html => {
|
||||
this.editorHTML = html;
|
||||
});
|
||||
window.$events.listen('editor-markdown-change', markdown => {
|
||||
this.editorMarkdown = markdown;
|
||||
});
|
||||
|
||||
// Changelog controls
|
||||
this.changelogInput.addEventListener('change', this.updateChangelogDisplay.bind(this));
|
||||
|
||||
// Draft Controls
|
||||
onSelect(this.saveDraftButton, this.saveDraft.bind(this));
|
||||
onSelect(this.discardDraftButton, this.discardDraft.bind(this));
|
||||
}
|
||||
|
||||
setInitialFocus() {
|
||||
if (this.hasDefaultTitle) {
|
||||
return this.titleElem.select();
|
||||
}
|
||||
|
||||
window.setTimeout(() => {
|
||||
window.$events.emit('editor::focus', '');
|
||||
}, 500);
|
||||
}
|
||||
|
||||
startAutoSave() {
|
||||
let lastContent = this.titleElem.value.trim() + '::' + this.editorHTML;
|
||||
this.autoSaveInterval = window.setInterval(() => {
|
||||
// Stop if manually saved recently to prevent bombarding the server
|
||||
let savedRecently = (Date.now() - this.autoSave.last < (this.autoSave.frequency)/2);
|
||||
if (savedRecently) return;
|
||||
const newContent = this.titleElem.value.trim() + '::' + this.editorHTML;
|
||||
if (newContent !== lastContent) {
|
||||
lastContent = newContent;
|
||||
this.saveDraft();
|
||||
}
|
||||
|
||||
}, this.autoSave.frequency);
|
||||
}
|
||||
|
||||
savePage() {
|
||||
this.container.closest('form').submit();
|
||||
}
|
||||
|
||||
async saveDraft() {
|
||||
const data = {
|
||||
name: this.titleElem.value.trim(),
|
||||
html: this.editorHTML,
|
||||
};
|
||||
|
||||
if (this.editorType === 'markdown') {
|
||||
data.markdown = this.editorMarkdown;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await window.$http.put(`/ajax/page/${this.pageId}/save-draft`, data);
|
||||
this.draftHasError = false;
|
||||
if (!this.isNewDraft) {
|
||||
this.toggleDiscardDraftVisibility(true);
|
||||
}
|
||||
this.draftNotifyChange(`${resp.data.message} ${Dates.utcTimeStampToLocalTime(resp.data.timestamp)}`);
|
||||
this.autoSave.last = Date.now();
|
||||
} catch (err) {
|
||||
if (!this.draftHasError) {
|
||||
this.draftHasError = true;
|
||||
window.$events.emit('error', this.autosaveFailText);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
draftNotifyChange(text) {
|
||||
this.draftDisplay.innerText = text;
|
||||
this.draftDisplayIcon.classList.add('visible');
|
||||
window.setTimeout(() => {
|
||||
this.draftDisplayIcon.classList.remove('visible');
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
async discardDraft() {
|
||||
let response;
|
||||
try {
|
||||
response = await window.$http.get(`/ajax/page/${this.pageId}`);
|
||||
} catch (e) {
|
||||
return console.error(e);
|
||||
}
|
||||
|
||||
if (this.autoSave.interval) {
|
||||
window.clearInterval(this.autoSave.interval);
|
||||
}
|
||||
|
||||
this.draftDisplay.innerText = this.editingPageText;
|
||||
this.toggleDiscardDraftVisibility(false);
|
||||
window.$events.emit('editor-html-update', response.data.html || '');
|
||||
window.$events.emit('editor-markdown-update', response.data.markdown || response.data.html);
|
||||
|
||||
this.titleElem.value = response.data.name;
|
||||
window.setTimeout(() => {
|
||||
this.startAutoSave();
|
||||
}, 1000);
|
||||
window.$events.emit('success', this.draftDiscardedText);
|
||||
|
||||
}
|
||||
|
||||
updateChangelogDisplay() {
|
||||
let summary = this.changelogInput.value.trim();
|
||||
if (summary.length === 0) {
|
||||
summary = this.setChangelogText;
|
||||
} else if (summary.length > 16) {
|
||||
summary = summary.slice(0, 16) + '...';
|
||||
}
|
||||
this.changelogDisplay.innerText = summary;
|
||||
}
|
||||
|
||||
toggleDiscardDraftVisibility(show) {
|
||||
this.discardDraftWrap.classList.toggle('hidden', !show);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default PageEditor;
|
@ -236,7 +236,7 @@ function codePlugin() {
|
||||
});
|
||||
}
|
||||
|
||||
function drawIoPlugin(drawioUrl, isDarkMode) {
|
||||
function drawIoPlugin(drawioUrl, isDarkMode, pageId) {
|
||||
|
||||
let pageEditor = null;
|
||||
let currentNode = null;
|
||||
@ -270,7 +270,6 @@ function drawIoPlugin(drawioUrl, isDarkMode) {
|
||||
async function updateContent(pngData) {
|
||||
const id = "image-" + Math.random().toString(16).slice(2);
|
||||
const loadingImage = window.baseUrl('/loading.gif');
|
||||
const pageId = Number(document.getElementById('page-editor').getAttribute('page-id'));
|
||||
|
||||
// Handle updating an existing image
|
||||
if (currentNode) {
|
||||
@ -410,19 +409,19 @@ function listenForBookStackEditorEvents(editor) {
|
||||
|
||||
class WysiwygEditor {
|
||||
|
||||
constructor(elem) {
|
||||
this.elem = elem;
|
||||
|
||||
const pageEditor = document.getElementById('page-editor');
|
||||
this.pageId = pageEditor.getAttribute('page-id');
|
||||
this.textDirection = pageEditor.getAttribute('text-direction');
|
||||
setup() {
|
||||
this.elem = this.$el;
|
||||
|
||||
this.pageId = this.$opts.pageId;
|
||||
this.textDirection = this.$opts.textDirection;
|
||||
this.isDarkMode = document.documentElement.classList.contains('dark-mode');
|
||||
|
||||
this.plugins = "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists codeeditor media";
|
||||
this.loadPlugins();
|
||||
|
||||
this.tinyMceConfig = this.getTinyMceConfig();
|
||||
window.$events.emitPublic(elem, 'editor-tinymce::pre-init', {config: this.tinyMceConfig});
|
||||
window.$events.emitPublic(this.elem, 'editor-tinymce::pre-init', {config: this.tinyMceConfig});
|
||||
window.tinymce.init(this.tinyMceConfig);
|
||||
}
|
||||
|
||||
@ -433,7 +432,7 @@ class WysiwygEditor {
|
||||
const drawioUrlElem = document.querySelector('[drawio-url]');
|
||||
if (drawioUrlElem) {
|
||||
const url = drawioUrlElem.getAttribute('drawio-url');
|
||||
drawIoPlugin(url, this.isDarkMode);
|
||||
drawIoPlugin(url, this.isDarkMode, this.pageId);
|
||||
this.plugins += ' drawio';
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user