Skip to content

Commit ef082a3

Browse files
committed
fix: add different implementation for 1.11.0
re #26
1 parent aedd824 commit ef082a3

File tree

2 files changed

+248
-1
lines changed

2 files changed

+248
-1
lines changed

src/Plugin.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
Menu,
2626
MenuItem,
2727
parseYaml,
28+
requireApiVersion,
2829
TFile,
2930
WorkspaceLeaf
3031
} from 'obsidian';
@@ -61,7 +62,6 @@ import { getLinkData } from './LinkData.ts';
6162
import { patchMultiTextPropertyWidgetComponent } from './MultiTextPropertyWidgetComponent.ts';
6263
import { PluginSettingsManager } from './PluginSettingsManager.ts';
6364
import { PluginSettingsTab } from './PluginSettingsTab.ts';
64-
import { patchTextPropertyWidgetComponent } from './TextPropertyWidgetComponent.ts';
6565
import { isSourceMode } from './Utils.ts';
6666

6767
type BasesNoteGetFn = BasesNote['get'];
@@ -120,6 +120,10 @@ export class Plugin extends PluginBase<PluginTypes> {
120120
protected override async onloadImpl(): Promise<void> {
121121
await super.onloadImpl();
122122

123+
const { patchTextPropertyWidgetComponent } = requireApiVersion('1.11.0')
124+
? await import('./TextPropertyWidgetComponent.1.11.0.ts')
125+
: await import('./TextPropertyWidgetComponent.ts');
126+
123127
patchTextPropertyWidgetComponent(this);
124128
patchMultiTextPropertyWidgetComponent(this);
125129
registerFrontmatterLinksEditorExtension(this);
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
import type { SearchResult } from 'obsidian';
2+
import type { ParseLinkResult } from 'obsidian-dev-utils/obsidian/Link';
3+
import type {
4+
MetadataTypeManagerRegisteredTypeWidgetsRecord,
5+
PropertyRenderContext,
6+
TextPropertyWidgetComponent
7+
} from 'obsidian-typings';
8+
9+
import { AbstractInputSuggest } from 'obsidian';
10+
import { getPrototypeOf } from 'obsidian-dev-utils/ObjectUtils';
11+
import {
12+
parseLink,
13+
parseLinks
14+
} from 'obsidian-dev-utils/obsidian/Link';
15+
import { registerPatch } from 'obsidian-dev-utils/obsidian/MonkeyAround';
16+
17+
import type { Plugin } from './Plugin.ts';
18+
19+
type GetValueFn = AbstractInputSuggest<MySearchResult>['getValue'];
20+
type RenderTextPropertyWidgetComponentFn = MetadataTypeManagerRegisteredTypeWidgetsRecord['text']['render'];
21+
type SelectSuggestionFn = AbstractInputSuggest<MySearchResult>['selectSuggestion'];
22+
23+
let isTextPropertyWidgetComponentPatched = false;
24+
25+
interface MySearchResult extends SearchResult {
26+
text: string;
27+
type: string;
28+
}
29+
30+
interface Offset {
31+
from: number;
32+
to: number;
33+
}
34+
35+
const patchedInputEls = new WeakMap<HTMLDivElement, Offset>();
36+
37+
export function patchTextPropertyWidgetComponent(plugin: Plugin): void {
38+
const widget = plugin.app.metadataTypeManager.registeredTypeWidgets.text;
39+
40+
registerPatch(plugin, widget, {
41+
render: (next: RenderTextPropertyWidgetComponentFn): RenderTextPropertyWidgetComponentFn => (el, value, ctx) => renderWidget(el, value, ctx, next, plugin)
42+
});
43+
44+
registerPatch(plugin, AbstractInputSuggest.prototype, {
45+
getValue: (next: GetValueFn): GetValueFn => {
46+
return function getValuePatched(this: AbstractInputSuggest<MySearchResult>): string {
47+
return getValue(next, this, plugin);
48+
};
49+
}
50+
});
51+
}
52+
53+
let isCustomAbstractInputSuggestPatched = false;
54+
55+
function getCaretCharacterOffset(): number {
56+
const sel = window.getSelection();
57+
if (!sel || sel.rangeCount === 0) {
58+
return 0;
59+
}
60+
const range = sel.getRangeAt(0);
61+
return range.startOffset;
62+
}
63+
64+
function getParseLinkResult(textPropertyComponent: TextPropertyWidgetComponent, useValue = false): null | ParseLinkResult {
65+
const text = useValue ? textPropertyComponent.value : textPropertyComponent.inputEl.textContent;
66+
return parseLink(text);
67+
}
68+
69+
function getValue(next: GetValueFn, suggest: AbstractInputSuggest<MySearchResult>, plugin: Plugin): string {
70+
if (!isCustomAbstractInputSuggestPatched) {
71+
const customAbstractInputSuggestProto = getPrototypeOf(suggest);
72+
registerPatch(plugin, customAbstractInputSuggestProto, {
73+
selectSuggestion: (nextSelectSuggestion: SelectSuggestionFn): SelectSuggestionFn => {
74+
return function selectSuggestionPatched(this: AbstractInputSuggest<MySearchResult>, value: MySearchResult, evt: KeyboardEvent | MouseEvent): void {
75+
selectSuggestion(nextSelectSuggestion, this, value, evt);
76+
};
77+
}
78+
});
79+
isCustomAbstractInputSuggestPatched = true;
80+
}
81+
82+
const value = next.call(suggest);
83+
if (!patchedInputEls.has(suggest.textInputEl)) {
84+
return value;
85+
}
86+
const caretOffset = getCaretCharacterOffset();
87+
const valueBeforeCaret = value.slice(0, caretOffset);
88+
const openBracketBeforeCaretIndex = valueBeforeCaret.lastIndexOf('[[');
89+
const closeBracketBeforeCaretIndex = valueBeforeCaret.lastIndexOf(']]');
90+
91+
if (openBracketBeforeCaretIndex < 0 || openBracketBeforeCaretIndex < closeBracketBeforeCaretIndex) {
92+
return value;
93+
}
94+
95+
patchedInputEls.set(suggest.textInputEl, { from: openBracketBeforeCaretIndex, to: caretOffset });
96+
return value.slice(openBracketBeforeCaretIndex, caretOffset);
97+
}
98+
99+
function render(textPropertyComponent: TextPropertyWidgetComponent, next: () => void): void {
100+
const parseLinkResult = getParseLinkResult(textPropertyComponent, true);
101+
if (parseLinkResult?.isExternal && parseLinkResult.hasAngleBrackets) {
102+
textPropertyComponent.value = parseLinkResult.encodedUrl ?? parseLinkResult.url;
103+
} else if (parseLinkResult?.isEmbed) {
104+
textPropertyComponent.value = parseLinkResult.raw.slice(1);
105+
}
106+
next.call(textPropertyComponent);
107+
}
108+
109+
function renderWidget(
110+
el: HTMLElement,
111+
data: unknown,
112+
ctx: PropertyRenderContext,
113+
next: RenderTextPropertyWidgetComponentFn,
114+
plugin: Plugin
115+
): TextPropertyWidgetComponent {
116+
if (typeof data !== 'string') {
117+
return next(el, data, ctx);
118+
}
119+
120+
const str = data;
121+
122+
if (!isTextPropertyWidgetComponentPatched) {
123+
const temp = el.createDiv();
124+
const textPropertyWidgetComponent = next(temp, '', ctx);
125+
const textPropertyWidgetComponentProto = getPrototypeOf(textPropertyWidgetComponent);
126+
registerPatch(plugin, textPropertyWidgetComponentProto, {
127+
render: (nextRender: () => void) =>
128+
function renderPatched(this: TextPropertyWidgetComponent): void {
129+
render(this, nextRender);
130+
}
131+
});
132+
isTextPropertyWidgetComponentPatched = true;
133+
temp.remove();
134+
}
135+
136+
const ctxWithRerenderOnChange = {
137+
...ctx,
138+
onChange: (newValue: unknown): void => {
139+
ctx.onChange(newValue);
140+
requestAnimationFrame(() => {
141+
el.empty();
142+
renderWidget(el, newValue, ctx, next, plugin);
143+
});
144+
}
145+
};
146+
147+
const parseLinkResults = parseLinks(str);
148+
el.addClass('frontmatter-markdown-links', 'text-property-widget-component');
149+
const childWidgetsContainerEl = el.createDiv('metadata-property-value');
150+
151+
const hasMultipleLinks = parseLinkResults.length > 0 && parseLinkResults[0]?.raw !== str;
152+
153+
if (hasMultipleLinks) {
154+
let startOffset = 0;
155+
156+
for (const parseLinkResult of parseLinkResults) {
157+
createChildWidget(startOffset, parseLinkResult.startOffset);
158+
createChildWidget(parseLinkResult.startOffset, parseLinkResult.endOffset);
159+
startOffset = parseLinkResult.endOffset;
160+
}
161+
162+
createChildWidget(startOffset, str.length);
163+
}
164+
165+
const widget = next(el, str, ctxWithRerenderOnChange);
166+
if (hasMultipleLinks) {
167+
widget.inputEl.hide();
168+
hideMetadataLink(widget);
169+
el.appendChild(childWidgetsContainerEl);
170+
171+
widget.inputEl.addEventListener('blur', () => {
172+
widget.inputEl.hide();
173+
hideMetadataLink(widget);
174+
childWidgetsContainerEl.show();
175+
});
176+
177+
patchedInputEls.set(widget.inputEl, { from: 0, to: 0 });
178+
}
179+
180+
return widget;
181+
182+
function hideMetadataLink(widget2: TextPropertyWidgetComponent): void {
183+
const metadataLinkEl = widget2.containerEl.find('.metadata-link') as HTMLElement | null;
184+
metadataLinkEl?.hide();
185+
}
186+
187+
function createChildWidget(widgetStartOffset: number, widgetEndOffset: number): void {
188+
if (widgetStartOffset >= widgetEndOffset) {
189+
return;
190+
}
191+
192+
const childWidgetValue = str.slice(widgetStartOffset, widgetEndOffset);
193+
const childEl = childWidgetsContainerEl.createDiv('metadata-property-value');
194+
195+
const childWidget = next(childEl, childWidgetValue, ctx);
196+
childWidget.inputEl.addEventListener('focus', () => {
197+
requestAnimationFrame(() => {
198+
const caretOffset = getCaretCharacterOffset();
199+
childWidgetsContainerEl.hide();
200+
widget.inputEl.show();
201+
widget.inputEl.focus();
202+
const sel = widget.inputEl.win.getSelection();
203+
if (!sel) {
204+
return;
205+
}
206+
if (!widget.inputEl.firstChild) {
207+
return;
208+
}
209+
210+
const range = widget.inputEl.doc.createRange();
211+
range.setStart(widget.inputEl.firstChild, widgetStartOffset + caretOffset);
212+
range.collapse(true);
213+
sel.removeAllRanges();
214+
sel.addRange(range);
215+
});
216+
});
217+
}
218+
}
219+
220+
function selectSuggestion(
221+
next: SelectSuggestionFn,
222+
suggest: AbstractInputSuggest<MySearchResult>,
223+
value: MySearchResult,
224+
evt: KeyboardEvent | MouseEvent
225+
): void {
226+
if (!patchedInputEls.has(suggest.textInputEl)) {
227+
next.call(suggest, value, evt);
228+
return;
229+
}
230+
231+
const oldValue = suggest.textInputEl.textContent;
232+
next.call(suggest, value, evt);
233+
const newValue = suggest.textInputEl.textContent;
234+
const { from, to } = patchedInputEls.get(suggest.textInputEl) ?? { from: 0, to: 0 };
235+
patchedInputEls.set(suggest.textInputEl, { from: 0, to: 0 });
236+
237+
const fixedValue = oldValue.slice(0, from) + newValue + oldValue.slice(to);
238+
next.call(suggest, {
239+
...value,
240+
text: fixedValue,
241+
type: 'text'
242+
}, evt);
243+
}

0 commit comments

Comments
 (0)