_reader.js 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. // resources/js/modules/_reader.js
  2. export function initReader() {
  3. injectCommentaryMarkers();
  4. }
  5. function injectCommentaryMarkers() {
  6. let counter = 0;
  7. document
  8. .querySelectorAll('div.sentence[data-note-id]')
  9. .forEach((sentenceEl) => {
  10. const uid = sentenceEl.dataset.noteId;
  11. counter++;
  12. const id = `commentary-${counter}`;
  13. // checkbox:控制展开,紧跟在 sentence 后
  14. const checkbox = document.createElement('input');
  15. checkbox.type = 'checkbox';
  16. checkbox.className = 'commentary-toggle';
  17. checkbox.id = id;
  18. // label(icon):行内,插在句子文字末尾
  19. const label = document.createElement('label');
  20. label.htmlFor = id;
  21. label.className = 'commentary-icon';
  22. label.innerHTML = '<i class="ti ti-message-circle"></i>';
  23. // 注释块:紧跟在 checkbox 后(CSS 相邻选择器依赖此顺序)
  24. const note = document.createElement('div');
  25. note.className = 'commentary-note';
  26. note.dataset.uuid = uid;
  27. note.dataset.loaded = 'false';
  28. // label 插入句子内文字末尾(行内不打断文字流)
  29. const innerSpan = sentenceEl.querySelector(':scope > span');
  30. (innerSpan ?? sentenceEl).appendChild(label);
  31. // sentence 后:先插 note,再插 checkbox(after 逆序)
  32. // 最终顺序:div.sentence → input.commentary-toggle → div.commentary-note
  33. sentenceEl.after(note);
  34. sentenceEl.after(checkbox);
  35. // 点击时懒加载
  36. checkbox.addEventListener('change', async () => {
  37. if (!checkbox.checked) {
  38. return;
  39. }
  40. if (note.dataset.loaded === 'true') {
  41. return;
  42. }
  43. note.innerHTML =
  44. '<span class="text-muted small">加载中…</span>';
  45. await fetchCommentary(note);
  46. });
  47. });
  48. }
  49. async function fetchCommentary(noteEl) {
  50. const uuid = noteEl.dataset.uuid;
  51. try {
  52. const res = await fetch(`/api/v2/sentence/${uuid}?format=html`);
  53. if (!res.ok) {
  54. throw new Error(res.status);
  55. }
  56. const json = await res.json();
  57. if (!json.ok) {
  58. throw new Error('api error');
  59. }
  60. noteEl.innerHTML = json.data.html;
  61. noteEl.dataset.loaded = 'true';
  62. } catch {
  63. noteEl.innerHTML = '<span class="text-muted small">加载失败</span>';
  64. }
  65. }