codemirror
This commit is contained in:
@@ -11,15 +11,13 @@
|
||||
var conflictApply = document.querySelector("[data-conflict-apply]");
|
||||
var conflictDismiss = document.querySelector("[data-conflict-dismiss]");
|
||||
var conflictResolution = document.querySelector("[data-conflict-resolution]");
|
||||
var conflictResolutionMount = document.querySelector("[data-conflict-monaco-mount]");
|
||||
var conflictResolutionMount = document.querySelector("[data-conflict-codemirror-mount]");
|
||||
var conflictUseServer = document.querySelector("[data-conflict-use='server']");
|
||||
var conflictUseLocal = document.querySelector("[data-conflict-use='local']");
|
||||
var conflictUseBoth = document.querySelector("[data-conflict-use='both']");
|
||||
var monacoContainer = document.querySelector("[data-monaco-mount]");
|
||||
var monacoLoaded = false;
|
||||
var monacoReadyPromise = null;
|
||||
var monacoDarkTheme = "cairnquire-dark";
|
||||
var monacoLightTheme = "cairnquire-light";
|
||||
var codeMirrorContainer = document.querySelector("[data-codemirror-mount]");
|
||||
var codeMirrorReadyPromise = null;
|
||||
var codeMirrorModules = null;
|
||||
|
||||
if (!form || !textarea || statusNodes.length === 0) {
|
||||
return;
|
||||
@@ -36,6 +34,9 @@
|
||||
var conflictDiffView = null;
|
||||
var resolutionEditor = null;
|
||||
var suppressResolutionEvents = false;
|
||||
var doneLink = document.querySelector("[data-done-link]");
|
||||
var isSaving = false;
|
||||
var suppressBeforeUnload = false;
|
||||
|
||||
function setStatus(nextStatus) {
|
||||
statusNodes.forEach(function (node) {
|
||||
@@ -223,7 +224,7 @@
|
||||
var value = getEditorValue();
|
||||
if (value === lastSavedValue) {
|
||||
setStatus("Saved");
|
||||
return;
|
||||
return "unchanged";
|
||||
}
|
||||
|
||||
isSaving = true;
|
||||
@@ -233,7 +234,7 @@
|
||||
if (!window.MDHubSync) {
|
||||
isSaving = false;
|
||||
setStatus("Queued");
|
||||
return;
|
||||
return "queued";
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -249,16 +250,17 @@
|
||||
}
|
||||
setStatus("Saved");
|
||||
isSaving = false;
|
||||
return;
|
||||
return "saved";
|
||||
}
|
||||
|
||||
if (result.status === "conflict") {
|
||||
showConflict(result.conflict, value);
|
||||
isSaving = false;
|
||||
return;
|
||||
return "conflict";
|
||||
}
|
||||
|
||||
setStatus("Queued");
|
||||
return "queued";
|
||||
} catch (err) {
|
||||
setStatus("Queued");
|
||||
throw err;
|
||||
@@ -328,138 +330,236 @@
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function loadMonaco() {
|
||||
if (monacoReadyPromise) {
|
||||
return monacoReadyPromise;
|
||||
}
|
||||
if (monacoLoaded && window.monaco) {
|
||||
monacoReadyPromise = Promise.resolve();
|
||||
return monacoReadyPromise;
|
||||
}
|
||||
monacoLoaded = true;
|
||||
function doneHref() {
|
||||
return doneLink ? doneLink.getAttribute("href") : "/docs/" + path.replace(/\.md$/, "");
|
||||
}
|
||||
|
||||
function clearScheduledSave() {
|
||||
if (saveTimer) {
|
||||
clearTimeout(saveTimer);
|
||||
saveTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function leaveEditorNow() {
|
||||
clearScheduledSave();
|
||||
suppressBeforeUnload = true;
|
||||
window.location.href = doneHref();
|
||||
}
|
||||
|
||||
function saveThenLeaveEditor() {
|
||||
var href = doneHref();
|
||||
|
||||
if (currentConflict) {
|
||||
setStatus("Conflict");
|
||||
if (conflictNotice) {
|
||||
conflictNotice.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
clearScheduledSave();
|
||||
|
||||
if (isSaving) {
|
||||
waitForSaveThenNavigate(href);
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus("Saving");
|
||||
saveNow()
|
||||
.then(function (result) {
|
||||
if (result === "saved" || result === "unchanged") {
|
||||
suppressBeforeUnload = true;
|
||||
window.location.href = href;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result === "conflict") {
|
||||
setStatus("Conflict");
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus("Queued");
|
||||
if (window.confirm("Could not save. Leave without saving?")) {
|
||||
suppressBeforeUnload = true;
|
||||
window.location.href = href;
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
setStatus("Queued");
|
||||
if (window.confirm("Could not save. Leave without saving?")) {
|
||||
suppressBeforeUnload = true;
|
||||
window.location.href = href;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadCodeMirror() {
|
||||
if (codeMirrorReadyPromise) {
|
||||
return codeMirrorReadyPromise;
|
||||
}
|
||||
|
||||
codeMirrorReadyPromise = Promise.all([
|
||||
import("/static/codemirror.bundle.js"),
|
||||
]).then(function (modules) {
|
||||
var bundle = modules[0];
|
||||
codeMirrorModules = {
|
||||
cm: bundle,
|
||||
markdown: bundle,
|
||||
vim: bundle,
|
||||
};
|
||||
registerVimCommands();
|
||||
return codeMirrorModules;
|
||||
});
|
||||
|
||||
return codeMirrorReadyPromise;
|
||||
}
|
||||
|
||||
function registerVimCommands() {
|
||||
var Vim = codeMirrorModules && codeMirrorModules.vim && codeMirrorModules.vim.Vim;
|
||||
if (!Vim) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vim.defineEx("write", "w", function () {
|
||||
saveNow().catch(function () {
|
||||
setStatus("Queued");
|
||||
});
|
||||
});
|
||||
Vim.defineEx("quit", "q", function () {
|
||||
leaveEditorNow();
|
||||
});
|
||||
Vim.defineEx("wq", "wq", function () {
|
||||
saveThenLeaveEditor();
|
||||
});
|
||||
Vim.defineEx("xit", "x", function () {
|
||||
saveThenLeaveEditor();
|
||||
});
|
||||
Vim.map("<Esc>", ":q<CR>");
|
||||
}
|
||||
|
||||
function codeMirrorTheme() {
|
||||
var EditorView = codeMirrorModules.cm.EditorView;
|
||||
var isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
|
||||
monacoReadyPromise = new Promise(function (resolve) {
|
||||
var script = document.createElement("script");
|
||||
script.src = "https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/loader.js";
|
||||
script.onload = function () {
|
||||
require.config({
|
||||
paths: { vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs" },
|
||||
});
|
||||
require(["vs/editor/editor.main"], function () {
|
||||
monaco.editor.defineTheme(monacoDarkTheme, {
|
||||
base: "vs-dark",
|
||||
inherit: true,
|
||||
rules: [
|
||||
{ token: "comment", foreground: "8B9DAF", fontStyle: "italic" },
|
||||
{ token: "keyword", foreground: "E8943A" },
|
||||
{ token: "string", foreground: "C9A96E" },
|
||||
{ token: "number", foreground: "C9A96E" },
|
||||
{ token: "type", foreground: "E8943A" },
|
||||
{ token: "tag", foreground: "E8943A" },
|
||||
{ token: "attribute.name", foreground: "D4A05A" },
|
||||
{ token: "attribute.value", foreground: "C9A96E" },
|
||||
{ token: "delimiter", foreground: "B0B8C4" },
|
||||
{ token: "variable", foreground: "D4D8DE" },
|
||||
],
|
||||
colors: {
|
||||
"editor.background": "#1E1E1E",
|
||||
"editor.foreground": "#E8ECF0",
|
||||
"editor.lineHighlightBackground": "#282828",
|
||||
"editor.selectionBackground": "#3A3A3A",
|
||||
"editorLineNumber.foreground": "#555555",
|
||||
"editorLineNumber.activeForeground": "#E8943A",
|
||||
"editor.inactiveSelectionBackground": "#333333",
|
||||
"editorCursor.foreground": "#E8943A",
|
||||
"editorIndentGuide.background": "#2A2A2A",
|
||||
"editorIndentGuide.activeBackground": "#3A3A3A",
|
||||
"editorWhitespace.foreground": "#2A2A2A",
|
||||
"editorGutter.background": "#1E1E1E",
|
||||
"editorOverviewRuler.border": "#1E1E1E",
|
||||
"scrollbarSlider.background": "#3A3A3A88",
|
||||
"scrollbarSlider.hoverBackground": "#4A4A4A88",
|
||||
"scrollbarSlider.activeBackground": "#5A5A5A88",
|
||||
"minimap.background": "#1E1E1E",
|
||||
},
|
||||
});
|
||||
return EditorView.theme({
|
||||
"&": {
|
||||
height: "100%",
|
||||
minHeight: "60vh",
|
||||
backgroundColor: "var(--panel)",
|
||||
color: "var(--text)",
|
||||
fontSize: "14px",
|
||||
},
|
||||
"&.cm-focused": {
|
||||
outline: "none",
|
||||
},
|
||||
".cm-scroller": {
|
||||
fontFamily: "'Iosevka', 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace",
|
||||
lineHeight: "22px",
|
||||
},
|
||||
".cm-content": {
|
||||
padding: "12px 0",
|
||||
caretColor: "var(--accent)",
|
||||
},
|
||||
".cm-line": {
|
||||
padding: "0 14px",
|
||||
},
|
||||
".cm-gutters": {
|
||||
backgroundColor: "var(--panel-strong)",
|
||||
borderRight: "1px solid var(--border)",
|
||||
color: "var(--muted)",
|
||||
},
|
||||
".cm-activeLine, .cm-activeLineGutter": {
|
||||
backgroundColor: isDark ? "oklch(0.24 0.012 55)" : "#f0ede5",
|
||||
},
|
||||
".cm-selectionBackground, &.cm-focused .cm-selectionBackground": {
|
||||
backgroundColor: "color-mix(in srgb, var(--accent) 22%, transparent)",
|
||||
},
|
||||
".cm-cursor": {
|
||||
borderLeftColor: "var(--accent)",
|
||||
},
|
||||
".cm-panel": {
|
||||
backgroundColor: "var(--panel-strong)",
|
||||
borderTop: "1px solid var(--border)",
|
||||
color: "var(--text)",
|
||||
fontFamily: "'Iosevka', 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace",
|
||||
},
|
||||
".cm-vim-panel input": {
|
||||
color: "var(--text)",
|
||||
},
|
||||
}, { dark: isDark });
|
||||
}
|
||||
|
||||
monaco.editor.defineTheme(monacoLightTheme, {
|
||||
base: "vs",
|
||||
inherit: true,
|
||||
rules: [
|
||||
{ token: "comment", foreground: "6A7B8C", fontStyle: "italic" },
|
||||
{ token: "keyword", foreground: "C67A2A" },
|
||||
{ token: "string", foreground: "8A6D3B" },
|
||||
{ token: "number", foreground: "8A6D3B" },
|
||||
{ token: "type", foreground: "C67A2A" },
|
||||
{ token: "tag", foreground: "C67A2A" },
|
||||
{ token: "attribute.name", foreground: "A87D3A" },
|
||||
{ token: "attribute.value", foreground: "8A6D3B" },
|
||||
],
|
||||
colors: {
|
||||
"editor.background": "#FCFAF5",
|
||||
"editor.foreground": "#1C2430",
|
||||
"editor.lineHighlightBackground": "#F0EDE5",
|
||||
"editor.selectionBackground": "#E8943A33",
|
||||
"editorLineNumber.foreground": "#AAAAAA",
|
||||
"editorLineNumber.activeForeground": "#C67A2A",
|
||||
"editorCursor.foreground": "#C67A2A",
|
||||
"editorIndentGuide.background": "#E8E5DD",
|
||||
"editorGutter.background": "#FCFAF5",
|
||||
},
|
||||
});
|
||||
function codeMirrorHighlighting() {
|
||||
var cm = codeMirrorModules.cm;
|
||||
var tags = codeMirrorModules.cm.tags;
|
||||
var urlColor = window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "oklch(0.78 0.14 190)"
|
||||
: "#047c78";
|
||||
|
||||
if (monacoContainer) {
|
||||
editor = monaco.editor.create(monacoContainer, {
|
||||
value: textarea.value,
|
||||
language: "markdown",
|
||||
theme: isDark ? monacoDarkTheme : monacoLightTheme,
|
||||
fontFamily: "'Iosevka', 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace",
|
||||
fontSize: 14,
|
||||
lineHeight: 22,
|
||||
minimap: { enabled: false },
|
||||
wordWrap: "on",
|
||||
scrollBeyondLastLine: false,
|
||||
padding: { top: 12 },
|
||||
renderLineHighlight: "line",
|
||||
smoothScrolling: true,
|
||||
cursorBlinking: "smooth",
|
||||
cursorSmoothCaretAnimation: "on",
|
||||
bracketPairColorization: { enabled: true },
|
||||
automaticLayout: true,
|
||||
});
|
||||
return cm.syntaxHighlighting(cm.HighlightStyle.define([
|
||||
{
|
||||
tag: [tags.url, tags.link],
|
||||
color: urlColor,
|
||||
textDecoration: "underline",
|
||||
textUnderlineOffset: "2px",
|
||||
},
|
||||
]));
|
||||
}
|
||||
|
||||
textarea.hidden = true;
|
||||
monacoContainer.classList.add("monaco-mounted");
|
||||
function createCodeMirrorEditor(mount, initialValue, options) {
|
||||
var cm = codeMirrorModules.cm;
|
||||
var markdownModule = codeMirrorModules.markdown;
|
||||
var vimModule = codeMirrorModules.vim;
|
||||
var onChange = options && options.onChange ? options.onChange : function () {};
|
||||
var extensions = [
|
||||
vimModule.vim({ status: true }),
|
||||
cm.basicSetup,
|
||||
markdownModule.markdown({
|
||||
base: markdownModule.markdownLanguage,
|
||||
}),
|
||||
cm.EditorView.lineWrapping,
|
||||
codeMirrorTheme(),
|
||||
codeMirrorHighlighting(),
|
||||
cm.EditorView.updateListener.of(function (update) {
|
||||
if (update.docChanged) {
|
||||
onChange(update.state.doc.toString());
|
||||
}
|
||||
}),
|
||||
];
|
||||
|
||||
editor.onDidChangeModelContent(function () {
|
||||
textarea.value = editor.getValue();
|
||||
scheduleSave();
|
||||
});
|
||||
mount.replaceChildren();
|
||||
mount.classList.add("codemirror-mounted");
|
||||
|
||||
if (window.MDHubSync) {
|
||||
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, function () {
|
||||
saveNow().catch(function () {
|
||||
setStatus("Queued");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var darkMQ = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
darkMQ.addEventListener("change", function (e) {
|
||||
monaco.editor.setTheme(e.matches ? monacoDarkTheme : monacoLightTheme);
|
||||
});
|
||||
|
||||
editor.focus();
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
var view = new cm.EditorView({
|
||||
doc: initialValue,
|
||||
extensions: extensions,
|
||||
parent: mount,
|
||||
});
|
||||
return monacoReadyPromise;
|
||||
|
||||
return {
|
||||
view: view,
|
||||
getValue: function () {
|
||||
return view.state.doc.toString();
|
||||
},
|
||||
setValue: function (value) {
|
||||
if (view.state.doc.toString() === value) {
|
||||
return;
|
||||
}
|
||||
view.dispatch({
|
||||
changes: { from: 0, to: view.state.doc.length, insert: value },
|
||||
});
|
||||
},
|
||||
focus: function () {
|
||||
view.focus();
|
||||
},
|
||||
hasTextFocus: function () {
|
||||
return view.hasFocus;
|
||||
},
|
||||
destroy: function () {
|
||||
view.destroy();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function ensureResolutionEditor() {
|
||||
@@ -467,54 +567,42 @@
|
||||
return;
|
||||
}
|
||||
|
||||
loadMonaco().then(function () {
|
||||
if (!window.monaco || resolutionEditor) {
|
||||
loadCodeMirror().then(function () {
|
||||
if (resolutionEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
var isDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
resolutionEditor = monaco.editor.create(conflictResolutionMount, {
|
||||
value: conflictResolution.value || resolvedConflictContent,
|
||||
language: "markdown",
|
||||
theme: isDark ? monacoDarkTheme : monacoLightTheme,
|
||||
fontFamily: "'Iosevka', 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace",
|
||||
fontSize: 14,
|
||||
lineHeight: 22,
|
||||
minimap: { enabled: false },
|
||||
wordWrap: "on",
|
||||
scrollBeyondLastLine: false,
|
||||
padding: { top: 12 },
|
||||
renderLineHighlight: "line",
|
||||
smoothScrolling: true,
|
||||
cursorBlinking: "smooth",
|
||||
cursorSmoothCaretAnimation: "on",
|
||||
bracketPairColorization: { enabled: true },
|
||||
automaticLayout: true,
|
||||
resolutionEditor = createCodeMirrorEditor(conflictResolutionMount, conflictResolution.value || resolvedConflictContent, {
|
||||
onChange: function (value) {
|
||||
if (suppressResolutionEvents) {
|
||||
return;
|
||||
}
|
||||
resolvedConflictContent = value;
|
||||
conflictResolution.value = value;
|
||||
if (conflictApply) {
|
||||
conflictApply.disabled = false;
|
||||
}
|
||||
setConflictMessage("Manual resolution updated. Apply it to save the resolved document.");
|
||||
},
|
||||
});
|
||||
|
||||
conflictResolution.hidden = true;
|
||||
conflictResolutionMount.classList.add("monaco-mounted");
|
||||
|
||||
resolutionEditor.onDidChangeModelContent(function () {
|
||||
if (suppressResolutionEvents) {
|
||||
return;
|
||||
}
|
||||
resolvedConflictContent = resolutionEditor.getValue();
|
||||
conflictResolution.value = resolvedConflictContent;
|
||||
if (conflictApply) {
|
||||
conflictApply.disabled = false;
|
||||
}
|
||||
setConflictMessage("Manual resolution updated. Apply it to save the resolved document.");
|
||||
});
|
||||
|
||||
resolutionEditor.focus();
|
||||
}).catch(function () {
|
||||
conflictResolution.hidden = false;
|
||||
});
|
||||
}
|
||||
|
||||
if (monacoContainer) {
|
||||
loadMonaco().catch(function () {
|
||||
if (codeMirrorContainer) {
|
||||
loadCodeMirror().then(function () {
|
||||
editor = createCodeMirrorEditor(codeMirrorContainer, textarea.value, {
|
||||
onChange: function (value) {
|
||||
textarea.value = value;
|
||||
scheduleSave();
|
||||
},
|
||||
});
|
||||
textarea.hidden = true;
|
||||
editor.focus();
|
||||
}).catch(function () {
|
||||
textarea.addEventListener("input", scheduleSave);
|
||||
});
|
||||
} else {
|
||||
@@ -582,59 +670,10 @@
|
||||
|
||||
restorePendingConflict();
|
||||
|
||||
var doneLink = document.querySelector("[data-done-link]");
|
||||
var isSaving = false;
|
||||
|
||||
if (doneLink) {
|
||||
doneLink.addEventListener("click", function (event) {
|
||||
var currentValue = getEditorValue();
|
||||
var href = doneLink.getAttribute("href");
|
||||
|
||||
// If there's an unresolved conflict, warn the user
|
||||
if (currentConflict) {
|
||||
event.preventDefault();
|
||||
setStatus("Conflict");
|
||||
if (conflictNotice) {
|
||||
conflictNotice.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If nothing changed, allow normal navigation
|
||||
if (currentValue === lastSavedValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Unsaved changes — cancel any pending timer and save immediately
|
||||
event.preventDefault();
|
||||
|
||||
if (saveTimer) {
|
||||
clearTimeout(saveTimer);
|
||||
saveTimer = null;
|
||||
}
|
||||
|
||||
if (isSaving) {
|
||||
// Already saving, just wait and then navigate
|
||||
waitForSaveThenNavigate(href);
|
||||
return;
|
||||
}
|
||||
|
||||
isSaving = true;
|
||||
setStatus("Saving");
|
||||
|
||||
saveNow()
|
||||
.then(function () {
|
||||
isSaving = false;
|
||||
window.location.href = href;
|
||||
})
|
||||
.catch(function () {
|
||||
isSaving = false;
|
||||
setStatus("Queued");
|
||||
// If save fails, ask user if they want to leave anyway
|
||||
if (window.confirm("Could not save. Leave without saving?")) {
|
||||
window.location.href = href;
|
||||
}
|
||||
});
|
||||
saveThenLeaveEditor();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -642,6 +681,14 @@
|
||||
var checkInterval = setInterval(function () {
|
||||
if (!isSaving) {
|
||||
clearInterval(checkInterval);
|
||||
if (currentConflict) {
|
||||
setStatus("Conflict");
|
||||
if (conflictNotice) {
|
||||
conflictNotice.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
}
|
||||
return;
|
||||
}
|
||||
suppressBeforeUnload = true;
|
||||
window.location.href = href;
|
||||
}
|
||||
}, 100);
|
||||
@@ -653,15 +700,42 @@
|
||||
|
||||
// Warn on browser unload if there are unsaved changes
|
||||
window.addEventListener("beforeunload", function (e) {
|
||||
if (suppressBeforeUnload) {
|
||||
return;
|
||||
}
|
||||
if (getEditorValue() !== lastSavedValue || currentConflict) {
|
||||
e.preventDefault();
|
||||
e.returnValue = "";
|
||||
}
|
||||
});
|
||||
|
||||
function editorSurfaceHasFocus(target) {
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
if (target === textarea || target.closest("[data-codemirror-mount], [data-conflict-codemirror-mount], [data-document-editor], .cm-editor")) {
|
||||
return true;
|
||||
}
|
||||
return Boolean(editor && editor.hasTextFocus && editor.hasTextFocus());
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", function (e) {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === "s") {
|
||||
e.preventDefault();
|
||||
saveNow().catch(function () {
|
||||
setStatus("Queued");
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (e.metaKey && e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
saveThenLeaveEditor();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!editorSurfaceHasFocus(e.target)) {
|
||||
return;
|
||||
}
|
||||
}, true);
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user