Skip to content

Commit 3bfd26b

Browse files
committed
Converted the page editor from vue to component
1 parent 9d6f574 commit 3bfd26b

File tree

11 files changed

+262
-220
lines changed

11 files changed

+262
-220
lines changed

app/Http/Controllers/PageController.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ public function show(string $bookSlug, string $pageSlug)
163163
public function getPageAjax(int $pageId)
164164
{
165165
$page = $this->pageRepo->getById($pageId);
166+
$page->setHidden(array_diff($page->getHidden(), ['html', 'markdown']));
167+
$page->addHidden(['book']);
166168
return response()->json($page);
167169
}
168170

resources/js/components/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ function parseOpts(name, element) {
129129
function kebabToCamel(kebab) {
130130
const ucFirst = (word) => word.slice(0,1).toUpperCase() + word.slice(1);
131131
const words = kebab.split('-');
132-
return words[0] + words.slice(1).map(ucFirst).join();
132+
return words[0] + words.slice(1).map(ucFirst).join('');
133133
}
134134

135135
/**

resources/js/components/markdown-editor.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@ import DrawIO from "../services/drawio";
88

99
class MarkdownEditor {
1010

11-
constructor(elem) {
12-
this.elem = elem;
11+
setup() {
12+
this.elem = this.$el;
1313

14-
const pageEditor = document.getElementById('page-editor');
15-
this.pageId = pageEditor.getAttribute('page-id');
16-
this.textDirection = pageEditor.getAttribute('text-direction');
14+
this.pageId = this.$opts.pageId;
15+
this.textDirection = this.$opts.textDirection;
1716

1817
this.markdown = new MarkdownIt({html: true});
1918
this.markdown.use(mdTasksLists, {label: true});
@@ -27,12 +26,18 @@ class MarkdownEditor {
2726

2827
this.onMarkdownScroll = this.onMarkdownScroll.bind(this);
2928

30-
this.display.addEventListener('load', () => {
29+
const displayLoad = () => {
3130
this.displayDoc = this.display.contentDocument;
3231
this.init();
33-
});
32+
};
33+
34+
if (this.display.contentDocument.readyState === 'complete') {
35+
displayLoad();
36+
} else {
37+
this.display.addEventListener('load', displayLoad.bind(this));
38+
}
3439

35-
window.$events.emitPublic(elem, 'editor-markdown::setup', {
40+
window.$events.emitPublic(this.elem, 'editor-markdown::setup', {
3641
markdownIt: this.markdown,
3742
displayEl: this.display,
3843
codeMirrorInstance: this.cm,
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import * as Dates from "../services/dates";
2+
import {onSelect} from "../services/dom";
3+
4+
/**
5+
* Page Editor
6+
* @extends {Component}
7+
*/
8+
class PageEditor {
9+
setup() {
10+
// Options
11+
this.draftsEnabled = this.$opts.draftsEnabled === 'true';
12+
this.editorType = this.$opts.editorType;
13+
this.pageId = Number(this.$opts.pageId);
14+
this.isNewDraft = this.$opts.pageNewDraft === 'true';
15+
this.hasDefaultTitle = this.$opts.isDefaultTitle || false;
16+
17+
// Elements
18+
this.container = this.$el;
19+
this.titleElem = this.$refs.titleContainer.querySelector('input');
20+
this.saveDraftButton = this.$refs.saveDraft;
21+
this.discardDraftButton = this.$refs.discardDraft;
22+
this.discardDraftWrap = this.$refs.discardDraftWrap;
23+
this.draftDisplay = this.$refs.draftDisplay;
24+
this.draftDisplayIcon = this.$refs.draftDisplayIcon;
25+
this.changelogInput = this.$refs.changelogInput;
26+
this.changelogDisplay = this.$refs.changelogDisplay;
27+
28+
// Translations
29+
this.draftText = this.$opts.draftText;
30+
this.autosaveFailText = this.$opts.autosaveFailText;
31+
this.editingPageText = this.$opts.editingPageText;
32+
this.draftDiscardedText = this.$opts.draftDiscardedText;
33+
this.setChangelogText = this.$opts.setChangelogText;
34+
35+
// State data
36+
this.editorHTML = '';
37+
this.editorMarkdown = '';
38+
this.autoSave = {
39+
interval: null,
40+
frequency: 30000,
41+
last: 0,
42+
};
43+
this.draftHasError = false;
44+
45+
if (this.pageId !== 0 && this.draftsEnabled) {
46+
window.setTimeout(() => {
47+
this.startAutoSave();
48+
}, 1000);
49+
}
50+
this.draftDisplay.innerHTML = this.draftText;
51+
52+
this.setupListeners();
53+
this.setInitialFocus();
54+
}
55+
56+
setupListeners() {
57+
// Listen to save events from editor
58+
window.$events.listen('editor-save-draft', this.saveDraft.bind(this));
59+
window.$events.listen('editor-save-page', this.savePage.bind(this));
60+
61+
// Listen to content changes from the editor
62+
window.$events.listen('editor-html-change', html => {
63+
this.editorHTML = html;
64+
});
65+
window.$events.listen('editor-markdown-change', markdown => {
66+
this.editorMarkdown = markdown;
67+
});
68+
69+
// Changelog controls
70+
this.changelogInput.addEventListener('change', this.updateChangelogDisplay.bind(this));
71+
72+
// Draft Controls
73+
onSelect(this.saveDraftButton, this.saveDraft.bind(this));
74+
onSelect(this.discardDraftButton, this.discardDraft.bind(this));
75+
}
76+
77+
setInitialFocus() {
78+
if (this.hasDefaultTitle) {
79+
return this.titleElem.select();
80+
}
81+
82+
window.setTimeout(() => {
83+
window.$events.emit('editor::focus', '');
84+
}, 500);
85+
}
86+
87+
startAutoSave() {
88+
let lastContent = this.titleElem.value.trim() + '::' + this.editorHTML;
89+
this.autoSaveInterval = window.setInterval(() => {
90+
// Stop if manually saved recently to prevent bombarding the server
91+
let savedRecently = (Date.now() - this.autoSave.last < (this.autoSave.frequency)/2);
92+
if (savedRecently) return;
93+
const newContent = this.titleElem.value.trim() + '::' + this.editorHTML;
94+
if (newContent !== lastContent) {
95+
lastContent = newContent;
96+
this.saveDraft();
97+
}
98+
99+
}, this.autoSave.frequency);
100+
}
101+
102+
savePage() {
103+
this.container.closest('form').submit();
104+
}
105+
106+
async saveDraft() {
107+
const data = {
108+
name: this.titleElem.value.trim(),
109+
html: this.editorHTML,
110+
};
111+
112+
if (this.editorType === 'markdown') {
113+
data.markdown = this.editorMarkdown;
114+
}
115+
116+
try {
117+
const resp = await window.$http.put(`/ajax/page/${this.pageId}/save-draft`, data);
118+
this.draftHasError = false;
119+
if (!this.isNewDraft) {
120+
this.toggleDiscardDraftVisibility(true);
121+
}
122+
this.draftNotifyChange(`${resp.data.message} ${Dates.utcTimeStampToLocalTime(resp.data.timestamp)}`);
123+
this.autoSave.last = Date.now();
124+
} catch (err) {
125+
if (!this.draftHasError) {
126+
this.draftHasError = true;
127+
window.$events.emit('error', this.autosaveFailText);
128+
}
129+
}
130+
131+
}
132+
133+
draftNotifyChange(text) {
134+
this.draftDisplay.innerText = text;
135+
this.draftDisplayIcon.classList.add('visible');
136+
window.setTimeout(() => {
137+
this.draftDisplayIcon.classList.remove('visible');
138+
}, 2000);
139+
}
140+
141+
async discardDraft() {
142+
let response;
143+
try {
144+
response = await window.$http.get(`/ajax/page/${this.pageId}`);
145+
} catch (e) {
146+
return console.error(e);
147+
}
148+
149+
if (this.autoSave.interval) {
150+
window.clearInterval(this.autoSave.interval);
151+
}
152+
153+
this.draftDisplay.innerText = this.editingPageText;
154+
this.toggleDiscardDraftVisibility(false);
155+
window.$events.emit('editor-html-update', response.data.html || '');
156+
window.$events.emit('editor-markdown-update', response.data.markdown || response.data.html);
157+
158+
this.titleElem.value = response.data.name;
159+
window.setTimeout(() => {
160+
this.startAutoSave();
161+
}, 1000);
162+
window.$events.emit('success', this.draftDiscardedText);
163+
164+
}
165+
166+
updateChangelogDisplay() {
167+
let summary = this.changelogInput.value.trim();
168+
if (summary.length === 0) {
169+
summary = this.setChangelogText;
170+
} else if (summary.length > 16) {
171+
summary = summary.slice(0, 16) + '...';
172+
}
173+
this.changelogDisplay.innerText = summary;
174+
}
175+
176+
toggleDiscardDraftVisibility(show) {
177+
this.discardDraftWrap.classList.toggle('hidden', !show);
178+
}
179+
180+
}
181+
182+
export default PageEditor;

resources/js/components/wysiwyg-editor.js

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ function codePlugin() {
236236
});
237237
}
238238

239-
function drawIoPlugin(drawioUrl, isDarkMode) {
239+
function drawIoPlugin(drawioUrl, isDarkMode, pageId) {
240240

241241
let pageEditor = null;
242242
let currentNode = null;
@@ -270,7 +270,6 @@ function drawIoPlugin(drawioUrl, isDarkMode) {
270270
async function updateContent(pngData) {
271271
const id = "image-" + Math.random().toString(16).slice(2);
272272
const loadingImage = window.baseUrl('/loading.gif');
273-
const pageId = Number(document.getElementById('page-editor').getAttribute('page-id'));
274273

275274
// Handle updating an existing image
276275
if (currentNode) {
@@ -410,19 +409,19 @@ function listenForBookStackEditorEvents(editor) {
410409

411410
class WysiwygEditor {
412411

413-
constructor(elem) {
414-
this.elem = elem;
415412

416-
const pageEditor = document.getElementById('page-editor');
417-
this.pageId = pageEditor.getAttribute('page-id');
418-
this.textDirection = pageEditor.getAttribute('text-direction');
413+
setup() {
414+
this.elem = this.$el;
415+
416+
this.pageId = this.$opts.pageId;
417+
this.textDirection = this.$opts.textDirection;
419418
this.isDarkMode = document.documentElement.classList.contains('dark-mode');
420419

421420
this.plugins = "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists codeeditor media";
422421
this.loadPlugins();
423422

424423
this.tinyMceConfig = this.getTinyMceConfig();
425-
window.$events.emitPublic(elem, 'editor-tinymce::pre-init', {config: this.tinyMceConfig});
424+
window.$events.emitPublic(this.elem, 'editor-tinymce::pre-init', {config: this.tinyMceConfig});
426425
window.tinymce.init(this.tinyMceConfig);
427426
}
428427

@@ -433,7 +432,7 @@ class WysiwygEditor {
433432
const drawioUrlElem = document.querySelector('[drawio-url]');
434433
if (drawioUrlElem) {
435434
const url = drawioUrlElem.getAttribute('drawio-url');
436-
drawIoPlugin(url, this.isDarkMode);
435+
drawIoPlugin(url, this.isDarkMode, this.pageId);
437436
this.plugins += ' drawio';
438437
}
439438

0 commit comments

Comments
 (0)