Compare commits

..

84 commits

Author SHA1 Message Date
Daniel
1948bb3195
📝 Update changelogs
Signed-off-by: Daniel <845765@qq.com>
2025-09-19 22:12:47 +08:00
Daniel
d030963329
⬆️ Upgrade lute
Signed-off-by: Daniel <845765@qq.com>
2025-09-19 11:44:14 +08:00
Daniel
8b18389e95
🎨 Improve font loading https://github.com/siyuan-note/siyuan/issues/15879
Signed-off-by: Daniel <845765@qq.com>
2025-09-19 09:08:49 +08:00
Vanessa
40328523ad Merge remote-tracking branch 'origin/dev' into dev 2025-09-18 23:21:42 +08:00
Vanessa
7cc61ba650 🎨 https://github.com/siyuan-note/siyuan/pull/15876 2025-09-18 23:21:19 +08:00
Daniel
d372e871f4
🎨 Improve font loading https://github.com/siyuan-note/siyuan/issues/15879
Signed-off-by: Daniel <845765@qq.com>
2025-09-18 23:10:06 +08:00
Daniel
ea781d30ed
🎨 Improve font loading https://github.com/siyuan-note/siyuan/issues/15879
Signed-off-by: Daniel <845765@qq.com>
2025-09-18 22:02:13 +08:00
Achuan-2
f747739fd5
关联列支持一键复制现有关联 (#15876) 2025-09-18 21:14:18 +08:00
Daniel
dffd12477e
🐛 https://github.com/siyuan-note/siyuan/issues/15875
Signed-off-by: Daniel <845765@qq.com>
2025-09-18 20:49:23 +08:00
Vanessa
67f232076e Merge remote-tracking branch 'origin/dev' into dev 2025-09-18 19:57:07 +08:00
Vanessa
8ea8db64ec 🎨 https://github.com/siyuan-note/siyuan/issues/15873 2025-09-18 19:56:53 +08:00
Daniel
76372ae017
🎨 https://github.com/siyuan-note/siyuan/issues/15866
Signed-off-by: Daniel <845765@qq.com>
2025-09-18 16:46:50 +08:00
Daniel
4689c1fa18
🎨 Clean code
Signed-off-by: Daniel <845765@qq.com>
2025-09-18 16:38:07 +08:00
Daniel
5b19ff6b8a
🎨 Block ref elements retain their original styles when exported https://github.com/siyuan-note/siyuan/issues/15698
Signed-off-by: Daniel <845765@qq.com>
2025-09-18 16:38:07 +08:00
Vanessa
1f69a87cc5 🎨 https://github.com/siyuan-note/siyuan/issues/15797 2025-09-17 23:57:11 +08:00
Vanessa
3feea1c7a7 🎨 https://github.com/siyuan-note/siyuan/issues/15864 2025-09-17 22:19:42 +08:00
Vanessa
330da59c6f 🐛 https://github.com/siyuan-note/siyuan/issues/15867 2025-09-17 18:22:32 +08:00
Daniel
639431e70d
⬆️ Upgrade lute
Signed-off-by: Daniel <845765@qq.com>
2025-09-17 12:20:33 +08:00
Daniel
07259718a8
🎨 Improve rollup checkbox https://github.com/siyuan-note/siyuan/issues/15858
Signed-off-by: Daniel <845765@qq.com>
2025-09-17 11:18:12 +08:00
Vanessa
a62593b2f4 Merge remote-tracking branch 'origin/dev' into dev 2025-09-17 11:14:32 +08:00
Vanessa
bbd49a7b19 🎨 https://github.com/siyuan-note/siyuan/issues/15858 2025-09-17 11:14:19 +08:00
Github1977
605959ba27
📝 Update README_zh_CN.md (#15862)
补充1Panel面板部署
2025-09-17 11:05:15 +08:00
Vanessa
4a3bd72a5e 🎨 https://github.com/siyuan-note/siyuan/issues/15858 2025-09-17 10:49:58 +08:00
Vanessa
1d6a2c5d33 🎨 https://github.com/siyuan-note/siyuan/issues/15859 2025-09-17 10:10:41 +08:00
Vanessa
b50789fa3a 🎨 https://github.com/siyuan-note/siyuan/issues/15860 2025-09-17 10:01:59 +08:00
Vanessa
2ed6bc0de7 🎨 https://github.com/siyuan-note/siyuan/issues/15850 2025-09-17 09:40:34 +08:00
Vanessa
9a605e8692 🎨 汇总主键添加 emoji 2025-09-16 08:52:15 +08:00
Vanessa
71ac6d648b Merge remote-tracking branch 'origin/dev' into dev 2025-09-16 00:44:18 +08:00
Vanessa
4014d1ecdc 🎨 https://github.com/siyuan-note/siyuan/issues/15726 2025-09-16 00:44:03 +08:00
Daniel
1344859c1e
🎨 Add "Show unique values" to the calculation of the database rollup field https://github.com/siyuan-note/siyuan/issues/15852
Signed-off-by: Daniel <845765@qq.com>
2025-09-16 00:40:00 +08:00
Vanessa
a8b5c513ae Merge remote-tracking branch 'origin/dev' into dev 2025-09-16 00:12:16 +08:00
Vanessa
e2f0f241f8 🎨 https://github.com/siyuan-note/siyuan/issues/15726 2025-09-16 00:12:00 +08:00
Daniel
bd5dea7424
🎨 Add scenes for expanding and collapsing headings https://github.com/siyuan-note/siyuan/issues/15726
Signed-off-by: Daniel <845765@qq.com>
2025-09-16 00:09:31 +08:00
Daniel
812dadb452
🎨 Add "Show unique values" to the calculation of the database rollup field https://github.com/siyuan-note/siyuan/issues/15852
Signed-off-by: Daniel <845765@qq.com>
2025-09-16 00:09:31 +08:00
Vanessa
66125f4b1d Merge remote-tracking branch 'origin/dev' into dev 2025-09-15 23:39:04 +08:00
Vanessa
7a249761e2 🎨 https://github.com/siyuan-note/siyuan/issues/15726 2025-09-15 23:38:49 +08:00
Daniel
3f7421a393
🎨 Add scenes for expanding and collapsing headings https://github.com/siyuan-note/siyuan/issues/15726
Signed-off-by: Daniel <845765@qq.com>
2025-09-15 22:07:53 +08:00
Daniel
b8e67bec2d
⬆️ Upgrade lute
Signed-off-by: Daniel <845765@qq.com>
2025-09-15 20:13:02 +08:00
Daniel
fe9aba122c
🎨 Some APIs will undergo breaking changes 一些 API 将发生破坏性变更 https://github.com/siyuan-note/siyuan/issues/15727
Signed-off-by: Daniel <845765@qq.com>
2025-09-15 17:58:38 +08:00
Daniel
07cfc58642
🎨 The database rollup field supports using the relation field https://github.com/siyuan-note/siyuan/issues/15851
Signed-off-by: Daniel <845765@qq.com>
2025-09-15 17:55:58 +08:00
Daniel
9b62385e86
🎨 Extend the time the interface waits for kernel startup on the desktop https://github.com/siyuan-note/siyuan/issues/15853
Signed-off-by: Daniel <845765@qq.com>
2025-09-15 16:36:09 +08:00
Vanessa
49e07f92f5 🎨 https://github.com/siyuan-note/siyuan/issues/15842 2025-09-15 10:48:04 +08:00
Daniel
7acf6f5225
Database kanban view https://github.com/siyuan-note/siyuan/issues/8873
Signed-off-by: Daniel <845765@qq.com>
2025-09-14 22:42:28 +08:00
Daniel
3211d331fa
Database kanban view https://github.com/siyuan-note/siyuan/issues/8873
Signed-off-by: Daniel <845765@qq.com>
2025-09-14 22:17:55 +08:00
Daniel
2d8f4a3030
Database kanban view https://github.com/siyuan-note/siyuan/issues/8873
Signed-off-by: Daniel <845765@qq.com>
2025-09-14 21:55:00 +08:00
Daniel
b8538772f8
🎨 Improve Add to Database
Signed-off-by: Daniel <845765@qq.com>
2025-09-14 21:40:19 +08:00
Daniel
0084a6427b
🐛 Add to Database shows databases that are not in the document https://github.com/siyuan-note/siyuan/issues/15847
Signed-off-by: Daniel <845765@qq.com>
2025-09-14 21:25:06 +08:00
Vanessa
1b077c164c 💄 https://github.com/siyuan-note/siyuan/issues/15837 2025-09-14 21:07:44 +08:00
Daniel
ec77e2bafd
Database kanban view https://github.com/siyuan-note/siyuan/issues/8873
Signed-off-by: Daniel <845765@qq.com>
2025-09-14 12:52:11 +08:00
Vanessa
f8d1d1958b 🎨 https://github.com/siyuan-note/siyuan/issues/15835 2025-09-14 12:38:40 +08:00
Vanessa
327c3aaabb 🎨 https://github.com/siyuan-note/siyuan/issues/15835 2025-09-13 23:27:45 +08:00
Vanessa
c150c16273 Merge remote-tracking branch 'origin/dev' into dev 2025-09-13 12:25:27 +08:00
Vanessa
9fff044869 🎨 https://github.com/siyuan-note/siyuan/issues/15839 2025-09-13 12:25:13 +08:00
Daniel
be6e92ad7a
🐛 Optimize typography exception when code block contains ``` https://github.com/siyuan-note/siyuan/issues/15843
Signed-off-by: Daniel <845765@qq.com>
2025-09-13 11:59:08 +08:00
Vanessa
bcc0472ed0 🎨 https://github.com/siyuan-note/siyuan/issues/15839 2025-09-13 11:50:13 +08:00
Daniel
7faf981b69
🎨 When "Default fill created time" is enabled for database date fields, the automatically filled time value is incorrect https://github.com/siyuan-note/siyuan/issues/15828
Signed-off-by: Daniel <845765@qq.com>
2025-09-13 09:45:28 +08:00
Vanessa
370601d277 Merge remote-tracking branch 'origin/dev' into dev 2025-09-13 09:29:29 +08:00
Vanessa
a99a55b512 🎨 https://github.com/siyuan-note/siyuan/issues/15840 2025-09-13 09:29:17 +08:00
Daniel
ba0c6883e6
🐛 File history cannot load the correct view of database blocks https://github.com/siyuan-note/siyuan/issues/15841
Signed-off-by: Daniel <845765@qq.com>
2025-09-13 09:21:06 +08:00
Vanessa
41f72e65d3 🚨 2025-09-13 09:15:54 +08:00
Vanessa
b70328c7e7 🎨 https://github.com/siyuan-note/siyuan/issues/15821 2025-09-13 09:15:06 +08:00
Vanessa
2ff8c148bd 💄 https://github.com/siyuan-note/siyuan/issues/15837 2025-09-13 08:59:41 +08:00
Vanessa
5797872b97 💄 https://github.com/siyuan-note/siyuan/issues/15842 2025-09-13 08:45:16 +08:00
Daniel
9d569ad37b
🎨 Support arm64 version in Microsoft Store https://github.com/siyuan-note/siyuan/issues/15836
Signed-off-by: Daniel <845765@qq.com>
2025-09-12 22:30:36 +08:00
Daniel
eca318c4ff
🎨 https://github.com/siyuan-note/siyuan/issues/15833 point 2
Signed-off-by: Daniel <845765@qq.com>
2025-09-12 19:49:09 +08:00
QYLexpired
9f76274747
为面包屑解锁/锁定按钮增加属性 (#15820)
* Update index.ts

* Update onGet.ts

* Update onGet.ts
2025-09-12 19:48:25 +08:00
Vanessa
582f60f574 🐛 https://github.com/siyuan-note/siyuan/issues/15827 2025-09-12 19:42:21 +08:00
Vanessa
a2329077e7 Merge remote-tracking branch 'origin/dev' into dev 2025-09-12 19:25:53 +08:00
Vanessa
a63686cb05 🎨 https://github.com/siyuan-note/siyuan/issues/15833 2025-09-12 19:25:38 +08:00
Daniel
d6e7d0163a
Database kanban view https://github.com/siyuan-note/siyuan/issues/8873
Signed-off-by: Daniel <845765@qq.com>
2025-09-12 19:23:09 +08:00
Daniel
6cc6ef66f9
🎨 Improve database date fields to automatically fill in creation time https://github.com/siyuan-note/siyuan/issues/15828
🎨 Improve database date fields to automatically fill in creation time https://github.com/siyuan-note/siyuan/issues/15828
2025-09-12 19:23:09 +08:00
Vanessa
72c84f5f3d 🎨 https://github.com/siyuan-note/siyuan/issues/15831 2025-09-12 17:13:30 +08:00
Vanessa
043511c2b8 🎨 https://github.com/siyuan-note/siyuan/issues/15831 2025-09-12 12:33:07 +08:00
Vanessa
e37ab0db78 🎨 https://github.com/siyuan-note/siyuan/issues/15825 2025-09-12 12:16:44 +08:00
Vanessa
fd83286438 🎨 https://github.com/siyuan-note/siyuan/issues/15821 2025-09-11 22:44:32 +08:00
Vanessa
24285aae56 🎨 https://github.com/siyuan-note/siyuan/issues/15821 2025-09-11 22:27:51 +08:00
Vanessa
8cbefc8788 Merge remote-tracking branch 'origin/dev' into dev 2025-09-11 22:25:18 +08:00
Vanessa
deb9b933af 🎨 https://github.com/siyuan-note/siyuan/issues/15762 2025-09-11 22:25:02 +08:00
Daniel
8476ddd18c
🎨 Block ref elements retain their original styles when exported https://github.com/siyuan-note/siyuan/issues/15698
Signed-off-by: Daniel <845765@qq.com>
2025-09-11 17:54:25 +08:00
Daniel
ee4ddf89c1
🎨 Refresh the data in the interface after moving the document https://github.com/siyuan-note/siyuan/issues/15762
Signed-off-by: Daniel <845765@qq.com>
2025-09-10 21:39:14 +08:00
Vanessa
d5e7b27a11 Merge remote-tracking branch 'origin/dev' into dev 2025-09-10 17:54:03 +08:00
Vanessa
9c37468386 🎨 https://github.com/siyuan-note/siyuan/issues/15762 2025-09-10 17:53:51 +08:00
Daniel
de19d69f99
Improve database group view performance https://github.com/siyuan-note/siyuan/issues/15811
Signed-off-by: Daniel <845765@qq.com>
2025-09-10 16:50:11 +08:00
Vanessa
9a15b466f3 🎨 https://github.com/siyuan-note/siyuan/issues/15819 2025-09-10 16:47:12 +08:00
96 changed files with 2488 additions and 948 deletions

View file

@ -44,6 +44,7 @@
* [Unraid 部署](#unraid-部署) * [Unraid 部署](#unraid-部署)
* [宝塔面板 部署](#宝塔面板部署) * [宝塔面板 部署](#宝塔面板部署)
* [小皮面板 部署](#小皮面板部署) * [小皮面板 部署](#小皮面板部署)
* [1Panel面板 部署](#1Panel面板部署)
* [内部预览版](#内部预览版) * [内部预览版](#内部预览版)
* [🏘️ 社区](#-社区) * [🏘️ 社区](#-社区)
* [🛠️ 开发指南](#-开发指南) * [🛠️ 开发指南](#-开发指南)
@ -361,6 +362,42 @@ Publish parameters: --accessAuthCode=******(访问授权码)
</details> </details>
### 1Panel面板部署
<details>
<summary>1Panel面板 部署文档</summary>
#### 前提
- 仅适用于1Panel面板v1.10.32-lts及以上版本
- 安装1Panel面板前往[1Panel](https://1panel.cn/)官网,选择正式版安装脚本下载安装
#### 部署
1. 登录1Panel面板在左侧菜单栏中点击 `应用商店`
2. 在 `应用商店-实用工具` 中找到 `思源笔记`,点击`安装`,也可以在搜索框直接搜索
3. 配置访问授权码等基本信息,点击 `确定`
- 名称:应用名称,默认 `siyuan`
- 版本:默认最新发行版
- 端口:默认 `6806`
- 访问授权码:访问笔记时需要使用的`访问密码`
- 端口外部访问:如你需通过 `IP+Port` 直接访问,请勾选,同时会开放服务器防火墙端口
- CPU限制默认为0不限制可根据实际需要设置
- 内存限制默认为0不限制可根据实际需要设置
4. 提交后面板会自动进行应用安装启动,应用状态会变为`安装中`,大概需要`1-3`分钟,耐心等待安装完成
5. 当应用状态变为`已启动`后,点击左侧的网站,首次使用需要安装`OpenResty`,点击`安装`
6. 安装完成后,点击`网站`菜单栏左上角`创建`,在弹出的页面中选择`反向代理`
7. 在`主域名`填入你的域名,网站代号会自动生成,代理选择`http`,代理地址填写`127.0.0.1:6806`,点击`确定`
8. (可选) 配置你创建的网站,可根据需要配置`https`访问增强访问安全性
#### 访问思源笔记
- 如果你通过`OpenResty`反向代理反代了网站,并且填写了域名,请在浏览器输入`域名`访问
- 如你选择了 `端口外部访问`,请在浏览器地输入 `http://<1Panel面板IP>:6806` 访问
</details>
### 内部预览版 ### 内部预览版
我们会在有重大更新前发布内部预览版,请访问 [https://github.com/siyuan-note/insider](https://github.com/siyuan-note/insider)。 我们会在有重大更新前发布内部预览版,请访问 [https://github.com/siyuan-note/insider](https://github.com/siyuan-note/insider)。

View file

@ -1,5 +1,9 @@
document.body.insertAdjacentHTML('afterbegin', `<svg id="iconsAnt" style="position: absolute; width: 0; height: 0; overflow: hidden;" xmlns="http://www.w3.org/2000/svg"> document.body.insertAdjacentHTML('afterbegin', `<svg id="iconsAnt" style="position: absolute; width: 0; height: 0; overflow: hidden;" xmlns="http://www.w3.org/2000/svg">
<defs> <defs>
<symbol id="iconInclude" viewBox="0 0 32 32">
<path d="M22.091 2.435v7.747h7.582v13.65h-2.637v-11.013h-4.945v9.156h-9.436v5.11h11.036v2.637h-13.674v-7.747h-7.582v-13.65h2.637v11.013h4.945v-9.156h9.436l0-5.11h-11.036v-2.637h13.674zM19.454 12.82h-6.799v6.518l6.799 0v-6.518z"></path>
<path d="M1 1h5.604v5.604h-5.604zM25.396 1h5.604v5.604h-5.604zM25.396 25.396h5.604v5.604h-5.604zM1 25.396h5.604v5.604h-5.604z"></path>
</symbol>
<symbol id="iconGroups" viewBox="0 0 32 32"> <symbol id="iconGroups" viewBox="0 0 32 32">
<path d="M3.074 1c-0.646 0-1.17 0.524-1.17 1.17s0.524 1.17 1.17 1.17h25.851c0.646 0 1.17-0.524 1.17-1.17s-0.524-1.17-1.17-1.17v0zM3.074 17.526c-0.646 0-1.17 0.524-1.17 1.17s0.524 1.17 1.17 1.17h25.851c0.646 0 1.17-0.524 1.17-1.17s-0.524-1.17-1.17-1.17v0zM1.904 8.517c0-1.175 0.953-2.128 2.128-2.128v0h3.83c1.175 0 2.128 0.953 2.128 2.128v0 3.83c0 1.175-0.953 2.128-2.128 2.128v0h-3.83c-1.175 0-2.128-0.953-2.128-2.128v0zM4.245 8.73v3.404h3.404v-3.404zM14.085 6.389c-1.175 0-2.128 0.953-2.128 2.128v0 3.83c0 1.175 0.953 2.128 2.128 2.128v0h3.83c1.175 0 2.128-0.953 2.128-2.128v0-3.83c0-1.175-0.953-2.128-2.128-2.128v0zM14.298 12.134v-3.404h3.404v3.404zM22.011 8.517c0-1.175 0.953-2.128 2.128-2.128v0h3.83c1.175 0 2.128 0.953 2.128 2.128v0 3.83c0 1.175-0.953 2.128-2.128 2.128v0h-3.83c-1.175 0-2.128-0.953-2.128-2.128v0zM24.351 8.73v3.404h3.404v-3.404zM4.032 22.915c-1.175 0-2.128 0.953-2.128 2.128v0 3.83c0 1.175 0.953 2.128 2.128 2.128v0h3.83c1.175 0 2.128-0.953 2.128-2.128v0-3.83c0-1.175-0.953-2.128-2.128-2.128v0zM4.245 28.66v-3.404h3.404v3.404zM11.957 25.043c0-1.175 0.953-2.128 2.128-2.128v0h3.83c1.175 0 2.128 0.953 2.128 2.128v0 3.83c0 1.175-0.953 2.128-2.128 2.128v0h-3.83c-1.175 0-2.128-0.953-2.128-2.128v0zM14.298 25.255v3.404h3.404v-3.404zM24.138 22.915c-1.175 0-2.128 0.953-2.128 2.128v0 3.83c0 1.175 0.953 2.128 2.128 2.128v0h3.83c1.175 0 2.128-0.953 2.128-2.128v0-3.83c0-1.175-0.953-2.128-2.128-2.128v0zM24.351 28.66v-3.404h3.404v3.404z"></path> <path d="M3.074 1c-0.646 0-1.17 0.524-1.17 1.17s0.524 1.17 1.17 1.17h25.851c0.646 0 1.17-0.524 1.17-1.17s-0.524-1.17-1.17-1.17v0zM3.074 17.526c-0.646 0-1.17 0.524-1.17 1.17s0.524 1.17 1.17 1.17h25.851c0.646 0 1.17-0.524 1.17-1.17s-0.524-1.17-1.17-1.17v0zM1.904 8.517c0-1.175 0.953-2.128 2.128-2.128v0h3.83c1.175 0 2.128 0.953 2.128 2.128v0 3.83c0 1.175-0.953 2.128-2.128 2.128v0h-3.83c-1.175 0-2.128-0.953-2.128-2.128v0zM4.245 8.73v3.404h3.404v-3.404zM14.085 6.389c-1.175 0-2.128 0.953-2.128 2.128v0 3.83c0 1.175 0.953 2.128 2.128 2.128v0h3.83c1.175 0 2.128-0.953 2.128-2.128v0-3.83c0-1.175-0.953-2.128-2.128-2.128v0zM14.298 12.134v-3.404h3.404v3.404zM22.011 8.517c0-1.175 0.953-2.128 2.128-2.128v0h3.83c1.175 0 2.128 0.953 2.128 2.128v0 3.83c0 1.175-0.953 2.128-2.128 2.128v0h-3.83c-1.175 0-2.128-0.953-2.128-2.128v0zM24.351 8.73v3.404h3.404v-3.404zM4.032 22.915c-1.175 0-2.128 0.953-2.128 2.128v0 3.83c0 1.175 0.953 2.128 2.128 2.128v0h3.83c1.175 0 2.128-0.953 2.128-2.128v0-3.83c0-1.175-0.953-2.128-2.128-2.128v0zM4.245 28.66v-3.404h3.404v3.404zM11.957 25.043c0-1.175 0.953-2.128 2.128-2.128v0h3.83c1.175 0 2.128 0.953 2.128 2.128v0 3.83c0 1.175-0.953 2.128-2.128 2.128v0h-3.83c-1.175 0-2.128-0.953-2.128-2.128v0zM14.298 25.255v3.404h3.404v-3.404zM24.138 22.915c-1.175 0-2.128 0.953-2.128 2.128v0 3.83c0 1.175 0.953 2.128 2.128 2.128v0h3.83c1.175 0 2.128-0.953 2.128-2.128v0-3.83c0-1.175-0.953-2.128-2.128-2.128v0zM24.351 28.66v-3.404h3.404v3.404z"></path>
</symbol> </symbol>

View file

@ -2,5 +2,5 @@
"name": "ant", "name": "ant",
"author": "Vanessa", "author": "Vanessa",
"url": "https://github.com/Vanessa219", "url": "https://github.com/Vanessa219",
"version": "1.34.0" "version": "1.35.0"
} }

View file

@ -28,6 +28,12 @@
<body> <body>
<h2>SiYuan</h2> <h2>SiYuan</h2>
<div class="fn__clear"> <div class="fn__clear">
<div>
<svg>
<use xlink:href="#iconInclude"></use>
</svg>
iconGroups
</div>
<div> <div>
<svg> <svg>
<use xlink:href="#iconGroups"></use> <use xlink:href="#iconGroups"></use>

View file

@ -1,5 +1,9 @@
document.body.insertAdjacentHTML('afterbegin', `<svg id="iconsMaterial" style="position: absolute; width: 0; height: 0; overflow: hidden;" xmlns="http://www.w3.org/2000/svg"> document.body.insertAdjacentHTML('afterbegin', `<svg id="iconsMaterial" style="position: absolute; width: 0; height: 0; overflow: hidden;" xmlns="http://www.w3.org/2000/svg">
<defs> <defs>
<symbol id="iconInclude" viewBox="0 0 32 32">
<path d="M22.091 2.435v7.747h7.582v13.65h-2.637v-11.013h-4.945v9.156h-9.436v5.11h11.036v2.637h-13.674v-7.747h-7.582v-13.65h2.637v11.013h4.945v-9.156h9.436l0-5.11h-11.036v-2.637h13.674zM19.454 12.82h-6.799v6.518l6.799 0v-6.518z"></path>
<path d="M1 1h5.604v5.604h-5.604zM25.396 1h5.604v5.604h-5.604zM25.396 25.396h5.604v5.604h-5.604zM1 25.396h5.604v5.604h-5.604z"></path>
</symbol>
<symbol id="iconGroups" viewBox="0 0 32 32"> <symbol id="iconGroups" viewBox="0 0 32 32">
<path d="M3.074 1c-0.646 0-1.17 0.524-1.17 1.17s0.524 1.17 1.17 1.17h25.851c0.646 0 1.17-0.524 1.17-1.17s-0.524-1.17-1.17-1.17v0zM3.074 17.526c-0.646 0-1.17 0.524-1.17 1.17s0.524 1.17 1.17 1.17h25.851c0.646 0 1.17-0.524 1.17-1.17s-0.524-1.17-1.17-1.17v0zM1.904 8.517c0-1.175 0.953-2.128 2.128-2.128v0h3.83c1.175 0 2.128 0.953 2.128 2.128v0 3.83c0 1.175-0.953 2.128-2.128 2.128v0h-3.83c-1.175 0-2.128-0.953-2.128-2.128v0zM4.245 8.73v3.404h3.404v-3.404zM14.085 6.389c-1.175 0-2.128 0.953-2.128 2.128v0 3.83c0 1.175 0.953 2.128 2.128 2.128v0h3.83c1.175 0 2.128-0.953 2.128-2.128v0-3.83c0-1.175-0.953-2.128-2.128-2.128v0zM14.298 12.134v-3.404h3.404v3.404zM22.011 8.517c0-1.175 0.953-2.128 2.128-2.128v0h3.83c1.175 0 2.128 0.953 2.128 2.128v0 3.83c0 1.175-0.953 2.128-2.128 2.128v0h-3.83c-1.175 0-2.128-0.953-2.128-2.128v0zM24.351 8.73v3.404h3.404v-3.404zM4.032 22.915c-1.175 0-2.128 0.953-2.128 2.128v0 3.83c0 1.175 0.953 2.128 2.128 2.128v0h3.83c1.175 0 2.128-0.953 2.128-2.128v0-3.83c0-1.175-0.953-2.128-2.128-2.128v0zM4.245 28.66v-3.404h3.404v3.404zM11.957 25.043c0-1.175 0.953-2.128 2.128-2.128v0h3.83c1.175 0 2.128 0.953 2.128 2.128v0 3.83c0 1.175-0.953 2.128-2.128 2.128v0h-3.83c-1.175 0-2.128-0.953-2.128-2.128v0zM14.298 25.255v3.404h3.404v-3.404zM24.138 22.915c-1.175 0-2.128 0.953-2.128 2.128v0 3.83c0 1.175 0.953 2.128 2.128 2.128v0h3.83c1.175 0 2.128-0.953 2.128-2.128v0-3.83c0-1.175-0.953-2.128-2.128-2.128v0zM24.351 28.66v-3.404h3.404v3.404z"></path> <path d="M3.074 1c-0.646 0-1.17 0.524-1.17 1.17s0.524 1.17 1.17 1.17h25.851c0.646 0 1.17-0.524 1.17-1.17s-0.524-1.17-1.17-1.17v0zM3.074 17.526c-0.646 0-1.17 0.524-1.17 1.17s0.524 1.17 1.17 1.17h25.851c0.646 0 1.17-0.524 1.17-1.17s-0.524-1.17-1.17-1.17v0zM1.904 8.517c0-1.175 0.953-2.128 2.128-2.128v0h3.83c1.175 0 2.128 0.953 2.128 2.128v0 3.83c0 1.175-0.953 2.128-2.128 2.128v0h-3.83c-1.175 0-2.128-0.953-2.128-2.128v0zM4.245 8.73v3.404h3.404v-3.404zM14.085 6.389c-1.175 0-2.128 0.953-2.128 2.128v0 3.83c0 1.175 0.953 2.128 2.128 2.128v0h3.83c1.175 0 2.128-0.953 2.128-2.128v0-3.83c0-1.175-0.953-2.128-2.128-2.128v0zM14.298 12.134v-3.404h3.404v3.404zM22.011 8.517c0-1.175 0.953-2.128 2.128-2.128v0h3.83c1.175 0 2.128 0.953 2.128 2.128v0 3.83c0 1.175-0.953 2.128-2.128 2.128v0h-3.83c-1.175 0-2.128-0.953-2.128-2.128v0zM24.351 8.73v3.404h3.404v-3.404zM4.032 22.915c-1.175 0-2.128 0.953-2.128 2.128v0 3.83c0 1.175 0.953 2.128 2.128 2.128v0h3.83c1.175 0 2.128-0.953 2.128-2.128v0-3.83c0-1.175-0.953-2.128-2.128-2.128v0zM4.245 28.66v-3.404h3.404v3.404zM11.957 25.043c0-1.175 0.953-2.128 2.128-2.128v0h3.83c1.175 0 2.128 0.953 2.128 2.128v0 3.83c0 1.175-0.953 2.128-2.128 2.128v0h-3.83c-1.175 0-2.128-0.953-2.128-2.128v0zM14.298 25.255v3.404h3.404v-3.404zM24.138 22.915c-1.175 0-2.128 0.953-2.128 2.128v0 3.83c0 1.175 0.953 2.128 2.128 2.128v0h3.83c1.175 0 2.128-0.953 2.128-2.128v0-3.83c0-1.175-0.953-2.128-2.128-2.128v0zM24.351 28.66v-3.404h3.404v3.404z"></path>
</symbol> </symbol>

View file

@ -2,5 +2,5 @@
"name": "material", "name": "material",
"author": "Vanessa", "author": "Vanessa",
"url": "https://github.com/Vanessa219", "url": "https://github.com/Vanessa219",
"version": "1.34.0" "version": "1.35.0"
} }

View file

@ -36,6 +36,7 @@
"showAllEntriesIcons": "عرض أيقونات المدخلات", "showAllEntriesIcons": "عرض أيقونات المدخلات",
"wrapAllFields": "التفاف الحقول تلقائيًا", "wrapAllFields": "التفاف الحقول تلقائيًا",
"gallery": "بطاقة", "gallery": "بطاقة",
"kanban": "Kanban",
"newTag": "علامة جديدة", "newTag": "علامة جديدة",
"pleaseWait": "يرجى الانتظار...", "pleaseWait": "يرجى الانتظار...",
"reconnectPrompt": "بعد تبديل التطبيقات، سيستغرق الأمر بعض الوقت لاستعادة تشغيل نواة SiYuan. يرجى الانتظار بضع ثوانٍ أو النقر فوق الزر \"إعادة المحاولة\"", "reconnectPrompt": "بعد تبديل التطبيقات، سيستغرق الأمر بعض الوقت لاستعادة تشغيل نواة SiYuan. يرجى الانتظار بضع ثوانٍ أو النقر فوق الزر \"إعادة المحاولة\"",
@ -169,12 +170,14 @@
"cloudStoragePurgeConfirm": "‫سيؤدي تطهير التخزين السحابي إلى حذف جميع اللقطات غير المرجعية وعناصر البيانات ذات الصلة. <ul class='fn__list'><li>الرجاء التأكد من أن الأجهزة الأخرى توقفت عن المزامنة قبل التنفيذ</li><li>عملية التطهير تستغرق وقتاً طويلاً جداً، الرجاء التأكد من أن الشبكة مستقرة</li></ul>هل أنت متأكد من تنفيذها الآن؟‬", "cloudStoragePurgeConfirm": "‫سيؤدي تطهير التخزين السحابي إلى حذف جميع اللقطات غير المرجعية وعناصر البيانات ذات الصلة. <ul class='fn__list'><li>الرجاء التأكد من أن الأجهزة الأخرى توقفت عن المزامنة قبل التنفيذ</li><li>عملية التطهير تستغرق وقتاً طويلاً جداً، الرجاء التأكد من أن الشبكة مستقرة</li></ul>هل أنت متأكد من تنفيذها الآن؟‬",
"dragFill": "اسحب عمودياً لملء القيمة", "dragFill": "اسحب عمودياً لملء القيمة",
"switchReadonly": "تبديل وضع القراءة فقط", "switchReadonly": "تبديل وضع القراءة فقط",
"original": "القيمة الأصلية", "original": "عرض القيمة الأصلية",
"uniqueValues": "عرض القيم الفريدة",
"selectRelation": "الرجاء تحديد الحقل ذي الصلة أولاً", "selectRelation": "الرجاء تحديد الحقل ذي الصلة أولاً",
"backRelation": "ثنائي الاتجاه", "backRelation": "ثنائي الاتجاه",
"thisDatabase": "قاعدة البيانات هذه", "thisDatabase": "قاعدة البيانات هذه",
"relatedTo": "الربط بـ", "relatedTo": "الربط بـ",
"relation": "ربط", "relation": "ربط",
"relatedItems": "العناصر المرتبطة",
"rollup": "القيمة المحتسبة", "rollup": "القيمة المحتسبة",
"rollupProperty": "الخاصية", "rollupProperty": "الخاصية",
"rollupCalc": "الحساب بـ", "rollupCalc": "الحساب بـ",
@ -1387,6 +1390,7 @@
"_attrView": { "_attrView": {
"table": "جدول", "table": "جدول",
"gallery": "بطاقة", "gallery": "بطاقة",
"kanban": "Kanban",
"key": "المفتاح الرئيسي", "key": "المفتاح الرئيسي",
"select": "تحديد" "select": "تحديد"
}, },

View file

@ -36,6 +36,7 @@
"showAllEntriesIcons": "Eintragssymbole anzeigen", "showAllEntriesIcons": "Eintragssymbole anzeigen",
"wrapAllFields": "Felder automatisch umbrechen", "wrapAllFields": "Felder automatisch umbrechen",
"gallery": "Karte", "gallery": "Karte",
"kanban": "Kanban",
"newTag": "Neuer Tag", "newTag": "Neuer Tag",
"pleaseWait": "Bitte warten...", "pleaseWait": "Bitte warten...",
"reconnectPrompt": "Nach dem Wechseln der Anwendungen dauert es einige Zeit, bis der Betrieb des SiYuan-Kernels wiederhergestellt ist. Bitte warten Sie einige Sekunden oder klicken Sie auf die Schaltfläche „Erneut versuchen“", "reconnectPrompt": "Nach dem Wechseln der Anwendungen dauert es einige Zeit, bis der Betrieb des SiYuan-Kernels wiederhergestellt ist. Bitte warten Sie einige Sekunden oder klicken Sie auf die Schaltfläche „Erneut versuchen“",
@ -169,12 +170,14 @@
"cloudStoragePurgeConfirm": "Das Bereinigen des Cloud-Speichers löscht alle nicht referenzierten Schnappschüsse und zugehörigen Datenobjekte vollständig. <ul class='fn__list'><li>Bitte stellen Sie sicher, dass andere Geräte die Synchronisation pausiert haben, bevor Sie fortfahren</li><li>Der Bereinigungsvorgang kann sehr zeitaufwendig sein, bitte stellen Sie sicher, dass das Netzwerk stabil ist</li></ul>Sind Sie sicher, dass Sie es jetzt ausführen möchten?", "cloudStoragePurgeConfirm": "Das Bereinigen des Cloud-Speichers löscht alle nicht referenzierten Schnappschüsse und zugehörigen Datenobjekte vollständig. <ul class='fn__list'><li>Bitte stellen Sie sicher, dass andere Geräte die Synchronisation pausiert haben, bevor Sie fortfahren</li><li>Der Bereinigungsvorgang kann sehr zeitaufwendig sein, bitte stellen Sie sicher, dass das Netzwerk stabil ist</li></ul>Sind Sie sicher, dass Sie es jetzt ausführen möchten?",
"dragFill": "Vertikal ziehen, um Werte zu füllen", "dragFill": "Vertikal ziehen, um Werte zu füllen",
"switchReadonly": "In den Nur-Lesen-Modus wechseln", "switchReadonly": "In den Nur-Lesen-Modus wechseln",
"original": "Original", "original": "Originalwert anzeigen",
"uniqueValues": "Eindeutige Werte anzeigen",
"selectRelation": "Bitte wählen Sie zuerst die zugehörige Spalte aus", "selectRelation": "Bitte wählen Sie zuerst die zugehörige Spalte aus",
"backRelation": "Bidirektional", "backRelation": "Bidirektional",
"thisDatabase": "Diese Datenbank", "thisDatabase": "Diese Datenbank",
"relatedTo": "Verbindung zu", "relatedTo": "Verbindung zu",
"relation": "Beziehung", "relation": "Beziehung",
"relatedItems": "Verknüpfte Einträge",
"rollup": "Rollup", "rollup": "Rollup",
"rollupProperty": "Eigenschaft", "rollupProperty": "Eigenschaft",
"rollupCalc": "Berechnen", "rollupCalc": "Berechnen",
@ -1387,6 +1390,7 @@
"_attrView": { "_attrView": {
"table": "Tabelle", "table": "Tabelle",
"gallery": "Karte", "gallery": "Karte",
"kanban": "Kanban",
"key": "Primärschlüssel", "key": "Primärschlüssel",
"select": "Auswählen" "select": "Auswählen"
}, },

View file

@ -36,6 +36,7 @@
"showAllEntriesIcons": "Show entry icons", "showAllEntriesIcons": "Show entry icons",
"wrapAllFields": "Auto-wrap fields", "wrapAllFields": "Auto-wrap fields",
"gallery": "Card", "gallery": "Card",
"kanban": "Kanban",
"newTag": "New tag", "newTag": "New tag",
"pleaseWait": "Please wait...", "pleaseWait": "Please wait...",
"reconnectPrompt": "After switching applications, it will take some time to restore the SiYuan kernel operation. Please wait a few seconds or click the \"Retry\" button", "reconnectPrompt": "After switching applications, it will take some time to restore the SiYuan kernel operation. Please wait a few seconds or click the \"Retry\" button",
@ -169,12 +170,14 @@
"cloudStoragePurgeConfirm": "Purging the cloud storage will completely delete all unreferenced snapshots and related data objects. <ul class='fn__list'><li>Please ensure that other devices have paused sync before execution</li><li>The purge operation is very time-consuming, please ensure that the network is stable</li></ul>Are you sure to execute it now?", "cloudStoragePurgeConfirm": "Purging the cloud storage will completely delete all unreferenced snapshots and related data objects. <ul class='fn__list'><li>Please ensure that other devices have paused sync before execution</li><li>The purge operation is very time-consuming, please ensure that the network is stable</li></ul>Are you sure to execute it now?",
"dragFill": "Drag vertically to fill value", "dragFill": "Drag vertically to fill value",
"switchReadonly": "Switch read-only mode", "switchReadonly": "Switch read-only mode",
"original": "Original", "original": "Show original values",
"uniqueValues": "Show unique values",
"selectRelation": "Please select the related field first", "selectRelation": "Please select the related field first",
"backRelation": "Bidirectional", "backRelation": "Bidirectional",
"thisDatabase": "This database", "thisDatabase": "This database",
"relatedTo": "Relation to", "relatedTo": "Relation to",
"relation": "Relation", "relation": "Relation",
"relatedItems": "Related Items",
"rollup": "Rollup", "rollup": "Rollup",
"rollupProperty": "Property", "rollupProperty": "Property",
"rollupCalc": "Calculate", "rollupCalc": "Calculate",
@ -1387,6 +1390,7 @@
"_attrView": { "_attrView": {
"table": "Table", "table": "Table",
"gallery": "Card", "gallery": "Card",
"kanban": "Kanban",
"key": "Primary Key", "key": "Primary Key",
"select": "Select" "select": "Select"
}, },

View file

@ -36,6 +36,7 @@
"showAllEntriesIcons": "Mostrar íconos de entradas", "showAllEntriesIcons": "Mostrar íconos de entradas",
"wrapAllFields": "Ajuste automático de campos", "wrapAllFields": "Ajuste automático de campos",
"gallery": "Tarjeta", "gallery": "Tarjeta",
"kanban": "Kanban",
"newTag": "Nueva etiqueta", "newTag": "Nueva etiqueta",
"pleaseWait": "Por favor, espere...", "pleaseWait": "Por favor, espere...",
"reconnectPrompt": "Después de cambiar de aplicación, tomará algún tiempo restaurar el funcionamiento del núcleo de SiYuan. Espere unos segundos o haga clic en el botón \"Reintentar\"", "reconnectPrompt": "Después de cambiar de aplicación, tomará algún tiempo restaurar el funcionamiento del núcleo de SiYuan. Espere unos segundos o haga clic en el botón \"Reintentar\"",
@ -169,12 +170,14 @@
"cloudStoragePurgeConfirm": "Al purgar el almacenamiento en la nube se eliminarán por completo todas las instantáneas sin referencia y los objetos de datos relacionados.<ul class='fn__list'><li>Asegúrese de que otros dispositivos hayan pausado la sincronización antes de la ejecución</li><li>La operación de purga requiere mucho tiempo; asegúrese de que la red esté estable</li></ul>¿Está seguro de ejecutarla ahora?", "cloudStoragePurgeConfirm": "Al purgar el almacenamiento en la nube se eliminarán por completo todas las instantáneas sin referencia y los objetos de datos relacionados.<ul class='fn__list'><li>Asegúrese de que otros dispositivos hayan pausado la sincronización antes de la ejecución</li><li>La operación de purga requiere mucho tiempo; asegúrese de que la red esté estable</li></ul>¿Está seguro de ejecutarla ahora?",
"dragFill": "Arrastra verticalmente para llenar valores", "dragFill": "Arrastra verticalmente para llenar valores",
"switchReadonly": "Cambiar modo de sólo lectura", "switchReadonly": "Cambiar modo de sólo lectura",
"original": "Original", "original": "Mostrar valor original",
"uniqueValues": "Mostrar valores únicos",
"selectRelation": "Seleccione primero la columna relacionada", "selectRelation": "Seleccione primero la columna relacionada",
"backRelation": "Bidireccional", "backRelation": "Bidireccional",
"thisDatabase": "Esta base de datos", "thisDatabase": "Esta base de datos",
"relatedTo": "Relación con", "relatedTo": "Relación con",
"relation": "Relación", "relation": "Relación",
"relatedItems": "Elementos relacionados",
"rollup": "Acumular", "rollup": "Acumular",
"rollupProperty": "Propiedad", "rollupProperty": "Propiedad",
"rollupCalc": "Calcular", "rollupCalc": "Calcular",
@ -1387,6 +1390,7 @@
"_attrView": { "_attrView": {
"table": "Tabla", "table": "Tabla",
"gallery": "Tarjeta", "gallery": "Tarjeta",
"kanban": "Kanban",
"key": "Clave principal", "key": "Clave principal",
"select": "Selección" "select": "Selección"
}, },

View file

@ -36,6 +36,7 @@
"showAllEntriesIcons": "Afficher les icônes des entrées", "showAllEntriesIcons": "Afficher les icônes des entrées",
"wrapAllFields": "Retour automatique des champs", "wrapAllFields": "Retour automatique des champs",
"gallery": "Carte", "gallery": "Carte",
"kanban": "Kanban",
"newTag": "Nouvelle étiquette", "newTag": "Nouvelle étiquette",
"pleaseWait": "Veuillez patienter...", "pleaseWait": "Veuillez patienter...",
"reconnectPrompt": "Après avoir changé d'application, il faudra un certain temps pour rétablir le fonctionnement du noyau SiYuan. Veuillez patienter quelques secondes ou cliquer sur le bouton « Réessayer »", "reconnectPrompt": "Après avoir changé d'application, il faudra un certain temps pour rétablir le fonctionnement du noyau SiYuan. Veuillez patienter quelques secondes ou cliquer sur le bouton « Réessayer »",
@ -169,12 +170,14 @@
"cloudStoragePurgeConfirm": "La purge du stockage cloud supprimera complètement tous les instantanés non référencés et les objets de données associés. <ul class='fn__list'><li>Veuillez vous assurer que la synchronisation des autres appareils a été suspendue avant l'exécution</li><li>L'opération de purge prend beaucoup de temps, veuillez vous assurer que le réseau est stable</li></ul>Êtes-vous sûr de l'exécuter maintenant ?", "cloudStoragePurgeConfirm": "La purge du stockage cloud supprimera complètement tous les instantanés non référencés et les objets de données associés. <ul class='fn__list'><li>Veuillez vous assurer que la synchronisation des autres appareils a été suspendue avant l'exécution</li><li>L'opération de purge prend beaucoup de temps, veuillez vous assurer que le réseau est stable</li></ul>Êtes-vous sûr de l'exécuter maintenant ?",
"dragFill": "Faites glisser verticalement pour remplir les valeurs", "dragFill": "Faites glisser verticalement pour remplir les valeurs",
"switchReadonly": "Changer de mode lecture seule", "switchReadonly": "Changer de mode lecture seule",
"original": "Originale", "original": "Afficher la valeur d'origine",
"uniqueValues": "Afficher les valeurs uniques",
"selectRelation": "Veuillez d'abord sélectionner la colonne associée", "selectRelation": "Veuillez d'abord sélectionner la colonne associée",
"backRelation": "Bidirectionnel", "backRelation": "Bidirectionnel",
"thisDatabase": "Cette base de données", "thisDatabase": "Cette base de données",
"relatedTo": " Relation avec ", "relatedTo": " Relation avec ",
"relation": "Relation", "relation": "Relation",
"relatedItems": "Éléments liés",
"rollup": "Rollup", "rollup": "Rollup",
"rollupProperty": "Propriété", "rollupProperty": "Propriété",
"rollupCalc": "Calculer", "rollupCalc": "Calculer",
@ -1387,6 +1390,7 @@
"_attrView": { "_attrView": {
"table": "Tableau", "table": "Tableau",
"gallery": "Carte", "gallery": "Carte",
"kanban": "Kanban",
"key": "Clé primaire", "key": "Clé primaire",
"select": "Sélectionner" "select": "Sélectionner"
}, },

View file

@ -36,6 +36,7 @@
"showAllEntriesIcons": "הצג סמלי כניסות", "showAllEntriesIcons": "הצג סמלי כניסות",
"wrapAllFields": "עטיפת שדות אוטומטית", "wrapAllFields": "עטיפת שדות אוטומטית",
"gallery": "כרטיס", "gallery": "כרטיס",
"kanban": "קאנבן",
"newTag": "תג חדש", "newTag": "תג חדש",
"pleaseWait": "אנא המתן...", "pleaseWait": "אנא המתן...",
"reconnectPrompt": "לאחר מעבר בין יישומים, יידרש זמן מה כדי לשחזר את פעולת ליבת SiYuan. אנא המתן מספר שניות או לחץ על כפתור \"נסה שוב\"", "reconnectPrompt": "לאחר מעבר בין יישומים, יידרש זמן מה כדי לשחזר את פעולת ליבת SiYuan. אנא המתן מספר שניות או לחץ על כפתור \"נסה שוב\"",
@ -169,12 +170,14 @@
"cloudStoragePurgeConfirm": "ניקוי האחסון בענן ימחוק לחלוטין את כל הצילומים הלא מתייחסים וכל האובייקטים הנתונים הקשורים. <ul class='fn__list'><li>אנא ודא שהמכשירים האחרים הפסיקו סנכרון לפני הביצוע</li><li>הניקוי מאוד לוקח זמן, אנא ודא שהרשת יציבה</li></ul>האם אתה בטוח לבצע את זה עכשיו?", "cloudStoragePurgeConfirm": "ניקוי האחסון בענן ימחוק לחלוטין את כל הצילומים הלא מתייחסים וכל האובייקטים הנתונים הקשורים. <ul class='fn__list'><li>אנא ודא שהמכשירים האחרים הפסיקו סנכרון לפני הביצוע</li><li>הניקוי מאוד לוקח זמן, אנא ודא שהרשת יציבה</li></ul>האם אתה בטוח לבצע את זה עכשיו?",
"dragFill": "גרור אנכית כדי למלא ערכים", "dragFill": "גרור אנכית כדי למלא ערכים",
"switchReadonly": "עבר למצב קריאה בלבד", "switchReadonly": "עבר למצב קריאה בלבד",
"original": "מקורי", "original": "הצג ערך מקורי",
"uniqueValues": "הצג ערכים ייחודיים",
"selectRelation": "אנא בחר קודם את העמודה הקשורה", "selectRelation": "אנא בחר קודם את העמודה הקשורה",
"backRelation": "דו-כיווני", "backRelation": "דו-כיווני",
"thisDatabase": "בסיס הנתונים הזה", "thisDatabase": "בסיס הנתונים הזה",
"relatedTo": "קשר ל", "relatedTo": "קשר ל",
"relation": "קשר", "relation": "קשר",
"relatedItems": "פריטים קשורים",
"rollup": "סיכום", "rollup": "סיכום",
"rollupProperty": "מאפיין", "rollupProperty": "מאפיין",
"rollupCalc": "חישוב", "rollupCalc": "חישוב",
@ -1387,6 +1390,7 @@
"_attrView": { "_attrView": {
"table": "טבלה", "table": "טבלה",
"gallery": "כרטיס", "gallery": "כרטיס",
"kanban": "קאנבן",
"key": "מפתח ראשי", "key": "מפתח ראשי",
"select": "בחר" "select": "בחר"
}, },

View file

@ -36,6 +36,7 @@
"showAllEntriesIcons": "Mostra icone delle voci", "showAllEntriesIcons": "Mostra icone delle voci",
"wrapAllFields": "Avvolgi automaticamente i campi", "wrapAllFields": "Avvolgi automaticamente i campi",
"gallery": "Scheda", "gallery": "Scheda",
"kanban": "Kanban",
"newTag": "Nuova etichetta", "newTag": "Nuova etichetta",
"pleaseWait": "Attendere prego...", "pleaseWait": "Attendere prego...",
"reconnectPrompt": "Dopo aver cambiato applicazione, ci vorrà un po' di tempo per ripristinare il funzionamento del kernel SiYuan. Attendere qualche secondo o fare clic sul pulsante \"Riprova\"", "reconnectPrompt": "Dopo aver cambiato applicazione, ci vorrà un po' di tempo per ripristinare il funzionamento del kernel SiYuan. Attendere qualche secondo o fare clic sul pulsante \"Riprova\"",
@ -169,12 +170,14 @@
"cloudStoragePurgeConfirm": "La pulizia dello storage cloud eliminerà completamente tutti gli snapshot non referenziati e gli oggetti dati correlati. <ul class='fn__list'><li>Assicurati che gli altri dispositivi abbiano sospeso la sincronizzazione prima dell'esecuzione</li><li>L'operazione di pulizia è molto lunga, assicurati che la rete sia stabile</li></ul>Sei sicuro di voler eseguire ora?", "cloudStoragePurgeConfirm": "La pulizia dello storage cloud eliminerà completamente tutti gli snapshot non referenziati e gli oggetti dati correlati. <ul class='fn__list'><li>Assicurati che gli altri dispositivi abbiano sospeso la sincronizzazione prima dell'esecuzione</li><li>L'operazione di pulizia è molto lunga, assicurati che la rete sia stabile</li></ul>Sei sicuro di voler eseguire ora?",
"dragFill": "Trascina verticalmente per riempire i valori", "dragFill": "Trascina verticalmente per riempire i valori",
"switchReadonly": "Passa alla modalità di sola lettura", "switchReadonly": "Passa alla modalità di sola lettura",
"original": "Originale", "original": "Mostra valore originale",
"uniqueValues": "Mostra valori unici",
"selectRelation": "Seleziona prima il campo correlato", "selectRelation": "Seleziona prima il campo correlato",
"backRelation": "Bidirezionale", "backRelation": "Bidirezionale",
"thisDatabase": "Questo database", "thisDatabase": "Questo database",
"relatedTo": "Relazionato a", "relatedTo": "Relazionato a",
"relation": "Relazione", "relation": "Relazione",
"relatedItems": "Elementi correlati",
"rollup": "Rollup", "rollup": "Rollup",
"rollupProperty": "Proprietà", "rollupProperty": "Proprietà",
"rollupCalc": "Calcola", "rollupCalc": "Calcola",
@ -1387,6 +1390,7 @@
"_attrView": { "_attrView": {
"table": "Tabella", "table": "Tabella",
"gallery": "Scheda", "gallery": "Scheda",
"kanban": "Kanban",
"key": "Chiave primaria", "key": "Chiave primaria",
"select": "Seleziona" "select": "Seleziona"
}, },

View file

@ -36,6 +36,7 @@
"showAllEntriesIcons": "エントリアイコンを表示", "showAllEntriesIcons": "エントリアイコンを表示",
"wrapAllFields": "フィールドを自動折り返し", "wrapAllFields": "フィールドを自動折り返し",
"gallery": "カード", "gallery": "カード",
"kanban": "カンバン",
"newTag": "新しいタグ", "newTag": "新しいタグ",
"pleaseWait": "しばらくお待ちください...", "pleaseWait": "しばらくお待ちください...",
"reconnectPrompt": "アプリを切り替えた後、思源カーネルの実行を再開するには少し時間がかかります。数秒待つか、「再試行」ボタンをクリックしてください", "reconnectPrompt": "アプリを切り替えた後、思源カーネルの実行を再開するには少し時間がかかります。数秒待つか、「再試行」ボタンをクリックしてください",
@ -169,12 +170,14 @@
"cloudStoragePurgeConfirm": "クラウドストレージを消去すると、参照されていないスナップショットと関連データオブジェクトが完全に削除されます。<ul class='fn__list'><li>実行前に他のデバイスが同期を一時停止していることを確認してください</li><li>消去操作は非常に時間がかかるため、ネットワークが安定していることを確認してください</li></ul>今すぐ実行してもよろしいですか?", "cloudStoragePurgeConfirm": "クラウドストレージを消去すると、参照されていないスナップショットと関連データオブジェクトが完全に削除されます。<ul class='fn__list'><li>実行前に他のデバイスが同期を一時停止していることを確認してください</li><li>消去操作は非常に時間がかかるため、ネットワークが安定していることを確認してください</li></ul>今すぐ実行してもよろしいですか?",
"dragFill": "値を埋めるために垂直にドラッグ", "dragFill": "値を埋めるために垂直にドラッグ",
"switchReadonly": "読み取り専用モードの切り替え", "switchReadonly": "読み取り専用モードの切り替え",
"original": "元の値", "original": "元の値を表示",
"uniqueValues": "一意の値を表示",
"selectRelation": "最初に関連する列を選択してください", "selectRelation": "最初に関連する列を選択してください",
"backRelation": "双方向の関連付け", "backRelation": "双方向の関連付け",
"thisDatabase": "現在のデータベース", "thisDatabase": "現在のデータベース",
"relatedTo": "関連付け", "relatedTo": "関連付け",
"relation": "関連", "relation": "関連",
"relatedItems": "関連項目",
"rollup": "集計", "rollup": "集計",
"rollupProperty": "属性", "rollupProperty": "属性",
"rollupCalc": "計算方法", "rollupCalc": "計算方法",
@ -1387,6 +1390,7 @@
"_attrView": { "_attrView": {
"table": "テーブル", "table": "テーブル",
"gallery": "カード", "gallery": "カード",
"kanban": "カンバン",
"key": "プライマリキー", "key": "プライマリキー",
"select": "選択" "select": "選択"
}, },

View file

@ -36,6 +36,7 @@
"showAllEntriesIcons": "Pokaż ikony wpisów", "showAllEntriesIcons": "Pokaż ikony wpisów",
"wrapAllFields": "Automatyczne zawijanie pól", "wrapAllFields": "Automatyczne zawijanie pól",
"gallery": "Karta", "gallery": "Karta",
"kanban": "Kanban",
"newTag": "Nowy tag", "newTag": "Nowy tag",
"pleaseWait": "Proszę czekać...", "pleaseWait": "Proszę czekać...",
"reconnectPrompt": "Po przełączeniu aplikacji ponowne uruchomienie jądra SiYuan może zająć trochę czasu. Proszę poczekać kilka sekund lub kliknąć przycisk „Ponów próbę”", "reconnectPrompt": "Po przełączeniu aplikacji ponowne uruchomienie jądra SiYuan może zająć trochę czasu. Proszę poczekać kilka sekund lub kliknąć przycisk „Ponów próbę”",
@ -169,12 +170,14 @@
"cloudStoragePurgeConfirm": "Oczyszczenie pamięci w chmurze całkowicie usunie wszystkie nieodwołane zrzuty i powiązane obiekty danych. <ul class='fn__list'><li>Proszę upewnić się, że inne urządzenia wstrzymały synchronizację przed aktem</li><li>Operacja oczyszczania jest czasochłonna, proszę upewnić się, że sieć jest stabilna</li></ul>Czy na pewno chcesz to wykonać teraz?", "cloudStoragePurgeConfirm": "Oczyszczenie pamięci w chmurze całkowicie usunie wszystkie nieodwołane zrzuty i powiązane obiekty danych. <ul class='fn__list'><li>Proszę upewnić się, że inne urządzenia wstrzymały synchronizację przed aktem</li><li>Operacja oczyszczania jest czasochłonna, proszę upewnić się, że sieć jest stabilna</li></ul>Czy na pewno chcesz to wykonać teraz?",
"dragFill": "Przeciągnij w pionie, aby wypełnić wartości", "dragFill": "Przeciągnij w pionie, aby wypełnić wartości",
"switchReadonly": "Przełącz tryb tylko do odczytu", "switchReadonly": "Przełącz tryb tylko do odczytu",
"original": "Oryginalny", "original": "Pokaż wartość oryginalną",
"uniqueValues": "Pokaż unikalne wartości",
"selectRelation": "Proszę najpierw wybrać powiązaną kolumnę", "selectRelation": "Proszę najpierw wybrać powiązaną kolumnę",
"backRelation": "Dwukierunkowa", "backRelation": "Dwukierunkowa",
"thisDatabase": "Ta baza danych", "thisDatabase": "Ta baza danych",
"relatedTo": "Powiązane z", "relatedTo": "Powiązane z",
"relation": "Relacja", "relation": "Relacja",
"relatedItems": "Powiązane elementy",
"rollup": "Skumuluje", "rollup": "Skumuluje",
"rollupProperty": "Właściwość", "rollupProperty": "Właściwość",
"rollupCalc": "Oblicz", "rollupCalc": "Oblicz",
@ -1387,6 +1390,7 @@
"_attrView": { "_attrView": {
"table": "Tabela", "table": "Tabela",
"gallery": "Karta", "gallery": "Karta",
"kanban": "Kanban",
"key": "Klucz główny", "key": "Klucz główny",
"select": "Wybierz" "select": "Wybierz"
}, },

View file

@ -36,6 +36,7 @@
"showAllEntriesIcons": "Mostrar ícones de entradas", "showAllEntriesIcons": "Mostrar ícones de entradas",
"wrapAllFields": "Quebrar automaticamente os campos", "wrapAllFields": "Quebrar automaticamente os campos",
"gallery": "Cartão", "gallery": "Cartão",
"kanban": "Kanban",
"newTag": "Nova tag", "newTag": "Nova tag",
"pleaseWait": "Por favor, aguarde...", "pleaseWait": "Por favor, aguarde...",
"reconnectPrompt": "Após alternar aplicativos, levará algum tempo para restaurar a operação do kernel SiYuan. Por favor, aguarde alguns segundos ou clique no botão \"Tentar novamente\"", "reconnectPrompt": "Após alternar aplicativos, levará algum tempo para restaurar a operação do kernel SiYuan. Por favor, aguarde alguns segundos ou clique no botão \"Tentar novamente\"",
@ -169,12 +170,14 @@
"cloudStoragePurgeConfirm": "Limpar o armazenamento em nuvem excluirá completamente todos os instantâneos não referenciados e objetos de dados relacionados. <ul class='fn__list'><li>Certifique-se de que outros dispositivos pausaram a sincronização antes da execução</li><li>A operação de limpeza é muito demorada, certifique-se de que a rede está estável</li></ul>Tem certeza que deseja executar agora?", "cloudStoragePurgeConfirm": "Limpar o armazenamento em nuvem excluirá completamente todos os instantâneos não referenciados e objetos de dados relacionados. <ul class='fn__list'><li>Certifique-se de que outros dispositivos pausaram a sincronização antes da execução</li><li>A operação de limpeza é muito demorada, certifique-se de que a rede está estável</li></ul>Tem certeza que deseja executar agora?",
"dragFill": "Arrastar verticalmente para preencher valor", "dragFill": "Arrastar verticalmente para preencher valor",
"switchReadonly": "Alternar modo somente leitura", "switchReadonly": "Alternar modo somente leitura",
"original": "Original", "original": "Exibir valor original",
"uniqueValues": "Exibir valores únicos",
"selectRelation": "Por favor, selecione o campo relacionado primeiro", "selectRelation": "Por favor, selecione o campo relacionado primeiro",
"backRelation": "Bidirecional", "backRelation": "Bidirecional",
"thisDatabase": "Este banco de dados", "thisDatabase": "Este banco de dados",
"relatedTo": "Relacionado a", "relatedTo": "Relacionado a",
"relation": "Relação", "relation": "Relação",
"relatedItems": "Itens relacionados",
"rollup": "Rollup", "rollup": "Rollup",
"rollupProperty": "Propriedade", "rollupProperty": "Propriedade",
"rollupCalc": "Calcular", "rollupCalc": "Calcular",
@ -1387,6 +1390,7 @@
"_attrView": { "_attrView": {
"table": "Tabela", "table": "Tabela",
"gallery": "Cartão", "gallery": "Cartão",
"kanban": "Kanban",
"key": "Chave Primária", "key": "Chave Primária",
"select": "Selecionar" "select": "Selecionar"
}, },

View file

@ -36,6 +36,7 @@
"showAllEntriesIcons": "Показать значки записей", "showAllEntriesIcons": "Показать значки записей",
"wrapAllFields": "Автоматический перенос полей", "wrapAllFields": "Автоматический перенос полей",
"gallery": "Карточка", "gallery": "Карточка",
"kanban": "Канбан",
"newTag": "Новый тег", "newTag": "Новый тег",
"pleaseWait": "Пожалуйста, подождите...", "pleaseWait": "Пожалуйста, подождите...",
"reconnectPrompt": "После переключения приложений потребуется некоторое время, чтобы восстановить работу ядра SiYuan. Пожалуйста, подождите несколько секунд или нажмите кнопку «Повторить»", "reconnectPrompt": "После переключения приложений потребуется некоторое время, чтобы восстановить работу ядра SiYuan. Пожалуйста, подождите несколько секунд или нажмите кнопку «Повторить»",
@ -169,12 +170,14 @@
"cloudStoragePurgeConfirm": "Очистка облачного хранилища полностью удалит все неиспользуемые снимки и связанные с ними объекты данных. <ul class='fn__list'><li>Пожалуйста, убедитесь, что другие устройства приостановили синхронизацию перед выполнением</li><li>Операция очистки занимает много времени, пожалуйста, убедитесь, что сеть стабильна</li></ul>Вы уверены, что хотите выполнить её сейчас?", "cloudStoragePurgeConfirm": "Очистка облачного хранилища полностью удалит все неиспользуемые снимки и связанные с ними объекты данных. <ul class='fn__list'><li>Пожалуйста, убедитесь, что другие устройства приостановили синхронизацию перед выполнением</li><li>Операция очистки занимает много времени, пожалуйста, убедитесь, что сеть стабильна</li></ul>Вы уверены, что хотите выполнить её сейчас?",
"dragFill": "Перетащите вертикально, чтобы заполнить значение", "dragFill": "Перетащите вертикально, чтобы заполнить значение",
"switchReadonly": "Переключить режим только для чтения", "switchReadonly": "Переключить режим только для чтения",
"original": "Оригинал", "original": "Показать исходное значение",
"uniqueValues": "Показать уникальные значения",
"selectRelation": "Пожалуйста, сначала выберите связанную колонку", "selectRelation": "Пожалуйста, сначала выберите связанную колонку",
"backRelation": "Двустороннее", "backRelation": "Двустороннее",
"thisDatabase": "Эта база данных", "thisDatabase": "Эта база данных",
"relatedTo": "Связано с", "relatedTo": "Связано с",
"relation": "Связь", "relation": "Связь",
"relatedItems": "Связанные элементы",
"rollup": "Свод", "rollup": "Свод",
"rollupProperty": "Свойство", "rollupProperty": "Свойство",
"rollupCalc": "Вычислить", "rollupCalc": "Вычислить",
@ -1387,6 +1390,7 @@
"_attrView": { "_attrView": {
"table": "Таблица", "table": "Таблица",
"gallery": "Карточка", "gallery": "Карточка",
"kanban": "Канбан",
"key": "Первичный ключ", "key": "Первичный ключ",
"select": "Выбрать" "select": "Выбрать"
}, },

View file

@ -36,6 +36,7 @@
"showAllEntriesIcons": "顯示條目圖標", "showAllEntriesIcons": "顯示條目圖標",
"wrapAllFields": "欄位自動換行", "wrapAllFields": "欄位自動換行",
"gallery": "卡片", "gallery": "卡片",
"kanban": "看板",
"newTag": "新建標籤", "newTag": "新建標籤",
"pleaseWait": "請稍等片刻...", "pleaseWait": "請稍等片刻...",
"reconnectPrompt": "切換應用後再次進入需要一些時間恢復思源內核運行,請稍等幾秒或者點擊“重試”按鈕", "reconnectPrompt": "切換應用後再次進入需要一些時間恢復思源內核運行,請稍等幾秒或者點擊“重試”按鈕",
@ -169,12 +170,14 @@
"cloudStoragePurgeConfirm": "清理雲端儲存會徹底刪除所有未引用的快照和相關資料物件。<ul class='fn__list'><li>執行前請確保其他設備已經暫停同步</li><li>清理作業非常耗時,請確保網路穩定</li></ul>確定現在就執行嗎?", "cloudStoragePurgeConfirm": "清理雲端儲存會徹底刪除所有未引用的快照和相關資料物件。<ul class='fn__list'><li>執行前請確保其他設備已經暫停同步</li><li>清理作業非常耗時,請確保網路穩定</li></ul>確定現在就執行嗎?",
"dragFill": "垂直拖動以填充值", "dragFill": "垂直拖動以填充值",
"switchReadonly": "唯讀模式切換", "switchReadonly": "唯讀模式切換",
"original": "原值", "original": "顯示原始值",
"uniqueValues": "顯示唯一值",
"selectRelation": "請先選擇關聯欄位", "selectRelation": "請先選擇關聯欄位",
"backRelation": "雙向關聯", "backRelation": "雙向關聯",
"thisDatabase": "目前資料庫", "thisDatabase": "目前資料庫",
"relatedTo": "關聯至", "relatedTo": "關聯至",
"relation": "關聯", "relation": "關聯",
"relatedItems": "已關聯條目",
"rollup": "匯總", "rollup": "匯總",
"rollupProperty": "總計欄位", "rollupProperty": "總計欄位",
"rollupCalc": "彙總方式", "rollupCalc": "彙總方式",
@ -1387,6 +1390,7 @@
"_attrView": { "_attrView": {
"table": "表格", "table": "表格",
"gallery": "卡片", "gallery": "卡片",
"kanban": "看板",
"key": "主鍵", "key": "主鍵",
"select": "單選" "select": "單選"
}, },

View file

@ -36,6 +36,7 @@
"showAllEntriesIcons": "显示条目图标", "showAllEntriesIcons": "显示条目图标",
"wrapAllFields": "字段自动换行", "wrapAllFields": "字段自动换行",
"gallery": "卡片", "gallery": "卡片",
"kanban": "看板",
"newTag": "新建标签", "newTag": "新建标签",
"pleaseWait": "请稍等片刻...", "pleaseWait": "请稍等片刻...",
"reconnectPrompt": "切换应用后再次进入需要一些时间恢复思源内核运行,请稍等几秒或者点击“重试”按钮", "reconnectPrompt": "切换应用后再次进入需要一些时间恢复思源内核运行,请稍等几秒或者点击“重试”按钮",
@ -169,12 +170,14 @@
"cloudStoragePurgeConfirm": "清理云端存储会彻底删除所有未引用的快照和相关数据对象。<ul class='fn__list'><li>执行前请确保其他设备已经暂停同步</li><li>清理操作非常耗时,请确保网络稳定</li></ul> 确定现在就执行吗?", "cloudStoragePurgeConfirm": "清理云端存储会彻底删除所有未引用的快照和相关数据对象。<ul class='fn__list'><li>执行前请确保其他设备已经暂停同步</li><li>清理操作非常耗时,请确保网络稳定</li></ul> 确定现在就执行吗?",
"dragFill": "垂直拖动以填充值", "dragFill": "垂直拖动以填充值",
"switchReadonly": "只读模式切换", "switchReadonly": "只读模式切换",
"original": "原值", "original": "显示原始值",
"uniqueValues": "显示唯一值",
"selectRelation": "请先选择关联字段", "selectRelation": "请先选择关联字段",
"backRelation": "双向关联", "backRelation": "双向关联",
"thisDatabase": "当前数据库", "thisDatabase": "当前数据库",
"relatedTo": "关联至", "relatedTo": "关联至",
"relation": "关联", "relation": "关联",
"relatedItems": "已关联条目",
"rollup": "汇总", "rollup": "汇总",
"rollupProperty": "汇总字段", "rollupProperty": "汇总字段",
"rollupCalc": "汇总方式", "rollupCalc": "汇总方式",
@ -1387,6 +1390,7 @@
"_attrView": { "_attrView": {
"table": "表格", "table": "表格",
"gallery": "卡片", "gallery": "卡片",
"kanban": "看板",
"key": "主键", "key": "主键",
"select": "单选" "select": "单选"
}, },

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<!--suppress XmlUnusedNamespaceDeclaration -->
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities">
<!-- use single quotes to avoid double quotes escaping in the publisher value -->
<Identity Name="89C2A984.SiYuan"
ProcessorArchitecture="arm64"
Publisher="CN=087C656E-C1D9-42D8-8807-CED45A74FC0F"
Version="3.3.2.0"/>
<Properties>
<DisplayName>SiYuan</DisplayName>
<PublisherDisplayName>云南链滴科技有限公司</PublisherDisplayName>
<Description>Refactor your thinking</Description>
<Logo>assets\StoreLogo.png</Logo>
</Properties>
<Resources>
<Resource Language="en-US"/>
<Resource Language="zh-CN"/>
</Resources>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14316.0" MaxVersionTested="10.0.14316.0"/>
</Dependencies>
<Capabilities>
<rescap:Capability Name="runFullTrust"/>
</Capabilities>
<Applications>
<Application Id="SiYuan" Executable="app\SiYuan.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
BackgroundColor="transparent"
DisplayName="SiYuan"
Square150x150Logo="assets\Square150x150Logo.png"
Square44x44Logo="assets\Square44x44Logo.png"
Description="Refactor your thinking">
<uap:DefaultTile Wide310x150Logo="assets\Wide310x150Logo.png"/>
</uap:VisualElements>
<Extensions>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="siyuan"/>
</uap:Extension>
</Extensions>
</Application>
</Applications>
</Package>

View file

@ -0,0 +1,56 @@
## Overview
This version improves some details.
## Changelogs
Below are the detailed changes in this version.
### Enhancement
* [Block ref elements retain their original styles when exported](https://github.com/siyuan-note/siyuan/issues/15698)
* [The new block created after folding the heading must be a heading of the same level](https://github.com/siyuan-note/siyuan/issues/15725)
* [Add scenes for expanding and collapsing headings](https://github.com/siyuan-note/siyuan/issues/15726)
* [Refresh the data in the interface after moving the document](https://github.com/siyuan-note/siyuan/issues/15762)
* [Improve copy/cut/delete `Headings and Bottom Blocks` menu](https://github.com/siyuan-note/siyuan/issues/15797)
* [Improve database group view performance](https://github.com/siyuan-note/siyuan/issues/15811)
* [Upgrade mermaid.js from 11.6.0 to 11.11.0](https://github.com/siyuan-note/siyuan/issues/15819)
* [The browser clipping extension supports "Show directories first in path search"](https://github.com/siyuan-note/siyuan/issues/15822)
* [Improve reopening interaction of closed notebooks](https://github.com/siyuan-note/siyuan/issues/15825)
* [Add new icon: iconInclude](https://github.com/siyuan-note/siyuan/issues/15831)
* [Support arm64 version in Microsoft Store](https://github.com/siyuan-note/siyuan/issues/15836)
* [Improve database menu rendering](https://github.com/siyuan-note/siyuan/issues/15839)
* [Improve the drag-and-drop in the chart and math editing dialogs](https://github.com/siyuan-note/siyuan/issues/15850)
* [The database rollup field supports using the relation field](https://github.com/siyuan-note/siyuan/issues/15851)
* [Add "Show unique values" to the calculation of the database rollup field](https://github.com/siyuan-note/siyuan/issues/15852)
* [Extend the time the interface waits for kernel startup on the desktop](https://github.com/siyuan-note/siyuan/issues/15853)
* [When exporting inline code to Markdown, try to remove the trailing zero-width space](https://github.com/siyuan-note/siyuan/issues/15854)
* [Improve HTML code block clipping](https://github.com/siyuan-note/siyuan/issues/15855)
* [Improve HTML clipping base64 images](https://github.com/siyuan-note/siyuan/issues/15856)
* [Improve database rollup checkbox fields](https://github.com/siyuan-note/siyuan/issues/15858)
* [Disallow editing of non-bound block icons in database relation field](https://github.com/siyuan-note/siyuan/issues/15859)
* [Improve database interaction details](https://github.com/siyuan-note/siyuan/issues/15861)
* [Improve the interaction when using the backspace key at the beginning of a code block](https://github.com/siyuan-note/siyuan/issues/15864)
* [The database relation fields support copying existing relations](https://github.com/siyuan-note/siyuan/pull/15876)
* [Improve font loading](https://github.com/siyuan-note/siyuan/issues/15879)
### Bugfix
* [The editor switch cursor does not follow](https://github.com/siyuan-note/siyuan/issues/15821)
* [After updating a select field in the database attribute panel, clicking on other select fields does not respond](https://github.com/siyuan-note/siyuan/issues/15827)
* [When "Default fill created time" is enabled for database date fields, the automatically filled time value is incorrect](https://github.com/siyuan-note/siyuan/issues/15828)
* [The menus for database relation and rollup fields on the mobile do not display the related database name and field name](https://github.com/siyuan-note/siyuan/issues/15835)
* [When exporting images, the code block line number will display 0](https://github.com/siyuan-note/siyuan/issues/15837)
* [File history cannot load the correct view of database blocks](https://github.com/siyuan-note/siyuan/issues/15841)
* [`Optimize typography` exception when code block contains ```](https://github.com/siyuan-note/siyuan/issues/15843)
* [`Add to Database` shows databases that are not in the document](https://github.com/siyuan-note/siyuan/issues/15847)
* [An error occurs when inserting a tag using a mouse click](https://github.com/siyuan-note/siyuan/issues/15867)
### Development
* [Add an attribute for the breadcrumb unlock/lock button tag](https://github.com/siyuan-note/siyuan/pull/15820)
## Download
* [B3log](https://b3log.org/siyuan/en/download.html)
* [GitHub](https://github.com/siyuan-note/siyuan/releases)

View file

@ -0,0 +1,56 @@
## 概述
該版本改進了一些細節。
## 變更記錄
以下是此版本中的詳細變更。
### 改進功能
* [塊引用元素導出時保留原有樣式](https://github.com/siyuan-note/siyuan/issues/15698)
* [折疊標題後新建的區塊為同級標題](https://github.com/siyuan-note/siyuan/issues/15725)
* [增加標題展開與折疊的場景](https://github.com/siyuan-note/siyuan/issues/15726)
* [行動文件後刷新介面資料](https://github.com/siyuan-note/siyuan/issues/15762)
* [改進複製/剪下/刪除 `標題與下方區塊` 選單](https://github.com/siyuan-note/siyuan/issues/15797)
* [提升資料庫分組檢視效能](https://github.com/siyuan-note/siyuan/issues/15811)
* [將 mermaid.js 從 11.6.0 升級到 11.11.0](https://github.com/siyuan-note/siyuan/issues/15819)
* [瀏覽器剪藏擴充功能支援「路徑搜尋時目錄優先顯示」](https://github.com/siyuan-note/siyuan/issues/15822)
* [改進關閉筆記本後重新開啟的互動](https://github.com/siyuan-note/siyuan/issues/15825)
* [新增圖示iconInclude](https://github.com/siyuan-note/siyuan/issues/15831)
* [Microsoft Store 支援 arm64 版本](https://github.com/siyuan-note/siyuan/issues/15836)
* [改進資料庫選單渲染](https://github.com/siyuan-note/siyuan/issues/15839)
* [改進圖表和數學編輯對話框的拖曳](https://github.com/siyuan-note/siyuan/issues/15850)
* [資料庫匯總欄位支援使用關聯欄位](https://github.com/siyuan-note/siyuan/issues/15851)
* [資料庫匯總欄位計算中增加「顯示唯一值」](https://github.com/siyuan-note/siyuan/issues/15852)
* [桌上端介面等待內核啟動時間延長](https://github.com/siyuan-note/siyuan/issues/15853)
* [匯出 Markdown 行內程式碼時盡量移除結尾零寬空格](https://github.com/siyuan-note/siyuan/issues/15854)
* [改進 HTML 程式碼區塊剪藏](https://github.com/siyuan-note/siyuan/issues/15855)
* [改進 HTML 剪藏 base64 圖片](https://github.com/siyuan-note/siyuan/issues/15856)
* [改進資料庫總複選方塊欄位](https://github.com/siyuan-note/siyuan/issues/15858)
* [資料庫關聯欄位不允許編輯未綁定區塊的圖示](https://github.com/siyuan-note/siyuan/issues/15859)
* [改進資料庫互動細節](https://github.com/siyuan-note/siyuan/issues/15861)
* [在程式碼區塊開頭使用退格鍵時改進互動](https://github.com/siyuan-note/siyuan/issues/15864)
* [資料庫關聯欄位支援複製已有關聯](https://github.com/siyuan-note/siyuan/pull/15876)
* [改進字體載入](https://github.com/siyuan-note/siyuan/issues/15879)
### 修復缺陷
* [編輯器切換時光標未跟隨](https://github.com/siyuan-note/siyuan/issues/15821)
* [資料庫屬性面板更新選擇欄位後,點選其他選擇欄位無回應](https://github.com/siyuan-note/siyuan/issues/15827)
* [資料庫日期欄位啟用「預設填入建立時間」後自動填入的時間值不正確](https://github.com/siyuan-note/siyuan/issues/15828)
* [行動端資料庫關聯和匯總欄位選單未顯示關聯資料庫名稱和欄位名稱](https://github.com/siyuan-note/siyuan/issues/15835)
* [匯出圖片時程式碼區塊行號顯示為 0](https://github.com/siyuan-note/siyuan/issues/15837)
* [檔案歷史無法載入資料庫區塊的正確檢視](https://github.com/siyuan-note/siyuan/issues/15841)
* [程式碼區塊包含 ``` 時「最佳化排版」異常](https://github.com/siyuan-note/siyuan/issues/15843)
* [「新增至資料庫」會顯示不在文件中的資料庫](https://github.com/siyuan-note/siyuan/issues/15847)
* [滑鼠點選插入標籤時報錯誤](https://github.com/siyuan-note/siyuan/issues/15867)
### 開發者
* [為麵包屑解鎖/鎖定按鈕標籤增加屬性](https://github.com/siyuan-note/siyuan/pull/15820)
## 下載
* [B3log](https://b3log.org/siyuan/download.html)
* [GitHub](https://github.com/siyuan-note/siyuan/releases)

View file

@ -0,0 +1,56 @@
## 概述
该版本改进了一些细节。
## 变更记录
以下是此版本中的详细变更。
### 改进功能
* [块引用元素导出时保留原有样式](https://github.com/siyuan-note/siyuan/issues/15698)
* [折叠标题后新建的块为同级标题](https://github.com/siyuan-note/siyuan/issues/15725)
* [增加标题展开与折叠的场景](https://github.com/siyuan-note/siyuan/issues/15726)
* [移动文档后刷新界面数据](https://github.com/siyuan-note/siyuan/issues/15762)
* [改进复制/剪切/删除 `标题与下方块` 菜单](https://github.com/siyuan-note/siyuan/issues/15797)
* [提升数据库分组视图性能](https://github.com/siyuan-note/siyuan/issues/15811)
* [将 mermaid.js 从 11.6.0 升级到 11.11.0](https://github.com/siyuan-note/siyuan/issues/15819)
* [浏览器剪藏扩展支持“路径搜索时目录优先显示”](https://github.com/siyuan-note/siyuan/issues/15822)
* [改进关闭笔记本后重新打开的交互](https://github.com/siyuan-note/siyuan/issues/15825)
* [新增图标iconInclude](https://github.com/siyuan-note/siyuan/issues/15831)
* [Microsoft Store 支持 arm64 版本](https://github.com/siyuan-note/siyuan/issues/15836)
* [改进数据库菜单渲染](https://github.com/siyuan-note/siyuan/issues/15839)
* [改进图表和数学编辑对话框的拖拽](https://github.com/siyuan-note/siyuan/issues/15850)
* [数据库汇总字段支持使用关联字段](https://github.com/siyuan-note/siyuan/issues/15851)
* [数据库汇总字段计算中增加“显示唯一值”](https://github.com/siyuan-note/siyuan/issues/15852)
* [桌面端界面等待内核启动时间延长](https://github.com/siyuan-note/siyuan/issues/15853)
* [导出 Markdown 行内代码时尽量去除末尾零宽空格](https://github.com/siyuan-note/siyuan/issues/15854)
* [改进 HTML 代码块剪藏](https://github.com/siyuan-note/siyuan/issues/15855)
* [改进 HTML 剪藏 base64 图片](https://github.com/siyuan-note/siyuan/issues/15856)
* [改进数据库汇总复选框字段](https://github.com/siyuan-note/siyuan/issues/15858)
* [数据库关联字段不允许编辑未绑定块的图标](https://github.com/siyuan-note/siyuan/issues/15859)
* [改进数据库交互细节](https://github.com/siyuan-note/siyuan/issues/15861)
* [在代码块开头使用退格键时改进交互](https://github.com/siyuan-note/siyuan/issues/15864)
* [数据库关联字段支持复制已有关联](https://github.com/siyuan-note/siyuan/pull/15876)
* [改进字体加载](https://github.com/siyuan-note/siyuan/issues/15879)
### 修复缺陷
* [编辑器切换时光标未跟随](https://github.com/siyuan-note/siyuan/issues/15821)
* [数据库属性面板更新选择字段后,点击其他选择字段无响应](https://github.com/siyuan-note/siyuan/issues/15827)
* [数据库日期字段启用“默认填充创建时间”后自动填充的时间值不正确](https://github.com/siyuan-note/siyuan/issues/15828)
* [移动端数据库关联和汇总字段菜单未显示关联数据库名和字段名](https://github.com/siyuan-note/siyuan/issues/15835)
* [导出图片时代码块行号显示为 0](https://github.com/siyuan-note/siyuan/issues/15837)
* [文件历史无法加载数据库块的正确视图](https://github.com/siyuan-note/siyuan/issues/15841)
* [代码块包含 ``` 时“优化排版”异常](https://github.com/siyuan-note/siyuan/issues/15843)
* [“添加到数据库”会显示不在文档中的数据库](https://github.com/siyuan-note/siyuan/issues/15847)
* [鼠标点击插入标签时报错](https://github.com/siyuan-note/siyuan/issues/15867)
### 开发者
* [为面包屑解锁/锁定按钮标签增加属性](https://github.com/siyuan-note/siyuan/pull/15820)
## 下载
* [B3log](https://b3log.org/siyuan/download.html)
* [GitHub](https://github.com/siyuan-note/siyuan/releases)

View file

@ -607,7 +607,7 @@ const initKernel = (workspace, port, lang) => {
resolve(false); resolve(false);
return; return;
} }
await sleep(200); await sleep(500);
} }
} }

View file

@ -18,7 +18,6 @@
"build:export": "webpack --mode production --config webpack.export.js", "build:export": "webpack --mode production --config webpack.export.js",
"gen:types": "tsc -d", "gen:types": "tsc -d",
"start": "NODE_ENV=development electron ./electron/main.js", "start": "NODE_ENV=development electron ./electron/main.js",
"dist-appx": "ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ electron-builder --config electron-appx-builder.yml",
"dist": "ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ electron-builder --config electron-builder.yml --publish=never", "dist": "ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ electron-builder --config electron-builder.yml --publish=never",
"dist-arm64": "ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ electron-builder --arm64 --config electron-builder-arm64.yml --publish=never", "dist-arm64": "ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ electron-builder --arm64 --config electron-builder-arm64.yml --publish=never",
"dist-darwin": "ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ electron-builder --mac --config electron-builder-darwin.yml --publish=never", "dist-darwin": "ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ electron-builder --mac --config electron-builder-darwin.yml --publish=never",

View file

@ -180,43 +180,43 @@ export class Asset extends Model {
<button id="previous" class="secondaryToolbarButton b3-menu__item pageUp"> <button id="previous" class="secondaryToolbarButton b3-menu__item pageUp">
<svg class="b3-menu__icon"><use xlink:href="#iconUp"></use></svg> <svg class="b3-menu__icon"><use xlink:href="#iconUp"></use></svg>
<span class="b3-menu__label">${window.siyuan.languages.previousLabel}</span> <span class="b3-menu__label">${window.siyuan.languages.previousLabel}</span>
<span class="b3-menu__accelerator">${updateHotkeyTip("P")}/${updateHotkeyTip("K")}</span> <span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip("P")}/${updateHotkeyTip("K")}</span>
</button> </button>
<button id="next" class="secondaryToolbarButton b3-menu__item pageDown"> <button id="next" class="secondaryToolbarButton b3-menu__item pageDown">
<svg class="b3-menu__icon"><use xlink:href="#iconDown"></use></svg> <svg class="b3-menu__icon"><use xlink:href="#iconDown"></use></svg>
<span class="b3-menu__label">${window.siyuan.languages.nextLabel}</span> <span class="b3-menu__label">${window.siyuan.languages.nextLabel}</span>
<span class="b3-menu__accelerator">${updateHotkeyTip("J")}/${updateHotkeyTip("N")}</span> <span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip("J")}/${updateHotkeyTip("N")}</span>
</button> </button>
<button id="firstPage" class="secondaryToolbarButton b3-menu__item firstPage"> <button id="firstPage" class="secondaryToolbarButton b3-menu__item firstPage">
<svg class="b3-menu__icon"><use xlink:href="#iconBack"></use></svg> <svg class="b3-menu__icon"><use xlink:href="#iconBack"></use></svg>
<span class="b3-menu__label">${window.siyuan.languages.firstPage}</span> <span class="b3-menu__label">${window.siyuan.languages.firstPage}</span>
<span class="b3-menu__accelerator">Home</span> <span class="b3-menu__accelerator b3-menu__accelerator--hotkey">Home</span>
</button> </button>
<button id="lastPage" class="secondaryToolbarButton b3-menu__item lastPage"> <button id="lastPage" class="secondaryToolbarButton b3-menu__item lastPage">
<svg class="b3-menu__icon"><use xlink:href="#iconForward"></use></svg> <svg class="b3-menu__icon"><use xlink:href="#iconForward"></use></svg>
<span class="b3-menu__label">${window.siyuan.languages.lastPage}</span> <span class="b3-menu__label">${window.siyuan.languages.lastPage}</span>
<span class="b3-menu__accelerator">End</span> <span class="b3-menu__accelerator b3-menu__accelerator--hotkey">End</span>
</button> </button>
<div class="horizontalToolbarSeparator b3-menu__separator"></div> <div class="horizontalToolbarSeparator b3-menu__separator"></div>
<button id="zoomOutButton" class="secondaryToolbarButton b3-menu__item zoomOut"> <button id="zoomOutButton" class="secondaryToolbarButton b3-menu__item zoomOut">
<svg class="b3-menu__icon"><use xlink:href="#iconLine"></use></svg> <svg class="b3-menu__icon"><use xlink:href="#iconLine"></use></svg>
<span class="b3-menu__label">${window.siyuan.languages.zoomOut}</span> <span class="b3-menu__label">${window.siyuan.languages.zoomOut}</span>
<span class="b3-menu__accelerator">${updateHotkeyTip("⌘-")}</span> <span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip("⌘-")}</span>
</button> </button>
<button id="zoomInButton" class="secondaryToolbarButton b3-menu__item zoomIn"> <button id="zoomInButton" class="secondaryToolbarButton b3-menu__item zoomIn">
<svg class="b3-menu__icon"><use xlink:href="#iconAdd"></use></svg> <svg class="b3-menu__icon"><use xlink:href="#iconAdd"></use></svg>
<span class="b3-menu__label">${window.siyuan.languages.zoomIn}</span> <span class="b3-menu__label">${window.siyuan.languages.zoomIn}</span>
<span class="b3-menu__accelerator">${updateHotkeyTip("⌘=")}</span> <span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip("⌘=")}</span>
</button> </button>
<button id="pageRotateCw" class="secondaryToolbarButton b3-menu__item rotateCw"> <button id="pageRotateCw" class="secondaryToolbarButton b3-menu__item rotateCw">
<svg class="b3-menu__icon"><use xlink:href="#iconRedo"></use></svg> <svg class="b3-menu__icon"><use xlink:href="#iconRedo"></use></svg>
<span class="b3-menu__label">${window.siyuan.languages.rotateCw}</span> <span class="b3-menu__label">${window.siyuan.languages.rotateCw}</span>
<span class="b3-menu__accelerator">R</span> <span class="b3-menu__accelerator b3-menu__accelerator--hotkey">R</span>
</button> </button>
<button id="pageRotateCcw" class="secondaryToolbarButton b3-menu__item rotateCcw"> <button id="pageRotateCcw" class="secondaryToolbarButton b3-menu__item rotateCcw">
<svg class="b3-menu__icon"><use xlink:href="#iconUndo"></use></svg> <svg class="b3-menu__icon"><use xlink:href="#iconUndo"></use></svg>
<span class="b3-menu__label">${window.siyuan.languages.rotateCcw}</span> <span class="b3-menu__label">${window.siyuan.languages.rotateCcw}</span>
<span class="b3-menu__accelerator">R</span> <span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip("⇧R")}</span>
</button> </button>
<div class="horizontalToolbarSeparator b3-menu__separator"></div> <div class="horizontalToolbarSeparator b3-menu__separator"></div>
@ -224,12 +224,12 @@ export class Asset extends Model {
<button id="cursorSelectTool" class="secondaryToolbarButton b3-menu__item selectTool toggled"> <button id="cursorSelectTool" class="secondaryToolbarButton b3-menu__item selectTool toggled">
<svg class="b3-menu__icon"><use xlink:href="#iconSelectText"></use></svg> <svg class="b3-menu__icon"><use xlink:href="#iconSelectText"></use></svg>
<span class="b3-menu__label">${window.siyuan.languages.cursorText}</span> <span class="b3-menu__label">${window.siyuan.languages.cursorText}</span>
<span class="b3-menu__accelerator">S</span> <span class="b3-menu__accelerator b3-menu__accelerator--hotkey">S</span>
</button> </button>
<button id="cursorHandTool" class="secondaryToolbarButton b3-menu__item handTool"> <button id="cursorHandTool" class="secondaryToolbarButton b3-menu__item handTool">
<svg class="b3-menu__icon"><use xlink:href="#iconHand"></use></svg> <svg class="b3-menu__icon"><use xlink:href="#iconHand"></use></svg>
<span class="b3-menu__label">${window.siyuan.languages.cursorHand}</span> <span class="b3-menu__label">${window.siyuan.languages.cursorHand}</span>
<span class="b3-menu__accelerator">H</span> <span class="b3-menu__accelerator b3-menu__accelerator--hotkey">H</span>
</button> </button>
<div class="horizontalToolbarSeparator b3-menu__separator"></div> <div class="horizontalToolbarSeparator b3-menu__separator"></div>
<button id="scrollVertical" class="secondaryToolbarButton b3-menu__item scrollModeButtons scrollVertical toggled"> <button id="scrollVertical" class="secondaryToolbarButton b3-menu__item scrollModeButtons scrollVertical toggled">
@ -262,7 +262,7 @@ export class Asset extends Model {
<button id="presentationMode" class="secondaryToolbarButton b3-menu__item presentationMode"> <button id="presentationMode" class="secondaryToolbarButton b3-menu__item presentationMode">
<svg class="b3-menu__icon"><use xlink:href="#iconPlay"></use></svg> <svg class="b3-menu__icon"><use xlink:href="#iconPlay"></use></svg>
<span class="b3-menu__label">${window.siyuan.languages.presentationMode}</span> <span class="b3-menu__label">${window.siyuan.languages.presentationMode}</span>
<span class="b3-menu__accelerator">${updateHotkeyTip("⌥⌘P")}</span> <span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip("⌥⌘P")}</span>
</button> </button>
<div class="horizontalToolbarSeparator b3-menu__separator spreadModeButtons"></div> <div class="horizontalToolbarSeparator b3-menu__separator spreadModeButtons"></div>
<button id="documentProperties" class="secondaryToolbarButton b3-menu__item documentProperties"> <button id="documentProperties" class="secondaryToolbarButton b3-menu__item documentProperties">

View file

@ -74,4 +74,8 @@
left: -8px; left: -8px;
bottom: 8px; bottom: 8px;
} }
&__move {
cursor: move;
}
} }

View file

@ -607,11 +607,15 @@
& > span { & > span {
pointer-events: none; pointer-events: none;
display: block; display: block;
color: var(--b3-theme-on-surface);
&:not(:empty)::before {
content: "";
}
&::before { &::before {
counter-increment: linenumber; counter-increment: linenumber;
content: counter(linenumber); content: counter(linenumber);
color: var(--b3-theme-on-surface);
display: block; display: block;
text-align: right; text-align: right;
white-space: nowrap; white-space: nowrap;

View file

@ -432,7 +432,7 @@
} }
.b3-menu { .b3-menu {
&__accelerator { &__accelerator--hotkey {
display: none; display: none;
} }

View file

@ -20,6 +20,12 @@
display: inline-block; display: inline-block;
flex-shrink: 0; flex-shrink: 0;
} }
&--5 {
width: 5px;
display: inline-block;
flex-shrink: 0;
}
} }
&__hr { &__hr {

View file

@ -150,7 +150,16 @@ export const insertEmptyBlock = (protyle: IProtyle, position: InsertPosition, id
if (blockElement.getAttribute("data-type") === "NodeListItem") { if (blockElement.getAttribute("data-type") === "NodeListItem") {
newElement = genListItemElement(blockElement, 0, true) as HTMLDivElement; newElement = genListItemElement(blockElement, 0, true) as HTMLDivElement;
orderIndex = parseInt(blockElement.parentElement.firstElementChild.getAttribute("data-marker")); orderIndex = parseInt(blockElement.parentElement.firstElementChild.getAttribute("data-marker"));
} else if (position === "beforebegin" && blockElement.previousElementSibling &&
blockElement.previousElementSibling.getAttribute("data-type") === "NodeHeading" &&
blockElement.previousElementSibling.getAttribute("fold") === "1") {
newElement = genHeadingElement(blockElement.previousElementSibling, false, true) as HTMLDivElement;
} else if (position === "afterend" && blockElement &&
blockElement.getAttribute("data-type") === "NodeHeading" &&
blockElement.getAttribute("fold") === "1") {
newElement = genHeadingElement(blockElement, false, true) as HTMLDivElement;
} }
const parentOldHTML = blockElement.parentElement.outerHTML; const parentOldHTML = blockElement.parentElement.outerHTML;
const newId = newElement.getAttribute("data-node-id"); const newId = newElement.getAttribute("data-node-id");
blockElement.insertAdjacentElement(position, newElement); blockElement.insertAdjacentElement(position, newElement);
@ -216,6 +225,17 @@ export const genEmptyElement = (zwsp = true, wbr = true, id?: string) => {
return element; return element;
}; };
export const genHeadingElement = (headElement: Element, getHTML = false, addWbr = false) => {
const html = `<div data-subtype="${headElement.getAttribute("data-subtype")}" data-node-id="${Lute.NewNodeID()}" data-type="NodeHeading" class="${headElement.className}"><div contenteditable="true" spellcheck="false">${addWbr ? "<wbr>" : ""}</div><div class="protyle-attr" contenteditable="false">${Constants.ZWSP}</div></div>`;
if (getHTML) {
return html;
} else {
const tempElement = document.createElement("template");
tempElement.innerHTML = html;
return tempElement.content.firstElementChild;
}
};
export const getLangByType = (type: string) => { export const getLangByType = (type: string) => {
let lang = type; let lang = type;
switch (type) { switch (type) {

View file

@ -13,13 +13,7 @@ import {showMessage} from "../../dialog/message";
import {fetchPost, fetchSyncPost} from "../../util/fetch"; import {fetchPost, fetchSyncPost} from "../../util/fetch";
import {openEmojiPanel, unicode2Emoji} from "../../emoji"; import {openEmojiPanel, unicode2Emoji} from "../../emoji";
import {mountHelp, newNotebook} from "../../util/mount"; import {mountHelp, newNotebook} from "../../util/mount";
import {confirmDialog} from "../../dialog/confirmDialog"; import {isNotCtrl, isOnlyMeta, setStorageVal, updateHotkeyAfterTip} from "../../protyle/util/compatibility";
import {
isNotCtrl,
isOnlyMeta,
setStorageVal,
updateHotkeyAfterTip
} from "../../protyle/util/compatibility";
import {openFileById} from "../../editor/util"; import {openFileById} from "../../editor/util";
import { import {
hasClosestByAttribute, hasClosestByAttribute,
@ -178,18 +172,6 @@ export class Files extends Model {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
break; break;
} else if (type === "remove") {
confirmDialog(window.siyuan.languages.deleteOpConfirm,
`${window.siyuan.languages.confirmDelete} <b>${escapeHtml(target.parentElement.querySelector(".b3-list-item__text").textContent)}</b>?`, () => {
fetchPost("/api/notebook/removeNotebook", {
notebook: target.getAttribute("data-url"),
callback: Constants.CB_MOUNT_REMOVE
});
}, undefined, true);
window.siyuan.menus.menu.remove();
event.stopPropagation();
event.preventDefault();
break;
} else if (type === "open") { } else if (type === "open") {
fetchPost("/api/notebook/openNotebook", { fetchPost("/api/notebook/openNotebook", {
notebook: target.getAttribute("data-url") notebook: target.getAttribute("data-url")
@ -813,14 +795,14 @@ export class Files extends Model {
private genNotebook(item: INotebook) { private genNotebook(item: INotebook) {
const emojiHTML = `<span class="b3-list-item__icon b3-tooltips b3-tooltips__e" aria-label="${window.siyuan.languages.changeIcon}">${unicode2Emoji(item.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].note)}</span>`; const emojiHTML = `<span class="b3-list-item__icon b3-tooltips b3-tooltips__e" aria-label="${window.siyuan.languages.changeIcon}">${unicode2Emoji(item.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].note)}</span>`;
if (item.closed) { if (item.closed) {
return `<li data-type="open" data-url="${item.id}" class="b3-list-item b3-list-item--hide-action"> return `<li data-url="${item.id}" class="b3-list-item b3-list-item--hide-action">
<span class="b3-list-item__toggle fn__hidden"> <span class="b3-list-item__toggle fn__hidden">
<svg class="b3-list-item__arrow"><use xlink:href="#iconRight"></use></svg> <svg class="b3-list-item__arrow"><use xlink:href="#iconRight"></use></svg>
</span> </span>
${emojiHTML} ${emojiHTML}
<span class="b3-list-item__text">${escapeHtml(item.name)}</span> <span class="b3-list-item__text" style="cursor: default;">${escapeHtml(item.name)}</span>
<span data-type="remove" data-url="${item.id}" class="b3-list-item__action b3-tooltips b3-tooltips__w${(window.siyuan.config.readonly) ? " fn__none" : ""}" aria-label="${window.siyuan.languages.delete}"> <span data-type="open" data-url="${item.id}" class="b3-list-item__action b3-tooltips b3-tooltips__w${(window.siyuan.config.readonly) ? " fn__none" : ""}" aria-label="${window.siyuan.languages.openBy}">
<svg><use xlink:href="#iconTrashcan"></use></svg> <svg><use xlink:href="#iconOpen"></use></svg>
</span> </span>
</li>`; </li>`;
} else { } else {
@ -1005,9 +987,12 @@ data-type="navigation-root" data-path="/">
} }
if (sourceElement.parentElement.childElementCount === 1) { if (sourceElement.parentElement.childElementCount === 1) {
if (sourceElement.parentElement.previousElementSibling) { if (sourceElement.parentElement.previousElementSibling) {
sourceElement.parentElement.previousElementSibling.querySelector(".b3-list-item__toggle").classList.add("fn__hidden"); const parentLiElement = sourceElement.parentElement.previousElementSibling;
sourceElement.parentElement.previousElementSibling.querySelector(".b3-list-item__arrow").classList.remove("b3-list-item__arrow--open"); if (parentLiElement.getAttribute("data-type") !== "navigation-root") {
const emojiElement = sourceElement.parentElement.previousElementSibling.querySelector(".b3-list-item__icon"); parentLiElement.querySelector(".b3-list-item__toggle").classList.add("fn__hidden");
}
parentLiElement.querySelector(".b3-list-item__arrow").classList.remove("b3-list-item__arrow--open");
const emojiElement = parentLiElement.querySelector(".b3-list-item__icon");
if (emojiElement.innerHTML === unicode2Emoji(window.siyuan.storage[Constants.LOCAL_IMAGES].folder)) { if (emojiElement.innerHTML === unicode2Emoji(window.siyuan.storage[Constants.LOCAL_IMAGES].folder)) {
emojiElement.innerHTML = unicode2Emoji(window.siyuan.storage[Constants.LOCAL_IMAGES].file); emojiElement.innerHTML = unicode2Emoji(window.siyuan.storage[Constants.LOCAL_IMAGES].file);
} }
@ -1016,6 +1001,12 @@ data-type="navigation-root" data-path="/">
} else { } else {
sourceElement.remove(); sourceElement.remove();
} }
} else {
const parentElement = this.element.querySelector(`ul[data-url="${response.data.fromNotebook}"] li[data-path="${pathPosix().dirname(response.data.fromPath)}.sy"]`) as HTMLElement;
if (parentElement && parentElement.getAttribute("data-count") === "1") {
parentElement.querySelector(".b3-list-item__toggle").classList.add("fn__hidden");
parentElement.querySelector(".b3-list-item__arrow").classList.remove("b3-list-item__arrow--open");
}
} }
const newElement = this.element.querySelector(`[data-url="${response.data.toNotebook}"] li[data-path="${response.data.toPath}"]`) as HTMLElement; const newElement = this.element.querySelector(`[data-url="${response.data.toNotebook}"] li[data-path="${response.data.toPath}"]`) as HTMLElement;
// 更新移动到的新文件夹 // 更新移动到的新文件夹

View file

@ -750,7 +750,7 @@ export const newModelByInitData = (app: App, tab: Tab, json: any) => {
rootId: json.rootId, rootId: json.rootId,
blockId: json.blockId, blockId: json.blockId,
mode: json.mode, mode: json.mode,
action: typeof json.action === "string" ? [json.action] : json.action, action: typeof json.action === "string" ? [json.action, Constants.CB_GET_FOCUS] : json.action.concat(Constants.CB_GET_FOCUS),
}); });
} }
return model; return model;

View file

@ -236,7 +236,7 @@ export class MenuItem {
html = `<svg class="b3-menu__icon ${options.iconClass || ""}" style="${options.icon === "iconClose" ? "height:10px;" : ""}"><use xlink:href="#${options.icon || ""}"></use></svg>${html}`; html = `<svg class="b3-menu__icon ${options.iconClass || ""}" style="${options.icon === "iconClose" ? "height:10px;" : ""}"><use xlink:href="#${options.icon || ""}"></use></svg>${html}`;
} }
if (options.accelerator) { if (options.accelerator) {
html += `<span class="b3-menu__accelerator">${updateHotkeyTip(options.accelerator)}</span>`; html += `<span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip(options.accelerator)}</span>`;
} }
if (options.action) { if (options.action) {
html += `<svg class="b3-menu__action${options.action === "iconCloseRound" ? " b3-menu__action--close" : ""}"><use xlink:href="#${options.action}"></use></svg>`; html += `<svg class="b3-menu__action${options.action === "iconCloseRound" ? " b3-menu__action--close" : ""}"><use xlink:href="#${options.action}"></use></svg>`;

View file

@ -9,7 +9,6 @@ import {genUUID} from "../../util/genID";
import {openMobileFileById} from "../editor"; import {openMobileFileById} from "../editor";
import {unicode2Emoji} from "../../emoji"; import {unicode2Emoji} from "../../emoji";
import {mountHelp, newNotebook} from "../../util/mount"; import {mountHelp, newNotebook} from "../../util/mount";
import {confirmDialog} from "../../dialog/confirmDialog";
import {newFile} from "../../util/newFile"; import {newFile} from "../../util/newFile";
import {MenuItem} from "../../menus/Menu"; import {MenuItem} from "../../menus/Menu";
import {App} from "../../index"; import {App} from "../../index";
@ -176,17 +175,6 @@ export class MobileFiles extends Model {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
break; break;
} else if (type === "remove") {
confirmDialog(window.siyuan.languages.deleteOpConfirm,
`${window.siyuan.languages.confirmDelete} <b>${escapeHtml(target.parentElement.querySelector(".b3-list-item__text").textContent)}</b>?`, () => {
fetchPost("/api/notebook/removeNotebook", {
notebook: target.getAttribute("data-url"),
callback: Constants.CB_MOUNT_REMOVE
});
}, undefined, true);
event.stopPropagation();
event.preventDefault();
break;
} else if (type === "open") { } else if (type === "open") {
fetchPost("/api/notebook/openNotebook", { fetchPost("/api/notebook/openNotebook", {
notebook: target.getAttribute("data-url") notebook: target.getAttribute("data-url")
@ -309,14 +297,14 @@ export class MobileFiles extends Model {
private genNotebook(item: INotebook) { private genNotebook(item: INotebook) {
const emojiHTML = `<span class="b3-list-item__icon b3-tooltips b3-tooltips__e" aria-label="${window.siyuan.languages.changeIcon}">${unicode2Emoji(item.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].note)}</span>`; const emojiHTML = `<span class="b3-list-item__icon b3-tooltips b3-tooltips__e" aria-label="${window.siyuan.languages.changeIcon}">${unicode2Emoji(item.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].note)}</span>`;
if (item.closed) { if (item.closed) {
return `<li data-type="open" data-url="${item.id}" class="b3-list-item"> return `<li data-url="${item.id}" class="b3-list-item">
<span class="b3-list-item__toggle fn__hidden"> <span class="b3-list-item__toggle fn__hidden">
<svg class="b3-list-item__arrow"><use xlink:href="#iconRight"></use></svg> <svg class="b3-list-item__arrow"><use xlink:href="#iconRight"></use></svg>
</span> </span>
${emojiHTML} ${emojiHTML}
<span class="b3-list-item__text">${escapeHtml(item.name)}</span> <span class="b3-list-item__text">${escapeHtml(item.name)}</span>
<span data-type="remove" data-url="${item.id}" class="b3-list-item__action${(window.siyuan.config.readonly) ? " fn__none" : ""}"> <span data-type="open" data-url="${item.id}" class="b3-list-item__action${(window.siyuan.config.readonly) ? " fn__none" : ""}">
<svg><use xlink:href="#iconTrashcan"></use></svg> <svg><use xlink:href="#iconOpen"></use></svg>
</span> </span>
</li>`; </li>`;
} else { } else {
@ -402,6 +390,12 @@ export class MobileFiles extends Model {
} else { } else {
sourceElement.remove(); sourceElement.remove();
} }
} else {
const parentElement = this.element.querySelector(`ul[data-url="${data.fromNotebook}"] li[data-path="${pathPosix().dirname(data.fromPath)}.sy"]`) as HTMLElement;
if (parentElement && parentElement.getAttribute("data-count") === "1") {
parentElement.querySelector(".b3-list-item__toggle").classList.add("fn__hidden");
parentElement.querySelector(".b3-list-item__arrow").classList.remove("b3-list-item__arrow--open");
}
} }
const newElement = this.element.querySelector(`[data-url="${data.toNotebook}"] li[data-path="${data.toPath}"]`) as HTMLElement; const newElement = this.element.querySelector(`[data-url="${data.toNotebook}"] li[data-path="${data.toPath}"]`) as HTMLElement;
// 重新展开移动到的新文件夹 // 重新展开移动到的新文件夹

View file

@ -752,7 +752,7 @@ export const popSearch = (app: App, searchConfig?: any) => {
<div class="toolbar"> <div class="toolbar">
<span class="fn__flex-1"></span> <span class="fn__flex-1"></span>
<svg data-type="toggle-replace" class="toolbar__icon${config.hasReplace ? " toolbar__icon--active" : ""}"><use xlink:href="#iconReplace"></use></svg> <svg data-type="toggle-replace" class="toolbar__icon${config.hasReplace ? " toolbar__icon--active" : ""}"><use xlink:href="#iconReplace"></use></svg>
<svg ${enableIncludeChild ? "" : "disabled"} data-type="include" class="toolbar__icon${includeChild ? " toolbar__icon--active" : ""}"><use xlink:href="#iconCopy"></use></svg> <svg ${enableIncludeChild ? "" : "disabled"} data-type="include" class="toolbar__icon${includeChild ? " toolbar__icon--active" : ""}"><use xlink:href="#iconInclude"></use></svg>
<svg data-type="path" class="toolbar__icon"><use xlink:href="#iconFolder"></use></svg> <svg data-type="path" class="toolbar__icon"><use xlink:href="#iconFolder"></use></svg>
<svg ${document.querySelector("#empty").classList.contains("fn__none") ? "" : "disabled"} data-type="currentPath" class="toolbar__icon"><use xlink:href="#iconFocus"></use></svg> <svg ${document.querySelector("#empty").classList.contains("fn__none") ? "" : "disabled"} data-type="currentPath" class="toolbar__icon"><use xlink:href="#iconFocus"></use></svg>
<svg data-type="expand" class="toolbar__icon${config.group === 0 ? " fn__none" : ""}"><use xlink:href="#iconExpand"></use></svg> <svg data-type="expand" class="toolbar__icon${config.group === 0 ? " fn__none" : ""}"><use xlink:href="#iconExpand"></use></svg>

View file

@ -58,7 +58,7 @@ export class Breadcrumb {
<span class="protyle-breadcrumb__space"></span> <span class="protyle-breadcrumb__space"></span>
<button class="protyle-breadcrumb__icon fn__none ariaLabel" aria-label="${updateHotkeyTip(window.siyuan.config.keymap.editor.general.exitFocus.custom)}" data-type="exit-focus">${window.siyuan.languages.exitFocus}</button> <button class="protyle-breadcrumb__icon fn__none ariaLabel" aria-label="${updateHotkeyTip(window.siyuan.config.keymap.editor.general.exitFocus.custom)}" data-type="exit-focus">${window.siyuan.languages.exitFocus}</button>
${padHTML} ${padHTML}
<button class="block__icon fn__flex-center ariaLabel${window.siyuan.config.readonly ? " fn__none" : ""}" aria-label="${window.siyuan.languages.lockEdit}" data-type="readonly"><svg><use xlink:href="#iconUnlock"></use></svg></button> <button class="block__icon fn__flex-center ariaLabel${window.siyuan.config.readonly ? " fn__none" : ""}" aria-label="${window.siyuan.languages.lockEdit}" data-type="readonly" data-subtype="unlock"><svg><use xlink:href="#iconUnlock"></use></svg></button>
<button class="block__icon fn__flex-center ariaLabel" data-type="doc" aria-label="${isMac() ? window.siyuan.languages.gutterTip2 : window.siyuan.languages.gutterTip2.replace("", "Shift+")}"><svg><use xlink:href="#iconFile"></use></svg></button> <button class="block__icon fn__flex-center ariaLabel" data-type="doc" aria-label="${isMac() ? window.siyuan.languages.gutterTip2 : window.siyuan.languages.gutterTip2.replace("", "Shift+")}"><svg><use xlink:href="#iconFile"></use></svg></button>
<button class="block__icon fn__flex-center ariaLabel" data-type="more" aria-label="${window.siyuan.languages.more}"><svg><use xlink:href="#iconMore"></use></svg></button> <button class="block__icon fn__flex-center ariaLabel" data-type="more" aria-label="${window.siyuan.languages.more}"><svg><use xlink:href="#iconMore"></use></svg></button>
<button class="block__icon fn__flex-center fn__none ariaLabel" data-type="context" aria-label="${window.siyuan.languages.context}"><svg><use xlink:href="#iconAlignCenter"></use></svg></button>`; <button class="block__icon fn__flex-center fn__none ariaLabel" data-type="context" aria-label="${window.siyuan.languages.context}"><svg><use xlink:href="#iconAlignCenter"></use></svg></button>`;

View file

@ -80,6 +80,9 @@ export const exportImage = (id: string) => {
objectElement.remove(); objectElement.remove();
} }
} }
previewElement.querySelectorAll(".protyle-linenumber__rows span").forEach((item, index) => {
item.textContent = (index + 1).toString();
});
setTimeout(() => { setTimeout(() => {
addScript("/stage/protyle/js/html-to-image.min.js?v=1.11.13", "protyleHtml2image").then(async () => { addScript("/stage/protyle/js/html-to-image.min.js?v=1.11.13", "protyleHtml2image").then(async () => {
let blob = await window.htmlToImage.toBlob(exportDialog.element.querySelector(".b3-dialog__content")); let blob = await window.htmlToImage.toBlob(exportDialog.element.querySelector(".b3-dialog__content"));

View file

@ -1682,7 +1682,7 @@ export class Gutter {
icon: "iconCopy", icon: "iconCopy",
label: `${window.siyuan.languages.copy} ${window.siyuan.languages.headings1}`, label: `${window.siyuan.languages.copy} ${window.siyuan.languages.headings1}`,
click() { click() {
fetchPost("/api/block/getHeadingChildrenDOM", {id, removeFoldAttr: true}, (response) => { fetchPost("/api/block/getHeadingChildrenDOM", {id, removeFoldAttr: false}, (response) => {
if (isInAndroid()) { if (isInAndroid()) {
window.JSAndroid.writeHTMLClipboard(protyle.lute.BlockDOM2StdMd(response.data).trimEnd(), response.data + Constants.ZWSP); window.JSAndroid.writeHTMLClipboard(protyle.lute.BlockDOM2StdMd(response.data).trimEnd(), response.data + Constants.ZWSP);
} else if (isInHarmony()) { } else if (isInHarmony()) {
@ -1698,7 +1698,7 @@ export class Gutter {
icon: "iconCut", icon: "iconCut",
label: `${window.siyuan.languages.cut} ${window.siyuan.languages.headings1}`, label: `${window.siyuan.languages.cut} ${window.siyuan.languages.headings1}`,
click() { click() {
fetchPost("/api/block/getHeadingChildrenDOM", {id, removeFoldAttr: true}, (response) => { fetchPost("/api/block/getHeadingChildrenDOM", {id, removeFoldAttr: false}, (response) => {
if (isInAndroid()) { if (isInAndroid()) {
window.JSAndroid.writeHTMLClipboard(protyle.lute.BlockDOM2StdMd(response.data).trimEnd(), response.data + Constants.ZWSP); window.JSAndroid.writeHTMLClipboard(protyle.lute.BlockDOM2StdMd(response.data).trimEnd(), response.data + Constants.ZWSP);
} else if (isInHarmony()) { } else if (isInHarmony()) {
@ -1802,15 +1802,6 @@ export class Gutter {
transferBlockRef(id); transferBlockRef(id);
} }
} }
window.siyuan.menus.menu.append(new MenuItem({
id: "jumpToParentNext",
label: window.siyuan.languages.jumpToParentNext,
accelerator: window.siyuan.config.keymap.editor.general.jumpToParentNext.custom,
click() {
hideElements(["select"], protyle);
jumpToParent(protyle, nodeElement, "next");
}
}).element);
window.siyuan.menus.menu.append(new MenuItem({ window.siyuan.menus.menu.append(new MenuItem({
id: "jumpToParentPrev", id: "jumpToParentPrev",
label: window.siyuan.languages.jumpToParentPrev, label: window.siyuan.languages.jumpToParentPrev,
@ -1820,6 +1811,15 @@ export class Gutter {
jumpToParent(protyle, nodeElement, "previous"); jumpToParent(protyle, nodeElement, "previous");
} }
}).element); }).element);
window.siyuan.menus.menu.append(new MenuItem({
id: "jumpToParentNext",
label: window.siyuan.languages.jumpToParentNext,
accelerator: window.siyuan.config.keymap.editor.general.jumpToParentNext.custom,
click() {
hideElements(["select"], protyle);
jumpToParent(protyle, nodeElement, "next");
}
}).element);
window.siyuan.menus.menu.append(new MenuItem({ window.siyuan.menus.menu.append(new MenuItem({
id: "jumpToParent", id: "jumpToParent",
label: window.siyuan.languages.jumpToParent, label: window.siyuan.languages.jumpToParent,

View file

@ -23,7 +23,7 @@ import {avRender} from "../render/av/render";
const getHotkeyOrMarker = (hotkey: string, marker: string) => { const getHotkeyOrMarker = (hotkey: string, marker: string) => {
if (hotkey) { if (hotkey) {
return `<span class="b3-menu__accelerator">${updateHotkeyTip(hotkey)}</span>`; return `<span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip(hotkey)}</span>`;
} else if (marker) { } else if (marker) {
return `<span class="b3-list-item__meta">${marker}</span>`; return `<span class="b3-list-item__meta">${marker}</span>`;
} }
@ -139,7 +139,7 @@ export const hintSlash = (key: string, protyle: IProtyle) => {
filter: [window.siyuan.languages.table, "table", "表格", "biaoge", "bg"], filter: [window.siyuan.languages.table, "table", "表格", "biaoge", "bg"],
id: "table", id: "table",
value: `| ${Lute.Caret} | | |\n| --- | --- | --- |\n| | | |\n| | | |`, value: `| ${Lute.Caret} | | |\n| --- | --- | --- |\n| | | |\n| | | |`,
html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconTable"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.table}</span><span class="b3-menu__accelerator">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.table.custom))}</span></div>`, html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconTable"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.table}</span><span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.table.custom))}</span></div>`,
}, { }, {
filter: [window.siyuan.languages.line, "thematic break", "divider", "分隔线", "分割线", "fengexian", "fgx"], filter: [window.siyuan.languages.line, "thematic break", "divider", "分隔线", "分割线", "fengexian", "fgx"],
id: "line", id: "line",
@ -168,62 +168,62 @@ export const hintSlash = (key: string, protyle: IProtyle) => {
filter: [window.siyuan.languages.link, "link", "a", "链接", "lianjie", "lj"], filter: [window.siyuan.languages.link, "link", "a", "链接", "lianjie", "lj"],
id: "link", id: "link",
value: "a", value: "a",
html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconLink"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.link}</span><span class="b3-menu__accelerator">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.link.custom))}</span></div>`, html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconLink"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.link}</span><span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.link.custom))}</span></div>`,
}, { }, {
filter: [window.siyuan.languages.bold, "bold", "strong", "粗体", "cuti", "ct", "加粗", "jiacu", "jc"], filter: [window.siyuan.languages.bold, "bold", "strong", "粗体", "cuti", "ct", "加粗", "jiacu", "jc"],
id: "bold", id: "bold",
value: "strong", value: "strong",
html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconBold"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.bold}</span><span class="b3-menu__accelerator">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.bold.custom))}</span></div>`, html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconBold"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.bold}</span><span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.bold.custom))}</span></div>`,
}, { }, {
filter: [window.siyuan.languages.italic, "italic", "em", "斜体", "xieti", "xt"], filter: [window.siyuan.languages.italic, "italic", "em", "斜体", "xieti", "xt"],
id: "italic", id: "italic",
value: "em", value: "em",
html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconItalic"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.italic}</span><span class="b3-menu__accelerator">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.italic.custom))}</span></div>`, html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconItalic"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.italic}</span><span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.italic.custom))}</span></div>`,
}, { }, {
filter: [window.siyuan.languages.underline, "underline", "下划线", "xiahuaxian", "xhx"], filter: [window.siyuan.languages.underline, "underline", "下划线", "xiahuaxian", "xhx"],
id: "underline", id: "underline",
value: "u", value: "u",
html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconUnderline"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.underline}</span><span class="b3-menu__accelerator">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.underline.custom))}</span></div>`, html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconUnderline"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.underline}</span><span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.underline.custom))}</span></div>`,
}, { }, {
filter: [window.siyuan.languages.strike, "strike", "delete", "删除线", "shanchuxian", "scx"], filter: [window.siyuan.languages.strike, "strike", "delete", "删除线", "shanchuxian", "scx"],
id: "strike", id: "strike",
value: "s", value: "s",
html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconStrike"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.strike}</span><span class="b3-menu__accelerator">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.strike.custom))}</span></div>`, html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconStrike"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.strike}</span><span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.strike.custom))}</span></div>`,
}, { }, {
filter: [window.siyuan.languages.mark, "mark", "标记", "biaoji", "bj", "高亮", "gaoliang", "gl"], filter: [window.siyuan.languages.mark, "mark", "标记", "biaoji", "bj", "高亮", "gaoliang", "gl"],
id: "mark", id: "mark",
value: "mark", value: "mark",
html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconMark"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.mark}</span><span class="b3-menu__accelerator">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.mark.custom))}</span></div>`, html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconMark"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.mark}</span><span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.mark.custom))}</span></div>`,
}, { }, {
filter: [window.siyuan.languages.sup, "superscript", "上标", "shangbiao", "sb"], filter: [window.siyuan.languages.sup, "superscript", "上标", "shangbiao", "sb"],
id: "sup", id: "sup",
value: "sup", value: "sup",
html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconSup"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.sup}</span><span class="b3-menu__accelerator">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.sup.custom))}</span></div>`, html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconSup"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.sup}</span><span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.sup.custom))}</span></div>`,
}, { }, {
filter: [window.siyuan.languages.sub, "subscript", "下标", "xiaobiao", "xb"], filter: [window.siyuan.languages.sub, "subscript", "下标", "xiaobiao", "xb"],
id: "sub", id: "sub",
value: "sub", value: "sub",
html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconSub"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.sub}</span><span class="b3-menu__accelerator">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.sub.custom))}</span></div>`, html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconSub"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.sub}</span><span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.sub.custom))}</span></div>`,
}, { }, {
filter: [window.siyuan.languages["inline-code"], "inline code", "行级代码", "hangjidaima", "hjdm"], filter: [window.siyuan.languages["inline-code"], "inline code", "行级代码", "hangjidaima", "hjdm"],
id: "inlineCode", id: "inlineCode",
value: "code", value: "code",
html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconInlineCode"></use></svg><span class="b3-list-item__text">${window.siyuan.languages["inline-code"]}</span><span class="b3-menu__accelerator">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert["inline-code"].custom))}</span></div>`, html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconInlineCode"></use></svg><span class="b3-list-item__text">${window.siyuan.languages["inline-code"]}</span><span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert["inline-code"].custom))}</span></div>`,
}, { }, {
filter: [window.siyuan.languages.kbd, "kbd", "键盘", "jianpan", "jp"], filter: [window.siyuan.languages.kbd, "kbd", "键盘", "jianpan", "jp"],
id: "kbd", id: "kbd",
value: "kbd", value: "kbd",
html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconKeymap"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.kbd}</span><span class="b3-menu__accelerator">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.kbd.custom))}</span></div>`, html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconKeymap"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.kbd}</span><span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.kbd.custom))}</span></div>`,
}, { }, {
filter: [window.siyuan.languages.tag, "tags", "标签", "biaoqian", "bq"], filter: [window.siyuan.languages.tag, "tags", "标签", "biaoqian", "bq"],
id: "tag", id: "tag",
value: "tag", value: "tag",
html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconTags"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.tag}</span><span class="b3-menu__accelerator">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.tag.custom))}</span></div>`, html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconTags"></use></svg><span class="b3-list-item__text">${window.siyuan.languages.tag}</span><span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert.tag.custom))}</span></div>`,
}, { }, {
filter: [window.siyuan.languages["inline-math"], "inline formulas", "inline math", "行级公式", "hangjigongshi", "hjgs", "行级数学公式", "hangjishuxvegongshi", "hangjishuxuegongshi", "hjsxgs"], filter: [window.siyuan.languages["inline-math"], "inline formulas", "inline math", "行级公式", "hangjigongshi", "hjgs", "行级数学公式", "hangjishuxvegongshi", "hangjishuxuegongshi", "hjsxgs"],
id: "inlineMath", id: "inlineMath",
value: "inline-math", value: "inline-math",
html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconMath"></use></svg><span class="b3-list-item__text">${window.siyuan.languages["inline-math"]}</span><span class="b3-menu__accelerator">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert["inline-math"].custom))}</span></div>`, html: `<div class="b3-list-item__first"><svg class="b3-list-item__graphic"><use xlink:href="#iconMath"></use></svg><span class="b3-list-item__text">${window.siyuan.languages["inline-math"]}</span><span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${updateHotkeyTip((window.siyuan.config.keymap.editor.insert["inline-math"].custom))}</span></div>`,
}, { }, {
value: "", value: "",
id: "separator_3", id: "separator_3",

View file

@ -542,9 +542,7 @@ ${genHintItemHTML(item)}
if (this.lastIndex > -1) { if (this.lastIndex > -1) {
range.setStart(range.startContainer, this.lastIndex); range.setStart(range.startContainer, this.lastIndex);
if (isIPhone()) { focusByRange(range);
focusByRange(range);
}
} }
// 新建文件 // 新建文件
if (Constants.BLOCK_HINT_KEYS.includes(this.splitChar) && value.startsWith("((newFile ") && value.endsWith(`${Lute.Caret}'))`)) { if (Constants.BLOCK_HINT_KEYS.includes(this.splitChar) && value.startsWith("((newFile ") && value.endsWith(`${Lute.Caret}'))`)) {

View file

@ -138,7 +138,7 @@ export const genAVValueHTML = (value: IAVCellValue) => {
if (item && item.block) { if (item && item.block) {
const rowID = value.relation.blockIDs[index]; const rowID = value.relation.blockIDs[index];
if (item?.isDetached) { if (item?.isDetached) {
html += `<span data-row-id="${rowID}" class="av__cell--relation"><span class="b3-menu__avemoji"></span><span class="av__celltext">${Lute.EscapeHTMLStr(item.block.content || window.siyuan.languages.untitled)}</span></span>`; html += `<span data-row-id="${rowID}" class="av__cell--relation"><span><span class="fn__space--5"></span></span><span class="av__celltext">${Lute.EscapeHTMLStr(item.block.content || window.siyuan.languages.untitled)}</span></span>`;
} else { } else {
// data-block-id 用于更新 emoji // data-block-id 用于更新 emoji
html += `<span data-row-id="${rowID}" class="av__cell--relation" data-block-id="${item.block.id}"><span class="b3-menu__avemoji" data-unicode="${item.block.icon || ""}">${unicode2Emoji(item.block.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].file)}</span><span data-type="block-ref" data-id="${item.block.id}" data-subtype="s" class="av__celltext av__celltext--ref">${Lute.EscapeHTMLStr(item.block.content || window.siyuan.languages.untitled)}</span></span>`; html += `<span data-row-id="${rowID}" class="av__cell--relation" data-block-id="${item.block.id}"><span class="b3-menu__avemoji" data-unicode="${item.block.icon || ""}">${unicode2Emoji(item.block.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].file)}</span><span data-type="block-ref" data-id="${item.block.id}" data-subtype="s" class="av__celltext av__celltext--ref">${Lute.EscapeHTMLStr(item.block.content || window.siyuan.languages.untitled)}</span></span>`;

View file

@ -131,6 +131,20 @@ export const openCalcMenu = async (protyle: IProtyle, calcElement: HTMLElement,
blockID, blockID,
target: calcElement target: calcElement
}); });
if (panelData?.data && type !== "checkbox") {
// 汇总字段汇总方式中才有“显示唯一值”选项 Add "Show unique values" to the calculation of the database rollup field https://github.com/siyuan-note/siyuan/issues/15852
calcItem({
menu,
protyle,
colId,
avId,
oldOperator,
operator: "Unique values",
data: panelData?.data,
blockID,
target: calcElement
});
}
calcItem({ calcItem({
menu, menu,
protyle, protyle,
@ -488,6 +502,8 @@ export const getNameByOperator = (operator: string, isRollup: boolean) => {
case undefined: case undefined:
case "": case "":
return isRollup ? window.siyuan.languages.original : window.siyuan.languages.calcOperatorNone; return isRollup ? window.siyuan.languages.original : window.siyuan.languages.calcOperatorNone;
case "Unique values": // 仅汇总字段的汇总方式在使用
return window.siyuan.languages.uniqueValues;
case "Count all": case "Count all":
return window.siyuan.languages.calcOperatorCountAll; return window.siyuan.languages.calcOperatorCountAll;
case "Count values": case "Count values":

View file

@ -335,7 +335,7 @@ export const genCellValue = (colType: TAVCol, value: string | any) => {
cellValue = { cellValue = {
type: colType, type: colType,
number: { number: {
content: null, content: 0,
isNotEmpty: false isNotEmpty: false
} }
}; };
@ -863,6 +863,10 @@ export const updateCellsValue = (protyle: IProtyle, nodeElement: HTMLElement, va
doOperations.push(...operations.doOperations); doOperations.push(...operations.doOperations);
undoOperations.push(...operations.undoOperations); undoOperations.push(...operations.undoOperations);
} }
// formattedContent 在单元格渲染时没有用到,需对比保持一致
if (type === "date") {
cellValue.date.formattedContent = oldValue.date.formattedContent;
}
if (objEquals(cellValue, oldValue)) { if (objEquals(cellValue, oldValue)) {
return; return;
} }
@ -989,21 +993,27 @@ export const renderCell = (cellValue: IAVCellValue, rowIndex = 0, showIcon = tru
} }
text += "</div>"; text += "</div>";
} else if (cellValue.type === "rollup") { } else if (cellValue.type === "rollup") {
let rollupType;
cellValue?.rollup?.contents?.forEach((item) => { cellValue?.rollup?.contents?.forEach((item) => {
const rollupText = ["template", "select", "mSelect", "mAsset", "checkbox", "relation"].includes(item.type) ? renderCell(item, rowIndex, showIcon, type) : renderRollup(item); const rollupText = ["template", "select", "mSelect", "mAsset", "relation"].includes(item.type) ? renderCell(item, rowIndex, showIcon, type) : renderRollup(item, showIcon);
if (rollupText) { if (rollupText) {
text += rollupText + ", "; text += rollupText + (item.type === "checkbox" ? "" : ", ");
} }
rollupType = item.type;
}); });
if (text && text.endsWith(", ")) { if (text) {
text = text.substring(0, text.length - 2); if (rollupType === "checkbox") {
text = `<div class="fn__flex">${text}</div>`;
} else if (text.endsWith(", ")) {
text = text.substring(0, text.length - 2);
}
} }
} else if (cellValue.type === "relation") { } else if (cellValue.type === "relation") {
cellValue?.relation?.contents?.forEach((item, index) => { cellValue?.relation?.contents?.forEach((item, index) => {
if (item && item.block) { if (item && item.block) {
const rowID = cellValue.relation.blockIDs[index]; const rowID = cellValue.relation.blockIDs[index];
if (item?.isDetached) { if (item?.isDetached) {
text += `<span data-row-id="${rowID}" class="av__cell--relation"><span class="b3-menu__avemoji${showIcon ? "" : " fn__none"}"></span><span class="av__celltext">${Lute.EscapeHTMLStr(item.block.content || window.siyuan.languages.untitled)}</span></span>`; text += `<span data-row-id="${rowID}" class="av__cell--relation"><span class="${showIcon ? "" : " fn__none"}"><span class="fn__space--5"></span></span><span class="av__celltext">${Lute.EscapeHTMLStr(item.block.content || window.siyuan.languages.untitled)}</span></span>`;
} else { } else {
// data-block-id 用于更新 emoji // data-block-id 用于更新 emoji
text += `<span data-row-id="${rowID}" class="av__cell--relation" data-block-id="${item.block.id}"><span class="b3-menu__avemoji${showIcon ? "" : " fn__none"}" data-unicode="${item.block.icon || ""}">${unicode2Emoji(item.block.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].file)}</span><span data-type="block-ref" data-id="${item.block.id}" data-subtype="s" class="av__celltext av__celltext--ref">${Lute.EscapeHTMLStr(item.block.content || window.siyuan.languages.untitled)}</span></span>`; text += `<span data-row-id="${rowID}" class="av__cell--relation" data-block-id="${item.block.id}"><span class="b3-menu__avemoji${showIcon ? "" : " fn__none"}" data-unicode="${item.block.icon || ""}">${unicode2Emoji(item.block.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].file)}</span><span data-type="block-ref" data-id="${item.block.id}" data-subtype="s" class="av__celltext av__celltext--ref">${Lute.EscapeHTMLStr(item.block.content || window.siyuan.languages.untitled)}</span></span>`;
@ -1024,7 +1034,7 @@ export const renderCell = (cellValue: IAVCellValue, rowIndex = 0, showIcon = tru
return text; return text;
}; };
const renderRollup = (cellValue: IAVCellValue) => { const renderRollup = (cellValue: IAVCellValue, showIcon: boolean) => {
let text = ""; let text = "";
if (["text"].includes(cellValue.type)) { if (["text"].includes(cellValue.type)) {
text = cellValue ? (cellValue[cellValue.type as "text"].content || "") : ""; text = cellValue ? (cellValue[cellValue.type as "text"].content || "") : "";
@ -1042,10 +1052,12 @@ const renderRollup = (cellValue: IAVCellValue) => {
if (cellValue?.isDetached) { if (cellValue?.isDetached) {
text = `<span class="av__celltext">${Lute.EscapeHTMLStr(cellValue.block?.content || window.siyuan.languages.untitled)}</span>`; text = `<span class="av__celltext">${Lute.EscapeHTMLStr(cellValue.block?.content || window.siyuan.languages.untitled)}</span>`;
} else { } else {
text = `<span data-type="block-ref" data-id="${cellValue.block?.id}" data-subtype="s" class="av__celltext av__celltext--ref">${Lute.EscapeHTMLStr(cellValue.block?.content || window.siyuan.languages.untitled)}</span>`; text = `<span class="b3-menu__avemoji${showIcon ? "" : " fn__none"}" data-unicode="${cellValue.block.icon || ""}">${unicode2Emoji(cellValue.block.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].file)}</span><span data-type="block-ref" data-id="${cellValue.block?.id}" data-subtype="s" class="av__celltext av__celltext--ref">${Lute.EscapeHTMLStr(cellValue.block?.content || window.siyuan.languages.untitled)}</span>`;
} }
} else if (cellValue.type === "number") { } else if (cellValue.type === "number") {
text = cellValue?.number.formattedContent || cellValue?.number.content.toString() || ""; text = cellValue?.number.formattedContent || cellValue?.number.content.toString() || "";
} else if (cellValue.type === "checkbox") {
text += `<svg class="av__checkbox"><use xlink:href="#icon${cellValue?.checkbox?.checked ? "Check" : "Uncheck"}"></use></svg><span class="fn__space"></span>`;
} else if (["date", "updated", "created"].includes(cellValue.type)) { } else if (["date", "updated", "created"].includes(cellValue.type)) {
const dataValue = cellValue ? cellValue[cellValue.type as "date"] : null; const dataValue = cellValue ? cellValue[cellValue.type as "date"] : null;
if (dataValue.formattedContent) { if (dataValue.formattedContent) {

View file

@ -932,11 +932,12 @@ export const showColMenu = (protyle: IProtyle, blockElement: Element, cellElemen
}); });
menu.addItem({ menu.addItem({
icon: "iconSoftWrap", icon: "iconSoftWrap",
label: `<label class="fn__flex" style="margin-bottom: 4px"><span>${window.siyuan.languages.wrap}</span><span class="fn__space fn__flex-1"></span> label: `<label class="fn__flex fn__pointer"><span>${window.siyuan.languages.wrap}</span><span class="fn__space fn__flex-1"></span>
<input type="checkbox" class="b3-switch b3-switch--menu"${cellElement.dataset.wrap === "true" ? " checked" : ""}></label>`, <input type="checkbox" class="b3-switch b3-switch--menu"${cellElement.dataset.wrap === "true" ? " checked" : ""}></label>`,
bind(element) { bind(element) {
const wrapElement = element.querySelector(".b3-switch") as HTMLInputElement; const wrapElement = element.querySelector(".b3-switch") as HTMLInputElement;
wrapElement.addEventListener("change", () => { wrapElement.addEventListener("change", () => {
cellElement.dataset.wrap = wrapElement.checked.toString();
transaction(protyle, [{ transaction(protyle, [{
action: "setAttrViewColWrap", action: "setAttrViewColWrap",
id: colId, id: colId,

View file

@ -2,7 +2,7 @@ import * as dayjs from "dayjs";
import {genCellValueByElement, updateCellsValue} from "./cell"; import {genCellValueByElement, updateCellsValue} from "./cell";
export const getDateHTML = (cellElements: HTMLElement[]) => { export const getDateHTML = (cellElements: HTMLElement[]) => {
const cellValue = genCellValueByElement("date", cellElements[0]).date; const cellValue = genCellValueByElement("date", cellElements[0]).date;
const isNotTime = cellValue.isNotTime; const isNotTime = cellValue.isNotTime;
let value = ""; let value = "";
const currentDate = new Date().getTime(); const currentDate = new Date().getTime();
@ -64,9 +64,9 @@ export const bindDateEvent = (options: {
} }
if (event.key === "Enter") { if (event.key === "Enter") {
updateCellsValue(options.protyle, options.blockElement as HTMLElement, { updateCellsValue(options.protyle, options.blockElement as HTMLElement, {
content: getFullYearTime(inputElements[0].dataset.value), content: getFullYearTime(inputElements[0].dataset.value) || 0,
isNotEmpty: inputElements[0].value !== "", isNotEmpty: inputElements[0].value !== "",
content2: getFullYearTime(inputElements[1].dataset.value), content2: getFullYearTime(inputElements[1].dataset.value) || 0,
isNotEmpty2: inputElements[1].value !== "", isNotEmpty2: inputElements[1].value !== "",
hasEndDate: inputElements[2].checked, hasEndDate: inputElements[2].checked,
isNotTime: !inputElements[3].checked, isNotTime: !inputElements[3].checked,
@ -114,9 +114,9 @@ export const bindDateEvent = (options: {
}); });
return () => { return () => {
updateCellsValue(options.protyle, options.blockElement as HTMLElement, { updateCellsValue(options.protyle, options.blockElement as HTMLElement, {
content: getFullYearTime(inputElements[0].dataset.value), content: getFullYearTime(inputElements[0].dataset.value) || 0,
isNotEmpty: inputElements[0].value !== "", isNotEmpty: inputElements[0].value !== "",
content2: getFullYearTime(inputElements[1].dataset.value), content2: getFullYearTime(inputElements[1].dataset.value) || 0,
isNotEmpty2: inputElements[1].value !== "", isNotEmpty2: inputElements[1].value !== "",
hasEndDate: inputElements[2].checked, hasEndDate: inputElements[2].checked,
isNotTime: !inputElements[3].checked, isNotTime: !inputElements[3].checked,

View file

@ -154,6 +154,10 @@ const afterRenderGallery = (options: ITableOptions) => {
if (typeof options.resetData.oldOffset === "number") { if (typeof options.resetData.oldOffset === "number") {
options.protyle.contentElement.scrollTop = options.resetData.oldOffset; options.protyle.contentElement.scrollTop = options.resetData.oldOffset;
} }
if (options.blockElement.getAttribute("data-need-focus") === "true") {
focusBlock(options.blockElement);
options.blockElement.removeAttribute("data-need-focus");
}
options.blockElement.setAttribute("data-render", "true"); options.blockElement.setAttribute("data-render", "true");
if (options.resetData.alignSelf) { if (options.resetData.alignSelf) {
options.blockElement.style.alignSelf = options.resetData.alignSelf; options.blockElement.style.alignSelf = options.resetData.alignSelf;

View file

@ -39,7 +39,7 @@ import {
openViewMenu openViewMenu
} from "./view"; } from "./view";
import {focusBlock} from "../../util/selection"; import {focusBlock} from "../../util/selection";
import {setPageSize} from "./row"; import {getFieldIdByCellElement, setPageSize} from "./row";
import {bindRelationEvent, getRelationHTML, openSearchAV, setRelationCell, updateRelation} from "./relation"; import {bindRelationEvent, getRelationHTML, openSearchAV, setRelationCell, updateRelation} from "./relation";
import {bindRollupData, getRollupHTML, goSearchRollupCol} from "./rollup"; import {bindRollupData, getRollupHTML, goSearchRollupCol} from "./rollup";
import {updateCellsValue} from "./cell"; import {updateCellsValue} from "./cell";
@ -157,7 +157,18 @@ export const openMenuPanel = (options: {
const menuElement = avPanelElement.lastElementChild as HTMLElement; const menuElement = avPanelElement.lastElementChild as HTMLElement;
let tabRect = options.blockElement.querySelector(`.av__views, .av__row[data-col-id="${options.colId}"] > .block__logo`)?.getBoundingClientRect(); let tabRect = options.blockElement.querySelector(`.av__views, .av__row[data-col-id="${options.colId}"] > .block__logo`)?.getBoundingClientRect();
if (["select", "date", "asset", "relation", "rollup"].includes(options.type)) { if (["select", "date", "asset", "relation", "rollup"].includes(options.type)) {
const cellRect = options.cellElements[options.cellElements.length - 1].getBoundingClientRect(); let lastElement = options.cellElements[options.cellElements.length - 1];
if (!options.blockElement.contains(lastElement)) {
// https://github.com/siyuan-note/siyuan/issues/15839
const rowID = getFieldIdByCellElement(lastElement, data.viewType);
if (data.viewType === "table") {
lastElement = options.blockElement.querySelector(`.av__row[data-id="${rowID}"] .av__cell[data-col-id="${lastElement.dataset.colId}"]`);
} else {
lastElement = options.blockElement.querySelector(`.av__gallery-item[data-id="${rowID}"] .av__cell[data-field-id="${lastElement.dataset.fieldId}"]`);
}
}
const cellRect = (lastElement || options.cellElements[options.cellElements.length - 1]).getBoundingClientRect();
if (options.type === "select") { if (options.type === "select") {
bindSelectEvent(options.protyle, data, menuElement, options.cellElements, options.blockElement); bindSelectEvent(options.protyle, data, menuElement, options.cellElements, options.blockElement);
} else if (options.type === "date") { } else if (options.type === "date") {
@ -1201,10 +1212,10 @@ export const openMenuPanel = (options: {
title: isTwoWay ? window.siyuan.languages.removeColConfirm : window.siyuan.languages.deleteOpConfirm, title: isTwoWay ? window.siyuan.languages.removeColConfirm : window.siyuan.languages.deleteOpConfirm,
content: `<div class="b3-dialog__content"> content: `<div class="b3-dialog__content">
${isTwoWay ? window.siyuan.languages.confirmRemoveRelationField ${isTwoWay ? window.siyuan.languages.confirmRemoveRelationField
.replace("${x}", menuElement.querySelector("input").value || window.siyuan.languages._kernel[272]) .replace("${x}", menuElement.querySelector("input").value || window.siyuan.languages._kernel[272])
.replace("${y}", menuElement.querySelector('.b3-menu__item[data-type="goSearchAV"] .b3-menu__accelerator').textContent) .replace("${y}", menuElement.querySelector('.b3-menu__item[data-type="goSearchAV"] .b3-menu__accelerator').textContent)
.replace("${z}", (menuElement.querySelector('input[data-type="colName"]') as HTMLInputElement).value || window.siyuan.languages._kernel[272]) .replace("${z}", (menuElement.querySelector('input[data-type="colName"]') as HTMLInputElement).value || window.siyuan.languages._kernel[272])
: window.siyuan.languages.removeCol.replace("${x}", menuElement.querySelector("input").value || window.siyuan.languages._kernel[272])} : window.siyuan.languages.removeCol.replace("${x}", menuElement.querySelector("input").value || window.siyuan.languages._kernel[272])}
<div class="fn__hr--b"></div> <div class="fn__hr--b"></div>
<button class="fn__block b3-button b3-button--remove" data-action="delete">${isTwoWay ? window.siyuan.languages.removeBothRelationField : window.siyuan.languages.delete}</button> <button class="fn__block b3-button b3-button--remove" data-action="delete">${isTwoWay ? window.siyuan.languages.removeBothRelationField : window.siyuan.languages.delete}</button>
<div class="fn__hr"></div> <div class="fn__hr"></div>

View file

@ -13,6 +13,8 @@ import {getFieldsByData, getViewName} from "./view";
import {getColId} from "./col"; import {getColId} from "./col";
import {getFieldIdByCellElement} from "./row"; import {getFieldIdByCellElement} from "./row";
import {isMobile} from "../../../util/functions"; import {isMobile} from "../../../util/functions";
import {showMessage} from "../../../dialog/message";
import {writeText} from "../../util/compatibility";
interface IAVItem { interface IAVItem {
avID: string; avID: string;
@ -249,6 +251,17 @@ export const toggleUpdateRelationBtn = (menuItemsElement: HTMLElement, avId: str
} }
}; };
const updateCopyRelatedItems = (menuElement: Element) => {
const inputElement = menuElement.querySelector(".b3-form__icona .b3-text-field");
if (menuElement.querySelector(".b3-menu__icon.fn__grab")) {
inputElement.nextElementSibling.classList.remove("fn__none");
inputElement.classList.add("b3-form__icona-input");
} else {
inputElement.nextElementSibling.classList.add("fn__none");
inputElement.classList.remove("b3-form__icona-input");
}
};
const genSelectItemHTML = (options: { const genSelectItemHTML = (options: {
type: "selected" | "empty" | "unselect", type: "selected" | "empty" | "unselect",
id?: string, id?: string,
@ -322,6 +335,7 @@ ${keyword ? genSelectItemHTML({
text: menuElement.querySelector(".popover__block").outerHTML text: menuElement.querySelector(".popover__block").outerHTML
}) : (html ? "" : genSelectItemHTML({type: "empty"}))}`; }) : (html ? "" : genSelectItemHTML({type: "empty"}))}`;
menuElement.querySelector(".b3-menu__items .b3-menu__item:not(.fn__none)").classList.add("b3-menu__item--current"); menuElement.querySelector(".b3-menu__items .b3-menu__item:not(.fn__none)").classList.add("b3-menu__item--current");
updateCopyRelatedItems(menuElement);
}); });
}; };
@ -369,7 +383,7 @@ ${html || genSelectItemHTML({type: "empty"})}`;
options.menuElement.querySelector(".b3-menu__items .b3-menu__item:not(.fn__none)").classList.add("b3-menu__item--current"); options.menuElement.querySelector(".b3-menu__items .b3-menu__item:not(.fn__none)").classList.add("b3-menu__item--current");
const inputElement = options.menuElement.querySelector("input"); const inputElement = options.menuElement.querySelector("input");
inputElement.focus(); inputElement.focus();
const databaseName = inputElement.parentElement.querySelector(".popover__block"); const databaseName = inputElement.parentElement.parentElement.querySelector(".popover__block");
databaseName.innerHTML = Lute.EscapeHTMLStr(response.data.name); databaseName.innerHTML = Lute.EscapeHTMLStr(response.data.name);
databaseName.setAttribute("data-id", response.data.blockIDs[0]); databaseName.setAttribute("data-id", response.data.blockIDs[0]);
const listElement = options.menuElement.querySelector(".b3-menu__items"); const listElement = options.menuElement.querySelector(".b3-menu__items");
@ -396,6 +410,26 @@ ${html || genSelectItemHTML({type: "empty"})}`;
event.stopPropagation(); event.stopPropagation();
filterItem(options.menuElement, options.cellElements[0], inputElement.value); filterItem(options.menuElement, options.cellElements[0], inputElement.value);
}); });
updateCopyRelatedItems(options.menuElement)
options.menuElement.querySelector('[data-type="copyRelatedItems"]').addEventListener("click", () => {
let copyText = "";
const selectedElements = options.menuElement.querySelectorAll('.b3-menu__item[draggable="true"]');
selectedElements.forEach((item: HTMLElement) => {
if (selectedElements.length > 1) {
copyText += "* ";
}
const textElement = item.querySelector(".b3-menu__label") as HTMLElement;
if (!textElement.dataset.id || textElement.dataset.id === "undefined") {
copyText += textElement.textContent + "\n";
} else {
copyText += `((${textElement.dataset.id} "${textElement.textContent}"))\n`;
}
});
if (copyText) {
writeText(copyText.trimEnd());
showMessage(window.siyuan.languages.copied);
}
});
}); });
}; };
@ -410,9 +444,12 @@ export const getRelationHTML = (data: IAV, cellElements?: HTMLElement[]) => {
if (colRelationData && colRelationData.avID) { if (colRelationData && colRelationData.avID) {
return `<div data-av-id="${colRelationData.avID}" class="fn__flex-column"> return `<div data-av-id="${colRelationData.avID}" class="fn__flex-column">
<div class="b3-menu__item" data-type="nobg"> <div class="b3-menu__item" data-type="nobg">
<input class="b3-text-field fn__flex-1"/> <div class="b3-form__icona">
<input class="b3-text-field fn__flex-1 b3-form__icona-input fn__size200"/>
<svg class="b3-form__icona-icon ariaLabel" data-position="north" data-type="copyRelatedItems" aria-label="${window.siyuan.languages.copy} ${window.siyuan.languages.relatedItems}"><use xlink:href="#iconCopy"></use></svg>
</div>
<span class="fn__space"></span> <span class="fn__space"></span>
<span style="color: var(--b3-protyle-inline-blockref-color);" data-id="" class="popover__block fn__pointer"></span> <span style="color: var(--b3-protyle-inline-blockref-color);max-width: 200px" data-id="" class="popover__block fn__pointer fn__ellipsis"></span>
</div> </div>
<div class="fn__hr"></div> <div class="fn__hr"></div>
<div class="b3-menu__items"> <div class="b3-menu__items">
@ -539,4 +576,5 @@ class="${target.className} ariaLabel" draggable="true">${genSelectItemHTML({
} }
} }
updateCellsValue(protyle, nodeElement, newValue, cellElements); updateCellsValue(protyle, nodeElement, newValue, cellElements);
updateCopyRelatedItems(menuElement);
}; };

View file

@ -282,6 +282,10 @@ const renderGroupTable = (options: ITableOptions) => {
}; };
const afterRenderTable = (options: ITableOptions) => { const afterRenderTable = (options: ITableOptions) => {
if (options.blockElement.getAttribute("data-need-focus") === "true") {
focusBlock(options.blockElement);
options.blockElement.removeAttribute("data-need-focus");
}
options.blockElement.setAttribute("data-render", "true"); options.blockElement.setAttribute("data-render", "true");
options.blockElement.querySelector(".av__scroll").scrollLeft = options.resetData.left; options.blockElement.querySelector(".av__scroll").scrollLeft = options.resetData.left;
options.blockElement.style.alignSelf = options.resetData.alignSelf; options.blockElement.style.alignSelf = options.resetData.alignSelf;
@ -761,6 +765,7 @@ export const refreshAV = (protyle: IProtyle, operation: IOperation) => {
const attrElement = document.querySelector(`.b3-dialog--open[data-key="${Constants.DIALOG_ATTR}"] .custom-attr > [data-av-id="${avID}"]`) as HTMLElement; const attrElement = document.querySelector(`.b3-dialog--open[data-key="${Constants.DIALOG_ATTR}"] .custom-attr > [data-av-id="${avID}"]`) as HTMLElement;
if (attrElement) { if (attrElement) {
// 更新属性面板 // 更新属性面板
attrElement.removeAttribute("data-rendering");
renderAVAttribute(attrElement.parentElement, attrElement.dataset.nodeId, protyle); renderAVAttribute(attrElement.parentElement, attrElement.dataset.nodeId, protyle);
} else { } else {
if (operation.action === "insertAttrViewBlock" && operation.context?.ignoreTip !== "true") { if (operation.action === "insertAttrViewBlock" && operation.context?.ignoreTip !== "true") {

View file

@ -81,7 +81,7 @@ const genSearchList = (element: Element, keyword: string, avId: string, isRelati
showMessage(window.siyuan.languages.selectRelation); showMessage(window.siyuan.languages.selectRelation);
return; return;
} }
fetchPost(isRelation ? "/api/av/searchAttributeViewRelationKey" : "/api/av/searchAttributeViewNonRelationKey", { fetchPost(isRelation ? "/api/av/searchAttributeViewRelationKey" : "/api/av/searchAttributeViewRollupDestKeys", {
avID: avId, avID: avId,
keyword keyword
}, (response) => { }, (response) => {

View file

@ -59,7 +59,7 @@ const filterSelectHTML = (key: string, options: {
<span class="fn__ellipsis">${escapeHtml(key)}</span> <span class="fn__ellipsis">${escapeHtml(key)}</span>
</span> </span>
</div> </div>
<span class="b3-menu__accelerator">${window.siyuan.languages.enterKey}</span> <span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${window.siyuan.languages.enterKey}</span>
</button>${html}`; </button>${html}`;
} else if (html.indexOf("b3-menu__item--current") === -1) { } else if (html.indexOf("b3-menu__item--current") === -1) {
html = html.replace('class="b3-menu__item"', 'class="b3-menu__item b3-menu__item--current"'); html = html.replace('class="b3-menu__item"', 'class="b3-menu__item b3-menu__item--current"');

View file

@ -14,7 +14,7 @@ export const mermaidRender = (element: Element, cdn = Constants.PROTYLE_CDN) =>
if (mermaidElements.length === 0) { if (mermaidElements.length === 0) {
return; return;
} }
addScript(`${cdn}/js/mermaid/mermaid.min.js?v=11.6.0`, "protyleMermaidScript").then(() => { addScript(`${cdn}/js/mermaid/mermaid.min.js?v=11.11.0`, "protyleMermaidScript").then(() => {
const config: any = { const config: any = {
securityLevel: "loose", // 升级后无 https://github.com/siyuan-note/siyuan/issues/3587可使用该选项 securityLevel: "loose", // 升级后无 https://github.com/siyuan-note/siyuan/issues/3587可使用该选项
altFontFamily: "sans-serif", altFontFamily: "sans-serif",

View file

@ -901,7 +901,7 @@ export class Toolbar {
this.subElement.style.padding = "0"; this.subElement.style.padding = "0";
} }
this.subElement.innerHTML = `<div ${(isPin && this.subElement.firstElementChild.getAttribute("data-drag") === "true") ? 'data-drag="true"' : ""}><div class="block__icons block__icons--menu fn__flex" style="border-radius: var(--b3-border-radius-b) var(--b3-border-radius-b) 0 0;"> this.subElement.innerHTML = `<div ${(isPin && this.subElement.firstElementChild.getAttribute("data-drag") === "true") ? 'data-drag="true"' : ""}><div class="block__icons block__icons--menu fn__flex" style="border-radius: var(--b3-border-radius-b) var(--b3-border-radius-b) 0 0;">
<span class="fn__flex-1 resize__move"> <span class="fn__flex-1 resize__move" style="line-height: 24px;">
${title} ${title}
</span> </span>
<span class="fn__space"></span> <span class="fn__space"></span>

View file

@ -9,7 +9,7 @@ import {lineNumberRender} from "../render/highlightRender";
import {hideMessage, showMessage} from "../../dialog/message"; import {hideMessage, showMessage} from "../../dialog/message";
import {genUUID} from "../../util/genID"; import {genUUID} from "../../util/genID";
import {getContenteditableElement, getLastBlock} from "../wysiwyg/getBlock"; import {getContenteditableElement, getLastBlock} from "../wysiwyg/getBlock";
import {genEmptyElement} from "../../block/util"; import {genEmptyElement, genHeadingElement} from "../../block/util";
import {transaction} from "../wysiwyg/transaction"; import {transaction} from "../wysiwyg/transaction";
import {focusByRange} from "../util/selection"; import {focusByRange} from "../util/selection";
/// #if !MOBILE /// #if !MOBILE
@ -137,16 +137,22 @@ export const initUI = (protyle: IProtyle) => {
return; return;
} }
} }
const lastRect = protyle.wysiwyg.element.lastElementChild.getBoundingClientRect(); const lastElement = protyle.wysiwyg.element.lastElementChild;
const lastRect = lastElement.getBoundingClientRect();
const range = document.createRange(); const range = document.createRange();
if (event.y > lastRect.bottom) { if (event.y > lastRect.bottom) {
const lastEditElement = getContenteditableElement(getLastBlock(protyle.wysiwyg.element.lastElementChild)); const lastEditElement = getContenteditableElement(getLastBlock(lastElement));
if (!protyle.options.click.preventInsetEmptyBlock && ( if (!protyle.options.click.preventInsetEmptyBlock && (
!lastEditElement || !lastEditElement ||
(protyle.wysiwyg.element.lastElementChild.getAttribute("data-type") !== "NodeParagraph" && protyle.wysiwyg.element.getAttribute("data-doc-type") !== "NodeListItem") || (lastElement.getAttribute("data-type") !== "NodeParagraph" && protyle.wysiwyg.element.getAttribute("data-doc-type") !== "NodeListItem") ||
(protyle.wysiwyg.element.lastElementChild.getAttribute("data-type") === "NodeParagraph" && getContenteditableElement(lastEditElement).innerHTML !== "")) (lastElement.getAttribute("data-type") === "NodeParagraph" && getContenteditableElement(lastEditElement).innerHTML !== ""))
) { ) {
const emptyElement = genEmptyElement(false, false); let emptyElement:Element;
if (lastElement.getAttribute("data-type") === "NodeHeading" && lastElement.getAttribute("fold") === "1") {
emptyElement = genHeadingElement(lastElement) as Element;
} else {
emptyElement = genEmptyElement(false, false);
}
protyle.wysiwyg.element.insertAdjacentElement("beforeend", emptyElement); protyle.wysiwyg.element.insertAdjacentElement("beforeend", emptyElement);
transaction(protyle, [{ transaction(protyle, [{
action: "insert", action: "insert",

View file

@ -44,10 +44,25 @@ const moveToNew = (protyle: IProtyle, sourceElements: Element[], targetElement:
const newSourceId = newSourceElement.getAttribute("data-node-id"); const newSourceId = newSourceElement.getAttribute("data-node-id");
const doOperations: IOperation[] = []; const doOperations: IOperation[] = [];
const undoOperations: IOperation[] = []; const undoOperations: IOperation[] = [];
targetElement.insertAdjacentElement(isBottom ? "afterend" : "beforebegin", newSourceElement); let ignoreInsert = false;
if (isBottom &&
targetElement.getAttribute("data-type") === "NodeHeading" &&
targetElement.getAttribute("fold") === "1") {
ignoreInsert = true;
} else if (!isBottom && targetElement.previousElementSibling &&
targetElement.previousElementSibling.getAttribute("data-type") === "NodeHeading" &&
targetElement.previousElementSibling.getAttribute("fold") === "1") {
ignoreInsert = true;
}
if (!ignoreInsert) {
targetElement.insertAdjacentElement(isBottom ? "afterend" : "beforebegin", newSourceElement);
}
if (isBottom) { if (isBottom) {
doOperations.push({ doOperations.push({
action: "insert", action: "insert",
context: {
ignoreProcess: ignoreInsert.toString(),
},
data: newSourceElement.outerHTML, data: newSourceElement.outerHTML,
id: newSourceId, id: newSourceId,
previousID: targetId, previousID: targetId,
@ -55,6 +70,9 @@ const moveToNew = (protyle: IProtyle, sourceElements: Element[], targetElement:
} else { } else {
doOperations.push({ doOperations.push({
action: "insert", action: "insert",
context: {
ignoreProcess: ignoreInsert.toString(),
},
data: newSourceElement.outerHTML, data: newSourceElement.outerHTML,
id: newSourceId, id: newSourceId,
nextID: targetId, nextID: targetId,
@ -139,6 +157,7 @@ const moveToNew = (protyle: IProtyle, sourceElements: Element[], targetElement:
id: newSourceId, id: newSourceId,
}); });
return { return {
ignoreInsert,
doOperations, doOperations,
undoOperations, undoOperations,
topSourceElement, topSourceElement,
@ -150,9 +169,32 @@ const moveTo = async (protyle: IProtyle, sourceElements: Element[], targetElemen
let topSourceElement; let topSourceElement;
const doOperations: IOperation[] = []; const doOperations: IOperation[] = [];
const undoOperations: IOperation[] = []; const undoOperations: IOperation[] = [];
const foldHeadingIds: { id: string, parentID: string }[] = []; const copyFoldHeadingIds: { newId: string, oldId: string }[] = [];
const targetId = targetElement.getAttribute("data-node-id"); const targetId = targetElement.getAttribute("data-node-id");
let tempTargetElement = targetElement; let tempTargetElement = targetElement;
let ignoreInsert = "";
const targetPreviousId = targetElement.previousElementSibling?.getAttribute("data-node-id");
if (position === "afterend" &&
targetElement.getAttribute("data-type") === "NodeHeading" &&
targetElement.getAttribute("fold") === "1") {
ignoreInsert = targetElement.getAttribute("data-subtype")?.replace("h", "");
} else if (position === "beforebegin" && targetElement.previousElementSibling &&
targetElement.previousElementSibling.getAttribute("data-type") === "NodeHeading" &&
targetElement.previousElementSibling.getAttribute("fold") === "1") {
ignoreInsert = targetElement.getAttribute("data-subtype")?.replace("h", "");
}
if (ignoreInsert) {
let breakIgnore = false;
sourceElements.forEach(item => {
if (item.getAttribute("data-type") === "NodeHeading" &&
parseInt(item.getAttribute("data-subtype").replace("h", "")) >= parseInt(ignoreInsert)) {
breakIgnore = true;
}
if (!breakIgnore) {
item.setAttribute("data-remove", "true");
}
});
}
sourceElements.reverse().forEach((item, index) => { sourceElements.reverse().forEach((item, index) => {
const id = item.getAttribute("data-node-id"); const id = item.getAttribute("data-node-id");
const parentID = item.parentElement.getAttribute("data-node-id") || protyle.block.rootID; const parentID = item.parentElement.getAttribute("data-node-id") || protyle.block.rootID;
@ -165,17 +207,19 @@ const moveTo = async (protyle: IProtyle, sourceElements: Element[], targetElemen
topSourceElement = targetElement; topSourceElement = targetElement;
} }
} }
const copyNewId = Lute.NewNodeID();
if (isCopy && item.getAttribute("data-type") === "NodeHeading" && item.getAttribute("fold") === "1") { if (isCopy && item.getAttribute("data-type") === "NodeHeading" && item.getAttribute("fold") === "1") {
item.removeAttribute("fold"); copyFoldHeadingIds.push({
foldHeadingIds.push({id, parentID}); newId: copyNewId,
oldId: id
});
} }
let copyId;
let copyElement; let copyElement;
if (isCopy) { if (isCopy) {
copyId = Lute.NewNodeID();
undoOperations.push({ undoOperations.push({
action: "delete", action: "delete",
id: copyId, id: copyNewId,
}); });
} else { } else {
undoOperations.push({ undoOperations.push({
@ -193,28 +237,41 @@ const moveTo = async (protyle: IProtyle, sourceElements: Element[], targetElemen
} }
} }
const needInset = !ignoreInsert || (ignoreInsert && !item.hasAttribute("data-remove"));
if (isCopy) { if (isCopy) {
item.removeAttribute("data-remove");
copyElement = item.cloneNode(true) as HTMLElement; copyElement = item.cloneNode(true) as HTMLElement;
copyElement.setAttribute("data-node-id", copyId); copyElement.setAttribute("data-node-id", copyNewId);
copyElement.querySelectorAll("[data-node-id]").forEach((e) => { copyElement.querySelectorAll("[data-node-id]").forEach((e) => {
const newId = Lute.NewNodeID(); const newId = Lute.NewNodeID();
e.setAttribute("data-node-id", newId); e.setAttribute("data-node-id", newId);
e.setAttribute("updated", newId.split("-")[0]); e.setAttribute("updated", newId.split("-")[0]);
}); });
tempTargetElement.insertAdjacentElement(position, copyElement); if (needInset) {
tempTargetElement.insertAdjacentElement(position, copyElement);
}
doOperations.push({ doOperations.push({
action: "insert", action: "insert",
id: copyId, context: {
ignoreProcess: (!needInset).toString(),
},
id: copyNewId,
data: copyElement.outerHTML, data: copyElement.outerHTML,
previousID: position === "afterend" ? targetId : copyElement.previousElementSibling?.getAttribute("data-node-id"), // 不能使用常量,移动后会被修改 previousID: position === "afterend" ? targetId : (!needInset ? targetPreviousId : copyElement.previousElementSibling?.getAttribute("data-node-id")), // 不能使用常量,移动后会被修改
parentID: copyElement.parentElement?.getAttribute("data-node-id") || protyle.block.parentID || protyle.block.rootID, parentID: copyElement.parentElement?.getAttribute("data-node-id") || protyle.block.parentID || protyle.block.rootID,
}); });
} else { } else {
tempTargetElement.insertAdjacentElement(position, item); if (needInset) {
tempTargetElement.insertAdjacentElement(position, item);
}
doOperations.push({ doOperations.push({
action: "move", action: "move",
context: {
ignoreProcess: (!needInset).toString(),
},
id, id,
previousID: position === "afterend" ? targetId : item.previousElementSibling?.getAttribute("data-node-id"), // 不能使用常量,移动后会被修改 previousID: position === "afterend" ? targetId : (!needInset ? targetPreviousId : item.previousElementSibling?.getAttribute("data-node-id")), // 不能使用常量,移动后会被修改
parentID: item.parentElement?.getAttribute("data-node-id") || protyle.block.parentID || protyle.block.rootID, parentID: item.parentElement?.getAttribute("data-node-id") || protyle.block.parentID || protyle.block.rootID,
}); });
} }
@ -222,29 +279,26 @@ const moveTo = async (protyle: IProtyle, sourceElements: Element[], targetElemen
tempTargetElement = isCopy ? copyElement : item; tempTargetElement = isCopy ? copyElement : item;
} }
}); });
undoOperations.reverse(); if (ignoreInsert) {
for (let j = 0; j < foldHeadingIds.length; j++) { // 不能在上一个循环中移除,否则会影响位置的判断和 tempTargetElement
const childrenItem = foldHeadingIds[j]; sourceElements.forEach(item => {
const headingIds = await fetchSyncPost("/api/block/getHeadingChildrenIDs", {id: childrenItem.id}); if (item.hasAttribute("data-remove")) {
headingIds.data.reverse().forEach((headingId: string) => { item.remove();
undoOperations.push({ }
action: "move",
id: headingId,
previousID: childrenItem.id,
parentID: childrenItem.parentID,
});
});
undoOperations.push({
action: "foldHeading",
id: childrenItem.id,
data: "remove"
});
doOperations.push({
action: "unfoldHeading",
id: childrenItem.id,
}); });
} }
undoOperations.reverse();
for (let j = 0; j < copyFoldHeadingIds.length; j++) {
const childrenItem = copyFoldHeadingIds[j];
const responseTransaction = await fetchSyncPost("/api/block/getHeadingInsertTransaction", {id: childrenItem.oldId});
responseTransaction.data.doOperations.splice(0, 1);
responseTransaction.data.doOperations[0].previousID = childrenItem.newId;
responseTransaction.data.undoOperations.splice(0, 1);
doOperations.push(...responseTransaction.data.doOperations);
undoOperations.push(...responseTransaction.data.undoOperations);
}
return { return {
ignoreInsert: ignoreInsert ? true : false,
doOperations, doOperations,
undoOperations, undoOperations,
topSourceElement, topSourceElement,
@ -367,7 +421,7 @@ const dragSb = async (protyle: IProtyle, sourceElements: Element[], targetElemen
id: newSourceId, id: newSourceId,
}); });
} else { } else {
const foldHeadingIds: { id: string, parentID: string }[] = []; const copyFoldHeadingIds: { newId: string, oldId: string }[] = [];
let afterPreviousID; let afterPreviousID;
sourceElements.reverse().forEach((item, index) => { sourceElements.reverse().forEach((item, index) => {
const id = item.getAttribute("data-node-id"); const id = item.getAttribute("data-node-id");
@ -384,8 +438,7 @@ const dragSb = async (protyle: IProtyle, sourceElements: Element[], targetElemen
} }
if (item.getAttribute("data-type") === "NodeHeading" && item.getAttribute("fold") === "1") { if (item.getAttribute("data-type") === "NodeHeading" && item.getAttribute("fold") === "1") {
if (isCopy) { if (isCopy) {
item.removeAttribute("fold"); copyFoldHeadingIds.push({oldId: id, newId: copyId});
foldHeadingIds.push({id, parentID});
} }
hasFoldHeading = true; hasFoldHeading = true;
} }
@ -434,29 +487,17 @@ const dragSb = async (protyle: IProtyle, sourceElements: Element[], targetElemen
} }
}); });
undoOperations.reverse(); undoOperations.reverse();
for (let j = 0; j < foldHeadingIds.length; j++) { for (let j = 0; j < copyFoldHeadingIds.length; j++) {
const childrenItem = foldHeadingIds[j]; const childrenItem = copyFoldHeadingIds[j];
const headingIds = await fetchSyncPost("/api/block/getHeadingChildrenIDs", {id: childrenItem.id}); const responseTransaction = await fetchSyncPost("/api/block/getHeadingInsertTransaction", {id: childrenItem.oldId});
headingIds.data.reverse().forEach((headingId: string) => { responseTransaction.data.doOperations.splice(0, 1);
undoOperations.push({ responseTransaction.data.doOperations[0].previousID = childrenItem.newId;
action: "move", responseTransaction.data.undoOperations.splice(0, 1);
id: headingId, doOperations.push(...responseTransaction.data.doOperations);
previousID: childrenItem.id, undoOperations.push(...responseTransaction.data.undoOperations);
parentID: childrenItem.parentID,
});
});
if (j === 0) { if (j === 0) {
afterPreviousID = headingIds.data[0]; afterPreviousID = copyFoldHeadingIds[0].newId;
} }
undoOperations.push({
action: "foldHeading",
id: childrenItem.id,
data: "remove"
});
doOperations.push({
action: "unfoldHeading",
id: childrenItem.id,
});
} }
if (isBottom) { if (isBottom) {
sbElement.insertAdjacentElement("afterbegin", targetElement); sbElement.insertAdjacentElement("afterbegin", targetElement);
@ -595,7 +636,7 @@ const dragSb = async (protyle: IProtyle, sourceElements: Element[], targetElemen
level: "row" level: "row"
}); });
} }
if ((sourceElements.length > 1 || hasFoldHeading)) { if (sourceElements.length > 1 || hasFoldHeading) {
turnsIntoOneTransaction({ turnsIntoOneTransaction({
protyle, protyle,
selectsElement: sourceElements.reverse(), selectsElement: sourceElements.reverse(),
@ -622,6 +663,7 @@ const dragSame = async (protyle: IProtyle, sourceElements: Element[], targetElem
newSourceElement.insertAdjacentHTML("beforeend", `<div class="protyle-attr" contenteditable="false">${Constants.ZWSP}</div>`); newSourceElement.insertAdjacentHTML("beforeend", `<div class="protyle-attr" contenteditable="false">${Constants.ZWSP}</div>`);
} }
let topSourceElement: Element; let topSourceElement: Element;
let ignoreInsert = false;
let oldSourceParentElement = sourceElements[0].parentElement; let oldSourceParentElement = sourceElements[0].parentElement;
if (isBottom) { if (isBottom) {
if (newSourceElement) { if (newSourceElement) {
@ -629,11 +671,13 @@ const dragSame = async (protyle: IProtyle, sourceElements: Element[], targetElem
doOperations.push(...moveToResult.doOperations); doOperations.push(...moveToResult.doOperations);
undoOperations.push(...moveToResult.undoOperations); undoOperations.push(...moveToResult.undoOperations);
topSourceElement = moveToResult.topSourceElement; topSourceElement = moveToResult.topSourceElement;
ignoreInsert = moveToResult.ignoreInsert;
} else { } else {
const moveToResult = await moveTo(protyle, sourceElements, targetElement, isSameDoc, "afterend", isCopy); const moveToResult = await moveTo(protyle, sourceElements, targetElement, isSameDoc, "afterend", isCopy);
doOperations.push(...moveToResult.doOperations); doOperations.push(...moveToResult.doOperations);
undoOperations.push(...moveToResult.undoOperations); undoOperations.push(...moveToResult.undoOperations);
topSourceElement = moveToResult.topSourceElement; topSourceElement = moveToResult.topSourceElement;
ignoreInsert = moveToResult.ignoreInsert;
} }
} else { } else {
if (newSourceElement) { if (newSourceElement) {
@ -641,11 +685,13 @@ const dragSame = async (protyle: IProtyle, sourceElements: Element[], targetElem
doOperations.push(...moveToResult.doOperations); doOperations.push(...moveToResult.doOperations);
undoOperations.push(...moveToResult.undoOperations); undoOperations.push(...moveToResult.undoOperations);
topSourceElement = moveToResult.topSourceElement; topSourceElement = moveToResult.topSourceElement;
ignoreInsert = moveToResult.ignoreInsert;
} else { } else {
const moveToResult = await moveTo(protyle, sourceElements, targetElement, isSameDoc, "beforebegin", isCopy); const moveToResult = await moveTo(protyle, sourceElements, targetElement, isSameDoc, "beforebegin", isCopy);
doOperations.push(...moveToResult.doOperations); doOperations.push(...moveToResult.doOperations);
undoOperations.push(...moveToResult.undoOperations); undoOperations.push(...moveToResult.undoOperations);
topSourceElement = moveToResult.topSourceElement; topSourceElement = moveToResult.topSourceElement;
ignoreInsert = moveToResult.ignoreInsert;
} }
} }
if (targetElement.getAttribute("data-type") === "NodeListItem" && targetElement.getAttribute("data-subtype") === "o") { if (targetElement.getAttribute("data-type") === "NodeListItem" && targetElement.getAttribute("data-subtype") === "o") {
@ -783,9 +829,9 @@ const dragSame = async (protyle: IProtyle, sourceElements: Element[], targetElem
/// #endif /// #endif
} }
if (isSameDoc || isCopy) { if (isSameDoc || isCopy) {
transaction(protyle, doOperations, undoOperations); transaction(protyle, doOperations, ignoreInsert ? undefined : undoOperations);
} else { } else {
// 跨文档不支持撤销 // 跨文档或插入折叠标题下不支持撤销
transaction(protyle, doOperations); transaction(protyle, doOperations);
} }
let hasFoldHeading = false; let hasFoldHeading = false;
@ -805,7 +851,11 @@ const dragSame = async (protyle: IProtyle, sourceElements: Element[], targetElem
level: "row" level: "row"
}); });
} }
focusBlock(sourceElements[0]); if (document.contains(sourceElements[0])) {
focusBlock(sourceElements[0]);
} else {
focusBlock(targetElement);
}
}; };
export const dropEvent = (protyle: IProtyle, editorElement: HTMLElement) => { export const dropEvent = (protyle: IProtyle, editorElement: HTMLElement) => {

View file

@ -368,8 +368,10 @@ export const disabledProtyle = (protyle: IProtyle) => {
item.setAttribute("draggable", "false"); item.setAttribute("draggable", "false");
}); });
if (protyle.breadcrumb) { if (protyle.breadcrumb) {
protyle.breadcrumb.element.parentElement.querySelector('[data-type="readonly"] use').setAttribute("xlink:href", "#iconLock"); const readonlyButton = protyle.breadcrumb.element.parentElement.querySelector('[data-type="readonly"]');
protyle.breadcrumb.element.parentElement.querySelector('[data-type="readonly"]').setAttribute("aria-label", window.siyuan.config.editor.readOnly ? window.siyuan.languages.tempUnlock : window.siyuan.languages.unlockEdit); readonlyButton.querySelector("use").setAttribute("xlink:href", "#iconLock");
readonlyButton.setAttribute("aria-label", window.siyuan.config.editor.readOnly ? window.siyuan.languages.tempUnlock : window.siyuan.languages.unlockEdit);
readonlyButton.setAttribute("data-subtype", "lock");
const undoElement = protyle.breadcrumb.element.parentElement.querySelector('[data-type="undo"]'); const undoElement = protyle.breadcrumb.element.parentElement.querySelector('[data-type="undo"]');
if (undoElement && !undoElement.classList.contains("fn__none")) { if (undoElement && !undoElement.classList.contains("fn__none")) {
undoElement.classList.add("fn__none"); undoElement.classList.add("fn__none");
@ -426,8 +428,10 @@ export const enableProtyle = (protyle: IProtyle) => {
} }
}); });
if (protyle.breadcrumb) { if (protyle.breadcrumb) {
protyle.breadcrumb.element.parentElement.querySelector('[data-type="readonly"] use').setAttribute("xlink:href", "#iconUnlock"); const readonlyButton = protyle.breadcrumb.element.parentElement.querySelector('[data-type="readonly"]');
protyle.breadcrumb.element.parentElement.querySelector('[data-type="readonly"]').setAttribute("aria-label", window.siyuan.config.editor.readOnly ? window.siyuan.languages.cancelTempUnlock : window.siyuan.languages.lockEdit); readonlyButton.querySelector("use").setAttribute("xlink:href", "#iconUnlock");
readonlyButton.setAttribute("aria-label", window.siyuan.config.editor.readOnly ? window.siyuan.languages.cancelTempUnlock : window.siyuan.languages.lockEdit);
readonlyButton.setAttribute("data-subtype", "unlock");
const undoElement = protyle.breadcrumb.element.parentElement.querySelector('[data-type="undo"]'); const undoElement = protyle.breadcrumb.element.parentElement.querySelector('[data-type="undo"]');
if (undoElement && undoElement.classList.contains("fn__none")) { if (undoElement && undoElement.classList.contains("fn__none")) {
undoElement.classList.remove("fn__none"); undoElement.classList.remove("fn__none");

View file

@ -473,7 +473,7 @@ export const paste = async (protyle: IProtyle, event: (ClipboardEvent | DragEven
const textHTMLLowercase = textHTML.toLowerCase(); const textHTMLLowercase = textHTML.toLowerCase();
if (textPlain && "" !== textPlain.trim() && (textHTML.startsWith("<span") || textHTML.startsWith("<br")) && if (textPlain && "" !== textPlain.trim() && (textHTML.startsWith("<span") || textHTML.startsWith("<br")) &&
(0 > textHTMLLowercase.indexOf("class=\"katex") && 0 > textHTMLLowercase.indexOf("class=\"math") && (0 > textHTMLLowercase.indexOf("class=\"katex") && 0 > textHTMLLowercase.indexOf("class=\"math") &&
0 > textHTMLLowercase.indexOf("</a>") && 0 > textHTMLLowercase.indexOf("</img>") && 0 > textHTMLLowercase.indexOf("</a>") && 0 > textHTMLLowercase.indexOf("</img>") && 0 > textHTMLLowercase.indexOf("</code>") &&
0 > textHTMLLowercase.indexOf("</b>") && 0 > textHTMLLowercase.indexOf("</strong>") && 0 > textHTMLLowercase.indexOf("</b>") && 0 > textHTMLLowercase.indexOf("</strong>") &&
0 > textHTMLLowercase.indexOf("</i>") && 0 > textHTMLLowercase.indexOf("</em>") && 0 > textHTMLLowercase.indexOf("</i>") && 0 > textHTMLLowercase.indexOf("</em>") &&
0 > textHTMLLowercase.indexOf("</ol>") && 0 > textHTMLLowercase.indexOf("</ul>") && 0 > textHTMLLowercase.indexOf("</ol>") && 0 > textHTMLLowercase.indexOf("</ul>") &&

View file

@ -610,6 +610,7 @@ export const focusBlock = (element: Element, parentElement?: HTMLElement, toStar
range.setStart(cursorElement.firstChild, 0); range.setStart(cursorElement.firstChild, 0);
setRange = true; setRange = true;
} else { } else {
element.setAttribute("data-need-focus", "true");
return false; return false;
} }
/// #else /// #else

View file

@ -1,4 +1,4 @@
import {genEmptyElement, insertEmptyBlock} from "../../block/util"; import {genEmptyElement, genHeadingElement, insertEmptyBlock} from "../../block/util";
import {focusByRange, focusByWbr, getSelectionOffset, setLastNodeRange} from "../util/selection"; import {focusByRange, focusByWbr, getSelectionOffset, setLastNodeRange} from "../util/selection";
import { import {
getContenteditableElement, getContenteditableElement,
@ -220,7 +220,11 @@ export const enter = (blockElement: HTMLElement, range: Range, protyle: IProtyle
} }
const id = blockElement.getAttribute("data-node-id"); const id = blockElement.getAttribute("data-node-id");
const newElement = document.createElement("div"); const newElement = document.createElement("div");
newElement.appendChild(genEmptyElement(false, false)); if (blockElement.getAttribute("data-type") === "NodeHeading" && blockElement.getAttribute("fold") === "1") {
newElement.innerHTML = genHeadingElement(blockElement, true) as string;
} else {
newElement.appendChild(genEmptyElement(false, false));
}
const newEditableElement = newElement.querySelector('[contenteditable="true"]'); const newEditableElement = newElement.querySelector('[contenteditable="true"]');
newEditableElement.appendChild(range.extractContents()); newEditableElement.appendChild(range.extractContents());
const selectWbrElement = newEditableElement.querySelector("wbr"); const selectWbrElement = newEditableElement.querySelector("wbr");

View file

@ -1741,7 +1741,7 @@ export class WYSIWYG {
} }
const range = getSelection().getRangeAt(0); const range = getSelection().getRangeAt(0);
if (this.element === range.startContainer || this.element.contains(range.startContainer)) { if (this.element === range.startContainer || this.element.contains(range.startContainer)) {
protyle.toolbar.range = range; protyle.toolbar.range = range.cloneRange();
} }
}); });

View file

@ -914,7 +914,8 @@ export const keydown = (protyle: IProtyle, editorElement: HTMLElement) => {
currentNode.textContent === "") // https://ld246.com/article/1649251218696 currentNode.textContent === "") // https://ld246.com/article/1649251218696
)) { )) {
if (!nodeElement.classList.contains("code-block") || if (!nodeElement.classList.contains("code-block") ||
(nodeElement.classList.contains("code-block") && editElement.textContent == "\n") (nodeElement.classList.contains("code-block") &&
(editElement.textContent == "\n" || nodeElement.parentElement.classList.contains("li")))
) { ) {
removeBlock(protyle, nodeElement, range, "Backspace"); removeBlock(protyle, nodeElement, range, "Backspace");
} }

View file

@ -300,6 +300,14 @@ export const removeBlock = async (protyle: IProtyle, blockElement: Element, rang
return; return;
} }
if (blockType === "NodeHeading") { if (blockType === "NodeHeading") {
if ((blockElement.previousElementSibling &&
blockElement.previousElementSibling.getAttribute("data-type") === "NodeHeading" &&
blockElement.previousElementSibling.getAttribute("fold") === "1") ||
(blockElement.getAttribute("data-type") === "NodeHeading" &&
blockElement.getAttribute("fold") === "1")) {
focusBlock(blockElement.previousElementSibling, undefined, false);
return;
}
turnsIntoTransaction({ turnsIntoTransaction({
protyle: protyle, protyle: protyle,
selectsElement: [blockElement], selectsElement: [blockElement],

View file

@ -643,6 +643,9 @@ export const onTransaction = (protyle: IProtyle, operation: IOperation, isUndo:
return; return;
} }
if (operation.action === "move") { if (operation.action === "move") {
if (operation.context?.ignoreProcess === "true") {
return;
}
/// #if !MOBILE /// #if !MOBILE
if (updateElements.length === 0) { if (updateElements.length === 0) {
// 打开两个相同的文档 A、A1从 A 拖拽块 B 到 A1在后续 ws 处理中,无法获取到拖拽出去的 B // 打开两个相同的文档 A、A1从 A 拖拽块 B 到 A1在后续 ws 处理中,无法获取到拖拽出去的 B

View file

@ -106,7 +106,7 @@ export const genSearch = (app: App, config: Config.IUILayoutTabSearchConfig, ele
</span> </span>
<span class="fn__space"></span> <span class="fn__space"></span>
<span data-position="9south" id="searchInclude" ${enableIncludeChild ? "" : "disabled"} aria-label="${window.siyuan.languages.includeChildDoc}" class="block__icon block__icon--show ariaLabel"> <span data-position="9south" id="searchInclude" ${enableIncludeChild ? "" : "disabled"} aria-label="${window.siyuan.languages.includeChildDoc}" class="block__icon block__icon--show ariaLabel">
<svg${includeChild ? ' class="ft__primary"' : ""}><use xlink:href="#iconCopy"></use></svg> <svg${includeChild ? ' class="ft__primary"' : ""}><use xlink:href="#iconInclude"></use></svg>
</span> </span>
<span class="fn__space"></span> <span class="fn__space"></span>
<span id="searchPath" aria-label="${window.siyuan.languages.specifyPath}" class="block__icon block__icon--show ariaLabel" data-position="9south"> <span id="searchPath" aria-label="${window.siyuan.languages.specifyPath}" class="block__icon block__icon--show ariaLabel" data-position="9south">
@ -494,6 +494,11 @@ export const genSearch = (app: App, config: Config.IUILayoutTabSearchConfig, ele
event.preventDefault(); event.preventDefault();
break; break;
} else if (target.id === "searchInclude") { } else if (target.id === "searchInclude") {
event.stopPropagation();
event.preventDefault();
if (target.hasAttribute("disabled")) {
return;
}
const svgElement = target.firstElementChild; const svgElement = target.firstElementChild;
svgElement.classList.toggle("ft__primary"); svgElement.classList.toggle("ft__primary");
if (!svgElement.classList.contains("ft__primary")) { if (!svgElement.classList.contains("ft__primary")) {
@ -511,8 +516,6 @@ export const genSearch = (app: App, config: Config.IUILayoutTabSearchConfig, ele
} }
config.page = 1; config.page = 1;
inputEvent(element, config, edit, true); inputEvent(element, config, edit, true);
event.stopPropagation();
event.preventDefault();
break; break;
} else if (target.id === "searchReplace") { } else if (target.id === "searchReplace") {
// ctrl+P 不需要保存 // ctrl+P 不需要保存

View file

@ -51,11 +51,19 @@ export const scrollCenter = (protyle: IProtyle, nodeElement?: Element, top = fal
brElement.remove(); brElement.remove();
return; return;
} }
// undo 时禁止数据库滚动
if (blockElement.classList.contains("av") && blockElement.dataset.render === "true" && ( if (blockElement.classList.contains("av") && blockElement.dataset.render === "true") {
blockElement.querySelector(".av__row--header")?.getAttribute("style")?.indexOf("transform") > -1 || // undo 时禁止数据库滚动
blockElement.querySelector(".av__row--footer")?.getAttribute("style")?.indexOf("transform") > -1 if (blockElement.querySelector(".av__row--header")?.getAttribute("style")?.indexOf("transform") > -1 ||
)) { blockElement.querySelector(".av__row--footer")?.getAttribute("style")?.indexOf("transform") > -1) {
return;
}
const activeElement = blockElement.querySelector(".av__cell--select, .av__row--select, .av__gallery-item--select");
if (activeElement) {
activeElement.scrollIntoView({block: "nearest", behavior});
} else {
blockElement.scrollIntoView({block: "nearest", behavior});
}
return; return;
} }
// 撤销时 br 插入删除会导致 rang 被修改 https://github.com/siyuan-note/siyuan/issues/12679 // 撤销时 br 插入删除会导致 rang 被修改 https://github.com/siyuan-note/siyuan/issues/12679

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -567,6 +567,24 @@ func searchAttributeViewNonRelationKey(c *gin.Context) {
} }
} }
func searchAttributeViewRollupDestKeys(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, _ := util.JsonArg(c, ret)
if nil == arg {
return
}
avID := arg["avID"].(string)
keyword := arg["keyword"].(string)
rollupDestKeys := model.SearchAttributeViewRollupDestKeys(avID, keyword)
ret.Data = map[string]interface{}{
"keys": rollupDestKeys,
}
}
func searchAttributeViewRelationKey(c *gin.Context) { func searchAttributeViewRelationKey(c *gin.Context) {
ret := gulu.Ret.NewResult() ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret) defer c.JSON(http.StatusOK, ret)
@ -675,7 +693,41 @@ func renderHistoryAttributeView(c *gin.Context) {
id := arg["id"].(string) id := arg["id"].(string)
created := arg["created"].(string) created := arg["created"].(string)
view, attrView, err := model.RenderHistoryAttributeView(id, created) blockIDArg := arg["blockID"]
var blockID string
if nil != blockIDArg {
blockID = blockIDArg.(string)
}
viewIDArg := arg["viewID"]
var viewID string
if nil != viewIDArg {
viewID = viewIDArg.(string)
}
page := 1
pageArg := arg["page"]
if nil != pageArg {
page = int(pageArg.(float64))
}
pageSize := -1
pageSizeArg := arg["pageSize"]
if nil != pageSizeArg {
pageSize = int(pageSizeArg.(float64))
}
query := ""
queryArg := arg["query"]
if nil != queryArg {
query = queryArg.(string)
}
groupPaging := map[string]interface{}{}
groupPagingArg := arg["groupPaging"]
if nil != groupPagingArg {
groupPaging = groupPagingArg.(map[string]interface{})
}
view, attrView, err := model.RenderHistoryAttributeView(blockID, id, viewID, query, page, pageSize, groupPaging, created)
if err != nil { if err != nil {
ret.Code = -1 ret.Code = -1
ret.Msg = err.Error() ret.Msg = err.Error()

View file

@ -243,6 +243,28 @@ func getHeadingDeleteTransaction(c *gin.Context) {
ret.Data = transaction ret.Data = transaction
} }
func getHeadingInsertTransaction(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
transaction, err := model.GetHeadingInsertTransaction(id)
if err != nil {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 7000}
return
}
ret.Data = transaction
}
func getHeadingLevelTransaction(c *gin.Context) { func getHeadingLevelTransaction(c *gin.Context) {
ret := gulu.Ret.NewResult() ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret) defer c.JSON(http.StatusOK, ret)

View file

@ -79,7 +79,7 @@ func html2BlockDOM(c *gin.Context) {
dom := arg["dom"].(string) dom := arg["dom"].(string)
luteEngine := util.NewLute() luteEngine := util.NewLute()
luteEngine.SetHTMLTag2TextMark(true) luteEngine.SetHTMLTag2TextMark(true)
luteEngine.SetHTML2MarkdownAttrs([]string{"name", "alias", "memo", "bookmark", "custom-*"}) luteEngine.SetHTML2MarkdownAttrs([]string{"alias", "memo", "bookmark", "custom-*"})
markdown, withMath, err := model.HTML2Markdown(dom, luteEngine) markdown, withMath, err := model.HTML2Markdown(dom, luteEngine)
if err != nil { if err != nil {
ret.Data = "Failed to convert" ret.Data = "Failed to convert"

View file

@ -214,6 +214,7 @@ func ServeAPI(ginServer *gin.Engine) {
ginServer.Handle("POST", "/api/block/setBlockReminder", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setBlockReminder) ginServer.Handle("POST", "/api/block/setBlockReminder", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setBlockReminder)
ginServer.Handle("POST", "/api/block/getHeadingLevelTransaction", model.CheckAuth, getHeadingLevelTransaction) ginServer.Handle("POST", "/api/block/getHeadingLevelTransaction", model.CheckAuth, getHeadingLevelTransaction)
ginServer.Handle("POST", "/api/block/getHeadingDeleteTransaction", model.CheckAuth, getHeadingDeleteTransaction) ginServer.Handle("POST", "/api/block/getHeadingDeleteTransaction", model.CheckAuth, getHeadingDeleteTransaction)
ginServer.Handle("POST", "/api/block/getHeadingInsertTransaction", model.CheckAuth, getHeadingInsertTransaction)
ginServer.Handle("POST", "/api/block/getHeadingChildrenIDs", model.CheckAuth, getHeadingChildrenIDs) ginServer.Handle("POST", "/api/block/getHeadingChildrenIDs", model.CheckAuth, getHeadingChildrenIDs)
ginServer.Handle("POST", "/api/block/getHeadingChildrenDOM", model.CheckAuth, getHeadingChildrenDOM) ginServer.Handle("POST", "/api/block/getHeadingChildrenDOM", model.CheckAuth, getHeadingChildrenDOM)
ginServer.Handle("POST", "/api/block/swapBlockRef", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, swapBlockRef) ginServer.Handle("POST", "/api/block/swapBlockRef", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, swapBlockRef)
@ -447,7 +448,8 @@ func ServeAPI(ginServer *gin.Engine) {
ginServer.Handle("POST", "/api/av/searchAttributeView", model.CheckAuth, model.CheckReadonly, searchAttributeView) ginServer.Handle("POST", "/api/av/searchAttributeView", model.CheckAuth, model.CheckReadonly, searchAttributeView)
ginServer.Handle("POST", "/api/av/getAttributeView", model.CheckAuth, model.CheckReadonly, getAttributeView) ginServer.Handle("POST", "/api/av/getAttributeView", model.CheckAuth, model.CheckReadonly, getAttributeView)
ginServer.Handle("POST", "/api/av/searchAttributeViewRelationKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, searchAttributeViewRelationKey) ginServer.Handle("POST", "/api/av/searchAttributeViewRelationKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, searchAttributeViewRelationKey)
ginServer.Handle("POST", "/api/av/searchAttributeViewNonRelationKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, searchAttributeViewNonRelationKey) ginServer.Handle("POST", "/api/av/searchAttributeViewNonRelationKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, searchAttributeViewNonRelationKey) // 请勿使用,该端点计划于 2026 年 6 月 30 日后删除 https://github.com/siyuan-note/siyuan/issues/15727
ginServer.Handle("POST", "/api/av/searchAttributeViewRollupDestKeys", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, searchAttributeViewRollupDestKeys)
ginServer.Handle("POST", "/api/av/getAttributeViewFilterSort", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, getAttributeViewFilterSort) ginServer.Handle("POST", "/api/av/getAttributeViewFilterSort", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, getAttributeViewFilterSort)
ginServer.Handle("POST", "/api/av/addAttributeViewKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, addAttributeViewKey) ginServer.Handle("POST", "/api/av/addAttributeViewKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, addAttributeViewKey)
ginServer.Handle("POST", "/api/av/removeAttributeViewKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeAttributeViewKey) ginServer.Handle("POST", "/api/av/removeAttributeViewKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeAttributeViewKey)

View file

@ -207,6 +207,7 @@ type View struct {
LayoutType LayoutType `json:"type"` // 当前布局类型 LayoutType LayoutType `json:"type"` // 当前布局类型
Table *LayoutTable `json:"table,omitempty"` // 表格布局 Table *LayoutTable `json:"table,omitempty"` // 表格布局
Gallery *LayoutGallery `json:"gallery,omitempty"` // 卡片布局 Gallery *LayoutGallery `json:"gallery,omitempty"` // 卡片布局
Kanban *LayoutKanban `json:"kanban,omitempty"` // 看板布局
ItemIDs []string `json:"itemIds,omitempty"` // 项目 ID 列表,用于维护所有项目 ItemIDs []string `json:"itemIds,omitempty"` // 项目 ID 列表,用于维护所有项目
Group *ViewGroup `json:"group,omitempty"` // 分组规则 Group *ViewGroup `json:"group,omitempty"` // 分组规则
@ -221,6 +222,10 @@ type View struct {
GroupSort int `json:"groupSort"` // 分组排序值,用于手动排序 GroupSort int `json:"groupSort"` // 分组排序值,用于手动排序
} }
func (view *View) IsGroupView() bool {
return nil != view.Group && "" != view.Group.Field
}
// GetGroupValue 获取分组视图的分组值。 // GetGroupValue 获取分组视图的分组值。
func (view *View) GetGroupValue() string { func (view *View) GetGroupValue() string {
if nil == view.GroupVal { if nil == view.GroupVal {
@ -270,7 +275,7 @@ func (view *View) RemoveGroupByID(groupID string) {
// GetGroupKey 获取分组视图的分组字段。 // GetGroupKey 获取分组视图的分组字段。
func (view *View) GetGroupKey(attrView *AttributeView) (ret *Key) { func (view *View) GetGroupKey(attrView *AttributeView) (ret *Key) {
if nil == view.Group || "" == view.Group.Field { if !view.IsGroupView() {
return return
} }
@ -295,14 +300,15 @@ type LayoutType string
const ( const (
LayoutTypeTable LayoutType = "table" // 属性视图类型 - 表格 LayoutTypeTable LayoutType = "table" // 属性视图类型 - 表格
LayoutTypeGallery LayoutType = "gallery" // 属性视图类型 - 卡片 LayoutTypeGallery LayoutType = "gallery" // 属性视图类型 - 卡片
LayoutTypeKanban LayoutType = "kanban" // 属性视图类型 - 看板
) )
const ( const (
ViewDefaultPageSize = 50 // 视图默认分页大小 ViewDefaultPageSize = 50 // 视图默认分页大小
) )
func NewTableView() (ret *View) { func NewTableView() *View {
ret = &View{ return &View{
ID: ast.NewNodeID(), ID: ast.NewNodeID(),
Name: GetAttributeViewI18n("table"), Name: GetAttributeViewI18n("table"),
Filters: []*ViewFilter{}, Filters: []*ViewFilter{},
@ -311,7 +317,6 @@ func NewTableView() (ret *View) {
LayoutType: LayoutTypeTable, LayoutType: LayoutTypeTable,
Table: NewLayoutTable(), Table: NewLayoutTable(),
} }
return
} }
func NewTableViewWithBlockKey(blockKeyID string) (view *View, blockKey, selectKey *Key) { func NewTableViewWithBlockKey(blockKeyID string) (view *View, blockKey, selectKey *Key) {
@ -334,7 +339,7 @@ func NewTableViewWithBlockKey(blockKeyID string) (view *View, blockKey, selectKe
} }
func NewGalleryView() (ret *View) { func NewGalleryView() (ret *View) {
ret = &View{ return &View{
ID: ast.NewNodeID(), ID: ast.NewNodeID(),
Name: GetAttributeViewI18n("gallery"), Name: GetAttributeViewI18n("gallery"),
Filters: []*ViewFilter{}, Filters: []*ViewFilter{},
@ -343,7 +348,18 @@ func NewGalleryView() (ret *View) {
LayoutType: LayoutTypeGallery, LayoutType: LayoutTypeGallery,
Gallery: NewLayoutGallery(), Gallery: NewLayoutGallery(),
} }
return }
func NewKanbanView() (ret *View) {
return &View{
ID: ast.NewNodeID(),
Name: GetAttributeViewI18n("kanban"),
Filters: []*ViewFilter{},
Sorts: []*ViewSort{},
PageSize: ViewDefaultPageSize,
LayoutType: LayoutTypeKanban,
Kanban: NewLayoutKanban(),
}
} }
// Viewable 描述了视图的接口。 // Viewable 描述了视图的接口。
@ -530,6 +546,15 @@ func SaveAttributeView(av *AttributeView) (err error) {
} }
} }
// 清理渲染回填值
for _, kv := range av.KeyValues {
for i := len(kv.Values) - 1; i >= 0; i-- {
if kv.Values[i].IsRenderAutoFill {
kv.Values = append(kv.Values[:i], kv.Values[i+1:]...)
}
}
}
var data []byte var data []byte
if util.UseSingleLineSave { if util.UseSingleLineSave {
data, err = gulu.JSON.MarshalJSON(av) data, err = gulu.JSON.MarshalJSON(av)
@ -741,6 +766,11 @@ func (av *AttributeView) Clone() (ret *AttributeView) {
for _, cardField := range view.Gallery.CardFields { for _, cardField := range view.Gallery.CardFields {
cardField.ID = keyIDMap[cardField.ID] cardField.ID = keyIDMap[cardField.ID]
} }
case LayoutTypeKanban:
view.Kanban.ID = ast.NewNodeID()
for _, field := range view.Kanban.Fields {
field.ID = keyIDMap[field.ID]
}
} }
view.ItemIDs = []string{} view.ItemIDs = []string{}
} }

View file

@ -33,6 +33,7 @@ type CalcOperator string
const ( const (
CalcOperatorNone CalcOperator = "" CalcOperatorNone CalcOperator = ""
CalcOperatorUniqueValues CalcOperator = "Unique values"
CalcOperatorCountAll CalcOperator = "Count all" CalcOperatorCountAll CalcOperator = "Count all"
CalcOperatorCountValues CalcOperator = "Count values" CalcOperatorCountValues CalcOperator = "Count values"
CalcOperatorCountUniqueValues CalcOperator = "Count unique values" CalcOperatorCountUniqueValues CalcOperator = "Count unique values"
@ -1709,9 +1710,35 @@ func calcFieldRollup(collection Collection, field Field, fieldIndex int) {
values := item.GetValues() values := item.GetValues()
if nil != values[fieldIndex] && nil != values[fieldIndex].Rollup { if nil != values[fieldIndex] && nil != values[fieldIndex].Rollup {
for _, content := range values[fieldIndex].Rollup.Contents { for _, content := range values[fieldIndex].Rollup.Contents {
if !uniqueValues[content.String(true)] { switch content.Type {
uniqueValues[content.String(true)] = true case KeyTypeRelation:
countUniqueValues++ for _, relationVal := range content.Relation.Contents {
key := relationVal.String(true)
if !uniqueValues[key] {
uniqueValues[key] = true
countUniqueValues++
}
}
case KeyTypeMSelect:
for _, mSelectVal := range content.MSelect {
if !uniqueValues[mSelectVal.Content] {
uniqueValues[mSelectVal.Content] = true
countUniqueValues++
}
}
case KeyTypeMAsset:
for _, mAssetVal := range content.MAsset {
if !uniqueValues[mAssetVal.Content] {
uniqueValues[mAssetVal.Content] = true
countUniqueValues++
}
}
default:
key := content.String(true)
if !uniqueValues[key] {
uniqueValues[key] = true
countUniqueValues++
}
} }
} }
} }

View file

@ -81,6 +81,9 @@ func NewViewBaseInstance(view *View) *BaseInstance {
case LayoutTypeGallery: case LayoutTypeGallery:
showIcon = view.Gallery.ShowIcon showIcon = view.Gallery.ShowIcon
wrapField = view.Gallery.WrapField wrapField = view.Gallery.WrapField
case LayoutTypeKanban:
showIcon = view.Kanban.ShowIcon
wrapField = view.Kanban.WrapField
} }
return &BaseInstance{ return &BaseInstance{
ID: view.ID, ID: view.ID,

168
kernel/av/layout_kanban.go Normal file
View file

@ -0,0 +1,168 @@
// SiYuan - Refactor your thinking
// Copyright (c) 2020-present, b3log.org
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package av
import (
"github.com/88250/lute/ast"
)
// LayoutKanban 描述了看板视图的结构。
type LayoutKanban struct {
*BaseLayout
CoverFrom CoverFrom `json:"coverFrom"` // 封面来源01内容图2资源字段
CoverFromAssetKeyID string `json:"coverFromAssetKeyID,omitempty"` // 资源字段 IDCoverFrom 为 2 时有效
CardAspectRatio CardAspectRatio `json:"cardAspectRatio"` // 卡片宽高比
CardSize CardSize `json:"cardSize"` // 卡片大小0小卡片1中卡片2大卡片
FitImage bool `json:"fitImage"` // 是否适应封面图片大小
DisplayFieldName bool `json:"displayFieldName"` // 是否显示字段名称
Fields []*ViewKanbanField `json:"fields"` // 字段
}
func NewLayoutKanban() *LayoutKanban {
return &LayoutKanban{
BaseLayout: &BaseLayout{
Spec: 0,
ID: ast.NewNodeID(),
ShowIcon: true,
},
CoverFrom: CoverFromContentImage,
CardAspectRatio: CardAspectRatio16_9,
CardSize: CardSizeMedium,
}
}
// ViewKanbanField 描述了看板字段的结构。
type ViewKanbanField struct {
*BaseField
}
// Kanban 描述了看板视图实例的结构。
type Kanban struct {
*BaseInstance
CoverFrom CoverFrom `json:"coverFrom"` // 封面来源
CoverFromAssetKeyID string `json:"coverFromAssetKeyID,omitempty"` // 资源字段 IDCoverFrom 为 CoverFromAssetField 时有效
CardAspectRatio CardAspectRatio `json:"cardAspectRatio"` // 卡片宽高比
CardSize CardSize `json:"cardSize"` // 卡片大小
FitImage bool `json:"fitImage"` // 是否适应封面图片大小
DisplayFieldName bool `json:"displayFieldName"` // 是否显示字段名称
Fields []*KanbanField `json:"fields"` // 卡片字段
Cards []*KanbanCard `json:"cards"` // 卡片
CardCount int `json:"rowCount"` // 总卡片数
}
// KanbanCard 描述了看板实例卡片的结构。
type KanbanCard struct {
ID string `json:"id"` // 卡片 ID
Values []*KanbanFieldValue `json:"values"` // 卡片字段值
CoverURL string `json:"coverURL"` // 卡片封面超链接
CoverContent string `json:"coverContent"` // 卡片封面文本内容
}
// KanbanField 描述了看板实例字段的结构。
type KanbanField struct {
*BaseInstanceField
}
// KanbanFieldValue 描述了卡片字段实例值的结构。
type KanbanFieldValue struct {
*BaseValue
}
func (card *KanbanCard) GetID() string {
return card.ID
}
func (card *KanbanCard) GetBlockValue() (ret *Value) {
for _, v := range card.Values {
if KeyTypeBlock == v.ValueType {
ret = v.Value
break
}
}
return
}
func (card *KanbanCard) GetValues() (ret []*Value) {
ret = []*Value{}
for _, v := range card.Values {
ret = append(ret, v.Value)
}
return
}
func (card *KanbanCard) GetValue(keyID string) (ret *Value) {
for _, value := range card.Values {
if nil != value.Value && keyID == value.Value.KeyID {
ret = value.Value
break
}
}
return
}
func (kanban *Kanban) GetItems() (ret []Item) {
ret = []Item{}
for _, card := range kanban.Cards {
ret = append(ret, card)
}
return
}
func (kanban *Kanban) SetItems(items []Item) {
kanban.Cards = []*KanbanCard{}
for _, item := range items {
kanban.Cards = append(kanban.Cards, item.(*KanbanCard))
}
}
func (kanban *Kanban) CountItems() int {
return len(kanban.Cards)
}
func (kanban *Kanban) GetFields() (ret []Field) {
ret = []Field{}
for _, field := range kanban.Fields {
ret = append(ret, field)
}
return ret
}
func (kanban *Kanban) GetField(id string) (ret Field, fieldIndex int) {
for i, field := range kanban.Fields {
if field.ID == id {
return field, i
}
}
return nil, -1
}
func (kanban *Kanban) GetValue(itemID, keyID string) (ret *Value) {
for _, card := range kanban.Cards {
if card.ID == itemID {
return card.GetValue(keyID)
}
}
return nil
}
func (kanban *Kanban) GetType() LayoutType {
return LayoutTypeKanban
}

View file

@ -57,6 +57,8 @@ type Value struct {
Checkbox *ValueCheckbox `json:"checkbox,omitempty"` Checkbox *ValueCheckbox `json:"checkbox,omitempty"`
Relation *ValueRelation `json:"relation,omitempty"` Relation *ValueRelation `json:"relation,omitempty"`
Rollup *ValueRollup `json:"rollup,omitempty"` Rollup *ValueRollup `json:"rollup,omitempty"`
IsRenderAutoFill bool `json:"-"` // 标识是否是渲染阶段自动填充的值,保存数据的时候要删掉
} }
func (value *Value) SetUpdatedAt(mills int64) { func (value *Value) SetUpdatedAt(mills int64) {
@ -806,6 +808,11 @@ func (r *ValueRollup) BuildContents(keyValues []*KeyValues, destKey *Key, relati
} }
if nil == destVal { if nil == destVal {
if KeyTypeCheckbox == destKey.Type {
// 没有编辑过复选框的时候没有值,没有值等同于未选中,所以这里补一个未选中的值 https://github.com/siyuan-note/siyuan/issues/15858
defaultVal := GetAttributeViewDefaultValue(ast.NewNodeID(), destKey.ID, blockID, destKey.Type, false)
r.Contents = append(r.Contents, defaultVal)
}
continue continue
} }
@ -832,6 +839,40 @@ func (r *ValueRollup) calcContents(calc *RollupCalc, destKey *Key) {
switch calc.Operator { switch calc.Operator {
case CalcOperatorNone: case CalcOperatorNone:
case CalcOperatorUniqueValues:
uniqueValues := map[string]bool{}
for _, content := range r.Contents {
switch content.Type {
case KeyTypeRelation:
var newRelationContents []*Value
for _, relationVal := range content.Relation.Contents {
key := relationVal.String(true)
if !uniqueValues[key] {
uniqueValues[key] = true
newRelationContents = append(newRelationContents, relationVal)
}
}
content.Relation.Contents = newRelationContents
case KeyTypeMSelect:
var newMSelect []*ValueSelect
for _, mSelect := range content.MSelect {
if !uniqueValues[mSelect.Content] {
uniqueValues[mSelect.Content] = true
newMSelect = append(newMSelect, mSelect)
}
}
content.MSelect = newMSelect
case KeyTypeMAsset:
var newMAsset []*ValueAsset
for _, mAsset := range content.MAsset {
if !uniqueValues[mAsset.Content] {
uniqueValues[mAsset.Content] = true
newMAsset = append(newMAsset, mAsset)
}
}
content.MAsset = newMAsset
}
}
case CalcOperatorCountAll: case CalcOperatorCountAll:
r.Contents = []*Value{{Type: KeyTypeNumber, Number: NewFormattedValueNumber(float64(len(r.Contents)), NumberFormatNone)}} r.Contents = []*Value{{Type: KeyTypeNumber, Number: NewFormattedValueNumber(float64(len(r.Contents)), NumberFormatNone)}}
case CalcOperatorCountValues: case CalcOperatorCountValues:
@ -1199,7 +1240,7 @@ func (r *ValueRollup) calcContents(calc *RollupCalc, destKey *Key) {
} }
} }
func GetAttributeViewDefaultValue(valueID, keyID, blockID string, typ KeyType) (ret *Value) { func GetAttributeViewDefaultValue(valueID, keyID, blockID string, typ KeyType, keyDateAutoFill bool) (ret *Value) {
if "" == valueID { if "" == valueID {
valueID = ast.NewNodeID() valueID = ast.NewNodeID()
} }
@ -1225,7 +1266,7 @@ func GetAttributeViewDefaultValue(valueID, keyID, blockID string, typ KeyType) (
case KeyTypeNumber: case KeyTypeNumber:
ret.Number = &ValueNumber{} ret.Number = &ValueNumber{}
case KeyTypeDate: case KeyTypeDate:
ret.Date = &ValueDate{IsNotTime: true} ret.Date = &ValueDate{IsNotTime: !keyDateAutoFill}
case KeyTypeSelect: case KeyTypeSelect:
ret.MSelect = []*ValueSelect{} ret.MSelect = []*ValueSelect{}
case KeyTypeMSelect: case KeyTypeMSelect:

View file

@ -34,7 +34,7 @@ type Export struct {
FileAnnotationRefMode int `json:"fileAnnotationRefMode"` // 文件标注引用导出模式0文件名 - 页码 - 锚文本1仅锚文本 FileAnnotationRefMode int `json:"fileAnnotationRefMode"` // 文件标注引用导出模式0文件名 - 页码 - 锚文本1仅锚文本
PandocBin string `json:"pandocBin"` // Pandoc 可执行文件路径 PandocBin string `json:"pandocBin"` // Pandoc 可执行文件路径
MarkdownYFM bool `json:"markdownYFM"` // Markdown 导出时是否添加 YAML Front Matter https://github.com/siyuan-note/siyuan/issues/7727 MarkdownYFM bool `json:"markdownYFM"` // Markdown 导出时是否添加 YAML Front Matter https://github.com/siyuan-note/siyuan/issues/7727
InlineMemo bool `json:"inlineMemo"` // 是否导出行级备忘录 https://github.com/siyuan-note/siyuan/issues/14605 InlineMemo bool `json:"inlineMemo"` // 是否导出行级备 https://github.com/siyuan-note/siyuan/issues/14605
PDFFooter string `json:"pdfFooter"` // PDF 导出时页脚内容 PDFFooter string `json:"pdfFooter"` // PDF 导出时页脚内容
DocxTemplate string `json:"docxTemplate"` // Docx 导出时模板文件路径 DocxTemplate string `json:"docxTemplate"` // Docx 导出时模板文件路径
PDFWatermarkStr string `json:"pdfWatermarkStr"` // PDF 导出时水印文本或水印文件路径 PDFWatermarkStr string `json:"pdfWatermarkStr"` // PDF 导出时水印文本或水印文件路径

View file

@ -206,6 +206,12 @@ func DocIAL(absPath string) (ret map[string]string) {
return return
} }
func TreeSize(tree *parse.Tree) (size uint64) {
luteEngine := util.NewLute() // 不关注用户的自定义解析渲染选项
renderer := render.NewJSONRenderer(tree, luteEngine.RenderOptions)
return uint64(len(renderer.Render()))
}
func WriteTree(tree *parse.Tree) (size uint64, err error) { func WriteTree(tree *parse.Tree) (size uint64, err error) {
data, filePath, err := prepareWriteTree(tree) data, filePath, err := prepareWriteTree(tree)
if err != nil { if err != nil {

View file

@ -8,7 +8,7 @@ require (
github.com/88250/epub v0.0.0-20230830085737-c19055cd1f48 github.com/88250/epub v0.0.0-20230830085737-c19055cd1f48
github.com/88250/go-humanize v0.0.0-20240424102817-4f78fac47ea7 github.com/88250/go-humanize v0.0.0-20240424102817-4f78fac47ea7
github.com/88250/gulu v1.2.3-0.20250227144607-7f4570b0d689 github.com/88250/gulu v1.2.3-0.20250227144607-7f4570b0d689
github.com/88250/lute v1.7.7-0.20250907110109-efc34e9d52fa github.com/88250/lute v1.7.7-0.20250917025242-1a7d55422e12
github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1 github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1
github.com/ClarkThan/ahocorasick v0.0.0-20231011042242-30d1ef1347f4 github.com/ClarkThan/ahocorasick v0.0.0-20231011042242-30d1ef1347f4
github.com/ConradIrwin/font v0.2.1 github.com/ConradIrwin/font v0.2.1

View file

@ -14,8 +14,8 @@ github.com/88250/go-sqlite3 v1.14.13-0.20231214121541-e7f54c482950 h1:Pa5hMiBceT
github.com/88250/go-sqlite3 v1.14.13-0.20231214121541-e7f54c482950/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/88250/go-sqlite3 v1.14.13-0.20231214121541-e7f54c482950/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/88250/gulu v1.2.3-0.20250227144607-7f4570b0d689 h1:39y5g7vnFAIcXhTN3IXPk7h2xBhC4a9hBTykDhHJqRY= github.com/88250/gulu v1.2.3-0.20250227144607-7f4570b0d689 h1:39y5g7vnFAIcXhTN3IXPk7h2xBhC4a9hBTykDhHJqRY=
github.com/88250/gulu v1.2.3-0.20250227144607-7f4570b0d689/go.mod h1:c8uVw25vW2W4dhJ/j4iYsX5H1hc19spim266jO5x2hU= github.com/88250/gulu v1.2.3-0.20250227144607-7f4570b0d689/go.mod h1:c8uVw25vW2W4dhJ/j4iYsX5H1hc19spim266jO5x2hU=
github.com/88250/lute v1.7.7-0.20250907110109-efc34e9d52fa h1:kbvW8LD3yJK5hwbLdJDb+7xqj7i68Rbz47xEqnNqf4I= github.com/88250/lute v1.7.7-0.20250917025242-1a7d55422e12 h1:1CzOIJ+NJsEt2xvHlc8RK3xx8XBx+c7Lksqf8cb7ygU=
github.com/88250/lute v1.7.7-0.20250907110109-efc34e9d52fa/go.mod h1:WYyUw//5yVw9BJnoVjx7rI/3szsISxNZCYGOqTIrV0o= github.com/88250/lute v1.7.7-0.20250917025242-1a7d55422e12/go.mod h1:WYyUw//5yVw9BJnoVjx7rI/3szsISxNZCYGOqTIrV0o=
github.com/88250/pdfcpu v0.3.14-0.20250424122812-f10e8d9d8d46 h1:Bq1JsDfVbHKUxNL/B2JXd8cC/1h6aFjrlXpGycnh0Hk= github.com/88250/pdfcpu v0.3.14-0.20250424122812-f10e8d9d8d46 h1:Bq1JsDfVbHKUxNL/B2JXd8cC/1h6aFjrlXpGycnh0Hk=
github.com/88250/pdfcpu v0.3.14-0.20250424122812-f10e8d9d8d46/go.mod h1:fVfOloBzs2+W2VJCCbq60XIxc3yJHAZ0Gahv1oO0gyI= github.com/88250/pdfcpu v0.3.14-0.20250424122812-f10e8d9d8d46/go.mod h1:fVfOloBzs2+W2VJCCbq60XIxc3yJHAZ0Gahv1oO0gyI=
github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1 h1:48T899JQDwyyRu9yXHePYlPdHtpJfrJEUGBMH3SMBWY= github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1 h1:48T899JQDwyyRu9yXHePYlPdHtpJfrJEUGBMH3SMBWY=

View file

@ -101,7 +101,7 @@ func GetAttrViewAddingBlockDefaultValues(avID, viewID, groupID, previousBlockID,
return return
} }
if 1 > len(view.Filters) && nil == view.Group { if 1 > len(view.Filters) && !view.IsGroupView() {
// 没有过滤条件也没有分组条件时忽略 // 没有过滤条件也没有分组条件时忽略
return return
} }
@ -128,7 +128,7 @@ func GetAttrViewAddingBlockDefaultValues(avID, viewID, groupID, previousBlockID,
func getAttrViewAddingBlockDefaultValues(attrView *av.AttributeView, view, groupView *av.View, previousItemID, addingItemID string) (ret map[string]*av.Value) { func getAttrViewAddingBlockDefaultValues(attrView *av.AttributeView, view, groupView *av.View, previousItemID, addingItemID string) (ret map[string]*av.Value) {
ret = map[string]*av.Value{} ret = map[string]*av.Value{}
if 1 > len(view.Filters) && nil == view.Group { if 1 > len(view.Filters) && !view.IsGroupView() {
// 没有过滤条件也没有分组条件时忽略 // 没有过滤条件也没有分组条件时忽略
return return
} }
@ -233,7 +233,7 @@ func getAttrViewAddingBlockDefaultValues(attrView *av.AttributeView, view, group
} }
} }
} else { } else {
newValue = av.GetAttributeViewDefaultValue(ast.NewNodeID(), groupKey.ID, addingItemID, groupKey.Type) newValue = av.GetAttributeViewDefaultValue(ast.NewNodeID(), groupKey.ID, addingItemID, groupKey.Type, false)
newValue.MSelect = append(newValue.MSelect, &av.ValueSelect{Content: opt.Name, Color: opt.Color}) newValue.MSelect = append(newValue.MSelect, &av.ValueSelect{Content: opt.Name, Color: opt.Color})
} }
} }
@ -278,7 +278,7 @@ func getAttrViewAddingBlockDefaultValues(attrView *av.AttributeView, view, group
if nil == nearItem && !filterKeyIDs[groupKey.ID] { if nil == nearItem && !filterKeyIDs[groupKey.ID] {
// 没有临近项并且分组字段和过滤字段不同时,使用分组值 // 没有临近项并且分组字段和过滤字段不同时,使用分组值
newValue = av.GetAttributeViewDefaultValue(ast.NewNodeID(), groupKey.ID, addingItemID, groupKey.Type) newValue = av.GetAttributeViewDefaultValue(ast.NewNodeID(), groupKey.ID, addingItemID, groupKey.Type, false)
if av.KeyTypeText == groupView.GroupVal.Type { if av.KeyTypeText == groupView.GroupVal.Type {
content := groupView.GroupVal.Text.Content content := groupView.GroupVal.Text.Content
switch newValue.Type { switch newValue.Type {
@ -429,7 +429,7 @@ func syncAttrViewTableColWidth(operation *Operation) (err error) {
break break
} }
} }
case av.LayoutTypeGallery: case av.LayoutTypeGallery, av.LayoutTypeKanban:
return return
} }
@ -533,7 +533,7 @@ func foldAttrViewGroup(avID, blockID, groupID string, folded bool) (err error) {
return err return err
} }
if nil == view.Group { if !view.IsGroupView() {
return return
} }
@ -667,6 +667,8 @@ func setAttrViewCardAspectRatio(operation *Operation) (err error) {
return return
case av.LayoutTypeGallery: case av.LayoutTypeGallery:
view.Gallery.CardAspectRatio = av.CardAspectRatio(operation.Data.(float64)) view.Gallery.CardAspectRatio = av.CardAspectRatio(operation.Data.(float64))
case av.LayoutTypeKanban:
view.Kanban.CardAspectRatio = av.CardAspectRatio(operation.Data.(float64))
} }
err = av.SaveAttributeView(attrView) err = av.SaveAttributeView(attrView)
@ -707,7 +709,7 @@ func ChangeAttrViewLayout(blockID, avID string, layout av.LayoutType) (err error
switch newLayout { switch newLayout {
case av.LayoutTypeTable: case av.LayoutTypeTable:
if view.Name == av.GetAttributeViewI18n("gallery") { if view.Name == av.GetAttributeViewI18n("gallery") || view.Name == av.GetAttributeViewI18n("kanban") {
view.Name = av.GetAttributeViewI18n("table") view.Name = av.GetAttributeViewI18n("table")
} }
@ -721,9 +723,13 @@ func ChangeAttrViewLayout(blockID, avID string, layout av.LayoutType) (err error
for _, field := range view.Gallery.CardFields { for _, field := range view.Gallery.CardFields {
view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{BaseField: &av.BaseField{ID: field.ID}}) view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{BaseField: &av.BaseField{ID: field.ID}})
} }
case av.LayoutTypeKanban:
for _, field := range view.Kanban.Fields {
view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{BaseField: &av.BaseField{ID: field.ID}})
}
} }
case av.LayoutTypeGallery: case av.LayoutTypeGallery:
if view.Name == av.GetAttributeViewI18n("table") { if view.Name == av.GetAttributeViewI18n("table") || view.Name == av.GetAttributeViewI18n("kanban") {
view.Name = av.GetAttributeViewI18n("gallery") view.Name = av.GetAttributeViewI18n("gallery")
} }
@ -737,6 +743,30 @@ func ChangeAttrViewLayout(blockID, avID string, layout av.LayoutType) (err error
for _, col := range view.Table.Columns { for _, col := range view.Table.Columns {
view.Gallery.CardFields = append(view.Gallery.CardFields, &av.ViewGalleryCardField{BaseField: &av.BaseField{ID: col.ID}}) view.Gallery.CardFields = append(view.Gallery.CardFields, &av.ViewGalleryCardField{BaseField: &av.BaseField{ID: col.ID}})
} }
case av.LayoutTypeKanban:
for _, field := range view.Kanban.Fields {
view.Gallery.CardFields = append(view.Gallery.CardFields, &av.ViewGalleryCardField{BaseField: &av.BaseField{ID: field.ID}})
}
}
case av.LayoutTypeKanban:
if view.Name == av.GetAttributeViewI18n("table") || view.Name == av.GetAttributeViewI18n("gallery") {
view.Name = av.GetAttributeViewI18n("kanban")
}
if nil != view.Kanban {
break
}
view.Kanban = av.NewLayoutKanban()
switch view.LayoutType {
case av.LayoutTypeTable:
for _, col := range view.Table.Columns {
view.Kanban.Fields = append(view.Kanban.Fields, &av.ViewKanbanField{BaseField: &av.BaseField{ID: col.ID}})
}
case av.LayoutTypeGallery:
for _, field := range view.Gallery.CardFields {
view.Kanban.Fields = append(view.Kanban.Fields, &av.ViewKanbanField{BaseField: &av.BaseField{ID: field.ID}})
}
} }
} }
@ -816,6 +846,11 @@ func setAttrViewWrapField(operation *Operation) (err error) {
for _, field := range view.Gallery.CardFields { for _, field := range view.Gallery.CardFields {
field.Wrap = allFieldWrap field.Wrap = allFieldWrap
} }
case av.LayoutTypeKanban:
view.Kanban.WrapField = allFieldWrap
for _, field := range view.Kanban.Fields {
field.Wrap = allFieldWrap
}
} }
err = av.SaveAttributeView(attrView) err = av.SaveAttributeView(attrView)
@ -846,6 +881,8 @@ func setAttrViewShowIcon(operation *Operation) (err error) {
view.Table.ShowIcon = operation.Data.(bool) view.Table.ShowIcon = operation.Data.(bool)
case av.LayoutTypeGallery: case av.LayoutTypeGallery:
view.Gallery.ShowIcon = operation.Data.(bool) view.Gallery.ShowIcon = operation.Data.(bool)
case av.LayoutTypeKanban:
view.Kanban.ShowIcon = operation.Data.(bool)
} }
err = av.SaveAttributeView(attrView) err = av.SaveAttributeView(attrView)
@ -876,6 +913,8 @@ func setAttrViewFitImage(operation *Operation) (err error) {
return return
case av.LayoutTypeGallery: case av.LayoutTypeGallery:
view.Gallery.FitImage = operation.Data.(bool) view.Gallery.FitImage = operation.Data.(bool)
case av.LayoutTypeKanban:
view.Kanban.FitImage = operation.Data.(bool)
} }
err = av.SaveAttributeView(attrView) err = av.SaveAttributeView(attrView)
@ -906,6 +945,8 @@ func setAttrViewDisplayFieldName(operation *Operation) (err error) {
return return
case av.LayoutTypeGallery: case av.LayoutTypeGallery:
view.Gallery.DisplayFieldName = operation.Data.(bool) view.Gallery.DisplayFieldName = operation.Data.(bool)
case av.LayoutTypeKanban:
view.Kanban.DisplayFieldName = operation.Data.(bool)
} }
err = av.SaveAttributeView(attrView) err = av.SaveAttributeView(attrView)
@ -936,6 +977,8 @@ func setAttrViewCardSize(operation *Operation) (err error) {
return return
case av.LayoutTypeGallery: case av.LayoutTypeGallery:
view.Gallery.CardSize = av.CardSize(operation.Data.(float64)) view.Gallery.CardSize = av.CardSize(operation.Data.(float64))
case av.LayoutTypeKanban:
view.Kanban.CardSize = av.CardSize(operation.Data.(float64))
} }
err = av.SaveAttributeView(attrView) err = av.SaveAttributeView(attrView)
@ -966,6 +1009,8 @@ func setAttrViewCoverFromAssetKeyID(operation *Operation) (err error) {
return return
case av.LayoutTypeGallery: case av.LayoutTypeGallery:
view.Gallery.CoverFromAssetKeyID = operation.KeyID view.Gallery.CoverFromAssetKeyID = operation.KeyID
case av.LayoutTypeKanban:
view.Kanban.CoverFromAssetKeyID = operation.KeyID
} }
err = av.SaveAttributeView(attrView) err = av.SaveAttributeView(attrView)
@ -996,6 +1041,8 @@ func setAttrViewCoverFrom(operation *Operation) (err error) {
return return
case av.LayoutTypeGallery: case av.LayoutTypeGallery:
view.Gallery.CoverFrom = av.CoverFrom(operation.Data.(float64)) view.Gallery.CoverFrom = av.CoverFrom(operation.Data.(float64))
case av.LayoutTypeKanban:
view.Kanban.CoverFrom = av.CoverFrom(operation.Data.(float64))
} }
err = av.SaveAttributeView(attrView) err = av.SaveAttributeView(attrView)
@ -1032,7 +1079,7 @@ func AppendAttributeViewDetachedBlocksWithValues(avID string, blocksValues [][]*
v.IsDetached = true v.IsDetached = true
v.CreatedAt = now v.CreatedAt = now
v.UpdatedAt = now v.UpdatedAt = now
v.IsRenderAutoFill = false
keyValues.Values = append(keyValues.Values, v) keyValues.Values = append(keyValues.Values, v)
if av.KeyTypeSelect == v.Type || av.KeyTypeMSelect == v.Type { if av.KeyTypeSelect == v.Type || av.KeyTypeMSelect == v.Type {
@ -1264,6 +1311,26 @@ func SearchAttributeViewNonRelationKey(avID, keyword string) (ret []*av.Key) {
return return
} }
func SearchAttributeViewRollupDestKeys(avID, keyword string) (ret []*av.Key) {
waitForSyncingStorages()
ret = []*av.Key{}
attrView, err := av.ParseAttributeView(avID)
if err != nil {
logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
return
}
for _, keyValues := range attrView.KeyValues {
if av.KeyTypeRollup != keyValues.Key.Type && av.KeyTypeLineNumber != keyValues.Key.Type {
if strings.Contains(strings.ToLower(keyValues.Key.Name), strings.ToLower(keyword)) {
ret = append(ret, keyValues.Key)
}
}
}
return
}
func SearchAttributeViewRelationKey(avID, keyword string) (ret []*av.Key) { func SearchAttributeViewRelationKey(avID, keyword string) (ret []*av.Key) {
waitForSyncingStorages() waitForSyncingStorages()
@ -1339,6 +1406,10 @@ func SearchAttributeView(keyword string, excludeAvIDs []string) (ret []*AvSearch
continue continue
} }
if gulu.Str.Contains(id, excludeAvIDs) {
continue
}
if nil == avBlockRels[id] { if nil == avBlockRels[id] {
continue continue
} }
@ -1398,7 +1469,8 @@ func SearchAttributeView(keyword string, excludeAvIDs []string) (ret []*AvSearch
} }
node = treenode.GetNodeInTree(tree, bID) node = treenode.GetNodeInTree(tree, bID)
if nil == node || "" == node.AttributeViewID { if nil == node || "" == node.AttributeViewID || ast.NodeAttributeView != node.Type {
node = nil
continue continue
} }
@ -1535,13 +1607,12 @@ func GetBlockAttributeViewKeys(nodeID string) (ret []*BlockAttributeViewKeys) {
keyValues = append(keyValues, kValues) keyValues = append(keyValues, kValues)
} else { } else {
// 如果没有值,那么就补一个默认值 // 如果没有值,那么就补一个默认值
kValues.Values = append(kValues.Values, av.GetAttributeViewDefaultValue(itemID[:14]+ast.NewNodeID()[14:], kv.Key.ID, itemID, kv.Key.Type)) kValues.Values = append(kValues.Values, av.GetAttributeViewDefaultValue(itemID[:14]+ast.NewNodeID()[14:], kv.Key.ID, itemID, kv.Key.Type, false))
keyValues = append(keyValues, kValues) keyValues = append(keyValues, kValues)
} }
} }
// 先渲染主键、创建时间、更新时间 // 渲染主键、创建时间、更新时间
for _, kv := range keyValues { for _, kv := range keyValues {
switch kv.Key.Type { switch kv.Key.Type {
case av.KeyTypeBlock: // 对于主键可能需要填充静态锚文本 Database-bound block primary key supports setting static anchor text https://github.com/siyuan-note/siyuan/issues/10049 case av.KeyTypeBlock: // 对于主键可能需要填充静态锚文本 Database-bound block primary key supports setting static anchor text https://github.com/siyuan-note/siyuan/issues/10049
@ -1575,8 +1646,7 @@ func GetBlockAttributeViewKeys(nodeID string) (ret []*BlockAttributeViewKeys) {
} }
} }
// 再渲染关联和汇总 // 渲染关联和汇总
rollupFurtherCollections := sql.GetFurtherCollections(attrView, cachedAttrViews) rollupFurtherCollections := sql.GetFurtherCollections(attrView, cachedAttrViews)
for _, kv := range keyValues { for _, kv := range keyValues {
switch kv.Key.Type { switch kv.Key.Type {
@ -1633,7 +1703,7 @@ func GetBlockAttributeViewKeys(nodeID string) (ret []*BlockAttributeViewKeys) {
} }
} }
// 最后渲染模板 // 渲染模板
templateKeys, _ := sql.GetTemplateKeysByResolutionOrder(attrView) templateKeys, _ := sql.GetTemplateKeysByResolutionOrder(attrView)
var renderTemplateErr error var renderTemplateErr error
for _, templateKey := range templateKeys { for _, templateKey := range templateKeys {
@ -1651,7 +1721,7 @@ func GetBlockAttributeViewKeys(nodeID string) (ret []*BlockAttributeViewKeys) {
ial = map[string]string{} ial = map[string]string{}
} }
if nil == kv.Values[0].Template { if nil == kv.Values[0].Template {
kv.Values[0] = av.GetAttributeViewDefaultValue(kv.Values[0].ID, kv.Key.ID, nodeID, kv.Key.Type) kv.Values[0] = av.GetAttributeViewDefaultValue(kv.Values[0].ID, kv.Key.ID, nodeID, kv.Key.Type, false)
} }
var renderErr error var renderErr error
@ -1725,7 +1795,7 @@ func GetBlockAttributeViewKeys(nodeID string) (ret []*BlockAttributeViewKeys) {
} }
func genAttrViewGroups(view *av.View, attrView *av.AttributeView) { func genAttrViewGroups(view *av.View, attrView *av.AttributeView) {
if nil == view.Group { if !view.IsGroupView() {
return return
} }
@ -1912,6 +1982,9 @@ func genAttrViewGroups(view *av.View, attrView *av.AttributeView) {
case av.LayoutTypeGallery: case av.LayoutTypeGallery:
v = av.NewGalleryView() v = av.NewGalleryView()
v.Gallery = av.NewLayoutGallery() v.Gallery = av.NewLayoutGallery()
case av.LayoutTypeKanban:
v = av.NewKanbanView()
v.Kanban = av.NewLayoutKanban()
default: default:
logging.LogWarnf("unknown layout type [%s] for group view", view.LayoutType) logging.LogWarnf("unknown layout type [%s] for group view", view.LayoutType)
return return
@ -1969,7 +2042,7 @@ type GroupState struct {
func getAttrViewGroupStates(view *av.View) (groupStates map[string]*GroupState) { func getAttrViewGroupStates(view *av.View) (groupStates map[string]*GroupState) {
groupStates = map[string]*GroupState{} groupStates = map[string]*GroupState{}
if nil == view.Group { if !view.IsGroupView() {
return return
} }
@ -2308,6 +2381,8 @@ func updateAttributeViewColRelation(operation *Operation) (err error) {
v.Table.Columns = append(v.Table.Columns, &av.ViewTableColumn{BaseField: &av.BaseField{ID: operation.BackRelationKeyID}}) v.Table.Columns = append(v.Table.Columns, &av.ViewTableColumn{BaseField: &av.BaseField{ID: operation.BackRelationKeyID}})
case av.LayoutTypeGallery: case av.LayoutTypeGallery:
v.Gallery.CardFields = append(v.Gallery.CardFields, &av.ViewGalleryCardField{BaseField: &av.BaseField{ID: operation.BackRelationKeyID}}) v.Gallery.CardFields = append(v.Gallery.CardFields, &av.ViewGalleryCardField{BaseField: &av.BaseField{ID: operation.BackRelationKeyID}})
case av.LayoutTypeKanban:
v.Kanban.Fields = append(v.Kanban.Fields, &av.ViewKanbanField{BaseField: &av.BaseField{ID: operation.BackRelationKeyID}})
} }
} }
@ -2329,6 +2404,7 @@ func updateAttributeViewColRelation(operation *Operation) (err error) {
destVal.Relation = &av.ValueRelation{} destVal.Relation = &av.ValueRelation{}
} }
destVal.UpdatedAt = now destVal.UpdatedAt = now
destVal.IsRenderAutoFill = false
} }
destVal.Relation.BlockIDs = append(destVal.Relation.BlockIDs, srcVal.BlockID) destVal.Relation.BlockIDs = append(destVal.Relation.BlockIDs, srcVal.BlockID)
destVal.Relation.BlockIDs = gulu.Str.RemoveDuplicatedElem(destVal.Relation.BlockIDs) destVal.Relation.BlockIDs = gulu.Str.RemoveDuplicatedElem(destVal.Relation.BlockIDs)
@ -2522,6 +2598,8 @@ func (tx *Transaction) doDuplicateAttrViewView(operation *Operation) (ret *TxErr
view = av.NewTableView() view = av.NewTableView()
case av.LayoutTypeGallery: case av.LayoutTypeGallery:
view = av.NewGalleryView() view = av.NewGalleryView()
case av.LayoutTypeKanban:
view = av.NewKanbanView()
} }
view.ID = operation.ID view.ID = operation.ID
@ -2590,6 +2668,25 @@ func (tx *Transaction) doDuplicateAttrViewView(operation *Operation) (ret *TxErr
view.Gallery.DisplayFieldName = masterView.Gallery.DisplayFieldName view.Gallery.DisplayFieldName = masterView.Gallery.DisplayFieldName
view.Gallery.ShowIcon = masterView.Gallery.ShowIcon view.Gallery.ShowIcon = masterView.Gallery.ShowIcon
view.Gallery.WrapField = masterView.Gallery.WrapField view.Gallery.WrapField = masterView.Gallery.WrapField
case av.LayoutTypeKanban:
for _, field := range masterView.Kanban.Fields {
view.Kanban.Fields = append(view.Kanban.Fields, &av.ViewKanbanField{
BaseField: &av.BaseField{
ID: field.ID,
Wrap: field.Wrap,
Hidden: field.Hidden,
Desc: field.Desc,
},
})
}
view.Kanban.CoverFrom = masterView.Kanban.CoverFrom
view.Kanban.CoverFromAssetKeyID = masterView.Kanban.CoverFromAssetKeyID
view.Kanban.CardSize = masterView.Kanban.CardSize
view.Kanban.FitImage = masterView.Kanban.FitImage
view.Kanban.DisplayFieldName = masterView.Kanban.DisplayFieldName
view.Kanban.ShowIcon = masterView.Kanban.ShowIcon
view.Kanban.WrapField = masterView.Kanban.WrapField
} }
view.ItemIDs = masterView.ItemIDs view.ItemIDs = masterView.ItemIDs
@ -2655,6 +2752,10 @@ func addAttrViewView(avID, viewID, blockID string, layout av.LayoutType) (err er
for _, field := range firstView.Gallery.CardFields { for _, field := range firstView.Gallery.CardFields {
view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{BaseField: &av.BaseField{ID: field.ID}}) view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{BaseField: &av.BaseField{ID: field.ID}})
} }
case av.LayoutTypeKanban:
for _, field := range firstView.Kanban.Fields {
view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{BaseField: &av.BaseField{ID: field.ID}})
}
} }
case av.LayoutTypeGallery: case av.LayoutTypeGallery:
view = av.NewGalleryView() view = av.NewGalleryView()
@ -2667,6 +2768,22 @@ func addAttrViewView(avID, viewID, blockID string, layout av.LayoutType) (err er
for _, field := range firstView.Gallery.CardFields { for _, field := range firstView.Gallery.CardFields {
view.Gallery.CardFields = append(view.Gallery.CardFields, &av.ViewGalleryCardField{BaseField: &av.BaseField{ID: field.ID}}) view.Gallery.CardFields = append(view.Gallery.CardFields, &av.ViewGalleryCardField{BaseField: &av.BaseField{ID: field.ID}})
} }
case av.LayoutTypeKanban:
for _, field := range firstView.Kanban.Fields {
view.Gallery.CardFields = append(view.Gallery.CardFields, &av.ViewGalleryCardField{BaseField: &av.BaseField{ID: field.ID}})
}
}
case av.LayoutTypeKanban:
view = av.NewKanbanView()
switch firstView.LayoutType {
case av.LayoutTypeTable:
for _, col := range firstView.Table.Columns {
view.Kanban.Fields = append(view.Kanban.Fields, &av.ViewKanbanField{BaseField: &av.BaseField{ID: col.ID}})
}
case av.LayoutTypeKanban:
for _, field := range firstView.Kanban.Fields {
view.Kanban.Fields = append(view.Kanban.Fields, &av.ViewKanbanField{BaseField: &av.BaseField{ID: field.ID}})
}
} }
default: default:
err = av.ErrWrongLayoutType err = av.ErrWrongLayoutType
@ -2995,7 +3112,7 @@ func setAttributeViewColumnCalc(operation *Operation) (err error) {
break break
} }
} }
case av.LayoutTypeGallery: case av.LayoutTypeGallery, av.LayoutTypeKanban:
return return
} }
@ -3145,12 +3262,19 @@ func addAttributeViewBlock(now int64, avID, dbBlockID, viewID, groupID, previous
// The database date field supports filling the current time by default https://github.com/siyuan-note/siyuan/issues/10823 // The database date field supports filling the current time by default https://github.com/siyuan-note/siyuan/issues/10823
for _, keyValues := range attrView.KeyValues { for _, keyValues := range attrView.KeyValues {
if av.KeyTypeDate == keyValues.Key.Type && nil != keyValues.Key.Date && keyValues.Key.Date.AutoFillNow { if av.KeyTypeDate == keyValues.Key.Type && nil != keyValues.Key.Date && keyValues.Key.Date.AutoFillNow {
if nil == keyValues.GetValue(addingItemID) { // 避免覆盖已有值(可能前面已经通过过滤或者分组条件填充了值) val := keyValues.GetValue(addingItemID)
if nil == val { // 避免覆盖已有值(可能前面已经通过过滤或者分组条件填充了值)
dateVal := &av.Value{ dateVal := &av.Value{
ID: ast.NewNodeID(), KeyID: keyValues.Key.ID, BlockID: addingItemID, Type: av.KeyTypeDate, IsDetached: isDetached, CreatedAt: now, UpdatedAt: now + 1000, ID: ast.NewNodeID(), KeyID: keyValues.Key.ID, BlockID: addingItemID, Type: av.KeyTypeDate, IsDetached: isDetached, CreatedAt: now, UpdatedAt: now + 1000,
Date: &av.ValueDate{Content: now, IsNotEmpty: true}, Date: &av.ValueDate{Content: now, IsNotEmpty: true},
} }
keyValues.Values = append(keyValues.Values, dateVal) keyValues.Values = append(keyValues.Values, dateVal)
} else {
if val.IsRenderAutoFill {
val.CreatedAt, val.UpdatedAt = now, now+1000
val.Date.Content, val.Date.IsNotEmpty, val.Date.IsNotTime = now, true, false
val.IsRenderAutoFill = false
}
} }
} }
} }
@ -3230,11 +3354,13 @@ func fillDefaultValue(attrView *av.AttributeView, view, groupView *av.View, prev
existingVal := keyValues.GetValue(addingItemID) existingVal := keyValues.GetValue(addingItemID)
if nil == existingVal { if nil == existingVal {
newValue.IsRenderAutoFill = false
keyValues.Values = append(keyValues.Values, newValue) keyValues.Values = append(keyValues.Values, newValue)
} else { } else {
newValueRaw := newValue.GetValByType(keyValues.Key.Type) newValueRaw := newValue.GetValByType(keyValues.Key.Type)
if av.KeyTypeBlock != existingVal.Type || (av.KeyTypeBlock == existingVal.Type && existingVal.IsDetached) { if av.KeyTypeBlock != existingVal.Type || (av.KeyTypeBlock == existingVal.Type && existingVal.IsDetached) {
// 非主键的值直接覆盖,主键的值只覆盖非绑定块 // 非主键的值直接覆盖,主键的值只覆盖非绑定块
existingVal.IsRenderAutoFill = false
existingVal.SetValByType(keyValues.Key.Type, newValueRaw) existingVal.SetValByType(keyValues.Key.Type, newValueRaw)
} }
} }
@ -3486,6 +3612,22 @@ func duplicateAttributeViewKey(operation *Operation) (err error) {
break break
} }
} }
case av.LayoutTypeKanban:
for i, field := range view.Kanban.Fields {
if field.ID == key.ID {
view.Kanban.Fields = append(view.Kanban.Fields[:i+1], append([]*av.ViewKanbanField{
{
BaseField: &av.BaseField{
ID: copyKey.ID,
Wrap: field.Wrap,
Hidden: field.Hidden,
Desc: field.Desc,
},
},
}, view.Kanban.Fields[i+1:]...)...)
break
}
}
} }
} }
@ -3520,7 +3662,7 @@ func setAttributeViewColWidth(operation *Operation) (err error) {
break break
} }
} }
case av.LayoutTypeGallery: case av.LayoutTypeGallery, av.LayoutTypeKanban:
return return
} }
@ -3566,6 +3708,14 @@ func setAttributeViewColWrap(operation *Operation) (err error) {
allFieldWrap = allFieldWrap && field.Wrap allFieldWrap = allFieldWrap && field.Wrap
} }
view.Gallery.WrapField = allFieldWrap view.Gallery.WrapField = allFieldWrap
case av.LayoutTypeKanban:
for _, field := range view.Kanban.Fields {
if field.ID == operation.ID {
field.Wrap = newWrap
}
allFieldWrap = allFieldWrap && field.Wrap
}
view.Kanban.WrapField = allFieldWrap
} }
err = av.SaveAttributeView(attrView) err = av.SaveAttributeView(attrView)
@ -3606,6 +3756,13 @@ func setAttributeViewColHidden(operation *Operation) (err error) {
break break
} }
} }
case av.LayoutTypeKanban:
for _, field := range view.Kanban.Fields {
if field.ID == operation.ID {
field.Hidden = operation.Data.(bool)
break
}
}
} }
err = av.SaveAttributeView(attrView) err = av.SaveAttributeView(attrView)
@ -3639,7 +3796,7 @@ func setAttributeViewColPin(operation *Operation) (err error) {
break break
} }
} }
case av.LayoutTypeGallery: case av.LayoutTypeGallery, av.LayoutTypeKanban:
return return
} }
@ -3867,6 +4024,27 @@ func SortAttributeViewViewKey(avID, blockID, keyID, previousKeyID string) (err e
} }
} }
view.Gallery.CardFields = util.InsertElem(view.Gallery.CardFields, previousIndex, field) view.Gallery.CardFields = util.InsertElem(view.Gallery.CardFields, previousIndex, field)
case av.LayoutTypeKanban:
var field *av.ViewKanbanField
for i, kanbanField := range view.Kanban.Fields {
if kanbanField.ID == keyID {
field = kanbanField
curIndex = i
break
}
}
if nil == field {
return
}
view.Kanban.Fields = append(view.Kanban.Fields[:curIndex], view.Kanban.Fields[curIndex+1:]...)
for i, kanbanField := range view.Kanban.Fields {
if kanbanField.ID == previousKeyID {
previousIndex = i + 1
break
}
}
view.Kanban.Fields = util.InsertElem(view.Kanban.Fields, previousIndex, field)
} }
err = av.SaveAttributeView(attrView) err = av.SaveAttributeView(attrView)
@ -3990,8 +4168,8 @@ func AddAttributeViewKey(avID, keyID, keyName, keyType, keyIcon, previousKeyID s
newField.Wrap = view.Table.WrapField newField.Wrap = view.Table.WrapField
if "" == previousKeyID { if "" == previousKeyID {
if av.LayoutTypeGallery == currentView.LayoutType { if av.LayoutTypeGallery == currentView.LayoutType || av.LayoutTypeKanban == currentView.LayoutType {
// 如果当前视图是卡片视图则添加到最后 // 如果当前视图是卡片或看板视图则添加到最后
view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{BaseField: newField}) view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{BaseField: newField})
} else { } else {
view.Table.Columns = append([]*av.ViewTableColumn{{BaseField: newField}}, view.Table.Columns...) view.Table.Columns = append([]*av.ViewTableColumn{{BaseField: newField}}, view.Table.Columns...)
@ -4030,6 +4208,26 @@ func AddAttributeViewKey(avID, keyID, keyName, keyType, keyIcon, previousKeyID s
} }
} }
} }
if nil != view.Kanban {
newField.Wrap = view.Kanban.WrapField
if "" == previousKeyID {
view.Kanban.Fields = append(view.Kanban.Fields, &av.ViewKanbanField{BaseField: newField})
} else {
added := false
for i, field := range view.Kanban.Fields {
if field.ID == previousKeyID {
view.Kanban.Fields = append(view.Kanban.Fields[:i+1], append([]*av.ViewKanbanField{{BaseField: newField}}, view.Kanban.Fields[i+1:]...)...)
added = true
break
}
}
if !added {
view.Kanban.Fields = append(view.Kanban.Fields, &av.ViewKanbanField{BaseField: newField})
}
}
}
} }
} }
@ -4242,6 +4440,13 @@ func RemoveAttributeViewKey(avID, keyID string, removeRelationDest bool) (err er
break break
} }
} }
case av.LayoutTypeKanban:
for i, field := range view.Kanban.Fields {
if field.ID == removedKey.Relation.BackKeyID {
view.Kanban.Fields = append(view.Kanban.Fields[:i], view.Kanban.Fields[i+1:]...)
break
}
}
} }
} }
} }
@ -4286,6 +4491,15 @@ func RemoveAttributeViewKey(avID, keyID string, removeRelationDest bool) (err er
} }
} }
} }
if nil != view.Kanban {
for i, field := range view.Kanban.Fields {
if field.ID == keyID {
view.Kanban.Fields = append(view.Kanban.Fields[:i], view.Kanban.Fields[i+1:]...)
break
}
}
}
} }
for _, view := range attrView.Views { for _, view := range attrView.Views {

View file

@ -97,14 +97,14 @@ func renderAttributeViewGroups(viewable av.Viewable, attrView *av.AttributeView,
if isGroupByDate(view) { if isGroupByDate(view) {
createdDate := time.UnixMilli(view.GroupCreated).Format("2006-01-02") createdDate := time.UnixMilli(view.GroupCreated).Format("2006-01-02")
if time.Now().Format("2006-01-02") != createdDate { if time.Now().Format("2006-01-02") != createdDate {
regenAttrViewGroups(attrView) genAttrViewGroups(view, attrView) // 仅重新生成一个视图的分组以提升性能
av.SaveAttributeView(attrView) av.SaveAttributeView(attrView)
} }
} }
// 如果是按模板分组则需要重新生成分组 // 如果是按模板分组则需要重新生成分组
if isGroupByTemplate(attrView, view) { if isGroupByTemplate(attrView, view) {
regenAttrViewGroups(attrView) genAttrViewGroups(view, attrView) // 仅重新生成一个视图的分组以提升性能
av.SaveAttributeView(attrView) av.SaveAttributeView(attrView)
} }
@ -167,6 +167,8 @@ func renderAttributeViewGroups(viewable av.Viewable, attrView *av.AttributeView,
groupView.Table.Columns = nil groupView.Table.Columns = nil
case av.LayoutTypeGallery: case av.LayoutTypeGallery:
groupView.Gallery.CardFields = nil groupView.Gallery.CardFields = nil
case av.LayoutTypeKanban:
groupView.Kanban.Fields = nil
} }
} }
viewable.SetGroups(groups) viewable.SetGroups(groups)
@ -177,7 +179,7 @@ func renderAttributeViewGroups(viewable av.Viewable, attrView *av.AttributeView,
} }
func hideEmptyGroupViews(view *av.View, viewable av.Viewable) { func hideEmptyGroupViews(view *av.View, viewable av.Viewable) {
if nil == view.Group { if !view.IsGroupView() {
return return
} }
@ -343,14 +345,14 @@ func sortGroupsBySelectOption(view *av.View, groupKey *av.Key) {
} }
func isGroupByDate(view *av.View) bool { func isGroupByDate(view *av.View) bool {
if nil == view.Group { if !view.IsGroupView() {
return false return false
} }
return av.GroupMethodDateDay == view.Group.Method || av.GroupMethodDateWeek == view.Group.Method || av.GroupMethodDateMonth == view.Group.Method || av.GroupMethodDateYear == view.Group.Method || av.GroupMethodDateRelative == view.Group.Method return av.GroupMethodDateDay == view.Group.Method || av.GroupMethodDateWeek == view.Group.Method || av.GroupMethodDateMonth == view.Group.Method || av.GroupMethodDateYear == view.Group.Method || av.GroupMethodDateRelative == view.Group.Method
} }
func isGroupByTemplate(attrView *av.AttributeView, view *av.View) bool { func isGroupByTemplate(attrView *av.AttributeView, view *av.View) bool {
if nil == view.Group { if !view.IsGroupView() {
return false return false
} }
@ -402,6 +404,19 @@ func renderViewableInstance(viewable av.Viewable, view *av.View, attrView *av.At
end = len(gallery.Cards) end = len(gallery.Cards)
} }
gallery.Cards = gallery.Cards[start:end] gallery.Cards = gallery.Cards[start:end]
case av.LayoutTypeKanban:
kanban := viewable.(*av.Kanban)
kanban.CardCount = 0
kanban.PageSize = view.PageSize
if 1 > pageSize {
pageSize = kanban.PageSize
}
start := (page - 1) * pageSize
end := start + pageSize
if len(kanban.Cards) < end {
end = len(kanban.Cards)
}
kanban.Cards = kanban.Cards[start:end]
} }
return return
} }
@ -486,7 +501,7 @@ func RenderRepoSnapshotAttributeView(indexID, avID string) (viewable av.Viewable
return return
} }
func RenderHistoryAttributeView(avID, created string) (viewable av.Viewable, attrView *av.AttributeView, err error) { func RenderHistoryAttributeView(blockID, avID, viewID, query string, page, pageSize int, groupPaging map[string]interface{}, created string) (viewable av.Viewable, attrView *av.AttributeView, err error) {
createdUnix, parseErr := strconv.ParseInt(created, 10, 64) createdUnix, parseErr := strconv.ParseInt(created, 10, 64)
if nil != parseErr { if nil != parseErr {
logging.LogErrorf("parse created [%s] failed: %s", created, parseErr) logging.LogErrorf("parse created [%s] failed: %s", created, parseErr)
@ -525,6 +540,6 @@ func RenderHistoryAttributeView(avID, created string) (viewable av.Viewable, att
} }
} }
viewable, err = renderAttributeView(attrView, "", "", "", 1, -1, nil) viewable, err = renderAttributeView(attrView, blockID, viewID, query, page, pageSize, groupPaging)
return return
} }

View file

@ -234,6 +234,22 @@ func GetBlockSiblingID(id string) (parent, previous, next string) {
next = flb.ID next = flb.ID
} }
} }
if "" == previous && "" == next && nil != current {
parent = current.ID
if nil != current.Previous {
previous = current.Previous.ID
if flb := treenode.FirstChildBlock(current.Previous); nil != flb {
previous = flb.ID
}
}
if nil != current.Next {
next = current.Next.ID
if flb := treenode.FirstChildBlock(current.Next); nil != flb {
next = flb.ID
}
}
}
return return
} }
@ -612,6 +628,46 @@ func GetHeadingDeleteTransaction(id string) (transaction *Transaction, err error
return return
} }
func GetHeadingInsertTransaction(id string) (transaction *Transaction, err error) {
tree, err := LoadTreeByBlockID(id)
if err != nil {
return
}
node := treenode.GetNodeInTree(tree, id)
if nil == node {
err = errors.New(fmt.Sprintf(Conf.Language(15), id))
return
}
if ast.NodeHeading != node.Type {
return
}
var nodes []*ast.Node
nodes = append(nodes, node)
nodes = append(nodes, treenode.HeadingChildren(node)...)
transaction = &Transaction{}
luteEngine := util.NewLute()
for _, n := range nodes {
n.ID = ast.NewNodeID()
n.SetIALAttr("id", n.ID)
op := &Operation{Context: map[string]any{"ignoreProcess": "true"}}
op.ID = n.ID
op.Action = "insert"
op.Data = luteEngine.RenderNodeBlockDOM(n)
transaction.DoOperations = append(transaction.DoOperations, op)
op = &Operation{}
op.ID = n.ID
op.Action = "delete"
transaction.UndoOperations = append(transaction.UndoOperations, op)
}
return
}
func GetHeadingChildrenIDs(id string) (ret []string) { func GetHeadingChildrenIDs(id string) (ret []string) {
tree, err := LoadTreeByBlockID(id) tree, err := LoadTreeByBlockID(id)
if err != nil { if err != nil {

View file

@ -473,6 +473,8 @@ func moveTree(tree *parse.Tree) {
box := Conf.Box(tree.Box) box := Conf.Box(tree.Box)
box.renameSubTrees(tree) box.renameSubTrees(tree)
refreshDocInfo(tree)
} }
func (box *Box) renameSubTrees(tree *parse.Tree) { func (box *Box) renameSubTrees(tree *parse.Tree) {

View file

@ -2271,12 +2271,11 @@ func exportTree(tree *parse.Tree, wysiwyg, keepFold, avHiddenCol bool,
switch blockRefMode { switch blockRefMode {
case 2: // 锚文本块链 case 2: // 锚文本块链
blockRefLink := &ast.Node{Type: ast.NodeTextMark, TextMarkType: "a", TextMarkTextContent: linkText, TextMarkAHref: "siyuan://blocks/" + defID} blockRefLink := &ast.Node{Type: ast.NodeTextMark, TextMarkTextContent: linkText, TextMarkAHref: "siyuan://blocks/" + defID}
blockRefLink.KramdownIAL = n.KramdownIAL blockRefLink.KramdownIAL = n.KramdownIAL
if n.IsTextMarkType("inline-memo") { // 除了块引还有其他元素 https://github.com/siyuan-note/siyuan/issues/15698
blockRefLink.TextMarkInlineMemoContent = n.TextMarkInlineMemoContent blockRefLink.TextMarkType = strings.TrimSpace(strings.ReplaceAll(n.TextMarkType, "block-ref", "a"))
blockRefLink.TextMarkType = "a inline-memo" blockRefLink.TextMarkInlineMemoContent = n.TextMarkInlineMemoContent
}
n.InsertBefore(blockRefLink) n.InsertBefore(blockRefLink)
unlinks = append(unlinks, n) unlinks = append(unlinks, n)
case 3: // 仅锚文本 case 3: // 仅锚文本
@ -2291,11 +2290,11 @@ func exportTree(tree *parse.Tree, wysiwyg, keepFold, avHiddenCol bool,
} }
} else { } else {
blockRefLink = &ast.Node{Type: ast.NodeText, Tokens: []byte(linkText)} blockRefLink = &ast.Node{Type: ast.NodeText, Tokens: []byte(linkText)}
if n.IsTextMarkType("inline-memo") { if "block-ref" != n.TextMarkType {
blockRefLink.Type = ast.NodeTextMark blockRefLink.Type = ast.NodeTextMark
blockRefLink.TextMarkInlineMemoContent = n.TextMarkInlineMemoContent blockRefLink.TextMarkType = strings.TrimSpace(strings.ReplaceAll(n.TextMarkType, "block-ref", ""))
blockRefLink.TextMarkType = "inline-memo"
blockRefLink.TextMarkTextContent = linkText blockRefLink.TextMarkTextContent = linkText
blockRefLink.TextMarkInlineMemoContent = n.TextMarkInlineMemoContent
} }
} }
n.InsertBefore(blockRefLink) n.InsertBefore(blockRefLink)
@ -2315,13 +2314,13 @@ func exportTree(tree *parse.Tree, wysiwyg, keepFold, avHiddenCol bool,
} }
text := &ast.Node{Type: ast.NodeText, Tokens: []byte(linkText)} text := &ast.Node{Type: ast.NodeText, Tokens: []byte(linkText)}
n.InsertBefore(text) if "block-ref" != n.TextMarkType {
if n.IsTextMarkType("inline-memo") {
text.Type = ast.NodeTextMark text.Type = ast.NodeTextMark
text.TextMarkType = "inline-memo" text.TextMarkType = strings.TrimSpace(strings.ReplaceAll(n.TextMarkType, "block-ref", ""))
text.TextMarkTextContent = linkText text.TextMarkTextContent = linkText
text.TextMarkInlineMemoContent = n.TextMarkInlineMemoContent text.TextMarkInlineMemoContent = n.TextMarkInlineMemoContent
} }
n.InsertBefore(text)
n.InsertBefore(&ast.Node{Type: ast.NodeFootnotesRef, Tokens: []byte("^" + refFoot.refNum), FootnotesRefId: refFoot.refNum, FootnotesRefLabel: []byte("^" + refFoot.refNum)}) n.InsertBefore(&ast.Node{Type: ast.NodeFootnotesRef, Tokens: []byte("^" + refFoot.refNum), FootnotesRefId: refFoot.refNum, FootnotesRefLabel: []byte("^" + refFoot.refNum)})
unlinks = append(unlinks, n) unlinks = append(unlinks, n)
} }
@ -3454,6 +3453,11 @@ func getAttrViewTable(attrView *av.AttributeView, view *av.View, query string) (
for _, field := range view.Gallery.CardFields { for _, field := range view.Gallery.CardFields {
view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{BaseField: &av.BaseField{ID: field.ID}}) view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{BaseField: &av.BaseField{ID: field.ID}})
} }
case av.LayoutTypeKanban:
view.Table = av.NewLayoutTable()
for _, field := range view.Kanban.Fields {
view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{BaseField: &av.BaseField{ID: field.ID}})
}
} }
depth := 1 depth := 1

View file

@ -933,7 +933,7 @@ func writeTreeUpsertQueue(tree *parse.Tree) (err error) {
return return
} }
sql.UpsertTreeQueue(tree) sql.UpsertTreeQueue(tree)
refreshDocInfo(tree, size) refreshDocInfoWithSize(tree, size)
return return
} }
@ -959,7 +959,7 @@ func renameWriteJSONQueue(tree *parse.Tree) (err error) {
} }
sql.RenameTreeQueue(tree) sql.RenameTreeQueue(tree)
treenode.UpsertBlockTree(tree) treenode.UpsertBlockTree(tree)
refreshDocInfo(tree, size) refreshDocInfoWithSize(tree, size)
return return
} }
@ -1371,6 +1371,8 @@ func moveDoc(fromBox *Box, fromPath string, toBox *Box, toPath string, luteEngin
return return
} }
fromParentTree := loadParentTree(tree)
moveToRoot := "/" == toPath moveToRoot := "/" == toPath
toBlockID := tree.ID toBlockID := tree.ID
fromFolder := path.Join(path.Dir(fromPath), tree.ID) fromFolder := path.Join(path.Dir(fromPath), tree.ID)
@ -1489,6 +1491,8 @@ func moveDoc(fromBox *Box, fromPath string, toBox *Box, toPath string, luteEngin
} }
evt.Callback = callback evt.Callback = callback
util.PushEvent(evt) util.PushEvent(evt)
refreshDocInfo(fromParentTree)
return return
} }

View file

@ -17,7 +17,10 @@
package model package model
import ( import (
"bytes"
"github.com/88250/lute/ast" "github.com/88250/lute/ast"
"github.com/88250/lute/editor"
"github.com/88250/lute/render" "github.com/88250/lute/render"
"github.com/siyuan-note/logging" "github.com/siyuan-note/logging"
"github.com/siyuan-note/siyuan/kernel/util" "github.com/siyuan-note/siyuan/kernel/util"
@ -37,13 +40,18 @@ func AutoSpace(rootID string) (err error) {
generateOpTypeHistory(tree, HistoryOpFormat) generateOpTypeHistory(tree, HistoryOpFormat)
luteEngine := NewLute() luteEngine := NewLute()
// 合并相邻的同类行级节点
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
if entering { if !entering {
switch n.Type { return ast.WalkContinue
case ast.NodeTextMark: }
luteEngine.MergeSameTextMark(n)
} switch n.Type {
case ast.NodeTextMark:
luteEngine.MergeSameTextMark(n) // 合并相邻的同类行级节点
case ast.NodeCodeBlockCode:
// 代码块中包含 ``` 时 `优化排版` 异常 `Optimize typography` exception when code block contains ``` https://github.com/siyuan-note/siyuan/issues/15843
n.Tokens = bytes.ReplaceAll(n.Tokens, []byte(editor.Zwj+"```"), []byte("```"))
n.Tokens = bytes.ReplaceAll(n.Tokens, []byte("```"), []byte(editor.Zwj+"```"))
} }
return ast.WalkContinue return ast.WalkContinue
}) })

View file

@ -18,7 +18,6 @@ package model
import ( import (
"os" "os"
"path"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
@ -38,25 +37,22 @@ import (
"github.com/siyuan-note/siyuan/kernel/util" "github.com/siyuan-note/siyuan/kernel/util"
) )
func refreshDocInfo(tree *parse.Tree, size uint64) { func refreshDocInfo(tree *parse.Tree) {
refreshDocInfoWithSize(tree, filesys.TreeSize(tree))
}
func refreshDocInfoWithSize(tree *parse.Tree, size uint64) {
refreshDocInfo0(tree, size) refreshDocInfo0(tree, size)
refreshParentDocInfo(tree) refreshParentDocInfo(tree)
} }
func refreshParentDocInfo(tree *parse.Tree) { func refreshParentDocInfo(tree *parse.Tree) {
parentTree := loadParentTree(tree)
if nil == parentTree {
return
}
luteEngine := lute.New() luteEngine := lute.New()
boxDir := filepath.Join(util.DataDir, tree.Box)
parentDir := path.Dir(tree.Path)
if parentDir == boxDir || parentDir == "/" {
return
}
parentPath := parentDir + ".sy"
parentTree, err := filesys.LoadTree(tree.Box, parentPath, luteEngine)
if err != nil {
return
}
renderer := render.NewJSONRenderer(parentTree, luteEngine.RenderOptions) renderer := render.NewJSONRenderer(parentTree, luteEngine.RenderOptions)
data := renderer.Render() data := renderer.Render()
refreshDocInfo0(parentTree, uint64(len(data))) refreshDocInfo0(parentTree, uint64(len(data)))

View file

@ -305,3 +305,16 @@ func indexTreeInFilesystem(rootID string) {
sql.IndexTreeQueue(tree) sql.IndexTreeQueue(tree)
logging.LogInfof("reindexed tree by filesystem [rootID=%s]", rootID) logging.LogInfof("reindexed tree by filesystem [rootID=%s]", rootID)
} }
func loadParentTree(tree *parse.Tree) (ret *parse.Tree) {
boxDir := filepath.Join(util.DataDir, tree.Box)
parentDir := path.Dir(tree.Path)
if parentDir == boxDir || parentDir == "/" {
return
}
luteEngine := lute.New()
parentPath := parentDir + ".sy"
ret, _ = filesys.LoadTree(tree.Box, parentPath, luteEngine)
return
}

View file

@ -53,6 +53,17 @@ func RenderGroupView(attrView *av.AttributeView, view, groupView *av.View, query
groupView.Gallery.CardSize = view.Gallery.CardSize groupView.Gallery.CardSize = view.Gallery.CardSize
groupView.Gallery.FitImage = view.Gallery.FitImage groupView.Gallery.FitImage = view.Gallery.FitImage
groupView.Gallery.DisplayFieldName = view.Gallery.DisplayFieldName groupView.Gallery.DisplayFieldName = view.Gallery.DisplayFieldName
case av.LayoutTypeKanban:
err = copier.CopyWithOption(&groupView.Kanban.Fields, &view.Kanban.Fields, copier.Option{DeepCopy: true})
groupView.Kanban.ShowIcon = view.Kanban.ShowIcon
groupView.Kanban.WrapField = view.Kanban.WrapField
groupView.Kanban.CoverFrom = view.Kanban.CoverFrom
groupView.Kanban.CoverFromAssetKeyID = view.Kanban.CoverFromAssetKeyID
groupView.Kanban.CardAspectRatio = view.Kanban.CardAspectRatio
groupView.Kanban.CardSize = view.Kanban.CardSize
groupView.Kanban.FitImage = view.Kanban.FitImage
groupView.Kanban.DisplayFieldName = view.Kanban.DisplayFieldName
} }
if nil != err { if nil != err {
logging.LogErrorf("copy view fields [%s] to group [%s] failed: %s", view.ID, groupView.ID, err) logging.LogErrorf("copy view fields [%s] to group [%s] failed: %s", view.ID, groupView.ID, err)
@ -61,6 +72,8 @@ func RenderGroupView(attrView *av.AttributeView, view, groupView *av.View, query
groupView.Table.Columns = view.Table.Columns groupView.Table.Columns = view.Table.Columns
case av.LayoutTypeGallery: case av.LayoutTypeGallery:
groupView.Gallery.CardFields = view.Gallery.CardFields groupView.Gallery.CardFields = view.Gallery.CardFields
case av.LayoutTypeKanban:
groupView.Kanban.Fields = view.Kanban.Fields
} }
} }
@ -91,6 +104,8 @@ func renderView(attrView *av.AttributeView, view *av.View, query string, depth *
ret = RenderAttributeViewTable(attrView, view, query, depth, cachedAttrViews) ret = RenderAttributeViewTable(attrView, view, query, depth, cachedAttrViews)
case av.LayoutTypeGallery: case av.LayoutTypeGallery:
ret = RenderAttributeViewGallery(attrView, view, query, depth, cachedAttrViews) ret = RenderAttributeViewGallery(attrView, view, query, depth, cachedAttrViews)
case av.LayoutTypeKanban:
ret = RenderAttributeViewKanban(attrView, view, query, depth, cachedAttrViews)
} }
return return
} }
@ -314,7 +329,7 @@ func filterNotFoundAttrViewItems(keyValuesMap map[string][]*av.KeyValues) {
} }
} }
func fillAttributeViewBaseValue(baseValue *av.BaseValue, fieldID, itemID string, fieldNumberFormat av.NumberFormat, fieldTemplate string) { func fillAttributeViewBaseValue(baseValue *av.BaseValue, fieldID, itemID string, fieldNumberFormat av.NumberFormat, fieldTemplate string, fieldDateAutoFill bool) {
switch baseValue.ValueType { switch baseValue.ValueType {
case av.KeyTypeNumber: // 格式化数字 case av.KeyTypeNumber: // 格式化数字
if nil != baseValue.Value && nil != baseValue.Value.Number && baseValue.Value.Number.IsNotEmpty { if nil != baseValue.Value && nil != baseValue.Value.Number && baseValue.Value.Number.IsNotEmpty {
@ -330,14 +345,14 @@ func fillAttributeViewBaseValue(baseValue *av.BaseValue, fieldID, itemID string,
} }
if nil == baseValue.Value { if nil == baseValue.Value {
baseValue.Value = av.GetAttributeViewDefaultValue(baseValue.ID, fieldID, itemID, baseValue.ValueType) baseValue.Value = av.GetAttributeViewDefaultValue(baseValue.ID, fieldID, itemID, baseValue.ValueType, fieldDateAutoFill)
} else { } else {
FillAttributeViewNilValue(baseValue.Value, baseValue.ValueType) FillAttributeViewNilValue(baseValue.Value, baseValue.ValueType)
} }
} }
func fillAttributeViewAutoGeneratedValues(attrView *av.AttributeView, collection av.Collection, ials map[string]map[string]string, depth *int, cachedAttrViews map[string]*av.AttributeView) { func fillAttributeViewAutoGeneratedValues(attrView *av.AttributeView, collection av.Collection, ials map[string]map[string]string, depth *int, cachedAttrViews map[string]*av.AttributeView) {
// 渲染主键、创建时间、更新时间 // 渲染主键、创建时间、更新时间
for _, item := range collection.GetItems() { for _, item := range collection.GetItems() {
for _, value := range item.GetValues() { for _, value := range item.GetValues() {
@ -400,8 +415,42 @@ func fillAttributeViewAutoGeneratedValues(attrView *av.AttributeView, collection
} }
} }
// 再渲染关联和汇总 // 渲染关联
for _, item := range collection.GetItems() {
for _, value := range item.GetValues() {
if av.KeyTypeRelation != value.Type {
continue
}
value.Relation.Contents = nil
relKey, _ := attrView.GetKey(value.KeyID)
if nil != relKey && nil != relKey.Relation {
destAv := cachedAttrViews[relKey.Relation.AvID]
if nil == destAv {
destAv, _ = av.ParseAttributeView(relKey.Relation.AvID)
if nil != destAv {
cachedAttrViews[relKey.Relation.AvID] = destAv
}
}
if nil != destAv {
blocks := map[string]*av.Value{}
blockValues := destAv.GetBlockKeyValues()
if nil != blockValues {
for _, blockValue := range blockValues.Values {
blocks[blockValue.BlockID] = blockValue
}
for _, blockID := range value.Relation.BlockIDs {
if val := blocks[blockID]; nil != val {
value.Relation.Contents = append(value.Relation.Contents, val)
}
}
}
}
}
}
}
// 渲染汇总
rollupFurtherCollections := map[string]av.Collection{} rollupFurtherCollections := map[string]av.Collection{}
for _, field := range collection.GetFields() { for _, field := range collection.GetFields() {
if av.KeyTypeRollup != field.GetType() { if av.KeyTypeRollup != field.GetType() {
@ -436,7 +485,7 @@ func fillAttributeViewAutoGeneratedValues(attrView *av.AttributeView, collection
isSameAv := destAv.ID == attrView.ID isSameAv := destAv.ID == attrView.ID
var furtherCollection av.Collection var furtherCollection av.Collection
if av.KeyTypeTemplate == destKey.Type || (!isSameAv && (av.KeyTypeUpdated == destKey.Type || av.KeyTypeCreated == destKey.Type)) { if av.KeyTypeTemplate == destKey.Type || (!isSameAv && (av.KeyTypeUpdated == destKey.Type || av.KeyTypeCreated == destKey.Type || av.KeyTypeRelation == destKey.Type)) {
viewable := renderView(destAv, destAv.Views[0], "", depth, cachedAttrViews) viewable := renderView(destAv, destAv.Views[0], "", depth, cachedAttrViews)
if nil != viewable { if nil != viewable {
furtherCollection = viewable.(av.Collection) furtherCollection = viewable.(av.Collection)
@ -450,70 +499,43 @@ func fillAttributeViewAutoGeneratedValues(attrView *av.AttributeView, collection
for _, item := range collection.GetItems() { for _, item := range collection.GetItems() {
for _, value := range item.GetValues() { for _, value := range item.GetValues() {
itemID := item.GetID() if av.KeyTypeRollup != value.Type {
continue
}
switch value.Type { rollupKey, _ := attrView.GetKey(value.KeyID)
case av.KeyTypeRollup: // 渲染汇总 if nil == rollupKey || nil == rollupKey.Rollup {
rollupKey, _ := attrView.GetKey(value.KeyID) break
if nil == rollupKey || nil == rollupKey.Rollup { }
break
}
relKey, _ := attrView.GetKey(rollupKey.Rollup.RelationKeyID) relKey, _ := attrView.GetKey(rollupKey.Rollup.RelationKeyID)
if nil == relKey || nil == relKey.Relation { if nil == relKey || nil == relKey.Relation {
break break
} }
relVal := attrView.GetValue(relKey.ID, itemID) relVal := attrView.GetValue(relKey.ID, item.GetID())
if nil == relVal || nil == relVal.Relation { if nil == relVal || nil == relVal.Relation {
break break
} }
destAv := cachedAttrViews[relKey.Relation.AvID] destAv := cachedAttrViews[relKey.Relation.AvID]
if nil == destAv { if nil == destAv {
destAv, _ = av.ParseAttributeView(relKey.Relation.AvID) destAv, _ = av.ParseAttributeView(relKey.Relation.AvID)
if nil != destAv { if nil != destAv {
cachedAttrViews[relKey.Relation.AvID] = destAv cachedAttrViews[relKey.Relation.AvID] = destAv
}
}
if nil == destAv {
break
}
destKey, _ := destAv.GetKey(rollupKey.Rollup.KeyID)
if nil == destKey {
break
}
furtherCollection := rollupFurtherCollections[rollupKey.ID]
value.Rollup.BuildContents(destAv.KeyValues, destKey, relVal, rollupKey.Rollup.Calc, furtherCollection)
case av.KeyTypeRelation: // 渲染关联
value.Relation.Contents = nil
relKey, _ := attrView.GetKey(value.KeyID)
if nil != relKey && nil != relKey.Relation {
destAv := cachedAttrViews[relKey.Relation.AvID]
if nil == destAv {
destAv, _ = av.ParseAttributeView(relKey.Relation.AvID)
if nil != destAv {
cachedAttrViews[relKey.Relation.AvID] = destAv
}
}
if nil != destAv {
blocks := map[string]*av.Value{}
blockValues := destAv.GetBlockKeyValues()
if nil != blockValues {
for _, blockValue := range blockValues.Values {
blocks[blockValue.BlockID] = blockValue
}
for _, blockID := range value.Relation.BlockIDs {
if val := blocks[blockID]; nil != val {
value.Relation.Contents = append(value.Relation.Contents, val)
}
}
}
}
} }
} }
if nil == destAv {
break
}
destKey, _ := destAv.GetKey(rollupKey.Rollup.KeyID)
if nil == destKey {
break
}
furtherCollection := rollupFurtherCollections[rollupKey.ID]
value.Rollup.BuildContents(destAv.KeyValues, destKey, relVal, rollupKey.Rollup.Calc, furtherCollection)
} }
} }
} }
@ -546,7 +568,7 @@ func GetFurtherCollections(attrView *av.AttributeView, cachedAttrViews map[strin
isSameAv := destAv.ID == attrView.ID isSameAv := destAv.ID == attrView.ID
var furtherCollection av.Collection var furtherCollection av.Collection
if av.KeyTypeTemplate == destKey.Type || (!isSameAv && (av.KeyTypeUpdated == destKey.Type || av.KeyTypeCreated == destKey.Type)) { if av.KeyTypeTemplate == destKey.Type || (!isSameAv && (av.KeyTypeUpdated == destKey.Type || av.KeyTypeCreated == destKey.Type || av.KeyTypeRelation == destKey.Type)) {
viewable := RenderView(destAv, destAv.Views[0], "") viewable := RenderView(destAv, destAv.Views[0], "")
if nil != viewable { if nil != viewable {
furtherCollection = viewable.(av.Collection) furtherCollection = viewable.(av.Collection)
@ -624,6 +646,7 @@ func fillAttributeViewKeyValues(attrView *av.AttributeView, collection av.Collec
} }
} }
if !exist { if !exist {
val.IsRenderAutoFill = true
keyValues.Values = append(keyValues.Values, val) keyValues.Values = append(keyValues.Values, val)
} }
} }
@ -772,6 +795,16 @@ func removeMissingField(attrView *av.AttributeView, view *av.View, missingKeyID
} }
} }
if nil != view.Kanban {
for i, kanbanField := range view.Kanban.Fields {
if kanbanField.ID == missingKeyID {
view.Kanban.Fields = append(view.Kanban.Fields[:i], view.Kanban.Fields[i+1:]...)
changed = true
break
}
}
}
if changed { if changed {
av.SaveAttributeView(attrView) av.SaveAttributeView(attrView)
} }

View file

@ -108,7 +108,11 @@ func RenderAttributeViewGallery(attrView *av.AttributeView, view *av.View, query
} }
galleryCard.ID = cardID galleryCard.ID = cardID
fillAttributeViewBaseValue(fieldValue.BaseValue, field.ID, cardID, field.NumberFormat, field.Template) filedDateAutoFill := false
if nil != field.Date {
filedDateAutoFill = field.Date.AutoFillNow
}
fillAttributeViewBaseValue(fieldValue.BaseValue, field.ID, cardID, field.NumberFormat, field.Template, filedDateAutoFill)
galleryCard.Values = append(galleryCard.Values, fieldValue) galleryCard.Values = append(galleryCard.Values, fieldValue)
} }

219
kernel/sql/av_kanban.go Normal file
View file

@ -0,0 +1,219 @@
package sql
import (
"fmt"
"github.com/88250/lute"
"github.com/88250/lute/ast"
"github.com/88250/lute/parse"
"github.com/siyuan-note/siyuan/kernel/av"
"github.com/siyuan-note/siyuan/kernel/filesys"
"github.com/siyuan-note/siyuan/kernel/treenode"
"github.com/siyuan-note/siyuan/kernel/util"
)
func RenderAttributeViewKanban(attrView *av.AttributeView, view *av.View, query string, depth *int, cachedAttrViews map[string]*av.AttributeView) (ret *av.Kanban) {
viewable := attrView.RenderedViewables[view.ID]
if nil != viewable {
ret = viewable.(*av.Kanban)
return
}
ret = &av.Kanban{
BaseInstance: av.NewViewBaseInstance(view),
CoverFrom: view.Kanban.CoverFrom,
CoverFromAssetKeyID: view.Kanban.CoverFromAssetKeyID,
CardAspectRatio: view.Kanban.CardAspectRatio,
CardSize: view.Kanban.CardSize,
FitImage: view.Kanban.FitImage,
DisplayFieldName: view.Kanban.DisplayFieldName,
Fields: []*av.KanbanField{},
Cards: []*av.KanbanCard{},
}
// 组装字段
for _, field := range view.Kanban.Fields {
key, getErr := attrView.GetKey(field.ID)
if nil != getErr {
// 找不到字段则在视图中删除
removeMissingField(attrView, view, field.ID)
continue
}
ret.Fields = append(ret.Fields, &av.KanbanField{
BaseInstanceField: &av.BaseInstanceField{
ID: key.ID,
Name: key.Name,
Type: key.Type,
Icon: key.Icon,
Wrap: field.Wrap,
Hidden: field.Hidden,
Desc: key.Desc,
Calc: field.Calc,
Options: key.Options,
NumberFormat: key.NumberFormat,
Template: key.Template,
Relation: key.Relation,
Rollup: key.Rollup,
Date: key.Date,
},
})
}
cardsValues := generateAttrViewItems(attrView, view) // 生成卡片
filterNotFoundAttrViewItems(cardsValues) // 过滤掉不存在的卡片
// 批量加载绑定块对应的树
var ialIDs []string
for _, keyValues := range cardsValues {
for _, kValues := range keyValues {
blockVal := kValues.GetBlockValue()
if nil != blockVal && !blockVal.IsDetached {
ialIDs = append(ialIDs, blockVal.Block.ID)
}
}
}
boundTrees := filesys.LoadTrees(ialIDs)
// 生成卡片字段值
for cardID, cardValues := range cardsValues {
var kanbanCard av.KanbanCard
for _, field := range ret.Fields {
var fieldValue *av.KanbanFieldValue
for _, keyValues := range cardValues {
if keyValues.Key.ID == field.ID {
fieldValue = &av.KanbanFieldValue{
BaseValue: &av.BaseValue{
ID: keyValues.Values[0].ID,
Value: keyValues.Values[0],
ValueType: field.Type,
},
}
break
}
}
if nil == fieldValue {
fieldValue = &av.KanbanFieldValue{
BaseValue: &av.BaseValue{
ID: cardID[:14] + ast.NewNodeID()[14:],
ValueType: field.Type,
},
}
}
kanbanCard.ID = cardID
filedDateAutoFill := false
if nil != field.Date {
filedDateAutoFill = field.Date.AutoFillNow
}
fillAttributeViewBaseValue(fieldValue.BaseValue, field.ID, cardID, field.NumberFormat, field.Template, filedDateAutoFill)
kanbanCard.Values = append(kanbanCard.Values, fieldValue)
}
fillAttributeViewKanbanCardCover(attrView, view, cardValues, &kanbanCard, cardID, luteEngine, boundTrees)
ret.Cards = append(ret.Cards, &kanbanCard)
}
// 回填补全数据
fillAttributeViewKeyValues(attrView, ret)
// 批量获取块属性以提升性能
ials := BatchGetBlockAttrsWitTrees(ialIDs, boundTrees)
// 渲染自动生成的字段值,比如关联、汇总、创建时间和更新时间
fillAttributeViewAutoGeneratedValues(attrView, ret, ials, depth, cachedAttrViews)
// 最后渲染模板字段,这样模板就可以使用汇总、关联、创建时间和更新时间的值了
renderTemplateErr := fillAttributeViewTemplateValues(attrView, view, ret, ials)
if nil != renderTemplateErr {
util.PushErrMsg(fmt.Sprintf(util.Langs[util.Lang][44], util.EscapeHTML(renderTemplateErr.Error())), 30000)
}
filterByQuery(query, ret)
manualSort(view, ret)
return
}
func fillAttributeViewKanbanCardCover(attrView *av.AttributeView, view *av.View, cardValues []*av.KeyValues, kanbanCard *av.KanbanCard, cardID string, luteEngine *lute.Lute, trees map[string]*parse.Tree) {
switch view.Kanban.CoverFrom {
case av.CoverFromNone:
case av.CoverFromContentImage:
blockValue := getBlockValue(cardValues)
if blockValue.IsDetached {
break
}
tree := trees[blockValue.Block.ID]
if nil == tree {
break
}
node := treenode.GetNodeInTree(tree, blockValue.Block.ID)
if nil == node {
break
}
if ast.NodeDocument == node.Type {
if titleImg := treenode.GetDocTitleImgPath(node); "" != titleImg {
kanbanCard.CoverURL = titleImg
break
}
if titleImgCSS := node.IALAttr("title-img"); "" != titleImgCSS {
kanbanCard.CoverURL = titleImgCSS
break
}
}
ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {
if !entering {
return ast.WalkContinue
}
if ast.NodeImage != n.Type {
return ast.WalkContinue
}
dest := n.ChildByType(ast.NodeLinkDest)
if nil == dest {
return ast.WalkContinue
}
kanbanCard.CoverURL = dest.TokensStr()
return ast.WalkStop
})
if "" == kanbanCard.CoverURL {
kanbanCard.CoverContent = renderCoverContentBlock(node, luteEngine)
return
}
case av.CoverFromAssetField:
if "" == view.Kanban.CoverFromAssetKeyID {
break
}
assetValue := attrView.GetValue(view.Kanban.CoverFromAssetKeyID, cardID)
if nil == assetValue || 1 > len(assetValue.MAsset) {
break
}
p := assetValue.MAsset[0].Content
if util.IsAssetsImage(p) {
kanbanCard.CoverURL = p
}
return
case av.CoverFromContentBlock:
blockValue := getBlockValue(cardValues)
if blockValue.IsDetached {
break
}
tree := trees[blockValue.Block.ID]
if nil == tree {
break
}
node := treenode.GetNodeInTree(tree, blockValue.Block.ID)
if nil == node {
break
}
kanbanCard.CoverContent = renderCoverContentBlock(node, luteEngine)
}
}

View file

@ -96,7 +96,11 @@ func RenderAttributeViewTable(attrView *av.AttributeView, view *av.View, query s
} }
tableRow.ID = rowID tableRow.ID = rowID
fillAttributeViewBaseValue(tableCell.BaseValue, col.ID, rowID, col.NumberFormat, col.Template) filedDateAutoFill := false
if nil != col.Date {
filedDateAutoFill = col.Date.AutoFillNow
}
fillAttributeViewBaseValue(tableCell.BaseValue, col.ID, rowID, col.NumberFormat, col.Template, filedDateAutoFill)
tableRow.Cells = append(tableRow.Cells, tableCell) tableRow.Cells = append(tableRow.Cells, tableCell)
} }
ret.Rows = append(ret.Rows, &tableRow) ret.Rows = append(ret.Rows, &tableRow)

View file

@ -121,15 +121,23 @@ func parseTTCFontFamily(fontPath string) (ret []string) {
continue continue
} }
family, _ := font.Name(nil, ttc.NameIDFamily) family, _ := font.Name(nil, ttc.NameIDFull)
if "" == family {
family, _ = font.Name(nil, ttc.NameIDTypographicFamily)
}
family = strings.TrimSpace(family) family = strings.TrimSpace(family)
if "" == family || strings.HasPrefix(family, ".") { if "" != family && !strings.HasPrefix(family, ".") {
continue ret = append(ret, family)
}
family, _ = font.Name(nil, ttc.NameIDFamily)
family = strings.TrimSpace(family)
if "" != family && !strings.HasPrefix(family, ".") {
ret = append(ret, family)
}
family, _ = font.Name(nil, ttc.NameIDTypographicFamily)
family = strings.TrimSpace(family)
if "" != family && !strings.HasPrefix(family, ".") {
ret = append(ret, family)
} }
ret = append(ret, family)
} }
ret = gulu.Str.RemoveDuplicatedElem(ret) ret = gulu.Str.RemoveDuplicatedElem(ret)
return return
@ -156,29 +164,28 @@ func parseTTFFontFamily(fontPath string) (ret string) {
return return
} }
var family, subfamily string
for _, e := range t.List() { for _, e := range t.List() {
if sfnt.NameFontFamily != e.NameID && sfnt.NamePreferredFamily != e.NameID { if sfnt.NameFontFamily == e.NameID && (sfnt.PlatformLanguageID(1033) == e.LanguageID || sfnt.PlatformLanguageID(2052) == e.LanguageID) {
continue
}
if sfnt.PlatformLanguageID(1033) == e.LanguageID || sfnt.PlatformLanguageID(2052) == e.LanguageID {
v, _, err := transform.Bytes(textUnicode.UTF16(textUnicode.BigEndian, textUnicode.IgnoreBOM).NewDecoder(), e.Value) v, _, err := transform.Bytes(textUnicode.UTF16(textUnicode.BigEndian, textUnicode.IgnoreBOM).NewDecoder(), e.Value)
if err != nil { if err == nil {
return "" family = strings.TrimSpace(string(v))
} }
val := string(v) }
if sfnt.NameFontFamily == e.NameID && "" != val { if sfnt.NameFontSubfamily == e.NameID && (sfnt.PlatformLanguageID(1033) == e.LanguageID || sfnt.PlatformLanguageID(2052) == e.LanguageID) {
ret = val v, _, err := transform.Bytes(textUnicode.UTF16(textUnicode.BigEndian, textUnicode.IgnoreBOM).NewDecoder(), e.Value)
} if err == nil {
if sfnt.NamePreferredFamily == e.NameID && "" != val { subfamily = strings.TrimSpace(string(v))
ret = val
} }
} }
} }
ret = strings.TrimSpace(ret) if family != "" && !strings.HasPrefix(family, ".") {
if strings.HasPrefix(ret, ".") { if subfamily != "" && !strings.Contains(subfamily, "<") && !strings.EqualFold(subfamily, "Regular") {
return "" ret = family + " " + subfamily // 例如 "PingFang SC Bold"
} else {
ret = family
}
} }
return return
} }

View file

@ -14,9 +14,9 @@ if errorlevel 1 (
cd .. cd ..
echo 'Cleaning Builds' echo 'Cleaning Builds'
del /S /Q /F app\build 1>nul rmdir /S /Q app\build 1>nul
del /S /Q /F app\kernel 1>nul rmdir /S /Q app\kernel 1>nul
del /S /Q /F app\kernel-arm64 1>nul rmdir /S /Q app\kernel-arm64 1>nul
echo 'Building Kernel' echo 'Building Kernel'
@REM the C compiler "gcc" is necessary https://sourceforge.net/projects/mingw-w64/files/mingw-w64/ @REM the C compiler "gcc" is necessary https://sourceforge.net/projects/mingw-w64/files/mingw-w64/
@ -68,4 +68,13 @@ cd ..
echo 'Building Appx' echo 'Building Appx'
echo 'Building Appx should be disabled if you do not need it. Not configured correctly will lead to build failures' echo 'Building Appx should be disabled if you do not need it. Not configured correctly will lead to build failures'
cd . > app\build\win-unpacked\resources\ms-store cd . > app\build\win-unpacked\resources\ms-store
electron-windows-store --input-directory app\build\win-unpacked --output-directory app\build\ --package-version 1.0.0.0 --package-name SiYuan --manifest app\appx\AppxManifest.xml --assets app\appx\assets\ --make-pri true call electron-windows-store --input-directory app\build\win-unpacked --output-directory app\build\ --package-version 1.0.0.0 --package-name SiYuan --manifest app\appx\AppxManifest.xml --assets app\appx\assets\ --make-pri true
rmdir /S /Q app\build\pre-appx 1>nul
echo 'Building Appx arm64'
echo 'Building Appx arm64 should be disabled if you do not need it. Not configured correctly will lead to build failures'
cd . > app\build\win-arm64-unpacked\resources\ms-store
call electron-windows-store --input-directory app\build\win-arm64-unpacked --output-directory app\build\ --package-version 1.0.0.0 --package-name SiYuan-arm64 --manifest app\appx\AppxManifest-arm64.xml --assets app\appx\assets\ --make-pri true
rmdir /S /Q app\build\pre-appx 1>nul