优化后台功能
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js"></script>
|
||||
<script src="{{ asset('vendor/easymde/easymde.min.js') }}"></script>
|
||||
<script>
|
||||
(function () {
|
||||
const previewEndpoint = '{{ route('admin.markdown.preview') }}';
|
||||
@@ -33,11 +33,11 @@
|
||||
|
||||
const markdown = editor.value();
|
||||
if (!markdown || markdown.trim() === '') {
|
||||
previewElement.innerHTML = '<div class="preview-placeholder">在左侧输入 Markdown 内容,这里会实时显示预览。</div>';
|
||||
previewElement.innerHTML = '<div class="preview-placeholder">Input Markdown on the left. Preview renders here.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
previewElement.innerHTML = '<div class="text-muted">预览生成中...</div>';
|
||||
previewElement.innerHTML = '<div class="text-muted">Generating preview...</div>';
|
||||
|
||||
try {
|
||||
const response = await fetch(previewEndpoint, {
|
||||
@@ -61,9 +61,9 @@
|
||||
|
||||
previewElement.innerHTML = payload.html && payload.html.trim() !== ''
|
||||
? payload.html
|
||||
: '<div class="preview-placeholder">暂无可预览内容。</div>';
|
||||
: '<div class="preview-placeholder">No preview content.</div>';
|
||||
} catch (_) {
|
||||
previewElement.innerHTML = '<div class="text-danger">预览失败,请稍后重试。</div>';
|
||||
previewElement.innerHTML = '<div class="text-danger">Preview failed. Please retry.</div>';
|
||||
}
|
||||
}, 320);
|
||||
|
||||
@@ -101,6 +101,89 @@
|
||||
return `\n\n`;
|
||||
};
|
||||
|
||||
const syncBodyFullscreenClass = () => {
|
||||
const hasFullscreen = document.querySelector('.editor-shell.is-fullscreen') !== null;
|
||||
document.body.classList.toggle('editor-fullscreen-open', hasFullscreen);
|
||||
};
|
||||
|
||||
const refreshEditorLayout = (editor) => {
|
||||
if (!editor || !editor.codemirror) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
editor.codemirror.refresh();
|
||||
});
|
||||
};
|
||||
|
||||
const updateFullscreenButton = (button, isFullscreen) => {
|
||||
if (!(button instanceof HTMLButtonElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
button.innerHTML = isFullscreen
|
||||
? '<i class="bi bi-fullscreen-exit me-1"></i>Exit Fullscreen'
|
||||
: '<i class="bi bi-arrows-fullscreen me-1"></i>Fullscreen';
|
||||
};
|
||||
|
||||
const initShellControls = (textarea, editor, previewElement) => {
|
||||
const shell = textarea.closest('.editor-shell');
|
||||
if (!(shell instanceof HTMLElement) || shell.dataset.initialized === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
const tabButtons = Array.from(shell.querySelectorAll('.js-md-tab-btn'));
|
||||
const panels = Array.from(shell.querySelectorAll('.editor-panel'));
|
||||
const fullscreenButton = shell.querySelector('.js-md-fullscreen-btn');
|
||||
|
||||
const activateTab = (tab) => {
|
||||
tabButtons.forEach((button) => {
|
||||
const active = button.getAttribute('data-tab') === tab;
|
||||
button.classList.toggle('is-active', active);
|
||||
button.setAttribute('aria-selected', active ? 'true' : 'false');
|
||||
});
|
||||
|
||||
panels.forEach((panel) => {
|
||||
const active = panel.getAttribute('data-panel') === tab;
|
||||
panel.classList.toggle('is-active', active);
|
||||
});
|
||||
|
||||
if (tab === 'preview') {
|
||||
renderPreview(editor, previewElement);
|
||||
}
|
||||
|
||||
refreshEditorLayout(editor);
|
||||
};
|
||||
|
||||
tabButtons.forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
activateTab(button.getAttribute('data-tab') || 'edit');
|
||||
});
|
||||
});
|
||||
|
||||
if (fullscreenButton instanceof HTMLButtonElement) {
|
||||
fullscreenButton.addEventListener('click', () => {
|
||||
const willEnterFullscreen = !shell.classList.contains('is-fullscreen');
|
||||
|
||||
document.querySelectorAll('.editor-shell.is-fullscreen').forEach((openedShell) => {
|
||||
if (openedShell !== shell) {
|
||||
openedShell.classList.remove('is-fullscreen');
|
||||
const button = openedShell.querySelector('.js-md-fullscreen-btn');
|
||||
updateFullscreenButton(button, false);
|
||||
}
|
||||
});
|
||||
|
||||
shell.classList.toggle('is-fullscreen', willEnterFullscreen);
|
||||
updateFullscreenButton(fullscreenButton, willEnterFullscreen);
|
||||
syncBodyFullscreenClass();
|
||||
refreshEditorLayout(editor);
|
||||
});
|
||||
}
|
||||
|
||||
shell.dataset.initialized = 'true';
|
||||
activateTab('edit');
|
||||
};
|
||||
|
||||
const initEditor = (textarea) => {
|
||||
if (!(textarea instanceof HTMLTextAreaElement) || instances.has(textarea.name)) {
|
||||
return;
|
||||
@@ -112,7 +195,7 @@
|
||||
autoDownloadFontAwesome: false,
|
||||
forceSync: true,
|
||||
status: ['lines', 'words'],
|
||||
placeholder: textarea.placeholder || '请输入 Markdown 内容',
|
||||
placeholder: textarea.placeholder || 'Write Markdown here',
|
||||
toolbar: [
|
||||
'bold',
|
||||
'italic',
|
||||
@@ -126,10 +209,6 @@
|
||||
'image',
|
||||
'table',
|
||||
'|',
|
||||
'preview',
|
||||
'side-by-side',
|
||||
'fullscreen',
|
||||
'|',
|
||||
'guide',
|
||||
],
|
||||
});
|
||||
@@ -142,6 +221,7 @@
|
||||
});
|
||||
|
||||
renderPreview(easyMde, previewElement);
|
||||
initShellControls(textarea, easyMde, previewElement);
|
||||
instances.set(textarea.name, easyMde);
|
||||
};
|
||||
|
||||
@@ -166,13 +246,13 @@
|
||||
|
||||
const originHtml = button.innerHTML;
|
||||
button.disabled = true;
|
||||
button.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>上传中...';
|
||||
button.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Uploading...';
|
||||
|
||||
try {
|
||||
const payload = await uploadImage(file);
|
||||
editor.codemirror.replaceSelection(markdownForImage(payload, file.name || 'image'));
|
||||
} catch (_) {
|
||||
alert('图片上传失败,请稍后重试');
|
||||
alert('Image upload failed. Please retry.');
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
button.innerHTML = originHtml;
|
||||
@@ -215,6 +295,21 @@
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key !== 'Escape') {
|
||||
return;
|
||||
}
|
||||
|
||||
document.querySelectorAll('.editor-shell.is-fullscreen').forEach((shell) => {
|
||||
shell.classList.remove('is-fullscreen');
|
||||
const button = shell.querySelector('.js-md-fullscreen-btn');
|
||||
updateFullscreenButton(button, false);
|
||||
});
|
||||
|
||||
syncBodyFullscreenClass();
|
||||
instances.forEach((editor) => refreshEditorLayout(editor));
|
||||
});
|
||||
|
||||
document.querySelectorAll('textarea.js-md-editor-modern').forEach(initEditor);
|
||||
connectUploadButtons();
|
||||
initSlugAutoFill();
|
||||
|
||||
Reference in New Issue
Block a user