' + cat.category + '
';
+ for (const sc of cat.shortcuts) {
+ html += '
' + sc.label + '';
+ html += sc.keys.map(k => '' + k + '').join('');
+ html += '
';
+ }
+ html += '
';
+ }
+ modal.innerHTML = html;
+ overlay.appendChild(modal);
+ const close = () => overlay.remove();
+ modal.querySelector('.shortcuts-modal-close')!.addEventListener('click', close);
+ overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
+ const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') { close(); document.removeEventListener('keydown', handler); } };
+ document.addEventListener('keydown', handler);
+ return overlay;
+}
+
+export function showShortcutModal(): void {
+ if (document.querySelector('.shortcuts-modal')) return;
+ document.body.appendChild(buildShortcutModal(DOCS_SHORTCUTS));
+}
+
+// ── Find Bar ────────────────────────────────────────────────
+
+const FIND_BAR_HTML = '';
+
+export function insertFindBar(): void {
+ const editorContainerEl = document.querySelector('.editor-container');
+ if (editorContainerEl) {
+ editorContainerEl.insertAdjacentHTML('afterbegin', FIND_BAR_HTML);
+ }
+}
+
+export function wireFindBar(deps: FindBarDeps): { updateFindBar: () => void } {
+ const { editor, $ } = deps;
+ const findBar = $('find-bar');
+ const findInput = $('find-input') as HTMLInputElement;
+ const replaceInput = $('replace-input') as HTMLInputElement;
+ const findCount = $('find-count');
+ const findReplaceRow = $('find-replace-row');
+
+ function updateFindBar() {
+ const storage = editor.storage.searchReplace;
+ findBar.style.display = storage.isOpen ? '' : 'none';
+ findReplaceRow.style.display = storage.showReplace ? '' : 'none';
+
+ if (storage.matches.length > 0) {
+ findCount.textContent = (storage.activeIndex + 1) + ' / ' + storage.matches.length;
+ } else if (storage.searchTerm) {
+ findCount.textContent = 'No results';
+ } else {
+ findCount.textContent = '';
+ }
+
+ $('find-case').classList.toggle('active', storage.caseSensitive);
+
+ if (storage.isOpen && document.activeElement !== findInput && document.activeElement !== replaceInput) {
+ findInput.focus();
+ findInput.select();
+ }
+ }
+
+ findInput.addEventListener('input', () => {
+ editor.commands.setSearchTerm(findInput.value);
+ });
+
+ findInput.addEventListener('keydown', (e: KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ if (e.shiftKey) {
+ editor.commands.prevMatch();
+ } else {
+ editor.commands.nextMatch();
+ }
+ } else if (e.key === 'Escape') {
+ e.preventDefault();
+ editor.commands.closeSearch();
+ editor.commands.focus();
+ }
+ });
+
+ replaceInput.addEventListener('input', () => {
+ editor.commands.setReplaceTerm(replaceInput.value);
+ });
+
+ replaceInput.addEventListener('keydown', (e: KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ editor.commands.replaceCurrent();
+ } else if (e.key === 'Escape') {
+ e.preventDefault();
+ editor.commands.closeSearch();
+ editor.commands.focus();
+ }
+ });
+
+ $('find-next').addEventListener('click', () => editor.commands.nextMatch());
+ $('find-prev').addEventListener('click', () => editor.commands.prevMatch());
+ $('find-case').addEventListener('click', () => editor.commands.toggleCaseSensitive());
+ $('find-close').addEventListener('click', () => {
+ editor.commands.closeSearch();
+ editor.commands.focus();
+ });
+ $('find-replace-toggle').addEventListener('click', () => {
+ const storage = editor.storage.searchReplace;
+ storage.showReplace = !storage.showReplace;
+ updateFindBar();
+ });
+ $('replace-one').addEventListener('click', () => editor.commands.replaceCurrent());
+ $('replace-all').addEventListener('click', () => editor.commands.replaceAll());
+
+ return { updateFindBar };
+}
+
+// ── Global keyboard shortcuts ───────────────────────────────
+
+export function wireGlobalShortcuts(deps: KeyboardShortcutDeps): void {
+ const { editor, provider, $, exportHTML, doExportPdf, printDocument, mdToggle, showShortcutModal: showModal } = deps;
+
+ document.addEventListener('keydown', (e: KeyboardEvent) => {
+ const mod = e.metaKey || e.ctrlKey;
+ if (mod && e.key === '/') {
+ e.preventDefault();
+ showModal();
+ return;
+ }
+ if (mod && !e.shiftKey && e.key.toLowerCase() === 'f') {
+ e.preventDefault();
+ editor.commands.openSearch();
+ return;
+ }
+ if (mod && e.key.toLowerCase() === 'h') {
+ e.preventDefault();
+ editor.commands.openSearchReplace();
+ return;
+ }
+ if (mod && e.shiftKey && e.key.toLowerCase() === 'g') {
+ if (editor.storage.searchReplace.isOpen) {
+ e.preventDefault();
+ editor.commands.prevMatch();
+ return;
+ }
+ }
+ if (mod && e.key.toLowerCase() === 'g') {
+ if (editor.storage.searchReplace.isOpen) {
+ e.preventDefault();
+ editor.commands.nextMatch();
+ return;
+ }
+ }
+ if (mod && e.shiftKey && e.key.toLowerCase() === 's') {
+ e.preventDefault();
+ exportHTML();
+ } else if (mod && e.key.toLowerCase() === 's') {
+ e.preventDefault();
+ provider._saveSnapshot();
+ } else if (mod && e.shiftKey && e.key.toLowerCase() === 'p') {
+ e.preventDefault();
+ doExportPdf();
+ } else if (mod && e.key.toLowerCase() === 'p') {
+ e.preventDefault();
+ printDocument();
+ } else if (mod && e.shiftKey && e.key.toLowerCase() === 'm') {
+ e.preventDefault();
+ mdToggle.toggle();
+ }
+ });
+}
diff --git a/src/docs/main.ts b/src/docs/main.ts
index e3570ae..991abde 100644
--- a/src/docs/main.ts
+++ b/src/docs/main.ts
@@ -48,14 +48,10 @@ import { SearchReplace } from './search-replace.js';
import { TabSupport } from './tab-support.js';
import { MarkdownAutoformat } from './extensions/markdown-autoformat.js';
import { WikiLink } from './extensions/wiki-link.js';
-import { exportPdf } from './pdf-export.js';
-import { exportDocx } from './docx-export.js';
-import { importDocx, isValidDocx } from './docx-import.js';
import { markdownToHtml } from './markdown-parser.js';
import { looksLikeMarkdown, htmlContainsRawMarkdown } from './markdown-paste.js';
import { htmlToMarkdown as turndownHtmlToMarkdown } from './markdown-export.js';
import { createMarkdownToggle, TOGGLE_MODE } from './markdown-toggle.js';
-import { VersionManager, computeWordCount } from '../lib/version-history.js';
import { createVersionPanel } from '../version-panel.js';
import { extractHeadings, computeViewportIndicator } from './minimap.js';
import {
@@ -82,7 +78,7 @@ import {
} from '../lib/follow-mode.js';
import { SuggestionManager, createSuggestionAttrs } from '../lib/suggesting.js';
import { OfflineManager } from '../lib/offline.js';
-import { extractHeadings, OutlineState } from './outline.js';
+import { extractHeadings as extractOutlineHeadings, OutlineState } from './outline.js';
import { TableToolbarState } from './table-toolbar.js';
import { LinkPreviewState, truncateUrl, computeTooltipPosition } from './link-preview.js';
import {
@@ -96,9 +92,31 @@ import { executeDocAction } from './ai-doc-actions.js';
import { ZenModeState, ZEN_STORAGE_KEY, ZEN_CLASS, ZEN_TRANSITION_MS } from './zen-mode.js';
import { SLASH_COMMAND_ITEMS, SlashMenuState, filterCommands, PLACEHOLDER_EMPTY, PLACEHOLDER_BLOCK } from './slash-menu.js';
import { createSlashCommands, getCommandExecutor } from './extensions/slash-commands.js';
-import { BlockHandleState, BLOCK_HANDLE_ACTIONS, TURN_INTO_ITEMS, filterTurnIntoItems, BLOCK_HANDLE_ICON, BLOCK_HANDLE_ADD_ICON } from './block-handle.js';
import { createCommandPalette, type PaletteAction } from '../command-palette.js';
+// --- Extracted modules ---
+import {
+ closeAllDropdowns as _closeAllDropdowns,
+ toggleDropdown as _toggleDropdown,
+ wireDropdownCloseHandlers,
+ wireToolbarButtons,
+ wireInlineComments,
+} from './toolbar-wiring.js';
+import {
+ DOCS_SHORTCUTS,
+ showShortcutModal,
+ insertFindBar,
+ wireFindBar,
+ wireGlobalShortcuts,
+} from './keyboard-shortcuts.js';
+import {
+ showToast,
+ handleImportedFile,
+ wireExportImportButtons,
+} from './export-import.js';
+import { wireVersionHistory, wireShareDialog } from './version-history-ui.js';
+import { wireBlockHandleUI } from './block-handle-ui.js';
+
// --- Resolve document ID and encryption key ---
const pathParts = location.pathname.split('/').filter(Boolean);
const docId = pathParts[1];
@@ -210,7 +228,7 @@ const editor = new Editor({
SuggestionInsert,
SuggestionDelete,
SearchReplace.configure({
- onStateChange: () => updateFindBar(),
+ onStateChange: () => findBarResult.updateFindBar(),
}),
TabSupport,
MarkdownAutoformat,
@@ -381,610 +399,20 @@ function hideSlashMenu() {
slashMenuCommandRef = null;
}
-// --- Block Handle ---
-const blockHandleState = new BlockHandleState();
-
-const blockHandleEl = document.createElement('div');
-blockHandleEl.className = 'block-handle';
-blockHandleEl.id = 'block-handle';
-blockHandleEl.style.display = 'none';
-blockHandleEl.innerHTML = `