Harden auth/vault path sanitization and clean WeKnora docs
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
// MNote UI 基础运行时:全局 portal 与 toast API。
|
||||
// MNote UI 基础运行时:全局 portal、toast、居中 confirm/alert。
|
||||
(function initMnoteUiRuntime() {
|
||||
if (window.__mnoteUiRuntimeStarted) return;
|
||||
window.__mnoteUiRuntimeStarted = true;
|
||||
|
||||
var nativeConfirm = typeof window.confirm === 'function' ? window.confirm.bind(window) : null;
|
||||
var nativeAlert = typeof window.alert === 'function' ? window.alert.bind(window) : null;
|
||||
var activeDialog = null;
|
||||
|
||||
function ensurePortalRoot() {
|
||||
var root = document.getElementById('mnote-portal-root');
|
||||
if (root instanceof HTMLElement) return root;
|
||||
@@ -52,15 +56,192 @@
|
||||
return item;
|
||||
}
|
||||
|
||||
function closeActiveDialog(result) {
|
||||
if (!activeDialog) return;
|
||||
var state = activeDialog;
|
||||
activeDialog = null;
|
||||
document.removeEventListener('keydown', state.onKeyDown, true);
|
||||
if (state.backdrop && state.backdrop.parentNode) {
|
||||
state.backdrop.parentNode.removeChild(state.backdrop);
|
||||
}
|
||||
if (typeof state.resolve === 'function') state.resolve(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 居中对话框。
|
||||
* @param {object} options
|
||||
* @param {string} [options.title]
|
||||
* @param {string} options.message
|
||||
* @param {'confirm'|'alert'} [options.mode]
|
||||
* @param {string} [options.confirmLabel]
|
||||
* @param {string} [options.cancelLabel]
|
||||
* @param {'primary'|'danger'} [options.tone]
|
||||
* @returns {Promise<boolean>} confirm 模式:确定=true;alert 模式始终 true
|
||||
*/
|
||||
function showDialog(options) {
|
||||
var config = options || {};
|
||||
var message = String(config.message || '').trim();
|
||||
if (!message) return Promise.resolve(config.mode === 'alert');
|
||||
|
||||
return new Promise(function (resolve) {
|
||||
if (activeDialog) closeActiveDialog(false);
|
||||
|
||||
var mode = config.mode === 'alert' ? 'alert' : 'confirm';
|
||||
var tone = String(config.tone || 'primary');
|
||||
var title =
|
||||
String(config.title || (mode === 'alert' ? '提示' : '确认')).trim() ||
|
||||
(mode === 'alert' ? '提示' : '确认');
|
||||
var confirmLabel = String(config.confirmLabel || (mode === 'alert' ? '知道了' : '确定')).trim();
|
||||
var cancelLabel = String(config.cancelLabel || '取消').trim();
|
||||
|
||||
var backdrop = document.createElement('div');
|
||||
backdrop.className = 'mnote-dialog-backdrop';
|
||||
backdrop.setAttribute('data-mnote-dialog-backdrop', 'true');
|
||||
backdrop.setAttribute('data-mode', mode);
|
||||
|
||||
var dialog = document.createElement('div');
|
||||
dialog.className =
|
||||
'mnote-dialog' + (tone === 'danger' ? ' mnote-dialog--danger' : '');
|
||||
dialog.setAttribute('role', 'dialog');
|
||||
dialog.setAttribute('aria-modal', 'true');
|
||||
dialog.setAttribute('data-mnote-dialog', 'true');
|
||||
|
||||
var body = document.createElement('div');
|
||||
body.className = 'mnote-dialog__body';
|
||||
|
||||
var titleEl = document.createElement('h2');
|
||||
titleEl.className = 'mnote-dialog__title';
|
||||
titleEl.textContent = title;
|
||||
body.appendChild(titleEl);
|
||||
|
||||
var messageEl = document.createElement('p');
|
||||
messageEl.className = 'mnote-dialog__message';
|
||||
messageEl.textContent = message;
|
||||
body.appendChild(messageEl);
|
||||
dialog.appendChild(body);
|
||||
|
||||
var actions = document.createElement('div');
|
||||
actions.className = 'mnote-dialog__actions';
|
||||
|
||||
var confirmBtn = document.createElement('button');
|
||||
confirmBtn.type = 'button';
|
||||
confirmBtn.className =
|
||||
'mnote-dialog__btn ' +
|
||||
(tone === 'danger' ? 'mnote-dialog__btn--danger' : 'mnote-dialog__btn--primary');
|
||||
confirmBtn.setAttribute('data-role', 'confirm');
|
||||
confirmBtn.textContent = confirmLabel || '确定';
|
||||
|
||||
if (mode === 'confirm') {
|
||||
var cancelBtn = document.createElement('button');
|
||||
cancelBtn.type = 'button';
|
||||
cancelBtn.className = 'mnote-dialog__btn';
|
||||
cancelBtn.setAttribute('data-role', 'cancel');
|
||||
cancelBtn.textContent = cancelLabel || '取消';
|
||||
cancelBtn.addEventListener('click', function () {
|
||||
closeActiveDialog(false);
|
||||
});
|
||||
actions.appendChild(cancelBtn);
|
||||
}
|
||||
|
||||
confirmBtn.addEventListener('click', function () {
|
||||
closeActiveDialog(true);
|
||||
});
|
||||
actions.appendChild(confirmBtn);
|
||||
dialog.appendChild(actions);
|
||||
backdrop.appendChild(dialog);
|
||||
|
||||
backdrop.addEventListener('click', function (event) {
|
||||
if (event.target === backdrop) {
|
||||
closeActiveDialog(mode === 'alert' ? true : false);
|
||||
}
|
||||
});
|
||||
|
||||
var onKeyDown = function (event) {
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
closeActiveDialog(mode === 'alert' ? true : false);
|
||||
} else if (event.key === 'Enter') {
|
||||
var tag = (event.target && event.target.tagName) || '';
|
||||
if (tag === 'TEXTAREA' || tag === 'BUTTON') return;
|
||||
event.preventDefault();
|
||||
closeActiveDialog(true);
|
||||
}
|
||||
};
|
||||
|
||||
activeDialog = {
|
||||
backdrop: backdrop,
|
||||
resolve: resolve,
|
||||
onKeyDown: onKeyDown,
|
||||
};
|
||||
document.addEventListener('keydown', onKeyDown, true);
|
||||
ensurePortalRoot().appendChild(backdrop);
|
||||
(mode === 'confirm' ? actions.querySelector('[data-role="cancel"]') || confirmBtn : confirmBtn).focus({
|
||||
preventScroll: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function confirmDialog(message, options) {
|
||||
var config = options && typeof options === 'object' ? options : {};
|
||||
return showDialog({
|
||||
mode: 'confirm',
|
||||
message: message,
|
||||
title: config.title,
|
||||
confirmLabel: config.confirmLabel || config.okText,
|
||||
cancelLabel: config.cancelLabel || config.cancelText,
|
||||
tone: config.tone || (config.danger ? 'danger' : 'primary'),
|
||||
});
|
||||
}
|
||||
|
||||
function alertDialog(message, options) {
|
||||
var config = options && typeof options === 'object' ? options : {};
|
||||
return showDialog({
|
||||
mode: 'alert',
|
||||
message: message,
|
||||
title: config.title || '提示',
|
||||
confirmLabel: config.confirmLabel || config.okText || '知道了',
|
||||
tone: config.tone || (config.kind === 'error' || config.danger ? 'danger' : 'primary'),
|
||||
}).then(function () {
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
window.mnote = window.mnote || {};
|
||||
window.mnote.ensurePortalRoot = ensurePortalRoot;
|
||||
window.mnote.toast = toast;
|
||||
window.mnote.confirm = confirmDialog;
|
||||
window.mnote.alert = alertDialog;
|
||||
window.mnote.dialog = showDialog;
|
||||
|
||||
// 全局替换:统一返回 Promise。调用点需 await(已在各 runtime 迁移)。
|
||||
window.alert = function mnoteWindowAlert(message) {
|
||||
if (!document.body) {
|
||||
if (nativeAlert) nativeAlert(message);
|
||||
return Promise.resolve();
|
||||
}
|
||||
return alertDialog(message);
|
||||
};
|
||||
|
||||
window.confirm = function mnoteWindowConfirm(message) {
|
||||
if (!document.body) {
|
||||
return Promise.resolve(nativeConfirm ? !!nativeConfirm(message) : true);
|
||||
}
|
||||
return confirmDialog(message);
|
||||
};
|
||||
|
||||
window.addEventListener('mnote:toast', function onMnoteToast(event) {
|
||||
var detail = event && event.detail ? event.detail : {};
|
||||
toast(detail.message, detail);
|
||||
});
|
||||
|
||||
window.addEventListener('mnote:confirm', function onMnoteConfirm(event) {
|
||||
var detail = event && event.detail ? event.detail : {};
|
||||
var message = detail.message || '';
|
||||
confirmDialog(message, detail).then(function (ok) {
|
||||
if (typeof detail.onResult === 'function') detail.onResult(ok);
|
||||
});
|
||||
});
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', ensurePortalRoot, { once: true });
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user