A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
1import {confirmDialog} from "../dialog/confirmDialog";
2import {Plugin} from "./index";
3import {hideMessage, showMessage} from "../dialog/message";
4import {Dialog} from "../dialog";
5import {fetchGet, fetchPost, fetchSyncPost} from "../util/fetch";
6import {getBackend, getFrontend} from "../util/functions";
7/// #if !MOBILE
8import {openFile, openFileById} from "../editor/util";
9import {openNewWindow, openNewWindowById} from "../window/openNewWindow";
10import {Tab} from "../layout/Tab";
11/// #endif
12import {updateHotkeyTip} from "../protyle/util/compatibility";
13import * as platformUtils from "../protyle/util/compatibility";
14import {App} from "../index";
15import {Constants} from "../constants";
16import {Setting} from "./Setting";
17import {Menu} from "./Menu";
18import {Protyle} from "../protyle";
19import {openMobileFileById} from "../mobile/editor";
20import {lockScreen, exitSiYuan} from "../dialog/processSystem";
21import {Model} from "../layout/Model";
22import {getActiveTab, getDockByType} from "../layout/tabUtil";
23/// #if !MOBILE
24import {getAllModels} from "../layout/getAll";
25/// #endif
26import {getAllEditor} from "../layout/getAll";
27import {openSetting} from "../config";
28import {openAttr, openFileAttr} from "../menus/commonMenuItem";
29import {globalCommand} from "../boot/globalEvent/command/global";
30import {exportLayout} from "../layout/util";
31import {saveScroll} from "../protyle/scroll/saveScroll";
32import {hasClosestByClassName} from "../protyle/util/hasClosest";
33import {Files} from "../layout/dock/Files";
34
35let openTab;
36let openWindow;
37/// #if MOBILE
38openTab = () => {
39 // TODO: Mobile
40};
41openWindow = () => {
42 // TODO: Mobile
43};
44/// #else
45openWindow = (options: {
46 position?: IPosition,
47 height?: number,
48 width?: number,
49 tab?: Tab,
50 doc?: {
51 id: string, // 块 id
52 },
53}) => {
54 if (options.doc && options.doc.id) {
55 openNewWindowById(options.doc.id, {position: options.position, width: options.width, height: options.height});
56 return;
57 }
58 if (options.tab) {
59 openNewWindow(options.tab, {position: options.position, width: options.width, height: options.height});
60 return;
61 }
62};
63
64openTab = (options: {
65 app: App,
66 doc?: {
67 id: string, // 块 id
68 action?: TProtyleAction [] // cb-get-all:获取所有内容;cb-get-focus:打开后光标定位在 id 所在的块;cb-get-hl: 打开后 id 块高亮
69 zoomIn?: boolean // 是否缩放
70 },
71 pdf?: {
72 path: string,
73 page?: number, // pdf 页码
74 id?: string, // File Annotation id
75 },
76 asset?: {
77 path: string,
78 },
79 search?: Config.IUILayoutTabSearchConfig
80 card?: {
81 type: TCardType,
82 id?: string, // cardType 为 all 时不传,否则传文档或笔记本 id
83 title?: string // cardType 为 all 时不传,否则传文档或笔记本名称
84 },
85 custom?: {
86 title: string,
87 icon: string,
88 data?: any
89 id: string
90 }
91 position?: "right" | "bottom",
92 keepCursor?: boolean // 是否跳转到新 tab 上
93 removeCurrentTab?: boolean // 在当前页签打开时需移除原有页签
94 afterOpen?: (model?: Model) => void // 打开后回调
95}) => {
96 if (options.doc) {
97 if (options.doc.zoomIn) {
98 if (options.doc.action && !options.doc.action.includes(Constants.CB_GET_ALL)) {
99 options.doc.action.push(Constants.CB_GET_ALL);
100 } else {
101 options.doc.action = [Constants.CB_GET_ALL];
102 }
103 }
104 if (!options.doc.action) {
105 options.doc.action = [];
106 }
107 return openFileById({
108 app: options.app,
109 keepCursor: options.keepCursor,
110 removeCurrentTab: options.removeCurrentTab,
111 position: options.position,
112 afterOpen: options.afterOpen,
113 id: options.doc.id,
114 action: options.doc.action,
115 zoomIn: options.doc.zoomIn
116 });
117 }
118 if (options.asset) {
119 return openFile({
120 app: options.app,
121 keepCursor: options.keepCursor,
122 removeCurrentTab: options.removeCurrentTab,
123 position: options.position,
124 afterOpen: options.afterOpen,
125 assetPath: options.asset.path,
126 });
127 }
128 if (options.pdf) {
129 return openFile({
130 app: options.app,
131 keepCursor: options.keepCursor,
132 removeCurrentTab: options.removeCurrentTab,
133 position: options.position,
134 afterOpen: options.afterOpen,
135 assetPath: options.pdf.path,
136 page: options.pdf.id || options.pdf.page,
137 });
138 }
139 if (options.search) {
140 if (!options.search.idPath) {
141 options.search.idPath = [];
142 }
143 if (!options.search.hPath) {
144 options.search.hPath = "";
145 }
146 return openFile({
147 app: options.app,
148 keepCursor: options.keepCursor,
149 removeCurrentTab: options.removeCurrentTab,
150 position: options.position,
151 afterOpen: options.afterOpen,
152 searchData: options.search,
153 });
154 }
155 if (options.card) {
156 return openFile({
157 app: options.app,
158 keepCursor: options.keepCursor,
159 removeCurrentTab: options.removeCurrentTab,
160 position: options.position,
161 afterOpen: options.afterOpen,
162 custom: {
163 icon: "iconRiffCard",
164 title: window.siyuan.languages.spaceRepetition,
165 data: {
166 cardType: options.card.type,
167 id: options.card.id || "",
168 title: options.card.title,
169 },
170 id: "siyuan-card"
171 },
172 });
173 }
174 if (options.custom) {
175 return openFile(options);
176 }
177
178};
179/// #endif
180
181const getModelByDockType = (type: TDock | string) => {
182 /// #if MOBILE
183 return window.siyuan.mobile.docks[type];
184 /// #else
185 return getDockByType(type).data[type];
186 /// #endif
187};
188
189const openAttributePanel = (options: {
190 data?: IObject // 块属性值
191 nodeElement?: HTMLElement, // 块元素
192 focusName: "bookmark" | "name" | "alias" | "memo" | "av" | "custom", // av 为数据库页签,custom 为自定义页签,其余为内置输入框
193 protyle?: IProtyle, // 有数据库时需要传入 protyle
194}) => {
195 if (options.data) {
196 openFileAttr(options.data, options.focusName, options.protyle);
197 } else {
198 openAttr(options.nodeElement, options.focusName, options.protyle);
199 }
200};
201
202const saveLayout = (cb: () => void) => {
203 /// #if MOBILE
204 if (window.siyuan.mobile.editor) {
205 const result = saveScroll(window.siyuan.mobile.editor.protyle);
206 if (cb && result instanceof Promise) {
207 result.then(() => {
208 cb();
209 });
210 }
211 }
212 /// #else
213 exportLayout({cb, errorExit: false});
214 /// #endif
215};
216
217const getActiveEditor = (wndActive = true) => {
218 let editor;
219 /// #if !MOBILE
220 const range = getSelection().rangeCount > 0 ? getSelection().getRangeAt(0) : null;
221 const allEditor = getAllEditor();
222 if (range) {
223 editor = allEditor.find(item => {
224 if (item.protyle.element.contains(range.startContainer)) {
225 return true;
226 }
227 });
228 }
229 if (!editor) {
230 editor = allEditor.find(item => {
231 if (hasClosestByClassName(item.protyle.element, "layout__wnd--active", true)) {
232 return true;
233 }
234 });
235 }
236 if (!editor && !wndActive) {
237 let activeTime = 0;
238 allEditor.forEach(item => {
239 let headerElement = item.protyle.model?.parent.headElement;
240 if (!headerElement && item.protyle.element.getBoundingClientRect().height > 0) {
241 const tabBodyElement = item.protyle.element.closest(".fn__flex-1[data-id]");
242 if (tabBodyElement) {
243 headerElement = document.querySelector(`.layout-tab-bar .item[data-id="${tabBodyElement.getAttribute("data-id")}"]`);
244 }
245 }
246 if (headerElement) {
247 if (headerElement.classList.contains("item--focus") && parseInt(headerElement.dataset.activetime) > activeTime) {
248 activeTime = parseInt(headerElement.dataset.activetime);
249 editor = item;
250 }
251 } else if (item.protyle.element.getBoundingClientRect().height > 0) {
252 editor = item;
253 }
254 });
255 }
256 /// #else
257 editor = window.siyuan.mobile.popEditor || window.siyuan.mobile.editor;
258 if (editor?.protyle.element.classList.contains("fn__none")) {
259 return undefined;
260 }
261 /// #endif
262 return editor;
263};
264
265export const expandDocTree = async (options: {
266 id: string,
267 isSetCurrent?: boolean
268}) => {
269 let isNotebook = false;
270 window.siyuan.notebooks.find(item => {
271 if (options.id === item.id) {
272 isNotebook = true;
273 return true;
274 }
275 });
276 let liElement: HTMLElement;
277 let notebookId = options.id;
278 const file = getModelByDockType("file") as Files;
279 if (typeof options.isSetCurrent === "undefined") {
280 options.isSetCurrent = true;
281 }
282 if (isNotebook) {
283 liElement = file.element.querySelector(`.b3-list[data-url="${options.id}"]`)?.firstElementChild as HTMLElement;
284 } else {
285 const response = await fetchSyncPost("api/block/getBlockInfo", {id: options.id});
286 if (response.code === -1) {
287 return;
288 }
289 notebookId = response.data.box;
290 liElement = await file.selectItem(response.data.box, response.data.path, undefined, undefined, options.isSetCurrent);
291 }
292 if (!liElement) {
293 return;
294 }
295 if (options.isSetCurrent || typeof options.isSetCurrent === "undefined") {
296 file.setCurrent(liElement);
297 }
298 const toggleElement = liElement.querySelector(".b3-list-item__arrow");
299 if (toggleElement.classList.contains("b3-list-item__arrow--open")) {
300 return;
301 }
302 file.getLeaf(liElement, notebookId);
303};
304
305export const API = {
306 adaptHotkey: updateHotkeyTip,
307 confirm: confirmDialog,
308 Constants,
309 showMessage,
310 hideMessage,
311 fetchPost,
312 fetchSyncPost,
313 fetchGet,
314 getFrontend,
315 getBackend,
316 getModelByDockType,
317 openTab,
318 openWindow,
319 openMobileFileById,
320 lockScreen,
321 exitSiYuan,
322 Protyle,
323 Plugin,
324 Dialog,
325 Menu,
326 Setting,
327 getAllEditor,
328 /// #if !MOBILE
329 getActiveTab,
330 getAllModels,
331 /// #endif
332 getActiveEditor,
333 platformUtils,
334 openSetting,
335 openAttributePanel,
336 saveLayout,
337 globalCommand,
338 expandDocTree
339};