A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 965 lines 42 kB view raw
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};