diff --git a/app/appearance/langs/ar_SA.json b/app/appearance/langs/ar_SA.json index e22fdeb4e..bc3d6ce82 100644 --- a/app/appearance/langs/ar_SA.json +++ b/app/appearance/langs/ar_SA.json @@ -1317,7 +1317,7 @@ "edit-mode": "تبديل الوضع", "emoji": "الرموز التعبيرية", "export": "تصدير", - "exportCodeBlock": "تصدير محتوى كتلة الكود", + "saveCodeBlockAsFile": "حفظ كتلة الكود كملف", "fileTypeError": "نوع الملف خطأ", "fullscreen": "ملء الشاشة", "generate": "جاري التوليد", diff --git a/app/appearance/langs/de_DE.json b/app/appearance/langs/de_DE.json index 9117ad2a8..a872a6399 100644 --- a/app/appearance/langs/de_DE.json +++ b/app/appearance/langs/de_DE.json @@ -1317,7 +1317,7 @@ "edit-mode": "Modus umschalten", "emoji": "Emoji", "export": "Exportieren", - "exportCodeBlock": "Codeblock-Inhalt exportieren", + "saveCodeBlockAsFile": "Codeblock als Datei speichern", "fileTypeError": "Dateityp ist fehlerhaft", "fullscreen": "Vollbildmodus umschalten", "generate": "Generierung", diff --git a/app/appearance/langs/en_US.json b/app/appearance/langs/en_US.json index 0570fe031..9c1f36a64 100644 --- a/app/appearance/langs/en_US.json +++ b/app/appearance/langs/en_US.json @@ -1317,7 +1317,7 @@ "edit-mode": "Toggle Mode", "emoji": "Emoji", "export": "Export", - "exportCodeBlock": "Export code block content", + "saveCodeBlockAsFile": "Save code block as file", "fileTypeError": "file type is error", "fullscreen": "Toggle Fullscreen", "generate": "Generating", diff --git a/app/appearance/langs/es_ES.json b/app/appearance/langs/es_ES.json index e9e9b511c..acd89e4ad 100644 --- a/app/appearance/langs/es_ES.json +++ b/app/appearance/langs/es_ES.json @@ -1317,7 +1317,7 @@ "edit-mode": "Modo de edición", "emoji": "Emoji", "export": "Exportar", - "exportCodeBlock": "Exportar contenido del bloque de código", + "saveCodeBlockAsFile": "Guardar bloque de código como archivo", "fileTypeError": "el tipo de archivo es desconocido", "fullscreen": "Alternar pantalla completa", "generate": "Generar", diff --git a/app/appearance/langs/fr_FR.json b/app/appearance/langs/fr_FR.json index e9d145e43..5748120e1 100644 --- a/app/appearance/langs/fr_FR.json +++ b/app/appearance/langs/fr_FR.json @@ -1317,7 +1317,7 @@ "edit-mode": "Mode Toggle", "emoji": "Emoji", "export": "Exporter", - "exportCodeBlock": "Exporter le contenu du bloc de code", + "saveCodeBlockAsFile": "Enregistrer le bloc de code en tant que fichier", "fileTypeError": "type de fichier est une erreur", "fullscreen": "Basculer en plein écran", "generate": "Génération en cours", diff --git a/app/appearance/langs/he_IL.json b/app/appearance/langs/he_IL.json index b639be1e4..25c00e756 100644 --- a/app/appearance/langs/he_IL.json +++ b/app/appearance/langs/he_IL.json @@ -1317,7 +1317,7 @@ "edit-mode": "החלף מצב", "emoji": "אימוג'י", "export": "ייצוא", - "exportCodeBlock": "ייצא תוכן בלוק קוד", + "saveCodeBlockAsFile": "שמור בלוק קוד כקובץ", "fileTypeError": "סוג הקובץ שגוי", "fullscreen": "החלף מסך מלא", "generate": "מייצר", diff --git a/app/appearance/langs/it_IT.json b/app/appearance/langs/it_IT.json index 88fcebcfa..01949faeb 100644 --- a/app/appearance/langs/it_IT.json +++ b/app/appearance/langs/it_IT.json @@ -1317,7 +1317,7 @@ "edit-mode": "Cambia modalità", "emoji": "Emoji", "export": "Esporta", - "exportCodeBlock": "Esporta contenuto blocco codice", + "saveCodeBlockAsFile": "Salva blocco di codice come file", "fileTypeError": "tipo di file errato", "fullscreen": "Attiva/disattiva schermo intero", "generate": "Generando", diff --git a/app/appearance/langs/ja_JP.json b/app/appearance/langs/ja_JP.json index c3f9edc98..87ddd8ecf 100644 --- a/app/appearance/langs/ja_JP.json +++ b/app/appearance/langs/ja_JP.json @@ -1317,7 +1317,7 @@ "edit-mode": "編集モードの切り替え", "emoji": "絵文字", "export": "エクスポート", - "exportCodeBlock": "コードブロックの内容をエクスポート", + "saveCodeBlockAsFile": "コードブロックをファイルとして保存", "fileTypeError": "ファイルの種類が正しくありません", "fullscreen": "禅モードの切り替え", "generate": "生成中", diff --git a/app/appearance/langs/ko_KR.json b/app/appearance/langs/ko_KR.json index da05a9568..b78f306c9 100644 --- a/app/appearance/langs/ko_KR.json +++ b/app/appearance/langs/ko_KR.json @@ -1317,7 +1317,7 @@ "edit-mode": "모드 전환", "emoji": "이모티콘", "export": "내보내기", - "exportCodeBlock": "코드 블록 내용 내보내기", + "saveCodeBlockAsFile": "코드 블록을 파일로 저장", "fileTypeError": "파일 유형 오류", "fullscreen": "전체 화면 전환", "generate": "생성 중", diff --git a/app/appearance/langs/pl_PL.json b/app/appearance/langs/pl_PL.json index d1bf17102..176a0d4fe 100644 --- a/app/appearance/langs/pl_PL.json +++ b/app/appearance/langs/pl_PL.json @@ -1317,7 +1317,7 @@ "edit-mode": "Przełącz tryb", "emoji": "Emoji", "export": "Eksportuj", - "exportCodeBlock": "Eksportuj zawartość bloku kodu", + "saveCodeBlockAsFile": "Zapisz blok kodu jako plik", "fileTypeError": "błąd typu pliku", "fullscreen": "Przełącz pełny ekran", "generate": "Generowanie", diff --git a/app/appearance/langs/pt_BR.json b/app/appearance/langs/pt_BR.json index c53d1982c..59bcdeac8 100644 --- a/app/appearance/langs/pt_BR.json +++ b/app/appearance/langs/pt_BR.json @@ -1317,7 +1317,7 @@ "edit-mode": "Alternar Modo", "emoji": "Emoji", "export": "Exportar", - "exportCodeBlock": "Exportar conteúdo do bloco de código", + "saveCodeBlockAsFile": "Salvar bloco de código como arquivo", "fileTypeError": "tipo de arquivo é inválido", "fullscreen": "Alternar Tela Cheia", "generate": "Gerando", diff --git a/app/appearance/langs/ru_RU.json b/app/appearance/langs/ru_RU.json index c5d42355f..72fd04b59 100644 --- a/app/appearance/langs/ru_RU.json +++ b/app/appearance/langs/ru_RU.json @@ -1317,7 +1317,7 @@ "edit-mode": "Переключить режим", "emoji": "Эмодзи", "export": "Экспорт", - "exportCodeBlock": "Экспортировать содержимое блока кода", + "saveCodeBlockAsFile": "Сохранить блок кода в файл", "fileTypeError": "Тип файла неправильный", "fullscreen": "Переключить полноэкранный режим", "generate": "Генерировать", diff --git a/app/appearance/langs/tr_TR.json b/app/appearance/langs/tr_TR.json index 7ed170ad9..6c664aa22 100644 --- a/app/appearance/langs/tr_TR.json +++ b/app/appearance/langs/tr_TR.json @@ -1317,7 +1317,7 @@ "edit-mode": "Düzenleme modunu değiştir", "emoji": "Emoji", "export": "Dışa aktar", - "exportCodeBlock": "Kod bloğu içeriğini dışa aktar", + "saveCodeBlockAsFile": "Kod bloğunu dosya olarak kaydet", "fileTypeError": "Dosya türü hatalı", "fullscreen": "Tam ekranı aç/kapat", "generate": "Oluşturuluyor", diff --git a/app/appearance/langs/zh_CHT.json b/app/appearance/langs/zh_CHT.json index c53deec44..1704fe157 100644 --- a/app/appearance/langs/zh_CHT.json +++ b/app/appearance/langs/zh_CHT.json @@ -1317,7 +1317,7 @@ "edit-mode": "模式切換", "emoji": "表情", "export": "匯出", - "exportCodeBlock": "匯出程式碼區塊內容", + "saveCodeBlockAsFile": "另存為檔案", "fileTypeError": "檔案類型不允許上傳", "fullscreen": "全螢幕切換", "generate": "生成中", diff --git a/app/appearance/langs/zh_CN.json b/app/appearance/langs/zh_CN.json index a799e9d86..3e2075531 100644 --- a/app/appearance/langs/zh_CN.json +++ b/app/appearance/langs/zh_CN.json @@ -1317,7 +1317,7 @@ "edit-mode": "模式切换", "emoji": "表情", "export": "导出", - "exportCodeBlock": "导出代码块内容", + "saveCodeBlockAsFile": "另存为文件", "fileTypeError": "文件类型不允许上传", "fullscreen": "全屏切换", "generate": "生成中", diff --git a/app/src/protyle/gutter/index.ts b/app/src/protyle/gutter/index.ts index 9eba125a8..f7c1843ed 100644 --- a/app/src/protyle/gutter/index.ts +++ b/app/src/protyle/gutter/index.ts @@ -36,7 +36,7 @@ import {highlightRender} from "../render/highlightRender"; import {blockRender} from "../render/blockRender"; import {getContenteditableElement, getParentBlock, getTopAloneElement, isNotEditBlock} from "../wysiwyg/getBlock"; import * as dayjs from "dayjs"; -import {fetchPost} from "../../util/fetch"; +import {fetchPost, fetchSyncPost} from "../../util/fetch"; import {cancelSB, genEmptyElement, getLangByType, insertEmptyBlock, jumpToParent,} from "../../block/util"; import {countBlockWord} from "../../layout/status"; import {Constants} from "../../constants"; @@ -61,7 +61,6 @@ import {processClonePHElement} from "../render/util"; /// #if !MOBILE import {openFileById} from "../../editor/util"; import * as path from "path"; -import {fetchSyncPost} from "../../util/fetch"; import {replaceLocalPath} from "../../editor/rename"; import {showMessage} from "../../dialog/message"; import {ipcRenderer} from "electron"; @@ -69,6 +68,7 @@ import * as fs from "node:fs"; /// #endif import {checkFold} from "../../util/noRelyPCFunction"; import {clearSelect} from "../util/clear"; +import {code160to32} from "../util/code160to32"; export class Gutter { public element: HTMLElement; @@ -1577,79 +1577,80 @@ export class Gutter { window.siyuan.menus.menu.remove(); }); } + }, { + /// #if !MOBILE + id: "saveCodeBlockAsFile", + iconHTML: "", + label: window.siyuan.languages.saveCodeBlockAsFile, + async bind(element) { + element.addEventListener("click", async () => { + const hljsElement = nodeElement.querySelector(".hljs") as HTMLElement; + let code = hljsElement?.textContent || ""; + code = code160to32(code); + // https://github.com/siyuan-note/siyuan/issues/14800 + code = code.replace(/\u200D```/g, "```"); + + let docName = window.siyuan.languages._kernel[16]; + if (protyle.block?.rootID) { + try { + const docInfo = await fetchSyncPost("/api/block/getDocInfo", { + id: protyle.block.rootID + }); + if (docInfo?.data?.name) { + docName = replaceLocalPath(docInfo.data.name); + let truncatedDocName = ""; + let byteCount = 0; + const encoder = new TextEncoder(); + for (const char of docName) { + const charBytes = encoder.encode(char).length; + if (byteCount + charBytes > 170) { // 189 - 19(-YYYYMMDDHHmmss.txt) + break; + } + truncatedDocName += char; + byteCount += charBytes; + } + docName = truncatedDocName; + } + } catch { + console.warn("Failed to fetch document info for code block export."); + } + } + + const fileName = `${docName}-${dayjs().format("YYYYMMDDHHmmss")}.txt`; + + /// #if BROWSER + const blob = new Blob([code], {type: "text/plain;charset=utf-8"}); + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = fileName; + document.body.appendChild(link); + link.click(); + link.remove(); + URL.revokeObjectURL(url); + showMessage(window.siyuan.languages.exported); + /// #else + + const result = await ipcRenderer.invoke(Constants.SIYUAN_GET, { + cmd: "showSaveDialog", + defaultPath: fileName, + properties: ["showOverwriteConfirmation"], + }); + if (!result.canceled && result.filePath) { + try { + fs.writeFileSync(result.filePath, code, "utf-8"); + showMessage(window.siyuan.languages.exported); + } catch (error) { + showMessage(window.siyuan.languages._kernel[14].replace("%s", (error instanceof Error ? error.message : String(error)))); + } + } + /// #endif + window.siyuan.menus.menu.remove(); + }); + } + /// #endif }] }).element); - /// #if !MOBILE - const hljsElement = nodeElement.querySelector(".hljs") as HTMLElement; - let code = hljsElement?.textContent || ""; - if (code.length > 0) { - window.siyuan.menus.menu.append(new MenuItem({ - id: "exportCodeBlock", - label: window.siyuan.languages.exportCodeBlock, - icon: "iconUpload", - async click() { - code = code.replace(/\u00A0/g, " "); - // https://github.com/siyuan-note/siyuan/issues/14800 - code = code.replace(/\u200D```/g, "```"); - - let docName = window.siyuan.languages._kernel[16]; - if (protyle.block?.rootID) { - try { - const docInfo = await fetchSyncPost("/api/block/getDocInfo", { - id: protyle.block.rootID - }); - if (docInfo?.data?.name) { - docName = replaceLocalPath(docInfo.data.name); - let truncatedDocName = ""; - let byteCount = 0; - const encoder = new TextEncoder(); - for (const char of docName) { - const charBytes = encoder.encode(char).length; - if (byteCount + charBytes > 170) { // 189 - 19(-YYYYMMDDHHmmss.txt) - break; - } - truncatedDocName += char; - byteCount += charBytes; - } - docName = truncatedDocName; - } - } catch { - // API 获取失败时使用默认值 - } - } - const fileName = `${docName}-${dayjs().format("YYYYMMDDHHmmss")}.txt`; - - /// #if BROWSER - const blob = new Blob([code], {type: "text/plain;charset=utf-8"}); - const url = URL.createObjectURL(blob); - const link = document.createElement("a"); - link.href = url; - link.download = fileName; - document.body.appendChild(link); - link.click(); - link.remove(); - URL.revokeObjectURL(url); - showMessage(window.siyuan.languages.exported); - /// #else - const result = await ipcRenderer.invoke(Constants.SIYUAN_GET, { - cmd: "showSaveDialog", - defaultPath: fileName, - properties: ["showOverwriteConfirmation"], - }); - if (!result.canceled && result.filePath) { - try { - fs.writeFileSync(result.filePath, code, "utf-8"); - showMessage(window.siyuan.languages.exported); - } catch (error) { - showMessage(window.siyuan.languages._kernel[14].replace("%s", (error instanceof Error ? error.message : String(error)))); - } - } - /// #endif - window.siyuan.menus.menu.remove(); - } - }).element); - } - /// #endif } else if (type === "NodeCodeBlock" && !protyle.disabled && ["echarts", "mindmap"].includes(nodeElement.getAttribute("data-subtype"))) { window.siyuan.menus.menu.append(new MenuItem({id: "separator_chart", type: "separator"}).element); const height = (nodeElement as HTMLElement).style.height;