mirror of
https://github.com/liufree/obsidian-querydash.git
synced 2026-07-22 05:41:49 +00:00
commit
21627467e2
5 changed files with 170 additions and 17 deletions
|
|
@ -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",
|
||||
|
|
|
|||
26
package.json
26
package.json
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}/>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
152
src/pages/kanbanview/KanbanView.tsx
Normal file
152
src/pages/kanbanview/KanbanView.tsx
Normal 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;
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Reference in a new issue