feat: Add Document Converter component and document conversion helpers

This commit is contained in:
2025-12-30 11:51:56 +00:00
parent 607d680faf
commit c80af80ca5
3 changed files with 543 additions and 5 deletions

View File

@@ -482,6 +482,263 @@
]
}
]
},
{
"id": "DocumentConverter",
"type": "card",
"props": {
"sx": { "maxWidth": 600, "mx": "auto", "mt": 3 }
},
"children": [
{
"type": "cardContent",
"children": [
{
"type": "typography",
"props": { "variant": "h5", "gutterBottom": true },
"children": "Document Converter"
},
{
"type": "typography",
"props": { "variant": "body2", "color": "text.secondary", "paragraph": true },
"children": "Convert Markdown, HTML, and other documents to PDF, DOCX, EPUB, and more."
},
{
"type": "box",
"props": { "component": "form", "sx": { "display": "flex", "flexDirection": "column", "gap": 2 } },
"children": [
{
"type": "textField",
"props": {
"label": "Input File",
"placeholder": "/path/to/document.md",
"fullWidth": true,
"required": true
},
"binding": "form.input_path"
},
{
"type": "grid",
"props": { "container": true, "spacing": 2 },
"children": [
{
"type": "grid",
"props": { "item": true, "xs": 6 },
"children": [
{
"type": "select",
"props": {
"label": "Input Format",
"fullWidth": true,
"options": [
{ "value": "markdown", "label": "Markdown" },
{ "value": "html", "label": "HTML" },
{ "value": "docx", "label": "Word (DOCX)" },
{ "value": "latex", "label": "LaTeX" },
{ "value": "rst", "label": "reStructuredText" },
{ "value": "org", "label": "Org-mode" }
]
},
"binding": "form.input_format"
}
]
},
{
"type": "grid",
"props": { "item": true, "xs": 6 },
"children": [
{
"type": "select",
"props": {
"label": "Output Format",
"fullWidth": true,
"options": [
{ "value": "pdf", "label": "PDF" },
{ "value": "html", "label": "HTML" },
{ "value": "docx", "label": "Word (DOCX)" },
{ "value": "epub", "label": "EPUB" },
{ "value": "odt", "label": "OpenDocument" },
{ "value": "latex", "label": "LaTeX" }
]
},
"binding": "form.output_format"
}
]
}
]
},
{
"type": "textField",
"props": {
"label": "Output Path (optional)",
"placeholder": "Auto-generated if empty",
"fullWidth": true
},
"binding": "form.output_path"
},
{
"type": "accordion",
"children": [
{
"type": "accordionSummary",
"props": { "expandIcon": "ExpandMore" },
"children": [
{
"type": "typography",
"children": "Advanced Options"
}
]
},
{
"type": "accordionDetails",
"children": [
{
"type": "box",
"props": { "sx": { "display": "flex", "flexDirection": "column", "gap": 2 } },
"children": [
{
"type": "textField",
"props": { "label": "Document Title", "fullWidth": true },
"binding": "form.title"
},
{
"type": "textField",
"props": { "label": "Author", "fullWidth": true },
"binding": "form.author"
},
{
"type": "formControlLabel",
"props": {
"label": "Include Table of Contents",
"control": { "type": "checkbox" }
},
"binding": "form.toc"
},
{
"type": "grid",
"props": { "container": true, "spacing": 2 },
"children": [
{
"type": "grid",
"props": { "item": true, "xs": 6 },
"children": [
{
"type": "select",
"props": {
"label": "Paper Size",
"fullWidth": true,
"options": [
{ "value": "a4", "label": "A4" },
{ "value": "letter", "label": "Letter" },
{ "value": "legal", "label": "Legal" }
]
},
"binding": "form.paper_size"
}
]
},
{
"type": "grid",
"props": { "item": true, "xs": 6 },
"children": [
{
"type": "textField",
"props": { "label": "Margin", "placeholder": "1in", "fullWidth": true },
"binding": "form.margin"
}
]
}
]
},
{
"type": "select",
"props": {
"label": "Code Highlight Style",
"fullWidth": true,
"options": [
{ "value": "tango", "label": "Tango" },
{ "value": "pygments", "label": "Pygments" },
{ "value": "kate", "label": "Kate" },
{ "value": "monochrome", "label": "Monochrome" },
{ "value": "espresso", "label": "Espresso" },
{ "value": "zenburn", "label": "Zenburn" }
]
},
"binding": "form.highlight_style"
},
{
"type": "textField",
"props": { "label": "Custom Template Path", "fullWidth": true },
"binding": "form.template"
}
]
}
]
}
]
},
{
"type": "button",
"props": {
"variant": "contained",
"color": "primary",
"size": "large",
"fullWidth": true,
"startIcon": { "type": "icon", "props": { "name": "Transform" } }
},
"events": {
"onClick": "document_helpers.convert(form.input_path, form.output_format, form)"
},
"children": "Convert Document"
}
]
}
]
}
]
},
{
"id": "QuickConvertButtons",
"type": "box",
"props": { "sx": { "display": "flex", "gap": 2, "flexWrap": "wrap" } },
"children": [
{
"type": "button",
"props": {
"variant": "outlined",
"startIcon": { "type": "icon", "props": { "name": "PictureAsPdf" } }
},
"events": { "onClick": "openDialog('DocumentConverter', { output_format: 'pdf' })" },
"children": "Markdown → PDF"
},
{
"type": "button",
"props": {
"variant": "outlined",
"startIcon": { "type": "icon", "props": { "name": "Code" } }
},
"events": { "onClick": "openDialog('DocumentConverter', { output_format: 'html' })" },
"children": "Markdown → HTML"
},
{
"type": "button",
"props": {
"variant": "outlined",
"startIcon": { "type": "icon", "props": { "name": "Article" } }
},
"events": { "onClick": "openDialog('DocumentConverter', { output_format: 'docx' })" },
"children": "Markdown → DOCX"
},
{
"type": "button",
"props": {
"variant": "outlined",
"startIcon": { "type": "icon", "props": { "name": "MenuBook" } }
},
"events": { "onClick": "openDialog('DocumentConverter', { output_format: 'epub' })" },
"children": "Markdown → EPUB"
}
]
}
]
}

View File

@@ -2,10 +2,10 @@
"id": "media_center",
"name": "Media Center",
"version": "1.0.0",
"description": "Media processing dashboard with job queue, radio, and TV channel management",
"description": "Media processing dashboard with job queue, radio, TV channel management, and document conversion",
"author": "MetaBuilder",
"minLevel": 3,
"tags": ["media", "streaming", "radio", "tv", "transcoding"],
"tags": ["media", "streaming", "radio", "tv", "transcoding", "documents", "pdf"],
"dependencies": ["notification_center", "data_table"],
"exports": {
"components": [
@@ -19,19 +19,23 @@
"TvChannelEditor",
"TvScheduleEditor",
"TvEpgViewer",
"MediaPlayer"
"MediaPlayer",
"DocumentConverter",
"QuickConvertButtons"
],
"pages": [
"MediaOverviewPage",
"JobsPage",
"RadioPage",
"TvPage"
"TvPage",
"DocumentsPage"
],
"scripts": [
"media_api",
"job_helpers",
"radio_helpers",
"tv_helpers"
"tv_helpers",
"document_helpers"
]
},
"routes": [

View File

@@ -0,0 +1,277 @@
---@alias DocumentFormat "md" | "markdown" | "html" | "docx" | "pdf" | "latex" | "epub" | "odt" | "rst" | "txt"
---@class DocumentConvertOptions
---@field toc? boolean Include table of contents
---@field template? string Custom template path
---@field title? string Document title
---@field author? string Document author
---@field date? string Document date
---@field paper_size? "a4" | "letter" | "legal" Paper size for PDF
---@field margin? string Margin (e.g., "1in", "2cm")
---@field highlight_style? string Code highlight style
---@class DocumentJob
---@field id string Job ID
---@field status string Job status
---@field progress? number Progress 0-100
---@field output_path? string Output file path
---@field error? string Error message
---@class DocumentHelpersModule
---@field convert fun(input_path: string, output_format: DocumentFormat, options?: DocumentConvertOptions): DocumentJob|nil, string?
---@field markdown_to_pdf fun(input_path: string, output_path?: string, options?: DocumentConvertOptions): DocumentJob|nil, string?
---@field markdown_to_html fun(input_path: string, output_path?: string, options?: DocumentConvertOptions): DocumentJob|nil, string?
---@field markdown_to_docx fun(input_path: string, output_path?: string, options?: DocumentConvertOptions): DocumentJob|nil, string?
---@field get_supported_formats fun(): table
local M = {}
local config = {
base_url = "http://localhost:8090"
}
---Configure the helper
---@param opts table Configuration options
function M.configure(opts)
if opts.base_url then config.base_url = opts.base_url end
end
---Get supported input formats
---@return string[] formats List of input formats
function M.get_input_formats()
return {
"md", "markdown", "gfm", "commonmark",
"html", "htm",
"tex", "latex",
"docx", "odt",
"rst", "org", "txt",
"json", "yaml"
}
end
---Get supported output formats
---@return string[] formats List of output formats
function M.get_output_formats()
return {
"pdf", "html", "html5",
"docx", "odt", "rtf",
"epub", "epub3",
"latex", "beamer",
"markdown", "gfm",
"plain", "json"
}
end
---Get supported format conversions as a table
---@return table formats { input = {...}, output = {...} }
function M.get_supported_formats()
return {
input = M.get_input_formats(),
output = M.get_output_formats()
}
end
---Check if a conversion is supported
---@param from_format DocumentFormat Input format
---@param to_format DocumentFormat Output format
---@return boolean supported Whether conversion is supported
function M.supports_conversion(from_format, to_format)
local input_formats = M.get_input_formats()
local output_formats = M.get_output_formats()
local input_ok = false
for _, fmt in ipairs(input_formats) do
if fmt == from_format then
input_ok = true
break
end
end
local output_ok = false
for _, fmt in ipairs(output_formats) do
if fmt == to_format then
output_ok = true
break
end
end
return input_ok and output_ok
end
---Generate output path from input path
---@param input_path string Input file path
---@param output_format string Output format extension
---@return string output_path Generated output path
local function generate_output_path(input_path, output_format)
-- Remove extension and add new one
local base = input_path:match("(.+)%.[^%.]+$") or input_path
return base .. "." .. output_format
end
---Convert a document
---@param input_path string Path to input file
---@param output_format DocumentFormat Target format
---@param options? DocumentConvertOptions Conversion options
---@return DocumentJob|nil job Job info if successful
---@return string? error Error message if failed
function M.convert(input_path, output_format, options)
if not input_path or input_path == "" then
return nil, "Input path is required"
end
if not output_format or output_format == "" then
return nil, "Output format is required"
end
options = options or {}
local output_path = options.output_path or generate_output_path(input_path, output_format)
-- Build request body
local body = {
type = "document_convert",
params = {
input_path = input_path,
output_path = output_path,
output_format = output_format,
variables = {}
},
notify_user = true
}
-- Add options as variables
if options.toc then
body.params.variables.toc = "true"
end
if options.template then
body.params.template_path = options.template
end
if options.title then
body.params.variables.title = options.title
end
if options.author then
body.params.variables.author = options.author
end
if options.date then
body.params.variables.date = options.date
end
if options.paper_size then
body.params.variables.papersize = options.paper_size
end
if options.margin then
body.params.variables["geometry:margin"] = options.margin
end
-- HTTP POST /api/jobs would go here
-- Return mock response for now
return {
id = "doc_" .. tostring(os.time()),
status = "pending",
progress = 0
}
end
---Convert Markdown to PDF
---@param input_path string Path to markdown file
---@param output_path? string Path for PDF output (auto-generated if nil)
---@param options? DocumentConvertOptions Conversion options
---@return DocumentJob|nil job Job info if successful
---@return string? error Error message if failed
function M.markdown_to_pdf(input_path, output_path, options)
options = options or {}
options.output_path = output_path
return M.convert(input_path, "pdf", options)
end
---Convert Markdown to HTML
---@param input_path string Path to markdown file
---@param output_path? string Path for HTML output (auto-generated if nil)
---@param options? DocumentConvertOptions Conversion options
---@return DocumentJob|nil job Job info if successful
---@return string? error Error message if failed
function M.markdown_to_html(input_path, output_path, options)
options = options or {}
options.output_path = output_path
return M.convert(input_path, "html", options)
end
---Convert Markdown to DOCX
---@param input_path string Path to markdown file
---@param output_path? string Path for DOCX output (auto-generated if nil)
---@param options? DocumentConvertOptions Conversion options
---@return DocumentJob|nil job Job info if successful
---@return string? error Error message if failed
function M.markdown_to_docx(input_path, output_path, options)
options = options or {}
options.output_path = output_path
return M.convert(input_path, "docx", options)
end
---Convert HTML to PDF
---@param input_path string Path to HTML file
---@param output_path? string Path for PDF output
---@param options? DocumentConvertOptions Conversion options
---@return DocumentJob|nil job Job info if successful
---@return string? error Error message if failed
function M.html_to_pdf(input_path, output_path, options)
options = options or {}
options.output_path = output_path
return M.convert(input_path, "pdf", options)
end
---Convert DOCX to PDF
---@param input_path string Path to DOCX file
---@param output_path? string Path for PDF output
---@param options? DocumentConvertOptions Conversion options
---@return DocumentJob|nil job Job info if successful
---@return string? error Error message if failed
function M.docx_to_pdf(input_path, output_path, options)
options = options or {}
options.output_path = output_path
return M.convert(input_path, "pdf", options)
end
---Get format display name
---@param format string Format code
---@return string name Display name
function M.get_format_name(format)
local names = {
md = "Markdown",
markdown = "Markdown",
gfm = "GitHub Flavored Markdown",
html = "HTML",
htm = "HTML",
pdf = "PDF",
docx = "Word Document",
odt = "OpenDocument Text",
rtf = "Rich Text Format",
epub = "EPUB",
latex = "LaTeX",
tex = "LaTeX",
rst = "reStructuredText",
org = "Org-mode",
txt = "Plain Text",
json = "JSON",
yaml = "YAML"
}
return names[format] or format:upper()
end
---Get format icon name (for UI)
---@param format string Format code
---@return string icon MUI icon name
function M.get_format_icon(format)
local icons = {
md = "Description",
markdown = "Description",
html = "Code",
pdf = "PictureAsPdf",
docx = "Article",
odt = "Article",
epub = "MenuBook",
latex = "Functions",
tex = "Functions"
}
return icons[format] or "InsertDriveFile"
end
return M