mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2026-04-27 15:50:57 -07:00
feat: Add workflow tiddler to workspace
This commit is contained in:
parent
abf531dc50
commit
72db085f2e
9 changed files with 101 additions and 23 deletions
|
|
@ -460,7 +460,8 @@
|
|||
"AddNewWorkflow": "Add new workflow",
|
||||
"AddNewWorkflowDescription": "Create a new automated workflow and save it to the selected workspace wiki to backup.",
|
||||
"BelongsToWorkspace": "Belongs to workspace",
|
||||
"AddNewWorkflowDoneMessage": "Add successfully"
|
||||
"AddNewWorkflowDoneMessage": "Add successfully",
|
||||
"AddTagsDescription": "press Enter to input a tag"
|
||||
},
|
||||
"Description": "Description",
|
||||
"Tags": "Tags",
|
||||
|
|
|
|||
|
|
@ -463,6 +463,7 @@
|
|||
"AddNewWorkflow": "添加新的工作流",
|
||||
"AddNewWorkflowDescription": "创建新的自动化工作流,并保存到所选的工作区Wiki里备份。",
|
||||
"AddNewWorkflowDoneMessage": "添加成功",
|
||||
"BelongsToWorkspace": "所属工作区"
|
||||
"BelongsToWorkspace": "所属工作区",
|
||||
"AddTagsDescription": "回车后变成标签的样子才算成功添加"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export enum WikiChannel {
|
|||
getTiddlerTextDone = 'wiki-get-tiddler-text-done',
|
||||
/**
|
||||
* `$tw.wiki.getTiddlersAsJson('[all[]]')`
|
||||
*
|
||||
*
|
||||
* result example:
|
||||
* ```js
|
||||
* `[
|
||||
|
|
|
|||
18
src/helpers/twUtils.ts
Normal file
18
src/helpers/twUtils.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// Convert a date into UTC YYYYMMDDHHMMSSmmm format
|
||||
export const stringifyDate = (value: Date) => {
|
||||
return value.getUTCFullYear().toString() +
|
||||
pad(value.getUTCMonth() + 1) +
|
||||
pad(value.getUTCDate()) +
|
||||
pad(value.getUTCHours()) +
|
||||
pad(value.getUTCMinutes()) +
|
||||
pad(value.getUTCSeconds()) +
|
||||
pad(value.getUTCMilliseconds(), 3);
|
||||
};
|
||||
|
||||
function pad(value: number, length = 2) {
|
||||
let s = value.toString();
|
||||
if (s.length < length) {
|
||||
s = '000000000000000000000000000'.substring(0, length - s.length) + s;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import type { IWorkflowListItem } from './WorkflowList';
|
|||
|
||||
interface AddItemDialogProps {
|
||||
availableFilterTags: string[];
|
||||
onAdd: (newItem: IWorkflowListItem) => void;
|
||||
onAdd: (newItem: IWorkflowListItem) => Promise<void>;
|
||||
onClose: () => void;
|
||||
open: boolean;
|
||||
workspacesList: IWorkspaceWithMetadata[] | undefined;
|
||||
|
|
@ -40,7 +40,7 @@ export const AddItemDialog: React.FC<AddItemDialogProps> = ({
|
|||
onClose();
|
||||
}, [onClose]);
|
||||
const workspaceIDs = useMemo(() => workspacesList?.map(workspace => workspace.id) ?? [], [workspacesList]);
|
||||
const onSubmit = useCallback(() => {
|
||||
const onSubmit = useCallback(async () => {
|
||||
const workspaceID = workspaceToSaveTo?.id ?? workspacesList?.[0]?.id;
|
||||
if (!workspaceID || !workspaceIDs.includes(workspaceID ?? '')) {
|
||||
console.error('No workspaceID found');
|
||||
|
|
@ -57,9 +57,10 @@ export const AddItemDialog: React.FC<AddItemDialogProps> = ({
|
|||
tags,
|
||||
workspaceID,
|
||||
};
|
||||
onAdd(newItem);
|
||||
await onAdd(newItem);
|
||||
setDoneMessageSnackBarOpen(true);
|
||||
}, [onAdd, tags, title, workspaceIDs, workspaceToSaveTo, workspacesList, setHasError, setDoneMessageSnackBarOpen]);
|
||||
closeAndCleanup();
|
||||
}, [workspaceToSaveTo?.id, workspacesList, workspaceIDs, title, tags, onAdd, closeAndCleanup]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -111,7 +112,7 @@ export const AddItemDialog: React.FC<AddItemDialogProps> = ({
|
|||
renderInput={(parameters) => (
|
||||
<TextField
|
||||
{...parameters}
|
||||
label={t('Tags')}
|
||||
label={`${t('Tags')} (${t('Workflow.AddTagsDescription')})`}
|
||||
margin='dense'
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ export const WorkflowManage: React.FC = () => {
|
|||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
|
||||
const workspacesList = useWorkspacesListObservable();
|
||||
const [availableFilterTags] = useAvailableFilterTags(workspacesList);
|
||||
const [workflows, onAddWorkflow] = useWorkflows(workspacesList);
|
||||
const [availableFilterTags, setTagsByWorkspace] = useAvailableFilterTags(workspacesList);
|
||||
const [workflows, onAddWorkflow] = useWorkflows(workspacesList, setTagsByWorkspace);
|
||||
|
||||
const handleOpenDialog = useCallback(() => {
|
||||
setDialogOpen(true);
|
||||
|
|
@ -30,8 +30,8 @@ export const WorkflowManage: React.FC = () => {
|
|||
const handleCloseDialog = useCallback(() => {
|
||||
setDialogOpen(false);
|
||||
}, []);
|
||||
const handleDialogAddWorkflow = useCallback((newItem: IWorkflowListItem) => {
|
||||
onAddWorkflow(newItem);
|
||||
const handleDialogAddWorkflow = useCallback(async (newItem: IWorkflowListItem) => {
|
||||
await onAddWorkflow(newItem);
|
||||
handleCloseDialog();
|
||||
}, [handleCloseDialog, onAddWorkflow]);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ import type { ITiddlerFields } from 'tiddlywiki';
|
|||
import { IWorkflowListItem } from './WorkflowList';
|
||||
|
||||
export function useAvailableFilterTags(workspacesList: IWorkspaceWithMetadata[] | undefined) {
|
||||
const tagsByWorkspace = usePromiseValue<Record<string, string[]>>(
|
||||
const [tagsByWorkspace, setTagsByWorkspace] = useState<Record<string, string[]>>({});
|
||||
const initialTagsByWorkspace = usePromiseValue<Record<string, string[]>>(
|
||||
async () => {
|
||||
const tasks = workspacesList?.map(async (workspace) => {
|
||||
try {
|
||||
|
|
@ -32,6 +33,10 @@ export function useAvailableFilterTags(workspacesList: IWorkspaceWithMetadata[]
|
|||
{},
|
||||
[workspacesList],
|
||||
)!;
|
||||
// loading TagsByWorkspace using filter expression is expensive, so we only do this on initial load. Later just update&use local state value
|
||||
useEffect(() => {
|
||||
setTagsByWorkspace(initialTagsByWorkspace);
|
||||
}, [initialTagsByWorkspace]);
|
||||
const allTagsSet = useMemo(() => {
|
||||
const allTags = new Set<string>();
|
||||
for (const tags of Object.values(tagsByWorkspace)) {
|
||||
|
|
@ -42,7 +47,7 @@ export function useAvailableFilterTags(workspacesList: IWorkspaceWithMetadata[]
|
|||
return allTags;
|
||||
}, [tagsByWorkspace]);
|
||||
const allTags = useMemo(() => [...allTagsSet], [allTagsSet]);
|
||||
return [allTags, allTagsSet, tagsByWorkspace] as const;
|
||||
return [allTags, setTagsByWorkspace, allTagsSet, tagsByWorkspace] as const;
|
||||
}
|
||||
|
||||
export interface IWorkflowTiddler extends ITiddlerFields {
|
||||
|
|
@ -97,16 +102,41 @@ export function useWorkflowFromWiki(workspacesList: IWorkspaceWithMetadata[] | u
|
|||
return workflowItems;
|
||||
}
|
||||
|
||||
export function useWorkflows(workspacesList: IWorkspaceWithMetadata[] | undefined) {
|
||||
export function useWorkflows(workspacesList: IWorkspaceWithMetadata[] | undefined, setTagsByWorkspace: React.Dispatch<React.SetStateAction<Record<string, string[]>>>) {
|
||||
const [workflows, setWorkflows] = useState<IWorkflowListItem[]>([]);
|
||||
const initialWorkflows = useWorkflowFromWiki(workspacesList);
|
||||
// loading workflows using filter expression is expensive, so we only do this on initial load. Later just update&use local state value
|
||||
useEffect(() => {
|
||||
setWorkflows(initialWorkflows);
|
||||
}, [initialWorkflows]);
|
||||
const onAddWorkflow = useCallback((newItem: IWorkflowListItem) => {
|
||||
// TODO: add workflow to wiki
|
||||
setWorkflows((workflows) => [...workflows, newItem]);
|
||||
}, []);
|
||||
const onAddWorkflow = useCallback(async (newItem: IWorkflowListItem) => {
|
||||
// add workflow to wiki
|
||||
await window.service.wiki.wikiOperation(
|
||||
WikiChannel.addTiddler,
|
||||
newItem.workspaceID,
|
||||
newItem.title,
|
||||
// only save an initial value at this creation time
|
||||
'{}',
|
||||
{
|
||||
type: 'application/json',
|
||||
tags: newItem.tags,
|
||||
description: newItem.description ?? '',
|
||||
'page-cover': newItem.image ?? '',
|
||||
} satisfies Omit<IWorkflowTiddler, 'text' | 'title'>,
|
||||
{ withDate: true },
|
||||
);
|
||||
// can overwrite a old workflow with same title
|
||||
setWorkflows((workflows) => [...workflows.filter(item => item.title !== newItem.title), newItem]);
|
||||
// update tag list in the search region tags filter
|
||||
setTagsByWorkspace((previousTagsByWorkspace) => {
|
||||
const newTags = newItem.tags.filter((tag) => !previousTagsByWorkspace[newItem.workspaceID]?.includes(tag));
|
||||
if (newTags.length === 0) return previousTagsByWorkspace;
|
||||
const previousTags = previousTagsByWorkspace[newItem.workspaceID] ?? [];
|
||||
return {
|
||||
...previousTagsByWorkspace,
|
||||
[newItem.workspaceID]: [...previousTags, ...newTags],
|
||||
};
|
||||
});
|
||||
}, [setTagsByWorkspace]);
|
||||
return [workflows, onAddWorkflow] as const;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,11 +59,32 @@ async function executeTWJavaScriptWhenIdle(script: string, options?: { onlyWhenV
|
|||
*
|
||||
* @param title tiddler title
|
||||
* @param text tiddler text
|
||||
* @param options stringifyed JSON object, is `{}` by default.
|
||||
* @param extraMeta extra meta data, is `{}` by default, a JSONStringified object
|
||||
*
|
||||
* ## options
|
||||
*
|
||||
* - withDate: boolean, whether to add `created` and `modified` field to tiddler
|
||||
*/
|
||||
ipcRenderer.on(WikiChannel.addTiddler, async (event, nonceReceived: number, title: string, text: string, extraMeta: string = '{}') => {
|
||||
ipcRenderer.on(WikiChannel.addTiddler, async (event, nonceReceived: number, title: string, text: string, extraMeta: string = '{}', optionsString: string = '{}') => {
|
||||
const options = JSON.parse(optionsString) as { withDate?: boolean };
|
||||
await executeTWJavaScriptWhenIdle(`
|
||||
$tw.wiki.addTiddler({ title: \`${title}\`, text: \`${text}\`, ...${extraMeta} });
|
||||
const dateObject = {};
|
||||
${
|
||||
options.withDate === true
|
||||
? `
|
||||
const existedTiddler = $tw.wiki.getTiddler(\`${title}\`);
|
||||
let created = existedTiddler?.fields?.created;
|
||||
const modified = $tw.utils.stringifyDate(new Date());
|
||||
if (!existedTiddler) {
|
||||
created = $tw.utils.stringifyDate(new Date());
|
||||
}
|
||||
dateObject.created = created;
|
||||
dateObject.modified = modified;
|
||||
`
|
||||
: ''
|
||||
}
|
||||
$tw.wiki.addTiddler({ title: \`${title}\`, text: \`${text}\`, ...${extraMeta}, ...dateObject });
|
||||
`);
|
||||
ipcRenderer.send(WikiChannel.addTiddler, nonceReceived);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -38,9 +38,15 @@ export const wikiOperations = {
|
|||
[WikiChannel.runFilter]: async <T extends string[]>(workspaceID: string, filterString: string): Promise<T | undefined> => {
|
||||
return await sendToMainWindowAndAwait<T>(WikiChannel.runFilter, workspaceID, [filterString]);
|
||||
},
|
||||
[WikiChannel.addTiddler]: async (workspaceID: string, title: string, text: string, meta?: unknown, options?: { timeout?: number }): Promise<void> => {
|
||||
[WikiChannel.addTiddler]: async (
|
||||
workspaceID: string,
|
||||
title: string,
|
||||
text: string,
|
||||
meta?: Record<string, unknown>,
|
||||
options?: { timeout?: number; withDate?: boolean },
|
||||
): Promise<void> => {
|
||||
const extraMeta = typeof meta === 'object' ? JSON.stringify(meta) : '{}';
|
||||
await sendToMainWindowAndAwait(WikiChannel.addTiddler, workspaceID, [title, text, extraMeta], options);
|
||||
await sendToMainWindowAndAwait(WikiChannel.addTiddler, workspaceID, [title, text, extraMeta, JSON.stringify(options ?? {})], options);
|
||||
},
|
||||
[WikiChannel.setTiddlerText]: async (workspaceID: string, title: string, value: string, options?: { timeout?: number }): Promise<void> => {
|
||||
await sendToMainWindowAndAwait(WikiChannel.setTiddlerText, workspaceID, [title, value], options);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue