fixed a lot of issues

This commit is contained in:
Bradley Wyatt 2025-02-06 09:18:16 -06:00
parent 4a1655afd0
commit c44de1fac2
8 changed files with 1040 additions and 92 deletions

327
main copy.ts Normal file
View file

@ -0,0 +1,327 @@
import {
Plugin,
MarkdownView,
PluginSettingTab,
App,
Setting,
ButtonComponent
} from 'obsidian';
interface CollapsibleCodeBlockSettings {
defaultCollapsed: boolean;
collapseIcon: string;
expandIcon: string;
enableHorizontalScroll: boolean;
collapsedLines: number;
}
const DEFAULT_SETTINGS: CollapsibleCodeBlockSettings = {
defaultCollapsed: false,
collapseIcon: '▼',
expandIcon: '▶',
enableHorizontalScroll: true,
collapsedLines: 0
};
export default class CollapsibleCodeBlockPlugin extends Plugin {
private contentObserver: MutationObserver;
public settings: CollapsibleCodeBlockSettings;
async onload() {
await this.loadSettings();
this.updateScrollSetting();
// Single content observer for the entire preview
this.contentObserver = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
this.processNewCodeBlocks(mutation.target as HTMLElement);
}
});
});
this.registerMarkdownPostProcessor((element) => {
this.processNewCodeBlocks(element);
this.contentObserver.observe(element, { childList: true, subtree: true });
});
this.addSettingTab(new CollapsibleCodeBlockSettingsTab(this.app, this));
}
private processNewCodeBlocks(element: HTMLElement) {
element.querySelectorAll('pre:not(.has-collapse-button)').forEach(pre => {
if (!(pre instanceof HTMLElement)) return;
pre.classList.add('has-collapse-button');
this.setupCodeBlock(pre);
});
}
private setupCodeBlock(pre: HTMLElement) {
document.documentElement.style.setProperty('--collapsed-lines', this.settings.collapsedLines.toString());
const toggleButton = this.createToggleButton();
pre.insertBefore(toggleButton, pre.firstChild);
if (this.settings.defaultCollapsed) {
pre.classList.add('collapsed');
toggleButton.textContent = this.settings.expandIcon;
this.updateCodeBlockVisibility(pre, true);
}
}
private createToggleButton(): HTMLElement {
const button = document.createElement('div');
button.className = 'code-block-toggle';
button.textContent = this.settings.collapseIcon;
button.setAttribute('role', 'button');
button.setAttribute('tabindex', '0');
button.setAttribute('aria-label', 'Toggle code block visibility');
const toggleHandler = (e: Event) => {
e.preventDefault();
const pre = (e.target as HTMLElement).closest('pre');
if (!pre) return;
pre.classList.toggle('collapsed');
this.updateCodeBlockVisibility(pre, true);
const isCollapsed = pre.classList.contains('collapsed');
button.textContent = isCollapsed ? this.settings.expandIcon : this.settings.collapseIcon;
button.setAttribute('aria-expanded', (!isCollapsed).toString());
this.app.workspace.requestSaveLayout();
};
button.addEventListener('click', toggleHandler);
button.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggleHandler(e);
}
});
return button;
}
private updateCodeBlockVisibility(pre: HTMLElement, forceRefresh: boolean = false) {
const isCollapsed = pre.classList.contains('collapsed');
// Update following elements' visibility and positioning
let elements: HTMLElement[] = [];
let curr = pre.nextElementSibling;
while (curr && !(curr instanceof HTMLPreElement)) {
if (curr instanceof HTMLElement) {
elements.push(curr);
if (!curr.dataset.originalDisplay) {
curr.dataset.originalDisplay = getComputedStyle(curr).display;
}
}
curr = curr.nextElementSibling;
}
// Only perform the layout refresh if forceRefresh is true
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (markdownView?.previewMode?.containerEl && forceRefresh) {
const previewElement = markdownView.previewMode.containerEl;
// Immediately update visibility classes
elements.forEach(el => {
if (isCollapsed) {
el.classList.add('element-hidden');
el.classList.remove('element-visible', 'element-spacing');
} else {
el.classList.remove('element-hidden');
el.classList.add('element-visible');
const preRect = pre.getBoundingClientRect();
const elRect = el.getBoundingClientRect();
if (elRect.top < preRect.bottom) {
document.documentElement.style.setProperty('--element-spacing', `${preRect.bottom - elRect.top + 10}px`);
el.classList.add('element-spacing');
}
}
});
// Force immediate viewport recalculation
void previewElement.offsetHeight;
// Get current scroll position and element position
const currentScroll = previewElement.scrollTop;
const preRect = pre.getBoundingClientRect();
const isAtTop = preRect.top <= 100; // Check if code block is near the top
// Optimized scroll sequence
const scrollSequence = async () => {
// If at top, scroll down first then back up
if (isAtTop) {
previewElement.scrollTop = Math.min(500, previewElement.scrollHeight / 2);
await new Promise(resolve => requestAnimationFrame(resolve));
previewElement.scrollTop = 0;
await new Promise(resolve => requestAnimationFrame(resolve));
}
// Quick scroll through content
const scrollPoints = [0, previewElement.scrollHeight / 2, currentScroll];
for (const scrollPos of scrollPoints) {
previewElement.scrollTop = scrollPos;
previewElement.dispatchEvent(new Event('scroll', { bubbles: true }));
await new Promise(resolve => requestAnimationFrame(resolve));
}
// Final layout adjustments
window.dispatchEvent(new Event('resize'));
previewElement.dispatchEvent(new Event('scroll', { bubbles: true }));
};
// Execute scroll sequence
scrollSequence();
} else {
// If not forcing refresh, just update the visibility classes
elements.forEach(el => {
if (isCollapsed) {
el.classList.add('element-hidden');
el.classList.remove('element-visible', 'element-spacing');
} else {
el.classList.remove('element-hidden');
el.classList.add('element-visible');
}
});
}
}
updateScrollSetting(): void {
document.body.classList.toggle('horizontal-scroll', this.settings.enableHorizontalScroll);
}
private sanitizeIcon(icon: string): string {
const cleaned = icon.trim();
return cleaned.length <= 2 ? cleaned : DEFAULT_SETTINGS.collapseIcon;
}
async loadSettings() {
const loadedData = await this.loadData();
this.settings = {
...DEFAULT_SETTINGS,
...loadedData,
// Sanitize icons when loading
collapseIcon: this.sanitizeIcon(loadedData?.collapseIcon ?? DEFAULT_SETTINGS.collapseIcon),
expandIcon: this.sanitizeIcon(loadedData?.expandIcon ?? DEFAULT_SETTINGS.expandIcon)
};
}
async saveSettings() {
// Sanitize before saving
this.settings.collapseIcon = this.sanitizeIcon(this.settings.collapseIcon);
this.settings.expandIcon = this.sanitizeIcon(this.settings.expandIcon);
await this.saveData(this.settings);
}
onunload() {
this.contentObserver?.disconnect();
}
}
class CollapsibleCodeBlockSettingsTab extends PluginSettingTab {
plugin: CollapsibleCodeBlockPlugin;
constructor(app: App, plugin: CollapsibleCodeBlockPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl('h2', { text: 'Collapsible Code Block Settings' });
new Setting(containerEl)
.setName('Default Collapsed State')
.setDesc('Should code blocks be collapsed by default?')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.defaultCollapsed)
.onChange(async (value) => {
this.plugin.settings.defaultCollapsed = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Collapse Icon')
.setDesc('Icon to show when code block is expanded (single character or emoji only)')
.addText(text => text
.setValue(this.plugin.settings.collapseIcon)
.onChange(async (value) => {
const sanitized = value.trim();
if (sanitized.length <= 2) {
this.plugin.settings.collapseIcon = sanitized || DEFAULT_SETTINGS.collapseIcon;
await this.plugin.saveSettings();
}
}));
new Setting(containerEl)
.setName('Expand Icon')
.setDesc('Icon to show when code block is collapsed (single character or emoji only)')
.addText(text => text
.setValue(this.plugin.settings.expandIcon)
.onChange(async (value) => {
const sanitized = value.trim();
if (sanitized.length <= 2) {
this.plugin.settings.expandIcon = sanitized || DEFAULT_SETTINGS.expandIcon;
await this.plugin.saveSettings();
}
}));
new Setting(containerEl)
.setName('Enable Horizontal Scrolling')
.setDesc('Allow code blocks to scroll horizontally instead of wrapping text.')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.enableHorizontalScroll)
.onChange(async (value) => {
this.plugin.settings.enableHorizontalScroll = value;
await this.plugin.saveSettings();
this.plugin.updateScrollSetting();
}));
const collapsedLinesSetting = new Setting(containerEl)
.setName('Collapsed Lines')
.setDesc('Number of lines visible when code block is collapsed');
let reloadButton: ButtonComponent | null = null;
collapsedLinesSetting.addButton(btn => {
reloadButton = btn
.setButtonText('Apply changes (reload plugin)')
.setCta();
reloadButton.buttonEl.classList.add('hidden');
reloadButton.onClick(async () => {
const pluginId = this.plugin.manifest.id;
// @ts-ignore
await this.app.plugins.disablePlugin(pluginId);
// @ts-ignore
await this.app.plugins.enablePlugin(pluginId);
// Re-open the settings
// @ts-ignore
this.app.setting.openTabById(pluginId);
});
});
collapsedLinesSetting.addText(text => {
text
.setValue(this.plugin.settings.collapsedLines.toString())
.onChange(async (value) => {
const numericValue = parseInt(value, 10);
this.plugin.settings.collapsedLines = isNaN(numericValue) || numericValue < 0 ? 0 : numericValue;
await this.plugin.saveSettings();
if (reloadButton) {
reloadButton.buttonEl.classList.remove('hidden');
}
});
});
}
}

310
main.ts
View file

@ -4,8 +4,13 @@ import {
PluginSettingTab,
App,
Setting,
ButtonComponent
ButtonComponent,
Editor,
EditorPosition
} from 'obsidian';
import { StateField, StateEffect, Extension, EditorState, Transaction } from '@codemirror/state';
import { EditorView, Decoration, DecorationSet, WidgetType } from '@codemirror/view';
import { syntaxTree } from '@codemirror/language';
interface CollapsibleCodeBlockSettings {
defaultCollapsed: boolean;
@ -15,6 +20,11 @@ interface CollapsibleCodeBlockSettings {
collapsedLines: number;
}
interface CodeBlockPosition {
startPos: number;
endPos: number;
}
const DEFAULT_SETTINGS: CollapsibleCodeBlockSettings = {
defaultCollapsed: false,
collapseIcon: '▼',
@ -23,6 +33,189 @@ const DEFAULT_SETTINGS: CollapsibleCodeBlockSettings = {
collapsedLines: 0
};
const toggleFoldEffect = StateEffect.define<{from: number, to: number}>();
function configureFoldField(settings: CollapsibleCodeBlockSettings) {
return StateField.define<DecorationSet>({
create() {
return Decoration.none
},
update(folds: DecorationSet, tr: Transaction) {
folds = folds.map(tr.changes)
for (let effect of tr.effects) {
if (effect.is(toggleFoldEffect)) {
const { from, to } = effect.value
let hasFold = false
folds.between(from, to, () => { hasFold = true })
if (hasFold) {
folds = folds.update({
filter: (from, to) => from !== effect.value.from || to !== effect.value.to
})
} else {
const deco = Decoration.replace({
block: true,
inclusive: true,
widget: new class extends WidgetType {
toDOM(view: EditorView) {
const container = document.createElement('div')
container.className = 'code-block-folded'
container.style.setProperty('--collapsed-lines', settings.collapsedLines.toString())
const contentDiv = document.createElement('div')
contentDiv.className = 'folded-content'
const lines = view.state.doc.sliceString(from, to).split('\n')
.slice(0, settings.collapsedLines)
.join('\n')
contentDiv.textContent = lines
const button = document.createElement('div')
button.className = 'code-block-toggle'
button.textContent = settings.expandIcon
button.onclick = (e) => {
e.preventDefault()
view.dispatch({
effects: toggleFoldEffect.of({from, to})
})
}
container.appendChild(button)
container.appendChild(contentDiv)
return container
}
}
})
folds = folds.update({
add: [deco.range(from, to)]
})
}
}
}
return folds
},
provide: f => EditorView.decorations.from(f)
})
}
const foldField = configureFoldField(DEFAULT_SETTINGS);
class FoldWidget extends WidgetType {
constructor(readonly startPos: number, readonly endPos: number) {
super();
}
toDOM(view: EditorView) {
const button = document.createElement('div');
button.className = 'code-block-toggle';
let isFolded = false;
try {
const field = view.state.field(foldField as StateField<DecorationSet>);
field.between(this.startPos, this.endPos, () => {
isFolded = true;
});
} catch (e) {
console.log("Field not found:", e);
}
button.innerHTML = isFolded ? '▶' : '▼';
button.setAttribute('aria-label', isFolded ? 'Expand code block' : 'Collapse code block');
button.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
view.dispatch({
effects: toggleFoldEffect.of({
from: this.startPos,
to: this.endPos
})
});
};
return button;
}
eq(other: FoldWidget) {
return other.startPos === this.startPos && other.endPos === this.endPos;
}
}
const codeBlockPositions = StateField.define<CodeBlockPosition[]>({
create(state: EditorState): CodeBlockPosition[] {
return findCodeBlockPositions(state);
},
update(value: CodeBlockPosition[], tr) {
return findCodeBlockPositions(tr.state);
}
});
function buildDecorations(state: EditorState): DecorationSet {
const widgets: any[] = [];
const positions = state.field(codeBlockPositions);
positions.forEach(pos => {
const widget = Decoration.widget({
widget: new FoldWidget(pos.startPos, pos.endPos),
side: -1
});
widgets.push(widget.range(pos.startPos));
});
return Decoration.set(widgets, true);
}
function findCodeBlockPositions(state: EditorState): CodeBlockPosition[] {
const positions: CodeBlockPosition[] = [];
syntaxTree(state).iterate({
enter: (node) => {
const nodeName = node.type.name;
if (nodeName.includes("HyperMD-codeblock-begin")) {
const line = state.doc.lineAt(node.from);
if (line.text.trim().startsWith('```')) {
let endFound = false;
for (let i = line.number; i <= state.doc.lines; i++) {
const currentLine = state.doc.line(i);
if (i !== line.number && currentLine.text.trim().startsWith('```')) {
positions.push({
startPos: line.from,
endPos: currentLine.to
});
endFound = true;
break;
}
}
if (!endFound) {
positions.push({
startPos: line.from,
endPos: state.doc.line(state.doc.lines).to
});
}
}
}
}
});
return positions;
}
const decorations = StateField.define<DecorationSet>({
create(state: EditorState): DecorationSet {
return buildDecorations(state);
},
update(value: DecorationSet, transaction): DecorationSet {
return buildDecorations(transaction.state);
},
provide(field: StateField<DecorationSet>): Extension {
return EditorView.decorations.from(field);
}
});
export default class CollapsibleCodeBlockPlugin extends Plugin {
private contentObserver: MutationObserver;
public settings: CollapsibleCodeBlockSettings;
@ -31,7 +224,12 @@ export default class CollapsibleCodeBlockPlugin extends Plugin {
await this.loadSettings();
this.updateScrollSetting();
// Single content observer for the entire preview
this.registerEditorExtension([
codeBlockPositions,
decorations,
configureFoldField(this.settings)
]);
this.contentObserver = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
@ -104,92 +302,48 @@ export default class CollapsibleCodeBlockPlugin extends Plugin {
}
private updateCodeBlockVisibility(pre: HTMLElement, forceRefresh: boolean = false) {
const isCollapsed = pre.classList.contains('collapsed');
// Update following elements' visibility and positioning
let elements: HTMLElement[] = [];
let curr = pre.nextElementSibling;
while (curr && !(curr instanceof HTMLPreElement)) {
if (curr instanceof HTMLElement) {
elements.push(curr);
if (!curr.dataset.originalDisplay) {
curr.dataset.originalDisplay = getComputedStyle(curr).display;
}
}
curr = curr.nextElementSibling;
}
const isCollapsed = pre.classList.contains('collapsed');
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!markdownView?.previewMode?.containerEl) return;
// Only perform the layout refresh if forceRefresh is true
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (markdownView?.previewMode?.containerEl && forceRefresh) {
const previewElement = markdownView.previewMode.containerEl;
const rect = pre.getBoundingClientRect();
const scrollTop = previewElement.scrollTop;
const elementTop = rect.top + scrollTop;
// Immediately update visibility classes
elements.forEach(el => {
if (isCollapsed) {
el.classList.add('element-hidden');
el.classList.remove('element-visible', 'element-spacing');
} else {
el.classList.remove('element-hidden');
el.classList.add('element-visible');
const preRect = pre.getBoundingClientRect();
const elRect = el.getBoundingClientRect();
if (elRect.top < preRect.bottom) {
document.documentElement.style.setProperty('--element-spacing', `${preRect.bottom - elRect.top + 10}px`);
el.classList.add('element-spacing');
let curr = pre.nextElementSibling;
while (curr && !(curr instanceof HTMLPreElement)) {
if (curr instanceof HTMLElement) {
if (isCollapsed) {
curr.classList.add('element-hidden');
curr.classList.remove('element-visible', 'element-spacing');
} else {
curr.classList.remove('element-hidden');
curr.classList.add('element-visible');
}
}
});
curr = curr.nextElementSibling;
}
// Force immediate viewport recalculation
void previewElement.offsetHeight;
void pre.offsetHeight;
// Get current scroll position and element position
const currentScroll = previewElement.scrollTop;
const preRect = pre.getBoundingClientRect();
const isAtTop = preRect.top <= 100; // Check if code block is near the top
// Optimized scroll sequence
const scrollSequence = async () => {
// If at top, scroll down first then back up
if (isAtTop) {
previewElement.scrollTop = Math.min(500, previewElement.scrollHeight / 2);
await new Promise(resolve => requestAnimationFrame(resolve));
previewElement.scrollTop = 0;
await new Promise(resolve => requestAnimationFrame(resolve));
}
// Quick scroll through content
const scrollPoints = [0, previewElement.scrollHeight / 2, currentScroll];
for (const scrollPos of scrollPoints) {
previewElement.scrollTop = scrollPos;
previewElement.dispatchEvent(new Event('scroll', { bubbles: true }));
await new Promise(resolve => requestAnimationFrame(resolve));
}
// Final layout adjustments
const triggerReflow = async () => {
window.dispatchEvent(new Event('resize'));
await new Promise(resolve => requestAnimationFrame(resolve));
const originalScroll = previewElement.scrollTop;
previewElement.scrollTop = Math.max(0, previewElement.scrollHeight - previewElement.clientHeight);
await new Promise(resolve => requestAnimationFrame(resolve));
previewElement.scrollTop = originalScroll;
window.dispatchEvent(new Event('resize'));
await new Promise(resolve => setTimeout(resolve, 50));
window.dispatchEvent(new Event('resize'));
previewElement.dispatchEvent(new Event('scroll', { bubbles: true }));
};
// Execute scroll sequence
scrollSequence();
} else {
// If not forcing refresh, just update the visibility classes
elements.forEach(el => {
if (isCollapsed) {
el.classList.add('element-hidden');
el.classList.remove('element-visible', 'element-spacing');
} else {
el.classList.remove('element-hidden');
el.classList.add('element-visible');
}
});
triggerReflow();
}
}
updateScrollSetting(): void {
document.body.classList.toggle('horizontal-scroll', this.settings.enableHorizontalScroll);
@ -205,14 +359,12 @@ export default class CollapsibleCodeBlockPlugin extends Plugin {
this.settings = {
...DEFAULT_SETTINGS,
...loadedData,
// Sanitize icons when loading
collapseIcon: this.sanitizeIcon(loadedData?.collapseIcon ?? DEFAULT_SETTINGS.collapseIcon),
expandIcon: this.sanitizeIcon(loadedData?.expandIcon ?? DEFAULT_SETTINGS.expandIcon)
};
}
async saveSettings() {
// Sanitize before saving
this.settings.collapseIcon = this.sanitizeIcon(this.settings.collapseIcon);
this.settings.expandIcon = this.sanitizeIcon(this.settings.expandIcon);
await this.saveData(this.settings);

252
old/main.js.old.js Normal file

File diff suppressed because one or more lines are too long

14
old/manifest.json Normal file
View file

@ -0,0 +1,14 @@
{
"id": "collapsible-code-blocks",
"name": "Collapsible Code Blocks",
"version": "1.0.0",
"minAppVersion": "0.15.0",
"description": "Makes code blocks collapsible in preview mode as well as enabling scroll-able code blocks.",
"author": "Bradley Wyatt",
"authorUrl": "https://github.com/bwya77",
"fundingUrl": {
"Buy Me a Coffee": "buymeacoffee.com/bwya77",
"GitHub Sponsor": "https://github.com/sponsors/bwya77"
},
"isDesktopOnly": true
}

6
old/package-lock.json generated Normal file
View file

@ -0,0 +1,6 @@
{
"name": "collapsible-codeblock",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

85
old/styles.css Normal file
View file

@ -0,0 +1,85 @@
/* styles.css */
.markdown-preview-view pre {
position: relative;
padding-top: 24px;
user-select: text;
transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden;
}
.markdown-preview-view pre code {
display: block;
line-height: 1.5em;
transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
overflow-x: auto;
}
.markdown-preview-view pre.collapsed code {
max-height: calc(var(--collapsed-lines, 1) * 1.5em);
overflow: hidden;
overflow-x: hidden;
}
.code-block-toggle {
position: absolute;
top: 0;
left: 0;
padding: 2px 8px;
cursor: pointer;
color: var(--text-muted);
z-index: 1;
user-select: none;
background: transparent;
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
transition: all 0.15s ease;
outline: none;
}
.code-block-toggle:hover {
color: var(--text-normal);
opacity: 1;
}
.code-block-toggle:focus-visible {
outline: 2px solid var(--text-accent);
outline-offset: 2px;
}
.code-block-toggle::selection {
background: transparent;
}
body.horizontal-scroll .markdown-preview-view pre:not(.collapsed) code {
white-space: pre;
}
.markdown-preview-view pre code::-webkit-scrollbar:vertical {
width: 0;
}
/* New styles for dynamic states */
.hidden {
display: none !important;
}
.element-hidden {
display: none !important;
}
.element-visible {
display: block;
}
.element-spacing {
margin-top: var(--element-spacing);
}
/* Custom properties for dynamic values */
:root {
--element-spacing: 0px;
--collapsed-lines: 1;
}

85
styles copy.css Normal file
View file

@ -0,0 +1,85 @@
/* styles.css */
.markdown-preview-view pre {
position: relative;
padding-top: 24px;
user-select: text;
transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden;
}
.markdown-preview-view pre code {
display: block;
line-height: 1.5em;
transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
overflow-x: auto;
}
.markdown-preview-view pre.collapsed code {
max-height: calc(var(--collapsed-lines, 1) * 1.5em);
overflow: hidden;
overflow-x: hidden;
}
.code-block-toggle {
position: absolute;
top: 0;
left: 0;
padding: 2px 8px;
cursor: pointer;
color: var(--text-muted);
z-index: 1;
user-select: none;
background: transparent;
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
transition: all 0.15s ease;
outline: none;
}
.code-block-toggle:hover {
color: var(--text-normal);
opacity: 1;
}
.code-block-toggle:focus-visible {
outline: 2px solid var(--text-accent);
outline-offset: 2px;
}
.code-block-toggle::selection {
background: transparent;
}
body.horizontal-scroll .markdown-preview-view pre:not(.collapsed) code {
white-space: pre;
}
.markdown-preview-view pre code::-webkit-scrollbar:vertical {
width: 0;
}
/* New styles for dynamic states */
.hidden {
display: none !important;
}
.element-hidden {
display: none !important;
}
.element-visible {
display: block;
}
.element-spacing {
margin-top: var(--element-spacing);
}
/* Custom properties for dynamic values */
:root {
--element-spacing: 0px;
--collapsed-lines: 1;
}

View file

@ -22,26 +22,25 @@
.code-block-toggle {
position: absolute;
top: 0;
left: 0;
padding: 2px 8px;
cursor: pointer;
color: var(--text-muted);
z-index: 1;
user-select: none;
background: transparent;
left: 4px;
top: 4px;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
transition: all 0.15s ease;
outline: none;
color: var(--text-muted);
cursor: pointer;
z-index: 2;
background: transparent;
border-radius: 4px;
user-select: none;
font-size: 16px;
}
.code-block-toggle:hover {
color: var(--text-normal);
opacity: 1;
background: var(--background-modifier-hover);
}
.code-block-toggle:focus-visible {
@ -82,4 +81,32 @@ body.horizontal-scroll .markdown-preview-view pre:not(.collapsed) code {
:root {
--element-spacing: 0px;
--collapsed-lines: 1;
}
.code-block-folded {
min-height: 3em;
padding: 1.5em 1em 1em 48px;
background: var(--code-background);
border-radius: 4px;
color: var(--text-muted);
font-family: var(--font-monospace);
font-size: var(--code-size);
line-height: var(--line-height-tight);
position: relative;
}
.HyperMD-codeblock {
position: relative;
padding-left: 32px;
}
.code-block-folded .folded-content {
max-height: calc(var(--collapsed-lines, 1) * 1.5em);
overflow: hidden;
white-space: pre;
}
.code-block-folded .code-block-toggle {
top: 4px;
left: 4px;
}