diff --git a/README.md b/README.md
index 24c3c91..04b25ac 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,8 @@ This project is a reimplementation of the original Python-based [grip](https://g
- [x] Todo list like the one on GitHub
- Support for github markdown emojis :+1: :bowtie:
- Support for mermaid diagrams
+- 🌍 **Enhanced multilingual print support** - optimized for global languages including Chinese, Japanese, Korean, Arabic, Hindi, and more
+- 🖨️ **Professional print layout** - optimized margins, typography, and page breaks for high-quality printing
```mermaid
graph TD;
@@ -85,12 +87,41 @@ It's also possible to activate the darkmode:
go-grip -d .
```
+To disable automatic browser reload on file changes (useful for stable editing):
+
+```bash
+go-grip --no-reload README.md
+```
+
To terminate the current server simply press `CTRL-C`.
## :pencil: Examples
+## :printer: Print Features
+
+**go-grip** now includes comprehensive print optimization for global users:
+
+### Multilingual Font Support
+- **East Asian**: Chinese (Simplified/Traditional), Japanese, Korean
+- **South Asian**: Hindi, Tamil, Bengali, and other Indic languages
+- **Middle Eastern**: Arabic, Hebrew with proper RTL support
+- **European**: Latin, Cyrillic scripts (Russian, Ukrainian, etc.)
+- **Southeast Asian**: Thai, Khmer, and more
+
+### Print Layout Optimization
+- **Smart margins**: Left 2cm (binding edge), others 1.5cm
+- **Professional typography**: Optimized font sizes and line spacing
+- **Page break control**: Prevents awkward splits in tables, code blocks
+- **Clean backgrounds**: Removes backgrounds while preserving colors
+- **URL printing**: Shows link destinations for reference
+
+### Usage
+1. Open any markdown file with `go-grip filename.md`
+2. Use browser's print function (`Ctrl+P` / `Cmd+P`)
+3. Enjoy high-quality multilingual printouts!
+
## :bug: Known TODOs / Bugs
- [ ] Tests and refactoring
diff --git a/cmd/root.go b/cmd/root.go
index 899c528..c4adb47 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -17,6 +17,7 @@ var rootCmd = &cobra.Command{
host, _ := cmd.Flags().GetString("host")
port, _ := cmd.Flags().GetInt("port")
boundingBox, _ := cmd.Flags().GetBool("bounding-box")
+ noReload, _ := cmd.Flags().GetBool("no-reload")
var file string
if len(args) == 1 {
@@ -24,7 +25,7 @@ var rootCmd = &cobra.Command{
}
parser := pkg.NewParser(theme)
- server := pkg.NewServer(host, port, theme, boundingBox, browser, parser)
+ server := pkg.NewServer(host, port, theme, boundingBox, browser, !noReload, parser)
return server.Serve(file)
},
}
@@ -42,4 +43,5 @@ func init() {
rootCmd.Flags().StringP("host", "H", "localhost", "Host to use")
rootCmd.Flags().IntP("port", "p", 6419, "Port to use")
rootCmd.Flags().Bool("bounding-box", true, "Add bounding box to HTML")
+ rootCmd.Flags().Bool("no-reload", false, "Disable automatic browser reload on file changes")
}
diff --git a/defaults/static/css/github-mermaid.css b/defaults/static/css/github-mermaid.css
new file mode 100644
index 0000000..0628f52
--- /dev/null
+++ b/defaults/static/css/github-mermaid.css
@@ -0,0 +1,20 @@
+/* Mermaid error and utility styles */
+.mermaid-error {
+ background: #fff0f0;
+ color: #cf222e;
+ border: 1px solid #cf222e;
+ border-radius: 6px;
+ padding: 8px 12px;
+ margin: 8px 0;
+ white-space: pre-wrap;
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
+ font-size: 12px;
+}
+
+@media (prefers-color-scheme: dark) {
+ .mermaid-error {
+ background: #2a1616;
+ color: #ff7b72;
+ border-color: #da3633;
+ }
+}
\ No newline at end of file
diff --git a/defaults/static/css/github-print.css b/defaults/static/css/github-print.css
index 43bc4be..3fa6850 100644
--- a/defaults/static/css/github-print.css
+++ b/defaults/static/css/github-print.css
@@ -1,9 +1,374 @@
-.container-inner {
- border-width: 0;
- border-color: transparent;
- border-style: none;
- border-radius: 0;
- margin-top: 0;
- margin-bottom: 0;
- padding: 0;
+@media print {
+ /* ===========================================
+ * COMPREHENSIVE PRINT OPTIMIZATION
+ * Supports global multilingual typography
+ * =========================================== */
+
+ /* ========== GLOBAL RESET & LAYOUT ========== */
+ * {
+ /* Universal multilingual font stack optimized for print */
+ font-family:
+ /* System fonts - best quality */
+ -apple-system, BlinkMacSystemFont, "Segoe UI",
+ /* Western European languages */
+ "Helvetica Neue", Helvetica, Arial,
+ /* East Asian languages */
+ "Microsoft YaHei", "微软雅黑", /* Simplified Chinese (Windows) */
+ "PingFang SC", "苹方-简", /* Simplified Chinese (macOS) */
+ "Hiragino Sans GB", "冬青黑体简体中文", /* Simplified Chinese (macOS legacy) */
+ "Microsoft JhengHei", "微軟正黑體", /* Traditional Chinese (Windows) */
+ "PingFang TC", "苹方-繁", /* Traditional Chinese (macOS) */
+ "Hiragino Sans CNS", "冬青黑体简体中文", /* Traditional Chinese (macOS legacy) */
+ "Yu Gothic", "游ゴシック", "YuGothic", /* Japanese (Windows 8.1+) */
+ "Hiragino Kaku Gothic ProN", "ヒラギノ角ゴ ProN W3", /* Japanese (macOS) */
+ "Meiryo", "メイリオ", /* Japanese (Windows legacy) */
+ "Malgun Gothic", "맑은 고딕", /* Korean (Windows) */
+ "Apple SD Gothic Neo", /* Korean (macOS) */
+ /* Indic languages */
+ "Noto Sans Devanagari", "Mangal", /* Hindi, Sanskrit, Marathi */
+ "Noto Sans Tamil", "Latha", /* Tamil */
+ "Noto Sans Bengali", "Vrinda", /* Bengali */
+ "Noto Sans Gujarati", "Shruti", /* Gujarati */
+ "Noto Sans Telugu", "Gautami", /* Telugu */
+ "Noto Sans Kannada", "Tunga", /* Kannada */
+ "Noto Sans Malayalam", "Kartika", /* Malayalam */
+ "Noto Sans Oriya", "Kalinga", /* Oriya */
+ "Noto Sans Gurmukhi", "Raavi", /* Punjabi */
+ /* Arabic and Hebrew */
+ "Noto Sans Arabic", "Tahoma", "Arial Unicode MS", /* Arabic */
+ "Noto Sans Hebrew", "David", "Miriam", /* Hebrew */
+ /* Cyrillic */
+ "Noto Sans", "DejaVu Sans", "Liberation Sans", /* Russian, Ukrainian, etc. */
+ /* Thai and Southeast Asian */
+ "Noto Sans Thai", "Tahoma", /* Thai */
+ "Noto Sans", "Khmer UI", /* Khmer */
+ /* Fallbacks */
+ sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji";
+
+ /* Remove backgrounds but preserve colors for better print readability */
+ background: transparent !important;
+
+ /* Remove shadows and effects for cleaner print */
+ box-shadow: none !important;
+ text-shadow: none !important;
+ }
+
+ /* Page setup with optimized margins */
+ @page {
+ margin-top: 1.5cm;
+ margin-right: 1.5cm;
+ margin-bottom: 1.5cm;
+ margin-left: 2cm; /* Larger left margin for binding */
+ size: A4;
+ }
+
+ html, body {
+ background: transparent !important;
+ font-size: 12pt !important;
+ line-height: 1.4 !important;
+ }
+
+ /* ========== CONTAINER & LAYOUT OPTIMIZATION ========== */
+ .container {
+ max-width: none !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ }
+
+ .container-inner {
+ border: none !important;
+ border-radius: 0 !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ box-shadow: none !important;
+ }
+
+ /* Remove footer for print */
+ .footer {
+ display: none !important;
+ }
+
+ /* ========== TYPOGRAPHY OPTIMIZATION ========== */
+ .markdown-body {
+ font-size: 12pt !important;
+ line-height: 1.4 !important;
+ background: transparent !important;
+ }
+
+ /* Headings optimization */
+ .markdown-body h1 {
+ font-size: 18pt !important;
+ font-weight: bold !important;
+ margin-top: 0 !important;
+ margin-bottom: 12pt !important;
+ page-break-after: avoid !important;
+ border-bottom: 2pt solid #333 !important;
+ padding-bottom: 6pt !important;
+ }
+
+ .markdown-body h2 {
+ font-size: 16pt !important;
+ font-weight: bold !important;
+ margin-top: 16pt !important;
+ margin-bottom: 8pt !important;
+ page-break-after: avoid !important;
+ border-bottom: 1pt solid #666 !important;
+ padding-bottom: 4pt !important;
+ }
+
+ .markdown-body h3 {
+ font-size: 14pt !important;
+ font-weight: bold !important;
+ margin-top: 12pt !important;
+ margin-bottom: 6pt !important;
+ page-break-after: avoid !important;
+ }
+
+ .markdown-body h4,
+ .markdown-body h5,
+ .markdown-body h6 {
+ font-size: 12pt !important;
+ font-weight: bold !important;
+ margin-top: 12pt !important;
+ margin-bottom: 6pt !important;
+ page-break-after: avoid !important;
+ }
+
+ /* Paragraph spacing */
+ .markdown-body p {
+ margin-top: 0 !important;
+ margin-bottom: 8pt !important;
+ orphans: 3 !important;
+ widows: 3 !important;
+ }
+
+ /* ========== LISTS OPTIMIZATION ========== */
+ .markdown-body ul,
+ .markdown-body ol {
+ margin-top: 0 !important;
+ margin-bottom: 8pt !important;
+ padding-left: 20pt !important;
+ }
+
+ .markdown-body li {
+ margin-bottom: 4pt !important;
+ page-break-inside: avoid !important;
+ }
+
+ /* Task lists */
+ .markdown-body .task-list-item {
+ list-style: none !important;
+ }
+
+ .markdown-body .task-list-item-checkbox {
+ margin-right: 6pt !important;
+ }
+
+ /* ========== CODE BLOCKS OPTIMIZATION ========== */
+ .markdown-body code,
+ .markdown-body tt {
+ font-family:
+ "Consolas", "Monaco", "Courier New",
+ "DejaVu Sans Mono", "Liberation Mono",
+ "Source Code Pro", "Fira Code",
+ monospace !important;
+ font-size: 10pt !important;
+ background: #f5f5f5 !important;
+ color: #000 !important;
+ border: 1pt solid #ddd !important;
+ border-radius: 2pt !important;
+ padding: 1pt 3pt !important;
+ }
+
+ .markdown-body pre {
+ font-family:
+ "Consolas", "Monaco", "Courier New",
+ "DejaVu Sans Mono", "Liberation Mono",
+ "Source Code Pro", "Fira Code",
+ monospace !important;
+ font-size: 9pt !important;
+ line-height: 1.3 !important;
+ background: #f8f8f8 !important;
+ color: #000 !important;
+ border: 1pt solid #ccc !important;
+ border-radius: 3pt !important;
+ padding: 8pt !important;
+ overflow: visible !important;
+ white-space: pre-wrap !important;
+ page-break-inside: avoid !important;
+ }
+
+ .markdown-body pre code {
+ background: transparent !important;
+ border: none !important;
+ padding: 0 !important;
+ font-size: inherit !important;
+ }
+
+ /* ========== TABLES OPTIMIZATION ========== */
+ .markdown-body table {
+ border-collapse: collapse !important;
+ width: 100% !important;
+ margin-bottom: 12pt !important;
+ page-break-inside: avoid !important;
+ }
+
+ .markdown-body th,
+ .markdown-body td {
+ border: 1pt solid #666 !important;
+ padding: 4pt 8pt !important;
+ text-align: left !important;
+ vertical-align: top !important;
+ }
+
+ .markdown-body th {
+ background: #f0f0f0 !important;
+ font-weight: bold !important;
+ }
+
+ .markdown-body tbody tr:nth-child(even) {
+ background: #f9f9f9 !important;
+ }
+
+ /* ========== BLOCKQUOTES & ALERTS ========== */
+ .markdown-body blockquote {
+ border-left: 3pt solid #666 !important;
+ margin: 8pt 0 !important;
+ padding: 0 0 0 12pt !important;
+ font-style: italic !important;
+ background: #f9f9f9 !important;
+ page-break-inside: avoid !important;
+ }
+
+ /* Markdown alerts */
+ .markdown-body .markdown-alert {
+ border: 1pt solid #ccc !important;
+ border-radius: 3pt !important;
+ padding: 8pt !important;
+ margin: 8pt 0 !important;
+ page-break-inside: avoid !important;
+ }
+
+ .markdown-body .markdown-alert-note {
+ border-left: 3pt solid #0969da !important;
+ background: #f0f8ff !important;
+ }
+
+ .markdown-body .markdown-alert-tip {
+ border-left: 3pt solid #1a7f37 !important;
+ background: #f0fff0 !important;
+ }
+
+ .markdown-body .markdown-alert-important {
+ border-left: 3pt solid #8250df !important;
+ background: #faf0ff !important;
+ }
+
+ .markdown-body .markdown-alert-warning {
+ border-left: 3pt solid #9a6700 !important;
+ background: #fffbf0 !important;
+ }
+
+ .markdown-body .markdown-alert-caution {
+ border-left: 3pt solid #cf222e !important;
+ background: #fff0f0 !important;
+ }
+
+ /* ========== LINKS OPTIMIZATION ========== */
+ .markdown-body a {
+ text-decoration: underline !important;
+ }
+
+ /* Print URLs after links */
+ .markdown-body a[href]:after {
+ content: " (" attr(href) ")" !important;
+ font-size: 9pt !important;
+ color: #666 !important;
+ word-break: break-all !important;
+ }
+
+ /* Don't print URLs for relative links or anchors */
+ .markdown-body a[href^="#"]:after,
+ .markdown-body a[href^="/"]:after,
+ .markdown-body a[href*="javascript:"]:after {
+ content: "" !important;
+ }
+
+ /* ========== IMAGES OPTIMIZATION ========== */
+ .markdown-body img {
+ max-width: 100% !important;
+ height: auto !important;
+ page-break-inside: avoid !important;
+ border: 1pt solid #ddd !important;
+ }
+
+ /* Emoji should stay inline */
+ .markdown-body img.emoji {
+ border: none !important;
+ display: inline !important;
+ margin: 0 !important;
+ }
+
+ /* ========== NAVIGATION & UI ELEMENTS ========== */
+ /* Hide elements that don't make sense in print */
+ .anchor,
+ .octicon,
+ .task-list-item .handle,
+ [data-footnote-backref] {
+ display: none !important;
+ }
+
+ /* ========== PAGE BREAK CONTROL ========== */
+ .markdown-body h1,
+ .markdown-body h2 {
+ page-break-before: auto !important;
+ page-break-after: avoid !important;
+ }
+
+ .markdown-body h3,
+ .markdown-body h4,
+ .markdown-body h5,
+ .markdown-body h6 {
+ page-break-after: avoid !important;
+ }
+
+ .markdown-body pre,
+ .markdown-body blockquote,
+ .markdown-body table,
+ .markdown-body .markdown-alert {
+ page-break-inside: avoid !important;
+ }
+
+ /* Avoid orphans and widows */
+ .markdown-body p,
+ .markdown-body li {
+ orphans: 3 !important;
+ widows: 3 !important;
+ }
+
+ /* ========== MERMAID DIAGRAMS ========== */
+ /* Mermaid diagrams should be handled gracefully */
+ .mermaid {
+ page-break-inside: avoid !important;
+ border: 1pt solid #ddd !important;
+ padding: 8pt !important;
+ margin: 8pt 0 !important;
+ }
+
+ /* ========== UTILITY CLASSES ========== */
+ .print-break-before {
+ page-break-before: always !important;
+ }
+
+ .print-break-after {
+ page-break-after: always !important;
+ }
+
+ .print-no-break {
+ page-break-inside: avoid !important;
+ }
+
+ .print-hide {
+ display: none !important;
+ }
}
diff --git a/defaults/static/js/mermaid-init.js b/defaults/static/js/mermaid-init.js
new file mode 100644
index 0000000..787f162
--- /dev/null
+++ b/defaults/static/js/mermaid-init.js
@@ -0,0 +1,162 @@
+(function(){
+ function computeTheme(){
+ try {
+ var bodyTheme = document.body.getAttribute('data-theme') || 'auto';
+ if (bodyTheme === 'dark') return 'dark';
+ if (bodyTheme === 'light') return 'default';
+ // auto
+ var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
+ return prefersDark ? 'dark' : 'default';
+ } catch(e){
+ return 'default';
+ }
+ }
+
+ function escapeHTML(str){
+ return String(str)
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/\"/g, '"')
+ .replace(/'/g, ''');
+ }
+
+ function getCode(node){
+ return (node.dataset && node.dataset.code) ? node.dataset.code : (node.textContent || '');
+ }
+ function setCode(node, code){
+ if (node.dataset) node.dataset.code = code;
+ }
+
+ function renderWithAPI(id, code, node){
+ return new Promise(function(resolve, reject){
+ try {
+ // Prefer mermaid.render if available (Mermaid v10)
+ if (mermaid && typeof mermaid.render === 'function') {
+ var res1 = mermaid.render(id, code);
+ if (res1 && typeof res1.then === 'function') {
+ res1.then(function(out){
+ try {
+ if (out && out.svg) {
+ node.innerHTML = out.svg;
+ if (typeof out.bindFunctions === 'function') out.bindFunctions(node);
+ }
+ resolve();
+ } catch(e1){ reject(e1); }
+ }).catch(reject);
+ return;
+ } else if (res1 && typeof res1.svg === 'string') {
+ node.innerHTML = res1.svg;
+ if (typeof res1.bindFunctions === 'function') res1.bindFunctions(node);
+ resolve();
+ return;
+ }
+ }
+
+ // Fallback to mermaid.mermaidAPI.render (v8/v9)
+ if (mermaid && mermaid.mermaidAPI && typeof mermaid.mermaidAPI.render === 'function') {
+ var cbHandled = false;
+ var res2 = mermaid.mermaidAPI.render(id, code, function(svgCode, bindFns){
+ try {
+ cbHandled = true;
+ if (svgCode) {
+ node.innerHTML = svgCode;
+ if (typeof bindFns === 'function') bindFns(node);
+ }
+ resolve();
+ } catch(e2){ reject(e2); }
+ } /* do not pass container to avoid doc-related issues */);
+
+ if (res2 && typeof res2.then === 'function') {
+ res2.then(function(out){
+ try {
+ if (out && out.svg) {
+ node.innerHTML = out.svg;
+ if (typeof out.bindFunctions === 'function') out.bindFunctions(node);
+ }
+ resolve();
+ } catch(e3){ reject(e3); }
+ }).catch(reject);
+ } else if (typeof res2 === 'string') {
+ node.innerHTML = res2;
+ resolve();
+ } else if (!cbHandled) {
+ // Neither promise nor string nor callback? Treat as error
+ reject(new Error('Unexpected return from mermaidAPI.render'));
+ }
+ return;
+ }
+
+ reject(new Error('No supported Mermaid render API'));
+ } catch(e){ reject(e); }
+ });
+ }
+
+ async function renderAll(){
+ var theme = computeTheme();
+ if (!window.mermaid) return;
+ try {
+ if (!window.__goGripMermaidInitDone) {
+ mermaid.initialize({ startOnLoad: false, theme: theme, securityLevel: 'loose', logLevel: 'error' });
+ window.__goGripMermaidInitDone = true;
+ } else {
+ // Update theme dynamically if needed
+ if (mermaid && mermaid.initialize) {
+ mermaid.initialize({ startOnLoad: false, theme: theme, securityLevel: 'loose', logLevel: 'error' });
+ }
+ }
+ } catch(e) {
+ console.error('Mermaid init error:', e);
+ }
+
+ var nodes = Array.prototype.slice.call(document.querySelectorAll('.mermaid'));
+
+ // Sequential rendering to avoid race conditions / global state issues
+ for (var i = 0; i < nodes.length; i++) {
+ var node = nodes[i];
+ try {
+ var code = getCode(node).trim();
+ setCode(node, code);
+ if (mermaid.mermaidAPI && mermaid.mermaidAPI.parse) {
+ try {
+ mermaid.mermaidAPI.parse(code);
+ } catch (parseErr) {
+ console.error('Mermaid parse error:', parseErr, { index: i, code: code });
+ node.classList.add('mermaid-error');
+ node.innerHTML = '
Mermaid parse error:\\n' + escapeHTML(parseErr.str || parseErr.message || String(parseErr)) + ''; + continue; + } + } + + var id = 'mermaid-svg-' + i + '-' + Date.now(); + try { + await renderWithAPI(id, code, node); + node.classList.remove('mermaid-error'); + } catch(runErr){ + console.error('Mermaid render error:', runErr, { index: i, code: code }); + node.classList.add('mermaid-error'); + node.innerHTML = '
Mermaid render error:\\n' + escapeHTML(runErr && runErr.message || String(runErr)) + ''; + } + } catch(err){ + console.error('Mermaid error:', err, { index: i }); + node.classList.add('mermaid-error'); + node.innerHTML = '
Mermaid error:\\n' + escapeHTML(err.message || String(err)) + ''; + } + } + } + + document.addEventListener('DOMContentLoaded', function(){ + renderAll(); + + // Re-render on theme change when in auto mode + var themeAttr = document.body.getAttribute('data-theme') || 'auto'; + if (themeAttr === 'auto' && window.matchMedia) { + var mq = window.matchMedia('(prefers-color-scheme: dark)'); + if (mq && mq.addEventListener) { + mq.addEventListener('change', function(){ + renderAll(); + }); + } + } + }); +})(); \ No newline at end of file diff --git a/defaults/templates/layout.html b/defaults/templates/layout.html index 87f4545..306a35a 100644 --- a/defaults/templates/layout.html +++ b/defaults/templates/layout.html @@ -25,9 +25,10 @@ {{end}} + - +