mirror of
https://github.com/zadam/trilium.git
synced 2025-11-06 21:36:05 +01:00
feat(react/bulk_actions): port add_label
This commit is contained in:
@@ -1,7 +1,41 @@
|
|||||||
|
import { ComponentChildren } from "preact";
|
||||||
|
|
||||||
interface BulkActionProps {
|
interface BulkActionProps {
|
||||||
|
label: string;
|
||||||
|
children: ComponentChildren;
|
||||||
|
helpText?: ComponentChildren;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BulkAction() {
|
export default function BulkAction({ label, children, helpText }: BulkActionProps) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={2}>
|
||||||
|
<div style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<div style={{ marginRight: "10px" }} className="text-nowrap">{label}</div>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="button-column">
|
||||||
|
{helpText && <div className="dropdown help-dropdown">
|
||||||
|
<span className="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||||
|
<div className="dropdown-menu dropdown-menu-right p-4">
|
||||||
|
{helpText}
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
|
||||||
|
<span className="bx bx-x icon-action action-conf-del"></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BulkActionText({ text }: { text: string }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{ marginRight: "10px", marginLeft: "10px" }}
|
||||||
|
className="text-nowrap">
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,9 @@ import server from "../../services/server.js";
|
|||||||
import ws from "../../services/ws.js";
|
import ws from "../../services/ws.js";
|
||||||
import utils from "../../services/utils.js";
|
import utils from "../../services/utils.js";
|
||||||
import type FAttribute from "../../entities/fattribute.js";
|
import type FAttribute from "../../entities/fattribute.js";
|
||||||
|
import { VNode } from "preact";
|
||||||
|
|
||||||
interface ActionDefinition {
|
export interface ActionDefinition {
|
||||||
script: string;
|
script: string;
|
||||||
relationName: string;
|
relationName: string;
|
||||||
targetNoteId: string;
|
targetNoteId: string;
|
||||||
@@ -30,15 +31,18 @@ export default abstract class AbstractBulkAction {
|
|||||||
render() {
|
render() {
|
||||||
try {
|
try {
|
||||||
const $rendered = this.doRender();
|
const $rendered = this.doRender();
|
||||||
|
if (Array.isArray($rendered)) {
|
||||||
|
$rendered
|
||||||
|
.find(".action-conf-del")
|
||||||
|
.on("click", () => this.deleteAction())
|
||||||
|
.attr("title", t("abstract_bulk_action.remove_this_search_action"));
|
||||||
|
|
||||||
$rendered
|
utils.initHelpDropdown($rendered);
|
||||||
.find(".action-conf-del")
|
|
||||||
.on("click", () => this.deleteAction())
|
|
||||||
.attr("title", t("abstract_bulk_action.remove_this_search_action"));
|
|
||||||
|
|
||||||
utils.initHelpDropdown($rendered);
|
return $rendered;
|
||||||
|
} else {
|
||||||
return $rendered;
|
return $rendered;
|
||||||
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logError(`Failed rendering search action: ${JSON.stringify(this.attribute.dto)} with error: ${e.message} ${e.stack}`);
|
logError(`Failed rendering search action: ${JSON.stringify(this.attribute.dto)} with error: ${e.message} ${e.stack}`);
|
||||||
return null;
|
return null;
|
||||||
@@ -46,7 +50,7 @@ export default abstract class AbstractBulkAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// to be overridden
|
// to be overridden
|
||||||
abstract doRender(): JQuery<HTMLElement>;
|
abstract doRender(): JQuery<HTMLElement> | VNode;
|
||||||
static get actionName() {
|
static get actionName() {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
import { t } from "../../../services/i18n.js";
|
|
||||||
import SpacedUpdate from "../../../services/spaced_update.js";
|
|
||||||
import AbstractBulkAction from "../abstract_bulk_action.js";
|
|
||||||
|
|
||||||
const TPL = /*html*/`
|
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<div style="display: flex; align-items: center">
|
|
||||||
<div style="margin-right: 10px;" class="text-nowrap">${t("add_label.add_label")}</div>
|
|
||||||
|
|
||||||
<input type="text"
|
|
||||||
class="form-control label-name"
|
|
||||||
placeholder="${t("add_label.label_name_placeholder")}"
|
|
||||||
pattern="[\\p{L}\\p{N}_:]+"
|
|
||||||
title="${t("add_label.label_name_title")}"/>
|
|
||||||
|
|
||||||
<div style="margin-right: 10px; margin-left: 10px;" class="text-nowrap">${t("add_label.to_value")}</div>
|
|
||||||
|
|
||||||
<input type="text" class="form-control label-value" placeholder="${t("add_label.new_value_placeholder")}"/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="button-column">
|
|
||||||
<div class="dropdown help-dropdown">
|
|
||||||
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
|
||||||
<div class="dropdown-menu dropdown-menu-right p-4">
|
|
||||||
<p>${t("add_label.help_text")}</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>${t("add_label.help_text_item1")}</li>
|
|
||||||
<li>${t("add_label.help_text_item2")}</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
${t("add_label.help_text_note")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span class="bx bx-x icon-action action-conf-del"></span>
|
|
||||||
</td>
|
|
||||||
</tr>`;
|
|
||||||
|
|
||||||
export default class AddLabelBulkAction extends AbstractBulkAction {
|
|
||||||
static get actionName() {
|
|
||||||
return "addLabel";
|
|
||||||
}
|
|
||||||
static get actionTitle() {
|
|
||||||
return t("add_label.add_label");
|
|
||||||
}
|
|
||||||
|
|
||||||
doRender() {
|
|
||||||
const $action = $(TPL);
|
|
||||||
|
|
||||||
const $labelName = $action.find(".label-name");
|
|
||||||
$labelName.val(this.actionDef.labelName || "");
|
|
||||||
|
|
||||||
const $labelValue = $action.find(".label-value");
|
|
||||||
$labelValue.val(this.actionDef.labelValue || "");
|
|
||||||
|
|
||||||
const spacedUpdate = new SpacedUpdate(async () => {
|
|
||||||
await this.saveAction({
|
|
||||||
labelName: $labelName.val(),
|
|
||||||
labelValue: $labelValue.val()
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
$labelName.on("input", () => spacedUpdate.scheduleUpdate());
|
|
||||||
$labelValue.on("input", () => spacedUpdate.scheduleUpdate());
|
|
||||||
|
|
||||||
return $action;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
56
apps/client/src/widgets/bulk_actions/label/add_label.tsx
Normal file
56
apps/client/src/widgets/bulk_actions/label/add_label.tsx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
import { t } from "../../../services/i18n";
|
||||||
|
import FormTextBox from "../../react/FormTextBox";
|
||||||
|
import AbstractBulkAction, { ActionDefinition } from "../abstract_bulk_action";
|
||||||
|
import BulkAction, { BulkActionText } from "../BulkAction";
|
||||||
|
import { useSpacedUpdate } from "../../react/hooks";
|
||||||
|
|
||||||
|
function AddLabelBulkActionComponent({ bulkAction, actionDef }: { bulkAction: AbstractBulkAction, actionDef: ActionDefinition }) {
|
||||||
|
const [ labelName, setLabelName ] = useState<string>(actionDef.labelName ?? "");
|
||||||
|
const [ labelValue, setLabelValue ] = useState<string>(actionDef.labelValue ?? "");
|
||||||
|
const spacedUpdate = useSpacedUpdate(() => bulkAction.saveAction({ labelName, labelValue }));
|
||||||
|
useEffect(() => spacedUpdate.scheduleUpdate(), [labelName, labelValue]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BulkAction
|
||||||
|
label={t("add_label.add_label")}
|
||||||
|
helpText={<>
|
||||||
|
<p>{t("add_label.help_text")}</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>{t("add_label.help_text_item1")}</li>
|
||||||
|
<li>{t("add_label.help_text_item2")}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{t("add_label.help_text_note")}
|
||||||
|
</>}
|
||||||
|
>
|
||||||
|
<FormTextBox
|
||||||
|
placeholder={t("add_label.label_name_placeholder")}
|
||||||
|
pattern="[\\p{L}\\p{N}_:]+"
|
||||||
|
title={t("add_label.label_name_title")}
|
||||||
|
currentValue={labelName} onChange={setLabelName}
|
||||||
|
/>
|
||||||
|
<BulkActionText text={t("add_label.to_value")} />
|
||||||
|
<FormTextBox
|
||||||
|
placeholder={t("add_label.new_value_placeholder")}
|
||||||
|
currentValue={labelValue} onChange={setLabelValue}
|
||||||
|
/>
|
||||||
|
</BulkAction>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class AddLabelBulkAction extends AbstractBulkAction {
|
||||||
|
|
||||||
|
doRender() {
|
||||||
|
return <AddLabelBulkActionComponent bulkAction={this} actionDef={this.actionDef} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get actionName() {
|
||||||
|
return "addLabel";
|
||||||
|
}
|
||||||
|
|
||||||
|
static get actionTitle() {
|
||||||
|
return t("add_label.add_label");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -104,16 +104,7 @@ function ExistingActionsList({ existingActions }: { existingActions?: RenameNote
|
|||||||
<table class="bulk-existing-action-list">
|
<table class="bulk-existing-action-list">
|
||||||
{ existingActions
|
{ existingActions
|
||||||
? existingActions
|
? existingActions
|
||||||
.map(action => {
|
.map(action => action.render())
|
||||||
const renderedAction = action.render();
|
|
||||||
if (renderedAction) {
|
|
||||||
return <RawHtmlBlock
|
|
||||||
html={renderedAction[0].innerHTML}
|
|
||||||
style={{ display: "flex", alignItems: "center" }} />
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(renderedAction => renderedAction !== null)
|
.filter(renderedAction => renderedAction !== null)
|
||||||
: <p>{t("bulk_actions.none_yet")}</p>
|
: <p>{t("bulk_actions.none_yet")}</p>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
import { HTMLInputTypeAttribute, RefObject } from "preact/compat";
|
import { InputHTMLAttributes, RefObject } from "preact/compat";
|
||||||
|
|
||||||
interface FormTextBoxProps {
|
interface FormTextBoxProps extends Pick<InputHTMLAttributes<HTMLInputElement>, "placeholder" | "autoComplete" | "className" | "type" | "name" | "pattern" | "title"> {
|
||||||
id?: string;
|
id?: string;
|
||||||
name: string;
|
|
||||||
type?: HTMLInputTypeAttribute;
|
|
||||||
currentValue?: string;
|
currentValue?: string;
|
||||||
className?: string;
|
|
||||||
autoComplete?: string;
|
|
||||||
onChange?(newValue: string): void;
|
onChange?(newValue: string): void;
|
||||||
inputRef?: RefObject<HTMLInputElement>;
|
inputRef?: RefObject<HTMLInputElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FormTextBox({ id, type, name, className, currentValue, onChange, autoComplete, inputRef }: FormTextBoxProps) {
|
export default function FormTextBox({ id, type, name, className, currentValue, onChange, autoComplete, inputRef, placeholder, title, pattern }: FormTextBoxProps) {
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
@@ -21,6 +17,9 @@ export default function FormTextBox({ id, type, name, className, currentValue, o
|
|||||||
name={name}
|
name={name}
|
||||||
value={currentValue}
|
value={currentValue}
|
||||||
autoComplete={autoComplete}
|
autoComplete={autoComplete}
|
||||||
|
placeholder={placeholder}
|
||||||
|
title={title}
|
||||||
|
pattern={pattern}
|
||||||
onInput={e => onChange?.(e.currentTarget.value)} />
|
onInput={e => onChange?.(e.currentTarget.value)} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useContext, useEffect, useState } from "preact/hooks";
|
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
|
||||||
import { EventData, EventNames } from "../../components/app_context";
|
import { EventData, EventNames } from "../../components/app_context";
|
||||||
import { ParentComponent } from "./ReactBasicWidget";
|
import { ParentComponent } from "./ReactBasicWidget";
|
||||||
|
import SpacedUpdate from "../../services/spaced_update";
|
||||||
|
|
||||||
export default function useTriliumEvent<T extends EventNames>(eventName: T, handler: (data: EventData<T>) => void) {
|
export default function useTriliumEvent<T extends EventNames>(eventName: T, handler: (data: EventData<T>) => void) {
|
||||||
const parentWidget = useContext(ParentComponent);
|
const parentWidget = useContext(ParentComponent);
|
||||||
@@ -29,4 +30,12 @@ export default function useTriliumEvent<T extends EventNames>(eventName: T, hand
|
|||||||
parentWidget[handlerName] = originalHandler;
|
parentWidget[handlerName] = originalHandler;
|
||||||
};
|
};
|
||||||
}, [parentWidget]);
|
}, [parentWidget]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSpacedUpdate(callback: () => Promise<void>, interval = 1000) {
|
||||||
|
const spacedUpdate = useMemo(() => {
|
||||||
|
return new SpacedUpdate(callback, interval);
|
||||||
|
}, [callback, interval]);
|
||||||
|
|
||||||
|
return spacedUpdate;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user