mirror of
https://github.com/olcubo/obsidian-cubox.git
synced 2026-07-22 12:20:33 +00:00
Compare commits
No commits in common. "master" and "1.0.5" have entirely different histories.
17 changed files with 308 additions and 588 deletions
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "22.x"
|
||||
node-version: "18.x"
|
||||
cache: 'npm'
|
||||
|
||||
- name: Build plugin
|
||||
|
|
|
|||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -24,5 +24,3 @@ data.json
|
|||
coverage/
|
||||
|
||||
test/
|
||||
|
||||
.cursor
|
||||
|
|
|
|||
146
README.md
146
README.md
|
|
@ -1,25 +1,22 @@
|
|||
# Cubox Plugin for Obsidian
|
||||
|
||||
[中文文档](README.zh-CN.md)
|
||||
|
||||
The official Cubox plugin for Obsidian syncs articles and annotations from Cubox into your vault.
|
||||
The official Cubox plugin for Obsidian allows you to synchronize articles and annotations from Cubox to Obsidian.
|
||||
|
||||
## Features
|
||||
|
||||
- **One-way sync**: Cubox → Obsidian only. Changes in Obsidian are not sent back to Cubox.
|
||||
- **Automatic or manual sync**: Run on a schedule or trigger sync from the ribbon icon / command palette.
|
||||
- **Filtering**: Filter by folder, type, tags, and status.
|
||||
- **Templates**: Customize file names, front matter, and note body.
|
||||
- **Annotation sync**: Export highlights and notes from Cubox into Obsidian.
|
||||
- Automatic Sync: Periodically sync articles and annotations from Cubox to Obsidian
|
||||
- Filtering: Filter content by folder, type, tags, and status
|
||||
- Templates: Customize file names, front matter, and content format
|
||||
- Annotation Sync: Sync highlighted content from Cubox to Obsidian notes
|
||||
|
||||
## Installation
|
||||
|
||||
### Install from the Community
|
||||
|
||||
1. Open Obsidian settings
|
||||
2. Navigate to the **Community plugins** tab
|
||||
3. Click **Browse** and search for **Cubox**
|
||||
4. Click **Install**, then **Enable**
|
||||
2. Navigate to the "Community plugins" tab
|
||||
3. Click the "Browse" button and search for "Cubox"
|
||||
4. Click Install
|
||||
|
||||
### Manual Installation
|
||||
|
||||
|
|
@ -30,125 +27,16 @@ The official Cubox plugin for Obsidian syncs articles and annotations from Cubox
|
|||
|
||||
## Configuration
|
||||
|
||||
1. **Cubox server domain**: Select the domain you use (`cubox.cc` or `cubox.pro`)
|
||||
2. **Cubox API key**: Enter your API key or paste the full key link from Cubox web settings under **Extensions & Automation → API Extension**
|
||||
1. Cubox Server Domain: Select the Cubox server domain you use (cubox.cc or cubox.pro)
|
||||
2. Cubox API Key: Enter your Cubox API key or link (generate it in the Cubox web settings under Extensions & Automation - API Extension)
|
||||
|
||||
## Quick Start
|
||||
## Usage
|
||||
|
||||
1. Select the correct server domain and enter your API key.
|
||||
2. Configure **all four filters** before the first sync:
|
||||
- Folder filter
|
||||
- Tag filter
|
||||
- Type filter
|
||||
- Status filter
|
||||
3. Choose a target folder and review the default templates if needed.
|
||||
4. Trigger sync manually (ribbon icon or **Sync now** command), or set an auto-sync interval.
|
||||
|
||||
> **Important:** Sync will not run until every filter category has at least one option selected. The default empty filter state is intentional—you must choose what to sync.
|
||||
|
||||
## How Sync Works
|
||||
|
||||
Understanding these rules prevents most confusion:
|
||||
|
||||
### One-way, create-only
|
||||
|
||||
- Sync direction is **Cubox → Obsidian only**.
|
||||
- The plugin **creates new notes**. It does **not update** notes that already exist in your vault.
|
||||
- If you add new highlights, edit a title, or change tags in Cubox after a note was synced, those changes **will not appear** in the existing Obsidian note.
|
||||
|
||||
### Each Cubox card is synced once
|
||||
|
||||
- Every synced note stores the Cubox card `id` in front matter (always included for deduplication).
|
||||
- On later syncs, if a note with the same `id` already exists at the target path, that card is **skipped**.
|
||||
- The completion notice may show `X new items, Y skipped`. **Skipped** means the card was already synced—not an error.
|
||||
|
||||
### Incremental fetching
|
||||
|
||||
- The plugin remembers where the last sync stopped and only fetches **new cards** on subsequent runs.
|
||||
- Changing the **target folder** resets that cursor, so historical cards may be fetched again—but they are still subject to the one-time dedup rule above. Existing notes in the old folder are **not** updated.
|
||||
|
||||
### Annotations are a snapshot
|
||||
|
||||
- Highlights exported to Obsidian reflect what Cubox returned **at sync time**.
|
||||
- Sync again after you finish annotating in Cubox. A longer interval or manual sync helps avoid exporting incomplete highlights.
|
||||
|
||||
### Default template does not include full article text
|
||||
|
||||
- The default content template includes title, description, links, and highlights.
|
||||
- To sync the full article body, add `{{content}}` or `{{content_highlighted}}` to your **Content template** in settings.
|
||||
|
||||
## Filters
|
||||
|
||||
Only items that match **all** filter categories are synced:
|
||||
|
||||
| Filter | What it does |
|
||||
|--------|----------------|
|
||||
| **Folder** | Limit to selected Cubox folders, or choose **All items** |
|
||||
| **Tag** | Limit to specific tags, **No tags**, or **All items** |
|
||||
| **Type** | Article, Snippet, and/or **Others** (Memo, Image, Audio, Video, File) |
|
||||
| **Status** | **All items**, or restrict to read / starred / annotated items |
|
||||
|
||||
Tips:
|
||||
|
||||
- If many items seem missing, review every filter—not just folders.
|
||||
- Selecting multiple status options (e.g. read **and** starred) narrows results further.
|
||||
- If you only need articles with highlights, use the annotated status filter—but remember that already-synced notes still will not receive new highlights later.
|
||||
|
||||
## Templates
|
||||
|
||||
Use plugin settings to customize:
|
||||
|
||||
- **File name template** — Mustache variables such as `{{{title}}}` and `{{{create_time}}}`
|
||||
- **Metadata variables** — Front matter fields (the Cubox `id` is always written)
|
||||
- **Content template** — Note body layout, including `{{#highlights}}` blocks
|
||||
- **Date format** — Format used for date variables in templates
|
||||
|
||||
See the reference links on the settings page for full variable lists and examples.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Why don't my new highlights show up in Obsidian?
|
||||
|
||||
The plugin does not update existing notes. New or edited highlights in Cubox stay in Cubox unless you edit the Obsidian note yourself.
|
||||
|
||||
There is no built-in "re-sync and overwrite" option. Workarounds:
|
||||
|
||||
- Manually update the note in Obsidian, or
|
||||
- Delete the Obsidian note and sync again (only if you understand dedup and filter behavior)
|
||||
|
||||
### Can I sync only once, or does it keep syncing?
|
||||
|
||||
Both are possible:
|
||||
|
||||
- **Auto sync** runs on your chosen interval and picks up **new** Cubox cards.
|
||||
- **Manual sync** (ribbon icon or command palette → **Sync now**) does the same on demand.
|
||||
- Already-synced cards are skipped; the plugin is not limited to a single run.
|
||||
|
||||
### Why is nothing syncing?
|
||||
|
||||
Check these in order:
|
||||
|
||||
1. API key and server domain are correct
|
||||
2. **All four filters** are configured (sync is blocked if any filter is empty)
|
||||
3. Your Cubox items actually match the combined filter rules
|
||||
|
||||
### Why are many items missing?
|
||||
|
||||
Common causes:
|
||||
|
||||
1. **Filters too strict** — e.g. only annotated + specific tag + one folder
|
||||
2. **Type filter** — **Others** not selected, so memos/files are excluded
|
||||
3. **Tag filter** — only certain tags selected, so untagged items are excluded
|
||||
4. **Default template** — no `{{content}}`, so notes look shorter than expected (metadata + highlights only)
|
||||
5. **Synced too early** — highlights were still being edited in Cubox when the card was first exported
|
||||
|
||||
### What does "skipped" mean in the sync notice?
|
||||
|
||||
Those Cubox cards were already exported to Obsidian (matched by `id`). They were not synced again.
|
||||
|
||||
### Do Obsidian edits sync back to Cubox?
|
||||
|
||||
No. Sync is one-way: Cubox → Obsidian.
|
||||
1. Before setting up, ensure you have selected the correct server and entered the API key
|
||||
2. Only content that meets all filter conditions will be synced
|
||||
3. Refer to the settings page for reference links to template variables
|
||||
4. Each item is synced only once from Cubox, updates in Cubox will not be synced to Obsidian unless you change the target folder
|
||||
5. It is recommended to set a longer sync interval or use manual sync to prevent syncing unfinished annotations
|
||||
|
||||
## Dependencies
|
||||
|
||||
|
|
@ -157,4 +45,4 @@ No. Sync is one-way: Cubox → Obsidian.
|
|||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT License](https://choosealicense.com/licenses/mit/).
|
||||
This project is licensed under the [MIT License](https://choosealicense.com/licenses/mit/).
|
||||
160
README.zh-CN.md
160
README.zh-CN.md
|
|
@ -1,160 +0,0 @@
|
|||
# Cubox Obsidian 插件
|
||||
|
||||
[English](README.md)
|
||||
|
||||
Cubox 官方 Obsidian 插件,可将 Cubox 中的收藏与标注单向同步到 Obsidian 库中。
|
||||
|
||||
## 功能
|
||||
|
||||
- **单向同步**:仅 Cubox → Obsidian,Obsidian 中的修改不会回写到 Cubox
|
||||
- **自动或手动同步**:按间隔自动执行,或通过侧边栏图标 / 命令面板手动触发
|
||||
- **筛选**:按文件夹、类型、标签、状态过滤要同步的内容
|
||||
- **模板**:自定义文件名、元数据(front matter)与正文格式
|
||||
- **标注同步**:将 Cubox 中的高亮与笔记导出到 Obsidian
|
||||
|
||||
## 安装
|
||||
|
||||
### 从社区插件安装
|
||||
|
||||
1. 打开 Obsidian **设置**
|
||||
2. 进入 **社区插件**
|
||||
3. 点击 **浏览**,搜索 **Cubox**
|
||||
4. 点击 **安装**,然后 **启用**
|
||||
|
||||
### 手动安装
|
||||
|
||||
1. 下载最新的 `main.js`、`manifest.json` 和 `styles.css`
|
||||
2. 在库中创建 `.obsidian/plugins/obsidian-cubox` 文件夹
|
||||
3. 将上述文件复制到该文件夹
|
||||
4. 在 Obsidian 设置中启用插件
|
||||
|
||||
## 配置
|
||||
|
||||
1. **Cubox 服务器域名**:选择你使用的域名(`cubox.cc` 或 `cubox.pro`)
|
||||
2. **Cubox API Key**:填写 API Key,或粘贴 Cubox 网页端 **扩展与自动化 → API 扩展** 中的完整链接
|
||||
|
||||
## 快速开始
|
||||
|
||||
1. 选择正确的服务器域名并填写 API Key。
|
||||
2. **首次同步前,必须配置全部四项筛选**:
|
||||
- 文件夹筛选(Folder filter)
|
||||
- 标签筛选(Tag filter)
|
||||
- 类型筛选(Type filter)
|
||||
- 状态筛选(Status filter)
|
||||
3. 设置目标文件夹,按需调整默认模板。
|
||||
4. 手动同步(侧边栏图标或 **Sync now** 命令),或设置自动同步间隔。
|
||||
|
||||
> **重要:** 任一筛选项未配置时,同步不会执行。默认四项均为空是正常状态,需要你主动选择要同步的范围。
|
||||
|
||||
## 同步机制说明
|
||||
|
||||
理解以下规则,可以避免大多数使用困惑:
|
||||
|
||||
### 单向、只创建不更新
|
||||
|
||||
- 同步方向为 **Cubox → Obsidian**。
|
||||
- 插件只会 **创建新笔记**,**不会更新** 库中已存在的笔记。
|
||||
- 某条收藏同步到 Obsidian 之后,你在 Cubox 里新增标注、修改标题或标签,**不会** 自动反映到已有 Obsidian 笔记中。
|
||||
|
||||
### 每条 Cubox 收藏只同步一次
|
||||
|
||||
- 每条同步的笔记会在 front matter 中写入 Cubox 卡片 `id`(用于去重,始终会写入)。
|
||||
- 后续同步时,若目标路径下已存在相同 `id` 的笔记,该卡片会被 **跳过**。
|
||||
- 完成提示可能显示 `X new items, Y skipped`。**skipped** 表示该卡片此前已同步,**不是错误**。
|
||||
|
||||
### 增量拉取
|
||||
|
||||
- 插件会记录上次同步进度,后续运行主要拉取 **新的** Cubox 卡片。
|
||||
- 修改 **目标文件夹** 会重置同步游标,可能再次尝试拉取历史卡片——但仍受「每条只同步一次」规则约束;**旧文件夹里的笔记不会被更新**。
|
||||
|
||||
### 标注是同步时刻的快照
|
||||
|
||||
- 导出到 Obsidian 的标注,是同步时 Cubox 返回的数据。
|
||||
- 建议在 Cubox 中完成标注后再同步;使用较长间隔或手动同步,可减少「标注尚未写完就被导出」的情况。
|
||||
|
||||
### 默认模板不含文章正文
|
||||
|
||||
- 默认正文模板包含标题、摘要、链接和标注列表。
|
||||
- 若需要同步 **全文正文**,请在设置的 **Content template** 中加入 `{{content}}` 或 `{{content_highlighted}}`。
|
||||
|
||||
## 筛选说明
|
||||
|
||||
只有 **同时满足** 各项筛选条件的内容才会被同步:
|
||||
|
||||
| 筛选项 | 说明 |
|
||||
|--------|------|
|
||||
| **文件夹** | 指定 Cubox 文件夹,或选择 **All items**(全部) |
|
||||
| **标签** | 指定标签、**No tags**(无标签),或 **All items** |
|
||||
| **类型** | Article、Snippet,以及 **Others**(Memo、Image、Audio、Video、File) |
|
||||
| **状态** | **All items**,或限定已读 / 星标 / 有标注 |
|
||||
|
||||
提示:
|
||||
|
||||
- 若感觉「少同步了很多」,请逐项检查四项筛选,而不只是文件夹。
|
||||
- 同时勾选多种状态(例如已读 **且** 星标)会进一步缩小范围。
|
||||
- 若只要带标注的内容,可使用 annotated 状态筛选——但已同步过的笔记仍不会收到 Cubox 里新增的标注。
|
||||
|
||||
## 模板
|
||||
|
||||
可在插件设置中自定义:
|
||||
|
||||
- **File name template** — 文件名,支持 Mustache 变量,如 `{{{title}}}`、`{{{create_time}}}`
|
||||
- **Metadata variables** — front matter 字段(Cubox `id` 始终会写入)
|
||||
- **Content template** — 正文布局,含 `{{#highlights}}` 等块
|
||||
- **Date format** — 模板中日期的显示格式
|
||||
|
||||
设置页中的 reference 链接可查看完整变量说明与示例。
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 为什么在 Cubox 里新加的标注,Obsidian 里没有?
|
||||
|
||||
插件 **不会更新** 已存在的笔记。Cubox 中新增或修改的标注,不会自动写入 Obsidian,需要你在 Obsidian 中手动编辑。
|
||||
|
||||
目前没有内置的「重新同步并覆盖」功能。可选做法:
|
||||
|
||||
- 在 Obsidian 中手动更新笔记,或
|
||||
- 删除 Obsidian 中的对应笔记后再次同步(请先理解去重与筛选规则)
|
||||
|
||||
### 是不是只能同步一次?还能继续同步吗?
|
||||
|
||||
可以持续同步:
|
||||
|
||||
- **自动同步** 按你设置的间隔运行,拉取 **新** 的 Cubox 卡片。
|
||||
- **手动同步**(侧边栏图标或命令面板 → **Sync now**)随时触发,行为相同。
|
||||
- 已同步过的卡片会被跳过;插件 **并非** 只能运行一次。
|
||||
|
||||
### 为什么完全没有任何内容被同步?
|
||||
|
||||
请按顺序检查:
|
||||
|
||||
1. API Key 与服务器域名是否正确
|
||||
2. **四项筛选是否都已配置**(任一项为空则不会同步)
|
||||
3. Cubox 中的内容是否确实满足全部筛选条件
|
||||
|
||||
### 为什么感觉少同步了很多数据?
|
||||
|
||||
常见原因:
|
||||
|
||||
1. **筛选过严** — 例如同时限定「有标注 + 某标签 + 某文件夹」
|
||||
2. **类型筛选** — 未勾选 **Others**,备忘录、图片、文件等类型不会被同步
|
||||
3. **标签筛选** — 只选了部分标签,无标签条目会被排除
|
||||
4. **默认模板** — 未包含 `{{content}}`,笔记看起来比预期短(仅有元数据与标注,无全文)
|
||||
5. **同步过早** — 卡片首次同步时 Cubox 中的标注尚未写完
|
||||
|
||||
### 同步完成提示里的 "skipped" 是什么意思?
|
||||
|
||||
表示这些 Cubox 卡片此前已导出到 Obsidian(通过 `id` 匹配),本次被跳过,未再次写入。
|
||||
|
||||
### Obsidian 里的修改会同步回 Cubox 吗?
|
||||
|
||||
不会。同步是单向的:**Cubox → Obsidian**。
|
||||
|
||||
## 依赖
|
||||
|
||||
- [Mustache](https://mustache.github.io/):模板渲染
|
||||
- [Luxon](https://moment.github.io/luxon/#/formatting?id=table-of-tokens):日期时间处理
|
||||
|
||||
## 许可证
|
||||
|
||||
本项目采用 [MIT License](https://choosealicense.com/licenses/mit/)。
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "cubox-sync",
|
||||
"name": "Cubox",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.5",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Sync your Cubox articles & annotations",
|
||||
"author": "delphi-2015",
|
||||
|
|
|
|||
28
package-lock.json
generated
28
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "obsidian-cubox",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-cubox",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"luxon": "^3.1.1",
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
"@types/jest": "^29.5.0",
|
||||
"@types/luxon": "^3.4.2",
|
||||
"@types/mustache": "^4.2.5",
|
||||
"@types/node": "^22.15.0",
|
||||
"@types/node": "^16.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
"builtin-modules": "3.3.0",
|
||||
|
|
@ -29,9 +29,6 @@
|
|||
"ts-jest": "^29.1.0",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
|
|
@ -1639,14 +1636,10 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.19.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",
|
||||
"integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
"version": "16.18.126",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.126.tgz",
|
||||
"integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/stack-utils": {
|
||||
"version": "2.0.3",
|
||||
|
|
@ -5773,13 +5766,6 @@
|
|||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-cubox",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.5",
|
||||
"description": "This is a cubox plugin for Obsidian (https://obsidian.md)",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
@ -13,12 +13,9 @@
|
|||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mustache": "^4.2.5",
|
||||
"@types/node": "^22.15.0",
|
||||
"@types/node": "^16.11.6",
|
||||
"@types/luxon": "^3.4.2",
|
||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||
"@typescript-eslint/parser": "5.29.0",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { Notice, requestUrl } from 'obsidian';
|
|||
import { ALL_FOLDERS_ID } from './modal/folderSelectModal';
|
||||
import { ALL_ITEMS } from './modal/tagSelectModal';
|
||||
import { ALL_STATUS_ID } from './modal/statusSelectModal';
|
||||
import { expandTypeFilterForApi } from './modal/typeSelectModal';
|
||||
|
||||
export interface CuboxArticle {
|
||||
id: string;
|
||||
|
|
@ -51,13 +50,6 @@ interface ListResponse {
|
|||
data: CuboxArticle[];
|
||||
}
|
||||
|
||||
export class CuboxApiKeyMissingError extends Error {
|
||||
constructor(message = 'Cubox API key is missing or invalid.') {
|
||||
super(message);
|
||||
this.name = 'CuboxApiKeyMissingError';
|
||||
}
|
||||
}
|
||||
|
||||
interface ContentResponse {
|
||||
code: number;
|
||||
message: string;
|
||||
|
|
@ -160,9 +152,9 @@ export class CuboxApi {
|
|||
}
|
||||
}
|
||||
|
||||
// 添加类型过滤(Others 展开为 Memo/Image/Audio/Video/File)
|
||||
// 添加类型过滤
|
||||
if (params.typeFilter && params.typeFilter.length > 0) {
|
||||
requestBody.type_filters = expandTypeFilterForApi(params.typeFilter);
|
||||
requestBody.type_filters = params.typeFilter;
|
||||
}
|
||||
|
||||
// 添加状态过滤
|
||||
|
|
@ -190,10 +182,6 @@ export class CuboxApi {
|
|||
method: 'POST',
|
||||
body: JSON.stringify(requestBody)
|
||||
}) as ListResponse;
|
||||
|
||||
if (response.code === -1100) {
|
||||
throw new CuboxApiKeyMissingError(response.message || 'API key not found');
|
||||
}
|
||||
|
||||
const articles = response.data ?? [];
|
||||
const hasMore = articles && articles.length >= pageSize;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import type CuboxSyncPlugin from './main';
|
|||
import { FRONT_MATTER_VARIABLES } from './templateProcessor';
|
||||
import { ALL_FOLDERS_ID, FolderSelectModal } from './modal/folderSelectModal';
|
||||
import { filenameTemplateInstructions, metadataVariablesInstructions, contentTemplateInstructions, cuboxDateFormat, getFilenameReferenceLink, getMetadataReferenceLink, getContentReferenceLink, getDateReferenceLink, getInstructionUrl } from './templateInstructions';
|
||||
import { isFullTypeSelection, storedTypesToModalSelection, TypeSelectModal } from './modal/typeSelectModal';
|
||||
import { ALL_CONTENT_TYPES, TypeSelectModal } from './modal/typeSelectModal';
|
||||
import { ALL_STATUS_ID, StatusSelectModal } from './modal/statusSelectModal';
|
||||
import { ALL_ITEMS, TagSelectModal } from './modal/tagSelectModal';
|
||||
import { normalizePath } from 'obsidian';
|
||||
|
|
@ -266,14 +266,13 @@ export class CuboxSyncSettingTab extends PluginSettingTab {
|
|||
// 打开类型选择模态框
|
||||
const modal = new TypeSelectModal(
|
||||
this.app,
|
||||
storedTypesToModalSelection(this.plugin.settings.typeFilter),
|
||||
this.plugin.settings.typeFilter,
|
||||
async (selectedTypes) => {
|
||||
this.plugin.settings.typeFilter = selectedTypes;
|
||||
await this.plugin.saveSettings();
|
||||
// 更新按钮文本
|
||||
button.setButtonText(this.getTypeFilterButtonText());
|
||||
},
|
||||
this.plugin.settings.domain
|
||||
}
|
||||
);
|
||||
modal.open();
|
||||
}));
|
||||
|
|
@ -490,7 +489,7 @@ export class CuboxSyncSettingTab extends PluginSettingTab {
|
|||
textComponent.inputEl.disabled = true;
|
||||
} else {
|
||||
// 已选择域名,更新描述和链接
|
||||
const url = `https://${domain}/web/settings/extensions`;
|
||||
const url = `https://${domain}/my/settings/extensions`;
|
||||
const descEl = this.apiKeySetting.descEl;
|
||||
|
||||
// 清空现有描述
|
||||
|
|
@ -579,7 +578,7 @@ export class CuboxSyncSettingTab extends PluginSettingTab {
|
|||
const typeFilters = this.plugin.settings.typeFilter;
|
||||
if (!typeFilters || typeFilters.length === 0) {
|
||||
return 'Select...';
|
||||
} else if (isFullTypeSelection(typeFilters)) {
|
||||
} else if (typeFilters.length === ALL_CONTENT_TYPES.length) {
|
||||
return 'All types';
|
||||
} else {
|
||||
return `${typeFilters.length} selected`;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { addIcon, Notice, Plugin, TFile, TFolder } from 'obsidian';
|
||||
import { CuboxApi, CuboxApiKeyMissingError } from './cuboxApi';
|
||||
import { CuboxApi } from './cuboxApi';
|
||||
import { TemplateProcessor } from './templateProcessor';
|
||||
import { formatDateTime } from './utils';
|
||||
import { CuboxSyncSettingTab, CuboxSyncSettings, DEFAULT_SETTINGS } from './cuboxSetting';
|
||||
import { normalizeTypeFilterStorage } from './modal/typeSelectModal';
|
||||
|
||||
|
||||
export default class CuboxSyncPlugin extends Plugin {
|
||||
|
|
@ -55,7 +54,6 @@ export default class CuboxSyncPlugin extends Plugin {
|
|||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
this.settings.typeFilter = normalizeTypeFilterStorage(this.settings.typeFilter);
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
|
|
@ -211,10 +209,6 @@ export default class CuboxSyncPlugin extends Plugin {
|
|||
new Notice(message);
|
||||
} catch (error) {
|
||||
console.error('同步 Cubox 数据失败:', error);
|
||||
if (error instanceof CuboxApiKeyMissingError) {
|
||||
new Notice('Cubox API Key not found. Please set your API Key in plugin settings and try again.');
|
||||
return;
|
||||
}
|
||||
new Notice('Cubox sync failed. Please check settings or network.');
|
||||
} finally {
|
||||
this.settings.syncing = false;
|
||||
|
|
|
|||
|
|
@ -68,36 +68,48 @@ export class FolderSelectModal extends Modal {
|
|||
private createFolderList() {
|
||||
this.listEl.empty();
|
||||
|
||||
new Setting(this.listEl)
|
||||
.setName('All items')
|
||||
.addToggle(toggle => {
|
||||
toggle.setValue(this.selectedFolders.has(ALL_FOLDERS_ID));
|
||||
toggle.onChange(value => {
|
||||
this.handleFolderToggle(ALL_FOLDERS_ID, value);
|
||||
const allItemsSetting = new Setting(this.listEl)
|
||||
.setName('All items');
|
||||
|
||||
if (this.selectedFolders.has(ALL_FOLDERS_ID)) {
|
||||
allItemsSetting.settingEl.addClass('is-selected');
|
||||
}
|
||||
|
||||
allItemsSetting.settingEl.addEventListener('click', () => {
|
||||
const isCurrentlySelected = this.selectedFolders.has(ALL_FOLDERS_ID);
|
||||
this.handleFolderToggle(ALL_FOLDERS_ID, !isCurrentlySelected);
|
||||
this.redraw();
|
||||
});
|
||||
|
||||
// 添加每个文件夹的选项
|
||||
this.folders.forEach(folder => {
|
||||
const folderSetting = new Setting(this.listEl)
|
||||
.setName(folder.nested_name);
|
||||
|
||||
// 如果"All Items"被选中,禁用其他选项
|
||||
if (this.selectedFolders.has(ALL_FOLDERS_ID)) {
|
||||
folderSetting.settingEl.addClass('is-disabled');
|
||||
} else {
|
||||
// 添加选中状态的类
|
||||
if (this.selectedFolders.has(folder.id)) {
|
||||
folderSetting.settingEl.addClass('is-selected');
|
||||
}
|
||||
|
||||
// 添加点击事件
|
||||
folderSetting.settingEl.addEventListener('click', () => {
|
||||
const isCurrentlySelected = this.selectedFolders.has(folder.id);
|
||||
this.handleFolderToggle(folder.id, !isCurrentlySelected);
|
||||
this.redraw();
|
||||
});
|
||||
});
|
||||
|
||||
this.folders.forEach(folder => {
|
||||
const allOn = this.selectedFolders.has(ALL_FOLDERS_ID);
|
||||
const folderSetting = new Setting(this.listEl)
|
||||
.setName(folder.nested_name)
|
||||
.addToggle(toggle => {
|
||||
toggle.setDisabled(allOn);
|
||||
toggle.setValue(!allOn && this.selectedFolders.has(folder.id));
|
||||
if (!allOn) {
|
||||
toggle.onChange(value => {
|
||||
this.handleFolderToggle(folder.id, value);
|
||||
this.redraw();
|
||||
});
|
||||
}
|
||||
});
|
||||
if (allOn) {
|
||||
folderSetting.settingEl.addClass('is-disabled');
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private isFolderSelected(folderId: string): boolean {
|
||||
return this.selectedFolders.has(folderId);
|
||||
}
|
||||
|
||||
private handleFolderToggle(folderId: string, isSelected: boolean) {
|
||||
if (folderId === ALL_FOLDERS_ID) {
|
||||
if (isSelected) {
|
||||
|
|
|
|||
|
|
@ -85,29 +85,36 @@ export class StatusSelectModal extends Modal {
|
|||
|
||||
// 添加每个状态的选项
|
||||
this.statuses.forEach(status => {
|
||||
const allOn = this.selectedStatuses.has(ALL_STATUS_ID);
|
||||
const statusSetting = new Setting(this.listEl)
|
||||
.setName(status.name)
|
||||
.addToggle(toggle => {
|
||||
if (status.id === ALL_STATUS_ID) {
|
||||
toggle.setValue(this.selectedStatuses.has(ALL_STATUS_ID));
|
||||
toggle.onChange(value => {
|
||||
this.handleStatusToggle(ALL_STATUS_ID, value);
|
||||
this.redraw();
|
||||
});
|
||||
} else {
|
||||
toggle.setDisabled(allOn);
|
||||
toggle.setValue(!allOn && this.selectedStatuses.has(status.id));
|
||||
if (!allOn) {
|
||||
toggle.onChange(value => {
|
||||
this.handleStatusToggle(status.id, value);
|
||||
this.redraw();
|
||||
});
|
||||
}
|
||||
}
|
||||
.setName(status.name);
|
||||
|
||||
// 如果是"All Items"选项
|
||||
if (status.id === ALL_STATUS_ID) {
|
||||
// 添加选中状态的类
|
||||
if (this.selectedStatuses.has(status.id)) {
|
||||
statusSetting.settingEl.addClass('is-selected');
|
||||
}
|
||||
|
||||
statusSetting.settingEl.addEventListener('click', () => {
|
||||
const isCurrentlySelected = this.selectedStatuses.has(status.id);
|
||||
this.handleStatusToggle(status.id, !isCurrentlySelected);
|
||||
this.redraw();
|
||||
});
|
||||
if (status.id !== ALL_STATUS_ID && allOn) {
|
||||
statusSetting.settingEl.addClass('is-disabled');
|
||||
} else {
|
||||
// 如果"All Items"被选中,禁用其他选项
|
||||
if (this.selectedStatuses.has(ALL_STATUS_ID)) {
|
||||
statusSetting.settingEl.addClass('is-disabled');
|
||||
} else {
|
||||
if (this.selectedStatuses.has(status.id)) {
|
||||
statusSetting.settingEl.addClass('is-selected');
|
||||
}
|
||||
|
||||
statusSetting.settingEl.addEventListener('click', () => {
|
||||
const isCurrentlySelected = this.selectedStatuses.has(status.id);
|
||||
this.handleStatusToggle(status.id, !isCurrentlySelected);
|
||||
this.redraw();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,52 +66,69 @@ export class TagSelectModal extends Modal {
|
|||
private createTagList() {
|
||||
this.listEl.empty();
|
||||
|
||||
new Setting(this.listEl)
|
||||
.setName('All items')
|
||||
.addToggle(toggle => {
|
||||
toggle.setValue(this.selectedTags.has(ALL_ITEMS));
|
||||
toggle.onChange(value => {
|
||||
this.handleTagToggle(ALL_ITEMS, value);
|
||||
this.redraw();
|
||||
});
|
||||
});
|
||||
|
||||
const allOn = this.selectedTags.has(ALL_ITEMS);
|
||||
const noTagsSetting = new Setting(this.listEl)
|
||||
.setName('No tags')
|
||||
.addToggle(toggle => {
|
||||
toggle.setDisabled(allOn);
|
||||
toggle.setValue(!allOn && this.selectedTags.has(NO_TAGS_ID));
|
||||
if (!allOn) {
|
||||
toggle.onChange(value => {
|
||||
this.handleTagToggle(NO_TAGS_ID, value);
|
||||
this.redraw();
|
||||
});
|
||||
}
|
||||
});
|
||||
if (allOn) {
|
||||
noTagsSetting.settingEl.addClass('is-disabled');
|
||||
// 添加"All Items"选项
|
||||
const allItemsSetting = new Setting(this.listEl)
|
||||
.setName('All items');
|
||||
|
||||
if (this.selectedTags.has(ALL_ITEMS)) {
|
||||
allItemsSetting.settingEl.addClass('is-selected');
|
||||
}
|
||||
|
||||
|
||||
// 添加点击事件
|
||||
allItemsSetting.settingEl.addEventListener('click', () => {
|
||||
const isCurrentlySelected = this.selectedTags.has(ALL_ITEMS);
|
||||
this.handleTagToggle(ALL_ITEMS, !isCurrentlySelected);
|
||||
this.redraw();
|
||||
});
|
||||
|
||||
// 添加"No Tags"选项
|
||||
const noTagsSetting = new Setting(this.listEl)
|
||||
.setName('No tags');
|
||||
|
||||
// 如果"All Items"被选中,禁用其他选项
|
||||
if (this.selectedTags.has(ALL_ITEMS)) {
|
||||
noTagsSetting.settingEl.addClass('is-disabled');
|
||||
} else {
|
||||
// 添加选中状态的类
|
||||
if (this.selectedTags.has(NO_TAGS_ID)) {
|
||||
noTagsSetting.settingEl.addClass('is-selected');
|
||||
}
|
||||
|
||||
noTagsSetting.settingEl.addEventListener('click', () => {
|
||||
const isCurrentlySelected = this.selectedTags.has(NO_TAGS_ID);
|
||||
this.handleTagToggle(NO_TAGS_ID, !isCurrentlySelected);
|
||||
this.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
// 添加每个标签的选项
|
||||
this.tags.forEach(tag => {
|
||||
const tagSetting = new Setting(this.listEl)
|
||||
.setName(tag.nested_name)
|
||||
.addToggle(toggle => {
|
||||
toggle.setDisabled(allOn);
|
||||
toggle.setValue(!allOn && this.selectedTags.has(tag.id));
|
||||
if (!allOn) {
|
||||
toggle.onChange(value => {
|
||||
this.handleTagToggle(tag.id, value);
|
||||
this.redraw();
|
||||
});
|
||||
}
|
||||
});
|
||||
if (allOn) {
|
||||
.setName(tag.nested_name);
|
||||
|
||||
// 如果"All Items"被选中,禁用其他选项
|
||||
if (this.selectedTags.has(ALL_ITEMS)) {
|
||||
tagSetting.settingEl.addClass('is-disabled');
|
||||
} else {
|
||||
// 添加选中状态的类
|
||||
if (this.selectedTags.has(tag.id)) {
|
||||
tagSetting.settingEl.addClass('is-selected');
|
||||
}
|
||||
|
||||
tagSetting.settingEl.addEventListener('click', () => {
|
||||
const isCurrentlySelected = this.selectedTags.has(tag.id);
|
||||
this.handleTagToggle(tag.id, !isCurrentlySelected);
|
||||
this.redraw();
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private isTagSelected(tagId: string): boolean {
|
||||
return this.selectedTags.has(tagId);
|
||||
}
|
||||
|
||||
private handleTagToggle(tagId: string, isSelected: boolean) {
|
||||
if (tagId === ALL_ITEMS) {
|
||||
if (isSelected) {
|
||||
|
|
|
|||
|
|
@ -1,92 +1,29 @@
|
|||
import { App, Modal, Setting, Notice, setIcon } from 'obsidian';
|
||||
import { App, Modal, Setting, Notice } from 'obsidian';
|
||||
|
||||
/** API 层使用的五个类型,在 UI 中合并为 Others */
|
||||
export const OTHERS_MEMBER_TYPES = ['Memo', 'Image', 'Audio', 'Video', 'File'] as const;
|
||||
|
||||
const OTHERS_MEMBER_SET = new Set<string>(OTHERS_MEMBER_TYPES);
|
||||
|
||||
/** 配置中用于表示上述五类合并项的标识 */
|
||||
export const OTHERS_TYPE_ID = 'Others';
|
||||
|
||||
export const ALL_CONTENT_TYPES = ['Article', 'Snippet', OTHERS_TYPE_ID];
|
||||
|
||||
/**
|
||||
* 读盘或读入的 typeFilter 规范化:将历史配置中的 Memo/Image/... 合并为 Others
|
||||
*/
|
||||
export function normalizeTypeFilterStorage(typeFilter: string[] | undefined): string[] {
|
||||
if (!typeFilter || typeFilter.length === 0) return [];
|
||||
const hasLegacyOthers = OTHERS_MEMBER_TYPES.some((t) => typeFilter.includes(t));
|
||||
const next = typeFilter.filter((t) => !OTHERS_MEMBER_SET.has(t));
|
||||
if (hasLegacyOthers && !next.includes(OTHERS_TYPE_ID)) {
|
||||
next.push(OTHERS_TYPE_ID);
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* 任意历史成员被选中则视为 Others 选中,供类型选择弹窗初始状态使用
|
||||
*/
|
||||
export function storedTypesToModalSelection(stored: string[]): string[] {
|
||||
const result = new Set<string>();
|
||||
let othersSelected = false;
|
||||
for (const t of stored) {
|
||||
if (OTHERS_MEMBER_SET.has(t)) {
|
||||
othersSelected = true;
|
||||
} else if (t === OTHERS_TYPE_ID) {
|
||||
othersSelected = true;
|
||||
} else {
|
||||
result.add(t);
|
||||
}
|
||||
}
|
||||
if (othersSelected) {
|
||||
result.add(OTHERS_TYPE_ID);
|
||||
}
|
||||
return Array.from(result);
|
||||
}
|
||||
|
||||
/** 发起 API 请求时把 Others 展开为五个具体 type_filters */
|
||||
export function expandTypeFilterForApi(stored: string[]): string[] {
|
||||
const out: string[] = [];
|
||||
for (const t of stored) {
|
||||
if (t === OTHERS_TYPE_ID) {
|
||||
out.push(...OTHERS_MEMBER_TYPES);
|
||||
} else {
|
||||
out.push(t);
|
||||
}
|
||||
}
|
||||
return [...new Set(out)];
|
||||
}
|
||||
|
||||
/** Article + Snippet + Others(或历史上五个子类全选)视为「全部类型」 */
|
||||
export function isFullTypeSelection(stored: string[]): boolean {
|
||||
if (!stored || stored.length === 0) return false;
|
||||
const hasArticle = stored.includes('Article');
|
||||
const hasSnippet = stored.includes('Snippet');
|
||||
const hasOthers =
|
||||
stored.includes(OTHERS_TYPE_ID) ||
|
||||
OTHERS_MEMBER_TYPES.every((t) => stored.includes(t));
|
||||
return hasArticle && hasSnippet && hasOthers;
|
||||
}
|
||||
export const ALL_CONTENT_TYPES = [
|
||||
'Article',
|
||||
'Snippet',
|
||||
'Memo',
|
||||
'Image',
|
||||
'Audio',
|
||||
'Video',
|
||||
'File'
|
||||
];
|
||||
|
||||
export class TypeSelectModal extends Modal {
|
||||
private onSave: (selectedTypes: string[]) => void;
|
||||
private selectedTypes: Set<string> = new Set();
|
||||
private listEl: HTMLElement;
|
||||
private footerEl: HTMLElement;
|
||||
private domain: string;
|
||||
|
||||
constructor(
|
||||
app: App,
|
||||
initialSelected: string[] = [],
|
||||
onSave: (selectedTypes: string[]) => void,
|
||||
domain: string = ''
|
||||
) {
|
||||
constructor(app: App, initialSelected: string[] = [], onSave: (selectedTypes: string[]) => void) {
|
||||
super(app);
|
||||
this.onSave = onSave;
|
||||
this.domain = domain;
|
||||
|
||||
|
||||
// 初始化已选择的类型
|
||||
if (initialSelected && initialSelected.length > 0) {
|
||||
initialSelected.forEach((id) => {
|
||||
// 添加初始选择的类型
|
||||
initialSelected.forEach(id => {
|
||||
if (id) this.selectedTypes.add(id);
|
||||
});
|
||||
}
|
||||
|
|
@ -95,27 +32,29 @@ export class TypeSelectModal extends Modal {
|
|||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
|
||||
|
||||
contentEl.createEl('h2', { text: 'Manage Cubox content types to be synced' });
|
||||
contentEl.addClass('cubox-modal');
|
||||
|
||||
|
||||
this.listEl = contentEl.createDiv({ cls: 'type-list-container cubox-list-container' });
|
||||
this.footerEl = contentEl.createDiv({ cls: 'modal-footer' });
|
||||
|
||||
|
||||
// 创建类型列表
|
||||
this.createTypeList();
|
||||
|
||||
|
||||
const cancelButton = this.footerEl.createEl('button', { text: 'Cancel' });
|
||||
cancelButton.addEventListener('click', () => {
|
||||
this.close();
|
||||
});
|
||||
|
||||
|
||||
const saveButton = this.footerEl.createEl('button', { text: 'Done', cls: 'mod-cta' });
|
||||
saveButton.addEventListener('click', () => {
|
||||
// 检查是否至少选择了一个选项
|
||||
if (this.selectedTypes.size === 0) {
|
||||
new Notice('Please select at least one option.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const selectedTypes = Array.from(this.selectedTypes);
|
||||
this.onSave(selectedTypes);
|
||||
this.close();
|
||||
|
|
@ -124,38 +63,22 @@ export class TypeSelectModal extends Modal {
|
|||
|
||||
private createTypeList() {
|
||||
this.listEl.empty();
|
||||
|
||||
ALL_CONTENT_TYPES.forEach((typeId) => {
|
||||
const row = new Setting(this.listEl);
|
||||
if (typeId === OTHERS_TYPE_ID) {
|
||||
const nameFrag = document.createDocumentFragment();
|
||||
const label = document.createElement('span');
|
||||
label.textContent = OTHERS_TYPE_ID;
|
||||
nameFrag.appendChild(label);
|
||||
const infoEl = document.createElement('span');
|
||||
infoEl.className = 'cubox-others-info-icon';
|
||||
infoEl.setAttribute('aria-label', 'About Memo and file types');
|
||||
setIcon(infoEl, 'info');
|
||||
infoEl.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const url =
|
||||
this.domain === 'cubox.pro'
|
||||
? 'https://story.cubox.pro/memo-file-update'
|
||||
: 'https://cubox.cc/blog/memo-file-update/';
|
||||
window.open(url);
|
||||
});
|
||||
nameFrag.appendChild(infoEl);
|
||||
row.setName(nameFrag);
|
||||
} else {
|
||||
row.setName(typeId);
|
||||
|
||||
// 添加每个类型的选项
|
||||
ALL_CONTENT_TYPES.forEach(typeId => {
|
||||
const typeSetting = new Setting(this.listEl)
|
||||
.setName(typeId);
|
||||
|
||||
// 添加选中状态的类
|
||||
if (this.selectedTypes.has(typeId)) {
|
||||
typeSetting.settingEl.addClass('is-selected');
|
||||
}
|
||||
row.addToggle((toggle) => {
|
||||
toggle.setValue(this.selectedTypes.has(typeId));
|
||||
toggle.onChange((value) => {
|
||||
this.handleTypeToggle(typeId, value);
|
||||
this.redraw();
|
||||
});
|
||||
|
||||
// 添加点击事件
|
||||
typeSetting.settingEl.addEventListener('click', () => {
|
||||
const isCurrentlySelected = this.selectedTypes.has(typeId);
|
||||
this.handleTypeToggle(typeId, !isCurrentlySelected);
|
||||
this.redraw();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -176,4 +99,4 @@ export class TypeSelectModal extends Modal {
|
|||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -128,16 +128,6 @@ export class TemplateProcessor {
|
|||
frontMatter[alias] = article.tags;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (variable === 'create_time' && article.create_time) {
|
||||
frontMatter[alias] = formatDateTime(article.create_time, this.dateFormat);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (variable === 'update_time' && article.update_time) {
|
||||
frontMatter[alias] = formatDateTime(article.update_time, this.dateFormat);
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = this.getArticleProperty(article, variable);
|
||||
if (value) {
|
||||
|
|
@ -195,8 +185,8 @@ export class TemplateProcessor {
|
|||
highlights: [],
|
||||
tags: article.tags || [],
|
||||
highlights_length: article.highlights?.length || 0,
|
||||
create_time: article.create_time ? formatDateTime(article.create_time, this.dateFormat) : '',
|
||||
update_time: article.update_time ? formatDateTime(article.update_time, this.dateFormat) : ''
|
||||
formatted_create_time: article.create_time ? formatDateTime(article.create_time, this.dateFormat) : '',
|
||||
formatted_update_time: article.update_time ? formatDateTime(article.update_time, this.dateFormat) : ''
|
||||
};
|
||||
|
||||
// 处理高亮内容
|
||||
|
|
@ -248,7 +238,7 @@ export class TemplateProcessor {
|
|||
...highlight,
|
||||
text: text,
|
||||
note: note,
|
||||
create_time: highlight.create_time ? formatDateTime(highlight.create_time, this.dateFormat) : '',
|
||||
formatted_create_time: highlight.create_time ? formatDateTime(highlight.create_time, this.dateFormat) : ''
|
||||
};
|
||||
|
||||
// 确保所有必要的字段都存在
|
||||
|
|
|
|||
137
styles.css
137
styles.css
|
|
@ -78,53 +78,136 @@ If your plugin does not need CSS, delete this file.
|
|||
overflow-y: auto;
|
||||
padding-top: 20px;
|
||||
padding-right: 10px;
|
||||
padding-bottom: 12px;
|
||||
overflow-anchor: none;
|
||||
scroll-behavior: auto;
|
||||
height: 100%;
|
||||
max-height: 65vh;
|
||||
}
|
||||
|
||||
/* Modal lists use Obsidian Setting + ToggleComponent; keep light row chrome only */
|
||||
/* Setting item styles for modal dialogs */
|
||||
.cubox-list-container .setting-item {
|
||||
position: relative;
|
||||
padding-left: 36px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.1s ease;
|
||||
/* Extra vertical room so text/toggle don’t sit flush on the row dividers */
|
||||
padding-top: 10px;
|
||||
padding-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 40px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cubox-list-container .setting-item:hover {
|
||||
background-color: var(--background-modifier-hover);
|
||||
}
|
||||
|
||||
.cubox-list-container .setting-item.is-disabled {
|
||||
opacity: 0.45;
|
||||
/* Setting item name styles */
|
||||
.cubox-list-container .setting-item-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Custom checkbox styles */
|
||||
.cubox-list-container .setting-item::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid var(--checkbox-border-color, var(--background-modifier-border));
|
||||
border-radius: 3px;
|
||||
background-color: var(--checkbox-color, var(--background-primary));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* First child checkbox positioning */
|
||||
.cubox-list-container .setting-item:first-child::before {
|
||||
left: 10px;
|
||||
top: 12px;
|
||||
}
|
||||
|
||||
/* Other items checkbox positioning */
|
||||
.cubox-list-container .setting-item:not(:first-child)::before {
|
||||
left: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
/* Selected checkbox state */
|
||||
.cubox-list-container .setting-item.is-selected::before {
|
||||
background-color: var(--checkbox-color-checked, var(--interactive-accent));
|
||||
border-color: var(--checkbox-border-color-checked, var(--interactive-accent));
|
||||
}
|
||||
|
||||
/* Checkmark for selected items */
|
||||
.cubox-list-container .setting-item.is-selected::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 5px;
|
||||
height: 9px;
|
||||
border-right: 2px solid var(--text-on-accent);
|
||||
border-bottom: 2px solid var(--text-on-accent);
|
||||
}
|
||||
|
||||
/* First child checkmark positioning */
|
||||
.cubox-list-container .setting-item:first-child.is-selected::after {
|
||||
left: 14px;
|
||||
top: 13px;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
/* Other items checkmark positioning */
|
||||
.cubox-list-container .setting-item:not(:first-child).is-selected::after {
|
||||
left: 14px;
|
||||
top: 50%;
|
||||
transform: translateY(-60%) rotate(45deg);
|
||||
}
|
||||
|
||||
/* First item special styling */
|
||||
.cubox-list-container .setting-item:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.cubox-list-container .setting-item:first-child .setting-item-name {
|
||||
font-weight: 600;
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
position: relative;
|
||||
top: 6px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Disabled state styling */
|
||||
.cubox-list-container .setting-item.is-disabled {
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.cubox-list-container .setting-item.is-disabled::before {
|
||||
background-color: var(--background-primary);
|
||||
border-color: var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.cubox-list-container .setting-item.is-disabled::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 2px;
|
||||
background-color: var(--text-muted);
|
||||
left: 14px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Consistent line height for all items */
|
||||
.cubox-list-container .setting-item-heading,
|
||||
.cubox-list-container .setting-item-name {
|
||||
line-height: normal;
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
/* Override first row bold font for type modal */
|
||||
.type-list-container .setting-item:first-child .setting-item-name {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.cubox-others-info-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 6px;
|
||||
vertical-align: middle;
|
||||
cursor: var(--cursor);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.cubox-others-info-icon:hover {
|
||||
color: var(--text-accent);
|
||||
}
|
||||
|
||||
.cubox-others-info-icon svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
|
@ -4,7 +4,5 @@
|
|||
"1.0.2": "0.15.0",
|
||||
"1.0.3": "0.15.0",
|
||||
"1.0.4": "0.15.0",
|
||||
"1.0.5": "0.15.0",
|
||||
"1.0.6": "0.15.0",
|
||||
"1.0.7": "0.15.0"
|
||||
"1.0.5": "0.15.0"
|
||||
}
|
||||
Loading…
Reference in a new issue