mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 03:29:02 +01:00 
			
		
		
		
	feat(export/zip): add option to export with share theme
This commit is contained in:
		
							parent
							
								
									55bb2fdb9b
								
							
						
					
					
						commit
						a9f68f5487
					
				| @ -85,6 +85,13 @@ const TPL = /*html*/` | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
| 
 | ||||
|                         <div class="form-check"> | ||||
|                             <label class="form-check-label tn-radio"> | ||||
|                                 <input class="form-check-input" type="radio" name="export-subtree-format" value="share"> | ||||
|                                 Share format | ||||
|                             </label> | ||||
|                         </div> | ||||
|                     </div> | ||||
| 
 | ||||
|                     <div class="form-check"> | ||||
|  | ||||
| @ -147,7 +147,7 @@ function register(router: Router) { | ||||
|         const note = eu.getAndCheckNote(req.params.noteId); | ||||
|         const format = req.query.format || "html"; | ||||
| 
 | ||||
|         if (typeof format !== "string" || !["html", "markdown"].includes(format)) { | ||||
|         if (typeof format !== "string" || !["html", "markdown", "share"].includes(format)) { | ||||
|             throw new eu.EtapiError(400, "UNRECOGNIZED_EXPORT_FORMAT", `Unrecognized export format '${format}', supported values are 'html' (default) or 'markdown'.`); | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -26,7 +26,7 @@ function exportBranch(req: Request, res: Response) { | ||||
|     const taskContext = new TaskContext(taskId, "export"); | ||||
| 
 | ||||
|     try { | ||||
|         if (type === "subtree" && (format === "html" || format === "markdown")) { | ||||
|         if (type === "subtree" && (format === "html" || format === "markdown" || format === "share")) { | ||||
|             zipExportService.exportToZip(taskContext, branch, format, res); | ||||
|         } else if (type === "single") { | ||||
|             if (format !== "html" && format !== "markdown") { | ||||
|  | ||||
| @ -4,7 +4,7 @@ import dateUtils from "../date_utils.js"; | ||||
| import path from "path"; | ||||
| import mimeTypes from "mime-types"; | ||||
| import packageInfo from "../../../package.json" with { type: "json" }; | ||||
| import { getContentDisposition, escapeHtml } from "../utils.js"; | ||||
| import { getContentDisposition } from "../utils.js"; | ||||
| import protectedSessionService from "../protected_session.js"; | ||||
| import sanitize from "sanitize-filename"; | ||||
| import fs from "fs"; | ||||
| @ -22,9 +22,11 @@ import type { NoteMetaFile } from "../meta/note_meta.js"; | ||||
| import HtmlExportProvider from "./zip/html.js"; | ||||
| import { AdvancedExportOptions, ZipExportProvider, ZipExportProviderData } from "./zip/abstract_provider.js"; | ||||
| import MarkdownExportProvider from "./zip/markdown.js"; | ||||
| import ShareThemeExportProvider from "./zip/share_theme.js"; | ||||
| import type BNote from "../../becca/entities/bnote.js"; | ||||
| 
 | ||||
| async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "html" | "markdown", res: Response | fs.WriteStream, setHeaders = true, zipExportOptions?: AdvancedExportOptions) { | ||||
|     if (!["html", "markdown"].includes(format)) { | ||||
| async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "html" | "markdown" | "share", res: Response | fs.WriteStream, setHeaders = true, zipExportOptions?: AdvancedExportOptions) { | ||||
|     if (!["html", "markdown", "share"].includes(format)) { | ||||
|         throw new ValidationError(`Only 'html' and 'markdown' allowed as export format, '${format}' given`); | ||||
|     } | ||||
| 
 | ||||
| @ -135,7 +137,7 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h | ||||
|                 prefix: branch.prefix, | ||||
|                 dataFileName: fileName, | ||||
|                 type: "text", // export will have text description
 | ||||
|                 format: format | ||||
|                 format: (format === "markdown" ? "markdown" : "html") | ||||
|             }; | ||||
|             return meta; | ||||
|         } | ||||
| @ -165,7 +167,7 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h | ||||
|         taskContext.increaseProgressCount(); | ||||
| 
 | ||||
|         if (note.type === "text") { | ||||
|             meta.format = format; | ||||
|             meta.format = (format === "markdown" ? "markdown" : "html"); | ||||
|         } | ||||
| 
 | ||||
|         noteIdToMeta[note.noteId] = meta as NoteMeta; | ||||
| @ -296,13 +298,18 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function prepareContent(title: string, content: string | Buffer, noteMeta: NoteMeta): string | Buffer { | ||||
|         if (["html", "markdown"].includes(noteMeta?.format || "")) { | ||||
|     function prepareContent(title: string, content: string | Buffer, noteMeta: NoteMeta, note?: BNote): string | Buffer { | ||||
|         const isText = ["html", "markdown"].includes(noteMeta?.format || ""); | ||||
|         if (isText) { | ||||
|             content = content.toString(); | ||||
|             content = rewriteFn(content, noteMeta); | ||||
|         } | ||||
| 
 | ||||
|         return provider.prepareContent(title, content, noteMeta); | ||||
|         content = provider.prepareContent(title, content, noteMeta, note, branch); | ||||
|         if (isText) { | ||||
|             content = rewriteFn(content as string, noteMeta); | ||||
|         } | ||||
| 
 | ||||
|         return content; | ||||
|     } | ||||
| 
 | ||||
|     function saveNote(noteMeta: NoteMeta, filePathPrefix: string) { | ||||
| @ -317,7 +324,7 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h | ||||
| 
 | ||||
|             let content: string | Buffer = `<p>This is a clone of a note. Go to its <a href="${targetUrl}">primary location</a>.</p>`; | ||||
| 
 | ||||
|             content = prepareContent(noteMeta.title, content, noteMeta); | ||||
|             content = prepareContent(noteMeta.title, content, noteMeta, undefined); | ||||
| 
 | ||||
|             archive.append(content, { name: filePathPrefix + noteMeta.dataFileName }); | ||||
| 
 | ||||
| @ -333,7 +340,7 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h | ||||
|         } | ||||
| 
 | ||||
|         if (noteMeta.dataFileName) { | ||||
|             const content = prepareContent(noteMeta.title, note.getContent(), noteMeta); | ||||
|             const content = prepareContent(noteMeta.title, note.getContent(), noteMeta, note); | ||||
| 
 | ||||
|             archive.append(content, { | ||||
|                 name: filePathPrefix + noteMeta.dataFileName, | ||||
| @ -395,6 +402,9 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h | ||||
|         case "markdown": | ||||
|             provider = new MarkdownExportProvider(providerData); | ||||
|             break; | ||||
|         case "share": | ||||
|             provider = new ShareThemeExportProvider(providerData); | ||||
|             break; | ||||
|         default: | ||||
|             throw new Error(); | ||||
|     } | ||||
|  | ||||
| @ -1,5 +1,7 @@ | ||||
| import { Archiver } from "archiver"; | ||||
| import type { default as NoteMeta, NoteMetaFile } from "../../meta/note_meta.js"; | ||||
| import type BNote from "../../../becca/entities/bnote.js"; | ||||
| import type BBranch from "../../../becca/entities/bbranch.js"; | ||||
| 
 | ||||
| type RewriteLinksFn = (content: string, noteMeta: NoteMeta) => string; | ||||
| 
 | ||||
| @ -44,6 +46,6 @@ export abstract class ZipExportProvider { | ||||
|     } | ||||
| 
 | ||||
|     abstract prepareMeta(): void; | ||||
|     abstract prepareContent(title: string, content: string | Buffer, noteMeta: NoteMeta): string | Buffer; | ||||
|     abstract prepareContent(title: string, content: string | Buffer, noteMeta: NoteMeta, note: BNote | undefined, branch: BBranch): string | Buffer; | ||||
|     abstract afterDone(): void; | ||||
| } | ||||
|  | ||||
							
								
								
									
										86
									
								
								apps/server/src/services/export/zip/share_theme.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								apps/server/src/services/export/zip/share_theme.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,86 @@ | ||||
| import { join } from "path"; | ||||
| import NoteMeta from "../../meta/note_meta"; | ||||
| import { ZipExportProvider } from "./abstract_provider"; | ||||
| import { RESOURCE_DIR } from "../../resource_dir"; | ||||
| import { getResourceDir, isDev } from "../../utils"; | ||||
| import fs from "fs"; | ||||
| import { renderNoteForExport } from "../../../share/content_renderer"; | ||||
| import type BNote from "../../../becca/entities/bnote.js"; | ||||
| import type BBranch from "../../../becca/entities/bbranch.js"; | ||||
| 
 | ||||
| export default class ShareThemeExportProvider extends ZipExportProvider { | ||||
| 
 | ||||
|     private assetsMeta: NoteMeta[] = []; | ||||
| 
 | ||||
|     prepareMeta(): void { | ||||
|         const assets = [ | ||||
|             "style.css", | ||||
|             "script.js", | ||||
|             "boxicons.css", | ||||
|             "boxicons.eot", | ||||
|             "boxicons.woff2", | ||||
|             "boxicons.woff", | ||||
|             "boxicons.ttf", | ||||
|             "boxicons.svg", | ||||
|             "icon-color.svg" | ||||
|         ]; | ||||
| 
 | ||||
|         for (const asset of assets) { | ||||
|             const assetMeta = { | ||||
|                 noImport: true, | ||||
|                 dataFileName: asset | ||||
|             }; | ||||
|             this.assetsMeta.push(assetMeta); | ||||
|             this.metaFile.files.push(assetMeta); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     prepareContent(title: string, content: string | Buffer, noteMeta: NoteMeta, note: BNote, branch: BBranch): string | Buffer { | ||||
|         if (!noteMeta?.notePath?.length) { | ||||
|             throw new Error("Missing note path."); | ||||
|         } | ||||
|         const basePath = "../".repeat(noteMeta.notePath.length - 1); | ||||
| 
 | ||||
|         content = renderNoteForExport(note, branch, basePath); | ||||
| 
 | ||||
|         return content; | ||||
|     } | ||||
| 
 | ||||
|     afterDone(): void { | ||||
|         this.#saveAssets(this.rootMeta, this.assetsMeta); | ||||
|     } | ||||
| 
 | ||||
|     #saveAssets(rootMeta: NoteMeta, assetsMeta: NoteMeta[]) { | ||||
|         for (const assetMeta of assetsMeta) { | ||||
|             if (!assetMeta.dataFileName) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             let cssContent = getShareThemeAssets(assetMeta.dataFileName); | ||||
|             this.archive.append(cssContent, { name: assetMeta.dataFileName }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| function getShareThemeAssets(nameWithExtension: string) { | ||||
|     // Rename share.css to style.css.
 | ||||
|     if (nameWithExtension === "style.css") { | ||||
|         nameWithExtension = "share.css"; | ||||
|     } else if (nameWithExtension === "script.js") { | ||||
|         nameWithExtension = "share.js"; | ||||
|     } | ||||
| 
 | ||||
|     let path: string | undefined; | ||||
|     if (nameWithExtension === "icon-color.svg") { | ||||
|         path = join(RESOURCE_DIR, "images", nameWithExtension); | ||||
|     } else if (isDev) { | ||||
|         path = join(getResourceDir(), "..", "..", "client", "dist", "src", nameWithExtension); | ||||
|     } | ||||
| 
 | ||||
|     if (!path) { | ||||
|         throw new Error("Not yet defined."); | ||||
|     } | ||||
| 
 | ||||
|     return fs.readFileSync(path); | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Elian Doran
						Elian Doran