diff --git a/main.js b/main.js new file mode 100644 index 0000000..c0c48bd --- /dev/null +++ b/main.js @@ -0,0 +1,101 @@ +const { Plugin, MarkdownView, WorkspaceLeaf, Component } = require('obsidian'); + +module.exports = class SyncEmbedPlugin extends Plugin { + async onload() { + console.log('Loading Sync Embeds Plugin'); + + // Add the command to insert a sync block. + this.addCommand({ + id: 'insert-synced-embed', + name: 'Insert synced embed', + editorCallback: (editor, view) => { + const selection = editor.getSelection(); + // If the user has selected text, use it as the note name. + const noteName = selection || ' '; + const textToInsert = `\`\`\`sync\n![[${noteName}]]\n\`\`\``; + editor.replaceSelection(textToInsert); + } + }); + + // Register the main code block processor. + this.registerMarkdownCodeBlockProcessor('sync', (source, el, ctx) => { + this.processSyncBlock(source, el, ctx); + }); + } + + onunload() { + console.log('Unloading Sync Embeds Plugin'); + } + + async processSyncBlock(source, el, ctx) { + el.empty(); + const syncContainer = el.createDiv('sync-container'); + + const embedLines = source.split('\n') + .map(line => line.trim()) + .filter(line => line.startsWith('![[') && line.endsWith(']]')); + + if (embedLines.length === 0) { + syncContainer.createDiv('sync-empty').setText('No embeds found in sync block'); + return; + } + + for (let i = 0; i < embedLines.length; i++) { + const embedLine = embedLines[i]; + await this.processEmbed(embedLine, syncContainer, ctx, i > 0); + } + } + + async processEmbed(embedLine, container, ctx, addGap = false) { + try { + const match = embedLine.match(/!\[\[([^\]]+)\]\]/); + if (!match) return; + + const linkText = match[1]; + const notePath = linkText.split('#')[0]; + + const file = this.app.metadataCache.getFirstLinkpathDest(notePath, ctx.sourcePath); + if (!file) { + this.renderError(container, `Note not found: ${notePath}`, addGap); + return; + } + + const embedContainer = container.createDiv('sync-embed'); + if (addGap) embedContainer.addClass('sync-embed-gap'); + + const component = new Component(); + const leaf = new WorkspaceLeaf(this.app); + + component.load(); + component.addChild(new (class extends Component { + async onunload() { + leaf.detach(); + } + })()); + + await leaf.openFile(file, { state: { mode: "source" } }); + + const view = leaf.view; + if (!(view instanceof MarkdownView)) { + this.renderError(embedContainer, 'Failed to load a markdown view.', addGap); + leaf.detach(); + return; + } + + embedContainer.appendChild(view.containerEl); + embedContainer.style.height = 'auto'; + + ctx.addChild(component); + + } catch (error) { + console.error('Sync Embeds: Error processing embed:', error); + this.renderError(container, `Error loading: ${error.message}`, addGap); + } + } + + renderError(container, message, addGap) { + const errorDiv = container.createDiv('sync-embed-error'); + if (addGap) errorDiv.addClass('sync-embed-gap'); + errorDiv.setText(message); + } +}; \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..d322e56 --- /dev/null +++ b/manifest.json @@ -0,0 +1,10 @@ +{ + "id": "sync-embeds", + "name": "Sync Embeds", + "version": "1.0.0", + "minAppVersion": "0.15.0", + "description": "Create fully editable, live-synced note embeds.", + "author": "uthvah", + "authorUrl": "https://github.com/uthvah", + "isDesktopOnly": false +} \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..75cc77e --- /dev/null +++ b/styles.css @@ -0,0 +1,84 @@ +/* Main container for the sync block */ +.sync-container { + border: 1px solid var(--background-modifier-border); + border-radius: 8px; + padding: 16px; + background: var(--background-primary); + margin: 12px 0; + display: flex; + flex-direction: column; + gap: 16px; +} + +/* Individual embed container */ +.sync-embed { + border: none !important; + margin: 0 !important; + padding: 0 !important; + background: transparent !important; + box-shadow: none !important; +} + +/* --- SEAMLESS STYLING FOR EMBEDDED EDITOR --- */ + +/* Hide the view's header (title bar, icons) completely */ +.sync-embed .view-header { + display: none !important; +} + +/* Make the main content area and editor background transparent */ +.sync-embed .view-content, +.sync-embed .markdown-source-view, +.sync-embed .cm-editor, +.sync-embed .cm-scroller { + background-color: transparent !important; + padding: 0 !important; + margin: 0 !important; +} + +/* Ensure the editor itself takes up the full width */ +.sync-embed .markdown-source-view.mod-cm6 .cm-editor { + width: 100%; +} + +/* Remove padding from the view content area */ +.sync-embed .view-content { + padding: 0 var(--file-margins) !important; +} + +/* Remove extra vertical padding from CodeMirror */ +.sync-embed .cm-s-obsidian .cm-gutters, +.sync-embed .cm-s-obsidian .cm-content { + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +/* Optional: remove line numbers */ +.sync-embed .cm-gutters { + display: none; +} +.sync-embed .cm-content { + padding-left: 0 !important; +} + +/* --- UTILITY & ERROR STYLING --- */ + +.sync-embed-gap { + padding-top: 16px; + border-top: 1px solid var(--background-modifier-border-hover); +} + +.sync-empty { + padding: 20px; + text-align: center; + color: var(--text-muted); + font-style: italic; +} + +.sync-embed-error { + padding: 8px 12px; + background: var(--background-modifier-error); + border: 1px solid var(--background-modifier-border); + border-radius: 4px; + color: var(--text-error); +} \ No newline at end of file