mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-12-02 13:58:57 +08:00
e0d9380055
- Removed old 'editor-*-update' commands to instead use the aligned 'editor::replace' command that we already have. - Changed the way custom styles are loaded for the WYSIWYG editor so we don't need an API call but instead scape content from the parent page header using comments as identifiers. Added tests to ensure comments exist and align.
190 lines
6.2 KiB
JavaScript
190 lines
6.2 KiB
JavaScript
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.hasDefaultTitle || 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.shownWarningsCache = new Set();
|
|
|
|
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);
|
|
if (!this.isNewDraft) {
|
|
this.toggleDiscardDraftVisibility(true);
|
|
}
|
|
this.draftNotifyChange(`${resp.data.message} ${Dates.utcTimeStampToLocalTime(resp.data.timestamp)}`);
|
|
this.autoSave.last = Date.now();
|
|
if (resp.data.warning && !this.shownWarningsCache.has(resp.data.warning)) {
|
|
window.$events.emit('warning', resp.data.warning);
|
|
this.shownWarningsCache.add(resp.data.warning);
|
|
}
|
|
} catch (err) {
|
|
// Save the editor content in LocalStorage as a last resort, just in case.
|
|
try {
|
|
const saveKey = `draft-save-fail-${(new Date()).toISOString()}`;
|
|
window.localStorage.setItem(saveKey, JSON.stringify(data));
|
|
} catch (err) {}
|
|
|
|
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::replace', {
|
|
html: response.data.html,
|
|
markdown: response.data.markdown,
|
|
});
|
|
|
|
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; |