mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-12-14 15:13:37 +08:00
3bcbf6b9c5
Focus now returns to the editor properly when you quit out the code editor without saving. This also sets the return location to be correct on normal saving (Would sometimes jump to the end of the document). For #4109.
214 lines
6.4 KiB
JavaScript
214 lines
6.4 KiB
JavaScript
import {onChildEvent, onEnterPress, onSelect} from '../services/dom';
|
|
import {Component} from './component';
|
|
|
|
export class CodeEditor extends Component {
|
|
|
|
/**
|
|
* @type {null|SimpleEditorInterface}
|
|
*/
|
|
editor = null;
|
|
|
|
/**
|
|
* @type {?Function}
|
|
*/
|
|
saveCallback = null;
|
|
|
|
/**
|
|
* @type {?Function}
|
|
*/
|
|
cancelCallback = null;
|
|
|
|
history = {};
|
|
|
|
historyKey = 'code_history';
|
|
|
|
setup() {
|
|
this.container = this.$refs.container;
|
|
this.popup = this.$el;
|
|
this.editorInput = this.$refs.editor;
|
|
this.languageButtons = this.$manyRefs.languageButton;
|
|
this.languageOptionsContainer = this.$refs.languageOptionsContainer;
|
|
this.saveButton = this.$refs.saveButton;
|
|
this.languageInput = this.$refs.languageInput;
|
|
this.historyDropDown = this.$refs.historyDropDown;
|
|
this.historyList = this.$refs.historyList;
|
|
this.favourites = new Set(this.$opts.favourites.split(','));
|
|
|
|
this.setupListeners();
|
|
this.setupFavourites();
|
|
}
|
|
|
|
setupListeners() {
|
|
this.container.addEventListener('keydown', event => {
|
|
if (event.ctrlKey && event.key === 'Enter') {
|
|
this.save();
|
|
}
|
|
});
|
|
|
|
onSelect(this.languageButtons, event => {
|
|
const language = event.target.dataset.lang;
|
|
this.languageInput.value = language;
|
|
this.languageInputChange(language);
|
|
});
|
|
|
|
onEnterPress(this.languageInput, () => this.save());
|
|
this.languageInput.addEventListener('input', () => this.languageInputChange(this.languageInput.value));
|
|
onSelect(this.saveButton, () => this.save());
|
|
|
|
onChildEvent(this.historyList, 'button', 'click', (event, elem) => {
|
|
event.preventDefault();
|
|
const historyTime = elem.dataset.time;
|
|
if (this.editor) {
|
|
this.editor.setContent(this.history[historyTime]);
|
|
}
|
|
});
|
|
}
|
|
|
|
setupFavourites() {
|
|
for (const button of this.languageButtons) {
|
|
this.setupFavouritesForButton(button);
|
|
}
|
|
|
|
this.sortLanguageList();
|
|
}
|
|
|
|
/**
|
|
* @param {HTMLButtonElement} button
|
|
*/
|
|
setupFavouritesForButton(button) {
|
|
const language = button.dataset.lang;
|
|
let isFavorite = this.favourites.has(language);
|
|
button.setAttribute('data-favourite', isFavorite ? 'true' : 'false');
|
|
|
|
onChildEvent(button.parentElement, '.lang-option-favorite-toggle', 'click', () => {
|
|
isFavorite = !isFavorite;
|
|
|
|
if (isFavorite) {
|
|
this.favourites.add(language);
|
|
} else {
|
|
this.favourites.delete(language);
|
|
}
|
|
|
|
button.setAttribute('data-favourite', isFavorite ? 'true' : 'false');
|
|
|
|
window.$http.patch('/preferences/update-code-language-favourite', {
|
|
language,
|
|
active: isFavorite,
|
|
});
|
|
|
|
this.sortLanguageList();
|
|
if (isFavorite) {
|
|
button.scrollIntoView({block: 'center', behavior: 'smooth'});
|
|
}
|
|
});
|
|
}
|
|
|
|
sortLanguageList() {
|
|
const sortedParents = this.languageButtons.sort((a, b) => {
|
|
const aFav = a.dataset.favourite === 'true';
|
|
const bFav = b.dataset.favourite === 'true';
|
|
|
|
if (aFav && !bFav) {
|
|
return -1;
|
|
} if (bFav && !aFav) {
|
|
return 1;
|
|
}
|
|
|
|
return a.dataset.lang > b.dataset.lang ? 1 : -1;
|
|
}).map(button => button.parentElement);
|
|
|
|
for (const parent of sortedParents) {
|
|
this.languageOptionsContainer.append(parent);
|
|
}
|
|
}
|
|
|
|
save() {
|
|
if (this.saveCallback) {
|
|
this.saveCallback(this.editor.getContent(), this.languageInput.value);
|
|
}
|
|
this.hide();
|
|
}
|
|
|
|
async open(code, language, saveCallback, cancelCallback) {
|
|
this.languageInput.value = language;
|
|
this.saveCallback = saveCallback;
|
|
this.cancelCallback = cancelCallback;
|
|
|
|
await this.show();
|
|
this.languageInputChange(language);
|
|
this.editor.setContent(code);
|
|
}
|
|
|
|
async show() {
|
|
const Code = await window.importVersioned('code');
|
|
if (!this.editor) {
|
|
this.editor = Code.popupEditor(this.editorInput, this.languageInput.value);
|
|
}
|
|
|
|
this.loadHistory();
|
|
this.getPopup().show(() => {
|
|
this.editor.focus();
|
|
}, () => {
|
|
this.addHistory();
|
|
if (this.cancelCallback) {
|
|
this.cancelCallback();
|
|
}
|
|
});
|
|
}
|
|
|
|
hide() {
|
|
this.getPopup().hide();
|
|
this.addHistory();
|
|
}
|
|
|
|
/**
|
|
* @returns {Popup}
|
|
*/
|
|
getPopup() {
|
|
return window.$components.firstOnElement(this.popup, 'popup');
|
|
}
|
|
|
|
async updateEditorMode(language) {
|
|
this.editor.setMode(language, this.editor.getContent());
|
|
}
|
|
|
|
languageInputChange(language) {
|
|
this.updateEditorMode(language);
|
|
const inputLang = language.toLowerCase();
|
|
|
|
for (const link of this.languageButtons) {
|
|
const lang = link.dataset.lang.toLowerCase().trim();
|
|
const isMatch = inputLang === lang;
|
|
link.classList.toggle('active', isMatch);
|
|
if (isMatch) {
|
|
link.scrollIntoView({block: 'center', behavior: 'smooth'});
|
|
}
|
|
}
|
|
}
|
|
|
|
loadHistory() {
|
|
this.history = JSON.parse(window.sessionStorage.getItem(this.historyKey) || '{}');
|
|
const historyKeys = Object.keys(this.history).reverse();
|
|
this.historyDropDown.classList.toggle('hidden', historyKeys.length === 0);
|
|
this.historyList.innerHTML = historyKeys.map(key => {
|
|
const localTime = (new Date(parseInt(key, 10))).toLocaleTimeString();
|
|
return `<li><button type="button" data-time="${key}" class="text-item">${localTime}</button></li>`;
|
|
}).join('');
|
|
}
|
|
|
|
addHistory() {
|
|
if (!this.editor) return;
|
|
const code = this.editor.getContent();
|
|
if (!code) return;
|
|
|
|
// Stop if we'd be storing the same as the last item
|
|
const lastHistoryKey = Object.keys(this.history).pop();
|
|
if (this.history[lastHistoryKey] === code) return;
|
|
|
|
this.history[String(Date.now())] = code;
|
|
const historyString = JSON.stringify(this.history);
|
|
window.sessionStorage.setItem(this.historyKey, historyString);
|
|
}
|
|
|
|
}
|