Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
72985bc
add missing `is-collapsed` class to callout's div when closed
GamerGirlandCo Feb 11, 2026
841d48e
add new modal component
GamerGirlandCo Feb 11, 2026
626d437
add ability to `dc.require` the `obsidian` package
GamerGirlandCo Jan 23, 2026
db33036
add `sort` function to `Groupings` namespace
GamerGirlandCo Jan 22, 2026
a4fad0e
add hooks and reducers to facilitate sorting
GamerGirlandCo Jan 23, 2026
b979e2b
re-add sorting functionality to table view
GamerGirlandCo Jan 23, 2026
5e5007f
export `TableContextProvider`
GamerGirlandCo Jan 23, 2026
d131f4e
add table context getter prop to sort button
GamerGirlandCo Feb 5, 2026
3dc0d9e
lift common props from table context into their own type
GamerGirlandCo Feb 5, 2026
199960a
create a `useAsync` hook and a companion `Suspend` component
GamerGirlandCo Apr 22, 2025
5ce8f79
add `loader` parameter to `useEffect` dependencies
GamerGirlandCo Apr 27, 2025
d4246cb
make `Suspend` fallback prettier
GamerGirlandCo Apr 27, 2025
7ece762
update `useAsync` to be more resilient to race conditions
GamerGirlandCo May 10, 2025
221c5fc
rewrite `useAsync` to use suspense boundaries properly
GamerGirlandCo Jun 29, 2025
f205e63
expose `SETTINGS_CONTEXT`, `COMPONENT_CONTEXT`, `DATACORE_CONTEXT` an…
GamerGirlandCo Feb 17, 2025
629f915
add ability to create views for queries that are treated like any oth…
GamerGirlandCo Apr 19, 2025
1d5a4e5
upgrade react-select to silence typescript errors
GamerGirlandCo May 8, 2025
ea02777
re-add vim codemirror plugin
GamerGirlandCo May 8, 2025
0848f4a
re-add missing autocomplete and javascript language codemirror plugins
GamerGirlandCo May 8, 2025
26d7c6d
add more dev deps to appease typescript
GamerGirlandCo May 8, 2025
10e5fe2
bring `@codemirror/view` up to date
GamerGirlandCo May 8, 2025
c8745c9
silence more bogus errors
GamerGirlandCo May 10, 2025
279ffd2
move select augmentation to proper folder, re-export some things
GamerGirlandCo May 10, 2025
305c096
re-add `src/ui/fields` folder (for now?)
GamerGirlandCo Aug 4, 2025
dd32cf8
re-add missing autocomplete and javascript language codemirror plugins
GamerGirlandCo May 8, 2025
bb50758
add more dev deps to appease typescript
GamerGirlandCo May 8, 2025
1e87843
bring `@codemirror/view` up to date
GamerGirlandCo May 8, 2025
650f4af
silence more bogus errors
GamerGirlandCo May 10, 2025
30de22d
move select augmentation to proper folder, re-export some things
GamerGirlandCo May 10, 2025
691f763
add "controlled" editable component
GamerGirlandCo Apr 19, 2025
c73f265
add controlled editable component to local api
GamerGirlandCo Apr 21, 2025
9e037af
add task + tasklist components, add utilities for manipulating tasks,…
GamerGirlandCo Apr 19, 2025
53a508e
fix for multiline list items
GamerGirlandCo Nov 1, 2024
16f1c3b
add list styles
GamerGirlandCo Apr 19, 2025
6c965ea
remove unused import from task utils
GamerGirlandCo Apr 19, 2025
a920046
remove unused imports from a couple files
GamerGirlandCo Apr 20, 2025
26c8379
re-add `setTaskText` and `setTaskCompletion` utilities to local api
GamerGirlandCo Apr 20, 2025
b5c8e8e
fix errors caused by rebase
GamerGirlandCo May 8, 2025
53a7940
update `setTaskText` signature to allow overwriting inline fields wit…
GamerGirlandCo Jun 28, 2025
7587e62
fix bug where fields not present in `newFields` get set to an empty v…
GamerGirlandCo Jun 28, 2025
e8cae82
refactor and improve react code for rendering list/task fields
GamerGirlandCo Jan 23, 2026
97347a7
make table columns optionally editable
GamerGirlandCo Apr 19, 2025
52cae3f
add components to local api
GamerGirlandCo Apr 19, 2025
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
97 changes: 93 additions & 4 deletions src/api/local-api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,41 @@ 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 { completeTask } from "utils/task";
import {
ControlledEditableTextField,
EditableTextField,
FieldCheckbox,
FieldSelect,
FieldSlider,
FieldSwitch,
} from "ui/fields/editable-fields";

/**
* Local API provided to specific codeblocks when they are executing.
Expand All @@ -37,6 +51,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 +109,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 +208,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 +267,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 +329,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 +441,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 +467,40 @@ 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;
public VanillaTextBox = ControlledEditableTextField;

////////////////////////////////////
// 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 FieldCheckbox = FieldCheckbox;
public FieldSlider = FieldSlider;
public FieldSelect = FieldSelect;
public FieldSwitch = FieldSwitch;
public TextField = EditableTextField;
}
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