diff --git a/README-zh.md b/README-zh.md index 18d29c4..2981589 100644 --- a/README-zh.md +++ b/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 + + +![DashTableView.png](docs/DashTableView.png) + +## dataview ~~~markdown ```querydash diff --git a/README.md b/README.md index d7a435e..69de49a 100644 --- a/README.md +++ b/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 + + +![DashTableView.png](docs/DashTableView.png) + +## 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 ![demo.gif](docs/demo.gif) **task** + ~~~markdown ```querydash task @@ -44,15 +62,18 @@ task **timeline** **simple mode** + ~~~markdown ```querydash timeline from #start ``` ~~~ + ![timeline.png](docs/timeline.png) **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. diff --git a/docs/DashTableView.png b/docs/DashTableView.png new file mode 100644 index 0000000..3ee9b25 Binary files /dev/null and b/docs/DashTableView.png differ diff --git a/manifest.json b/manifest.json index 4c4031e..d23b104 100644 --- a/manifest.json +++ b/manifest.json @@ -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 diff --git a/package.json b/package.json index 1edb8e6..6358747 100644 --- a/package.json +++ b/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" } } diff --git a/src/main.tsx b/src/main.tsx index f993b65..270d827 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -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() { diff --git a/src/models/ViewProps.ts b/src/models/ViewProps.ts index ac210ce..2559e2e 100644 --- a/src/models/ViewProps.ts +++ b/src/models/ViewProps.ts @@ -1,6 +1,6 @@ import {App} from "obsidian"; export interface ViewProps { - app: App; + app: App ; source: string; } diff --git a/src/pages/bases/DashTableView.tsx b/src/pages/bases/DashTableView.tsx new file mode 100644 index 0000000..2892ab1 --- /dev/null +++ b/src/pages/bases/DashTableView.tsx @@ -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 = { + dataIndex: 'index', + valueType: 'indexBorder', + title: 'index', + sorter: true, + } + columns.push(firstColumn); + const dynamicColumns: ProColumns[] = 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 + {ellipsisLink(app, displayName, path)} + + }); + } + + if (name == 'tags') { + const tag = display.split(','); + return tag.map((v: any) => { + if (typeof v === "object") { + return + {ellipsisLink(app, v.display, v.name)} + + } else { + // tags + return + {v || null} + + } + }) + } + + 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 = [] + const data = this.data.data; + data.map(item => { + + let newItem: Record = {}; + + 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(); + } + +} + +const + TableView: React.FC = ({columnsHead, data}) => { + + const [isDarkTheme, setIsDarkTheme] = useState( + document.body.classList.contains("theme-dark") + ); + + const [columns, setColumns] = React.useState[]>([]); + + function parseTableResult(rows: any, params: any): Array> { + 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 ( + + 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, + }); + }} + /> + + ); + }; + +