Repository navigation
Expand file tree
/
Copy pathscript.js
More file actions
196 lines (170 loc) · 6.94 KB
/
Copy pathscript.js
File metadata and controls
196 lines (170 loc) · 6.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
document.addEventListener('DOMContentLoaded', () => {
// UI Elements
const textInput = document.getElementById('text-input');
const processTextBtn = document.getElementById('process-text-btn');
const exportBtn = document.getElementById('export-btn');
const printContainer = document.getElementById('print-container');
const fontSlider = document.getElementById('font-size');
const fontVal = document.getElementById('font-size-val');
const colSlider = document.getElementById('column-count');
const colVal = document.getElementById('column-count-val');
const smartAbbrToggle = document.getElementById('smart-abbr-toggle');
// Setup marked options
marked.setOptions({
gfm: true, // Github flavored markdown (supports tables)
breaks: true // Convert \n to <br>
});
// Dictionary for "Smart Abbreviations"
const abbrDict = {
'\\bwith\\b': 'w/',
'\\bwithout\\b': 'w/o',
'\\band\\b': '&',
'\\bbecause\\b': 'bc',
'\\btherefore\\b': '∴',
'\\bapproximately\\b': '≈',
'\\bimportant\\b': 'IMP',
'\\bexample\\b': 'ex',
'\\bmaximum\\b': 'max',
'\\bminimum\\b': 'min',
'\\bpositive\\b': '+ve',
'\\bnegative\\b': '-ve',
'\\bbetween\\b': 'btwn',
'\\bdefinition\\b': 'def',
'\\bfunction\\b': 'fn'
};
const abbrRules = Object.entries(abbrDict).map(([pattern, replace]) => ({
regex: new RegExp(pattern, 'gi'),
replace
}));
// Pre-process math (KaTeX)
// Avoids processing $ inside code blocks by temporarily masking them could be hard with regex.
// We will do a simple pass looking for $$...$$ and $...$
const renderMath = (text) => {
// Block math $$...$$
let processed = text.replace(/\$\$([\s\S]+?)\$\$/g, (match, formula) => {
try {
return katex.renderToString(formula, { displayMode: true, throwOnError: false });
} catch (e) {
return match;
}
});
// Inline math $...$
processed = processed.replace(/\$([^\n\$]+?)\$/g, (match, formula) => {
try {
return katex.renderToString(formula, { displayMode: false, throwOnError: false });
} catch (e) {
return match;
}
});
return processed;
};
// Apply Smart Abbreviations safely by traversing DOM text nodes
const applyAbbreviations = (element) => {
// Elements to skip completely
const skipTags = ['CODE', 'PRE', 'STYLE', 'SCRIPT', 'SVG', 'MATH'];
const skipClasses = ['katex', 'katex-display'];
if (element.nodeType === Node.TEXT_NODE) {
let text = element.textContent;
let modified = false;
abbrRules.forEach(rule => {
if (rule.regex.test(text)) {
text = text.replace(rule.regex, rule.replace);
modified = true;
}
});
if (modified) {
element.textContent = text;
}
} else if (element.nodeType === Node.ELEMENT_NODE) {
// Check if we should skip this element
if (skipTags.includes(element.tagName.toUpperCase())) return;
for (let c of skipClasses) {
if (element.classList.contains(c)) return;
}
// Recurse down children
Array.from(element.childNodes).forEach(child => applyAbbreviations(child));
}
};
// --- REAL TEXT PROCESSING ENGINE ---
const processInput = () => {
const rawText = textInput.value;
if (!rawText.trim()) {
printContainer.innerHTML = '<div class="micro-document"><p>No text provided.</p></div>';
return;
}
// 1. Escape HTML first to prevent code blocks from rendering as raw HTML
const escapedText = rawText.replace(/[&<>"']/g, function(m) {
return {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
}[m];
});
// 2. Render Math into HTML strings
let textWithMath = renderMath(escapedText);
// 3. Parse Markdown to HTML
let rawHtml = marked.parse(textWithMath);
// 4. Sanitize (allows standard markdown + mathml/katex structures)
// Adjust Purify to allow KaTeX math/svg tags and classes
const cleanHtml = DOMPurify.sanitize(rawHtml, {
ADD_TAGS: ['math', 'semantics', 'mrow', 'mi', 'mo', 'mn', 'msup', 'mspace', 'annotation', 'svg', 'path', 'g'],
ADD_ATTR: ['xmlns', 'viewBox', 'd', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin']
});
// 5. Wrap & apply abbreviations
const wrapper = document.createElement('div');
wrapper.className = 'micro-document';
wrapper.innerHTML = cleanHtml;
if (smartAbbrToggle.checked) {
applyAbbreviations(wrapper);
}
// 6. Update DOM
printContainer.innerHTML = '';
printContainer.appendChild(wrapper.cloneNode(true));
// Also update the preview modal if it exists
const previewPage = document.getElementById('preview-page');
if (previewPage) {
previewPage.innerHTML = '';
previewPage.appendChild(wrapper);
}
};
// Settings logic dynamically updates CSS Variables
const updatePrintVariables = () => {
document.documentElement.style.setProperty('--print-font-size', `${fontSlider.value}pt`);
document.documentElement.style.setProperty('--print-columns', `${colSlider.value}`);
fontVal.innerText = fontSlider.value;
colVal.innerText = colSlider.value;
};
// Event Listeners
fontSlider.addEventListener('input', updatePrintVariables);
colSlider.addEventListener('input', updatePrintVariables);
processTextBtn.addEventListener('click', () => {
processInput();
const modal = document.getElementById('preview-modal');
if (modal) modal.classList.remove('hidden');
});
const closeBtn = document.getElementById('close-modal-btn');
if (closeBtn) {
closeBtn.addEventListener('click', () => {
const modal = document.getElementById('preview-modal');
if (modal) modal.classList.add('hidden');
});
}
// Process smartly when typing (debounced)
let typingTimer;
textInput.addEventListener('input', () => {
clearTimeout(typingTimer);
typingTimer = setTimeout(processInput, 800);
});
smartAbbrToggle.addEventListener('change', processInput);
// Export / Print Logic
exportBtn.addEventListener('click', () => {
processInput(); // ensure latest is processed
setTimeout(() => {
window.print();
}, 100);
});
// Initialize properly
updatePrintVariables();
});