mirror of
https://github.com/liufree/obsidian-querydash.git
synced 2026-07-22 12:20:26 +00:00
commit
d3daeac12f
8 changed files with 336 additions and 24 deletions
12
README-zh.md
12
README-zh.md
|
|
@ -1,5 +1,7 @@
|
|||
**Obsidian Bases Table View Support**
|
||||
|
||||
|
||||
QueryDash now supports views powered by the Obsidian Bases plugin. Thanks to the open API provided by Bases, users can
|
||||
seamlessly display and interact with Bases data in a flexible format.
|
||||
|
||||
**项目目标**
|
||||
QueryDash的目标是开发一款类似Notion Database的Obsidian插件,但不仅限于数据库功能。
|
||||
|
|
@ -19,8 +21,14 @@ QueryDash的目标是开发一款类似Notion Database的Obsidian插件,但不
|
|||
- **分页**:优化大数据量下的浏览体验。
|
||||
- **任务**:支持任务管理,包括任务编辑。
|
||||
|
||||
**使用教程**
|
||||
# **使用教程**
|
||||
|
||||
## bases
|
||||
|
||||
|
||||

|
||||
|
||||
## dataview
|
||||
|
||||
~~~markdown
|
||||
```querydash
|
||||
|
|
|
|||
43
README.md
43
README.md
|
|
@ -1,15 +1,24 @@
|
|||
**Obsidian Bases Table View Support**
|
||||
|
||||
QueryDash now supports views powered by the Obsidian Bases plugin. Thanks to the open API provided by Bases, users can
|
||||
seamlessly display and interact with Bases data in a flexible format.
|
||||
|
||||
**Project Objective**
|
||||
The goal of QueryDash is to develop an Obsidian plugin similar to Notion Database, but not limited to database functionality.
|
||||
The goal of QueryDash is to develop an Obsidian plugin similar to Notion Database, but not limited to database
|
||||
functionality.
|
||||
**Since the API of Dataview is used, the Dataview plugin must be enabled.**
|
||||
In the future, it will gradually expand with more practical features to help users manage knowledge, tasks, and life more efficiently.
|
||||
In the future, it will gradually expand with more practical features to help users manage knowledge, tasks, and life
|
||||
more efficiently.
|
||||
|
||||
**If you are interested in this project, please consider starring it. Thank you very much, it means a lot to me.**
|
||||
|
||||
**language**
|
||||
|
||||
- [English](README.md)
|
||||
- [简体中文](README-zh.md)
|
||||
|
||||
**Current Features**
|
||||
|
||||
1. **Multi-view Support**: Provides table and list views to meet different scenario needs.
|
||||
- **timeline**: use timeline view to display data.
|
||||
2. **Dataview SQL Support**: Compatible with Dataview's SQL syntax .
|
||||
|
|
@ -18,11 +27,19 @@ In the future, it will gradually expand with more practical features to help use
|
|||
- **Filter**: Screen data based on conditions.
|
||||
- **Aggregation**: Supports data summary statistics.
|
||||
- **Pagination**: Optimizes browsing experience for large datasets.
|
||||
- **Task**: Supports task management, including task editing.
|
||||
- **Task**: Supports task management, including task editing.
|
||||
|
||||
**Tutorial**
|
||||
# **Tutorial**
|
||||
|
||||
## bases
|
||||
|
||||
|
||||

|
||||
|
||||
## dataview
|
||||
|
||||
**table**
|
||||
|
||||
~~~markdown
|
||||
```querydash
|
||||
table file.name , file.outlinks as "links" ,file.ctime as "ctime",
|
||||
|
|
@ -33,6 +50,7 @@ file.mtime as "mtime" ,file.tags as "tags" from #clippings
|
|||

|
||||
|
||||
**task**
|
||||
|
||||
~~~markdown
|
||||
```querydash
|
||||
task
|
||||
|
|
@ -44,15 +62,18 @@ task
|
|||
**timeline**
|
||||
|
||||
**simple mode**
|
||||
|
||||
~~~markdown
|
||||
```querydash
|
||||
timeline from #start
|
||||
```
|
||||
~~~
|
||||
|
||||

|
||||
**full mode**
|
||||
|
||||
If you want to append the time later, alias a specific field as "time".
|
||||
|
||||
~~~markdown
|
||||
```querydash
|
||||
timeline file.ctime as "time" from #start where file.ctime<=date(today) sort file.mtime desc
|
||||
|
|
@ -60,6 +81,7 @@ timeline file.ctime as "time" from #start where file.ctime<=date(today) sort fil
|
|||
~~~
|
||||
|
||||
**Future Vision**
|
||||
|
||||
1. **Homepage**: Integrate frequently used functions into the homepage for one-stop convenience.
|
||||
2. **Multi-view Support**: Supports timeline, gallery, table, and other views to meet diverse scenario needs.
|
||||
3. **Review Cards**: Built-in review function to help users efficiently consolidate knowledge.
|
||||
|
|
@ -67,18 +89,25 @@ timeline file.ctime as "time" from #start where file.ctime<=date(today) sort fil
|
|||
5. **More Features**: Continuous development to enrich plugin functionality and enhance user experience.
|
||||
|
||||
**Reference Projects**
|
||||
|
||||
- [Dataview](https://github.com/blacksmithgu/obsidian-dataview): A powerful data query and management tool.
|
||||
- [Project](https://github.com/marcusolsson/obsidian-projects): A task and project management plugin.
|
||||
|
||||
**Project Introduction**
|
||||
As a deep user of Obsidian, I found that existing plugins could not fully meet my needs in terms of search, review, and editing/deleting Dataview tables. Therefore, I decided to develop a brand-new view system based on Ant Design, aiming to enhance the Obsidian experience and better assist daily life and work.
|
||||
As a deep user of Obsidian, I found that existing plugins could not fully meet my needs in terms of search, review, and
|
||||
editing/deleting Dataview tables. Therefore, I decided to develop a brand-new view system based on Ant Design, aiming to
|
||||
enhance the Obsidian experience and better assist daily life and work.
|
||||
|
||||
Currently, the project is in its early stages, but I will continue to invest in development to gradually improve and expand its features. I believe that through continuous iteration, this project will become increasingly practical and powerful.
|
||||
Currently, the project is in its early stages, but I will continue to invest in development to gradually improve and
|
||||
expand its features. I believe that through continuous iteration, this project will become increasingly practical and
|
||||
powerful.
|
||||
|
||||
**About Sponsorship**
|
||||
This project is entirely developed out of personal interest. If you find it helpful and are willing to support my work, every sponsorship will be a motivation for me to continue developing. Thank you very much!
|
||||
This project is entirely developed out of personal interest. If you find it helpful and are willing to support my work,
|
||||
every sponsorship will be a motivation for me to continue developing. Thank you very much!
|
||||
|
||||
**Future Plans**
|
||||
|
||||
- Improve existing features to enhance user experience.
|
||||
- Add more practical features, such as advanced search and intelligent review.
|
||||
- Optimize performance to ensure system stability.
|
||||
|
|
|
|||
BIN
docs/DashTableView.png
Normal file
BIN
docs/DashTableView.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 138 KiB |
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"id": "querydash",
|
||||
"name": "QueryDash",
|
||||
"version": "1.1.1",
|
||||
"version": "2.0.0",
|
||||
"minAppVersion": "1.8.0",
|
||||
"description": "Refer to Dataview and add search, sorting, and pagination functions, just like Notion.",
|
||||
"description": "The new view for bases refers to an updated or additional interface that allows users to interact with base data in a different way, such as through enhanced search, sorting, and pagination features, similar to Notion. This improves usability and data management within your application. It was originally a new view for dataview, but now its functionality has been extended to bases.",
|
||||
"author": "lwx",
|
||||
"authorUrl": "https://github.com/liufree",
|
||||
"isDesktopOnly": false
|
||||
|
|
|
|||
24
package.json
24
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-querydash",
|
||||
"description": "Refer to Dataview and add search, sorting, and pagination functions, just like Notion.",
|
||||
"description": "The new view for bases refers to an updated or additional interface that allows users to interact with base data in a different way, such as through enhanced search, sorting, and pagination features, similar to Notion. This improves usability and data management within your application. It was originally a new view for dataview, but now its functionality has been extended to bases.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "vite build --watch --mode development",
|
||||
|
|
@ -11,25 +11,25 @@
|
|||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^6.0.0",
|
||||
"@ant-design/pro-components": "^2.8.7",
|
||||
"@ant-design/icons": "^6.1.0",
|
||||
"@ant-design/pro-components": "^2.8.10",
|
||||
"umi-request": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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",
|
||||
"@types/luxon": "^3.7.1",
|
||||
"@types/node": "^24.7.2",
|
||||
"@types/react": "^18.3.7",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.0",
|
||||
"@typescript-eslint/parser": "8.46.0",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"builtin-modules": "5.0.0",
|
||||
"husky": "^9",
|
||||
"lint-staged": "^15.5.1",
|
||||
"lint-staged": "^16.2.4",
|
||||
"obsidian": "latest",
|
||||
"obsidian-dataview": "^0.5.68",
|
||||
"tslib": "2.8.1",
|
||||
"vite": "^6.3.3",
|
||||
"vite": "7.1.9",
|
||||
"vite-plugin-css-injected-by-js": "^3.5.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import {Plugin} from 'obsidian';
|
||||
import ReactComponent from "./pages/ReactComponent";
|
||||
import {Root} from 'react-dom/client';
|
||||
import {DashTableView, TableViewType} from "./pages/bases/DashTableView";
|
||||
|
||||
|
||||
export default class QueryDashPlugin extends Plugin {
|
||||
|
|
@ -14,6 +15,14 @@ export default class QueryDashPlugin extends Plugin {
|
|||
const reactComponent = new ReactComponent(container, source, this.app);
|
||||
ctx.addChild(reactComponent);
|
||||
});
|
||||
|
||||
this.registerBasesView(TableViewType, {
|
||||
name: 'DashTableView',
|
||||
icon: 'lucide-graduation-cap',
|
||||
factory: (controller, containerEl) =>
|
||||
new DashTableView(controller, containerEl)
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
onunload() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import {App} from "obsidian";
|
||||
|
||||
export interface ViewProps {
|
||||
app: App;
|
||||
app: App ;
|
||||
source: string;
|
||||
}
|
||||
|
|
|
|||
266
src/pages/bases/DashTableView.tsx
Normal file
266
src/pages/bases/DashTableView.tsx
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
import {BasesPropertyId, BasesView, MarkdownRenderer, parsePropertyId, QueryController} from 'obsidian';
|
||||
import {createRoot} from "react-dom/client";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {ViewProps} from "../../models/ViewProps";
|
||||
import {ProColumns, ProTable} from "@ant-design/pro-components";
|
||||
import {ellipsisDisplay, ellipsisLink, externalLink} from "../GenerateColumns";
|
||||
import {ConfigProvider, List, Tag, theme} from "antd";
|
||||
import enUS from "antd/locale/en_US";
|
||||
|
||||
export const TableViewType = 'dash-table-view';
|
||||
|
||||
export class DashTableView extends BasesView {
|
||||
readonly type = TableViewType;
|
||||
private containerEl: HTMLElement;
|
||||
|
||||
|
||||
constructor(controller: QueryController, parentEl: HTMLElement) {
|
||||
super(controller);
|
||||
this.containerEl = parentEl.createDiv('bases-example-view-container');
|
||||
}
|
||||
|
||||
// 获取链接的显示文本(别名)
|
||||
getLinkDisplayName(linkText: string): string {
|
||||
const linkMatch = linkText.match(/\[\[([^|\]]+)(?:\|([^\]]+))?\]\]/);
|
||||
if (!linkMatch) return linkText;
|
||||
|
||||
return linkMatch[2] || linkMatch[1]; // 返回别名或文件名
|
||||
}
|
||||
|
||||
parseWikilink(linktext: string): string | null {
|
||||
// 匹配 [[文件名|别名]] 或 [[文件名]]
|
||||
const match = linktext.match(/\[\[([^|\]]+)(?:\|([^\]]+))?\]\]/);
|
||||
if (match) {
|
||||
return match[1]; // 返回 "ss"
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
createColumns = (app: any, properties: string[]) => {
|
||||
let columns = [];
|
||||
const firstColumn: ProColumns<any> = {
|
||||
dataIndex: 'index',
|
||||
valueType: 'indexBorder',
|
||||
title: 'index',
|
||||
sorter: true,
|
||||
}
|
||||
columns.push(firstColumn);
|
||||
const dynamicColumns: ProColumns<any>[] = properties.map((property) => {
|
||||
return {
|
||||
title: property,
|
||||
dataIndex: property,
|
||||
sorter: (a, b) => {
|
||||
return a[property]?.display?.localeCompare(b[property]?.display);
|
||||
},
|
||||
render: (_, record) => {
|
||||
const data = record[property];
|
||||
|
||||
const filePath = record["file.path"];
|
||||
|
||||
const {type, name, display} = data || {};
|
||||
|
||||
if (display == 'null') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type == 'file' && name == 'name') {
|
||||
return ellipsisLink(app, display, filePath);
|
||||
}
|
||||
|
||||
if (type == 'file' && (name == 'links' || name == 'backlinks')) {
|
||||
const linkTexts = display.split(',');
|
||||
return linkTexts.map((linkText: string) => {
|
||||
const path = this.parseWikilink(linkText) + ".md";
|
||||
const displayName = this.getLinkDisplayName(linkText);
|
||||
|
||||
return <List.Item>
|
||||
{ellipsisLink(app, displayName, path)}
|
||||
</List.Item>
|
||||
});
|
||||
}
|
||||
|
||||
if (name == 'tags') {
|
||||
const tag = display.split(',');
|
||||
return tag.map((v: any) => {
|
||||
if (typeof v === "object") {
|
||||
return <List.Item>
|
||||
{ellipsisLink(app, v.display, v.name)}
|
||||
</List.Item>
|
||||
} else {
|
||||
// tags
|
||||
return <List.Item>
|
||||
<Tag>{v || null}</Tag>
|
||||
</List.Item>
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (type === "externalLink") {
|
||||
return externalLink(display, name);
|
||||
}
|
||||
|
||||
return ellipsisDisplay(display?.toString());
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
columns.push(...dynamicColumns);
|
||||
return columns;
|
||||
}
|
||||
|
||||
|
||||
public onDataUpdated(): void {
|
||||
const {app} = this;
|
||||
this.containerEl.empty();
|
||||
|
||||
let newDatas: Record<string, any> = []
|
||||
const data = this.data.data;
|
||||
data.map(item => {
|
||||
|
||||
let newItem: Record<string, any> = {};
|
||||
|
||||
this.data.properties.map((prop: BasesPropertyId) => {
|
||||
|
||||
const {type, name} = parsePropertyId(prop);
|
||||
|
||||
// `entry.getValue` returns the evaluated result of the property
|
||||
// in the context of this entry.
|
||||
const value = item.getValue(prop);
|
||||
|
||||
const rowData = {
|
||||
type: type,
|
||||
name: name,
|
||||
display: value?.toString() || "",
|
||||
}
|
||||
newItem[prop] = rowData
|
||||
})
|
||||
|
||||
const filePath = item.getValue("file.path");
|
||||
newItem["file.path"] = filePath
|
||||
|
||||
newDatas.push(newItem);
|
||||
|
||||
});
|
||||
const columnsHead = this.createColumns(app, this.data.properties);
|
||||
|
||||
const container = this.containerEl.createDiv();
|
||||
const root = createRoot(container);
|
||||
root.render(<TableView columnsHead={columnsHead} data={newDatas}/>);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const
|
||||
TableView: React.FC<any> = ({columnsHead, data}) => {
|
||||
|
||||
const [isDarkTheme, setIsDarkTheme] = useState<boolean>(
|
||||
document.body.classList.contains("theme-dark")
|
||||
);
|
||||
|
||||
const [columns, setColumns] = React.useState<ProColumns<any>[]>([]);
|
||||
|
||||
function parseTableResult(rows: any, params: any): Array<Record<string, any>> {
|
||||
delete params.current;
|
||||
delete params.pageSize;
|
||||
|
||||
const filteredData = rows.filter(item => {
|
||||
return Object.keys(params).every(key => {
|
||||
if (params[key]) {
|
||||
if (item[key]) {
|
||||
return item[key]?.display?.toString().toLowerCase().includes(params[key].toString().toLowerCase());
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
return filteredData;
|
||||
}
|
||||
|
||||
const executeTableQuery = async (datas: any, params: any) => {
|
||||
const queryResult = datas
|
||||
|
||||
const tableData: any = parseTableResult(queryResult, params);
|
||||
|
||||
const columns = columnsHead;
|
||||
|
||||
return {tableData: tableData, columns: columns};
|
||||
}
|
||||
|
||||
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();
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<ConfigProvider
|
||||
locale={enUS}
|
||||
theme={{
|
||||
algorithm: isDarkTheme ? theme.darkAlgorithm : theme.defaultAlgorithm,
|
||||
// token: {
|
||||
// colorPrimary: isDarkTheme ? "#1E90FF" : "#1890FF", // 自定义主色
|
||||
// colorBgBase: isDarkTheme ? "#1F1F1F" : "#FFFFFF", // 自定义背景色
|
||||
// colorText: isDarkTheme ? "#E0E0E0" : "#000000", // 自定义文本颜色
|
||||
// },
|
||||
}}
|
||||
>
|
||||
<ProTable
|
||||
scroll={{x: 'max-content'}}
|
||||
cardBordered
|
||||
editable={{
|
||||
type: 'multiple',
|
||||
}}
|
||||
columns={columns}
|
||||
// dataSource={data}
|
||||
columnsState={{
|
||||
persistenceKey: 'pro-table-singe-demos',
|
||||
persistenceType: 'localStorage',
|
||||
defaultValue: {
|
||||
option: {fixed: 'right', disable: true},
|
||||
},
|
||||
onChange(value) {
|
||||
// console.log('value: ', value);
|
||||
},
|
||||
}}
|
||||
//rowKey={(record) => record.key}
|
||||
search={{
|
||||
labelWidth: 'auto',
|
||||
}}
|
||||
pagination={{
|
||||
showSizeChanger: true,
|
||||
}}
|
||||
headerTitle="TableView"
|
||||
request={async (params, sort, filter) => {
|
||||
// console.log('params:', params);
|
||||
const response = await executeTableQuery(data, params);
|
||||
|
||||
const {tableData, columns} = response;
|
||||
|
||||
setColumns(columns);
|
||||
return Promise.resolve({
|
||||
data: tableData,
|
||||
success: true,
|
||||
total: tableData.length,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</ConfigProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Loading…
Reference in a new issue