mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-08-26 12:16:07 +00:00
Converted the page editor from vue to component
This commit is contained in:
parent
9d6f574494
commit
3bfd26bf86
@ -163,6 +163,8 @@ class PageController extends Controller
|
|||||||
public function getPageAjax(int $pageId)
|
public function getPageAjax(int $pageId)
|
||||||
{
|
{
|
||||||
$page = $this->pageRepo->getById($pageId);
|
$page = $this->pageRepo->getById($pageId);
|
||||||
|
$page->setHidden(array_diff($page->getHidden(), ['html', 'markdown']));
|
||||||
|
$page->addHidden(['book']);
|
||||||
return response()->json($page);
|
return response()->json($page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ function parseOpts(name, element) {
|
|||||||
function kebabToCamel(kebab) {
|
function kebabToCamel(kebab) {
|
||||||
const ucFirst = (word) => word.slice(0,1).toUpperCase() + word.slice(1);
|
const ucFirst = (word) => word.slice(0,1).toUpperCase() + word.slice(1);
|
||||||
const words = kebab.split('-');
|
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 {
|
class MarkdownEditor {
|
||||||
|
|
||||||
constructor(elem) {
|
setup() {
|
||||||
this.elem = elem;
|
this.elem = this.$el;
|
||||||
|
|
||||||
const pageEditor = document.getElementById('page-editor');
|
this.pageId = this.$opts.pageId;
|
||||||
this.pageId = pageEditor.getAttribute('page-id');
|
this.textDirection = this.$opts.textDirection;
|
||||||
this.textDirection = pageEditor.getAttribute('text-direction');
|
|
||||||
|
|
||||||
this.markdown = new MarkdownIt({html: true});
|
this.markdown = new MarkdownIt({html: true});
|
||||||
this.markdown.use(mdTasksLists, {label: true});
|
this.markdown.use(mdTasksLists, {label: true});
|
||||||
@ -27,12 +26,18 @@ class MarkdownEditor {
|
|||||||
|
|
||||||
this.onMarkdownScroll = this.onMarkdownScroll.bind(this);
|
this.onMarkdownScroll = this.onMarkdownScroll.bind(this);
|
||||||
|
|
||||||
this.display.addEventListener('load', () => {
|
const displayLoad = () => {
|
||||||
this.displayDoc = this.display.contentDocument;
|
this.displayDoc = this.display.contentDocument;
|
||||||
this.init();
|
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,
|
markdownIt: this.markdown,
|
||||||
displayEl: this.display,
|
displayEl: this.display,
|
||||||
codeMirrorInstance: this.cm,
|
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 pageEditor = null;
|
||||||
let currentNode = null;
|
let currentNode = null;
|
||||||
@ -270,7 +270,6 @@ function drawIoPlugin(drawioUrl, isDarkMode) {
|
|||||||
async function updateContent(pngData) {
|
async function updateContent(pngData) {
|
||||||
const id = "image-" + Math.random().toString(16).slice(2);
|
const id = "image-" + Math.random().toString(16).slice(2);
|
||||||
const loadingImage = window.baseUrl('/loading.gif');
|
const loadingImage = window.baseUrl('/loading.gif');
|
||||||
const pageId = Number(document.getElementById('page-editor').getAttribute('page-id'));
|
|
||||||
|
|
||||||
// Handle updating an existing image
|
// Handle updating an existing image
|
||||||
if (currentNode) {
|
if (currentNode) {
|
||||||
@ -410,19 +409,19 @@ function listenForBookStackEditorEvents(editor) {
|
|||||||
|
|
||||||
class WysiwygEditor {
|
class WysiwygEditor {
|
||||||
|
|
||||||
constructor(elem) {
|
|
||||||
this.elem = elem;
|
|
||||||
|
|
||||||
const pageEditor = document.getElementById('page-editor');
|
setup() {
|
||||||
this.pageId = pageEditor.getAttribute('page-id');
|
this.elem = this.$el;
|
||||||
this.textDirection = pageEditor.getAttribute('text-direction');
|
|
||||||
|
this.pageId = this.$opts.pageId;
|
||||||
|
this.textDirection = this.$opts.textDirection;
|
||||||
this.isDarkMode = document.documentElement.classList.contains('dark-mode');
|
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.plugins = "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists codeeditor media";
|
||||||
this.loadPlugins();
|
this.loadPlugins();
|
||||||
|
|
||||||
this.tinyMceConfig = this.getTinyMceConfig();
|
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);
|
window.tinymce.init(this.tinyMceConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,7 +432,7 @@ class WysiwygEditor {
|
|||||||
const drawioUrlElem = document.querySelector('[drawio-url]');
|
const drawioUrlElem = document.querySelector('[drawio-url]');
|
||||||
if (drawioUrlElem) {
|
if (drawioUrlElem) {
|
||||||
const url = drawioUrlElem.getAttribute('drawio-url');
|
const url = drawioUrlElem.getAttribute('drawio-url');
|
||||||
drawIoPlugin(url, this.isDarkMode);
|
drawIoPlugin(url, this.isDarkMode, this.pageId);
|
||||||
this.plugins += ' drawio';
|
this.plugins += ' drawio';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,167 +0,0 @@
|
|||||||
import * as Dates from "../services/dates";
|
|
||||||
|
|
||||||
let autoSaveFrequency = 30;
|
|
||||||
|
|
||||||
let autoSave = false;
|
|
||||||
let draftErroring = false;
|
|
||||||
|
|
||||||
let currentContent = {
|
|
||||||
title: false,
|
|
||||||
html: false
|
|
||||||
};
|
|
||||||
|
|
||||||
let lastSave = 0;
|
|
||||||
|
|
||||||
function mounted() {
|
|
||||||
let elem = this.$el;
|
|
||||||
this.draftsEnabled = elem.getAttribute('drafts-enabled') === 'true';
|
|
||||||
this.editorType = elem.getAttribute('editor-type');
|
|
||||||
this.pageId= Number(elem.getAttribute('page-id'));
|
|
||||||
this.isNewDraft = Number(elem.getAttribute('page-new-draft')) === 1;
|
|
||||||
this.isUpdateDraft = Number(elem.getAttribute('page-update-draft')) === 1;
|
|
||||||
this.titleElem = elem.querySelector('input[name=name]');
|
|
||||||
this.hasDefaultTitle = this.titleElem.closest('[is-default-value]') !== null;
|
|
||||||
|
|
||||||
if (this.pageId !== 0 && this.draftsEnabled) {
|
|
||||||
window.setTimeout(() => {
|
|
||||||
this.startAutoSave();
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isUpdateDraft || this.isNewDraft) {
|
|
||||||
this.draftText = trans('entities.pages_editing_draft');
|
|
||||||
} else {
|
|
||||||
this.draftText = trans('entities.pages_editing_page');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen to save events from editor
|
|
||||||
window.$events.listen('editor-save-draft', this.saveDraft);
|
|
||||||
window.$events.listen('editor-save-page', this.savePage);
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setInitialFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = {
|
|
||||||
draftsEnabled: false,
|
|
||||||
editorType: 'wysiwyg',
|
|
||||||
pagedId: 0,
|
|
||||||
isNewDraft: false,
|
|
||||||
isUpdateDraft: false,
|
|
||||||
|
|
||||||
draftText: '',
|
|
||||||
draftUpdated : false,
|
|
||||||
changeSummary: '',
|
|
||||||
|
|
||||||
editorHTML: '',
|
|
||||||
editorMarkdown: '',
|
|
||||||
|
|
||||||
hasDefaultTitle: false,
|
|
||||||
titleElem: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
let methods = {
|
|
||||||
|
|
||||||
setInitialFocus() {
|
|
||||||
if (this.hasDefaultTitle) {
|
|
||||||
this.titleElem.select();
|
|
||||||
} else {
|
|
||||||
window.setTimeout(() => {
|
|
||||||
this.$events.emit('editor::focus', '');
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
startAutoSave() {
|
|
||||||
currentContent.title = this.titleElem.value.trim();
|
|
||||||
currentContent.html = this.editorHTML;
|
|
||||||
|
|
||||||
autoSave = window.setInterval(() => {
|
|
||||||
// Return if manually saved recently to prevent bombarding the server
|
|
||||||
if (Date.now() - lastSave < (1000 * autoSaveFrequency)/2) return;
|
|
||||||
const newTitle = this.titleElem.value.trim();
|
|
||||||
const newHtml = this.editorHTML;
|
|
||||||
|
|
||||||
if (newTitle !== currentContent.title || newHtml !== currentContent.html) {
|
|
||||||
currentContent.html = newHtml;
|
|
||||||
currentContent.title = newTitle;
|
|
||||||
this.saveDraft();
|
|
||||||
}
|
|
||||||
|
|
||||||
}, 1000 * autoSaveFrequency);
|
|
||||||
},
|
|
||||||
|
|
||||||
saveDraft() {
|
|
||||||
if (!this.draftsEnabled) return;
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
name: this.titleElem.value.trim(),
|
|
||||||
html: this.editorHTML
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.editorType === 'markdown') data.markdown = this.editorMarkdown;
|
|
||||||
|
|
||||||
const url = window.baseUrl(`/ajax/page/${this.pageId}/save-draft`);
|
|
||||||
window.$http.put(url, data).then(response => {
|
|
||||||
draftErroring = false;
|
|
||||||
if (!this.isNewDraft) this.isUpdateDraft = true;
|
|
||||||
this.draftNotifyChange(`${response.data.message} ${Dates.utcTimeStampToLocalTime(response.data.timestamp)}`);
|
|
||||||
lastSave = Date.now();
|
|
||||||
}, errorRes => {
|
|
||||||
if (draftErroring) return;
|
|
||||||
window.$events.emit('error', trans('errors.page_draft_autosave_fail'));
|
|
||||||
draftErroring = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
savePage() {
|
|
||||||
this.$el.closest('form').submit();
|
|
||||||
},
|
|
||||||
|
|
||||||
draftNotifyChange(text) {
|
|
||||||
this.draftText = text;
|
|
||||||
this.draftUpdated = true;
|
|
||||||
window.setTimeout(() => {
|
|
||||||
this.draftUpdated = false;
|
|
||||||
}, 2000);
|
|
||||||
},
|
|
||||||
|
|
||||||
discardDraft() {
|
|
||||||
let url = window.baseUrl(`/ajax/page/${this.pageId}`);
|
|
||||||
window.$http.get(url).then(response => {
|
|
||||||
if (autoSave) window.clearInterval(autoSave);
|
|
||||||
|
|
||||||
this.draftText = trans('entities.pages_editing_page');
|
|
||||||
this.isUpdateDraft = 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', trans('entities.pages_draft_discarded'));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
let computed = {
|
|
||||||
changeSummaryShort() {
|
|
||||||
let len = this.changeSummary.length;
|
|
||||||
if (len === 0) return trans('entities.pages_edit_set_changelog');
|
|
||||||
if (len <= 16) return this.changeSummary;
|
|
||||||
return this.changeSummary.slice(0, 16) + '...';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
mounted, data, methods, computed,
|
|
||||||
};
|
|
@ -5,11 +5,9 @@ function exists(id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
import imageManager from "./image-manager";
|
import imageManager from "./image-manager";
|
||||||
import pageEditor from "./page-editor";
|
|
||||||
|
|
||||||
let vueMapping = {
|
let vueMapping = {
|
||||||
'image-manager': imageManager,
|
'image-manager': imageManager,
|
||||||
'page-editor': pageEditor,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.vues = {};
|
window.vues = {};
|
||||||
|
@ -1,22 +1,19 @@
|
|||||||
<div class="page-editor flex-fill flex" id="page-editor"
|
<div component="page-editor" class="page-editor flex-fill flex"
|
||||||
drafts-enabled="{{ $draftsEnabled ? 'true' : 'false' }}"
|
option:page-editor:drafts-enabled="{{ $draftsEnabled ? 'true' : 'false' }}"
|
||||||
@if(config('services.drawio'))
|
@if(config('services.drawio'))
|
||||||
drawio-url="{{ is_string(config('services.drawio')) ? config('services.drawio') : 'https://www.draw.io/?embed=1&proto=json&spin=1' }}"
|
drawio-url="{{ is_string(config('services.drawio')) ? config('services.drawio') : 'https://www.draw.io/?embed=1&proto=json&spin=1' }}"
|
||||||
@endif
|
@endif
|
||||||
editor-type="{{ setting('app-editor') }}"
|
@if($model->name === trans('entities.pages_initial_name'))
|
||||||
page-id="{{ $model->id ?? 0 }}"
|
option:page-editor:has-default-title="true"
|
||||||
text-direction="{{ config('app.rtl') ? 'rtl' : 'ltr' }}"
|
@endif
|
||||||
page-new-draft="{{ $model->draft ?? 0 }}"
|
option:page-editor:editor-type="{{ setting('app-editor') }}"
|
||||||
page-update-draft="{{ $model->isDraft ?? 0 }}">
|
option:page-editor:page-id="{{ $model->id ?? '0' }}"
|
||||||
|
option:page-editor:page-new-draft="{{ ($model->draft ?? false) ? 'true' : 'false' }}"
|
||||||
@exposeTranslations([
|
option:page-editor:draft-text="{{ ($model->draft || $model->isDraft) ? trans('entities.pages_editing_draft') : trans('entities.pages_editing_page') }}"
|
||||||
'entities.pages_editing_draft',
|
option:page-editor:autosave-fail-text="{{ trans('errors.page_draft_autosave_fail') }}"
|
||||||
'entities.pages_editing_page',
|
option:page-editor:editing-page-text="{{ trans('entities.pages_editing_page') }}"
|
||||||
'errors.page_draft_autosave_fail',
|
option:page-editor:draft-discarded-text="{{ trans('entities.pages_draft_discarded') }}"
|
||||||
'entities.pages_editing_page',
|
option:page-editor:set-changelog-text="{{ trans('entities.pages_edit_set_changelog') }}">
|
||||||
'entities.pages_draft_discarded',
|
|
||||||
'entities.pages_edit_set_changelog',
|
|
||||||
])
|
|
||||||
|
|
||||||
{{--Header Bar--}}
|
{{--Header Bar--}}
|
||||||
<div class="primary-background-light toolbar page-edit-toolbar">
|
<div class="primary-background-light toolbar page-edit-toolbar">
|
||||||
@ -27,33 +24,38 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center px-m py-xs">
|
<div class="text-center px-m py-xs">
|
||||||
<div v-show="draftsEnabled"
|
<div component="dropdown"
|
||||||
component="dropdown"
|
|
||||||
option:dropdown:move-menu="true"
|
option:dropdown:move-menu="true"
|
||||||
class="dropdown-container draft-display text">
|
class="dropdown-container draft-display text {{ $draftsEnabled ? '' : 'hidden' }}">
|
||||||
<button type="button" refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" title="{{ trans('entities.pages_edit_draft_options') }}" class="text-primary text-button"><span class="faded-text" v-text="draftText"></span> @icon('more')</button>
|
<button type="button" refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" title="{{ trans('entities.pages_edit_draft_options') }}" class="text-primary text-button"><span refs="page-editor@draftDisplay" class="faded-text"></span> @icon('more')</button>
|
||||||
@icon('check-circle', ['class' => 'text-pos draft-notification svg-icon', ':class' => '{visible: draftUpdated}'])
|
@icon('check-circle', ['class' => 'text-pos draft-notification svg-icon', 'refs' => 'page-editor@draftDisplayIcon'])
|
||||||
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
|
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
|
||||||
<li>
|
<li>
|
||||||
<button type="button" @click="saveDraft()" class="text-pos">@icon('save'){{ trans('entities.pages_edit_save_draft') }}</button>
|
<button refs="page-editor@saveDraft" type="button" class="text-pos">@icon('save'){{ trans('entities.pages_edit_save_draft') }}</button>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="isNewDraft">
|
@if ($model->draft)
|
||||||
|
<li>
|
||||||
<a href="{{ $model->getUrl('/delete') }}" class="text-neg">@icon('delete'){{ trans('entities.pages_edit_delete_draft') }}</a>
|
<a href="{{ $model->getUrl('/delete') }}" class="text-neg">@icon('delete'){{ trans('entities.pages_edit_delete_draft') }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="isUpdateDraft">
|
@endif
|
||||||
<button type="button" @click="discardDraft" class="text-neg">@icon('cancel'){{ trans('entities.pages_edit_discard_draft') }}</button>
|
<li refs="page-editor@discardDraftWrap" class="{{ ($model->isDraft ?? false) ? '' : 'hidden' }}">
|
||||||
|
<button refs="page-editor@discardDraft" type="button" class="text-neg">@icon('cancel'){{ trans('entities.pages_edit_discard_draft') }}</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-buttons px-m py-xs" v-cloak>
|
<div class="action-buttons px-m py-xs">
|
||||||
<div component="dropdown" dropdown-move-menu class="dropdown-container">
|
<div component="dropdown" dropdown-move-menu class="dropdown-container">
|
||||||
<button refs="dropdown@toggle" type="button" aria-haspopup="true" aria-expanded="false" class="text-primary text-button">@icon('edit') <span v-text="changeSummaryShort"></span></button>
|
<button refs="dropdown@toggle" type="button" aria-haspopup="true" aria-expanded="false" class="text-primary text-button">@icon('edit') <span refs="page-editor@changelogDisplay">{{ trans('entities.pages_edit_set_changelog') }}</span></button>
|
||||||
<ul refs="dropdown@menu" class="wide dropdown-menu">
|
<ul refs="dropdown@menu" class="wide dropdown-menu">
|
||||||
<li class="px-l py-m">
|
<li class="px-l py-m">
|
||||||
<p class="text-muted pb-s">{{ trans('entities.pages_edit_enter_changelog_desc') }}</p>
|
<p class="text-muted pb-s">{{ trans('entities.pages_edit_enter_changelog_desc') }}</p>
|
||||||
<input name="summary" id="summary-input" type="text" placeholder="{{ trans('entities.pages_edit_enter_changelog') }}" v-model="changeSummary" />
|
<input refs="page-editor@changelogInput"
|
||||||
|
name="summary"
|
||||||
|
id="summary-input"
|
||||||
|
type="text"
|
||||||
|
placeholder="{{ trans('entities.pages_edit_enter_changelog') }}" />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<span>{{-- Prevents button jumping on menu show --}}</span>
|
<span>{{-- Prevents button jumping on menu show --}}</span>
|
||||||
@ -65,8 +67,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{--Title input--}}
|
{{--Title input--}}
|
||||||
<div class="title-input page-title clearfix" v-pre>
|
<div class="title-input page-title clearfix">
|
||||||
<div class="input" @if($model->name === trans('entities.pages_initial_name')) is-default-value @endif>
|
<div refs="page-editor@titleContainer" class="input">
|
||||||
@include('form.text', ['name' => 'name', 'model' => $model, 'placeholder' => trans('entities.pages_title')])
|
@include('form.text', ['name' => 'name', 'model' => $model, 'placeholder' => trans('entities.pages_title')])
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -86,5 +88,8 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" id="save-button-mobile" title="{{ trans('entities.pages_save') }}" class="text-primary text-button hide-over-m page-save-mobile-button">@icon('save')</button>
|
<button type="submit"
|
||||||
|
id="save-button-mobile"
|
||||||
|
title="{{ trans('entities.pages_save') }}"
|
||||||
|
class="text-primary text-button hide-over-m page-save-mobile-button">@icon('save')</button>
|
||||||
</div>
|
</div>
|
@ -1,4 +1,7 @@
|
|||||||
<div v-pre id="markdown-editor" markdown-editor class="flex-fill flex code-fill">
|
<div id="markdown-editor" component="markdown-editor"
|
||||||
|
option:markdown-editor:page-id="{{ $model->id ?? 0 }}"
|
||||||
|
option:markdown-editor:text-direction="{{ config('app.rtl') ? 'rtl' : 'ltr' }}"
|
||||||
|
class="flex-fill flex code-fill">
|
||||||
@exposeTranslations([
|
@exposeTranslations([
|
||||||
'errors.image_upload_error',
|
'errors.image_upload_error',
|
||||||
])
|
])
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<div wysiwyg-editor class="flex-fill flex">
|
<div component="wysiwyg-editor"
|
||||||
|
option:wysiwyg-editor:page-id="{{ $model->id ?? 0 }}"
|
||||||
|
option:wysiwyg-editor:text-direction="{{ config('app.rtl') ? 'rtl' : 'ltr' }}"
|
||||||
|
class="flex-fill flex">
|
||||||
|
|
||||||
@exposeTranslations([
|
@exposeTranslations([
|
||||||
'errors.image_upload_error',
|
'errors.image_upload_error',
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<?php namespace Tests\Entity;
|
<?php namespace Tests\Entity;
|
||||||
|
|
||||||
|
use BookStack\Entities\Page;
|
||||||
use BookStack\Entities\Repos\PageRepo;
|
use BookStack\Entities\Repos\PageRepo;
|
||||||
use Tests\BrowserKitTest;
|
use Tests\BrowserKitTest;
|
||||||
|
|
||||||
@ -101,4 +102,15 @@ class PageDraftTest extends BrowserKitTest
|
|||||||
->dontSeeInElement('.book-contents', 'New Page');
|
->dontSeeInElement('.book-contents', 'New Page');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_page_html_in_ajax_fetch_response()
|
||||||
|
{
|
||||||
|
$this->asAdmin();
|
||||||
|
$page = Page::query()->first();
|
||||||
|
|
||||||
|
$this->getJson('/ajax/page/' . $page->id);
|
||||||
|
$this->seeJson([
|
||||||
|
'html' => $page->html,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user