mirror of
https://github.com/liufree/obsidian-querydash.git
synced 2026-07-22 05:41:49 +00:00
commit
f7f9e263d3
23 changed files with 470 additions and 1003 deletions
19
.umirc.ts
19
.umirc.ts
|
|
@ -1,19 +0,0 @@
|
|||
import {defineConfig} from '@umijs/max';
|
||||
|
||||
export default defineConfig({
|
||||
|
||||
|
||||
antd: {},
|
||||
access: {},
|
||||
model: {},
|
||||
initialState: {},
|
||||
request: {},
|
||||
layout: {
|
||||
title: '@umijs/max',
|
||||
},
|
||||
routes: [
|
||||
{path: '/', component: '@/pages/index'},
|
||||
],
|
||||
npmClient: 'npm',
|
||||
});
|
||||
|
||||
42
README-zh.md
Normal file
42
README-zh.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
|
||||
|
||||
**项目目标**
|
||||
QueryDash的目标是开发一款类似Notion Database的Obsidian插件,但不仅限于数据库功能。未来将逐步扩展更多实用特性,帮助用户更高效地管理知识、任务和生活。
|
||||
|
||||
**现有功能**
|
||||
1. **多视图支持**:提供表格和列表两种视图,满足不同场景需求。
|
||||
2. **Dataview SQL支持**:兼容Dataview的SQL语法(暂不支持`GROUP BY`)。
|
||||
3. **增强功能**:
|
||||
- **搜索**:快速定位所需内容。
|
||||
- **过滤**:根据条件筛选数据。
|
||||
- **合计**:支持数据汇总统计。
|
||||
- **分页**:优化大数据量下的浏览体验。
|
||||
|
||||
**使用教程**
|
||||

|
||||
|
||||
|
||||
**远景规划**
|
||||
1. **首页**:将常用功能整合到首页,提供一站式便捷操作。
|
||||
2. **多视图支持**:支持时间线、画廊、表格等多种视图,满足不同场景需求。
|
||||
3. **复习卡片**:内置复习功能,帮助用户高效巩固知识。
|
||||
4. **Database功能**:支持创建、编辑、删除等操作,打造灵活的数据管理体验。
|
||||
5. **更多功能**:持续开发,不断丰富插件功能,提升用户体验。
|
||||
|
||||
**参考项目**
|
||||
- [Dataview](https://github.com/blacksmithgu/obsidian-dataview):强大的数据查询和管理工具。
|
||||
- [Project](https://github.com/marcusolsson/obsidian-projects):任务和项目管理插件。
|
||||
|
||||
**项目简介**
|
||||
作为一名Obsidian的深度用户,我发现现有的插件在搜索、复习以及Dataview表格的编辑和删除等功能上无法完全满足我的需求。因此,我决定基于Ant Design开发一套全新的视图系统,旨在提升Obsidian的使用体验,更好地辅助日常生活和工作。
|
||||
|
||||
目前,项目的功能还处于初期阶段,但我会持续投入开发,逐步完善和扩展功能。我相信通过不断迭代,这个项目会变得越来越实用和强大。
|
||||
|
||||
**关于赞助**
|
||||
本项目完全基于个人兴趣开发,如果您觉得这个项目对您有帮助,并愿意支持我的工作。您的每一份赞助都将成为我持续开发的动力,非常感谢!
|
||||
|
||||
**未来计划**
|
||||
- 完善现有功能,提升用户体验
|
||||
- 增加更多实用特性,如高级搜索、智能复习等
|
||||
- 优化性能,确保系统稳定运行
|
||||
118
README.md
118
README.md
|
|
@ -1,96 +1,38 @@
|
|||
# Obsidian Sample Plugin
|
||||
**Project Objective**
|
||||
The goal of QueryDash is to develop an Obsidian plugin similar to Notion Database, but not limited to database functionality. In the future, it will gradually expand with more practical features to help users manage knowledge, tasks, and life more efficiently.
|
||||
|
||||
This is a sample plugin for Obsidian (https://obsidian.md).
|
||||
**Current Features**
|
||||
1. **Multi-view Support**: Provides table and list views to meet different scenario needs.
|
||||
2. **Dataview SQL Support**: Compatible with Dataview's SQL syntax (currently does not support `GROUP BY`).
|
||||
3. **Enhanced Features**:
|
||||
- **Search**: Quickly locate the content you need.
|
||||
- **Filter**: Screen data based on conditions.
|
||||
- **Aggregation**: Supports data summary statistics.
|
||||
- **Pagination**: Optimizes browsing experience for large datasets.
|
||||
|
||||
This project uses TypeScript to provide type checking and documentation.
|
||||
The repo depends on the latest plugin API (obsidian.d.ts) in TypeScript Definition format, which contains TSDoc comments describing what it does.
|
||||
**Tutorial**
|
||||

|
||||
|
||||
**Note:** The Obsidian API is still in early alpha and is subject to change at any time!
|
||||
**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.
|
||||
4. **Database Functionality**: Supports create, edit, delete, and other operations for flexible data management.
|
||||
5. **More Features**: Continuous development to enrich plugin functionality and enhance user experience.
|
||||
|
||||
This sample plugin demonstrates some of the basic functionality the plugin API can do.
|
||||
- Adds a ribbon icon, which shows a Notice when clicked.
|
||||
- Adds a command "Open Sample Modal" which opens a Modal.
|
||||
- Adds a plugin setting tab to the settings page.
|
||||
- Registers a global click event and output 'click' to the console.
|
||||
- Registers a global interval which logs 'setInterval' to the console.
|
||||
**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.
|
||||
|
||||
## First time developing plugins?
|
||||
**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.
|
||||
|
||||
Quick starting guide for new plugin devs:
|
||||
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.
|
||||
|
||||
- Check if [someone already developed a plugin for what you want](https://obsidian.md/plugins)! There might be an existing plugin similar enough that you can partner up with.
|
||||
- Make a copy of this repo as a template with the "Use this template" button (login to GitHub if you don't see it).
|
||||
- Clone your repo to a local development folder. For convenience, you can place this folder in your `.obsidian/plugins/your-plugin-name` folder.
|
||||
- Install NodeJS, then run `npm i` in the command line under your repo folder.
|
||||
- Run `npm run dev` to compile your plugin from `main.ts` to `main.js`.
|
||||
- Make changes to `main.ts` (or create new `.ts` files). Those changes should be automatically compiled into `main.js`.
|
||||
- Reload Obsidian to load the new version of your plugin.
|
||||
- Enable plugin in settings window.
|
||||
- For updates to the Obsidian API run `npm update` in the command line under your repo folder.
|
||||
**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!
|
||||
|
||||
## Releasing new releases
|
||||
|
||||
- Update your `manifest.json` with your new version number, such as `1.0.1`, and the minimum Obsidian version required for your latest release.
|
||||
- Update your `versions.json` file with `"new-plugin-version": "minimum-obsidian-version"` so older versions of Obsidian can download an older version of your plugin that's compatible.
|
||||
- Create new GitHub release using your new version number as the "Tag version". Use the exact version number, don't include a prefix `v`. See here for an example: https://github.com/obsidianmd/obsidian-sample-plugin/releases
|
||||
- Upload the files `manifest.json`, `main.js`, `styles.css` as binary attachments. Note: The manifest.json file must be in two places, first the root path of your repository and also in the release.
|
||||
- Publish the release.
|
||||
|
||||
> You can simplify the version bump process by running `npm version patch`, `npm version minor` or `npm version major` after updating `minAppVersion` manually in `manifest.json`.
|
||||
> The command will bump version in `manifest.json` and `package.json`, and add the entry for the new version to `versions.json`
|
||||
|
||||
## Adding your plugin to the community plugin list
|
||||
|
||||
- Check the [plugin guidelines](https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines).
|
||||
- Publish an initial version.
|
||||
- Make sure you have a `README.md` file in the root of your repo.
|
||||
- Make a pull request at https://github.com/obsidianmd/obsidian-releases to add your plugin.
|
||||
|
||||
## How to use
|
||||
|
||||
- Clone this repo.
|
||||
- Make sure your NodeJS is at least v16 (`node --version`).
|
||||
- `npm i` or `yarn` to install dependencies.
|
||||
- `npm run dev` to start compilation in watch mode.
|
||||
|
||||
## Manually installing the plugin
|
||||
|
||||
- Copy over `main.js`, `styles.css`, `manifest.json` to your vault `VaultFolder/.obsidian/plugins/your-plugin-id/`.
|
||||
|
||||
## Improve code quality with eslint (optional)
|
||||
- [ESLint](https://eslint.org/) is a tool that analyzes your code to quickly find problems. You can run ESLint against your plugin to find common bugs and ways to improve your code.
|
||||
- To use eslint with this project, make sure to install eslint from terminal:
|
||||
- `npm install -g eslint`
|
||||
- To use eslint to analyze this project use this command:
|
||||
- `eslint main.ts`
|
||||
- eslint will then create a report with suggestions for code improvement by file and line number.
|
||||
- If your source code is in a folder, such as `src`, you can use eslint with this command to analyze all files in that folder:
|
||||
- `eslint .\src\`
|
||||
|
||||
## Funding URL
|
||||
|
||||
You can include funding URLs where people who use your plugin can financially support it.
|
||||
|
||||
The simple way is to set the `fundingUrl` field to your link in your `manifest.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"fundingUrl": "https://buymeacoffee.com"
|
||||
}
|
||||
```
|
||||
|
||||
If you have multiple URLs, you can also do:
|
||||
|
||||
```json
|
||||
{
|
||||
"fundingUrl": {
|
||||
"Buy Me a Coffee": "https://buymeacoffee.com",
|
||||
"GitHub Sponsor": "https://github.com/sponsors",
|
||||
"Patreon": "https://www.patreon.com/"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Documentation
|
||||
|
||||
See https://github.com/obsidianmd/obsidian-api
|
||||
**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/demo.gif
Normal file
BIN
docs/demo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 MiB |
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"id": "sample-plugin",
|
||||
"name": "Sample Plugin",
|
||||
"id": "obsidian-querydash-plugin",
|
||||
"name": "QueryDash",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Demonstrates some of the capabilities of the Obsidian API.",
|
||||
"author": "Obsidian",
|
||||
"authorUrl": "https://obsidian.md",
|
||||
"minAppVersion": "1.8.0",
|
||||
"description": "This is a database search plugin for Obsidian",
|
||||
"author": "lwx",
|
||||
"authorUrl": "https://github.com/liufree/obsidian-querydash",
|
||||
"fundingUrl": "https://obsidian.md/pricing",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "obsidian-sample-plugin",
|
||||
"name": "obsidian-querydash",
|
||||
"version": "1.0.0",
|
||||
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
|
||||
"description": "This is a database search plugin for Obsidian",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "vite build --watch --mode development",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import {App, PluginSettingTab, Setting} from "obsidian";
|
||||
import MyPlugin from "../main";
|
||||
import QueryDashPlugin from "../main";
|
||||
|
||||
class SampleSettingTab extends PluginSettingTab {
|
||||
plugin: MyPlugin;
|
||||
plugin: QueryDashPlugin;
|
||||
|
||||
constructor(app: App, plugin: MyPlugin) {
|
||||
constructor(app: App, plugin: QueryDashPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
|
|
|||
121
src/main.tsx
121
src/main.tsx
|
|
@ -1,108 +1,18 @@
|
|||
import {Editor, MarkdownView, Notice, Plugin} from 'obsidian';
|
||||
import SampleSettingTab from './components/SampleSettingTab';
|
||||
import SampleModal from "./components/SampleModal";
|
||||
import {Plugin} from 'obsidian';
|
||||
import ReactComponent from "./pages/ReactComponent";
|
||||
import {Root} from 'react-dom/client';
|
||||
|
||||
import {SampleView} from "./sampleView";
|
||||
import {SAMPLE_VIEW_TYPE} from './constants';
|
||||
import React from "react";
|
||||
import AntdTableDemo from "./pages/AntdTableDemo";
|
||||
import TableDemo from "./pages/TableDemo";
|
||||
import {StrictMode} from 'react';
|
||||
import ReactDemo from "./pages/ReactDemo";
|
||||
import { Root, createRoot } from 'react-dom/client';
|
||||
|
||||
interface MyPluginSettings {
|
||||
mySetting: string;
|
||||
}
|
||||
export default class QueryDashPlugin extends Plugin {
|
||||
|
||||
const DEFAULT_SETTINGS: MyPluginSettings = {
|
||||
mySetting: 'default'
|
||||
}
|
||||
|
||||
export default class MyPlugin extends Plugin {
|
||||
settings: MyPluginSettings;
|
||||
root: Root | null = null;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
this.registerView(SAMPLE_VIEW_TYPE, (leaf) => new SampleView(leaf, this));
|
||||
|
||||
// This creates an icon in the left ribbon.
|
||||
const ribbonIconEl = this.addRibbonIcon('dice', 'Sample Plugin', (evt: MouseEvent) => {
|
||||
// Called when the user clicks the icon.
|
||||
new Notice('This is a notice!');
|
||||
this.openMemos();
|
||||
});
|
||||
// Perform additional things with the ribbon
|
||||
ribbonIconEl.addClass('my-plugin-ribbon-class');
|
||||
|
||||
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
|
||||
const statusBarItemEl = this.addStatusBarItem();
|
||||
statusBarItemEl.setText('Status Bar Text');
|
||||
|
||||
// This adds a simple command that can be triggered anywhere
|
||||
this.addCommand({
|
||||
id: 'open-sample-modal-simple',
|
||||
name: 'Open sample modal (simple)',
|
||||
callback: () => {
|
||||
new SampleModal(this.app).open();
|
||||
}
|
||||
});
|
||||
// This adds an editor command that can perform some operation on the current editor instance
|
||||
this.addCommand({
|
||||
id: 'sample-editor-command',
|
||||
name: 'Sample editor command',
|
||||
editorCallback: (editor: Editor, view: MarkdownView) => {
|
||||
console.log(editor.getSelection());
|
||||
editor.replaceSelection('Sample Editor Command');
|
||||
}
|
||||
});
|
||||
// This adds a complex command that can check whether the current state of the app allows execution of the command
|
||||
this.addCommand({
|
||||
id: 'open-sample-modal-complex',
|
||||
name: 'Open sample modal (complex)',
|
||||
checkCallback: (checking: boolean) => {
|
||||
// Conditions to check
|
||||
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (markdownView) {
|
||||
// If checking is true, we're simply "checking" if the command can be run.
|
||||
// If checking is false, then we want to actually perform the operation.
|
||||
if (!checking) {
|
||||
new SampleModal(this.app).open();
|
||||
}
|
||||
|
||||
// This command will only show up in Command Palette when the check function returns true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// This adds a settings tab so the user can configure various aspects of the plugin
|
||||
this.addSettingTab(new SampleSettingTab(this.app, this));
|
||||
|
||||
// If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
|
||||
// Using this function will automatically remove the event listener when this plugin is disabled.
|
||||
this.registerDomEvent(document, 'click', (evt: MouseEvent) => {
|
||||
console.log('click', evt);
|
||||
});
|
||||
|
||||
// When registering intervals, this function will automatically clear the interval when the plugin is disabled.
|
||||
this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000));
|
||||
|
||||
// 代码块中标注table的绘制成sampleView
|
||||
this.registerMarkdownCodeBlockProcessor('sample', (source, el, ctx) => {
|
||||
|
||||
console.log("start")
|
||||
console.log('source',source)
|
||||
console.log('ctx',ctx)
|
||||
// 插入一个div
|
||||
this.registerMarkdownCodeBlockProcessor('querydash', (source, el, ctx) => {
|
||||
const container = el.createDiv();
|
||||
|
||||
// 创建一个 MarkdownRenderChild 实例
|
||||
const reactComponent = new ReactDemo(container, source,this.app);
|
||||
const reactComponent = new ReactComponent(container, source, this.app);
|
||||
ctx.addChild(reactComponent);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -110,24 +20,5 @@ export default class MyPlugin extends Plugin {
|
|||
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
|
||||
async openMemos() {
|
||||
const workspace = this.app.workspace;
|
||||
workspace.detachLeavesOfType(SAMPLE_VIEW_TYPE);
|
||||
// const leaf = workspace.getLeaf(
|
||||
// !Platform.isMobile && workspace.activeLeaf && workspace.activeLeaf.view instanceof FileView,
|
||||
// );
|
||||
const leaf = workspace.getLeaf(false);
|
||||
await leaf.setViewState({type: SAMPLE_VIEW_TYPE});
|
||||
workspace.revealLeaf(leaf);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
6
src/models/ViewProps.ts
Normal file
6
src/models/ViewProps.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import {App} from "obsidian";
|
||||
|
||||
export interface ViewProps {
|
||||
app: App;
|
||||
source: string;
|
||||
}
|
||||
|
|
@ -1,207 +0,0 @@
|
|||
import React from 'react';
|
||||
import { InboxOutlined, UploadOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Col,
|
||||
ColorPicker,
|
||||
Form,
|
||||
InputNumber,
|
||||
Radio,
|
||||
Rate,
|
||||
Row,
|
||||
Select,
|
||||
Slider,
|
||||
Space,
|
||||
Switch,
|
||||
Upload,
|
||||
} from 'antd';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: { span: 6 },
|
||||
wrapperCol: { span: 14 },
|
||||
};
|
||||
|
||||
const normFile = (e: any) => {
|
||||
console.log('Upload event:', e);
|
||||
if (Array.isArray(e)) {
|
||||
return e;
|
||||
}
|
||||
return e?.fileList;
|
||||
};
|
||||
|
||||
const onFinish = (values: any) => {
|
||||
console.log('Received values of form: ', values);
|
||||
};
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Form
|
||||
name="validate_other"
|
||||
{...formItemLayout}
|
||||
onFinish={onFinish}
|
||||
initialValues={{
|
||||
'input-number': 3,
|
||||
'checkbox-group': ['A', 'B'],
|
||||
rate: 3.5,
|
||||
'color-picker': null,
|
||||
}}
|
||||
style={{ maxWidth: 600 }}
|
||||
>
|
||||
<Form.Item label="Plain Text">
|
||||
<span className="ant-form-text">China</span>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="select"
|
||||
label="Select"
|
||||
hasFeedback
|
||||
rules={[{ required: true, message: 'Please select your country!' }]}
|
||||
>
|
||||
<Select placeholder="Please select a country">
|
||||
<Option value="china">China</Option>
|
||||
<Option value="usa">U.S.A</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="select-multiple"
|
||||
label="Select[multiple]"
|
||||
rules={[{ required: true, message: 'Please select your favourite colors!', type: 'array' }]}
|
||||
>
|
||||
<Select mode="multiple" placeholder="Please select favourite colors">
|
||||
<Option value="red">Red</Option>
|
||||
<Option value="green">Green</Option>
|
||||
<Option value="blue">Blue</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="InputNumber">
|
||||
<Form.Item name="input-number" noStyle>
|
||||
<InputNumber min={1} max={10} />
|
||||
</Form.Item>
|
||||
<span className="ant-form-text" style={{ marginInlineStart: 8 }}>
|
||||
machines
|
||||
</span>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="switch" label="Switch" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="slider" label="Slider">
|
||||
<Slider
|
||||
marks={{
|
||||
0: 'A',
|
||||
20: 'B',
|
||||
40: 'C',
|
||||
60: 'D',
|
||||
80: 'E',
|
||||
100: 'F',
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="radio-group" label="Radio.Group">
|
||||
<Radio.Group>
|
||||
<Radio value="a">item 1</Radio>
|
||||
<Radio value="b">item 2</Radio>
|
||||
<Radio value="c">item 3</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="radio-button"
|
||||
label="Radio.Button"
|
||||
rules={[{ required: true, message: 'Please pick an item!' }]}
|
||||
>
|
||||
<Radio.Group>
|
||||
<Radio.Button value="a">item 1</Radio.Button>
|
||||
<Radio.Button value="b">item 2</Radio.Button>
|
||||
<Radio.Button value="c">item 3</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="checkbox-group" label="Checkbox.Group">
|
||||
<Checkbox.Group>
|
||||
<Row>
|
||||
<Col span={8}>
|
||||
<Checkbox value="A" style={{ lineHeight: '32px' }}>
|
||||
A
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Checkbox value="B" style={{ lineHeight: '32px' }} disabled>
|
||||
B
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Checkbox value="C" style={{ lineHeight: '32px' }}>
|
||||
C
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Checkbox value="D" style={{ lineHeight: '32px' }}>
|
||||
D
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Checkbox value="E" style={{ lineHeight: '32px' }}>
|
||||
E
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Checkbox value="F" style={{ lineHeight: '32px' }}>
|
||||
F
|
||||
</Checkbox>
|
||||
</Col>
|
||||
</Row>
|
||||
</Checkbox.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="rate" label="Rate">
|
||||
<Rate />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="upload"
|
||||
label="Upload"
|
||||
valuePropName="fileList"
|
||||
getValueFromEvent={normFile}
|
||||
extra="longgggggggggggggggggggggggggggggggggg"
|
||||
>
|
||||
<Upload name="logo" action="/upload.do" listType="picture">
|
||||
<Button icon={<UploadOutlined />}>Click to upload</Button>
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
<Form.Item label="Dragger">
|
||||
<Form.Item name="dragger" valuePropName="fileList" getValueFromEvent={normFile} noStyle>
|
||||
<Upload.Dragger name="files" action="/upload.do">
|
||||
<p className="ant-upload-drag-icon">
|
||||
<InboxOutlined />
|
||||
</p>
|
||||
<p className="ant-upload-text">Click or drag file to this area to upload</p>
|
||||
<p className="ant-upload-hint">Support for a single or bulk upload.</p>
|
||||
</Upload.Dragger>
|
||||
</Form.Item>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="color-picker"
|
||||
label="ColorPicker"
|
||||
rules={[{ required: true, message: 'color is required!' }]}
|
||||
>
|
||||
<ColorPicker />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item wrapperCol={{ span: 12, offset: 6 }}>
|
||||
<Space>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Submit
|
||||
</Button>
|
||||
<Button htmlType="reset">reset</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
import { SmileOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
FooterToolbar,
|
||||
PageContainer,
|
||||
ProForm,
|
||||
ProFormDateRangePicker,
|
||||
ProFormDigit,
|
||||
ProFormRadio,
|
||||
ProFormSelect,
|
||||
ProFormText,
|
||||
ProFormTextArea,
|
||||
ProFormUploadButton,
|
||||
ProLayout,
|
||||
} from '@ant-design/pro-components';
|
||||
import { Card } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<ProLayout
|
||||
fixSiderbar
|
||||
fixedHeader
|
||||
breakpoint={false}
|
||||
defaultCollapsed
|
||||
pageTitleRender={false}
|
||||
menuDataRender={() => [
|
||||
{
|
||||
path: '/one',
|
||||
icon: <SmileOutlined />,
|
||||
name: '一级名称',
|
||||
children: [
|
||||
{
|
||||
path: 'two',
|
||||
name: '二级名称',
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
layout="mix"
|
||||
location={{
|
||||
pathname: '/one/two',
|
||||
}}
|
||||
>
|
||||
<PageContainer title="输入表单">
|
||||
<Card>
|
||||
<ProForm
|
||||
submitter={{
|
||||
render: (_, dom) => <FooterToolbar>{dom}</FooterToolbar>,
|
||||
}}
|
||||
onFinish={async (values) => console.log(values)}
|
||||
>
|
||||
<ProForm.Group>
|
||||
<ProFormText
|
||||
name="name"
|
||||
label="签约客户名称"
|
||||
tooltip="最长为 24 位"
|
||||
placeholder="请输入名称"
|
||||
/>
|
||||
<ProFormText
|
||||
width="md"
|
||||
name="company"
|
||||
label="我方公司名称"
|
||||
placeholder="请输入名称"
|
||||
/>
|
||||
</ProForm.Group>
|
||||
<ProForm.Group>
|
||||
<ProFormText
|
||||
name={['contract', 'name']}
|
||||
label="合同名称"
|
||||
placeholder="请输入名称"
|
||||
/>
|
||||
<ProFormDateRangePicker
|
||||
name={['contract', 'createTime']}
|
||||
label="合同生效时间"
|
||||
/>
|
||||
</ProForm.Group>
|
||||
<ProForm.Group>
|
||||
<ProFormSelect
|
||||
options={[
|
||||
{
|
||||
value: 'chapter',
|
||||
label: '盖章后生效',
|
||||
},
|
||||
]}
|
||||
width="xs"
|
||||
name="chapter"
|
||||
label="合同约定生效方式"
|
||||
/>
|
||||
<ProFormSelect
|
||||
width="xs"
|
||||
options={[
|
||||
{
|
||||
value: 'time',
|
||||
label: '履行完终止',
|
||||
},
|
||||
]}
|
||||
name="unusedMode"
|
||||
label="合同约定失效效方式"
|
||||
/>
|
||||
</ProForm.Group>
|
||||
<ProFormText width="sm" name="id" label="主合同编号" />
|
||||
<ProFormText
|
||||
name="project"
|
||||
disabled
|
||||
label="项目名称"
|
||||
initialValue="xxxx项目"
|
||||
/>
|
||||
<ProFormText
|
||||
width="xs"
|
||||
name="mangerName"
|
||||
disabled
|
||||
label="商务经理"
|
||||
initialValue="启途"
|
||||
/>
|
||||
<ProForm.Group>
|
||||
<ProFormSelect
|
||||
initialValue="money"
|
||||
options={[
|
||||
{
|
||||
value: 'money',
|
||||
label: '确认金额',
|
||||
},
|
||||
]}
|
||||
width="xs"
|
||||
name="useMode"
|
||||
label="金额类型"
|
||||
/>
|
||||
<ProFormSelect
|
||||
options={[
|
||||
{
|
||||
value: '6',
|
||||
label: '6%',
|
||||
},
|
||||
{
|
||||
value: '12',
|
||||
label: '12%',
|
||||
},
|
||||
]}
|
||||
initialValue="6"
|
||||
width="xs"
|
||||
name="taxRate"
|
||||
label="税率"
|
||||
/>
|
||||
<ProFormRadio.Group
|
||||
label="发票类型"
|
||||
name="invoiceType"
|
||||
initialValue="发票"
|
||||
options={['发票', '普票', '无票']}
|
||||
/>
|
||||
</ProForm.Group>
|
||||
<ProFormUploadButton
|
||||
extra="支持扩展名:.jpg .zip .doc .wps"
|
||||
label="倒签报备附件"
|
||||
name="file"
|
||||
title="上传文件"
|
||||
/>
|
||||
<ProFormDigit
|
||||
width="xs"
|
||||
name="num"
|
||||
label="合同份数"
|
||||
initialValue={5}
|
||||
/>
|
||||
</ProForm>
|
||||
</Card>
|
||||
</PageContainer>
|
||||
</ProLayout>
|
||||
);
|
||||
};
|
||||
37
src/pages/GenerateColumns.tsx
Normal file
37
src/pages/GenerateColumns.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import dayjs from "dayjs";
|
||||
|
||||
export function formatValue(
|
||||
value: Record<string, any>
|
||||
) {
|
||||
let res;
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
console.log("format value", value);
|
||||
if (Array.isArray(value)) {
|
||||
value.map((v) => (typeof v === "object" ? formatObject(v) : v));
|
||||
res = {type: "array", display: value};
|
||||
} else if (typeof value === "object") {
|
||||
res = formatObject(value);
|
||||
} else {
|
||||
res = {type: "text", display: value};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function formatObject(value: any) {
|
||||
const type =value.type;
|
||||
if(type==='file') {
|
||||
const fileName = value.path.split('/').pop()?.split('.')[0];
|
||||
return {type: "link", path: value.path, display: fileName};
|
||||
}
|
||||
|
||||
if ("path" in value && "display" in value) {
|
||||
console.log("link value", value);
|
||||
return {type: "link", path: value.path, display: value.display};
|
||||
}
|
||||
if ("ts" in value) {
|
||||
const display= dayjs(value.ts).format("YYYY-MM-DD HH:mm:ss");
|
||||
return {type: "datetime", display: display};
|
||||
}
|
||||
}
|
||||
40
src/pages/QueryDashView.tsx
Normal file
40
src/pages/QueryDashView.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
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 enUS from 'antd/locale/en_US';
|
||||
|
||||
|
||||
interface QueryDashViewDashs {
|
||||
app: App; // Obsidian app
|
||||
source: string;
|
||||
}
|
||||
|
||||
|
||||
const QueryDashView: React.FC<QueryDashViewDashs> = ({app, source}) => {
|
||||
|
||||
|
||||
const [sourceType, setSourceType] = React.useState<string>("table");
|
||||
|
||||
useEffect(() => {
|
||||
const sourceType = source.toLowerCase().startsWith("list") ? "list" : "table";
|
||||
setSourceType(sourceType);
|
||||
}, []);
|
||||
|
||||
const getView = (app: App, source: string) => {
|
||||
if (sourceType === "table") {
|
||||
return <TableView app={app} source={source}/>;
|
||||
} else {
|
||||
return <ListView app={app} source={source}/>;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ConfigProvider locale={enUS}>
|
||||
{getView(app, source)}
|
||||
</ConfigProvider>
|
||||
)
|
||||
|
||||
};
|
||||
export default QueryDashView;
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
import {App, Plugin, MarkdownRenderChild} from 'obsidian';
|
||||
import {App, MarkdownRenderChild} from 'obsidian';
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import TableDemo from "./TableDemo";
|
||||
import AntdTableDemo from "./AntdTableDemo";
|
||||
import {createRoot, Root} from "react-dom/client";
|
||||
import QueryDashView from "./QueryDashView";
|
||||
import { Platform } from 'obsidian';
|
||||
|
||||
|
||||
export default class ReactComponent extends MarkdownRenderChild {
|
||||
private sampleComponent: React.ReactElement;
|
||||
root: Root;
|
||||
container: HTMLElement;
|
||||
|
||||
|
|
@ -16,15 +15,12 @@ export default class ReactComponent extends MarkdownRenderChild {
|
|||
}
|
||||
|
||||
onload() {
|
||||
// 渲染 React 组件
|
||||
this.renderReactComponent();
|
||||
}
|
||||
|
||||
renderReactComponent() {
|
||||
// 使用 ReactDOM.render 渲染 React 组件
|
||||
|
||||
// 使用 createRoot 渲染 React 组件
|
||||
const root = createRoot(this.container);
|
||||
root.render(<TableDemo app={this.app} source={this.source} />);
|
||||
root.render(<QueryDashView app={this.app} source={this.source} />);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
.container {
|
||||
all: initial; /* 重置容器内的样式 */
|
||||
}
|
||||
|
|
@ -1,306 +0,0 @@
|
|||
import {EllipsisOutlined, PlusOutlined} from '@ant-design/icons';
|
||||
import type {ActionType, ProColumns} from '@ant-design/pro-components';
|
||||
import {ProTable, TableDropdown} from '@ant-design/pro-components';
|
||||
import {Button, Dropdown, Space, Tag} from 'antd';
|
||||
import {useEffect, useRef} from 'react';
|
||||
import React from 'react';
|
||||
import request from 'umi-request';
|
||||
import { getAPI} from 'obsidian-dataview';
|
||||
import {App, Workspace} from "obsidian";
|
||||
import {continuedIndent} from "@codemirror/language";
|
||||
|
||||
export const waitTimePromise = async (time: number = 100) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(true);
|
||||
}, time);
|
||||
});
|
||||
};
|
||||
|
||||
export const waitTime = async (time: number = 100) => {
|
||||
await waitTimePromise(time);
|
||||
};
|
||||
|
||||
type GithubIssueItem = {
|
||||
url: string;
|
||||
id: number;
|
||||
number: number;
|
||||
title: string;
|
||||
labels: {
|
||||
name: string;
|
||||
color: string;
|
||||
}[];
|
||||
state: string;
|
||||
comments: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
closed_at?: string;
|
||||
header: {
|
||||
path: string;
|
||||
},
|
||||
text: string;
|
||||
};
|
||||
|
||||
|
||||
interface MyReactComponentProps {
|
||||
app: any; // Obsidian的App对象
|
||||
source: string; // 当前文件的路径
|
||||
}
|
||||
|
||||
const MyReactComponent: React.FC<MyReactComponentProps> = ({app, source}) => {
|
||||
const actionRef = useRef<ActionType>();
|
||||
|
||||
const dv = getAPI(app);
|
||||
|
||||
const [data, setData] = React.useState<GithubIssueItem[]>([]);
|
||||
|
||||
function parseTableResult(value: any, params:any): Array<Record<string, any>> {
|
||||
const headers: string[] = value.headers;
|
||||
|
||||
const rows: Array<Record<string, any>> = [];
|
||||
console.log("params1", params);
|
||||
|
||||
|
||||
|
||||
value.values.forEach((row) => {
|
||||
const values: Record<string, any> = {};
|
||||
|
||||
let flag = []
|
||||
headers.forEach((header, index) => {
|
||||
|
||||
const searchParam = params[header];
|
||||
console.log("searchParam",searchParam);
|
||||
|
||||
|
||||
const value = row[index];
|
||||
// 如果字符串value中不包含searchParam,则排除
|
||||
if(searchParam){
|
||||
flag[index] = value.indexOf(searchParam) > -1;
|
||||
}else{
|
||||
flag[index] = true;
|
||||
}
|
||||
|
||||
console.log("header",header)
|
||||
console.log("row.value",value)
|
||||
values[header] = value;
|
||||
});
|
||||
rows.push(values);
|
||||
});
|
||||
|
||||
|
||||
// params参数去除current和pageSize
|
||||
delete params.current;
|
||||
delete params.pageSize;
|
||||
|
||||
const filteredData = rows.filter(item => {
|
||||
return Object.keys(params).every(key => {
|
||||
console.log("key", key);
|
||||
console.log("params[key]", params[key]);
|
||||
console.log("item[key]", item[key]);
|
||||
if (params[key]) {
|
||||
// If item[key] is empty, return false directly.
|
||||
if (item[key]) {
|
||||
return item[key].toString().toLowerCase().includes(params[key].toString().toLowerCase());
|
||||
}else{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
console.log("rows2", rows);
|
||||
console.log("keyParams", Object.keys(params));
|
||||
console.log("params2", params);
|
||||
|
||||
return filteredData;
|
||||
}
|
||||
|
||||
const executeTableQuery=async (dvApi: any,source:any,params:any) => {
|
||||
// 定义查询语句
|
||||
const sql = source;
|
||||
|
||||
// 使用 Dataview API 执行查询
|
||||
const queryResult = await dvApi.query(sql);
|
||||
console.log("Query Result:", queryResult);
|
||||
console.log("Query Result1:", queryResult.successful);
|
||||
console.log("Query Result2:", queryResult.value);
|
||||
|
||||
const tableData:any =parseTableResult(queryResult.value,params);
|
||||
|
||||
|
||||
console.log("tableData:", tableData);
|
||||
|
||||
// 处理查询结果
|
||||
if (queryResult.successful) {
|
||||
const tableData = queryResult.value;
|
||||
console.log("Query Result:", tableData);
|
||||
// 在这里可以对查询结果进行进一步处理
|
||||
tableData.values.forEach(row => {
|
||||
console.log("Row:", row);
|
||||
});
|
||||
} else {
|
||||
console.error("Query failed:", queryResult.error);
|
||||
}
|
||||
return tableData;
|
||||
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
//executeTableQuery(dv,source,{});
|
||||
|
||||
// const data = dv.pages().file.lists.values;
|
||||
//console.log("data", data);
|
||||
// setData(data);
|
||||
}, []);
|
||||
|
||||
const columns: ProColumns<GithubIssueItem>[] = [
|
||||
{
|
||||
dataIndex: 'index',
|
||||
valueType: 'indexBorder',
|
||||
width: 48,
|
||||
},
|
||||
{
|
||||
title: '标题',
|
||||
dataIndex: 'file.name',
|
||||
render: (_, record) => (
|
||||
console.log("record,{},text", _,record),
|
||||
<a onClick={() => {
|
||||
// 调用 Obsidian API
|
||||
const file = app.vault.getAbstractFileByPath(record.File.path);
|
||||
if (file) {
|
||||
app.workspace.activeLeaf?.openFile(file);
|
||||
}
|
||||
}}>
|
||||
{record.File.path}
|
||||
</a>
|
||||
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '文本',
|
||||
dataIndex: 'text',
|
||||
filters: true,
|
||||
onFilter: true,
|
||||
ellipsis: true,
|
||||
render: (_, record) => (
|
||||
console.log("record", record),
|
||||
<Space>
|
||||
{record.text}
|
||||
</Space>
|
||||
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '文本1',
|
||||
dataIndex: 'file.ctime',
|
||||
filters: true,
|
||||
onFilter: true,
|
||||
ellipsis: true,
|
||||
render: (_, record) => (
|
||||
console.log("record", record),
|
||||
<Space>
|
||||
{record.text}
|
||||
</Space>
|
||||
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ProTable<GithubIssueItem>
|
||||
columns={columns}
|
||||
actionRef={actionRef}
|
||||
cardBordered
|
||||
editable={{
|
||||
type: 'multiple',
|
||||
}}
|
||||
// dataSource={data}
|
||||
columnsState={{
|
||||
persistenceKey: 'pro-table-singe-demos',
|
||||
persistenceType: 'localStorage',
|
||||
defaultValue: {
|
||||
option: {fixed: 'right', disable: true},
|
||||
},
|
||||
onChange(value) {
|
||||
console.log('value: ', value);
|
||||
},
|
||||
}}
|
||||
rowKey="title"
|
||||
search={{
|
||||
labelWidth: 'auto',
|
||||
}}
|
||||
options={{
|
||||
setting: {
|
||||
listsHeight: 400,
|
||||
},
|
||||
}}
|
||||
form={{
|
||||
// 由于配置了 transform,提交的参数与定义的不同这里需要转化一下
|
||||
syncToUrl: (values, type) => {
|
||||
if (type === 'get') {
|
||||
return {
|
||||
...values,
|
||||
created_at: [values.startTime, values.endTime],
|
||||
};
|
||||
}
|
||||
return values;
|
||||
},
|
||||
}}
|
||||
pagination={{
|
||||
pageSize: 5,
|
||||
onChange: (page) => console.log(page),
|
||||
}}
|
||||
dateFormatter="string"
|
||||
headerTitle="高级表格"
|
||||
request={async (params, sort, filter) => {
|
||||
console.log('params:', params);
|
||||
const response = await executeTableQuery(dv,source,params);
|
||||
console.log("response1",response);
|
||||
return {
|
||||
data: response,
|
||||
success: true,
|
||||
total: response.length,
|
||||
};
|
||||
}}
|
||||
toolBarRender={() => [
|
||||
<Button
|
||||
key="button"
|
||||
icon={<PlusOutlined/>}
|
||||
onClick={() => {
|
||||
actionRef.current?.reload();
|
||||
}}
|
||||
type="primary"
|
||||
>
|
||||
新建
|
||||
</Button>,
|
||||
<Dropdown
|
||||
key="menu"
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
label: '1st item',
|
||||
key: '1',
|
||||
},
|
||||
{
|
||||
label: '2nd item',
|
||||
key: '2',
|
||||
},
|
||||
{
|
||||
label: '3rd item',
|
||||
key: '3',
|
||||
},
|
||||
],
|
||||
}}
|
||||
>
|
||||
<Button>
|
||||
<EllipsisOutlined/>
|
||||
</Button>
|
||||
</Dropdown>,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default MyReactComponent;
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
// 运行时配置
|
||||
|
||||
// 全局初始化数据配置,用于 Layout 用户信息和权限初始化
|
||||
// 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate
|
||||
export async function getInitialState(): Promise<{ name: string }> {
|
||||
return { name: '@umijs/max' };
|
||||
}
|
||||
|
||||
export const layout = () => {
|
||||
return {
|
||||
logo: 'https://img.alicdn.com/tfs/TB1YHEpwUT1gK0jSZFhXXaAtVXa-28-27.svg',
|
||||
menu: {
|
||||
locale: false,
|
||||
},
|
||||
};
|
||||
};
|
||||
0
src/pages/calendarview/todo
Normal file
0
src/pages/calendarview/todo
Normal file
|
|
@ -1,12 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
const IndexPage: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Hello Umi</h1>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IndexPage;
|
||||
0
src/pages/kanbanview/todo
Normal file
0
src/pages/kanbanview/todo
Normal file
111
src/pages/listview/ListView.tsx
Normal file
111
src/pages/listview/ListView.tsx
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import {ProList} from '@ant-design/pro-components';
|
||||
import React from "react";
|
||||
import {formatValue} from "../GenerateColumns";
|
||||
import {getAPI} from 'obsidian-dataview';
|
||||
import {ViewProps} from "../../models/ViewProps";
|
||||
|
||||
|
||||
const ListView: React.FC<ViewProps> = ({app, source}) => {
|
||||
|
||||
const [metas, setMetas] = React.useState<any>({});
|
||||
|
||||
const dv = getAPI(app);
|
||||
|
||||
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);
|
||||
});
|
||||
// params参数去除current和pageSize
|
||||
delete params.current;
|
||||
delete params.pageSize;
|
||||
|
||||
console.log("list rows", rows);
|
||||
|
||||
const filteredData = rows.filter(item => {
|
||||
return Object.keys(params).every(key => {
|
||||
|
||||
if (params[key]) {
|
||||
// If item[key] is empty, return false directly.
|
||||
if (item[key]) {
|
||||
return item[key].toString().toLowerCase().includes(params[key].toString().toLowerCase());
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
return filteredData;
|
||||
}
|
||||
|
||||
function replaceFirstWord(source: string): string {
|
||||
// 将句子按空格分割成单词数组
|
||||
const words = source.split(' ');
|
||||
|
||||
// 如果第一个单词是"List",则替换为"Table"
|
||||
if (words[0] === 'list') {
|
||||
words[0] = 'table';
|
||||
}
|
||||
|
||||
// 将单词数组重新组合成句子
|
||||
return words.join(' ');
|
||||
}
|
||||
|
||||
const executeTableQuery = async (dvApi: any, source: any, params: any) => {
|
||||
// 定义查询语句
|
||||
// source是一个句子,将source的第一个单词List转成Table
|
||||
|
||||
const sql = replaceFirstWord(source);
|
||||
// 使用 Dataview API 执行查询
|
||||
const queryResult = await dvApi.query(sql);
|
||||
const tableData: any = parseTableResult(queryResult.value, params);
|
||||
return {tableData: tableData};
|
||||
|
||||
}
|
||||
|
||||
|
||||
return <ProList
|
||||
// search={{}}
|
||||
rowKey="name"
|
||||
headerTitle="ListView"
|
||||
request={async (params = {}) => {
|
||||
// console.log('params:', params);
|
||||
const response = await executeTableQuery(dv, source, params);
|
||||
const {tableData} = response;
|
||||
// console.log("listData", tableData);
|
||||
return Promise.resolve({
|
||||
data: tableData,
|
||||
success: true,
|
||||
total: tableData.length,
|
||||
// columns: columns,
|
||||
});
|
||||
}}
|
||||
pagination={{
|
||||
pageSize: 5,
|
||||
}}
|
||||
showActions="hover"
|
||||
metas={{
|
||||
title: {
|
||||
title: 'title',
|
||||
render: (_, record: any) => {
|
||||
console.log("list record", record);
|
||||
return <a>{record.File.path}</a>;
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
export default ListView;
|
||||
182
src/pages/tableview/TableView.tsx
Normal file
182
src/pages/tableview/TableView.tsx
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
import type {ActionType, ProColumns} from '@ant-design/pro-components';
|
||||
import {ProTable} from '@ant-design/pro-components';
|
||||
import {List, Tag} from 'antd';
|
||||
import React, {useEffect, useRef} from 'react';
|
||||
import {getAPI} from 'obsidian-dataview';
|
||||
import {formatValue} from "../GenerateColumns";
|
||||
import {App, TFile} from "obsidian";
|
||||
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);
|
||||
|
||||
const createColumns = (headers: string[]) => {
|
||||
let columns = [];
|
||||
const firstColumn: ProColumns<any> = {
|
||||
dataIndex: 'index',
|
||||
valueType: 'indexBorder',
|
||||
title: 'index',
|
||||
sorter: true,
|
||||
};
|
||||
columns.push(firstColumn);
|
||||
const dynamicColumns: ProColumns<any>[] = headers.map((header) => {
|
||||
return {
|
||||
title: header,
|
||||
dataIndex: header,
|
||||
sorter: (a, b) => a[header].display.toString().localeCompare(b[header].display.toString()),
|
||||
render: (_, record) => {
|
||||
const {type, path, display} = record[header];
|
||||
if (type === "datetime") {
|
||||
return display;
|
||||
}
|
||||
if (type === "link") {
|
||||
return <a onClick={() => {
|
||||
const file = app.vault.getAbstractFileByPath(path);
|
||||
if (file && file instanceof TFile) {
|
||||
const leaf = app.workspace.getLeaf();
|
||||
leaf.openFile(file);
|
||||
}
|
||||
}}>
|
||||
{display}
|
||||
</a>
|
||||
}
|
||||
if (type === "array") {
|
||||
return display.map((v: any) => {
|
||||
if (typeof v === "object") {
|
||||
return <List.Item>
|
||||
<a onClick={() => {
|
||||
const file = app.vault.getAbstractFileByPath(v.path);
|
||||
if (file && file instanceof TFile) {
|
||||
const leaf = app.workspace.getLeaf();
|
||||
leaf.openFile(file);
|
||||
}
|
||||
}}>
|
||||
{v.display}
|
||||
</a>
|
||||
</List.Item>
|
||||
} else {
|
||||
// tags
|
||||
return <List.Item>
|
||||
<Tag>{v}</Tag>
|
||||
</List.Item>
|
||||
}
|
||||
})
|
||||
}
|
||||
return display.toString();
|
||||
}
|
||||
};
|
||||
});
|
||||
columns.push(...dynamicColumns);
|
||||
return columns;
|
||||
}
|
||||
|
||||
function parseTableResult(value: any, params: any): Array<Record<string, any>> {
|
||||
const headers: string[] = value.headers;
|
||||
console.log("headers", headers);
|
||||
// console.log("valueData", value);
|
||||
|
||||
const rows: Array<Record<string, any>> = [];
|
||||
|
||||
value.values.forEach((row: any) => {
|
||||
const values: Record<string, any> = {};
|
||||
headers.forEach((header, index) => {
|
||||
const value = row[index];
|
||||
const resValue = formatValue(value);
|
||||
values[header] = resValue;
|
||||
});
|
||||
rows.push(values);
|
||||
});
|
||||
|
||||
delete params.current;
|
||||
delete params.pageSize;
|
||||
|
||||
// console.log("rowsData", rows);
|
||||
// console.log("paramsData", params);
|
||||
|
||||
const filteredData = rows.filter(item => {
|
||||
return Object.keys(params).every(key => {
|
||||
if (params[key]) {
|
||||
// console.log("param1 key", key);
|
||||
// If item[key] is empty, return false directly.
|
||||
if (item[key]) {
|
||||
// console.log("item[key]", item[key]);
|
||||
return item[key].display.toString().toLowerCase().includes(params[key].toString().toLowerCase());
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
return filteredData;
|
||||
}
|
||||
|
||||
const executeTableQuery = async (dvApi: any, source: any, params: any) => {
|
||||
const sql = source;
|
||||
|
||||
const queryResult = await dvApi.query(sql);
|
||||
|
||||
const tableData: any = parseTableResult(queryResult.value, params);
|
||||
|
||||
const headers: string[] = queryResult.value.headers;
|
||||
const columns = createColumns(headers);
|
||||
|
||||
return {tableData: tableData, columns: columns};
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log("source", source);
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<ProTable
|
||||
scroll={{x: 'max-content'}}
|
||||
actionRef={actionRef}
|
||||
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="title"
|
||||
search={{
|
||||
labelWidth: 'auto',
|
||||
}}
|
||||
pagination={{
|
||||
showSizeChanger: true,
|
||||
}}
|
||||
headerTitle="TablieView"
|
||||
request={async (params, sort, filter) => {
|
||||
console.log('params:', params);
|
||||
const response = await executeTableQuery(dv, source, params);
|
||||
const {tableData, columns} = response;
|
||||
|
||||
setColumns(columns);
|
||||
return Promise.resolve({
|
||||
data: tableData,
|
||||
success: true,
|
||||
total: tableData.length,
|
||||
// columns: columns,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default TableView;
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
import {HoverPopover, ItemView, WorkspaceLeaf} from 'obsidian';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import type MyPlugin from './main';
|
||||
import { Root,createRoot } from 'react-dom/client';
|
||||
|
||||
import FormDemo from "./pages/FormDemo";
|
||||
import TableDemo from "./pages/TableDemo";
|
||||
import AntdTableDemo from "./pages/AntdTableDemo";
|
||||
|
||||
|
||||
export class SampleView extends ItemView {
|
||||
plugin: MyPlugin;
|
||||
hoverPopover: HoverPopover | null;
|
||||
private sampleComponent: React.ReactElement;
|
||||
root: Root;
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, plugin: MyPlugin) {
|
||||
super(leaf);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
getDisplayText(): string {
|
||||
// TODO: Make this interactive: Either the active workspace or the local graph
|
||||
return 'Memos';
|
||||
}
|
||||
|
||||
getIcon(): string {
|
||||
return 'Memos';
|
||||
}
|
||||
|
||||
getViewType(): string {
|
||||
return "umi-view";
|
||||
}
|
||||
|
||||
|
||||
async onOpen(): Promise<void> {
|
||||
|
||||
this.sampleComponent = React.createElement(AntdTableDemo);
|
||||
this.root = createRoot(this.contentEl as HTMLElement);
|
||||
this.root.render(this.sampleComponent);
|
||||
|
||||
}
|
||||
|
||||
async onClose() {
|
||||
// Nothing to clean up.
|
||||
this.root.unmount();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue