注意:在保存之后,您可能需要清除浏览器缓存才能看到所作出的变更的影响。
?_=1
来访问最新页面。https://mzh.moegirl.org.cn/User:AnnAngela/js/wikieditor-highlight.js?_=1
// From [[Special:固定链接/5337466]] /* global CodeMirror */ // 本页面大部分内容均直接或间接修改自[[MW:Extension:CodeMirror]] "use strict"; $(() => (async () => { if (!["edit", "submit"].includes(mw.config.get("wgAction")) || mw.config.get("wgPageContentModel") !== "wikitext") { return; } let cm, $doc; let state = JSON.parse(localStorage.getItem("wikieditor-codemirror")); const $textarea = $("#wpTextbox1"); const isAdvanced = ["loading", "loaded", "executing", "ready"].includes(mw.loader.getState("ext.wikiEditor")); const ns = mw.config.get("wgNamespaceNumber"); const init = async () => { mw.loader.load("//cdn.jsdelivr.net/npm/codemirror@5.35.0/lib/codemirror.min.css", "text/css"); mw.loader.load("//cdn.jsdelivr.net/gh/bhsd-harry/LLWiki@2.17/otherwiki/mediawiki.min.css", "text/css"); mw.loader.addStyleTag(` #wikiEditor-ui-toolbar .menu { position: relative; z-index: 5; } .CodeMirror pre { font-family: Monaco, Menlo, "Ubuntu Mono", Consolas, "source-code-pro", monospace; } .skin-vector #wpTextbox1:not([readonly])+.CodeMirror { font-size: 13px; line-height: 1.5; } #wpTextbox1[readonly]+.CodeMirror, .skin-minerva #wpTextbox1+.CodeMirror { font-size: 16px; line-height: 1.2; border: 1px solid #c8ccd1; } .cm-matchingbracket, .cm-nonmatchingbracket { margin: -1px; border: 1px solid #c0c0c0; } .cm-matchingbracket { background-color: #0b04; } .cm-nonmatchingbracket { background-color: #ec14; } `); const $search = $(".group-search a"); const addon = () => { const Pos = CodeMirror.Pos, defaults = { bracketRegex: /[{}[\]]/, maxScanLineLength: 3000, maxScanLines: 100, afterCursor: false, strict: false, maxHighlightLineLength: 1000, highlightNonMatching: true, }; const matching = { "[": "]>", "]": "[<", "{": "}>", "}": "{<", }; const pair = { "[": /[[\]]/, "]": /[[\]]/, "{": /[{}]/, "}": /[{}]/, }; const bracketRegex = (config) => { return config && config.bracketRegex; }; const scanForBracket = (cm, where, dir, style, config) => { const maxScanLen = config && config.maxScanLineLength; const maxScanLines = config && config.maxScanLines; const stack = []; const re = bracketRegex(config); const lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lineCount()) : Math.max(-1, where.line - maxScanLines); let lineNo = where.line; for (; lineNo !== lineEnd; lineNo += dir) { const line = cm.getLine(lineNo); if (!line || line.length > maxScanLen) { continue; } const end = dir > 0 ? line.length : -1; let pos = dir > 0 ? 0 : line.length - 1; if (lineNo === where.line) { pos = where.ch - (dir < 0 ? 1 : 0); } for (; pos !== end; pos += dir) { const ch = line.charAt(pos); if (re.test(ch) && (style === undefined || (cm.getTokenTypeAt(Pos(lineNo, pos + 1)) || "") === (style || ""))) { const match = matching[ch]; if (match && match.charAt(1) === ">" === dir > 0) { stack.push(ch); } else if (stack.length === 0) { return { pos: Pos(lineNo, pos), ch: ch, }; } else { stack.pop(); } } } } return lineNo - dir === (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null; }; const findMatchingBracket = (cm, where, config) => { const line = cm.getLine(where.line); const re = bracketRegex(config); const afterCursor = config && config.afterCursor; const pos = !afterCursor && where.ch > 0 ? where.ch - 1 : where.ch; const key = line.charAt(pos); const match = re.test(key) && matching[key]; if (!match) { return null; } const dir = match.charAt(1) === ">" ? 1 : -1; if (config && config.strict && dir > 0 !== (pos === where.ch)) { return null; } const newConfig = Object.assign({}, config, { bracketRegex: pair[key], }); const style = cm.getTokenTypeAt(Pos(where.line, pos + 1)); const found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style, newConfig); if (found === null) { return null; } return { from: Pos(where.line, pos), to: found && found.pos, match: found && found.ch === match.charAt(0), forward: dir > 0, }; }; const markChar = (cm, pos, style) => cm.markText(pos, Pos(pos.line, pos.ch + 1), { className: style, }); const matchBrackets = (cm, autoclear, _config) => { const config = _config || cm.state.matchBrackets; const maxHighlightLen = config && config.maxHighlightLineLength; const highlightNonMatching = config && config.highlightNonMatching; const marks = []; cm.listSelections().forEach((range) => { const match = range.empty() && findMatchingBracket(cm, range.head, config); if (match && (match.match || highlightNonMatching) && cm.getLine(match.from.line).length <= maxHighlightLen) { const style = match.match ? "cm-matchingbracket" : "cm-nonmatchingbracket"; marks.push(markChar(cm, match.from, style)); if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen) { marks.push(markChar(cm, match.to, style)); } } }); if (marks.length) { const clear = () => { cm.operation(() => { marks.forEach((mark) => { mark.clear(); }); }); }; if (autoclear) { setTimeout(clear, 800); } else { return clear; } } }; const doMatchBrackets = (cm) => { cm.operation(() => { if (cm.state.matchBrackets.currentlyHighlighted) { cm.state.matchBrackets.currentlyHighlighted(); cm.state.matchBrackets.currentlyHighlighted = null; } cm.state.matchBrackets.currentlyHighlighted = matchBrackets(cm, false); }); }; const clearHighlighted = (cm) => { if (cm.state.matchBrackets && cm.state.matchBrackets.currentlyHighlighted) { cm.state.matchBrackets.currentlyHighlighted(); cm.state.matchBrackets.currentlyHighlighted = null; } }; CodeMirror.defineOption("matchBrackets", false, (cm, val, old) => { if (old && old !== CodeMirror.Init) { cm.off("cursorActivity", doMatchBrackets); cm.off("focus", doMatchBrackets); cm.off("blur", clearHighlighted); clearHighlighted(cm); } if (val) { cm.state.matchBrackets = $.extend({}, defaults, typeof val === "object" ? val : {}); cm.on("cursorActivity", doMatchBrackets); cm.on("focus", doMatchBrackets); cm.on("blur", clearHighlighted); } }); CodeMirror.defineExtension("matchBrackets", (config) => { matchBrackets(CodeMirror, true, config); }); }; await $.get({ dataType: "script", cache: true, url: "//cdn.jsdelivr.net/npm/codemirror@5.35.0/lib/codemirror.min.js", }); addon(); await $.get({ dataType: "script", cache: true, url: "//cdn.jsdelivr.net/gh/bhsd-harry/LLWiki@2.17/otherwiki/mediawiki.min.js", }); if (ns === 274) { await Promise.all([ $.get({ dataType: "script", cache: true, url: "//cdn.jsdelivr.net/npm/codemirror@5.35.0/mode/javascript/javascript.min.js", }), $.get({ dataType: "script", cache: true, url: "//cdn.jsdelivr.net/npm/codemirror@5.35.0/mode/css/css.min.js", }), ]); } const config = await $.get({ dataType: "json", cache: true, url: "//cdn.jsdelivr.net/gh/bhsd-harry/LLWiki@2.16/otherwiki/gadget-CodeMirror.json", }); if (ns === 274) { $.extend(config.tags, { script: true, style: true, }); $.extend(config.tagModes, { script: "javascript", style: "css", }); } window.mwConfig = config; if (isAdvanced) { cm = new CodeMirror($textarea.parent()[0], { mode: "text/mediawiki", mwConfig: window.mwConfig, lineWrapping: true, lineNumbers: true, readOnly: $textarea.prop("readonly"), matchBrackets: true, }); } else { cm = CodeMirror.fromTextArea($textarea[0], { mode: "text/mediawiki", mwConfig: window.mwConfig, lineWrapping: true, lineNumbers: true, readOnly: $textarea.prop("readonly"), matchBrackets: true, }); cm.setSize(null, $textarea.height()); } mw.hook("wiki-codemirror").fire(cm); $doc = $(cm.getWrapperElement()); $.valHooks.textarea = { get: (ele) => { return ele === $textarea[0] && state ? cm.getValue() : ele.value; }, set: (ele, val) => { ele === $textarea[0] && state ? cm.setValue(val) : ele.value = val; }, }; if (mw.loader.getState("jquery.ui.resizable") === "ready") { $doc.resizable({ handles: "s", }); } if ($search.length === 0) { return; } cm.addKeyMap({ "Ctrl-F": () => { $search.trigger("click"); }, "Cmd-F": () => { $search.trigger("click"); }, }); }; if (state === null || state === undefined || !isAdvanced) { state = true; } if (!isAdvanced) { init(); return; } const $form = $(document.editform); const btn = new OO.ui.ButtonWidget({ classes: ["tool"], icon: "highlight", framed: false, title: "代码高亮开关", }).on("click", () => { if (cm) { $doc.toggle(); update(); } else { initAndUpdate(); } }); const fn = { getSelection: () => { return cm.getSelection(); }, setSelection: function (options) { cm.setSelection(cm.posFromIndex(options.start), cm.posFromIndex(options.end)); cm.focus(); return this; }, getCaretPosition: (options) => { const caretPos = cm.indexFromPos(cm.getCursor("from")), endPos = cm.indexFromPos(cm.getCursor("to")); if (options.startAndEnd) { return [caretPos, endPos]; } return caretPos; }, scrollToCaretPosition: function () { cm.scrollIntoView(); return this; }, }; const submit = () => { $textarea[0].value = cm.getValue(); }; const shared = () => { btn.$element.toggleClass("tool-active"); if (state) { cm.setValue($textarea[0].value); cm.setSize(null, $textarea.height()); } else { $textarea[0].value = cm.getValue(); } $textarea.toggle(); $form[state ? "on" : "off"]("submit", submit); if ($textarea.textSelection) { $textarea.textSelection(state ? "register" : "unregister", fn); } }; const update = () => { state = !state; localStorage.setItem("wikieditor-codemirror", state); shared(); }; const initAndUpdate = async () => { await init(); update(); }; const group = $("#wikiEditor-section-main > .group-insert")[0]; $textarea.on("wikiEditor-toolbar-doneInitialSections", () => { btn.$element.appendTo("#wikiEditor-section-main > .group-insert"); }); if (group && !group.contains(btn.$element[0])) { $textarea.trigger("wikiEditor-toolbar-doneInitialSections"); } if (state) { await mw.loader.using("ext.wikiEditor"); await init(); shared(); } })()); //</nowiki>