|
10 | 10 | } from '$lib/data/stores/lexicon.svelte'; |
11 | 11 | import type { SqlValue } from 'sql.js'; |
12 | 12 |
|
| 13 | + const clips = import.meta.glob('./*', { |
| 14 | + import: 'default', |
| 15 | + eager: true, |
| 16 | + base: '/src/gen-assets/clips' |
| 17 | + }) as Record<string, string>; |
| 18 | +
|
13 | 19 | interface Props { |
14 | 20 | wordIds: number[] | null; |
15 | 21 | onSelectWord: (word: SelectedWord) => void; |
|
27 | 33 | const dynamicQuery = wordIds.map(() => `id = ?`).join(' OR '); |
28 | 34 | const dynamicParams = wordIds.map((id) => id); |
29 | 35 | const results = db.exec(`SELECT xml FROM entries WHERE ${dynamicQuery}`, dynamicParams); |
30 | | - console.log('results:', results[0].values); |
31 | 36 |
|
32 | 37 | return results[0].values; |
33 | 38 | } catch (error) { |
|
42 | 47 | const parser = new DOMParser(); |
43 | 48 | const xmlDoc = parser.parseFromString(xmlString, 'text/xml'); |
44 | 49 |
|
| 50 | + // Collect audio elements to add at the end |
| 51 | + let audioElements = ''; |
| 52 | +
|
45 | 53 | const parseError = xmlDoc.querySelector('parsererror'); |
46 | 54 | if (parseError) { |
47 | 55 | console.error('XML parsing error:', parseError.textContent); |
|
89 | 97 |
|
90 | 98 | output += `<span class="clickable cursor-pointer" style="background-color: var(--BackgroundColor);" data-word="${word}" data-index="${index}" data-homonym="${homonymIndex}">${linkText}</span>`; |
91 | 99 | } |
| 100 | + } else if (node.tagName === 'audio-link' && node.hasAttribute('src')) { |
| 101 | + // Handle audio-link tag - create audio element and clickable link |
| 102 | + const audioFile = node.getAttribute('src'); |
| 103 | + const src = clips[`./${audioFile}`] ?? 'clips/' + audioFile; |
| 104 | + const audioId = 'audio-' + Math.random().toString(36).substr(2, 9); // Generate unique ID |
| 105 | +
|
| 106 | + // Collect audio element to add at the very end |
| 107 | + audioElements += `<audio id="${audioId}" src="${src}" preload="auto" style="display: none;"></audio>`; |
| 108 | +
|
| 109 | + // Add just the inline clickable icon - no audio element here |
| 110 | + output += `<button type="button" class="audio-link" data-audio-id="${audioId}" aria-label="Play audio" style="display: inline-block; vertical-align: middle; margin: 0 2px; width: 24px; height: 24px; overflow: visible;"><svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" style="display: block; overflow: visible;"><path d="M14 20.725v-2.05q2.25-.65 3.625-2.5t1.375-4.2q0-2.35-1.375-4.2T14 5.275v-2.05q3.1.7 5.05 3.137Q21 8.8 21 11.975q0 3.175-1.95 5.612-1.95 2.438-5.05 3.138ZM3 15V9h4l5-5v16l-5-5Zm11 1V7.95q1.175.55 1.838 1.65.662 1.1.662 2.4q0 1.275-.662 2.362Q15.175 15.45 14 16Z"/></svg></button>`; |
92 | 111 | } else { |
93 | 112 | output += `<${node.tagName}`; |
94 | 113 | for (let attr of node.attributes) { |
|
123 | 142 | return output; |
124 | 143 | } |
125 | 144 |
|
126 | | - return processNode(xmlDoc.documentElement); |
| 145 | + return processNode(xmlDoc.documentElement) + audioElements; |
127 | 146 | } |
128 | 147 |
|
129 | 148 | async function updateXmlData() { |
|
166 | 185 | }); |
167 | 186 | }); |
168 | 187 | }); |
| 188 | +
|
| 189 | + const audioButtons = document.querySelectorAll('.audio-link'); |
| 190 | + audioButtons.forEach((button) => { |
| 191 | + button.addEventListener('click', () => { |
| 192 | + const audioId = button.getAttribute('data-audio-id'); |
| 193 | + const audioElement = document.getElementById(audioId) as HTMLAudioElement; |
| 194 | + if (audioElement) { |
| 195 | + audioElement.play(); |
| 196 | + } |
| 197 | + }); |
| 198 | + }); |
169 | 199 | } |
170 | 200 |
|
171 | 201 | function applyStyles() { |
|
0 commit comments