mirror of
https://github.com/uthvah/sync-embeds.git
synced 2026-07-22 06:43:40 +00:00
Fixes the destructive behavior where notes were overwritten if an embed pointed to a non-existent header. Huge thanks to @parkeraddison!
This commit is contained in:
parent
5b27cff2f2
commit
47e058331d
2 changed files with 115 additions and 106 deletions
|
|
@ -25,7 +25,7 @@ class EmbedManager {
|
|||
}
|
||||
});
|
||||
this.activeEmbeds.clear();
|
||||
|
||||
|
||||
// Clean up dynamic paths cache
|
||||
if (this.dynamicPaths) {
|
||||
this.dynamicPaths.cleanup();
|
||||
|
|
@ -48,7 +48,7 @@ class EmbedManager {
|
|||
async processSyncBlock(source, el, ctx) {
|
||||
el.empty();
|
||||
const syncContainer = el.createDiv('sync-container');
|
||||
|
||||
|
||||
// Apply CSS custom properties
|
||||
syncContainer.style.setProperty('--sync-embed-height', this.plugin.settings.embedHeight);
|
||||
syncContainer.style.setProperty('--sync-max-height', this.plugin.settings.maxEmbedHeight);
|
||||
|
|
@ -70,7 +70,7 @@ class EmbedManager {
|
|||
for (let i = 0; i < embedLines.length; i++) {
|
||||
await this.processEmbed(embedLines[i], syncContainer, ctx, i > 0);
|
||||
}
|
||||
|
||||
|
||||
// Remove min-height after all loaded
|
||||
setTimeout(() => {
|
||||
syncContainer.style.minHeight = '';
|
||||
|
|
@ -81,11 +81,11 @@ class EmbedManager {
|
|||
// Parse options like: ![[note|alias{height:500px,title:false}]]
|
||||
const optionsMatch = line.match(/\{([^}]+)\}\]\]$/);
|
||||
const options = {};
|
||||
|
||||
|
||||
if (optionsMatch) {
|
||||
const optionsStr = optionsMatch[1];
|
||||
const pairs = optionsStr.split(',');
|
||||
|
||||
|
||||
pairs.forEach(pair => {
|
||||
const [key, value] = pair.split(':').map(s => s.trim());
|
||||
if (key && value !== undefined) {
|
||||
|
|
@ -95,11 +95,11 @@ class EmbedManager {
|
|||
else options[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Remove options from line for further parsing
|
||||
line = line.replace(/\{[^}]+\}\]\]$/, ']]');
|
||||
}
|
||||
|
||||
|
||||
return { line, options };
|
||||
}
|
||||
|
||||
|
|
@ -107,23 +107,23 @@ class EmbedManager {
|
|||
try {
|
||||
// Parse custom options first
|
||||
const { line: cleanedLine, options } = this.parseEmbedOptions(embedLine);
|
||||
|
||||
|
||||
// Parse embed syntax: ![[path#section|alias]]
|
||||
const match = cleanedLine.match(/!\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/);
|
||||
if (!match) return;
|
||||
|
||||
let linkText = match[1];
|
||||
let displayAlias = match[2]?.trim();
|
||||
|
||||
|
||||
// Check if this has dynamic patterns
|
||||
const hasDynamicPattern = /\{\{(date|time|title)/.test(linkText);
|
||||
|
||||
|
||||
if (hasDynamicPattern) {
|
||||
// Check cache first (with 1 second TTL for date patterns)
|
||||
const cacheKey = `${linkText}-${ctx.sourcePath}`;
|
||||
const cached = this.dynamicPaths.pathCache.get(cacheKey);
|
||||
const now = Date.now();
|
||||
|
||||
|
||||
let resolvedText;
|
||||
if (cached && (now - cached.timestamp < 1000)) {
|
||||
resolvedText = cached.value;
|
||||
|
|
@ -134,21 +134,21 @@ class EmbedManager {
|
|||
// Resolve dynamic patterns
|
||||
resolvedText = this.dynamicPaths.resolve(linkText, ctx);
|
||||
this.dynamicPaths.pathCache.set(cacheKey, { value: resolvedText, timestamp: now });
|
||||
|
||||
|
||||
if (this.plugin.settings.debugMode) {
|
||||
console.log('[Sync Embeds] Fresh resolution:', linkText, '→', resolvedText);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If no alias provided, use the original pattern as display name
|
||||
if (!displayAlias) {
|
||||
displayAlias = linkText;
|
||||
}
|
||||
|
||||
|
||||
// CRITICAL: Use resolved text as the actual file path
|
||||
linkText = resolvedText;
|
||||
}
|
||||
|
||||
|
||||
const linkPath = linkText.split('|')[0].trim();
|
||||
let notePath = linkPath.split('#')[0];
|
||||
const section = linkPath.includes('#') ? linkPath.substring(linkPath.indexOf('#') + 1) : null;
|
||||
|
|
@ -160,7 +160,7 @@ class EmbedManager {
|
|||
this.renderError(container, `Note not found: ${notePath}`, addGap);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Only check for direct recursion
|
||||
if (file.path === ctx.sourcePath) {
|
||||
this.renderError(container, "Cannot create a recursive embed of the same note.", addGap);
|
||||
|
|
@ -233,12 +233,12 @@ class EmbedManager {
|
|||
async onunload() {
|
||||
// Remove from active embeds
|
||||
this.manager.activeEmbeds.delete(this.embedData);
|
||||
|
||||
|
||||
// Clear focus if this was focused
|
||||
if (this.manager.plugin.currentFocusedEmbed?.containerEl === this.embedData.containerEl) {
|
||||
this.manager.plugin.currentFocusedEmbed = null;
|
||||
}
|
||||
|
||||
|
||||
// Properly detach the leaf and clean up the view
|
||||
if (this.embedData.leaf) {
|
||||
this.embedData.leaf.detach();
|
||||
|
|
@ -270,15 +270,24 @@ class EmbedManager {
|
|||
if (customOptions.height) {
|
||||
embedContainer.style.setProperty('--sync-embed-height', customOptions.height);
|
||||
}
|
||||
|
||||
|
||||
if (customOptions.maxHeight) {
|
||||
embedContainer.style.setProperty('--sync-max-height', customOptions.maxHeight);
|
||||
}
|
||||
|
||||
// For section embeds, setup viewport restriction
|
||||
// For section embeds, validate section exists then set up
|
||||
if (section) {
|
||||
const content = view.editor.getValue();
|
||||
const sectionInfo = this.viewportController.findSectionBounds(content, section);
|
||||
|
||||
if (sectionInfo.startLine === -1) {
|
||||
this.renderError(embedContainer.parentElement, `Section not found: ${section}`, false);
|
||||
leaf.detach();
|
||||
return;
|
||||
}
|
||||
|
||||
await this.viewportController.setupSectionViewport(embedData);
|
||||
|
||||
|
||||
// If there's an alias, show it instead of the actual header
|
||||
if (alias) {
|
||||
this.setupAliasDisplay(embedData, alias, true);
|
||||
|
|
@ -294,10 +303,10 @@ class EmbedManager {
|
|||
}
|
||||
|
||||
// Handle inline title visibility
|
||||
const showTitle = customOptions.title !== undefined
|
||||
? customOptions.title
|
||||
const showTitle = customOptions.title !== undefined
|
||||
? customOptions.title
|
||||
: this.plugin.settings.showInlineTitle;
|
||||
|
||||
|
||||
if (!showTitle || section) {
|
||||
this.hideInlineTitle(embedData);
|
||||
}
|
||||
|
|
@ -306,7 +315,7 @@ class EmbedManager {
|
|||
placeholder.remove();
|
||||
embedContainer.appendChild(view.containerEl);
|
||||
embedContainer.removeClass('sync-embed-loading');
|
||||
|
||||
|
||||
ctx.addChild(component);
|
||||
|
||||
} catch (error) {
|
||||
|
|
@ -318,7 +327,7 @@ class EmbedManager {
|
|||
|
||||
setupAliasDisplay(embedData, displayAlias, isSection) {
|
||||
const { view } = embedData;
|
||||
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
setTimeout(() => {
|
||||
// Hide the inline title
|
||||
|
|
@ -344,7 +353,7 @@ class EmbedManager {
|
|||
// Create and insert alias header with consistent styling
|
||||
const aliasHeader = view.containerEl.createDiv('sync-embed-alias-header');
|
||||
aliasHeader.textContent = displayAlias;
|
||||
|
||||
|
||||
const viewContent = view.containerEl.querySelector('.view-content');
|
||||
if (viewContent) {
|
||||
viewContent.insertBefore(aliasHeader, viewContent.firstChild);
|
||||
|
|
@ -355,7 +364,7 @@ class EmbedManager {
|
|||
|
||||
setupPropertiesCollapse(embedData) {
|
||||
const { view } = embedData;
|
||||
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
setTimeout(() => {
|
||||
const propertiesEl = view.containerEl.querySelector('.metadata-container');
|
||||
|
|
@ -392,4 +401,4 @@ class EmbedManager {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = EmbedManager;
|
||||
module.exports = EmbedManager;
|
||||
|
|
|
|||
|
|
@ -7,35 +7,35 @@ class ViewportController {
|
|||
|
||||
async setupSectionViewport(embedData) {
|
||||
const { view, editor, file, section } = embedData;
|
||||
|
||||
|
||||
// Wait for editor to be ready
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
|
||||
const content = editor.getValue();
|
||||
const sectionInfo = this.findSectionBounds(content, section);
|
||||
|
||||
|
||||
// Missing section should be handled by the caller. Log to console for debugging if we reach this point.
|
||||
if (sectionInfo.startLine === -1) {
|
||||
// Section not found
|
||||
editor.setValue(`# ${section}\n\n*Section not found in file.*`);
|
||||
console.warn('Sync Embeds: Section not found for viewport embedding:', section);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Store section metadata
|
||||
embedData.sectionInfo = sectionInfo;
|
||||
embedData.viewportActive = true;
|
||||
|
||||
|
||||
// Apply the viewport restriction
|
||||
this.applyViewportRestriction(embedData);
|
||||
|
||||
|
||||
// Setup header protection (simplified, no auto-correction)
|
||||
this.setupHeaderProtection(embedData);
|
||||
|
||||
|
||||
// NEW: Setup input interception to block # typing
|
||||
this.setupHeaderInputInterception(embedData);
|
||||
|
||||
|
||||
// Setup cursor and content constraints
|
||||
this.setupContentConstraints(embedData);
|
||||
|
||||
|
||||
// Scroll to section
|
||||
this.scrollToSection(embedData);
|
||||
}
|
||||
|
|
@ -43,22 +43,22 @@ class ViewportController {
|
|||
applyViewportRestriction(embedData) {
|
||||
const { view, sectionInfo } = embedData;
|
||||
const { startLine, endLine } = sectionInfo;
|
||||
|
||||
|
||||
// Use CSS to hide lines outside the viewport
|
||||
const style = document.createElement('style');
|
||||
style.className = 'sync-viewport-style';
|
||||
|
||||
|
||||
// Generate unique ID for this embed
|
||||
const embedId = 'embed-' + Math.random().toString(36).substr(2, 9);
|
||||
view.containerEl.setAttribute('data-embed-id', embedId);
|
||||
|
||||
|
||||
// Store embed ID for dynamic updates
|
||||
embedData.embedId = embedId;
|
||||
|
||||
|
||||
this.updateViewportCSS(embedData, style);
|
||||
|
||||
|
||||
view.containerEl.appendChild(style);
|
||||
|
||||
|
||||
// Store for cleanup and updates
|
||||
embedData.viewportStyle = style;
|
||||
}
|
||||
|
|
@ -66,19 +66,19 @@ class ViewportController {
|
|||
updateViewportCSS(embedData, style) {
|
||||
const { sectionInfo, embedId } = embedData;
|
||||
const { startLine, endLine } = sectionInfo;
|
||||
|
||||
|
||||
// PERFORMANCE: Use efficient CSS selectors
|
||||
const css = `
|
||||
/* Hide all lines before the section */
|
||||
[data-embed-id="${embedId}"] .cm-line:nth-child(-n+${startLine}) {
|
||||
display: none !important;
|
||||
[data-embed-id="${embedId}"] .cm-line:nth-child(-n+${startLine}) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
/* Hide all lines after the section */
|
||||
[data-embed-id="${embedId}"] .cm-line:nth-child(n+${endLine + 1}) {
|
||||
display: none !important;
|
||||
[data-embed-id="${embedId}"] .cm-line:nth-child(n+${endLine + 1}) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
/* Style the header line - No background, proper theming */
|
||||
[data-embed-id="${embedId}"] .cm-line:nth-child(${startLine + 1}) {
|
||||
pointer-events: none !important;
|
||||
|
|
@ -86,27 +86,27 @@ class ViewportController {
|
|||
cursor: default !important;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
style.textContent = css;
|
||||
}
|
||||
|
||||
setupHeaderProtection(embedData) {
|
||||
const { view, editor, sectionInfo, component } = embedData;
|
||||
const { startLine } = sectionInfo;
|
||||
|
||||
|
||||
// Store original header to restore if modified
|
||||
const originalHeader = editor.getLine(startLine);
|
||||
embedData.originalHeader = originalHeader;
|
||||
|
||||
|
||||
let isProgrammaticUpdate = false;
|
||||
|
||||
|
||||
const changeHandler = (changedEditor) => {
|
||||
if (changedEditor !== editor || isProgrammaticUpdate) return;
|
||||
if (!embedData.viewportActive) return;
|
||||
|
||||
|
||||
const cursorPos = editor.getCursor();
|
||||
const absoluteLine = cursorPos.line;
|
||||
|
||||
|
||||
// Protect the header line from any edits
|
||||
if (absoluteLine === startLine) {
|
||||
const currentLine = editor.getLine(startLine);
|
||||
|
|
@ -123,7 +123,7 @@ class ViewportController {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
component.registerEvent(
|
||||
this.plugin.app.workspace.on('editor-change', changeHandler)
|
||||
);
|
||||
|
|
@ -134,11 +134,11 @@ class ViewportController {
|
|||
const { view, editor, component } = embedData;
|
||||
// The header level of the main section won't change, so it's safe to get it once.
|
||||
const { headerLevel } = embedData.sectionInfo;
|
||||
|
||||
|
||||
// Always enforce in section embeds
|
||||
let lastNoticeTime = 0;
|
||||
const noticeDebounce = 5000; // 5 seconds between notices
|
||||
|
||||
|
||||
// CRITICAL: Wait for the editor to be fully ready and focused
|
||||
// Use a small delay to ensure the CodeMirror editor is properly initialized
|
||||
const setupHandlers = () => {
|
||||
|
|
@ -149,26 +149,26 @@ class ViewportController {
|
|||
setTimeout(setupHandlers, 50);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Handle input event to catch # at line start (use 'input' not 'beforeinput' for better timing)
|
||||
const inputHandler = (event) => {
|
||||
// Only process insertText events
|
||||
if (event.inputType !== 'insertText' && event.inputType !== 'insertFromPaste') return;
|
||||
if (event.data !== '#') return;
|
||||
|
||||
|
||||
// Check immediately if # was typed at line start
|
||||
const cursor = editor.getCursor();
|
||||
const line = editor.getLine(cursor.line);
|
||||
|
||||
|
||||
// Check if the # is at the start of the line (accounting for whitespace)
|
||||
const beforeHash = line.substring(0, cursor.ch - 1);
|
||||
const isAtLineStart = /^\s*$/.test(beforeHash);
|
||||
|
||||
|
||||
// CHANGED: Read directly from `embedData.sectionInfo` to get the latest, non-stale bounds.
|
||||
if (isAtLineStart &&
|
||||
cursor.line > embedData.sectionInfo.startLine &&
|
||||
if (isAtLineStart &&
|
||||
cursor.line > embedData.sectionInfo.startLine &&
|
||||
cursor.line < embedData.sectionInfo.endLine) {
|
||||
|
||||
|
||||
// Remove the # that was just typed
|
||||
const currentLine = editor.getLine(cursor.line);
|
||||
const newLine = currentLine.substring(0, cursor.ch - 1) + currentLine.substring(cursor.ch);
|
||||
|
|
@ -178,19 +178,19 @@ class ViewportController {
|
|||
{ line: cursor.line, ch: currentLine.length }
|
||||
);
|
||||
editor.setCursor({ line: cursor.line, ch: cursor.ch - 1 });
|
||||
|
||||
|
||||
// Show notice (debounced)
|
||||
const now = Date.now();
|
||||
if (this.plugin.settings.showHeaderHints &&
|
||||
if (this.plugin.settings.showHeaderHints &&
|
||||
now - lastNoticeTime > noticeDebounce) {
|
||||
|
||||
|
||||
lastNoticeTime = now;
|
||||
|
||||
|
||||
const availableLevels = [];
|
||||
for (let i = headerLevel + 1; i <= 6; i++) {
|
||||
availableLevels.push(`H${i} (Alt+${i})`);
|
||||
}
|
||||
|
||||
|
||||
new Notice(
|
||||
`⚠️ Cannot create H1-H${headerLevel} headers in this section.\n` +
|
||||
`Use: ${availableLevels.join(', ')}`,
|
||||
|
|
@ -199,29 +199,29 @@ class ViewportController {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Also handle paste events
|
||||
const pasteHandler = (event) => {
|
||||
const clipboardData = event.clipboardData?.getData('text');
|
||||
if (!clipboardData) return;
|
||||
|
||||
|
||||
const cursor = editor.getCursor();
|
||||
|
||||
|
||||
// CHANGED: Read directly from `embedData.sectionInfo` to get the latest, non-stale bounds.
|
||||
if (cursor.line > embedData.sectionInfo.startLine &&
|
||||
if (cursor.line > embedData.sectionInfo.startLine &&
|
||||
cursor.line < embedData.sectionInfo.endLine) {
|
||||
|
||||
|
||||
const lines = clipboardData.split('\n');
|
||||
let hasInvalidHeaders = false;
|
||||
|
||||
|
||||
// Check if pasted content has invalid headers
|
||||
const adjustedLines = lines.map(line => {
|
||||
const match = line.match(/^(#{1,6})\s+(.*)$/);
|
||||
if (!match) return line;
|
||||
|
||||
|
||||
const [, hashes, content] = match;
|
||||
const level = hashes.length;
|
||||
|
||||
|
||||
if (level <= headerLevel) {
|
||||
hasInvalidHeaders = true;
|
||||
// Adjust to minimum allowed level
|
||||
|
|
@ -230,71 +230,71 @@ class ViewportController {
|
|||
}
|
||||
return line;
|
||||
});
|
||||
|
||||
|
||||
if (hasInvalidHeaders) {
|
||||
event.preventDefault();
|
||||
|
||||
|
||||
if (this.plugin.settings.showHeaderHints) {
|
||||
new Notice(
|
||||
'Pasted headers adjusted to maintain section hierarchy',
|
||||
4000
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Insert adjusted content
|
||||
editor.replaceSelection(adjustedLines.join('\n'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Use 'input' event which fires AFTER the character is inserted
|
||||
cmEditor.addEventListener('input', inputHandler, true);
|
||||
cmEditor.addEventListener('paste', pasteHandler, true);
|
||||
|
||||
|
||||
// Cleanup
|
||||
component.register(() => {
|
||||
cmEditor.removeEventListener('input', inputHandler, true);
|
||||
cmEditor.removeEventListener('paste', pasteHandler, true);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Start setup with a small delay to ensure editor is ready
|
||||
setTimeout(setupHandlers, 100);
|
||||
}
|
||||
|
||||
setupContentConstraints(embedData) {
|
||||
const { view, editor, sectionInfo, 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;
|
||||
|
||||
|
||||
// Update CSS synchronously to prevent flashing
|
||||
if (embedData.viewportStyle) {
|
||||
this.updateViewportCSS(embedData, embedData.viewportStyle);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Monitor for content changes with instant updates
|
||||
const constrainContent = () => {
|
||||
if (isProgrammaticUpdate || !embedData.viewportActive) return;
|
||||
|
||||
|
||||
// Update viewport bounds IMMEDIATELY
|
||||
updateViewportImmediately();
|
||||
|
||||
|
||||
// Constrain cursor to section bounds
|
||||
const cursor = editor.getCursor();
|
||||
const currentSectionInfo = embedData.sectionInfo;
|
||||
|
||||
|
||||
if (cursor.line < currentSectionInfo.startLine) {
|
||||
isProgrammaticUpdate = true;
|
||||
editor.setCursor({ line: currentSectionInfo.startLine + 1, ch: 0 });
|
||||
|
|
@ -307,7 +307,7 @@ class ViewportController {
|
|||
isProgrammaticUpdate = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// CHANGED: Removed `requestAnimationFrame`. Updates must be synchronous to prevent a race condition
|
||||
// where the `input` event for typing a hash fires before the section bounds are updated.
|
||||
component.registerEvent(
|
||||
|
|
@ -317,18 +317,18 @@ class ViewportController {
|
|||
}
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
// Prevent scrolling outside section
|
||||
const cmScroller = view.containerEl.querySelector('.cm-scroller');
|
||||
if (cmScroller) {
|
||||
const preventScroll = (e) => {
|
||||
if (!embedData.viewportActive) return;
|
||||
|
||||
|
||||
const scrollTop = cmScroller.scrollTop;
|
||||
const lineHeight = editor.defaultTextHeight || 20;
|
||||
const firstVisibleLine = Math.floor(scrollTop / lineHeight);
|
||||
const currentSectionInfo = embedData.sectionInfo;
|
||||
|
||||
|
||||
// Constrain scrolling to section
|
||||
if (firstVisibleLine < Math.max(0, currentSectionInfo.startLine - 2)) {
|
||||
cmScroller.scrollTop = Math.max(0, currentSectionInfo.startLine - 2) * lineHeight;
|
||||
|
|
@ -336,7 +336,7 @@ class ViewportController {
|
|||
cmScroller.scrollTop = (currentSectionInfo.endLine - 2) * lineHeight;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
cmScroller.addEventListener('scroll', preventScroll);
|
||||
component.register(() => {
|
||||
cmScroller.removeEventListener('scroll', preventScroll);
|
||||
|
|
@ -347,11 +347,11 @@ class ViewportController {
|
|||
scrollToSection(embedData) {
|
||||
const { view, editor, sectionInfo } = embedData;
|
||||
const { startLine } = sectionInfo;
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
// Scroll to the start of the section
|
||||
editor.scrollIntoView({ line: startLine, ch: 0 }, true);
|
||||
|
||||
|
||||
// Set cursor at line after header
|
||||
editor.setCursor({ line: startLine + 1, ch: 0 });
|
||||
}, 150);
|
||||
|
|
@ -361,7 +361,7 @@ class ViewportController {
|
|||
const lines = content.split('\n');
|
||||
const escapedName = this.escapeRegExp(sectionName);
|
||||
const headerRegex = new RegExp(`^#{1,6}\\s+${escapedName}\\s*$`);
|
||||
|
||||
|
||||
let startLine = -1;
|
||||
let headerLevel = 0;
|
||||
|
||||
|
|
@ -403,4 +403,4 @@ class ViewportController {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = ViewportController;
|
||||
module.exports = ViewportController;
|
||||
|
|
|
|||
Loading…
Reference in a new issue