-
Notifications
You must be signed in to change notification settings - Fork 7
ENG-519 Make human-readable labels for node ids #1271
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
maparent
wants to merge
10
commits into
main
Choose a base branch
from
eng-519-make-human-readable-labels-for-node-ids
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
0b0a483
eng-519-human-readable-label-for-node-type-Claude-nodeTypeIdPropertyW…
maparent db4605f
add try-catch clauses
maparent fd3f363
obsidianUnofficialTypes
maparent 96c67a1
consistent registration
maparent e05dfa6
move widget to components. Remove unused exports. Name the widget types.
maparent 3776d1a
use bare minimum from MetadataTypeManager
maparent 08e80f8
remove dependencies accordingly
maparent 245f7db
add another catch
maparent b0c5329
fix: restore prior nodeTypeId widget assignment on unload
maparent 1fc7243
nits
maparent File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
101 changes: 101 additions & 0 deletions
101
apps/obsidian/src/components/nodeTypeIdPropertyWidget.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| import type { | ||
| AppWithUnofficialApis, | ||
| PropertyWidget, | ||
| PropertyWidgetComponentBase, | ||
| } from "~/utils/obsidianUnofficialTypes"; | ||
| import type DiscourseGraphPlugin from "~/index"; | ||
| import { getNodeTypeById } from "~/utils/typeUtils"; | ||
|
|
||
| export const NODE_TYPE_ID_PROPERTY_KEY = "nodeTypeId"; | ||
| const WIDGET_TYPE = "dg-node-type-id"; | ||
|
|
||
| type NodeTypeIdPropertyWidgetComponent = PropertyWidgetComponentBase; | ||
|
|
||
| type NodeTypeIdPropertyWidget = | ||
| PropertyWidget<NodeTypeIdPropertyWidgetComponent>; | ||
|
|
||
| /** | ||
| * Obsidian's frontmatter Properties UI (reading view + live preview) renders each | ||
| * property via a widget looked up by `metadataTypeManager`. This is unofficial/ | ||
| * internal API (see `obsidian-typings`), so it degrades gracefully: if Obsidian | ||
| * ever drops support, the widget type is simply unrecognized and the property | ||
| * renders as its raw text value, same as before this widget existed. | ||
| */ | ||
| const createWidget = ( | ||
| plugin: DiscourseGraphPlugin, | ||
| ): NodeTypeIdPropertyWidget => ({ | ||
| type: WIDGET_TYPE, | ||
| icon: "shapes", | ||
| name: () => "Discourse node type", | ||
| validate: (value: unknown) => typeof value === "string", | ||
| render: (containerEl: HTMLElement, data: string) => { | ||
| const nodeType = getNodeTypeById(plugin, data); | ||
|
|
||
| const el = containerEl.createSpan({ | ||
| cls: "dg-node-type-id-value", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. stale class |
||
| text: nodeType?.name ?? data, | ||
| }); | ||
| el.setAttr("title", data); | ||
| el.tabIndex = 0; | ||
|
|
||
| return { | ||
| type: WIDGET_TYPE, | ||
| focus: () => el.focus(), | ||
| }; | ||
| }, | ||
| }); | ||
|
|
||
| let previouslyAssignedWidgetType: string | null = null; | ||
|
|
||
| export const registerNodeTypeIdPropertyWidget = ( | ||
| plugin: DiscourseGraphPlugin, | ||
| ): void => { | ||
| const metadataTypeManager = (plugin.app as AppWithUnofficialApis) | ||
| .metadataTypeManager; | ||
|
|
||
| if (metadataTypeManager) | ||
| try { | ||
| const assignedWidget = metadataTypeManager.getAssignedWidget( | ||
| NODE_TYPE_ID_PROPERTY_KEY, | ||
| ); | ||
| if (assignedWidget !== WIDGET_TYPE) { | ||
| previouslyAssignedWidgetType = assignedWidget; | ||
| } | ||
|
|
||
| metadataTypeManager.registeredTypeWidgets[WIDGET_TYPE] = | ||
| createWidget(plugin); | ||
| metadataTypeManager | ||
| .setType(NODE_TYPE_ID_PROPERTY_KEY, WIDGET_TYPE) | ||
| .catch((error) => console.error(error)); | ||
| } catch (error) { | ||
| console.error(error); | ||
| } | ||
| }; | ||
|
maparent marked this conversation as resolved.
|
||
|
|
||
| export const unregisterNodeTypeIdPropertyWidget = ( | ||
| plugin: DiscourseGraphPlugin, | ||
| ): void => { | ||
| const metadataTypeManager = (plugin.app as AppWithUnofficialApis) | ||
| .metadataTypeManager; | ||
|
|
||
| if (metadataTypeManager) | ||
| try { | ||
| if ( | ||
| metadataTypeManager.getAssignedWidget(NODE_TYPE_ID_PROPERTY_KEY) === | ||
| WIDGET_TYPE | ||
| ) { | ||
| if (previouslyAssignedWidgetType) { | ||
| metadataTypeManager | ||
| .setType(NODE_TYPE_ID_PROPERTY_KEY, previouslyAssignedWidgetType) | ||
| .catch((error) => console.error(error)); | ||
| } else { | ||
| metadataTypeManager | ||
| .unsetType(NODE_TYPE_ID_PROPERTY_KEY) | ||
| .catch((error) => console.error(error)); | ||
| } | ||
| } | ||
| delete metadataTypeManager.registeredTypeWidgets[WIDGET_TYPE]; | ||
| } catch (error) { | ||
| console.error(error); | ||
| } | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| import { App, Events, Plugin, Component } from "obsidian"; | ||
|
|
||
| // extracted the MetadataTypeManager from "obsidian-types", | ||
| // and parts of internalPlugins, plugins. | ||
|
|
||
| type FocusMode = "both" | "end" | "start"; | ||
|
|
||
| export type PropertyWidgetComponentBase = { | ||
| /** | ||
| * The type of the property widget. | ||
| */ | ||
| type: string; | ||
| /** | ||
| * Focus the property widget. | ||
| * | ||
| * @param mode - The focus mode. | ||
| */ | ||
| focus(mode?: FocusMode): void; | ||
| }; | ||
|
|
||
| type PropertyRenderContext = { | ||
| /** | ||
| * Reference to the app. | ||
| */ | ||
| app: App; | ||
| /** | ||
| * Key of the property field. | ||
| */ | ||
| key: string; | ||
| /** | ||
| * Determine the source path of current context. | ||
| */ | ||
| sourcePath: string; | ||
| /** | ||
| * Callback called on property field unfocus. | ||
| */ | ||
| blur(): void; | ||
| /** | ||
| * Callback called on property value change. | ||
| * | ||
| * @param value - The new property value. | ||
| */ | ||
| onChange(value: unknown): void; | ||
| }; | ||
|
|
||
| export type PropertyWidget< | ||
| ComponentType extends | ||
| PropertyWidgetComponentBase = PropertyWidgetComponentBase, | ||
| > = { | ||
| /** | ||
| * Lucide-dev icon associated with the widget. | ||
| */ | ||
| icon: string; | ||
| /** | ||
| * Reserved keys for the widget. | ||
| */ | ||
| reservedKeys?: string[]; | ||
| /** | ||
| * Identifier for the widget. | ||
| */ | ||
| type: string; | ||
| /** | ||
| * Returns the I18N name of the widget. | ||
| * | ||
| * @returns The localized name of the widget. | ||
| */ | ||
| name(): string; | ||
| /** | ||
| * Render function for the widget on field container given context and data. | ||
| * | ||
| * @param containerEl - The container element to render the widget into. | ||
| * @param data - The property data to render. | ||
| * @param context - The rendering context for the property. | ||
| * @returns The rendered widget component. | ||
| */ | ||
| render( | ||
| containerEl: HTMLElement, | ||
| data: unknown, | ||
| context: PropertyRenderContext, | ||
| ): ComponentType; | ||
| /** | ||
| * Validate whether the input value to the widget is correct. | ||
| * | ||
| * @param value - The value to validate. | ||
| * @returns Whether the value is valid. | ||
| */ | ||
| validate(value: unknown): boolean; | ||
| }; | ||
|
|
||
| type PropertyWidgetType = string; | ||
|
|
||
| type MetadataTypeManager = { | ||
| /** | ||
| * Registered type widgets. | ||
| */ | ||
| registeredTypeWidgets: Record<PropertyWidgetType, PropertyWidget>; | ||
| /** | ||
| * Get assigned widget type for property. | ||
| * | ||
| * @param property - Property name. | ||
| * @returns The assigned widget type, or `null`. | ||
| */ | ||
| getAssignedWidget(property: string): null | PropertyWidgetType; | ||
| /** | ||
| * Set widget type for property. | ||
| * | ||
| * @param property - Property name. | ||
| * @param type - Widget type to assign. | ||
| * @returns A promise that resolves when the widget type is set. | ||
| */ | ||
| setType(property: string, type: PropertyWidgetType): Promise<void>; | ||
| /** | ||
| * Unset widget type for property. | ||
| * | ||
| * @param property - Property name. | ||
| * @returns A promise that resolves when the widget type is unset. | ||
| */ | ||
| unsetType(property: string): Promise<void>; | ||
| } & Events; | ||
|
|
||
| export type InternalPluginInstance = { | ||
| plugin: InternalPlugin; | ||
| }; | ||
|
|
||
| type InternalPlugin = { | ||
| enabled: boolean; | ||
| instance: InternalPluginInstance; | ||
| } & Component; | ||
|
|
||
| export type AppWithUnofficialApis = App & { | ||
|
trangdoan982 marked this conversation as resolved.
|
||
| appId: string; | ||
| metadataTypeManager?: MetadataTypeManager; | ||
| plugins?: Events & { plugins?: Record<string, Plugin> }; | ||
| internalPlugins?: Events & { | ||
| plugins?: Record<string, InternalPlugin>; | ||
| }; | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: fileName in
componentsshould be CamelCase