mirror of
https://github.com/zadam/trilium.git
synced 2025-10-20 15:19:01 +02:00
chore(react/ribbon): port bulk actions for search
This commit is contained in:
parent
3218ab971b
commit
99a911a220
@ -18,7 +18,7 @@ import type FNote from "../entities/fnote.js";
|
||||
import toast from "./toast.js";
|
||||
import { BulkAction } from "@triliumnext/commons";
|
||||
|
||||
const ACTION_GROUPS = [
|
||||
export const ACTION_GROUPS = [
|
||||
{
|
||||
title: t("bulk_actions.labels"),
|
||||
actions: [AddLabelBulkAction, UpdateLabelValueBulkAction, RenameLabelBulkAction, DeleteLabelBulkAction]
|
||||
|
@ -13,11 +13,12 @@ export interface DropdownProps {
|
||||
dropdownContainerStyle?: CSSProperties;
|
||||
dropdownContainerClassName?: string;
|
||||
hideToggleArrow?: boolean;
|
||||
noSelectButtonStyle?: boolean;
|
||||
disabled?: boolean;
|
||||
text?: ComponentChildren;
|
||||
}
|
||||
|
||||
export default function Dropdown({ className, buttonClassName, isStatic, children, title, text, dropdownContainerStyle, dropdownContainerClassName, hideToggleArrow, disabled }: DropdownProps) {
|
||||
export default function Dropdown({ className, buttonClassName, isStatic, children, title, text, dropdownContainerStyle, dropdownContainerClassName, hideToggleArrow, disabled, noSelectButtonStyle }: DropdownProps) {
|
||||
const dropdownRef = useRef<HTMLDivElement | null>(null);
|
||||
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
||||
|
||||
@ -57,7 +58,7 @@ export default function Dropdown({ className, buttonClassName, isStatic, childre
|
||||
return (
|
||||
<div ref={dropdownRef} class={`dropdown ${className ?? ""}`} style={{ display: "flex" }}>
|
||||
<button
|
||||
className={`btn select-button ${buttonClassName ?? ""} ${!hideToggleArrow ? "dropdown-toggle" : ""}`}
|
||||
className={`btn ${!noSelectButtonStyle ? "select-button" : ""} ${buttonClassName ?? ""} ${!hideToggleArrow ? "dropdown-toggle" : ""}`}
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { VNode } from "preact";
|
||||
import { t } from "../../services/i18n";
|
||||
import Button from "../react/Button";
|
||||
import { TabContext } from "./ribbon-interface";
|
||||
@ -15,6 +14,11 @@ import server from "../../services/server";
|
||||
import ws from "../../services/ws";
|
||||
import tree from "../../services/tree";
|
||||
import { SEARCH_OPTIONS, SearchOption } from "./SearchDefinitionOptions";
|
||||
import Dropdown from "../react/Dropdown";
|
||||
import Icon from "../react/Icon";
|
||||
import bulk_action, { ACTION_GROUPS } from "../../services/bulk_action";
|
||||
import { FormListHeader, FormListItem } from "../react/FormList";
|
||||
import RenameNoteBulkAction from "../bulk_actions/note/rename_note";
|
||||
|
||||
export default function SearchDefinitionTab({ note, ntxId }: TabContext) {
|
||||
const parentComponent = useContext(ParentComponent);
|
||||
@ -77,12 +81,15 @@ export default function SearchDefinitionTab({ note, ntxId }: TabContext) {
|
||||
<td colSpan={2} className="add-search-option">
|
||||
{searchOptions?.availableOptions.map(({ icon, label, tooltip, attributeName, attributeType, defaultValue }) => (
|
||||
<Button
|
||||
size="small"
|
||||
icon={icon}
|
||||
text={label}
|
||||
title={tooltip}
|
||||
onClick={() => attributes.setAttribute(note, attributeType, attributeName, defaultValue ?? "")}
|
||||
/>
|
||||
))}
|
||||
|
||||
<AddBulkActionButton note={note} />
|
||||
</td>
|
||||
</tr>
|
||||
<tbody className="search-options">
|
||||
@ -98,9 +105,7 @@ export default function SearchDefinitionTab({ note, ntxId }: TabContext) {
|
||||
});
|
||||
})}
|
||||
</tbody>
|
||||
<tbody className="action-options">
|
||||
|
||||
</tbody>
|
||||
<BulkActionsList note={note} />
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colSpan={3}>
|
||||
@ -150,3 +155,48 @@ export default function SearchDefinitionTab({ note, ntxId }: TabContext) {
|
||||
)
|
||||
}
|
||||
|
||||
function BulkActionsList({ note }: { note: FNote }) {
|
||||
const [ bulkActions, setBulkActions ] = useState<RenameNoteBulkAction[]>();
|
||||
|
||||
function refreshBulkActions() {
|
||||
if (note) {
|
||||
setBulkActions(bulk_action.parseActions(note));
|
||||
}
|
||||
}
|
||||
|
||||
// React to changes.
|
||||
useEffect(refreshBulkActions, [ note ]);
|
||||
useTriliumEventBeta("entitiesReloaded", ({loadResults}) => {
|
||||
if (loadResults.getAttributeRows().find(attr => attr.type === "label" && attr.name === "action" && attributes.isAffecting(attr, note))) {
|
||||
refreshBulkActions();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<tbody className="action-options">
|
||||
{bulkActions?.map(bulkAction => (
|
||||
bulkAction.doRender()
|
||||
))}
|
||||
</tbody>
|
||||
)
|
||||
}
|
||||
|
||||
function AddBulkActionButton({ note }: { note: FNote }) {
|
||||
return (
|
||||
<Dropdown
|
||||
buttonClassName="action-add-toggle btn-sm"
|
||||
text={<><Icon icon="bx bxs-zap" />{" "}{t("search_definition.action")}</>}
|
||||
noSelectButtonStyle
|
||||
>
|
||||
{ACTION_GROUPS.map(({ actions, title }) => (
|
||||
<>
|
||||
<FormListHeader text={title} />
|
||||
|
||||
{actions.map(({ actionName, actionTitle }) => (
|
||||
<FormListItem onClick={() => bulk_action.addAction(note.noteId, actionName)}>{actionTitle}</FormListItem>
|
||||
))}
|
||||
</>
|
||||
))}
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import server from "../../services/server.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import ws from "../../services/ws.js";
|
||||
import toastService from "../../services/toast.js";
|
||||
import treeService from "../../services/tree.js";
|
||||
|
||||
import SearchString from "../search_options/search_string.js";
|
||||
import FastSearch from "../search_options/fast_search.js";
|
||||
import Ancestor from "../search_options/ancestor.js";
|
||||
import IncludeArchivedNotes from "../search_options/include_archived_notes.js";
|
||||
import OrderBy from "../search_options/order_by.js";
|
||||
import SearchScript from "../search_options/search_script.js";
|
||||
import Limit from "../search_options/limit.js";
|
||||
import Debug from "../search_options/debug.js";
|
||||
import appContext, { type EventData } from "../../components/app_context.js";
|
||||
import bulkActionService from "../../services/bulk_action.js";
|
||||
import { Dropdown } from "bootstrap";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type { AttributeType } from "../../entities/fattribute.js";
|
||||
import { renderReactWidget } from "../react/react_utils.jsx";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="">
|
||||
<div class="">
|
||||
<tr>
|
||||
<td>
|
||||
<div class="dropdown" style="display: inline-block;">
|
||||
<button class="btn btn-sm dropdown-toggle action-add-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="bx bxs-zap"></span>
|
||||
${t("search_definition.action")}
|
||||
</button>
|
||||
<div class="dropdown-menu action-list"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody class="search-options"></tbody>
|
||||
<tbody class="action-options"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
const OPTION_CLASSES = [SearchString, SearchScript, Ancestor, FastSearch, IncludeArchivedNotes, OrderBy, Limit, Debug];
|
||||
|
||||
export default class SearchDefinitionWidget extends NoteContextAwareWidget {
|
||||
|
||||
private $component!: JQuery<HTMLElement>;
|
||||
private $actionList!: JQuery<HTMLElement>;
|
||||
private $searchOptions!: JQuery<HTMLElement>;
|
||||
private $searchButton!: JQuery<HTMLElement>;
|
||||
private $searchAndExecuteButton!: JQuery<HTMLElement>;
|
||||
private $saveToNoteButton!: JQuery<HTMLElement>;
|
||||
private $actionOptions!: JQuery<HTMLElement>;
|
||||
|
||||
get name() {
|
||||
return "searchDefinition";
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.contentSized();
|
||||
this.$component = this.$widget.find(".search-definition-widget");
|
||||
this.$actionList = this.$widget.find(".action-list");
|
||||
|
||||
for (const actionGroup of bulkActionService.ACTION_GROUPS) {
|
||||
this.$actionList.append($('<h6 class="dropdown-header">').append(actionGroup.title));
|
||||
|
||||
for (const action of actionGroup.actions) {
|
||||
this.$actionList.append($('<a class="dropdown-item" href="#">').attr("data-action-add", action.actionName).text(action.actionTitle));
|
||||
}
|
||||
}
|
||||
|
||||
this.$widget.on("click", "[data-action-add]", async (event) => {
|
||||
Dropdown.getOrCreateInstance(this.$widget.find(".action-add-toggle")[0]);
|
||||
|
||||
const actionName = $(event.target).attr("data-action-add");
|
||||
|
||||
if (this.noteId && actionName) {
|
||||
await bulkActionService.addAction(this.noteId, actionName);
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
});
|
||||
|
||||
this.$searchOptions = this.$widget.find(".search-options");
|
||||
this.$actionOptions = this.$widget.find(".action-options");
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
if (!this.note) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actions = bulkActionService.parseActions(this.note);
|
||||
const renderedEls = actions
|
||||
.map((action) => renderReactWidget(this, action.doRender()))
|
||||
.filter((e) => e) as JQuery<HTMLElement>[];
|
||||
|
||||
this.$actionOptions.empty().append(...renderedEls);
|
||||
this.$searchAndExecuteButton.css("visibility", actions.length > 0 ? "visible" : "_hidden");
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user