A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
1/// #if !BROWSER
2import {shell} from "electron";
3/// #endif
4import {confirmDialog} from "../dialog/confirmDialog";
5import {getSearch, isMobile, isValidAttrName} from "../util/functions";
6import {isLocalPath, movePathTo, moveToPath, pathPosix} from "../util/pathName";
7import {MenuItem} from "./Menu";
8import {onExport, saveExport} from "../protyle/export";
9import {isInAndroid, isInHarmony, openByMobile, writeText} from "../protyle/util/compatibility";
10import {fetchPost, fetchSyncPost} from "../util/fetch";
11import {hideMessage, showMessage} from "../dialog/message";
12import {Dialog} from "../dialog";
13import {focusBlock, focusByRange, getEditorRange} from "../protyle/util/selection";
14/// #if !MOBILE
15import {openAsset, openBy} from "../editor/util";
16/// #endif
17import {rename, replaceFileName} from "../editor/rename";
18import * as dayjs from "dayjs";
19import {Constants} from "../constants";
20import {exportImage} from "../protyle/export/util";
21import {App} from "../index";
22import {renderAVAttribute} from "../protyle/render/av/blockAttr";
23import {openAssetNewWindow} from "../window/openNewWindow";
24import {escapeHtml} from "../util/escape";
25import {copyTextByType} from "../protyle/toolbar/util";
26import {hideElements} from "../protyle/ui/hideElements";
27import {Protyle} from "../protyle";
28import {getAllEditor} from "../layout/getAll";
29
30const bindAttrInput = (inputElement: HTMLInputElement, id: string) => {
31 inputElement.addEventListener("change", () => {
32 fetchPost("/api/attr/setBlockAttrs", {
33 id,
34 attrs: {[inputElement.dataset.name]: inputElement.value}
35 });
36 });
37};
38
39export const openWechatNotify = (nodeElement: Element) => {
40 const id = nodeElement.getAttribute("data-node-id");
41 const range = getEditorRange(nodeElement);
42 const reminder = nodeElement.getAttribute(Constants.CUSTOM_REMINDER_WECHAT);
43 let reminderFormat = "";
44 if (reminder) {
45 reminderFormat = dayjs(reminder).format("YYYY-MM-DD HH:mm");
46 }
47 const dialog = new Dialog({
48 width: isMobile() ? "92vw" : "50vw",
49 title: window.siyuan.languages.wechatReminder,
50 content: `<div class="b3-dialog__content custom-attr">
51 <div class="fn__flex">
52 <span class="ft__on-surface fn__flex-center" style="text-align: right;white-space: nowrap;width: 100px">${window.siyuan.languages.notifyTime}</span>
53 <div class="fn__space"></div>
54 <input class="b3-text-field fn__flex-1" type="datetime-local" max="9999-12-31 23:59" value="${reminderFormat}">
55 </div>
56 <div class="b3-label__text" style="text-align: center">${window.siyuan.languages.wechatTip}</div>
57</div>
58<div class="b3-dialog__action">
59 <button class="b3-button b3-button--cancel">${window.siyuan.languages.cancel}</button><div class="fn__space"></div>
60 <button class="b3-button b3-button--text">${window.siyuan.languages.remove}</button><div class="fn__space"></div>
61 <button class="b3-button b3-button--text">${window.siyuan.languages.confirm}</button>
62</div>`,
63 destroyCallback() {
64 focusByRange(range);
65 }
66 });
67 dialog.element.setAttribute("data-key", Constants.DIALOG_WECHATREMINDER);
68 const btnsElement = dialog.element.querySelectorAll(".b3-button");
69 btnsElement[0].addEventListener("click", () => {
70 dialog.destroy();
71 });
72 btnsElement[1].addEventListener("click", () => {
73 if (btnsElement[1].getAttribute("disabled")) {
74 return;
75 }
76 btnsElement[1].setAttribute("disabled", "disabled");
77 fetchPost("/api/block/setBlockReminder", {id, timed: "0"}, () => {
78 nodeElement.removeAttribute(Constants.CUSTOM_REMINDER_WECHAT);
79 dialog.destroy();
80 });
81 });
82 btnsElement[2].addEventListener("click", () => {
83 const date = dialog.element.querySelector("input").value;
84 if (date) {
85 if (new Date(date) <= new Date()) {
86 showMessage(window.siyuan.languages.reminderTip);
87 return;
88 }
89 if (btnsElement[2].getAttribute("disabled")) {
90 return;
91 }
92 btnsElement[2].setAttribute("disabled", "disabled");
93 const timed = dayjs(date).format("YYYYMMDDHHmmss");
94 fetchPost("/api/block/setBlockReminder", {id, timed}, () => {
95 nodeElement.setAttribute(Constants.CUSTOM_REMINDER_WECHAT, timed);
96 dialog.destroy();
97 });
98 } else {
99 showMessage(window.siyuan.languages.notEmpty);
100 }
101 });
102};
103
104export const openFileWechatNotify = (protyle: IProtyle) => {
105 fetchPost("/api/block/getDocInfo", {
106 id: protyle.block.rootID
107 }, (response) => {
108 const reminder = response.data.ial[Constants.CUSTOM_REMINDER_WECHAT];
109 let reminderFormat = "";
110 if (reminder) {
111 reminderFormat = dayjs(reminder).format("YYYY-MM-DD HH:mm");
112 }
113 const dialog = new Dialog({
114 width: isMobile() ? "92vw" : "50vw",
115 title: window.siyuan.languages.wechatReminder,
116 content: `<div class="b3-dialog__content custom-attr">
117 <div class="fn__flex">
118 <span class="ft__on-surface fn__flex-center" style="text-align: right;white-space: nowrap;width: 100px">${window.siyuan.languages.notifyTime}</span>
119 <div class="fn__space"></div>
120 <input class="b3-text-field fn__flex-1" type="datetime-local" max="9999-12-31 23:59" value="${reminderFormat}">
121 </div>
122 <div class="b3-label__text" style="text-align: center">${window.siyuan.languages.wechatTip}</div>
123</div>
124<div class="b3-dialog__action">
125 <button class="b3-button b3-button--cancel">${window.siyuan.languages.cancel}</button><div class="fn__space"></div>
126 <button class="b3-button b3-button--text">${window.siyuan.languages.remove}</button><div class="fn__space"></div>
127 <button class="b3-button b3-button--text">${window.siyuan.languages.confirm}</button>
128</div>`
129 });
130 dialog.element.setAttribute("data-key", Constants.DIALOG_WECHATREMINDER);
131 const btnsElement = dialog.element.querySelectorAll(".b3-button");
132 btnsElement[0].addEventListener("click", () => {
133 dialog.destroy();
134 });
135 btnsElement[1].addEventListener("click", () => {
136 fetchPost("/api/block/setBlockReminder", {id: protyle.block.rootID, timed: "0"}, () => {
137 dialog.destroy();
138 });
139 });
140 btnsElement[2].addEventListener("click", () => {
141 const date = dialog.element.querySelector("input").value;
142 if (date) {
143 if (new Date(date) <= new Date()) {
144 showMessage(window.siyuan.languages.reminderTip);
145 return;
146 }
147 fetchPost("/api/block/setBlockReminder", {
148 id: protyle.block.rootID,
149 timed: dayjs(date).format("YYYYMMDDHHmmss")
150 }, () => {
151 dialog.destroy();
152 });
153 } else {
154 showMessage(window.siyuan.languages.notEmpty);
155 }
156 });
157 });
158};
159
160export const openFileAttr = (attrs: IObject, focusName = "bookmark", protyle?: IProtyle) => {
161 let customHTML = "";
162 let notifyHTML = "";
163 let hasAV = false;
164 const range = getSelection().rangeCount > 0 ? getSelection().getRangeAt(0) : null;
165 let ghostProtyle: Protyle;
166 if (!protyle) {
167 getAllEditor().find(item => {
168 if (attrs.id === item.protyle.block.rootID) {
169 protyle = item.protyle;
170 return true;
171 }
172 });
173 if (!protyle) {
174 ghostProtyle = new Protyle(window.siyuan.ws.app, document.createElement("div"), {
175 blockId: attrs.id,
176 });
177 }
178 }
179 Object.keys(attrs).forEach(item => {
180 if (Constants.CUSTOM_RIFF_DECKS === item || item.startsWith("custom-sy-")) {
181 return;
182 }
183 if (item === Constants.CUSTOM_REMINDER_WECHAT) {
184 notifyHTML = `<label class="b3-label b3-label--noborder">
185 ${window.siyuan.languages.wechatReminder}
186 <div class="fn__hr"></div>
187 <input class="b3-text-field fn__block" type="datetime-local" max="9999-12-31 23:59" readonly data-name="${item}" value="${dayjs(attrs[item]).format("YYYY-MM-DD HH:mm")}">
188</label>`;
189 } else if (item.indexOf("custom-av") > -1) {
190 hasAV = true;
191 } else if (item.indexOf("custom") > -1) {
192 customHTML += `<label class="b3-label b3-label--noborder">
193 <div class="fn__flex">
194 <span class="fn__flex-1">${item.replace("custom-", "")}</span>
195 <span data-action="remove" class="block__icon block__icon--show"><svg><use xlink:href="#iconMin"></use></svg></span>
196 </div>
197 <div class="fn__hr"></div>
198 <textarea style="resize: vertical;" spellcheck="false" class="b3-text-field fn__block" rows="1" data-name="${item}">${attrs[item]}</textarea>
199</label>`;
200 }
201 });
202 const dialog = new Dialog({
203 width: isMobile() ? "92vw" : "50vw",
204 containerClassName: "b3-dialog__container--theme",
205 height: "80vh",
206 content: `<div class="fn__flex-column">
207 <div class="layout-tab-bar fn__flex" style="flex-shrink:0;border-radius: var(--b3-border-radius-b) var(--b3-border-radius-b) 0 0">
208 <div class="item item--full item--focus" data-type="attr">
209 <span class="fn__flex-1"></span>
210 <span class="item__text">${window.siyuan.languages.builtIn}</span>
211 <span class="fn__flex-1"></span>
212 </div>
213 <div class="item item--full${hasAV ? "" : " fn__none"}" data-type="NodeAttributeView">
214 <span class="fn__flex-1"></span>
215 <span class="item__text">${window.siyuan.languages.database}</span>
216 <span class="fn__flex-1"></span>
217 </div>
218 <div class="item item--full" data-type="custom">
219 <span class="fn__flex-1"></span>
220 <span class="item__text">${window.siyuan.languages.custom}</span>
221 <span class="fn__flex-1"></span>
222 </div>
223 </div>
224 <div class="fn__flex-1">
225 <div class="custom-attr" data-type="attr">
226 <label class="b3-label b3-label--noborder">
227 <div class="fn__flex">
228 <span class="fn__flex-1">${window.siyuan.languages.bookmark}</span>
229 <span data-action="bookmark" class="block__icon block__icon--show"><svg><use xlink:href="#iconDown"></use></svg></span>
230 </div>
231 <div class="fn__hr"></div>
232 <input spellcheck="${window.siyuan.config.editor.spellcheck}" class="b3-text-field fn__block" placeholder="${window.siyuan.languages.attrBookmarkTip}" data-name="bookmark">
233 </label>
234 <label class="b3-label b3-label--noborder">
235 ${window.siyuan.languages.name}
236 <div class="fn__hr"></div>
237 <input spellcheck="${window.siyuan.config.editor.spellcheck}" class="b3-text-field fn__block" placeholder="${window.siyuan.languages.attrNameTip}" data-name="name">
238 </label>
239 <label class="b3-label b3-label--noborder">
240 ${window.siyuan.languages.alias}
241 <div class="fn__hr"></div>
242 <input spellcheck="${window.siyuan.config.editor.spellcheck}" class="b3-text-field fn__block" placeholder="${window.siyuan.languages.attrAliasTip}" data-name="alias">
243 </label>
244 <label class="b3-label b3-label--noborder">
245 ${window.siyuan.languages.memo}
246 <div class="fn__hr"></div>
247 <textarea style="resize: vertical" spellcheck="${window.siyuan.config.editor.spellcheck}" class="b3-text-field fn__block" placeholder="${window.siyuan.languages.attrMemoTip}" rows="2" data-name="memo">${attrs.memo || ""}</textarea>
248 </label>
249 ${notifyHTML}
250 </div>
251 <div data-type="NodeAttributeView" class="fn__none custom-attr"></div>
252 <div data-type="custom" class="fn__none custom-attr">
253 ${customHTML}
254 <div class="b3-label">
255 <button data-action="addCustom" class="b3-button b3-button--cancel">
256 <svg><use xlink:href="#iconAdd"></use></svg>${window.siyuan.languages.addAttr}
257 </button>
258 </div>
259 </div>
260 </div>
261</div>`,
262 destroyCallback() {
263 focusByRange(range);
264 if (protyle) {
265 hideElements(["select"], protyle);
266 } else {
267 ghostProtyle.destroy();
268 }
269 }
270 });
271 dialog.element.setAttribute("data-key", Constants.DIALOG_ATTR);
272 (dialog.element.querySelector('.b3-text-field[data-name="bookmark"]') as HTMLInputElement).value = attrs.bookmark || "";
273 (dialog.element.querySelector('.b3-text-field[data-name="name"]') as HTMLInputElement).value = attrs.name || "";
274 (dialog.element.querySelector('.b3-text-field[data-name="alias"]') as HTMLInputElement).value = attrs.alias || "";
275 dialog.element.addEventListener("click", (event) => {
276 let target = event.target as HTMLElement;
277 if (typeof event.detail === "string") {
278 target = dialog.element.querySelector(`.item--full[data-type="${event.detail}"]`);
279 }
280 while (target !== dialog.element) {
281 const type = target.dataset.action;
282 if (target.classList.contains("item--full")) {
283 target.parentElement.querySelector(".item--focus").classList.remove("item--focus");
284 target.classList.add("item--focus");
285 dialog.element.querySelectorAll(".custom-attr").forEach((item: HTMLElement) => {
286 if (item.dataset.type === target.dataset.type) {
287 if (item.dataset.type === "NodeAttributeView" && item.innerHTML === "") {
288 renderAVAttribute(item, attrs.id, protyle || ghostProtyle.protyle);
289 }
290 item.classList.remove("fn__none");
291 } else {
292 item.classList.add("fn__none");
293 }
294 });
295 } else if (type === "remove") {
296 fetchPost("/api/attr/setBlockAttrs", {
297 id: attrs.id,
298 attrs: {["custom-" + target.previousElementSibling.textContent]: ""}
299 });
300 target.parentElement.parentElement.remove();
301 event.stopPropagation();
302 event.preventDefault();
303 break;
304 } else if (type === "bookmark") {
305 fetchPost("/api/attr/getBookmarkLabels", {}, (response) => {
306 window.siyuan.menus.menu.remove();
307 if (response.data.length === 0) {
308 window.siyuan.menus.menu.append(new MenuItem({
309 id: "emptyContent",
310 iconHTML: "",
311 label: window.siyuan.languages.emptyContent,
312 type: "readonly",
313 }).element);
314 } else {
315 response.data.forEach((item: string) => {
316 window.siyuan.menus.menu.append(new MenuItem({
317 label: item,
318 click() {
319 const bookmarkInputElement = target.parentElement.parentElement.querySelector("input");
320 bookmarkInputElement.value = item;
321 bookmarkInputElement.dispatchEvent(new CustomEvent("change"));
322 }
323 }).element);
324 });
325 }
326 window.siyuan.menus.menu.element.classList.add("b3-menu--list");
327 window.siyuan.menus.menu.popup({x: event.clientX, y: event.clientY + 16, w: 16});
328 });
329 event.stopPropagation();
330 event.preventDefault();
331 break;
332 } else if (type === "addCustom") {
333 const addDialog = new Dialog({
334 title: window.siyuan.languages.attrName,
335 content: `<div class="b3-dialog__content"><input spellcheck="false" class="b3-text-field fn__block" value=""></div>
336<div class="b3-dialog__action">
337 <button class="b3-button b3-button--cancel">${window.siyuan.languages.cancel}</button><div class="fn__space"></div>
338 <button class="b3-button b3-button--text">${window.siyuan.languages.confirm}</button>
339</div>`,
340 width: isMobile() ? "92vw" : "520px",
341 });
342 addDialog.element.setAttribute("data-key", Constants.DIALOG_SETCUSTOMATTR);
343 const inputElement = addDialog.element.querySelector("input") as HTMLInputElement;
344 const btnsElement = addDialog.element.querySelectorAll(".b3-button");
345 addDialog.bindInput(inputElement, () => {
346 (btnsElement[1] as HTMLButtonElement).click();
347 });
348 inputElement.focus();
349 inputElement.select();
350 btnsElement[0].addEventListener("click", () => {
351 addDialog.destroy();
352 });
353 btnsElement[1].addEventListener("click", () => {
354 if (!isValidAttrName(inputElement.value)) {
355 showMessage(window.siyuan.languages.attrName + " <b>" + escapeHtml(inputElement.value) + "</b> " + window.siyuan.languages.invalid);
356 return false;
357 }
358 target.parentElement.insertAdjacentHTML("beforebegin", `<div class="b3-label b3-label--noborder">
359 <div class="fn__flex">
360 <span class="fn__flex-1">${inputElement.value}</span>
361 <span data-action="remove" class="block__icon block__icon--show"><svg><use xlink:href="#iconMin"></use></svg></span>
362 </div>
363 <div class="fn__hr"></div>
364 <textarea style="resize: vertical" spellcheck="false" data-name="custom-${inputElement.value}" class="b3-text-field fn__block" rows="1" placeholder="${window.siyuan.languages.attrValue1}"></textarea>
365</div>`);
366 const valueElement = target.parentElement.previousElementSibling.querySelector(".b3-text-field") as HTMLInputElement;
367 valueElement.focus();
368 bindAttrInput(valueElement, attrs.id);
369 addDialog.destroy();
370 });
371 event.stopPropagation();
372 event.preventDefault();
373 break;
374 }
375 target = target.parentElement;
376 }
377 });
378 dialog.element.querySelectorAll(".b3-text-field").forEach((item: HTMLInputElement) => {
379 if (focusName !== "av" && focusName !== "custom" && focusName === item.getAttribute("data-name")) {
380 item.focus();
381 }
382 bindAttrInput(item, attrs.id);
383 });
384 if (focusName === "av") {
385 dialog.element.dispatchEvent(new CustomEvent("click", {detail: "NodeAttributeView"}));
386 } else if (focusName === "custom") {
387 dialog.element.dispatchEvent(new CustomEvent("click", {detail: "custom"}));
388 }
389};
390
391export const openAttr = (nodeElement: Element, focusName = "bookmark", protyle: IProtyle) => {
392 if (nodeElement.getAttribute("data-type") === "NodeThematicBreak") {
393 return;
394 }
395 const id = nodeElement.getAttribute("data-node-id");
396 fetchPost("/api/attr/getBlockAttrs", {id}, (response) => {
397 openFileAttr(response.data, focusName, protyle);
398 });
399};
400
401export const copySubMenu = (ids: string[], accelerator = true, focusElement?: Element, stdMarkdownId?: string) => {
402 const menuItems = [{
403 id: "copyBlockRef",
404 iconHTML: "",
405 accelerator: accelerator ? window.siyuan.config.keymap.editor.general.copyBlockRef.custom : undefined,
406 label: window.siyuan.languages.copyBlockRef,
407 click: () => {
408 copyTextByType(ids, "ref");
409 if (focusElement) {
410 focusBlock(focusElement);
411 }
412 }
413 }, {
414 id: "copyBlockEmbed",
415 iconHTML: "",
416 label: window.siyuan.languages.copyBlockEmbed,
417 accelerator: accelerator ? window.siyuan.config.keymap.editor.general.copyBlockEmbed.custom : undefined,
418 click: () => {
419 copyTextByType(ids, "blockEmbed");
420 if (focusElement) {
421 focusBlock(focusElement);
422 }
423 }
424 }, {
425 id: "copyProtocol",
426 iconHTML: "",
427 label: window.siyuan.languages.copyProtocol,
428 accelerator: accelerator ? window.siyuan.config.keymap.editor.general.copyProtocol.custom : undefined,
429 click: () => {
430 copyTextByType(ids, "protocol");
431 if (focusElement) {
432 focusBlock(focusElement);
433 }
434 }
435 }, {
436 id: "copyProtocolInMd",
437 iconHTML: "",
438 label: window.siyuan.languages.copyProtocolInMd,
439 accelerator: accelerator ? window.siyuan.config.keymap.editor.general.copyProtocolInMd.custom : undefined,
440 click: () => {
441 copyTextByType(ids, "protocolMd");
442 if (focusElement) {
443 focusBlock(focusElement);
444 }
445 }
446 }, {
447 id: "copyHPath",
448 iconHTML: "",
449 label: window.siyuan.languages.copyHPath,
450 accelerator: accelerator ? window.siyuan.config.keymap.editor.general.copyHPath.custom : undefined,
451 click: () => {
452 copyTextByType(ids, "hPath");
453 if (focusElement) {
454 focusBlock(focusElement);
455 }
456 }
457 }, {
458 id: "copyID",
459 iconHTML: "",
460 label: window.siyuan.languages.copyID,
461 accelerator: accelerator ? window.siyuan.config.keymap.editor.general.copyID.custom : undefined,
462 click: () => {
463 copyTextByType(ids, "id");
464 if (focusElement) {
465 focusBlock(focusElement);
466 }
467 }
468 }];
469
470 if (stdMarkdownId) {
471 menuItems.push({
472 id: "copyMarkdown",
473 iconHTML: "",
474 label: window.siyuan.languages.copyMarkdown,
475 accelerator: undefined,
476 click: async () => {
477 const response = await fetchSyncPost("/api/export/exportMdContent", {
478 id: stdMarkdownId,
479 refMode: 3,
480 embedMode: 1,
481 yfm: false,
482 fillCSSVar: false,
483 adjustHeadingLevel: false
484 });
485 const text = response.data.content;
486 writeText(text);
487 if (focusElement) {
488 focusBlock(focusElement);
489 }
490 }
491 });
492 }
493
494 return menuItems;
495};
496
497export const exportMd = (id: string) => {
498 if (window.siyuan.isPublish) {
499 return;
500 }
501 return new MenuItem({
502 id: "export",
503 label: window.siyuan.languages.export,
504 type: "submenu",
505 icon: "iconUpload",
506 submenu: [{
507 id: "exportTemplate",
508 label: window.siyuan.languages.template,
509 iconClass: "ft__error",
510 icon: "iconMarkdown",
511 click: async () => {
512 const result = await fetchSyncPost("/api/block/getRefText", {id: id});
513
514 const dialog = new Dialog({
515 title: window.siyuan.languages.fileName,
516 content: `<div class="b3-dialog__content"><input class="b3-text-field fn__block" value=""></div>
517<div class="b3-dialog__action">
518 <button class="b3-button b3-button--cancel">${window.siyuan.languages.cancel}</button><div class="fn__space"></div>
519 <button class="b3-button b3-button--text">${window.siyuan.languages.confirm}</button>
520</div>`,
521 width: isMobile() ? "92vw" : "520px",
522 });
523 dialog.element.setAttribute("data-key", Constants.DIALOG_EXPORTTEMPLATE);
524 const inputElement = dialog.element.querySelector("input") as HTMLInputElement;
525 const btnsElement = dialog.element.querySelectorAll(".b3-button");
526 dialog.bindInput(inputElement, () => {
527 (btnsElement[1] as HTMLButtonElement).click();
528 });
529 let name = replaceFileName(result.data);
530 const maxNameLen = 32;
531 if (name.length > maxNameLen) {
532 name = name.substring(0, maxNameLen);
533 }
534 inputElement.value = name;
535 inputElement.focus();
536 inputElement.select();
537 btnsElement[0].addEventListener("click", () => {
538 dialog.destroy();
539 });
540 btnsElement[1].addEventListener("click", () => {
541 if (inputElement.value.trim() === "") {
542 inputElement.value = window.siyuan.languages.untitled;
543 } else {
544 inputElement.value = replaceFileName(inputElement.value);
545 }
546
547 if (name.length > maxNameLen) {
548 name = name.substring(0, maxNameLen);
549 }
550
551 fetchPost("/api/template/docSaveAsTemplate", {
552 id,
553 name: inputElement.value,
554 overwrite: false
555 }, response => {
556 if (response.code === 1) {
557 // 重名
558 confirmDialog(window.siyuan.languages.export, window.siyuan.languages.exportTplTip, () => {
559 fetchPost("/api/template/docSaveAsTemplate", {
560 id,
561 name: inputElement.value,
562 overwrite: true
563 }, resp => {
564 if (resp.code === 0) {
565 showMessage(window.siyuan.languages.exportTplSucc);
566 }
567 });
568 });
569 return;
570 }
571 showMessage(window.siyuan.languages.exportTplSucc);
572 });
573 dialog.destroy();
574 });
575 }
576 }, {
577 id: "exportSiYuanZip",
578 label: "SiYuan .sy.zip",
579 icon: "iconSiYuan",
580 click: () => {
581 const msgId = showMessage(window.siyuan.languages.exporting, -1);
582 fetchPost("/api/export/exportSY", {
583 id,
584 }, response => {
585 hideMessage(msgId);
586 openByMobile(response.data.zip);
587 });
588 }
589 }, {
590 id: "exportMarkdown",
591 label: "Markdown .zip",
592 icon: "iconMarkdown",
593 click: () => {
594 const msgId = showMessage(window.siyuan.languages.exporting, -1);
595 fetchPost("/api/export/exportMd", {
596 id,
597 }, response => {
598 hideMessage(msgId);
599 openByMobile(response.data.zip);
600 });
601 }
602 }, {
603 id: "exportImage",
604 label: window.siyuan.languages.image,
605 icon: "iconImage",
606 click: () => {
607 exportImage(id);
608 }
609 },
610 /// #if !BROWSER
611 {
612 id: "exportPDF",
613 label: "PDF",
614 icon: "iconPDF",
615 click: () => {
616 saveExport({type: "pdf", id});
617 }
618 }, {
619 id: "exportHTML_SiYuan",
620 label: "HTML (SiYuan)",
621 iconClass: "ft__error",
622 icon: "iconHTML5",
623 click: () => {
624 saveExport({type: "html", id});
625 }
626 }, {
627 id: "exportHTML_Markdown",
628 label: "HTML (Markdown)",
629 icon: "iconHTML5",
630 click: () => {
631 saveExport({type: "htmlmd", id});
632 }
633 }, {
634 id: "exportWord",
635 label: "Word .docx",
636 icon: "iconExact",
637 click: () => {
638 saveExport({type: "word", id});
639 }
640 }, {
641 id: "exportMore",
642 label: window.siyuan.languages.more,
643 icon: "iconMore",
644 type: "submenu",
645 submenu: [{
646 id: "exportReStructuredText",
647 label: "reStructuredText",
648 click: () => {
649 const msgId = showMessage(window.siyuan.languages.exporting, -1);
650 fetchPost("/api/export/exportReStructuredText", {
651 id,
652 }, response => {
653 hideMessage(msgId);
654 openByMobile(response.data.zip);
655 });
656 }
657 }, {
658 id: "exportAsciiDoc",
659 label: "AsciiDoc",
660 click: () => {
661 const msgId = showMessage(window.siyuan.languages.exporting, -1);
662 fetchPost("/api/export/exportAsciiDoc", {
663 id,
664 }, response => {
665 hideMessage(msgId);
666 openByMobile(response.data.zip);
667 });
668 }
669 }, {
670 id: "exportTextile",
671 label: "Textile",
672 click: () => {
673 const msgId = showMessage(window.siyuan.languages.exporting, -1);
674 fetchPost("/api/export/exportTextile", {
675 id,
676 }, response => {
677 hideMessage(msgId);
678 openByMobile(response.data.zip);
679 });
680 }
681 }, {
682 id: "exportOPML",
683 label: "OPML",
684 click: () => {
685 const msgId = showMessage(window.siyuan.languages.exporting, -1);
686 fetchPost("/api/export/exportOPML", {
687 id,
688 }, response => {
689 hideMessage(msgId);
690 openByMobile(response.data.zip);
691 });
692 }
693 }, {
694 id: "exportOrgMode",
695 label: "Org-Mode",
696 click: () => {
697 const msgId = showMessage(window.siyuan.languages.exporting, -1);
698 fetchPost("/api/export/exportOrgMode", {
699 id,
700 }, response => {
701 hideMessage(msgId);
702 openByMobile(response.data.zip);
703 });
704 }
705 }, {
706 id: "exportMediaWiki",
707 label: "MediaWiki",
708 click: () => {
709 const msgId = showMessage(window.siyuan.languages.exporting, -1);
710 fetchPost("/api/export/exportMediaWiki", {
711 id,
712 }, response => {
713 hideMessage(msgId);
714 openByMobile(response.data.zip);
715 });
716 }
717 }, {
718 id: "exportODT",
719 label: "ODT",
720 click: () => {
721 const msgId = showMessage(window.siyuan.languages.exporting, -1);
722 fetchPost("/api/export/exportODT", {
723 id,
724 }, response => {
725 hideMessage(msgId);
726 openByMobile(response.data.zip);
727 });
728 }
729 }, {
730 id: "exportRTF",
731 label: "RTF",
732 click: () => {
733 const msgId = showMessage(window.siyuan.languages.exporting, -1);
734 fetchPost("/api/export/exportRTF", {
735 id,
736 }, response => {
737 hideMessage(msgId);
738 openByMobile(response.data.zip);
739 });
740 }
741 }, {
742 id: "exportEPUB",
743 label: "EPUB",
744 click: () => {
745 const msgId = showMessage(window.siyuan.languages.exporting, -1);
746 fetchPost("/api/export/exportEPUB", {
747 id,
748 }, response => {
749 hideMessage(msgId);
750 openByMobile(response.data.zip);
751 });
752 }
753 },
754 ]
755 },
756 /// #else
757 {
758 id: "exportPDF",
759 label: window.siyuan.languages.print,
760 icon: "iconPDF",
761 ignore: !isInAndroid() && !isInHarmony(),
762 click: () => {
763 const msgId = showMessage(window.siyuan.languages.exporting);
764 const localData = window.siyuan.storage[Constants.LOCAL_EXPORTPDF];
765 fetchPost("/api/export/exportPreviewHTML", {
766 id,
767 keepFold: localData.keepFold,
768 merge: localData.mergeSubdocs,
769 }, async response => {
770 const servePath = window.location.protocol + "//" + window.location.host + "/";
771 const html = await onExport(response, undefined, servePath, {type: "pdf", id});
772 if (isInAndroid()) {
773 window.JSAndroid.print(html);
774 } else if (isInHarmony()) {
775 window.JSHarmony.print(html);
776 }
777
778 setTimeout(() => {
779 hideMessage(msgId);
780 }, 3000);
781 });
782 }
783 }, {
784 id: "exportHTML_SiYuan",
785 label: "HTML (SiYuan)",
786 iconClass: "ft__error",
787 icon: "iconHTML5",
788 click: () => {
789 saveExport({type: "html", id});
790 }
791 }, {
792 id: "exportHTML_Markdown",
793 label: "HTML (Markdown)",
794 icon: "iconHTML5",
795 click: () => {
796 saveExport({type: "htmlmd", id});
797 }
798 },
799 /// #endif
800 ]
801 }).element;
802};
803
804export const openMenu = (app: App, src: string, onlyMenu: boolean, showAccelerator: boolean) => {
805 const submenu = [];
806 /// #if MOBILE
807 submenu.push({
808 id: isInAndroid() ? "useDefault" : "useBrowserView",
809 label: isInAndroid() ? window.siyuan.languages.useDefault : window.siyuan.languages.useBrowserView,
810 accelerator: showAccelerator ? window.siyuan.languages.click : "",
811 click: () => {
812 openByMobile(src);
813 }
814 });
815 /// #else
816 if (isLocalPath(src)) {
817 if (Constants.SIYUAN_ASSETS_EXTS.includes(pathPosix().extname(src).split("?")[0]) &&
818 (!src.endsWith(".pdf") ||
819 (src.endsWith(".pdf") && !src.startsWith("file://")))
820 ) {
821 submenu.push({
822 id: "insertRight",
823 icon: "iconLayoutRight",
824 label: window.siyuan.languages.insertRight,
825 accelerator: showAccelerator ? window.siyuan.languages.click : "",
826 click() {
827 openAsset(app, src.trim(), parseInt(getSearch("page", src)), "right");
828 }
829 });
830 submenu.push({
831 id: "openBy",
832 label: window.siyuan.languages.openBy,
833 icon: "iconOpen",
834 accelerator: showAccelerator ? "⌥" + window.siyuan.languages.click : "",
835 click() {
836 openAsset(app, src.trim(), parseInt(getSearch("page", src)));
837 }
838 });
839 /// #if !BROWSER
840 submenu.push({
841 id: "openByNewWindow",
842 label: window.siyuan.languages.openByNewWindow,
843 icon: "iconOpenWindow",
844 click() {
845 openAssetNewWindow(src.trim());
846 }
847 });
848 submenu.push({
849 id: "showInFolder",
850 icon: "iconFolder",
851 label: window.siyuan.languages.showInFolder,
852 accelerator: showAccelerator ? "⌘" + window.siyuan.languages.click : "",
853 click: () => {
854 openBy(src, "folder");
855 }
856 });
857 submenu.push({
858 id: "useDefault",
859 label: window.siyuan.languages.useDefault,
860 accelerator: showAccelerator ? "⇧" + window.siyuan.languages.click : "",
861 click() {
862 openBy(src, "app");
863 }
864 });
865 /// #endif
866 } else {
867 /// #if !BROWSER
868 submenu.push({
869 id: "useDefault",
870 label: window.siyuan.languages.useDefault,
871 accelerator: showAccelerator ? window.siyuan.languages.click : "",
872 click() {
873 openBy(src, "app");
874 }
875 });
876 submenu.push({
877 id: "showInFolder",
878 icon: "iconFolder",
879 label: window.siyuan.languages.showInFolder,
880 accelerator: showAccelerator ? "⌘" + window.siyuan.languages.click : "",
881 click: () => {
882 openBy(src, "folder");
883 }
884 });
885 /// #else
886 submenu.push({
887 id: isInAndroid() || isInHarmony() ? "useDefault" : "useBrowserView",
888 label: isInAndroid() || isInHarmony() ? window.siyuan.languages.useDefault : window.siyuan.languages.useBrowserView,
889 accelerator: showAccelerator ? window.siyuan.languages.click : "",
890 click: () => {
891 openByMobile(src);
892 }
893 });
894 /// #endif
895 }
896 } else if (src) {
897 if (0 > src.indexOf(":")) {
898 // 使用 : 判断,不使用 :// 判断 Open external application protocol invalid https://github.com/siyuan-note/siyuan/issues/10075
899 // Support click to open hyperlinks like `www.foo.com` https://github.com/siyuan-note/siyuan/issues/9986
900 src = `https://${src}`;
901 }
902 /// #if !BROWSER
903 submenu.push({
904 id: "useDefault",
905 label: window.siyuan.languages.useDefault,
906 accelerator: showAccelerator ? window.siyuan.languages.click : "",
907 click: () => {
908 shell.openExternal(src).catch((e) => {
909 showMessage(e);
910 });
911 }
912 });
913 /// #else
914 submenu.push({
915 id: isInAndroid() || isInHarmony() ? "useDefault" : "useBrowserView",
916 label: isInAndroid() || isInHarmony() ? window.siyuan.languages.useDefault : window.siyuan.languages.useBrowserView,
917 accelerator: showAccelerator ? window.siyuan.languages.click : "",
918 click: () => {
919 openByMobile(src);
920 }
921 });
922 /// #endif
923 }
924 /// #endif
925 if (onlyMenu) {
926 return submenu;
927 }
928 window.siyuan.menus.menu.append(new MenuItem({
929 id: "openBy",
930 label: window.siyuan.languages.openBy,
931 icon: "iconOpen",
932 submenu
933 }).element);
934};
935
936export const renameMenu = (options: {
937 path: string
938 notebookId: string
939 name: string,
940 type: "notebook" | "file"
941}) => {
942 return new MenuItem({
943 id: "rename",
944 accelerator: window.siyuan.config.keymap.editor.general.rename.custom,
945 icon: "iconEdit",
946 label: window.siyuan.languages.rename,
947 click: () => {
948 rename(options);
949 }
950 }).element;
951};
952
953export const movePathToMenu = (paths: string[]) => {
954 return new MenuItem({
955 id: "move",
956 label: window.siyuan.languages.move,
957 icon: "iconMove",
958 accelerator: window.siyuan.config.keymap.general.move.custom,
959 click() {
960 movePathTo((toPath, toNotebook) => {
961 moveToPath(paths, toNotebook[0], toPath[0]);
962 }, paths);
963 }
964 }).element;
965};