Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
83b5edb
add missing `is-collapsed` class to callout's div when closed
GamerGirlandCo Feb 11, 2026
07baefe
add new modal component
GamerGirlandCo Feb 11, 2026
e5bbbba
add ability to `dc.require` the `obsidian` package
GamerGirlandCo Jan 23, 2026
2b3a497
add `sort` function to `Groupings` namespace
GamerGirlandCo Jan 22, 2026
83fd96b
add hooks and reducers to facilitate sorting
GamerGirlandCo Jan 23, 2026
e3d4b24
re-add sorting functionality to table view
GamerGirlandCo Jan 23, 2026
5996813
export `TableContextProvider`
GamerGirlandCo Jan 23, 2026
bbc8dca
add table context getter prop to sort button
GamerGirlandCo Feb 5, 2026
32f1d58
lift common props from table context into their own type
GamerGirlandCo Feb 5, 2026
e9dde80
create a `useAsync` hook and a companion `Suspend` component
GamerGirlandCo Apr 22, 2025
8a06ed1
add `loader` parameter to `useEffect` dependencies
GamerGirlandCo Apr 27, 2025
4ae3c08
make `Suspend` fallback prettier
GamerGirlandCo Apr 27, 2025
1ac5022
update `useAsync` to be more resilient to race conditions
GamerGirlandCo May 10, 2025
29cdf89
rewrite `useAsync` to use suspense boundaries properly
GamerGirlandCo Jun 29, 2025
293ee74
expose `SETTINGS_CONTEXT`, `COMPONENT_CONTEXT`, `DATACORE_CONTEXT` an…
GamerGirlandCo Feb 17, 2025
6cbd5ff
add ability to create views for queries that are treated like any oth…
GamerGirlandCo Apr 19, 2025
69147b2
upgrade react-select to silence typescript errors
GamerGirlandCo May 8, 2025
d86f9e4
re-add vim codemirror plugin
GamerGirlandCo May 8, 2025
894c335
re-add missing autocomplete and javascript language codemirror plugins
GamerGirlandCo May 8, 2025
da56bc3
add more dev deps to appease typescript
GamerGirlandCo May 8, 2025
cd33589
bring `@codemirror/view` up to date
GamerGirlandCo May 8, 2025
3a4ddd0
silence more bogus errors
GamerGirlandCo May 10, 2025
045aecc
move select augmentation to proper folder, re-export some things
GamerGirlandCo May 10, 2025
9719e59
re-add `src/ui/fields` folder (for now?)
GamerGirlandCo Aug 4, 2025
91c7ef7
re-add missing autocomplete and javascript language codemirror plugins
GamerGirlandCo May 8, 2025
5fbcd15
add more dev deps to appease typescript
GamerGirlandCo May 8, 2025
5e99195
bring `@codemirror/view` up to date
GamerGirlandCo May 8, 2025
a9cfd3e
silence more bogus errors
GamerGirlandCo May 10, 2025
6196e1d
move select augmentation to proper folder, re-export some things
GamerGirlandCo May 10, 2025
2fa6125
add "controlled" editable component
GamerGirlandCo Apr 19, 2025
035945f
add controlled editable component to local api
GamerGirlandCo Apr 21, 2025
bf4a8c9
add task + tasklist components, add utilities for manipulating tasks,…
GamerGirlandCo Apr 19, 2025
872de4f
fix for multiline list items
GamerGirlandCo Nov 1, 2024
f917c0d
add list styles
GamerGirlandCo Apr 19, 2025
77109c1
remove unused import from task utils
GamerGirlandCo Apr 19, 2025
c2f5cff
remove unused imports from a couple files
GamerGirlandCo Apr 20, 2025
c2f36ca
re-add `setTaskText` and `setTaskCompletion` utilities to local api
GamerGirlandCo Apr 20, 2025
d9783b8
fix errors caused by rebase
GamerGirlandCo May 8, 2025
15d667c
update `setTaskText` signature to allow overwriting inline fields wit…
GamerGirlandCo Jun 28, 2025
a8041ca
fix bug where fields not present in `newFields` get set to an empty v…
GamerGirlandCo Jun 28, 2025
a3d9a7d
refactor and improve react code for rendering list/task fields
GamerGirlandCo Jan 23, 2026
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
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
Expand Down
87 changes: 83 additions & 4 deletions src/api/local-api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,34 @@ import { Datacore } from "index/datacore";
import { SearchResult } from "index/datastore";
import { IndexQuery } from "index/types/index-query";
import { Indexable } from "index/types/indexable";
import { MarkdownPage } from "index/types/markdown";
import { MarkdownPage, MarkdownTaskItem } 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";
import { Callout } from "./ui/views/callout";
import { TaskList } from "./ui/views/task";
import { DataArray } from "./data-array";
import { Coerce } from "./coerce";
import { ScriptCache } from "./script-cache";
import { Expression } from "expression/expression";
import { Card } from "./ui/views/cards";
import { ListView } from "./ui/views/list";
import { Modal, Modals, SubmittableModal, useModalContext } from "./ui/views/modal";
import * as obsidian from "obsidian";
import { ControlledEditable } from "ui/fields/editable";
import { setTaskText, useSetField } from "utils/fields";
import { ControlledEditableTextField, EditableFieldCheckbox, EditableTextField } from "ui/fields/editable-fields";
import { completeTask } from "utils/task";

/**
* Local API provided to specific codeblocks when they are executing.
Expand All @@ -37,6 +44,8 @@ export class DatacoreLocalApi {
/** @internal The cache of all currently loaded scripts in this context. */
private scriptCache: ScriptCache;

private modalTypes: Modals = new Modals();

public constructor(public api: DatacoreApi, public path: string) {
this.scriptCache = new ScriptCache(this.core.datastore);
}
Expand Down Expand Up @@ -93,6 +102,9 @@ export class DatacoreLocalApi {
* ```
*/
public async require(path: string | Link): Promise<unknown> {
if (typeof path === "string" && path === "obsidian") {
return Result.success(obsidian);
}
const result = await this.scriptCache.load(path, { dc: this });
return result.orElseThrow();
}
Expand Down Expand Up @@ -189,6 +201,35 @@ export class DatacoreLocalApi {
public tryFullQuery<T extends Indexable = Indexable>(query: string | IndexQuery): Result<SearchResult<T>, string>;
public tryFullQuery(query: string | IndexQuery): Result<SearchResult<Indexable>, string> {
return this.api.tryFullQuery(query);
}
/** Sets the text of a given task programmatically. */
public setTaskText(newText: string, task: MarkdownTaskItem, newFields: Record<string, Literal> = {}): void {
setTaskText(this.app, this.core, newText, task, newFields);
}

/** Sets the completion status of a given task programmatically. */
public setTaskCompletion(completed: boolean, task: MarkdownTaskItem): void {
completeTask(completed, task, this.app.vault, this.core);
}

//////////////
// 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;
}

/////////////
Expand Down Expand Up @@ -219,6 +260,8 @@ export class DatacoreLocalApi {
* React's reference-equality-based caching.
*/
public useInterning = useInterning;
public useAsync = useAsync;
public useSetField = useSetField;

/** Memoize the input automatically and process it using a DataArray; returns a vanilla array back. */
public useArray<T, U>(
Expand Down Expand Up @@ -279,6 +322,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);
Expand Down Expand Up @@ -389,6 +434,19 @@ export class DatacoreLocalApi {
return <ErrorMessage message={`No valid embedding for element '${element.$id}' from '${element.$file}'`} />;
}).bind(this);

/** Accessor for raw modal classes. */
public get modals() {
return this.modalTypes;
}

/** Wrapper around an obsidian modal. */
public Modal = Modal;

/** Wrapper around an obsidian modal that returns a result when submitted. */
public SubmittableModal = SubmittableModal;

public useModalContext = useModalContext;

///////////
// Views //
///////////
Expand All @@ -402,16 +460,37 @@ export class DatacoreLocalApi {
public List = ListView;
/** A single card which can be composed into a grid view. */
public Card = Card;
public TaskList = TaskList;

/////////////////////////
// Interative elements //
/////////////////////////

public ControlledEditable = ControlledEditable;
public Button = Button;
public Textbox = Textbox;
public Callout = Callout;
public Checkbox = Checkbox;
public Slider = Slider;
public Switch = Switch;
public VanillaSelect = VanillaSelect;

////////////////////////////////////
// Stateful / internal components //
////////////////////////////////////

/**
* Updates the path for the local API; usually only called by the top-level script renderer on
* path changes (such as renaming a file).
* @internal
*/
updatePath(path: string): void {
this.path = path;
}
/////////////////////////
// field editors //
/////////////////////////
public EditableFieldCheckbox = EditableFieldCheckbox;
public EditableFieldTextbox = EditableTextField;
public TextEditor = ControlledEditableTextField;
}
2 changes: 1 addition & 1 deletion src/api/ui/views/callout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export function Callout({
data-callout-metadata={type?.split(METADATA_SPLIT_REGEX)?.[1]}
data-callout={type?.split(METADATA_SPLIT_REGEX)?.[0]}
data-callout-fold={open ? "+" : "-"}
className={combineClasses("datacore", "callout", collapsible ? "is-collapsible" : undefined)}
className={combineClasses("datacore", "callout", collapsible ? "is-collapsible" : undefined, !open ? "is-collapsed" : undefined)}
>
<div className="callout-title" onClick={() => collapsible && setOpen(!open)}>
{icon && <div className="callout-icon">{icon}</div>}
Expand Down
28 changes: 28 additions & 0 deletions src/api/ui/views/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { ControlledPager, useDatacorePaging } from "./paging";
import { useAsElement } from "ui/hooks";
import { CSSProperties, ReactNode } from "preact/compat";
import { MarkdownListItem } from "index/types/markdown";
import { BaseFieldProps } from "ui/fields/common-props";
import { ControlledEditable, EditableElement } from "ui/fields/editable";

/** The render type of the list view. */
export type ListViewType = "ordered" | "unordered" | "block";
Expand Down Expand Up @@ -59,6 +61,8 @@ export interface ListViewProps<T> {
* If null, child extraction is disabled and no children will be fetched. If undefined, uses the default.
*/
childSource?: null | string | string[] | ((row: T) => T[]);
/** fields to display under each item in this task list */
displayedFields?: (BaseFieldProps<Literal> & { key: string })[];
}

/**
Expand Down Expand Up @@ -430,3 +434,27 @@ function fetchProps<T>(element: T, props: string[]): T[] {

return result;
}
export function EditableListElement<T>({
element: item,
editor,
onUpdate,
file,
editorProps,
}: {
editor: (value: T) => EditableElement<T>;
element: T;
file: string;
onUpdate: (value: T) => unknown;
editorProps: unknown;
}) {
return (
<ControlledEditable<T>
props={editorProps}
sourcePath={file}
content={item}
editor={editor(item)}
onUpdate={onUpdate}
defaultRender={<DefaultListElement element={item} />}
/>
);
}
54 changes: 54 additions & 0 deletions src/api/ui/views/lists.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
.datacore-list-item-content {
display: inline-flex;
justify-content: space-between;
width: 100%;
}
.datacore-list-item-content > :first-child {
flex-grow: 1;
}

:is(ul, ol) li:not(:first-of-type) p:first-of-type {
margin-block-start: unset !important;
}
ul.datacore.contains-task-list > li {
/* margin-inline-start: 0; */
}
input.datacore.task-list-item-checkbox {
/* position: absolute; */
float: left;
margin-inline-start: calc(var(--checkbox-size) * 0.1) !important;
/* margin-inline-start: 0 !important; */
}

li.datacore.task-list-item > *:nth-child(3) {
display: flow-root !important;
top: -5px;
padding-left: 0.5em;
position: relative;
}

.datacore-collapser,
.datacore-collapser svg.svg-icon {
transition: transform 100ms ease-in-out;
}
.datacore-collapser.is-collapsed svg.svg-icon {
transform: rotate(calc(var(--direction) * -1 * 90deg));
}
li.datacore.datacore.task-list-item .datacore-collapser {
margin-right: 0.7em;
float: left;
}
/* li.datacore.task-list-item > :first-child {
display: flex;
float: left;
} */
li.datacore.task-list-item .datacore-collapser {
vertical-align: middle;
align-self: start;
top: -0.1em;
position: absolute;
margin-inline-start: calc(var(--checkbox-size) * -1.4);
}
li.datacore.task-list-item .datacore-collapser.no-children {
visibility: hidden;
}
Loading
Loading