mirror of
https://github.com/uthvah/sync-embeds.git
synced 2026-07-22 16:30:22 +00:00
Merge branch 'uthvah:main' into sync-callouts
This commit is contained in:
commit
aa72dca846
4 changed files with 170 additions and 20 deletions
|
|
@ -1,4 +1,4 @@
|
|||
const { Component, WorkspaceLeaf, MarkdownView } = require('obsidian');
|
||||
const { Component, WorkspaceLeaf, MarkdownView, setIcon } = require('obsidian');
|
||||
const ViewportController = require('./viewport-controller');
|
||||
const DynamicPaths = require('./dynamic-paths');
|
||||
|
||||
|
|
@ -230,6 +230,12 @@ class EmbedManager {
|
|||
const placeholder = embedContainer.createDiv('sync-embed-placeholder');
|
||||
placeholder.setText(`Loading ${placeholderText}...`);
|
||||
|
||||
const renderAsCallout = options.callout !== undefined ? options.callout : this.plugin.settings.renderAsCallout;
|
||||
|
||||
if (renderAsCallout) {
|
||||
embedContainer.addClass('is-callout-style');
|
||||
}
|
||||
|
||||
// Aggressive lazy loading to prevent scrollbar jumps
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
|
|
@ -268,7 +274,8 @@ class EmbedManager {
|
|||
alias,
|
||||
component,
|
||||
leaf,
|
||||
customOptions
|
||||
customOptions,
|
||||
sourcePath: ctx.sourcePath
|
||||
};
|
||||
|
||||
// Register cleanup
|
||||
|
|
@ -323,6 +330,16 @@ class EmbedManager {
|
|||
embedContainer.style.setProperty('--sync-max-height', customOptions.maxHeight);
|
||||
}
|
||||
|
||||
// Handle collapse option
|
||||
if (customOptions.collapse === true) {
|
||||
embedContainer.addClass('is-collapsed');
|
||||
}
|
||||
|
||||
const renderAsCallout = customOptions.callout !== undefined ? customOptions.callout : this.plugin.settings.renderAsCallout;
|
||||
|
||||
// Construct the display title: prefer alias, then "Note > Section", then just Note
|
||||
const headerTitle = alias || (section ? `${file.basename} > ${section}` : file.basename);
|
||||
|
||||
// For section embeds, validate section exists then set up
|
||||
if (section) {
|
||||
const content = view.editor.getValue();
|
||||
|
|
@ -345,12 +362,12 @@ class EmbedManager {
|
|||
await this.viewportController.setupSectionViewport(embedData);
|
||||
|
||||
// If there's an alias, show it instead of the actual header
|
||||
if (alias) {
|
||||
this.setupAliasDisplay(embedData, alias, true);
|
||||
if (alias || renderAsCallout) {
|
||||
this.setupAliasDisplay(embedData, headerTitle, true);
|
||||
}
|
||||
} else if (alias) {
|
||||
// For whole note embeds with alias, show alias as title
|
||||
this.setupAliasDisplay(embedData, alias, false);
|
||||
} else if (alias || renderAsCallout) {
|
||||
// For whole note embeds with alias or callout style, show title
|
||||
this.setupAliasDisplay(embedData, headerTitle, false);
|
||||
}
|
||||
|
||||
// Handle properties collapse
|
||||
|
|
@ -363,13 +380,17 @@ class EmbedManager {
|
|||
? customOptions.title
|
||||
: this.plugin.settings.showInlineTitle;
|
||||
|
||||
if (!showTitle || section) {
|
||||
// Hide title if disabled, if it's a section, OR if we're rendering as a callout (since callout has its own title)
|
||||
if (!showTitle || section || renderAsCallout) {
|
||||
this.hideInlineTitle(embedData);
|
||||
}
|
||||
|
||||
// Remove placeholder and show actual content
|
||||
placeholder.remove();
|
||||
embedContainer.appendChild(view.containerEl);
|
||||
// placeholder.remove();
|
||||
// embedContainer.appendChild(view.containerEl);
|
||||
// Using replaceWith instead of remove + appendChild
|
||||
placeholder.replaceWith(view.containerEl);
|
||||
|
||||
embedContainer.removeClass('sync-embed-loading');
|
||||
|
||||
ctx.addChild(component);
|
||||
|
|
@ -382,7 +403,10 @@ class EmbedManager {
|
|||
}
|
||||
|
||||
setupAliasDisplay(embedData, displayAlias, isSection) {
|
||||
const { view } = embedData;
|
||||
const { view, file, section } = embedData;
|
||||
const renderAsCallout = embedData.customOptions.callout !== undefined
|
||||
? embedData.customOptions.callout
|
||||
: this.plugin.settings.renderAsCallout;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
setTimeout(() => {
|
||||
|
|
@ -393,22 +417,48 @@ class EmbedManager {
|
|||
}
|
||||
|
||||
// For section embeds, also hide the section header
|
||||
if (isSection) {
|
||||
if (isSection && embedData.sectionInfo) {
|
||||
const cmContent = view.containerEl.querySelector('.cm-content');
|
||||
if (cmContent) {
|
||||
if (embedData.sectionInfo) {
|
||||
const headerLineNumber = embedData.sectionInfo.startLine + 1;
|
||||
const firstLine = cmContent.querySelector(`.cm-line:nth-child(${headerLineNumber})`);
|
||||
if (firstLine) {
|
||||
firstLine.style.display = 'none';
|
||||
}
|
||||
const headerLineNumber = embedData.sectionInfo.startLine + 1;
|
||||
const firstLine = cmContent.querySelector(`.cm-line:nth-child(${headerLineNumber})`);
|
||||
if (firstLine) {
|
||||
firstLine.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create and insert alias header with consistent styling
|
||||
const aliasHeader = view.containerEl.createDiv('sync-embed-alias-header');
|
||||
aliasHeader.textContent = displayAlias;
|
||||
|
||||
if (renderAsCallout) {
|
||||
aliasHeader.addClass('is-sticky');
|
||||
|
||||
// Add fold button
|
||||
const foldBtn = aliasHeader.createDiv('sync-embed-fold');
|
||||
setIcon(foldBtn, 'chevron-down');
|
||||
|
||||
// Create a clickable link
|
||||
const linkPath = section ? `${file.path}#${section}` : file.path;
|
||||
aliasHeader.createEl('a', {
|
||||
cls: 'internal-link',
|
||||
text: displayAlias,
|
||||
attr: {
|
||||
'href': linkPath,
|
||||
'data-href': linkPath
|
||||
}
|
||||
});
|
||||
|
||||
// Handle toggle (only if clicking the header background, not the link)
|
||||
aliasHeader.addEventListener('click', (e) => {
|
||||
if (e.target.closest('a')) return;
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
embedData.containerEl.classList.toggle('is-collapsed');
|
||||
});
|
||||
} else {
|
||||
aliasHeader.textContent = displayAlias;
|
||||
}
|
||||
|
||||
const viewContent = view.containerEl.querySelector('.view-content');
|
||||
if (viewContent) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ const DEFAULT_SETTINGS = {
|
|||
maxEmbedHeight: 'none',
|
||||
collapsePropertiesByDefault: true,
|
||||
showInlineTitle: true,
|
||||
renderAsCallout: false,
|
||||
enableCommandInterception: true,
|
||||
gapBetweenEmbeds: '16px',
|
||||
lazyLoadThreshold: '100px',
|
||||
|
|
|
|||
|
|
@ -158,6 +158,16 @@ class SyncEmbedsSettingTab extends PluginSettingTab {
|
|||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Render as callout')
|
||||
.setDesc('Render embeds as callouts with sticky headers and collapse functionality')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.renderAsCallout)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.renderAsCallout = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
|
||||
// === HEADER MANAGEMENT SECTION ===
|
||||
containerEl.createEl('h3', { text: 'Header Management' });
|
||||
|
||||
|
|
@ -231,6 +241,8 @@ class SyncEmbedsSettingTab extends PluginSettingTab {
|
|||
<li><code>![[Note|Alias{height:500px}]]</code> - Custom height for this embed</li>
|
||||
<li><code>![[Note|Alias{maxHeight:600px}]]</code> - Custom max height</li>
|
||||
<li><code>![[Note|Alias{title:false}]]</code> - Hide title for this embed</li>
|
||||
<li><code>![[Note|Alias{collapse:true}]]</code> - Start embed in collapsed state (requires callout style)</li>
|
||||
<li><code>![[Note|Alias{callout:true}]]</code> - Force callout style for this embed</li>
|
||||
<li><code>![[Note|Alias{height:400px,title:false}]]</code> - Multiple options</li>
|
||||
</ul>
|
||||
<p><em>Note: Options go inside curly braces before the closing ]]</em></p>
|
||||
|
|
@ -277,6 +289,7 @@ class SyncEmbedsSettingTab extends PluginSettingTab {
|
|||
<ul>
|
||||
<li>Use aliases (text after <code>|</code>) with dynamic patterns for better display</li>
|
||||
<li>Per-embed options override global settings: <code>{height:400px,title:false}</code></li>
|
||||
<li>Use <code>collapse:true</code> to hide embed content by default (requires callout style)</li>
|
||||
<li>Section embeds are fully editable and changes sync immediately</li>
|
||||
<li>Press Tab/Shift+Tab to navigate between embeds</li>
|
||||
<li>Keyboard shortcuts work inside embeds when command interception is enabled</li>
|
||||
|
|
|
|||
88
styles.css
88
styles.css
|
|
@ -20,7 +20,89 @@
|
|||
height: var(--sync-embed-height, auto);
|
||||
max-height: var(--sync-max-height, none);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
/* border: 1px solid var(--background-modifier-border) !important; */
|
||||
}
|
||||
|
||||
/* === STICKY ALIAS HEADER (CALLOUT STYLE) === */
|
||||
.sync-embed .markdown-source-view,
|
||||
.sync-embed .cm-editor,
|
||||
.sync-embed .cm-scroller,
|
||||
.sync-embed .view-content,
|
||||
.sync-embed .workspace-leaf-content {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.sync-embed-alias-header.is-sticky {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
background-color: var(--background-primary) !important;
|
||||
padding: 8px 40px 8px 10px !important;
|
||||
font-size: var(--font-text-size);
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 8px !important;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
||||
cursor: pointer;
|
||||
user-select: none !important;
|
||||
/* border-bottom: 1px solid var(--background-modifier-border) !important; */
|
||||
}
|
||||
|
||||
.sync-embed-alias-header.is-sticky:hover {
|
||||
background-color: var(--background-primary-alt) !important;
|
||||
}
|
||||
|
||||
.sync-embed-alias-header.is-sticky a.internal-link {
|
||||
text-decoration: none;
|
||||
color: var(--text-accent) !important;
|
||||
font-weight: 600 !important;
|
||||
/* Truncation */
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.sync-embed-alias-header.is-sticky a.internal-link:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--text-accent-hover) !important;
|
||||
}
|
||||
|
||||
/* === FOLD ICON === */
|
||||
.sync-embed-fold {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.sync-embed-fold svg {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* === COLLAPSED STATE === */
|
||||
.sync-embed.is-collapsed {
|
||||
height: auto !important;
|
||||
min-height: 0 !important;
|
||||
max-height: none !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* Hide everything except header when collapsed */
|
||||
.sync-embed.is-collapsed .view-content > *:not(.sync-embed-alias-header) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Remove bottom border of header when collapsed */
|
||||
.sync-embed.is-collapsed .sync-embed-alias-header.is-sticky {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
/* Rotate fold icon when collapsed */
|
||||
.sync-embed.is-collapsed .sync-embed-fold {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
/* Gap between embeds */
|
||||
|
|
@ -122,7 +204,6 @@
|
|||
padding-right: 10px !important;
|
||||
margin-bottom: 0;
|
||||
|
||||
pointer-events: none !important;
|
||||
user-select: none !important;
|
||||
cursor: default !important;
|
||||
background: transparent !important;
|
||||
|
|
@ -131,6 +212,11 @@
|
|||
outline: none !important;
|
||||
}
|
||||
|
||||
.sync-embed-alias-header a.internal-link {
|
||||
color: inherit !important;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Theme inheritance */
|
||||
.theme-dark .sync-embed-alias-header,
|
||||
.theme-light .sync-embed-alias-header {
|
||||
|
|
|
|||
Loading…
Reference in a new issue