Merge pull request #16 from liufree/dev

Dev
This commit is contained in:
liufree 2025-05-06 23:19:22 +08:00 committed by GitHub
commit 21627467e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 170 additions and 17 deletions

View file

@ -1,7 +1,7 @@
{
"id": "querydash",
"name": "QueryDash",
"version": "1.0.4",
"version": "1.0.5",
"minAppVersion": "1.8.0",
"description": "Refer to Dataview and add search, sorting, and pagination functions, just like Notion.",
"author": "lwx",

View file

@ -11,25 +11,25 @@
"author": "",
"license": "MIT",
"dependencies": {
"@ant-design/icons": "^5.0.1",
"@ant-design/icons": "^6.0.0",
"@ant-design/pro-components": "^2.8.7",
"umi-request": "^1.4.0"
},
"devDependencies": {
"@types/luxon": "^3.4.2",
"@types/node": "^22.13.8",
"@types/react": "^18.0.33",
"@types/react-dom": "^18.0.11",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"@vitejs/plugin-react": "^4.3.4",
"builtin-modules": "3.3.0",
"@types/luxon": "^3.6.2",
"@types/node": "^22.15.2",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"@typescript-eslint/eslint-plugin": "8.31.0",
"@typescript-eslint/parser": "8.31.0",
"@vitejs/plugin-react": "^4.4.1",
"builtin-modules": "5.0.0",
"husky": "^9",
"lint-staged": "^13.2.0",
"lint-staged": "^15.5.1",
"obsidian": "latest",
"obsidian-dataview": "^0.5.67",
"tslib": "2.4.0",
"vite": "^6.2.6",
"obsidian-dataview": "^0.5.68",
"tslib": "2.8.1",
"vite": "^6.3.3",
"vite-plugin-css-injected-by-js": "^3.5.2"
}
}

View file

@ -5,6 +5,7 @@ import ListView from "./listview/ListView";
import {ConfigProvider, ConfigProviderProps, Timeline} from "antd";
import enUS from 'antd/locale/en_US';
import TimeLineView from "./timelineview/TimeLineView";
import KanbanView from "./kanbanview/KanbanView";
interface QueryDashViewDashs {
@ -31,6 +32,8 @@ const QueryDashView: React.FC<QueryDashViewDashs> = ({app, source}) => {
return <ListView app={app} source={source}/>;
} else if (sourceType === "timeline") {
return <TimeLineView app={app} source={source}/>;
} else if (sourceType === "task") {
return <KanbanView app={app} source={source}/>;
}
}

View file

@ -0,0 +1,152 @@
import React, {useEffect, useState} from "react";
import {ProCard} from "@ant-design/pro-components";
import {Checkbox, CheckboxChangeEvent, CheckboxProps, Typography} from "antd";
import {getAPI, STask} from "obsidian-dataview";
import {ViewProps} from "../../models/ViewProps";
import {TFile, Vault} from "obsidian";
const {Text, Link} = Typography;
const KanbanView: React.FC<ViewProps> = ({app, source}) => {
const [columns, setColumns] = useState<any[]>([]);
const [refresh, setRefresh] = useState(false);
const dv = getAPI(app);
const ellipsisLink = (display: string, path: string) => (
<Link
onClick={() => {
const file = app.vault.getAbstractFileByPath(path);
if (file && file instanceof TFile) {
const leaf = app.workspace.getLeaf();
leaf.openFile(file);
}
}}
>
<Text ellipsis>{display}</Text>
</Link>
);
const parseTaskData = (value: any) => {
const tasks = value.values || [];
const groupedTasks: Record<string, any[]> = {
done: [],
doing: [],
todo: [],
};
tasks.forEach((task: any) => {
const status = task.status === "x" ? "done" : task.status === "?" ? "doing" : "todo";
groupedTasks[status].push({
...task,
type: "link",
});
});
return Object.keys(groupedTasks).map((status) => ({
title: status.toUpperCase(),
items: groupedTasks[status],
}));
};
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 onChange: (e: CheckboxChangeEvent, item: STask) => void = async (e, item) => {
console.log("completed", e.target.checked);
console.log("item", item);
const completed = e.target.checked;
const status = completed ? "x" : " ";
console.log("status", status);
let updatedText: string = item.text;
await rewriteTask(app.vault, item, status, updatedText)
setRefresh(true)
};
const LIST_ITEM_REGEX = /^[\s>]*(\d+\.|\d+\)|\*|-|\+)\s*(\[.{0,1}\])?\s*(.*)$/mu;
/** Rewrite a task with the given completion status and new text. */
async function rewriteTask(vault: Vault, task: STask, desiredStatus: string, desiredText?: string) {
if (desiredStatus == task.status && (desiredText == undefined || desiredText == task.text)) return;
desiredStatus = desiredStatus == "" ? " " : desiredStatus;
let rawFiletext = await vault.adapter.read(task.path);
console.log("rawFiletext", rawFiletext);
let hasRN = rawFiletext.contains("\r");
let filetext = rawFiletext.split(/\r?\n/u);
console.log("filetext", filetext);
if (filetext.length < task.line) {
console.log("line", filetext);
return;
}
let match = LIST_ITEM_REGEX.exec(filetext[task.line]);
if (!match || match[2].length == 0) {
console.log("match", match);
return;
}
let taskTextParts = task.text.split("\n");
if (taskTextParts[0].trim() != match[3].trim()) {
console.log("taskTextParts", taskTextParts);
return;
}
// We have a positive match here at this point, so go ahead and do the rewrite of the status.
let initialSpacing = /^[\s>]*/u.exec(filetext[task.line])!![0];
if (desiredText) {
let desiredParts = desiredText.split("\n");
let newTextLines: string[] = [`${initialSpacing}${task.symbol} [${desiredStatus}] ${desiredParts[0]}`].concat(
desiredParts.slice(1).map(l => initialSpacing + "\t" + l)
);
filetext.splice(task.line, task.lineCount, ...newTextLines);
} else {
filetext[task.line] = `${initialSpacing}${task.symbol} [${desiredStatus}] ${taskTextParts[0].trim()}`;
}
let newText = filetext.join(hasRN ? "\r\n" : "\n");
console.log("newText", newText);
console.log("taskPath", task.path);
await vault.adapter.write(task.path, newText);
}
useEffect(() => {
executeTaskQuery(dv, source)
.then((data) => setColumns(JSON.parse(JSON.stringify(data))))
.catch((error) => console.error("Error executing query:", error));
}, [app, source, refresh]);
return (
<ProCard ghost gutter={8}>
{columns.map((column) => (
<ProCard key={column.title} title={column.title} bordered>
{column.items.map((item: any, index: number) => (
<>
<ProCard key={index} bordered>
<Checkbox onChange={(e) => onChange(e, item)} checked={item.checked}>
{item.type === "link"
? ellipsisLink(item.text, item.path)
: <Text>{item.text}</Text>}
</Checkbox>
</ProCard>
</>
))}
</ProCard>
))}
</ProCard>
);
};
export default KanbanView;

View file

@ -10,7 +10,6 @@ import {ViewProps} from "../../models/ViewProps";
const TableView: React.FC<ViewProps> = ({app, source}) => {
const actionRef = useRef<ActionType>();
const [columns, setColumns] = React.useState<ProColumns<any>[]>([]);
const dv = getAPI(app);
@ -61,7 +60,7 @@ const TableView: React.FC<ViewProps> = ({app, source}) => {
return {
title: header,
dataIndex: header,
sorter: (a, b) => a[header].display.toString().localeCompare(b[header].display.toString()),
sorter: (a, b) => a[header]?.display?.toString()?.localeCompare(b[header]?.display?.toString()),
render: (_, record) => {
const {type, path, display} = record[header] || {};
if (type === "datetime") {
@ -155,7 +154,6 @@ const TableView: React.FC<ViewProps> = ({app, source}) => {
return (
<ProTable
scroll={{x: 'max-content'}}
actionRef={actionRef}
cardBordered
editable={{
type: 'multiple',