PageSourceSearch

https://www.ezfurigana.com/static/js/vocab_renderer.1562f65439be.js

js ezfurigana.com collected 2026-10-02 00:49:17 UTC 7,510 bytes, 222 lines download raw bytes

1/**
2 * vocab_renderer.js
3 * Shared vocabulary table rendering utilities for the main app and PDF editor.
4 *
5 * Exposes window.VocabRenderer with:
6 *   - filterVocabByThreshold(data, threshold)
7 *   - renderVocabTable(containerEl, data, options)
8 *   - buildAnkiTsv(data)
9 *   - exportAnkiTsv(data, filename)
10 *
11 * Expected VocabItem schema for renderVocabTable/buildAnkiTsv:
12 *   { word, reading, originalWord?, context?, jlptLevel, posTag, frequency, order,
13 *     isAmbiguous?, altReadings?, hasDefinitions? }
14 */
15(function (global) {
16  'use strict';
17
18  function t(key, fallback) {
19    var value = global.AppConfig && global.AppConfig.i18n && global.AppConfig.i18n[key];
20    return typeof value === 'string' && value.length > 0 ? value : fallback;
21  }
22
23  function filterVocabByThreshold(data, threshold) {
24    var t = Math.round(parseFloat(threshold));
25    return data.filter(function (item) {
26      return item.jlptLevel === 0 || item.jlptLevel <= t;
27    });
28  }
29
30  function renderVocabTable(containerEl, data, options) {
31    var opts = Object.assign({
32      readingVisible: true,
33      furiganaHideOnHover: false,
34      activeSortBy: '',
35      translatePosFn: function (x) { return x; },
36      flagBtnTitle: t('reportError', 'Report error'),
37      definitionsBtnTitle: t('showDefinitions', 'Show definitions')
38    }, options);
39
40    containerEl.innerHTML = '';
41
42    if (data.length === 0) {
43      var emptyRow = document.createElement('tr');
44      var emptyTd = document.createElement('td');
45      emptyTd.colSpan = 3;
46      emptyTd.className = 'vocabulary-ledger-empty';
47      emptyTd.textContent = t('noVocabData', 'No vocabulary data available');
48      emptyRow.appendChild(emptyTd);
49      containerEl.appendChild(emptyRow);
50      return;
51    }
52
53    data.forEach(function (item) {
54      var word = item.word;
55      var reading = item.reading;
56      var jlptLevel = item.jlptLevel || 0;
57      var posTag = item.posTag || '';
58
59      var row = document.createElement('tr');
60      row.className = 'vocabulary-ledger-row';
61      if (opts.furiganaHideOnHover) { row.className += ' hide-on-hover'; }
62
63      // --- First cell: word + badges ---
64      var td1 = document.createElement('td');
65      td1.className = 'vocabulary-ledger-term';
66      td1.dataset.surface = word;
67      td1.dataset.dictForm = word;
68      td1.dataset.reading = reading;
69      td1.dataset.jlpt = String(jlptLevel);
70      td1.dataset.pos = posTag;
71      td1.dataset.altReadings = item.altReadings || '';
72      td1.dataset.ambiguous = String(!!item.isAmbiguous);
73
74      var wordSpan = document.createElement('span');
75      wordSpan.className = 'vocabulary-ledger-word';
76      wordSpan.dataset.word = word;
77      wordSpan.dataset.dictForm = word;
78      wordSpan.dataset.reading = reading;
79      wordSpan.textContent = word;
80      td1.appendChild(wordSpan);
81
82      // JLPT badge
83      if (jlptLevel > 0) {
84        var jlptBadge = document.createElement('span');
85        jlptBadge.className = 'vocabulary-ledger-tag';
86        jlptBadge.textContent = 'N' + jlptLevel;
87        td1.appendChild(jlptBadge);
88      }
89
90      // POS badge
91      if (posTag) {
92        var posBadge = document.createElement('span');
93        posBadge.className = 'vocabulary-ledger-tag';
94        posBadge.textContent = opts.translatePosFn(posTag);
95        td1.appendChild(posBadge);
96      }
97
98      // Frequency badge
99      if (opts.activeSortBy === 'frequency' && item.frequency > 1) {
100        var freqBadge = document.createElement('span');
101        freqBadge.className = 'vocabulary-ledger-tag';
102        freqBadge.textContent = '\u00d7' + item.frequency;
103        td1.appendChild(freqBadge);
104      }
105
106      row.appendChild(td1);
107
108      // --- Second cell: reading ---
109      var td2 = document.createElement('td');
110      td2.className = 'vocabulary-ledger-reading-cell';
111
112      var readingSpan = document.createElement('span');
113      readingSpan.className = opts.readingVisible
114        ? 'vocabulary-ledger-reading'
115        : 'vocabulary-ledger-reading reading-masked px-2 py-0.5';
116      readingSpan.textContent = reading;
117
118      // Help button for ambiguous words with definitions
119      if (item.isAmbiguous && item.altReadings) {
120        var altList = item.altReadings.split(',');
121        readingSpan.textContent = reading + '\u3001' + altList.join('\u3001');
122        if (item.hasDefinitions) {
123          var helpBtn = document.createElement('button');
124          helpBtn.className = 'definition-help-btn ml-2';
125          helpBtn.dataset.word = word;
126          helpBtn.title = opts.definitionsBtnTitle;
127          helpBtn.textContent = '?';
128          readingSpan.appendChild(helpBtn);
129        }
130      }
131
132      td2.appendChild(readingSpan);
133      row.appendChild(td2);
134
135      // --- Third cell: flag button ---
136      var td3 = document.createElement('td');
137      td3.className = 'vocabulary-ledger-action-cell';
138
139      var flagBtn = document.createElement('button');
140      flagBtn.className = 'vocabulary-ledger-report flag-btn error-report-btn';
141      flagBtn.dataset.word = word;
142      flagBtn.dataset.reading = reading;
143      flagBtn.title = opts.flagBtnTitle;
144
145      var svgNs = 'http://www.w3.org/2000/svg';
146      var svg = document.createElementNS(svgNs, 'svg');
147      svg.classList.add('w-5', 'h-5');
148      svg.setAttribute('fill', 'none');
149      svg.setAttribute('stroke', 'currentColor');
150      svg.setAttribute('stroke-width', '2');
151      svg.setAttribute('stroke-linecap', 'round');
152      svg.setAttribute('stroke-linejoin', 'round');
153      svg.setAttribute('viewBox', '0 0 24 24');
154
155      var path1 = document.createElementNS(svgNs, 'path');
156      path1.setAttribute('d', 'M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z');
157      svg.appendChild(path1);
158
159      var line1 = document.createElementNS(svgNs, 'line');
160      line1.setAttribute('x1', '4');
161      line1.setAttribute('y1', '22');
162      line1.setAttribute('x2', '4');
163      line1.setAttribute('y2', '15');
164      svg.appendChild(line1);
165
166      flagBtn.appendChild(svg);
167      td3.appendChild(flagBtn);
168      row.appendChild(td3);
169
170      containerEl.appendChild(row);
171    });
172  }
173
174  function normalizeAnkiTsvField(value) {
175    return String(value == null ? '' : value)
176      .replace(/[\t\r\n]+/g, ' ')
177      .trim();
178  }
179
180  function buildAnkiTsv(data) {
181    var headers = [
182      '#separator:Tab',
183      '#html:false',
184      '#tags:ezfurigana',
185      '#columns:Expression\tReading\tSurface\tContext\tJLPT\tPart of Speech\tFrequency\tAlternative Readings'
186    ];
187    var rows = data.map(function (item) {
188      return [
189        item.word,
190        item.reading,
191        item.originalWord || item.word,
192        item.context || '',
193        item.jlptLevel ? 'N' + item.jlptLevel : '',
194        item.posTag || '',
195        item.frequency || 1,
196        item.altReadings || ''
197      ].map(normalizeAnkiTsvField).join('\t');
198    });
199    return headers.concat(rows).join('\n');
200  }
201
202  function exportAnkiTsv(data, filename) {
203    var blob = new Blob(['\ufeff' + buildAnkiTsv(data)], {
204      type: 'text/tab-separated-values;charset=utf-8'
205    });
206    var url = URL.createObjectURL(blob);
207    var a = document.createElement('a');
208    Object.assign(a, { href: url, download: filename });
209    document.body.appendChild(a);
210    a.click();
211    document.body.removeChild(a);
212    URL.revokeObjectURL(url);
213  }
214
215  global.VocabRenderer = {
216    filterVocabByThreshold: filterVocabByThreshold,
217    renderVocabTable: renderVocabTable,
218    buildAnkiTsv: buildAnkiTsv,
219    exportAnkiTsv: exportAnkiTsv
220  };
221
222}(window));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.