diff --git a/src/embed-manager.js b/src/embed-manager.js index 366bf01..488425c 100644 --- a/src/embed-manager.js +++ b/src/embed-manager.js @@ -149,10 +149,8 @@ class EmbedManager { // Check if we should load all embeds on page load if (this.plugin.settings.loadAllOnPageLoad) { - // Load immediately without intersection observer - requestAnimationFrame(() => { - this.loadEmbed(embedContainer, file, section, displayAlias, ctx, placeholder, options); - }); + await this.loadEmbed(embedContainer, file, section, displayAlias, ctx, placeholder, options); + await new Promise(r => setTimeout(r, 50)); } else { // Aggressive lazy loading to prevent scrollbar jumps const observer = new IntersectionObserver((entries) => { diff --git a/src/main.js b/src/main.js index e404982..a528818 100644 --- a/src/main.js +++ b/src/main.js @@ -16,8 +16,6 @@ const DEFAULT_SETTINGS = { loadAllOnPageLoad: false, showFocusHighlight: true, showHeaderHints: true, // NEW: Header hints (enforcement is always on) - hideSectionHeaders: false, // NEW: Hide headers in section embeds - showPropertiesToggle: true, // NEW: Show properties collapse toggle button debugMode: false }; @@ -34,11 +32,11 @@ module.exports = class SyncEmbedPlugin extends Plugin { async onload() { await this.loadSettings(); - + // Initialize managers this.embedManager = new EmbedManager(this); this.commandInterceptor = new CommandInterceptor(this); - + // Setup command interception with monkey-around if (this.settings.enableCommandInterception) { this.setupCommandInterception(); @@ -90,26 +88,26 @@ module.exports = class SyncEmbedPlugin extends Plugin { // Add settings tab this.addSettingTab(new SyncEmbedsSettingTab(this.app, this)); - + // Apply focus highlight setting this.updateFocusHighlight(); - + // Log successful load this.log('Sync Embeds plugin loaded successfully'); } onunload() { this.log('Unloading Sync Embeds plugin'); - + // Clean up command interception this.uninstallers.forEach(uninstall => uninstall()); this.uninstallers = []; - + // Clean up managers if (this.embedManager) { this.embedManager.cleanup(); } - + this.currentFocusedEmbed = null; } @@ -121,7 +119,7 @@ module.exports = class SyncEmbedPlugin extends Plugin { { level: 5, name: 'Heading 5', key: '5' }, { level: 6, name: 'Heading 6', key: '6' }, ]; - + headerLevels.forEach(({ level, name, key }) => { this.addCommand({ id: `insert-header-${level}`, @@ -129,13 +127,13 @@ module.exports = class SyncEmbedPlugin extends Plugin { editorCallback: (editor, view) => { // Check if we're in a sync embed const focusedEmbed = this.getFocusedEmbed(); - + if (focusedEmbed) { // Use our custom handler const handler = this.commandInterceptor.insertHeaderCommand(level); return handler(focusedEmbed); } - + // Fall back to normal behavior for non-embed editing this.commandInterceptor.insertHeader({ editor }, level); }, @@ -147,7 +145,7 @@ module.exports = class SyncEmbedPlugin extends Plugin { ] }); }); - + this.log('Registered header commands with default hotkeys (Alt+2-6)'); } @@ -159,7 +157,7 @@ module.exports = class SyncEmbedPlugin extends Plugin { const plugin = this; return function(command, ...args) { const focusedEmbed = plugin.getFocusedEmbed(); - + // If embed is focused, check if we should intercept if (focusedEmbed) { // Check for our header commands first @@ -170,17 +168,17 @@ module.exports = class SyncEmbedPlugin extends Plugin { const handler = plugin.commandInterceptor.insertHeaderCommand(level); return handler(focusedEmbed); } - + // Check if we have a custom handler if (plugin.commandInterceptor.hasHandler(command.id)) { plugin.log(`Intercepting command: ${command.id}`); return plugin.commandInterceptor.handle(command.id, focusedEmbed, ...args); } - + // For commands we don't handle, let them execute on the embed's editor // This ensures ALL hotkeys work, including custom user-defined ones plugin.log(`Passing through command to embed: ${command.id}`); - + // Check if command has a callback that expects an editor if (command.editorCallback && focusedEmbed.editor) { try { @@ -191,7 +189,7 @@ module.exports = class SyncEmbedPlugin extends Plugin { } } } - + return old.call(this, command, ...args); }; } @@ -213,7 +211,7 @@ module.exports = class SyncEmbedPlugin extends Plugin { } }); this.uninstallers.push(getActiveViewUninstall); - + // Patch getActiveViewOfType on workspace.activeLeaf as well const getActiveLeafUninstall = around(this.app.workspace, { activeLeaf: { @@ -231,7 +229,7 @@ module.exports = class SyncEmbedPlugin extends Plugin { } }); this.uninstallers.push(getActiveLeafUninstall); - + this.log('Command interception setup complete'); } catch (error) { console.error('Sync Embeds: Failed to setup command interception:', error); @@ -285,12 +283,7 @@ module.exports = class SyncEmbedPlugin extends Plugin { container.style.setProperty('--sync-max-height', this.settings.maxEmbedHeight); container.style.setProperty('--sync-gap', this.settings.gapBetweenEmbeds); }); - - // Update viewport CSS for section embeds (header visibility) - if (this.embedManager) { - this.embedManager.refreshViewportCSS(); - } - + this.log('Refreshed all embeds with new settings'); } diff --git a/src/settings.js b/src/settings.js index acbb14c..99916e9 100644 --- a/src/settings.js +++ b/src/settings.js @@ -14,7 +14,7 @@ class SyncEmbedsSettingTab extends PluginSettingTab { // === APPEARANCE SECTION === containerEl.createEl('h3', { text: 'Appearance' }); - + new Setting(containerEl) .setName('Render as callout') .setDesc('Render embeds as callouts with sticky headers and collapse functionality') @@ -148,16 +148,6 @@ class SyncEmbedsSettingTab extends PluginSettingTab { await this.plugin.saveSettings(); })); - new Setting(containerEl) - .setName('Show properties toggle button') - .setDesc('Display a toggle button to collapse/expand properties in embeds') - .addToggle(toggle => toggle - .setValue(this.plugin.settings.showPropertiesToggle) - .onChange(async (value) => { - this.plugin.settings.showPropertiesToggle = value; - await this.plugin.saveSettings(); - })); - new Setting(containerEl) .setName('Show inline title') .setDesc('Display note title at the top of whole-note embeds (not applicable to section embeds or embeds with aliases)') @@ -179,17 +169,7 @@ class SyncEmbedsSettingTab extends PluginSettingTab { })); // === HEADER MANAGEMENT SECTION === - containerEl.createEl('h3', { text: 'Header Management' }); - - new Setting(containerEl) - .setName('Hide section headers') - .setDesc('Hide the header when embedding a section under a header') - .addToggle(toggle => toggle - .setValue(this.plugin.settings.hideSectionHeaders) - .onChange(async (value) => { - this.plugin.settings.hideSectionHeaders = value; - await this.plugin.saveSettings(); - })); + containerEl.createEl('h3', { text: 'Header Management' }); new Setting(containerEl) .setName('Show header hints') @@ -265,7 +245,7 @@ class SyncEmbedsSettingTab extends PluginSettingTab {
  • ![[Note Name|Custom Title]] - Display with custom title
  • ![[Note Name#Section|Custom Title]] - Section with custom title
  • - +

    Per-Embed Custom Options:

    Note: Options go inside curly braces before the closing ]]

    - +

    Dynamic Patterns:

    - +

    Header Management:

    - +

    Date Format Examples:

    - +

    Complete Example:

    \`\`\`sync
     ![[Daily Notes/{{date:YYYY-MM-DD}}|Today's Note]]
    @@ -314,7 +294,7 @@ class SyncEmbedsSettingTab extends PluginSettingTab {
     ![[Tasks#Inbox|My Tasks{height:300px}]]
     ![[Projects/{{title}}#Notes|Project Notes{title:false}]]
     \`\`\`
    - +

    Tips:

    - +

    Note: Dynamic patterns are cached for 1 second to improve performance.

    `; @@ -377,15 +357,15 @@ class SyncEmbedsSettingTab extends PluginSettingTab { validateCSSValue(value) { // Basic validation for CSS length values if (!value || value.trim() === '') return false; - + // Allow common CSS units const validPattern = /^(auto|none|\d+(\.\d+)?(px|em|rem|vh|vw|%))$/; const isValid = validPattern.test(value.trim()); - + if (!isValid) { new Notice('Invalid CSS value. Use units like: px, em, rem, vh, vw, %, or "auto"/"none"'); } - + return isValid; } } diff --git a/src/viewport-controller.js b/src/viewport-controller.js index 5514898..3e17600 100644 --- a/src/viewport-controller.js +++ b/src/viewport-controller.js @@ -33,7 +33,7 @@ class ViewportController { applyViewportRestriction(embedData) { const { view } = embedData; - + const style = document.createElement('style'); style.className = 'sync-viewport-style'; @@ -47,7 +47,7 @@ class ViewportController { } updateViewportCSS(embedData, style) { - const { sectionInfo, embedId, propertiesLineCount = 0, file } = embedData; + const { sectionInfo, embedId, file } = embedData; const { startLine, endLine } = sectionInfo; // Frontmatter DOM Offset compensation @@ -62,22 +62,14 @@ class ViewportController { const domStartLine = Math.max(0, startLine - domOffset); const domEndLine = Math.max(0, endLine - domOffset); - - let adjustedStartLine = domStartLine - propertiesLineCount; - let adjustedEndLine = domEndLine - propertiesLineCount; - - if (hideHeader) { - adjustedStartLine += 1; - } - const css = ` /* Hide all lines BEFORE and INCLUDING the section header */ - [data-embed-id="${embedId}"] .cm-line:nth-child(-n+${adjustedStartLine + 1}) { + [data-embed-id="${embedId}"] .cm-line:nth-child(-n+${domStartLine + 1}) { display: none !important; } /* Hide all lines AFTER the section */ - [data-embed-id="${embedId}"] .cm-line:nth-child(n+${adjustedEndLine + 1}) { + [data-embed-id="${embedId}"] .cm-line:nth-child(n+${domEndLine + 1}) { display: none !important; } @@ -247,27 +239,7 @@ class ViewportController { setupContentConstraints(embedData) { const { view, editor, component } = embedData; let isProgrammaticUpdate = false; - - // CRITICAL FIX: Update viewport IMMEDIATELY before visible changes - const updateViewportImmediately = () => { - if (!embedData.viewportActive) return; - - const currentContent = editor.getValue(); - const newSectionInfo = this.findSectionBounds(currentContent, embedData.section); - - if (newSectionInfo.startLine !== -1) { - embedData.sectionInfo = newSectionInfo; - - // recalculate properties line count on content change - embedData.propertiesLineCount = this.countPropertiesLines(currentContent); - - // Update CSS synchronously to prevent flashing - if (embedData.viewportStyle) { - this.updateViewportCSS(embedData, embedData.viewportStyle); - } - } - }; - + // Force cursor strictly inside bounds (preventing up/down arrow drifting) const enforceCursorBounds = () => { if (isProgrammaticUpdate || !embedData.viewportActive || !embedData.sectionInfo) return; @@ -330,14 +302,14 @@ class ViewportController { const scrollTop = cmScroller.scrollTop; const lineHeight = editor.defaultTextHeight || 20; const firstVisibleLine = Math.floor(scrollTop / lineHeight); - + // Frontmatter DOM Offset compensation for scrolling bounds let domOffset = 0; const fileCache = this.plugin.app.metadataCache.getFileCache(embedData.file); if (fileCache && fileCache.frontmatterPosition) { domOffset = fileCache.frontmatterPosition.end.line; } - + const domStartLine = Math.max(0, embedData.sectionInfo.startLine - domOffset); const domEndLine = Math.max(0, embedData.sectionInfo.endLine - domOffset); @@ -416,35 +388,6 @@ class ViewportController { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } - countPropertiesLines(content) { - // Count the number of lines used by frontmatter/properties - // Properties are wrapped in --- markers at the start of the file - const lines = content.split('\n'); - - // Check if file starts with properties - if (lines.length === 0 || lines[0] !== '---') { - return 0; - } - - // Find the closing --- marker - let propertiesEndLine = -1; - for (let i = 1; i < lines.length; i++) { - if (lines[i] === '---') { - propertiesEndLine = i; - break; - } - } - - if (propertiesEndLine === -1) { - // Invalid frontmatter, no closing marker - return 0; - } - - // Return the number of lines including both --- markers - // These lines are NOT rendered as .cm-line elements - return propertiesEndLine + 1; - } - cleanupViewport(embedData) { if (embedData.viewportStyle) { embedData.viewportStyle.remove();