Skip to content

Commit e208ca4

Browse files
staredclaude
andcommitted
Refactor for code clarity and brevity
- Extract shared extractTermOrder() utility in parser.ts - Simplify enableTermPointerEvents() to only target term elements - Pass parsedContent to ExportControls (avoid double-parsing) - Consolidate LaTeX description converter in htmlConverter.ts - Add buildTermColorMap() for O(1) color lookups 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 2ada052 commit e208ca4

File tree

10 files changed

+57
-69
lines changed

10 files changed

+57
-69
lines changed

src/App.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<button class="toolbar-btn" title="Show/hide editor" @click="editorCollapsed = !editorCollapsed">
2424
<span class="icon">{{ editorCollapsed ? '▶' : '◀' }}</span>
2525
</button>
26-
<ExportControls :markdown="markdown" :colors="colorScheme" />
26+
<ExportControls :parsed-content="parsedContent" :colors="colorScheme" />
2727
<a href="https://github.com/stared/equations-explained-colorfully" class="toolbar-link" target="_blank" rel="noopener">Contribute</a>
2828
</div>
2929
<div class="editor-container">

src/components/CentralPanel.vue

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
import { ref, computed, watch } from 'vue'
3636
import type { ParsedContent } from '../utils/parser'
3737
import type { ColorScheme } from '../export'
38-
import { getTermColor as getColor } from '../utils/colorSchemes'
38+
import { buildTermColorMap } from '../utils/colorSchemes'
3939
4040
import EquationDisplay from './equation/EquationDisplay.vue'
4141
import DescriptionPanel from './equation/DescriptionPanel.vue'
@@ -48,9 +48,11 @@ const props = defineProps<{
4848
4949
const termOrder = computed(() => props.content.termOrder)
5050
51-
// Color helper
51+
// Build color map once (O(1) lookups instead of O(n) indexOf)
52+
const termColorMap = computed(() => buildTermColorMap(termOrder.value, props.colors))
53+
5254
function getTermColor(term: string): string {
53-
return getColor(term, termOrder.value, props.colors)
55+
return termColorMap.value.get(term) ?? '#000000'
5456
}
5557
5658
// Hover/click state (owned internally)

src/components/MarkdownEditor.vue

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
77
import { CodeJar } from 'codejar'
88
import Prism from 'prismjs'
9-
import { parseContent } from '../utils/parser'
9+
import { parseContent, extractTermOrder } from '../utils/parser'
1010
import type { ColorScheme } from '../export'
1111
1212
// ============================================================================
@@ -98,27 +98,9 @@ Prism.languages.eqmd = {
9898
9999
// Extract ONLY terms from equation section ($$...$$)
100100
function extractEquationTerms(markdown: string): string[] {
101-
const terms: string[] = []
102-
const seenTerms = new Set<string>()
103-
104-
// Extract equation block (everything between $$...$$)
105101
const equationMatch = markdown.match(/\$\$([\s\S]*?)\$\$/)
106-
if (!equationMatch) return terms
107-
108-
const equationContent = equationMatch[1]
109-
110-
// Extract \mark[classname] in order of appearance
111-
const markPattern = /\\mark\[([^\]]+)\]/g
112-
let match
113-
while ((match = markPattern.exec(equationContent)) !== null) {
114-
const term = match[1]
115-
if (!seenTerms.has(term)) {
116-
terms.push(term)
117-
seenTerms.add(term)
118-
}
119-
}
120-
121-
return terms
102+
if (!equationMatch) return []
103+
return extractTermOrder(equationMatch[1])
122104
}
123105
124106
// Helper to mark tokens with errors based on a condition

src/components/controls/ExportControls.vue

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,33 +19,23 @@
1919
</template>
2020

2121
<script setup lang="ts">
22-
import { ref, computed } from 'vue'
23-
import { parseContent } from '../../utils/parser'
22+
import { ref } from 'vue'
23+
import type { ParsedContent } from '../../utils/parser'
2424
import type { ColorScheme, ExportFormat } from '../../export'
2525
2626
const props = defineProps<{
27-
markdown: string
27+
parsedContent: ParsedContent | null
2828
colors: ColorScheme
2929
}>()
3030
3131
const selectedFormat = ref('')
3232
33-
// Parse markdown internally when needed
34-
const parsedContent = computed(() => {
35-
if (!props.markdown.trim()) return null
36-
try {
37-
return parseContent(props.markdown)
38-
} catch {
39-
return null
40-
}
41-
})
42-
4333
async function handleExport() {
44-
if (!selectedFormat.value || !parsedContent.value) return
34+
if (!selectedFormat.value || !props.parsedContent) return
4535
4636
const { exportContent, getFileExtension } = await import('../../export')
4737
const format = selectedFormat.value as ExportFormat
48-
const content = exportContent(format, parsedContent.value, props.colors)
38+
const content = exportContent(format, props.parsedContent, props.colors)
4939
const extension = getFileExtension(format)
5040
5141
// Download file
@@ -62,10 +52,10 @@ async function handleExport() {
6252
}
6353
6454
async function handleCopy() {
65-
if (!parsedContent.value) return
55+
if (!props.parsedContent) return
6656
6757
const { exportContent } = await import('../../export')
68-
const content = exportContent('html', parsedContent.value, props.colors)
58+
const content = exportContent('html', props.parsedContent, props.colors)
6959
7060
try {
7161
await navigator.clipboard.writeText(content)

src/export/beamerExport.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import type { ParsedContent } from '../utils/parser';
55
import type { ColorScheme } from '.';
66
import { transformHtmlClass } from '../utils/latex';
7-
import { convertHtmlDescription } from './htmlConverter';
7+
import { convertDescriptionToLatex } from './htmlConverter';
88
import { escapePreservingMath, escapeLaTeX } from './escape';
99
import { getTermColor } from '../utils/colorSchemes';
1010

@@ -21,15 +21,6 @@ function injectTikzNodesInLatex(latex: string): { latex: string; nodeCount: numb
2121
return { latex: result, nodeCount };
2222
}
2323

24-
// Convert HTML description to LaTeX text
25-
function convertDescriptionToLatex(html: string): string {
26-
return convertHtmlDescription(
27-
html,
28-
escapeLaTeX,
29-
(className, content) => `\\textcolor{term${className}}{${escapeLaTeX(content)}}`
30-
);
31-
}
32-
3324
/**
3425
* Export to Beamer with TikZ arrows (presentation format)
3526
* Based on texample.net/tikz/examples/beamer-arrows/ pattern

src/export/htmlConverter.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { escapeLaTeX } from './escape';
2+
13
// Transform HTML <span class="term-X">content</span> to custom format
24
export function convertHtmlDescription(
35
html: string,
@@ -54,3 +56,12 @@ export function convertHtmlDescription(
5456

5557
return result.trim();
5658
}
59+
60+
// Convert HTML description to LaTeX with \textcolor (shared by latexExport and beamerExport)
61+
export function convertDescriptionToLatex(html: string): string {
62+
return convertHtmlDescription(
63+
html,
64+
escapeLaTeX,
65+
(className, content) => `\\textcolor{term${className}}{${escapeLaTeX(content)}}`
66+
);
67+
}

src/export/latexExport.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import type { ParsedContent } from '../utils/parser';
55
import type { ColorScheme } from '.';
66
import { transformHtmlClass } from '../utils/latex';
7-
import { convertHtmlDescription } from './htmlConverter';
7+
import { convertDescriptionToLatex } from './htmlConverter';
88
import { escapePreservingMath, escapeLaTeX } from './escape';
99
import { getTermColor } from '../utils/colorSchemes';
1010

@@ -18,15 +18,6 @@ function stripHtmlClassForLatex(latex: string): string {
1818
);
1919
}
2020

21-
// Convert HTML description to LaTeX text
22-
function convertDescriptionToLatex(html: string): string {
23-
return convertHtmlDescription(
24-
html,
25-
escapeLaTeX,
26-
(className, content) => `\\textcolor{term${className}}{${escapeLaTeX(content)}}`
27-
);
28-
}
29-
3021
/**
3122
* Export to LaTeX (complete document with xcolor)
3223
* Non-interactive: colors defined in preamble, applied with \textcolor

src/utils/colorSchemes.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,10 @@ export function getTermColor(term: string, termOrder: string[], scheme: ColorSch
3737
if (index === -1) return '#000000'
3838
return scheme.colors[index] ?? '#000000'
3939
}
40+
41+
// Build term→color map for O(1) lookups (use in computed properties)
42+
export function buildTermColorMap(termOrder: string[], scheme: ColorScheme): Map<string, string> {
43+
return new Map(
44+
termOrder.map((term, i) => [term, scheme.colors[i] ?? '#000000'])
45+
)
46+
}

src/utils/parser.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22

33
import { findMatchingBrace } from './latex';
44

5+
/**
6+
* Extract term names from \mark[term] patterns in equation content (for color ordering)
7+
*/
8+
export function extractTermOrder(equationContent: string): string[] {
9+
const terms: string[] = []
10+
const seen = new Set<string>()
11+
const pattern = /\\mark\[([^\]]+)\]/g
12+
let match
13+
while ((match = pattern.exec(equationContent)) !== null) {
14+
if (!seen.has(match[1])) {
15+
terms.push(match[1])
16+
seen.add(match[1])
17+
}
18+
}
19+
return terms
20+
}
21+
522
export interface ParsedContent {
623
title: string; // Title from # heading
724
latex: string; // LaTeX equation with \htmlClass annotations

src/utils/termDom.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,11 @@ export function setupTermListeners(
3939
}
4040
}
4141

42-
/** Set pointer-events on term elements (needed for KaTeX) */
42+
/** Set pointer-events on term elements (needed for KaTeX which nests many spans) */
4343
export function enableTermPointerEvents(container: HTMLElement): void {
44-
container.querySelectorAll('*').forEach((el) => {
44+
container.querySelectorAll('[class*="term-"]').forEach((el) => {
4545
const htmlEl = el as HTMLElement
46-
const hasTermClass = Array.from(el.classList).some(c => c.startsWith('term-'))
47-
htmlEl.style.pointerEvents = hasTermClass ? 'auto' : 'none'
48-
if (hasTermClass) {
49-
htmlEl.style.cursor = 'pointer'
50-
}
46+
htmlEl.style.pointerEvents = 'auto'
47+
htmlEl.style.cursor = 'pointer'
5148
})
5249
}

0 commit comments

Comments
 (0)