mirror of
https://github.com/siyuan-note/siyuan.git
synced 2026-01-13 12:18:50 +01:00
🎨 Supports exporting a code block as a file https://github.com/siyuan-note/siyuan/pull/16774
Signed-off-by: Daniel <845765@qq.com>
This commit is contained in:
parent
9199188e96
commit
e6d79c1760
16 changed files with 89 additions and 88 deletions
|
|
@ -1317,7 +1317,7 @@
|
|||
"edit-mode": "تبديل الوضع",
|
||||
"emoji": "الرموز التعبيرية",
|
||||
"export": "تصدير",
|
||||
"exportCodeBlock": "تصدير محتوى كتلة الكود",
|
||||
"saveCodeBlockAsFile": "حفظ كتلة الكود كملف",
|
||||
"fileTypeError": "نوع الملف خطأ",
|
||||
"fullscreen": "ملء الشاشة",
|
||||
"generate": "جاري التوليد",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -1317,7 +1317,7 @@
|
|||
"edit-mode": "החלף מצב",
|
||||
"emoji": "אימוג'י",
|
||||
"export": "ייצוא",
|
||||
"exportCodeBlock": "ייצא תוכן בלוק קוד",
|
||||
"saveCodeBlockAsFile": "שמור בלוק קוד כקובץ",
|
||||
"fileTypeError": "סוג הקובץ שגוי",
|
||||
"fullscreen": "החלף מסך מלא",
|
||||
"generate": "מייצר",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -1317,7 +1317,7 @@
|
|||
"edit-mode": "編集モードの切り替え",
|
||||
"emoji": "絵文字",
|
||||
"export": "エクスポート",
|
||||
"exportCodeBlock": "コードブロックの内容をエクスポート",
|
||||
"saveCodeBlockAsFile": "コードブロックをファイルとして保存",
|
||||
"fileTypeError": "ファイルの種類が正しくありません",
|
||||
"fullscreen": "禅モードの切り替え",
|
||||
"generate": "生成中",
|
||||
|
|
|
|||
|
|
@ -1317,7 +1317,7 @@
|
|||
"edit-mode": "모드 전환",
|
||||
"emoji": "이모티콘",
|
||||
"export": "내보내기",
|
||||
"exportCodeBlock": "코드 블록 내용 내보내기",
|
||||
"saveCodeBlockAsFile": "코드 블록을 파일로 저장",
|
||||
"fileTypeError": "파일 유형 오류",
|
||||
"fullscreen": "전체 화면 전환",
|
||||
"generate": "생성 중",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -1317,7 +1317,7 @@
|
|||
"edit-mode": "Переключить режим",
|
||||
"emoji": "Эмодзи",
|
||||
"export": "Экспорт",
|
||||
"exportCodeBlock": "Экспортировать содержимое блока кода",
|
||||
"saveCodeBlockAsFile": "Сохранить блок кода в файл",
|
||||
"fileTypeError": "Тип файла неправильный",
|
||||
"fullscreen": "Переключить полноэкранный режим",
|
||||
"generate": "Генерировать",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -1317,7 +1317,7 @@
|
|||
"edit-mode": "模式切換",
|
||||
"emoji": "表情",
|
||||
"export": "匯出",
|
||||
"exportCodeBlock": "匯出程式碼區塊內容",
|
||||
"saveCodeBlockAsFile": "另存為檔案",
|
||||
"fileTypeError": "檔案類型不允許上傳",
|
||||
"fullscreen": "全螢幕切換",
|
||||
"generate": "生成中",
|
||||
|
|
|
|||
|
|
@ -1317,7 +1317,7 @@
|
|||
"edit-mode": "模式切换",
|
||||
"emoji": "表情",
|
||||
"export": "导出",
|
||||
"exportCodeBlock": "导出代码块内容",
|
||||
"saveCodeBlockAsFile": "另存为文件",
|
||||
"fileTypeError": "文件类型不允许上传",
|
||||
"fullscreen": "全屏切换",
|
||||
"generate": "生成中",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue