From c957c970a5fc3c7cfd29bb07a3aa6c509b8d38d8 Mon Sep 17 00:00:00 2001 From: liufree Date: Wed, 30 Apr 2025 00:55:21 +0800 Subject: [PATCH 1/6] feat: support task --- src/pages/kanbanview/KanbanView.tsx | 67 +++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/src/pages/kanbanview/KanbanView.tsx b/src/pages/kanbanview/KanbanView.tsx index f107efe..9eef38d 100644 --- a/src/pages/kanbanview/KanbanView.tsx +++ b/src/pages/kanbanview/KanbanView.tsx @@ -29,13 +29,12 @@ const KanbanView: React.FC = ({app, source}) => { const parseTaskData = (value: any) => { const tasks = value.values || []; const groupedTasks: Record = { - done: [], - doing: [], todo: [], + done: [], }; tasks.forEach((task: any) => { - const status = task.status === "x" ? "done" : task.status === "?" ? "doing" : "todo"; + const status = task.status === "x" ? "done" : "todo"; groupedTasks[status].push({ ...task, type: "link", @@ -48,13 +47,15 @@ const KanbanView: React.FC = ({app, source}) => { })); }; - const executeTaskQuery = async (dvApi: any, source: any) => { - const queryResult = await dvApi.query(source); - console.log("queryResult", queryResult); - if (queryResult.successful) { - return parseTaskData(queryResult.value); - } - return []; + const executeTaskQuery = (dvApi: any, source: any) => { + dvApi.query(source).then((result: any) => { + console.log("queryResult", result); + if (result.successful) { + const data = parseTaskData(result.value); + console.log("Data:", data); // 输出 data + setColumns([...data]); + } + }); }; const onChange: (e: CheckboxChangeEvent, item: STask) => void = async (e, item) => { @@ -64,8 +65,7 @@ const KanbanView: React.FC = ({app, source}) => { const status = completed ? "x" : " "; console.log("status", status); let updatedText: string = item.text; - await rewriteTask(app.vault, item, status, updatedText) - setRefresh(true) + await rewriteTask(app.vault, item, status, updatedText); }; @@ -118,16 +118,49 @@ const KanbanView: React.FC = ({app, source}) => { let newText = filetext.join(hasRN ? "\r\n" : "\n"); console.log("newText", newText); console.log("taskPath", task.path); - await vault.adapter.write(task.path, newText); + vault.adapter.write(task.path, newText).then((res) => { + + }); } useEffect(() => { executeTaskQuery(dv, source) - .then((data) => setColumns(JSON.parse(JSON.stringify(data)))) - .catch((error) => console.error("Error executing query:", error)); }, [app, source, refresh]); + useEffect(() => { + + + const onModify = (file: TFile) => { + setTimeout(() => { + setRefresh((prev) => !prev); // 触发刷新 + }, 300); // 延迟 500 毫秒,可根据实际情况调整 + console.log("文件已修改:", file.path); + }; + + const onCreate = (file: TFile) => { + console.log("文件已创建:", file.path); + setRefresh(!refresh); // 触发刷新 + }; + + const onDelete = (file: TFile) => { + console.log("文件已删除:", file.path); + setRefresh(!refresh); // 触发刷新 + }; + + // 监听文件事件 + app.vault.on("modify", onModify); + app.vault.on("create", onCreate); + app.vault.on("delete", onDelete); + + // 清理事件监听器 + return () => { + app.vault.off("modify", onModify); + app.vault.off("create", onCreate); + app.vault.off("delete", onDelete); + }; + }, [app]); + return ( {columns.map((column) => ( @@ -136,9 +169,7 @@ const KanbanView: React.FC = ({app, source}) => { <> onChange(e, item)} checked={item.checked}> - {item.type === "link" - ? ellipsisLink(item.text, item.path) - : {item.text}} + {item.text} From a5912fc7f2d23a73a0a86cca2b766a2721aa6d22 Mon Sep 17 00:00:00 2001 From: liufree Date: Wed, 30 Apr 2025 01:39:40 +0800 Subject: [PATCH 2/6] feat: support task --- src/pages/kanbanview/EditableText.tsx | 77 +++++++++++++++++++++++++++ src/pages/kanbanview/KanbanView.tsx | 39 ++++++++++---- 2 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 src/pages/kanbanview/EditableText.tsx diff --git a/src/pages/kanbanview/EditableText.tsx b/src/pages/kanbanview/EditableText.tsx new file mode 100644 index 0000000..032117d --- /dev/null +++ b/src/pages/kanbanview/EditableText.tsx @@ -0,0 +1,77 @@ +import React, {useState} from "react"; +import {Input, Typography} from "antd"; +import {EditOutlined} from "@ant-design/icons"; +import {TFile} from "obsidian"; + +const {Text, Link} = Typography; + + +const EditableText: React.FC<{ app: any, item: any; onSave: (newText: string) => void; }> = ({app, item, onSave}) => { + const [isEditing, setIsEditing] = useState(false); + const [editableText, setEditableText] = useState(item.text); + + const handleSave = () => { + setIsEditing(false); + onSave(editableText); // 保存编辑后的值 + }; + + const getSelectionState = (item: any) => { + const selectionState = { + eState: { + cursor: { + from: {line: item.line, ch: item.position.start.col}, + to: {line: item.line + item.lineCount - 1, ch: item.position.end.col}, + }, + line: item.line, + }, + }; + return selectionState; + }; + + const ellipsisLink = (item: any, display: string, path: string) => ( + { + const selectionState = getSelectionState(item); +// MacOS interprets the Command key as Meta. + app.workspace.openLinkText( + item.link.toFile().obsidianLink(), + item.path, + false, + selectionState as any + ); + /*const file = app.vault.getAbstractFileByPath(path); + if (file && file instanceof TFile) { + const leaf = app.workspace.getLeaf(); + leaf.openFile(file); + }*/ + }} + > + + {display} + + + + ); + + return isEditing ? ( + setEditableText(e.target.value)} + onBlur={handleSave} // 失去焦点时保存 + onPressEnter={handleSave} // 按下回车时保存 + autoFocus + /> + ) : ( +
+ {ellipsisLink(item, editableText, item.path)} + setIsEditing(true)} // 点击图标进入编辑模式 + /> +
+ ); +}; + +export default EditableText; diff --git a/src/pages/kanbanview/KanbanView.tsx b/src/pages/kanbanview/KanbanView.tsx index 9eef38d..72cbfdd 100644 --- a/src/pages/kanbanview/KanbanView.tsx +++ b/src/pages/kanbanview/KanbanView.tsx @@ -1,9 +1,10 @@ import React, {useEffect, useState} from "react"; import {ProCard} from "@ant-design/pro-components"; -import {Checkbox, CheckboxChangeEvent, CheckboxProps, Typography} from "antd"; +import {Checkbox, CheckboxChangeEvent, CheckboxProps, Row, Typography} from "antd"; import {getAPI, STask} from "obsidian-dataview"; import {ViewProps} from "../../models/ViewProps"; import {TFile, Vault} from "obsidian"; +import EditableText from "./EditableText"; const {Text, Link} = Typography; @@ -22,7 +23,16 @@ const KanbanView: React.FC = ({app, source}) => { } }} > - {display} + + → + + ); @@ -58,13 +68,13 @@ const KanbanView: React.FC = ({app, source}) => { }); }; - const onChange: (e: CheckboxChangeEvent, item: STask) => void = async (e, item) => { - console.log("completed", e.target.checked); + const onChange: (checked: boolean, updateText: string, item: STask) => void = async (checked, updateText, item) => { + console.log("completed", checked); console.log("item", item); - const completed = e.target.checked; + const completed = checked; const status = completed ? "x" : " "; console.log("status", status); - let updatedText: string = item.text; + let updatedText: string = updateText; await rewriteTask(app.vault, item, status, updatedText); }; @@ -168,9 +178,20 @@ const KanbanView: React.FC = ({app, source}) => { {column.items.map((item: any, index: number) => ( <> - onChange(e, item)} checked={item.checked}> - {item.text} - + + onChange(e.target.checked, item.text, item)} + checked={item.checked} + /> + { + console.log("保存的新文本:", newText); + onChange(item.checked, newText, item); + }} + /> + ))} From 2a1324d130913f04f6f7b02270cea925ab13e0f6 Mon Sep 17 00:00:00 2001 From: liufree Date: Wed, 30 Apr 2025 01:47:53 +0800 Subject: [PATCH 3/6] feat: support task --- src/pages/kanbanview/KanbanView.tsx | 30 ++++++++++++++++------------- src/pages/kanbanview/todo | 0 2 files changed, 17 insertions(+), 13 deletions(-) delete mode 100644 src/pages/kanbanview/todo diff --git a/src/pages/kanbanview/KanbanView.tsx b/src/pages/kanbanview/KanbanView.tsx index 72cbfdd..9b7f68b 100644 --- a/src/pages/kanbanview/KanbanView.tsx +++ b/src/pages/kanbanview/KanbanView.tsx @@ -1,6 +1,6 @@ import React, {useEffect, useState} from "react"; import {ProCard} from "@ant-design/pro-components"; -import {Checkbox, CheckboxChangeEvent, CheckboxProps, Row, Typography} from "antd"; +import {Checkbox, CheckboxChangeEvent, CheckboxProps, Col, Input, Row, Typography} from "antd"; import {getAPI, STask} from "obsidian-dataview"; import {ViewProps} from "../../models/ViewProps"; import {TFile, Vault} from "obsidian"; @@ -179,18 +179,22 @@ const KanbanView: React.FC = ({app, source}) => { <> - onChange(e.target.checked, item.text, item)} - checked={item.checked} - /> - { - console.log("保存的新文本:", newText); - onChange(item.checked, newText, item); - }} - /> + + onChange(e.target.checked, item.text, item)} + checked={item.checked} + /> + + + { + console.log("保存的新文本:", newText); + onChange(item.checked, newText, item); + }} + /> + diff --git a/src/pages/kanbanview/todo b/src/pages/kanbanview/todo deleted file mode 100644 index e69de29..0000000 From da991daa6a211ada18daa87843389a494d1dc101 Mon Sep 17 00:00:00 2001 From: liufree Date: Wed, 7 May 2025 22:53:54 +0800 Subject: [PATCH 4/6] feat: support task --- manifest.json | 2 +- src/components/EditableTable.tsx | 244 ++++++++++++++++++++++++++++ src/pages/kanbanview/KanbanView.tsx | 67 ++++---- 3 files changed, 281 insertions(+), 32 deletions(-) create mode 100644 src/components/EditableTable.tsx diff --git a/manifest.json b/manifest.json index 70266ad..4aec6e9 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "querydash", "name": "QueryDash", - "version": "1.0.4", + "version": "1.0.6", "minAppVersion": "1.8.0", "description": "Refer to Dataview and add search, sorting, and pagination functions, just like Notion.", "author": "lwx", diff --git a/src/components/EditableTable.tsx b/src/components/EditableTable.tsx new file mode 100644 index 0000000..7f6d392 --- /dev/null +++ b/src/components/EditableTable.tsx @@ -0,0 +1,244 @@ +import {PlusOutlined} from '@ant-design/icons'; +import type {ActionType, ProColumns} from '@ant-design/pro-components'; +import { + EditableProTable, + ProCard, + ProFormField, +} from '@ant-design/pro-components'; +import type {InputRef} from 'antd'; +import {Button, Form, Input, Space, Tag} from 'antd'; +import React, {useRef, useState} from 'react'; + +const waitTime = (time: number = 100) => { + return new Promise((resolve) => { + setTimeout(() => { + resolve(true); + }, time); + }); +}; + +const TagList: React.FC<{ + value?: { + key: string; + label: string; + }[]; + onChange?: ( + value: { + key: string; + label: string; + }[], + ) => void; +}> = ({value, onChange}) => { + const ref = useRef(null); + const [newTags, setNewTags] = useState< + { + key: string; + label: string; + }[] + >([]); + const [inputValue, setInputValue] = useState(''); + + const handleInputChange = (e: React.ChangeEvent) => { + setInputValue(e.target.value); + }; + + const handleInputConfirm = () => { + let tempsTags = [...(value || [])]; + if ( + inputValue && + tempsTags.filter((tag) => tag.label === inputValue).length === 0 + ) { + tempsTags = [ + ...tempsTags, + {key: `new-${tempsTags.length}`, label: inputValue}, + ]; + } + onChange?.(tempsTags); + setNewTags([]); + setInputValue(''); + }; + + return ( + + {(value || []).concat(newTags).map((item) => ( + {item.label} + ))} + + + ); +}; + +type DataSourceType = { + id: React.Key; + title?: string; + labels?: { + key: string; + label: string; + }[]; + state?: string; + created_at?: number; + children?: DataSourceType[]; +}; + +const defaultData: DataSourceType[] = [ + { + id: 624748504, + title: '活动名称一', + labels: [{key: 'woman', label: '川妹子'}], + state: 'open', + created_at: 1590486176000, + }, + { + id: 624691229, + title: '活动名称二', + labels: [{key: 'man', label: '西北汉子'}], + state: 'closed', + created_at: 1590481162000, + }, +]; + +const columns: ProColumns[] = [ + { + title: '活动名称', + dataIndex: 'title', + formItemProps: { + rules: [ + { + required: true, + message: '此项为必填项', + }, + ], + }, + width: '30%', + }, + { + title: '状态', + key: 'state', + dataIndex: 'state', + valueType: 'select', + valueEnum: { + all: {text: '全部', status: 'Default'}, + open: { + text: '未解决', + status: 'Error', + }, + closed: { + text: '已解决', + status: 'Success', + }, + }, + }, + { + title: '标签', + dataIndex: 'labels', + width: '20%', + formItemProps: { + rules: [ + { + required: true, + message: '此项为必填项', + }, + ], + }, + renderFormItem: (_, {isEditable}) => { + return isEditable ? : ; + }, + render: (_, row) => + row?.labels?.map((item) => {item.label}), + }, + { + title: '操作', + valueType: 'option', + width: 250, + render: (text, record, _, action) => [ + { + action?.startEditable?.(record.id); + }} + > + 编辑 + , + + 复制此项到末尾 + , + ], + }, +]; + +export default () => { + const actionRef = useRef(); + const [editableKeys, setEditableRowKeys] = useState([]); + const [dataSource, setDataSource] = useState([]); + const [form] = Form.useForm(); + return ( + <> + + rowKey="id" + scroll={{ + x: 960, + }} + actionRef={actionRef} + headerTitle="可编辑表格" + maxLength={5} + // 关闭默认的新建按钮 + recordCreatorProps={{ + position: 'top', + record: () => ({id: (Math.random() * 1000000).toFixed(0)}), + }} + columns={columns} + request={async () => ({ + data: defaultData, + total: 3, + success: true, + })} + value={dataSource} + onChange={setDataSource} + search={{ + labelWidth: 'auto', + }} + pagination={{ + pageSize: 5, + onChange: (page) => console.log(page), + }} + editable={{ + form, + editableKeys, + onSave: async () => { + await waitTime(2000); + }, + onChange: setEditableRowKeys, + actionRender: (row, config, dom) => [dom.save, dom.cancel], + }} + /> + + + + + ); +}; diff --git a/src/pages/kanbanview/KanbanView.tsx b/src/pages/kanbanview/KanbanView.tsx index 9b7f68b..24e5706 100644 --- a/src/pages/kanbanview/KanbanView.tsx +++ b/src/pages/kanbanview/KanbanView.tsx @@ -5,6 +5,7 @@ import {getAPI, STask} from "obsidian-dataview"; import {ViewProps} from "../../models/ViewProps"; import {TFile, Vault} from "obsidian"; import EditableText from "./EditableText"; +import EditableTable from "../../components/EditableTable"; const {Text, Link} = Typography; @@ -22,7 +23,7 @@ const KanbanView: React.FC = ({app, source}) => { leaf.openFile(file); } }} - > + > = ({app, source}) => { }, [app]); return ( - - {columns.map((column) => ( - - {column.items.map((item: any, index: number) => ( - <> - - - - onChange(e.target.checked, item.text, item)} - checked={item.checked} - /> - - - { - console.log("保存的新文本:", newText); - onChange(item.checked, newText, item); - }} - /> - - - - - ))} - - ))} - + <> + + + + {columns.map((column) => ( + + {column.items.map((item: any, index: number) => ( + <> + + + + onChange(e.target.checked, item.text, item)} + checked={item.checked} + /> + + + { + console.log("保存的新文本:", newText); + onChange(item.checked, newText, item); + }} + /> + + + + + ))} + + ))} + + ); }; From bfc9beb0693df2834bf365c9d93c5fc18516209c Mon Sep 17 00:00:00 2001 From: liufree Date: Sat, 10 May 2025 00:55:37 +0800 Subject: [PATCH 5/6] feat: task --- src/components/EditableTable.tsx | 2 -- src/pages/kanbanview/KanbanView.tsx | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/EditableTable.tsx b/src/components/EditableTable.tsx index 7f6d392..ce21dad 100644 --- a/src/components/EditableTable.tsx +++ b/src/components/EditableTable.tsx @@ -182,7 +182,6 @@ const columns: ProColumns[] = [ ]; export default () => { - const actionRef = useRef(); const [editableKeys, setEditableRowKeys] = useState([]); const [dataSource, setDataSource] = useState([]); const [form] = Form.useForm(); @@ -193,7 +192,6 @@ export default () => { scroll={{ x: 960, }} - actionRef={actionRef} headerTitle="可编辑表格" maxLength={5} // 关闭默认的新建按钮 diff --git a/src/pages/kanbanview/KanbanView.tsx b/src/pages/kanbanview/KanbanView.tsx index 24e5706..e93cbdb 100644 --- a/src/pages/kanbanview/KanbanView.tsx +++ b/src/pages/kanbanview/KanbanView.tsx @@ -174,7 +174,7 @@ const KanbanView: React.FC = ({app, source}) => { return ( <> - + {/**/} {columns.map((column) => ( From 2822e4ae054c2924a49a5890b89d795752f93802 Mon Sep 17 00:00:00 2001 From: liufree Date: Sat, 10 May 2025 01:24:45 +0800 Subject: [PATCH 6/6] feat: add dark theme --- src/pages/QueryDashView.tsx | 52 +++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/src/pages/QueryDashView.tsx b/src/pages/QueryDashView.tsx index b79f93c..20b83e6 100644 --- a/src/pages/QueryDashView.tsx +++ b/src/pages/QueryDashView.tsx @@ -1,29 +1,45 @@ -import React, {useEffect} from 'react'; +import React, {useEffect, useState} from 'react'; import {App} from "obsidian"; import TableView from "./tableview/TableView"; import ListView from "./listview/ListView"; -import {ConfigProvider, ConfigProviderProps, Timeline} from "antd"; +import {ConfigProvider, theme} from "antd"; import enUS from 'antd/locale/en_US'; import TimeLineView from "./timelineview/TimeLineView"; import KanbanView from "./kanbanview/KanbanView"; - interface QueryDashViewDashs { app: App; // Obsidian app source: string; } - const QueryDashView: React.FC = ({app, source}) => { - - - const [sourceType, setSourceType] = React.useState("table"); + const [sourceType, setSourceType] = useState("table"); + const [isDarkTheme, setIsDarkTheme] = useState( + document.body.classList.contains("theme-dark") + ); useEffect(() => { - // 获取第一个单词 + // Detect Obsidian's theme + const updateTheme = () => { + const isDark = document.body.classList.contains("theme-dark"); + setIsDarkTheme(isDark); + }; + + // Initial theme detection + updateTheme(); + + // Listen for theme changes + const observer = new MutationObserver(updateTheme); + observer.observe(document.body, {attributes: true, attributeFilter: ["class"]}); + + return () => observer.disconnect(); + }, []); + + useEffect(() => { + // Get the first word of the source const sourceType = source.split(" ")[0].toLowerCase(); setSourceType(sourceType); - }, []); + }, [source]); const getView = (app: App, source: string) => { if (sourceType === "table") { @@ -35,13 +51,23 @@ const QueryDashView: React.FC = ({app, source}) => { } else if (sourceType === "task") { return ; } - } + }; return ( - + {getView(app, source)} - ) - + ); }; + export default QueryDashView;