mirror of
https://github.com/rmellmer/obsidian-link-range.git
synced 2026-07-22 07:40:31 +00:00
add new live preview renderer and supporting settings values
This commit is contained in:
parent
ddb9ca65d2
commit
a6ef3a830c
5 changed files with 218 additions and 40 deletions
5
main.ts
5
main.ts
|
|
@ -5,6 +5,8 @@ import { DEFAULT_SETTINGS, LinkRangeSettings, LinkRangeSettingTab } from 'src/se
|
|||
import { linkRangePostProcessor } from 'src/markdownPostProcessor';
|
||||
import { checkLink } from 'src/utils';
|
||||
import { LinkRangeView } from 'src/linkRangeView';
|
||||
import { buildCMViewPlugin } from 'src/livePreviewDisplayView';
|
||||
import { Prec } from "@codemirror/state";
|
||||
|
||||
export default class LinkRange extends Plugin {
|
||||
settings: LinkRangeSettings;
|
||||
|
|
@ -28,6 +30,9 @@ export default class LinkRange extends Plugin {
|
|||
return new LinkRangeView(this.settings, this.app)
|
||||
}));
|
||||
|
||||
const ext = Prec.lowest(buildCMViewPlugin(this.app, this.settings));
|
||||
this.registerEditorExtension(ext);
|
||||
|
||||
const pagePreviewPlugin = this.app.internalPlugins.plugins["page-preview"];
|
||||
|
||||
console.log("LinkRange: Hooking into page-preview onHover calls")
|
||||
|
|
|
|||
78
package-lock.json
generated
78
package-lock.json
generated
|
|
@ -7,6 +7,7 @@
|
|||
"": {
|
||||
"name": "obsidian-sample-plugin",
|
||||
"version": "1.0.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/codemirror": "^5.60.7",
|
||||
|
|
@ -14,6 +15,7 @@
|
|||
"monkey-around": "^2.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@codemirror/language": "6.10.1",
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
|
|
@ -54,13 +56,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@codemirror/language": {
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.6.0.tgz",
|
||||
"integrity": "sha512-cwUd6lzt3MfNYOobdjf14ZkLbJcnv4WtndYaoBkbor/vF+rCNguMPK0IRtvZJG4dsWiaWPcK8x1VijhvSxnstg==",
|
||||
"version": "6.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.1.tgz",
|
||||
"integrity": "sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ==",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@codemirror/view": "^6.23.0",
|
||||
"@lezer/common": "^1.1.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0",
|
||||
"style-mod": "^4.0.0"
|
||||
|
|
@ -87,17 +89,17 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@codemirror/state": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz",
|
||||
"integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA=="
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.0.tgz",
|
||||
"integrity": "sha512-hm8XshYj5Fo30Bb922QX9hXB/bxOAVH+qaqHBzw5TKa72vOeslyGwd4X8M0c1dJ9JqxlaMceOQ8RsL9tC7gU0A=="
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.9.1.tgz",
|
||||
"integrity": "sha512-bzfSjJn9dAADVpabLKWKNmMG4ibyTV2e3eOGowjElNPTdTkSbi6ixPYHm2u0ADcETfKsi2/R84Rkmi91dH9yEg==",
|
||||
"version": "6.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.24.0.tgz",
|
||||
"integrity": "sha512-zK6m5pNkdhdJl8idPP1gA4N8JKTiSsOz8U/Iw+C1ChMwyLG7+MLiNXnH/wFuAk6KeGEe33/adOiAh5jMqee03w==",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.1.4",
|
||||
"style-mod": "^4.0.0",
|
||||
"@codemirror/state": "^6.4.0",
|
||||
"style-mod": "^4.1.0",
|
||||
"w3c-keyname": "^2.2.4"
|
||||
}
|
||||
},
|
||||
|
|
@ -524,9 +526,9 @@
|
|||
"peer": true
|
||||
},
|
||||
"node_modules/@lezer/common": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.2.tgz",
|
||||
"integrity": "sha512-SVgiGtMnMnW3ActR8SXgsDhw7a0w0ChHSYAyAUxxrOiJ1OqYWEKk/xJd84tTSPo1mo6DXLObAJALNnd0Hrv7Ng=="
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz",
|
||||
"integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ=="
|
||||
},
|
||||
"node_modules/@lezer/highlight": {
|
||||
"version": "1.1.3",
|
||||
|
|
@ -2374,9 +2376,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/style-mod": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz",
|
||||
"integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw=="
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz",
|
||||
"integrity": "sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA=="
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
|
|
@ -2596,13 +2598,13 @@
|
|||
}
|
||||
},
|
||||
"@codemirror/language": {
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.6.0.tgz",
|
||||
"integrity": "sha512-cwUd6lzt3MfNYOobdjf14ZkLbJcnv4WtndYaoBkbor/vF+rCNguMPK0IRtvZJG4dsWiaWPcK8x1VijhvSxnstg==",
|
||||
"version": "6.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.1.tgz",
|
||||
"integrity": "sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ==",
|
||||
"requires": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@lezer/common": "^1.0.0",
|
||||
"@codemirror/view": "^6.23.0",
|
||||
"@lezer/common": "^1.1.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0",
|
||||
"style-mod": "^4.0.0"
|
||||
|
|
@ -2629,17 +2631,17 @@
|
|||
}
|
||||
},
|
||||
"@codemirror/state": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz",
|
||||
"integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA=="
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.0.tgz",
|
||||
"integrity": "sha512-hm8XshYj5Fo30Bb922QX9hXB/bxOAVH+qaqHBzw5TKa72vOeslyGwd4X8M0c1dJ9JqxlaMceOQ8RsL9tC7gU0A=="
|
||||
},
|
||||
"@codemirror/view": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.9.1.tgz",
|
||||
"integrity": "sha512-bzfSjJn9dAADVpabLKWKNmMG4ibyTV2e3eOGowjElNPTdTkSbi6ixPYHm2u0ADcETfKsi2/R84Rkmi91dH9yEg==",
|
||||
"version": "6.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.24.0.tgz",
|
||||
"integrity": "sha512-zK6m5pNkdhdJl8idPP1gA4N8JKTiSsOz8U/Iw+C1ChMwyLG7+MLiNXnH/wFuAk6KeGEe33/adOiAh5jMqee03w==",
|
||||
"requires": {
|
||||
"@codemirror/state": "^6.1.4",
|
||||
"style-mod": "^4.0.0",
|
||||
"@codemirror/state": "^6.4.0",
|
||||
"style-mod": "^4.1.0",
|
||||
"w3c-keyname": "^2.2.4"
|
||||
}
|
||||
},
|
||||
|
|
@ -2849,9 +2851,9 @@
|
|||
"peer": true
|
||||
},
|
||||
"@lezer/common": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.2.tgz",
|
||||
"integrity": "sha512-SVgiGtMnMnW3ActR8SXgsDhw7a0w0ChHSYAyAUxxrOiJ1OqYWEKk/xJd84tTSPo1mo6DXLObAJALNnd0Hrv7Ng=="
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz",
|
||||
"integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ=="
|
||||
},
|
||||
"@lezer/highlight": {
|
||||
"version": "1.1.3",
|
||||
|
|
@ -4200,9 +4202,9 @@
|
|||
"peer": true
|
||||
},
|
||||
"style-mod": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz",
|
||||
"integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw=="
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz",
|
||||
"integrity": "sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA=="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@
|
|||
"obsidian": "latest",
|
||||
"patch-package": "^6.5.1",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
"typescript": "4.7.4",
|
||||
"@codemirror/language": "6.10.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/codemirror": "^5.60.7",
|
||||
|
|
|
|||
142
src/livePreviewDisplayView.ts
Normal file
142
src/livePreviewDisplayView.ts
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
import { App } from 'obsidian';
|
||||
import { Decoration, DecorationSet, EditorView, ViewPlugin, ViewUpdate, WidgetType } from "@codemirror/view";
|
||||
import { RangeSetBuilder } from "@codemirror/state";
|
||||
import { syntaxTree } from "@codemirror/language";
|
||||
import { LinkRangeSettings } from "./settings";
|
||||
|
||||
class CharacterOverwriteWidget extends WidgetType {
|
||||
|
||||
private char: string;
|
||||
constructor(char: string) {
|
||||
super();
|
||||
this.char = char;
|
||||
}
|
||||
|
||||
toDOM() {
|
||||
let el = document.createElement("span");
|
||||
el.innerText = this.char;
|
||||
el.style.textDecoration = 'underline';
|
||||
return el;
|
||||
}
|
||||
}
|
||||
|
||||
export function buildCMViewPlugin(app: App, settings: LinkRangeSettings) {
|
||||
|
||||
const viewPlugin = ViewPlugin.fromClass(
|
||||
class {
|
||||
decorations: DecorationSet;
|
||||
decoratedRanges: Array<{ from: number, to: number }>;
|
||||
lastLocation: { from: number; to: number; };
|
||||
|
||||
constructor(view: EditorView) {
|
||||
this.decorations = this.buildDecorations(view, null);
|
||||
this.lastLocation = { from: 0, to: 0 };;
|
||||
}
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
let currentLocation = {
|
||||
from: update.state.selection.ranges[0].from,
|
||||
to: update.state.selection.ranges[0].to
|
||||
};
|
||||
if (update.docChanged || update.viewportChanged) {
|
||||
this.decorations = this.buildDecorations(update.view, currentLocation);
|
||||
}
|
||||
else {
|
||||
// isRepeatUpdate - avoid repeated updates and renders
|
||||
let isRepeatUpdate = this.lastLocation.from == currentLocation.from && this.lastLocation.to === currentLocation.to;
|
||||
if (update.state.selection.ranges.length > 0 && !isRepeatUpdate) {
|
||||
this.decorations = this.buildDecorations(update.view, currentLocation);
|
||||
}
|
||||
}
|
||||
this.lastLocation = currentLocation;
|
||||
}
|
||||
|
||||
buildDecorations(view: EditorView, location: { from: number, to: number } | null): DecorationSet {
|
||||
let builder = new RangeSetBuilder<Decoration>();
|
||||
|
||||
const lastPassDecoratedRanges: Array<{from: number, to: number}> = this.decoratedRanges;
|
||||
this.decoratedRanges = [];
|
||||
|
||||
const inLastPass = function(nodeStart: number, index: number | undefined): boolean {
|
||||
if (index === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i in lastPassDecoratedRanges) {
|
||||
const rng = lastPassDecoratedRanges[i];
|
||||
|
||||
if (rng.from == nodeStart && index >= rng.from && index <= rng.to) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let {from, to} of view.visibleRanges) {
|
||||
|
||||
syntaxTree(view.state).iterate({
|
||||
from,
|
||||
to,
|
||||
enter: (node) => {
|
||||
// Big thanks to Supercharged Links. Used your code as an example! https://github.com/mdelobelle/obsidian_supercharged_links
|
||||
|
||||
// TODO: there used to be a different way than splitting the name but the api for this has
|
||||
// changed in CodeMirror. Should look into it more.
|
||||
const tokenProps = node.type.name.split('_');
|
||||
if (tokenProps) {
|
||||
const props = new Set(tokenProps);
|
||||
const isLink = props.has("hmd-internal-link");
|
||||
const isAlias = props.has("link-alias");
|
||||
const isPipe = props.has("link-alias-pipe");
|
||||
|
||||
const isMDUrl = props.has('url');
|
||||
|
||||
if (isLink && !isAlias && !isPipe || isMDUrl) {
|
||||
let linkText = view.state.doc.sliceString(node.from, node.to);
|
||||
|
||||
const indexOfHeaderMarker = linkText.indexOf('#') + node.from;
|
||||
if (indexOfHeaderMarker >= node.from && indexOfHeaderMarker <= node.to) {
|
||||
|
||||
if (!inLastPass(node.from - 2, location?.from)) {
|
||||
|
||||
let attributeDeco = Decoration.mark({
|
||||
class: 'heading-range-live-link'
|
||||
});
|
||||
builder.add(node.from - 2, node.to + 2, attributeDeco);
|
||||
|
||||
let overrideP2HWidget = Decoration.widget({
|
||||
widget: new CharacterOverwriteWidget(settings.headingVisual),
|
||||
});
|
||||
builder.add(indexOfHeaderMarker, indexOfHeaderMarker + 1, overrideP2HWidget);
|
||||
|
||||
const indexOfRangeMarker = linkText.indexOf(settings.headingSeparator) + node.from;
|
||||
if (indexOfRangeMarker >= node.from && indexOfRangeMarker <= node.to) {
|
||||
let overrideH2HWidget = Decoration.widget({
|
||||
widget: new CharacterOverwriteWidget(settings.headingSeparatorVisual),
|
||||
});
|
||||
builder.add(indexOfRangeMarker, indexOfRangeMarker + settings.headingSeparator.length, overrideH2HWidget);
|
||||
}
|
||||
}
|
||||
|
||||
this.decoratedRanges.push({
|
||||
from: node.from - 2,
|
||||
to: node.to + 2
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const bufferedDecs = builder.finish();
|
||||
return bufferedDecs;
|
||||
}
|
||||
},
|
||||
{
|
||||
decorations: v => v.decorations
|
||||
}
|
||||
);
|
||||
|
||||
return viewPlugin;
|
||||
}
|
||||
|
|
@ -6,12 +6,16 @@ export interface LinkRangeSettings {
|
|||
headingSeparator: string;
|
||||
altFormat: string;
|
||||
endInclusive: boolean;
|
||||
headingVisual: string,
|
||||
headingSeparatorVisual: string,
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: LinkRangeSettings = {
|
||||
headingSeparator: '..',
|
||||
altFormat: '$note:$h1-$h2',
|
||||
endInclusive: true
|
||||
endInclusive: true,
|
||||
headingVisual: ':',
|
||||
headingSeparatorVisual: '-',
|
||||
}
|
||||
|
||||
export class LinkRangeSettingTab extends PluginSettingTab {
|
||||
|
|
@ -53,6 +57,30 @@ export class LinkRangeSettingTab extends PluginSettingTab {
|
|||
postProcessorUpdate(this.app)
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Heading visual override for live preview')
|
||||
.setDesc('Defines the override for the heading (#) character for live preview mode.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter an override')
|
||||
.setValue(this.plugin.settings.headingVisual)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.headingVisual = value;
|
||||
await this.plugin.saveSettings();
|
||||
postProcessorUpdate(this.app)
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Heading separator visual override for live preview')
|
||||
.setDesc('Defines the override for the heading (>, or whatever is defined) character for live preview mode.')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter an override')
|
||||
.setValue(this.plugin.settings.headingSeparatorVisual)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.headingSeparatorVisual = value;
|
||||
await this.plugin.saveSettings();
|
||||
postProcessorUpdate(this.app)
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('End Inclusive')
|
||||
.setDesc('Whether or not the end heading should be inclusive or exclusive')
|
||||
|
|
|
|||
Loading…
Reference in a new issue