diff --git a/package.json b/package.json index 96d08a638..5c071f54b 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,12 @@ "author": "Michael Brenan", "license": "MIT", "devDependencies": { + "@codemirror/autocomplete": "^6.18.6", + "@codemirror/commands": "^6.8.1", "@codemirror/language": "https://github.com/lishid/cm-language", + "@codemirror/search": "^6.5.10", "@codemirror/state": "^6.0.1", - "@codemirror/view": "^6.0.1", + "@codemirror/view": "^6.36.7", "@microsoft/api-extractor": "^7.52.7", "@types/jest": "^27.0.1", "@types/luxon": "^2.3.2", @@ -45,17 +48,19 @@ "typescript": "^5.4.2" }, "dependencies": { + "@codemirror/lang-javascript": "^6.2.3", "@datastructures-js/queue": "^4.2.3", "@fortawesome/fontawesome-svg-core": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/react-fontawesome": "^0.2.0", + "@replit/codemirror-vim": "^6.3.0", "emoji-regex": "^10.2.1", "flatqueue": "^2.0.3", "localforage": "1.10.0", "luxon": "^2.4.0", "parsimmon": "^1.18.0", - "preact": "^10.17.1", - "react-select": "^5.8.0", + "preact": "^10.26.6", + "react-select": "^5.10.1", "sorted-btree": "^1.8.1", "sucrase": "3.35.0", "yaml": "^2.3.3" diff --git a/src/api/local-api.tsx b/src/api/local-api.tsx index 0b40d4317..90d4b59fb 100644 --- a/src/api/local-api.tsx +++ b/src/api/local-api.tsx @@ -9,15 +9,15 @@ import { IndexQuery } from "index/types/index-query"; import { Indexable } from "index/types/indexable"; import { MarkdownPage } from "index/types/markdown"; import { App } from "obsidian"; -import { useFileMetadata, useFullQuery, useIndexUpdates, useInterning, useQuery } from "ui/hooks"; +import { useAsync, useFileMetadata, useFullQuery, useIndexUpdates, useInterning, useQuery } from "ui/hooks"; import * as luxon from "luxon"; import * as preact from "preact"; import * as hooks from "preact/hooks"; import { Result } from "./result"; import { Group, Stack } from "./ui/layout"; import { Embed, LineSpanEmbed } from "api/ui/embed"; -import { CURRENT_FILE_CONTEXT, ErrorMessage, Lit, Markdown, ObsidianLink } from "ui/markdown"; -import { CSSProperties } from "preact/compat"; +import { APP_CONTEXT, COMPONENT_CONTEXT, CURRENT_FILE_CONTEXT, DATACORE_CONTEXT, ErrorMessage, Lit, Markdown, ObsidianLink, SETTINGS_CONTEXT } from "ui/markdown"; +import { CSSProperties, Suspense } from "preact/compat"; import { Literal, Literals } from "expression/literal"; import { Button, Checkbox, Icon, Slider, Switch, Textbox, VanillaSelect } from "./ui/basics"; import { TableView } from "./ui/views/table"; @@ -28,6 +28,7 @@ import { ScriptCache } from "./script-cache"; import { Expression } from "expression/expression"; import { Card } from "./ui/views/cards"; import { ListView } from "./ui/views/list"; +import { ControlledEditable } from "ui/fields/editable"; /** * Local API provided to specific codeblocks when they are executing. @@ -184,6 +185,25 @@ export class DatacoreLocalApi { /** Execute a textual or typed index query, returning results plus performance metadata. */ public tryFullQuery(query: string | IndexQuery): Result, string> { return this.api.tryFullQuery(query); + } + ////////////// + // Contexts // + ////////////// + + // export the necessary contexts to enable rendering + // datacore components outside the datacore plugin + // itself + get SETTINGS_CONTEXT(): typeof SETTINGS_CONTEXT { + return SETTINGS_CONTEXT; + } + get COMPONENT_CONTEXT(): typeof COMPONENT_CONTEXT { + return COMPONENT_CONTEXT; + } + get DATACORE_CONTEXT(): typeof DATACORE_CONTEXT { + return DATACORE_CONTEXT; + } + get APP_CONTEXT(): typeof APP_CONTEXT { + return APP_CONTEXT; } ///////////// @@ -214,6 +234,7 @@ export class DatacoreLocalApi { * React's reference-equality-based caching. */ public useInterning = useInterning; + public useAsync = useAsync; /** Memoize the input automatically and process it using a DataArray; returns a vanilla array back. */ public useArray( @@ -267,6 +288,8 @@ export class DatacoreLocalApi { /** Horizontal flexbox container; good for putting items together in a row. */ public Group = Group; + public Suspense = Suspense; + /** Renders a literal value in a pretty way that respects settings. */ public Literal = (({ value, sourcePath, inline }: { value: Literal; sourcePath?: string; inline?: boolean }) => { const implicitSourcePath = hooks.useContext(CURRENT_FILE_CONTEXT); @@ -395,6 +418,7 @@ export class DatacoreLocalApi { // Interative elements // ///////////////////////// + public ControlledEditable = ControlledEditable; public Button = Button; public Textbox = Textbox; public Callout = Callout; diff --git a/src/main.ts b/src/main.ts index e201a4fdf..d495b11c5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,6 +3,7 @@ import { Datacore } from "index/datacore"; import { DateTime } from "luxon"; import { App, Plugin, PluginSettingTab, Setting } from "obsidian"; import { DEFAULT_SETTINGS, Settings } from "settings"; +import { DatacoreQueryView as DatacoreJSView, VIEW_TYPE_DATACOREJS } from "ui/view-page"; /** @internal Reactive data engine for your Obsidian.md vault. */ export default class DatacorePlugin extends Plugin { @@ -47,6 +48,21 @@ export default class DatacorePlugin extends Plugin { -100 ); + // Views: DatacoreJS view. + // @ts-ignore be quiet + this.registerView(VIEW_TYPE_DATACOREJS, (leaf) => new DatacoreJSView(leaf, this.api)); + + // Add a command for creating a new view page. + this.addCommand({ + id: "datacore-add-view-page", + name: "Create View Page", + callback: () => { + const newLeaf = this.app.workspace.getLeaf("tab"); + newLeaf.setViewState({ type: VIEW_TYPE_DATACOREJS, active: true }); + this.app.workspace.setActiveLeaf(newLeaf, { focus: true }); + }, + }); + // Register JS highlighting for codeblocks. this.register(this.registerCodeblockHighlighting()); diff --git a/src/typings/obsidian-ex.d.ts b/src/typings/obsidian-ex.d.ts index 74841050a..cedbf17cd 100644 --- a/src/typings/obsidian-ex.d.ts +++ b/src/typings/obsidian-ex.d.ts @@ -1,10 +1,49 @@ import type { DatacorePlugin } from "main"; -import type { CanvasMetadataIndex } from "index/types/json/canvas"; - +import { Extension } from "@codemirror/state"; +import type { DatacoreApi } from "api/api"; +import { CanvasMetadataIndex } from "index/types/json/canvas"; import "obsidian"; +import { App } from "obsidian"; +import * as hooks from "preact/hooks"; /** Provides extensions used by datacore or provider to other plugins via datacore. */ declare module "obsidian" { + interface WorkspaceLeaf { + serialize(): { + id: string; + type: "leaf"; + state: { + type: string; + state: any; + }; + }; + tabHeaderEl: HTMLElement; + tabHeaderInnerTitleEl: HTMLElement; + } + interface View { + getState(): any; + } + interface ItemView { + titleEl: HTMLElement; + getState(): any; + } + + interface InternalPlugin { + id: string; + name: string; + description: string; + instance: T; + } + export interface PagePreviewPlugin { + onLinkHover: ( + view: View, + hovered: HTMLElement, + hoveredPath: string, + sourcePath: string, + _unknown: unknown + ) => void; + } + interface FileManager { linkUpdaters: { canvas: { @@ -16,16 +55,26 @@ declare module "obsidian" { }; }; } - + interface Vault { + getConfig: (conf: string) => any; + } interface App { appId?: string; plugins: { enabledPlugins: Set; plugins: { - datacore?: DatacorePlugin; + datacore?: { + api: DatacoreApi; + }; + "datacore-addon-autocomplete"?: { + readonly extensions: Extension[]; + }; }; }; + internalPlugins: { + getPluginById: (id: string) => InternalPlugin; + }; embedRegistry: { embedByExtension: { @@ -54,5 +103,10 @@ declare module "obsidian" { declare global { interface Window { datacore?: DatacoreApi; + app: App; + CodeMirror: { + defineMode: (mode: string, conf: (config: any) => any) => unknown; + [key: string]: any; + }; } } diff --git a/src/typings/select.d.ts b/src/typings/select.d.ts new file mode 100644 index 000000000..568ce8166 --- /dev/null +++ b/src/typings/select.d.ts @@ -0,0 +1,20 @@ + +declare module "react-select" { + import { RefAttributes, ReactElement, JSX } from "preact/compat"; + import { StateManagerAdditionalProps } from "react-select/dist/declarations/src/useStateManager"; + import { Props } from "react-select/dist/declarations/src/Select"; + import Select from "react-select/dist/declarations/src/Select"; + export * from "react-select/dist/declarations/src/types"; + declare type StateManagedPropKeys = 'inputValue' | 'menuIsOpen' | 'onChange' | 'onInputChange' | 'onMenuClose' | 'onMenuOpen' | 'value'; + declare type PublicBaseSelectProps> = JSX.LibraryManagedAttributes>; + declare type SelectPropsWithOptionalStateManagedProps> = Omit, StateManagedPropKeys> & Partial>; + export declare type StateManagerProps