Skip to content
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

feat: multi select notes uses the new notes input (better UI and search) #207

Merged
merged 2 commits into from
Jan 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 12 additions & 30 deletions src/FormModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@ import type { FormDefinition, FormOptions } from "./core/formDefinition";
import { FileSuggest } from "./suggesters/suggestFile";
import { DataviewSuggest } from "./suggesters/suggestFromDataview";
import { SvelteComponent } from "svelte";
import { executeSandboxedDvQuery, sandboxedDvQuery } from "./suggesters/SafeDataviewQuery";
import { A, E, parseFunctionBody, pipe, throttle } from "@std";
import { E, parseFunctionBody, pipe, throttle } from "@std";
import { log_error, log_notice } from "./utils/Log";
import { FieldValue, FormEngine, makeFormEngine } from "./store/formStore";
import { Writable } from "svelte/store";
import { FolderSuggest } from "./suggesters/suggestFolder";
import { allowsUnknownValues } from "./core/InputDefinitionSchema";
import { MultiSelectModel, MultiSelectTags } from "./views/components/MultiSelectModel";

export type SubmitFn = (formResult: FormResult) => void;

Expand Down Expand Up @@ -164,55 +163,38 @@ export class FormModal extends Modal {
slider.onChange(fieldStore.value.set);
});
case "multiselect": {
const source = fieldInput.source;
const allowUnknownValues = allowsUnknownValues(fieldInput);
const options =
source == "fixed"
? fieldInput.multi_select_options
: source == "notes"
? pipe(
get_tfiles_from_folder(fieldInput.folder, this.app),
E.map(A.map((file) => file.basename)),
E.getOrElse((err) => {
log_error(err);
return [] as string[];
}),
)
: executeSandboxedDvQuery(
sandboxedDvQuery(fieldInput.query),
this.app,
);
fieldStore.value.set(initialValue ?? []);
this.svelteComponents.push(
new MultiSelect({
target: fieldBase.controlEl,
props: {
model: MultiSelectModel(
fieldInput,
this.app,
fieldStore.value as Writable<string[]>,
),
values: fieldStore.value as Writable<string[]>,
availableOptions: options,
errors: fieldStore.errors,
setting: fieldBase,
app: this.app,
allowUnknownValues,
},
}),
);
return;
}
case "tag": {
const options = Object.keys(this.app.metadataCache.getTags()).map((tag) =>
tag.slice(1),
); // remove the #
fieldStore.value.set(initialValue ?? []);
this.svelteComponents.push(
new MultiSelect({
target: fieldBase.controlEl,
props: {
values: fieldStore.value as Writable<string[]>,
availableOptions: options,
setting: fieldBase,
errors: fieldStore.errors,
app: this.app,
allowUnknownValues: true,
model: MultiSelectTags(
fieldInput,
this.app,
fieldStore.value as Writable<string[]>,
),
},
}),
);
Expand Down
1 change: 1 addition & 0 deletions src/core/InputDefinitionSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,5 @@ export type inputDataviewSource = Output<typeof InputDataviewSourceSchema>;
export type inputSelectFixed = Output<typeof InputSelectFixedSchema>;
export type basicInput = Output<typeof InputBasicSchema>;
export type multiselect = Output<typeof MultiselectSchema>;
export type inputTag = Output<typeof InputTagSchema>;
export type inputType = Output<typeof InputTypeSchema>;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AbstractInputSuggest, App } from "obsidian";

export class MultiSuggest extends AbstractInputSuggest<string> {
export class StringSuggest extends AbstractInputSuggest<string> {
content: Set<string>;

constructor(
Expand Down
33 changes: 4 additions & 29 deletions src/views/components/MultiSelect.svelte
Original file line number Diff line number Diff line change
@@ -1,46 +1,21 @@
<script lang="ts">
import type { App, Setting } from "obsidian";
import { MultiSuggest } from "../../suggesters/MultiSuggest";
import { StringSuggest } from "../../suggesters/StringSuggest";
import { A, pipe } from "@std";
import { Readable, Writable } from "svelte/store";
import { MultiSelectModel } from "./MultiSelectModel";

export let availableOptions: string[] = [];
export let model: MultiSelectModel;
export let errors: Readable<string[]>;
export let values: Writable<string[]>;
export let allowUnknownValues: boolean = false;
// We take the setting to make it consistent with the other input components
export let setting: Setting;
export let app: App;

setting.settingEl.setCssStyles({
alignItems: "baseline",
});

$: remainingOptions = new Set(availableOptions);

function createInput(element: HTMLInputElement) {
new MultiSuggest(
element,
remainingOptions,
(selected) => {
remainingOptions.delete(selected);
remainingOptions = remainingOptions;
values.update((x) => [...x, selected]);
},
app,
allowUnknownValues,
);
}
function removeValue(value: string) {
remainingOptions.add(value);
remainingOptions = remainingOptions;
values.update((xs) =>
pipe(
xs,
A.filter((x) => x !== value),
),
);
}
const { createInput, removeValue } = model;
</script>

<div class="multi-select-root">
Expand Down
114 changes: 114 additions & 0 deletions src/views/components/MultiSelectModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { A, pipe } from "@std";
import { absurd } from "fp-ts/function";
import { App } from "obsidian";
import { multiselect, inputTag } from "src/core/InputDefinitionSchema";
import { executeSandboxedDvQuery, sandboxedDvQuery } from "src/suggesters/SafeDataviewQuery";
import { StringSuggest } from "src/suggesters/StringSuggest";
import { FileSuggest } from "src/suggesters/suggestFile";
import { Writable } from "svelte/store";

export interface MultiSelectModel {
createInput(element: HTMLInputElement): void;
removeValue(value: string): void;
}

export function MultiSelectModel(
fieldInput: multiselect,
app: App,
values: Writable<string[]>,
): MultiSelectModel {
const source = fieldInput.source;
const removeValue = (value: string) =>
values.update((xs) =>
pipe(
xs,
A.filter((x) => x !== value),
),
);
switch (source) {
case "dataview":
case "fixed": {
const remainingOptions = new Set(
source === "fixed"
? fieldInput.multi_select_options
: executeSandboxedDvQuery(sandboxedDvQuery(fieldInput.query), app),
);
return {
createInput(element: HTMLInputElement) {
new StringSuggest(
element,
remainingOptions,
(selected) => {
remainingOptions.delete(selected);
values.update((x) => [...x, selected]);
},
app,
fieldInput.allowUnknownValues,
);
},
removeValue(value: string) {
remainingOptions.add(value);
removeValue(value);
},
};
}
case "notes": {
return {
createInput(element: HTMLInputElement) {
new FileSuggest(
app,
element,
{
renderSuggestion(file) {
return file.basename;
},
selectSuggestion(file) {
values.update((x) => [...x, file.basename]);
return "";
},
},
fieldInput.folder,
);
},
removeValue,
};
}
default:
return absurd(source);
}
}

export function MultiSelectTags(
fieldInput: inputTag,
app: App,
values: Writable<string[]>,
): MultiSelectModel {
const remainingOptions = new Set(
Object.keys(app.metadataCache.getTags()).map(
(tag) => tag.slice(1) /** remove the leading # */,
),
);
return {
createInput(element: HTMLInputElement) {
new StringSuggest(
element,
remainingOptions,
(selected) => {
remainingOptions.delete(selected);
values.update((x) => [...x, selected]);
},
app,
false,
);
},
removeValue(value: string) {
remainingOptions.add(value);
values.update((x) =>
pipe(
x,
A.filter((x) => x !== value),
),
);
},
};
}