Files
mixly3/common/modules/mixly-modules/common/editor-ace.js

409 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
goog.loadJs('common', () => {
goog.require('ace');
goog.require('ace.ExtLanguageTools');
goog.require('Mixly.XML');
goog.require('Mixly.Env');
goog.require('Mixly.Msg');
goog.require('Mixly.Debug');
goog.require('Mixly.HTMLTemplate');
goog.require('Mixly.EditorBase');
goog.provide('Mixly.EditorAce');
const {
XML,
Env,
Msg,
Debug,
HTMLTemplate,
EditorBase
} = Mixly;
class EditorAce extends EditorBase {
static {
this.CTRL_BTNS = ['resetFontSize', 'increaseFontSize', 'decreaseFontSize'];
this.CTRL_BTN_TEMPLATE = '<div m-id="{{d.mId}}" class="code-editor-btn setFontSize"></div>';
this.MODE_MAP = goog.readJsonSync(path.join(Env.templatePath, 'json/ace-mode-map.json'));
HTMLTemplate.add(
'html/editor/editor-code.html',
new HTMLTemplate(goog.readFileSync(path.join(Env.templatePath, 'html/editor/editor-code.html')))
);
if (['zh-hans', 'zh-hant'].includes(Msg.nowLang)) {
ace.config.setMessages(
goog.readJsonSync(path.join(Env.templatePath, `json/ace.i18n.${Msg.nowLang}.json`))
);
}
}
#editor_ = null;
#destroyed_ = null;
#cursorLayer_ = null;
constructor() {
super();
const editorHTMLTemplate = HTMLTemplate.get('html/editor/editor-code.html');
this.setContent($(editorHTMLTemplate.render()));
this.id = editorHTMLTemplate.id;
}
init() {
super.init();
this.#editor_ = ace.edit(this.getContent()[0]);
this.resetFontSize();
this.#addCursorLayer_();
this.#addCursorEventsListener_();
this.#addDefaultCommand_();
}
getEditor() {
return this.#editor_;
}
dispose() {
super.dispose();
this.#editor_.destroy();
this.#destroyed_ = true;
}
setValue(data, scroll = true) {
if (this.#destroyed_) {
return;
}
this.#editor_.updateSelectionMarkers();
const { selection } = this.#editor_;
const initCursor = selection.getCursor();
if (this.getValue() !== data) {
this.#editor_.setValue(data);
}
if (scroll) {
this.scrollToBottom();
} else {
selection.moveCursorTo(initCursor.row, initCursor.column, true);
selection.clearSelection();
}
}
addValue(data, scroll = true) {
if (this.#destroyed_) {
return;
}
const { session } = this.#editor_;
const endCursor = this.getEndPos();
session.insert(endCursor, data);
if (scroll) {
this.scrollToBottom();
}
}
replaceLine(lineNumber, newContent) {
const session = this.#editor_.getSession();
const totalLines = session.getLength();
let targetLine = lineNumber < 0 ? totalLines + lineNumber : lineNumber;
if (targetLine < 0) {
targetLine = 0;
} else if (targetLine >= totalLines) {
targetLine = totalLines - 1;
}
const cursorPos = this.#editor_.getCursorPosition();
const oldLine = session.getLine(targetLine);
const range = new ace.Range(targetLine, 0, targetLine, oldLine.length);
session.replace(range, newContent);
if (cursorPos.row > targetLine) {
this.#editor_.moveCursorTo(cursorPos.row, cursorPos.column);
} else if (cursorPos.row === targetLine) {
const newCol = Math.min(cursorPos.column, newContent.length);
this.#editor_.moveCursorTo(targetLine, newCol);
} else {
this.#editor_.moveCursorTo(cursorPos.row, cursorPos.column);
}
this.#editor_.renderer.scrollCursorIntoView(null, 0.5);
}
appendLine(text) {
const session = this.#editor_.getSession();
const totalLines = session.getLength();
const lastLineIndex = totalLines - 1;
const lastLineText = session.getLine(lastLineIndex);
if (lastLineText.trim()) {
session.insert({ row: totalLines, column: 0 }, `\n${text}`);
} else {
const range = new ace.Range(lastLineIndex, 0, lastLineIndex, lastLineText.length);
session.replace(range, text);
}
this.scrollToBottom();
}
getValue() {
return this.#destroyed_ ? '' : this.#editor_.getValue();
}
getValueRange(startPos, endPos) {
if (this.#destroyed_ || !startPos || !endPos
|| typeof startPos !== 'object' || typeof endPos !== 'object') {
return "";
}
const session = this.#editor_.getSession();
return session.getTextRange(new ace.Range(
startPos.row,
startPos.column,
endPos.row,
endPos.column
));
}
getEndPos() {
if (this.#destroyed_) {
return { row: 0, column: 0 };
}
const session = this.#editor_.getSession();
const row = session.getLength() - 1;
const column = session.getLine(row).length;
return { row, column };
}
clear() {
this.setValue('', true);
}
scrollToBottom() {
if (this.#destroyed_) {
return;
}
const { selection, session } = this.#editor_;
this.#editor_.updateSelectionMarkers();
this.#editor_.gotoLine(session.getLength());
selection.moveCursorLineEnd();
}
scrollToTop() {
if (this.#destroyed_) {
return;
}
this.#editor_.gotoLine(0);
}
#addDefaultCommand_() {
const { commands } = this.#editor_;
commands.addCommands([{
name: "increaseFontSize",
bindKey: "Ctrl-=|Ctrl-+",
exec: (editor) => {
this.increaseFontSize();
}
}, {
name: "decreaseFontSize",
bindKey: "Ctrl+-|Ctrl-_",
exec: (editor) => {
this.decreaseFontSize();
}
}, {
name: "resetFontSize",
bindKey: "Ctrl+0|Ctrl-Numpad0",
exec: (editor) => {
this.resetFontSize();
}
}, {
name: "mixly-message",
bindKey: "backspace|delete|enter",
readOnly: true,
exec: (editor) => {
if (!editor.getReadOnly()) {
return false;
}
this.#cursorLayer_.show();
return false;
}
}]);
}
#addCursorLayer_() {
this.#cursorLayer_ = tippy(this.getContent().find('.ace_cursor')[0], {
content: Msg.Lang['editor.viewReadOnly'],
trigger: 'manual',
hideOnClick: true,
delay: 0,
duration: [ 0, 0 ],
placement: 'right',
offset: [ 0, 0 ],
popperOptions: {
strategy: 'fixed',
modifiers: [
{
name: 'flip',
options: {
fallbackPlacements: ['top-end', 'right']
}
}
]
}
});
}
#addCursorEventsListener_() {
const editor = this.getEditor();
$('#mixly-footer-cursor').hide();
editor.on('focus', () => {
const cursor = selection.getCursor();
$('#mixly-footer-row').html(cursor.row + 1);
$('#mixly-footer-column').html(cursor.column + 1);
$('#mixly-footer-cursor').show();
});
editor.on("blur", () => {
this.#cursorLayer_.hide();
$('#mixly-footer-cursor').hide();
});
const { selection } = editor.getSession();
const { session } = editor;
selection.on('changeCursor', () => {
const cursor = selection.getCursor();
$('#mixly-footer-row').html(cursor.row + 1);
$('#mixly-footer-column').html(cursor.column + 1);
});
selection.on("changeSelection", () => {
if (selection.isEmpty()) {
$('#mixly-footer-selected').parent().hide();
} else {
const range = selection.getRange();
const text = session.getTextRange(range);
$('#mixly-footer-selected').parent().css('display', 'inline-flex');
$('#mixly-footer-selected').html(text.length);
}
});
session.on("changeScrollTop", () => {
this.#cursorLayer_.hide();
});
}
addCtrlBtns() {
const $content = this.getContent();
for (let mId of EditorAce.CTRL_BTNS) {
$content.append(XML.render(EditorAce.CTRL_BTN_TEMPLATE, { mId }));
}
this.$ctrlBtns = $content.children('.code-editor-btn');
this.$ctrlBtns.off().click((event) => {
const mId = $(event.target).attr('m-id');
this[mId]();
});
}
showCtrlBtns() {
this.$ctrlBtns.css('display', 'block');
}
hideCtrlBtns() {
this.$ctrlBtns.css('display', 'none');
}
resetFontSize() {
this.#editor_.setFontSize(17);
}
increaseFontSize() {
const size = parseInt(this.#editor_.getFontSize(), 10) || 17;
this.#editor_.setFontSize(size + 1);
}
decreaseFontSize() {
const size = parseInt(this.#editor_.getFontSize(), 10) || 17;
this.#editor_.setFontSize(Math.max(size - 1 || 1));
}
undo() {
super.undo();
this.#editor_.undo();
}
redo() {
super.redo();
this.#editor_.redo();
}
setReadOnly(status) {
this.#editor_.setReadOnly(status);
}
setMode(type) {
this.#editor_.session.setMode(`ace/mode/${type}`);
}
setFileMode(extname) {
const mode = this.getFileMode(extname) ?? 'text';
this.setMode(mode);
}
getFileMode(extname) {
for (const [mode, extensions] of Object.entries(EditorAce.MODE_MAP)) {
if (extensions.includes(extname)) {
return mode;
}
}
return null;
}
cut() {
const { selection, session } = this.#editor_;
const cutLine = selection.isEmpty();
const range = cutLine ? selection.getLineRange() : selection.getRange();
this.#editor_._emit('cut', range);
if (!range.isEmpty()) {
const copyText = session.getTextRange(range);
navigator.clipboard.writeText(copyText)
.then(() => {
Debug.log('clipboard复制成功', copyText);
}).catch((error) => {
Debug.error('clipboard复制失败', error);
});
session.remove(range);
}
this.#editor_.clearSelection();
}
copy() {
const copyText = this.#editor_.getSelectedText();
this.#editor_.clearSelection();
if (!copyText) {
return;
}
navigator.clipboard.writeText(copyText)
.then(() => {
Debug.log('clipboard复制成功', copyText);
}).catch((error) => {
Debug.error('clipboard复制失败', error);
});
}
paste() {
navigator.clipboard.readText()
.then((message) => {
this.#editor_.execCommand('paste', message);
Debug.log('clipboard粘贴成功', message);
})
.catch((error) => {
Debug.error('clipboard粘贴失败', error);
});
}
commentLine() {
this.#editor_.execCommand('togglecomment');
}
blockComment() {
this.#editor_.execCommand('toggleBlockComment');
}
resize() {
this.#editor_.resize();
super.resize();
}
focus() {
this.#editor_.focus();
}
}
Mixly.EditorAce = EditorAce;
});