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; this.changeEditorButtons = this.$manyRefs.changeEditor; this.switchDialogContainer = this.$refs.switchDialog; // 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)); // Change editor controls onSelect(this.changeEditorButtons, this.changeEditor.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; } let didSave = false; 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); } didSave = true; } 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); } return didSave; } 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); } async changeEditor(event) { event.preventDefault(); const link = event.target.closest('a').href; const dialog = this.switchDialogContainer.components['confirm-dialog']; const [saved, confirmed] = await Promise.all([this.saveDraft(), dialog.show()]); if (saved && confirmed) { window.location = link; } } } export default PageEditor;