1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-04-19 18:22:16 +03:00
bookstack/resources/js/components/page-editor.js
Dan Brown 14ea6c9de3
Made fixes/updates during pre-release review
- Fixed page editor default focus not working as expected due to
  misnamed attribute.
- Added owned_by to relevant areas of the API including the docs.
- Made book relation on page accessible even if deleted since it could cause an issue on views, such as audit trail, when the relation is accessed when the book is deleted.
2021-01-03 22:29:58 +00:00

184 lines
6.1 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,
};
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() {
console.log({'HAS': this.hasDefaultTitle});
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();
} 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-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;