Merge pull request #12 from liufree/dev

feat: add timeline view
This commit is contained in:
liufree 2025-04-24 23:20:05 +08:00 committed by GitHub
commit 4768d1fc0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 174 additions and 6 deletions

View file

@ -6,8 +6,11 @@ QueryDash的目标是开发一款类似Notion Database的Obsidian插件但不
**由于使用了dataview的api必须启用dataview插件。**
未来将逐步扩展更多实用特性,帮助用户更高效地管理知识、任务和生活。
**如果你对项目感兴趣请star一下非常感谢这对我很重要**
**现有功能**
1. **多视图支持**:提供表格和列表两种视图,满足不同场景需求。
- **时间轴视图**: 使用时间轴来展示数据
2. **Dataview SQL支持**兼容Dataview的SQL语法。
3. **增强功能**
- **搜索**:快速定位所需内容。
@ -27,6 +30,24 @@ file.mtime as "mtime" ,file.tags as "tags" from #clippings
![demo.gif](docs/demo.gif)
**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
```
~~~
**远景规划**
1. **首页**:将常用功能整合到首页,提供一站式便捷操作。

View file

@ -3,12 +3,15 @@ The goal of QueryDash is to develop an Obsidian plugin similar to Notion Databas
**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.
**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 .
3. **Enhanced Features**:
- **Search**: Quickly locate the content you need.
@ -18,6 +21,7 @@ In the future, it will gradually expand with more practical features to help use
**Tutorial**
**table**
~~~markdown
```querydash
table file.name , file.outlinks as "links" ,file.ctime as "ctime",
@ -27,6 +31,24 @@ file.mtime as "mtime" ,file.tags as "tags" from #clippings
![demo.gif](docs/demo.gif)
**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
```
~~~
**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.

BIN
docs/timeline.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View file

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

View file

@ -1,6 +1,5 @@
{
"name": "obsidian-querydash",
"version": "1.0.2",
"description": "Refer to Dataview and add search, sorting, and pagination functions, just like Notion.",
"main": "main.js",
"scripts": {

View file

@ -21,7 +21,7 @@ export function formatValue(
function formatObject(value: any) {
const type =value.type;
if(type==='file') {
const fileName = value.path.replace(/\.md$/, '');
const fileName = value.path.split('/').pop().replace(/\.md$/, '')
return {type: "link", path: value.path, display: fileName};
}

View file

@ -2,8 +2,9 @@ import React, {useEffect} from 'react';
import {App} from "obsidian";
import TableView from "./tableview/TableView";
import ListView from "./listview/ListView";
import {ConfigProvider, ConfigProviderProps} from "antd";
import {ConfigProvider, ConfigProviderProps, Timeline} from "antd";
import enUS from 'antd/locale/en_US';
import TimeLineView from "./timelineview/TimeLineView";
interface QueryDashViewDashs {
@ -18,15 +19,18 @@ const QueryDashView: React.FC<QueryDashViewDashs> = ({app, source}) => {
const [sourceType, setSourceType] = React.useState<string>("table");
useEffect(() => {
const sourceType = source.toLowerCase().startsWith("list") ? "list" : "table";
// 获取第一个单词
const sourceType = source.split(" ")[0].toLowerCase();
setSourceType(sourceType);
}, []);
const getView = (app: App, source: string) => {
if (sourceType === "table") {
return <TableView app={app} source={source}/>;
} else {
} else if (sourceType === "list") {
return <ListView app={app} source={source}/>;
} else if (sourceType === "timeline") {
return <TimeLineView app={app} source={source}/>;
}
}

View file

@ -0,0 +1,122 @@
import {ProList} from '@ant-design/pro-components';
import React, {useEffect, useState} from "react";
import {formatValue} from "../GenerateColumns";
import {getAPI} from 'obsidian-dataview';
import {ViewProps} from "../../models/ViewProps";
import {Radio, RadioChangeEvent, Timeline} from "antd";
import {TFile} from "obsidian";
const TimeLineView: React.FC<ViewProps> = ({app, source}) => {
const [data, setData] = React.useState<any>([]);
const dv = getAPI(app);
const [mode, setMode] = useState<'left' | 'alternate' | 'right'>('alternate');
const onChange = (e: RadioChangeEvent) => {
setMode(e.target.value);
};
const renderItem = (itemFile: any, itemCtime: any) => {
const date = itemCtime?.display.split(" ")[0] || "";
return (
<span>
<a onClick={() => {
const file = app.vault.getAbstractFileByPath(itemFile.path);
if (file && file instanceof TFile) {
const leaf = app.workspace.getLeaf();
leaf.openFile(file);
}
}}>
{itemFile.display}
</a>
{date ? " " + date : ""}
</span>
)
}
useEffect(() => {
let resData: any[] = [];
const response = executeTableQuery(dv, source, {});
response.then((res) => {
const {tableData} = res;
tableData.forEach((item: any) => {
const file = item["File"];
const ctime = item["time"];
const finalDisplay = renderItem(file, ctime);
resData.push({"children": finalDisplay});
});
setData(resData);
}).catch((error) => {
console.error("Error executing query:", error);
});
// console.log(response, response);
}, [app, source]);
function parseTableResult(value: any, params: any): Array<Record<string, any>> {
const headers: string[] = value.headers;
const rows: Array<Record<string, any>> = [];
value.values.forEach((row: any) => {
const values: Record<string, any> = {};
headers.forEach((header, index) => {
const value = row[index];
// console.log("list value", value);
const resValue = formatValue(value);
// console.log("list resValue", resValue);
values[header] = resValue;
});
rows.push(values);
}); // console.log("list rows", rows);
return rows;
}
function replaceFirstWord(source: string): string {
const words = source.split(' ');
if (words[0] === 'timeline') {
words[0] = 'table';
}
return words.join(' ');
}
const executeTableQuery = async (dvApi: any, source: any, params: any) => {
const sql = replaceFirstWord(source);
const queryResult = await dvApi.query(sql);
console.log("queryResult", queryResult);
const tableData: any = parseTableResult(queryResult.value, params);
return {tableData: tableData};
}
return (
<>
<h4>Timeline</h4>
<br/>
<Radio.Group
onChange={onChange}
value={mode}
style={{
marginBottom: 20,
}}
>
<Radio value="left">Left</Radio>
<Radio value="right">Right</Radio>
<Radio value="alternate">Alternate</Radio>
</Radio.Group>
<Timeline
mode={mode}
items={data}
/>
</>)
}
export default TimeLineView;