Skip to content

Support line editing in live preview #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 71 additions & 1 deletion src/CodeBlock.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { type MarkdownPostProcessorContext, MarkdownRenderChild } from 'obsidian';
import { type MarkdownPostProcessorContext, MarkdownRenderChild, MarkdownView, Notice } from 'obsidian';
import type ShikiPlugin from 'src/main';

// css class name of obsidian edit block button in live preview
const EDIT_BLOCK_CLASSNAME = '.edit-block-button';

export class CodeBlock extends MarkdownRenderChild {
plugin: ShikiPlugin;
source: string;
language: string;
ctx: MarkdownPostProcessorContext;
cachedMetaString: string;
lineStart?: number;

constructor(plugin: ShikiPlugin, containerEl: HTMLElement, source: string, language: string, ctx: MarkdownPostProcessorContext) {
super(containerEl);
Expand All @@ -25,6 +29,7 @@ export class CodeBlock extends MarkdownRenderChild {
return '';
}

this.lineStart = sectionInfo.lineStart;
const lines = sectionInfo.text.split('\n');
const startLine = lines[sectionInfo.lineStart];

Expand All @@ -40,6 +45,8 @@ export class CodeBlock extends MarkdownRenderChild {

private async render(metaString: string): Promise<void> {
await this.plugin.highlighter.renderWithEc(this.source, this.language, metaString, this.containerEl);
this.addRowEditButtons();
this.hideCopyButtons();
}

public async rerenderOnNoteChange(): Promise<void> {
Expand All @@ -58,6 +65,69 @@ export class CodeBlock extends MarkdownRenderChild {
await this.render(this.cachedMetaString);
}

/**
* In live preview, add a row edit button
* to each line of code block to improve editability,
* except lines in collapsible section summary.
*/
private addRowEditButtons(): void {
if (!this.plugin.settings.rowEditButtons)
return;

const lineStart = this.lineStart;
const editBlock = this.containerEl.parentElement?.find(EDIT_BLOCK_CLASSNAME);
const view = this.plugin.app.workspace.getActiveViewOfType(MarkdownView);

if (lineStart && editBlock && view?.getMode() === 'source') {
const lines = this.containerEl.getElementsByClassName('ec-line');
for (let i = 0, lineNo = 0; i < lines.length; i++) {
// ignore lines in collapsible section summary
if (lines[i].parentElement?.tagName === 'SUMMARY')
continue;
const editBtn = lines[i].createEl("div", { cls: "ec-edit-btn", attr: { "data-line": lineNo+1 } });
editBtn.addEventListener("click", (e: Event) => {
const lineNo = (e.currentTarget as HTMLElement).getAttribute("data-line");
if (!lineNo) return;

// a workaround to break the non-editable state
// of embed code block by clicking this 'edit block' button
editBlock.click();

// select the row in editor
let _i = lineStart + parseInt(lineNo);
let _from = { line: _i, ch: 0 };
let _to = { line: _i, ch: view.editor.getLine(_i).length };
view.editor.setSelection(_from, _to);
});
lineNo++;
}
// Hide native buttons to avoid blocking row edit buttons
if (this.plugin.settings.hideNativeBlockEdit) {
editBlock.style.display = 'none';
}
}
}

/**
* Hide copy buttons for shiki code blocks.
* Now right-click on a code block to copy the entire code.
*/
private hideCopyButtons(): void {
if (!this.plugin.settings.hideNativeCopy)
return;
const copyBtn = this.containerEl.find('.copy>button');
if (copyBtn) {
copyBtn.style.display = 'none';
this.containerEl.addEventListener('contextmenu', () => {
// only copy entire code when there is no selection.
// if there is selection, obsidian will show a "Ctrl+C" context menu.
if (!window.getSelection()?.toString()) {
copyBtn.click();
}
});
}
}

public onload(): void {
super.onload();

Expand Down
6 changes: 6 additions & 0 deletions src/settings/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export interface Settings {
theme: string;
preferThemeColors: boolean;
inlineHighlighting: boolean;
rowEditButtons: boolean;
hideNativeBlockEdit: boolean;
hideNativeCopy: boolean;
}

export const DEFAULT_SETTINGS: Settings = {
Expand All @@ -14,4 +17,7 @@ export const DEFAULT_SETTINGS: Settings = {
theme: 'obsidian-theme',
preferThemeColors: true,
inlineHighlighting: true,
rowEditButtons: false,
hideNativeBlockEdit: false,
hideNativeCopy: false
};
35 changes: 35 additions & 0 deletions src/settings/SettingsTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,41 @@ export class ShikiSettingsTab extends PluginSettingTab {
await this.plugin.saveSettings();
});
});

new Setting(this.containerEl).setHeading().setName('Button Settings').setDesc('Configure code block button settings. Changes will apply to NEWLY RENDERED CODE.');

new Setting(this.containerEl)
.setName('Show Row Edit Buttons in Live Preivew')
.setDesc('Whether to add a row edit button to each line of code block to improve editability.')
.addToggle(toggle => {
toggle.setValue(this.plugin.settings.rowEditButtons).onChange(async value => {
hideNativeBlockEdit.setDisabled(!value);
this.plugin.settings.rowEditButtons = value;
await this.plugin.saveSettings();
});
});

const hideNativeBlockEdit = new Setting(this.containerEl)
.setName('Hide Native Block Edit Buttons')
.setDesc('When row edit buttons are enabled, whether to hide native block edit buttons to avoid blocking.')
.setClass('shiki-foldable-setting')
.setDisabled(!this.plugin.settings.rowEditButtons)
.addToggle(toggle => {
toggle.setValue(this.plugin.settings.hideNativeBlockEdit).onChange(async value => {
this.plugin.settings.hideNativeBlockEdit = value;
await this.plugin.saveSettings();
});
});

new Setting(this.containerEl)
.setName('Hide Native Copy Buttons')
.setDesc('Whether to hide copy buttons for shiki code blocks. When enabled, right-click on a code block to copy the entire code.')
.addToggle(toggle => {
toggle.setValue(this.plugin.settings.hideNativeCopy).onChange(async value => {
this.plugin.settings.hideNativeCopy = value;
await this.plugin.saveSettings();
});
});

new Setting(this.containerEl).setHeading().setName('Language Settings').setDesc('Configure language settings. RESTART REQUIRED AFTER CHANGES.');

Expand Down
23 changes: 23 additions & 0 deletions styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,30 @@ span.shiki-ul {
text-decoration: underline;
}

/* Row edit buttons in live preview mode */
.ec-line:hover .ec-edit-btn {
cursor: pointer;
position: fixed;
right: 0;
&::before {
content: var(--ec-edit-btn-content, "✏️");
padding-inline: var(--ec-edit-btn-padding, 3px 5px);
background: var(--tmLineBgCol, var(--shiki-code-background));
}
}

/* Settings tab */
.setting-item-control input.shiki-custom-theme-folder {
min-width: 250px;
}

.shiki-foldable-setting {
transition: opacity 1s ease-out;

&.is-disabled {
padding: 0;
opacity: 0;
height: 0;
overflow: hidden;
}
}