Просмотр исходного кода

feat(api-v13): 阅读设置增加「注释」行内/段后呈现模式

visuddhinanda 1 день назад
Родитель
Сommit
19f87feb04

+ 88 - 7
api-v13/resources/css/modules/reader-content.css

@@ -155,11 +155,8 @@ article.reader-body label.sidenote-number {
     }
 }
 
-/* sidenote 编号上标 */
-article.reader-body .sidenote-number {
-    counter-increment: sidenote-counter;
-}
-
+/* sidenote 编号上标:编号由 JS 按「段」分组写入 data-idx(见 reader.js 的
+   numberAnnotations),与段后脚注的 [N] 一一对应,可互跳。 */
 article.reader-body .sidenote-number::after,
 article.reader-body .sidenote::before {
     position: relative;
@@ -167,7 +164,7 @@ article.reader-body .sidenote::before {
 }
 
 article.reader-body .sidenote-number::after {
-    content: '[' counter(sidenote-counter) ']';
+    content: '[' attr(data-idx) ']';
     font-size: 0.75rem;
     top: -0.5rem;
     left: 0.1rem;
@@ -175,7 +172,7 @@ article.reader-body .sidenote-number::after {
 }
 
 article.reader-body .sidenote::before {
-    content: counter(sidenote-counter) ' ';
+    content: attr(data-idx) ' ';
     font-size: 0.75rem;
     top: -0.5rem;
 }
@@ -231,6 +228,90 @@ article.reader-body .commentary-toggle:checked + .commentary-note {
     background-color: rgba(139, 109, 56, 0.15);
 }
 
+/* ══════════════════════════════════════════
+   三·五、注释呈现模式(行内 / 段后)
+   「段后」时正文保留角标 [N],注释以脚注列表缀在段后;「行内」沿用 sidenote 边注。
+   DOM 结构与 wikipali-mobile 的 ReaderLayerPane 保持一致。
+   ══════════════════════════════════════════ */
+
+/* 段后脚注列表:默认(行内模式)隐藏,段后模式由 [data-annotation-mode] 打开 */
+article.reader-body .anno-footnotes {
+    display: none;
+    margin: 0.5rem 0 1rem;
+}
+
+article.reader-body .anno-footnote {
+    margin: 0.4rem 0;
+    font-size: 0.875rem;
+    line-height: 1.6;
+    color: var(--tblr-secondary);
+    border: 1px solid var(--tblr-border-color);
+    border-radius: 6px;
+    padding: 6px 10px;
+}
+
+article.reader-body .anno-fn-toggle {
+    display: none;
+}
+
+article.reader-body .anno-fn-label {
+    display: flex;
+    align-items: flex-start;
+    cursor: pointer;
+}
+
+article.reader-body .anno-fn-num {
+    color: var(--tblr-primary);
+    flex: none;
+    margin-right: 0.45em;
+    cursor: pointer;
+}
+
+/* 脚注正文默认收起为一行,点击展开 */
+article.reader-body .anno-fn-body {
+    flex: 1;
+    min-width: 0;
+    display: -webkit-box;
+    -webkit-line-clamp: 1;
+    -webkit-box-orient: vertical;
+    overflow: hidden;
+    cursor: pointer;
+}
+
+article.reader-body .anno-fn-toggle:checked + .anno-fn-label .anno-fn-body {
+    -webkit-line-clamp: unset;
+    display: block;
+    overflow: visible;
+}
+
+/* 义注跳转 cite:收起时隐藏,展开才显示 */
+article.reader-body .anno-footnote .anno-jump {
+    display: none;
+    margin-left: 0.4em;
+    font-style: normal;
+    color: var(--tblr-primary);
+}
+
+article.reader-body .anno-fn-toggle:checked ~ .anno-jump {
+    display: inline;
+}
+
+/* 互跳高亮 */
+article.reader-body .anno-highlight {
+    background: rgba(32, 107, 196, 0.16);
+    border-radius: 3px;
+    transition: background 0.5s;
+}
+
+/* 段后模式:显示段后脚注列表,隐藏 sidenote(角标保留) */
+article.reader-body[data-annotation-mode="footnote"] .anno-footnotes {
+    display: block;
+}
+
+article.reader-body[data-annotation-mode="footnote"] .sidenote {
+    display: none !important;
+}
+
 /* ══════════════════════════════════════════
    四、正文内 origin(原文显示)
    ══════════════════════════════════════════ */

+ 103 - 0
api-v13/resources/js/modules/reader.js

@@ -1,10 +1,34 @@
 // resources/js/modules/reader.js
 
 export function initReader() {
+    applyAnnotationMode();
+    numberAnnotations();
     injectCommentaryMarkers();
     injectEvaluateMarkers();
     initTocToggle();
     initHashScroll();
+    initAnnotationMode();
+}
+
+// 读取阅读偏好 cookie(与 read.blade.php 内联脚本的 setCookie 对应)。注意:该 cookie
+// 由前端 document.cookie 明文写入,不经过 Laravel 加密,只能在客户端读。
+function readCookie(name) {
+    const value = `; ${document.cookie}`;
+    const parts = value.split(`; ${name}=`);
+    if (parts.length === 2) {
+        return decodeURIComponent(parts.pop().split(';').shift());
+    }
+    return null;
+}
+
+// 把「注释」呈现模式(行内 / 段后)从 cookie 应用到正文根节点。
+function applyAnnotationMode() {
+    const article = document.querySelector('article.reader-body');
+    if (!article) {
+        return;
+    }
+    article.dataset.annotationMode =
+        readCookie('annotation_mode') === 'footnote' ? 'footnote' : 'inline';
 }
 
 // 处理 URL hash(#354 / #94-354 / #para-354):滚动到对应段落并加高亮底色
@@ -57,6 +81,85 @@ function initTocToggle() {
     });
 }
 
+// 注释编号:按「段」分组,给正文角标 .sidenote-number 与段后 .anno-footnote 分别
+// 编号 [1][2]…,二者一一对应(参照 wikipali-mobile 的 numberPara)。段后脚注的编号
+// <span class="anno-fn-num"> 由 JS 动态补进 label。
+function numberAnnotations() {
+    document
+        .querySelectorAll('article.reader-body div[data-para]')
+        .forEach((para) => {
+            para.querySelectorAll('.sidenote-number').forEach((marker, i) => {
+                marker.dataset.idx = String(i + 1);
+            });
+
+            para.querySelectorAll('.anno-footnote').forEach((fn, i) => {
+                fn.dataset.idx = String(i + 1);
+                const label = fn.querySelector('.anno-fn-label');
+                if (label && !label.querySelector('.anno-fn-num')) {
+                    const num = document.createElement('span');
+                    num.className = 'anno-fn-num';
+                    num.textContent = `[${i + 1}]`;
+                    num.dataset.idx = String(i + 1);
+                    label.insertBefore(num, label.firstChild);
+                }
+            });
+        });
+}
+
+// 段后模式下的互跳:点正文角标 [N] → 滚到对应脚注;点脚注编号 [N] → 滚回正文角标。
+// 行内模式不挂此监听,保留 label 的原生 checkbox 展开行为。
+function initAnnotationMode() {
+    const article = document.querySelector('article.reader-body');
+    if (!article || article.dataset.annotationMode !== 'footnote') {
+        return;
+    }
+
+    const flashAnno = (el) => {
+        if (!el) {
+            return;
+        }
+        el.classList.add('anno-highlight');
+        setTimeout(() => el.classList.remove('anno-highlight'), 2200);
+    };
+
+    article.addEventListener('click', (e) => {
+        const marker = e.target.closest('.sidenote-number');
+        if (marker) {
+            const para = marker.closest('div[data-para]');
+            const fn =
+                para &&
+                para.querySelector(
+                    `.anno-footnote[data-idx="${marker.dataset.idx}"]`
+                );
+            if (fn) {
+                e.preventDefault();
+                fn.scrollIntoView({ block: 'center', behavior: 'smooth' });
+                flashAnno(fn);
+            }
+            return;
+        }
+
+        const fnNum = e.target.closest('.anno-fn-num');
+        if (fnNum) {
+            const fn = fnNum.closest('.anno-footnote');
+            const para = fn && fn.closest('div[data-para]');
+            const marker =
+                para &&
+                para.querySelector(
+                    `.sidenote-number[data-idx="${fnNum.dataset.idx}"]`
+                );
+            if (marker) {
+                e.preventDefault();
+                marker.scrollIntoView({
+                    block: 'center',
+                    behavior: 'smooth',
+                });
+                flashAnno(marker);
+            }
+        }
+    });
+}
+
 function injectCommentaryMarkers() {
     let counter = 0;
 

+ 3 - 0
api-v13/resources/lang/en/library.php

@@ -131,6 +131,9 @@ return [
     'script_myanmar' => 'Myanmar',
     'script_thai' => 'Thai',
     'commentary_version' => 'Commentary Version',
+    'annotation_mode' => 'Annotations',
+    'annotation_inline' => 'Inline',
+    'annotation_footnote' => 'After paragraph',
     'none' => 'None',
     'cancel' => 'Cancel',
     'confirm' => 'OK',

+ 3 - 0
api-v13/resources/lang/lo/library.php

@@ -130,6 +130,9 @@ return [
     'script_myanmar' => 'ມຽນມາ',
     'script_thai' => 'ໄທ',
     'commentary_version' => 'ເວີຊັນອັດຖະກະຖາ',
+    'annotation_mode' => 'ໝາຍເຫດ',
+    'annotation_inline' => 'ໃນແຖວ',
+    'annotation_footnote' => 'ຫຼັງວັກ',
     'none' => 'ບໍ່ມີ',
     'cancel' => 'ຍົກເລີກ',
     'confirm' => 'ຕົກລົງ',

+ 3 - 0
api-v13/resources/lang/my/library.php

@@ -130,6 +130,9 @@ return [
     'script_myanmar' => 'မြန်မာ',
     'script_thai' => 'ထိုင်း',
     'commentary_version' => 'အနက်ဖြာဗားရှင်း',
+    'annotation_mode' => 'မှတ်ချက်',
+    'annotation_inline' => 'စာကြောင်းအတွင်း',
+    'annotation_footnote' => 'စာပိုဒ်နောက်',
     'none' => 'မရှိ',
     'cancel' => 'မလုပ်တော့',
     'confirm' => 'အတည်ပြုပါ',

+ 3 - 0
api-v13/resources/lang/si/library.php

@@ -130,6 +130,9 @@ return [
     'script_myanmar' => 'මියන්මාර්',
     'script_thai' => 'තායි',
     'commentary_version' => 'අටුවා අනුවාදය',
+    'annotation_mode' => 'ටිප්පණි',
+    'annotation_inline' => 'පේළිය තුළ',
+    'annotation_footnote' => 'ඡේදයෙන් පසු',
     'none' => 'කිසිවක් නැත',
     'cancel' => 'අවලංගු කරන්න',
     'confirm' => 'හරි',

+ 3 - 0
api-v13/resources/lang/th/library.php

@@ -130,6 +130,9 @@ return [
     'script_myanmar' => 'พม่า',
     'script_thai' => 'ไทย',
     'commentary_version' => 'เวอร์ชันอรรถกถา',
+    'annotation_mode' => 'เชิงอรรถ',
+    'annotation_inline' => 'ในบรรทัด',
+    'annotation_footnote' => 'หลังย่อหน้า',
     'none' => 'ไม่มี',
     'cancel' => 'ยกเลิก',
     'confirm' => 'ตกลง',

+ 3 - 0
api-v13/resources/lang/vi/library.php

@@ -130,6 +130,9 @@ return [
     'script_myanmar' => 'Miến Điện',
     'script_thai' => 'Thái',
     'commentary_version' => 'Phiên bản chú giải',
+    'annotation_mode' => 'Chú thích',
+    'annotation_inline' => 'Trong dòng',
+    'annotation_footnote' => 'Sau đoạn',
     'none' => 'Không',
     'cancel' => 'Hủy',
     'confirm' => 'Đồng ý',

+ 3 - 0
api-v13/resources/lang/zh-Hans/library.php

@@ -131,6 +131,9 @@ return [
     'script_myanmar' => '缅文',
     'script_thai' => '泰文',
     'commentary_version' => '注疏版本',
+    'annotation_mode' => '注释',
+    'annotation_inline' => '行内',
+    'annotation_footnote' => '段后',
     'none' => '不显示',
     'cancel' => '取消',
     'confirm' => '确定',

+ 3 - 0
api-v13/resources/lang/zh-Hant/library.php

@@ -131,6 +131,9 @@ return [
     'script_myanmar' => '緬甸文',
     'script_thai' => '泰文',
     'commentary_version' => '注疏版本',
+    'annotation_mode' => '註釋',
+    'annotation_inline' => '行內',
+    'annotation_footnote' => '段後',
     'none' => '無',
     'cancel' => '取消',
     'confirm' => '確定',

+ 9 - 0
api-v13/resources/views/library/book/read.blade.php

@@ -348,6 +348,13 @@
                     </select>
                 </div>
                 @endisset
+                <div class="mb-4">
+                    <label class="form-label">{{ __('library.annotation_mode') }}</label>
+                    <select class="form-select" id="annotationMode">
+                        <option value="inline">{{ __('library.annotation_inline') }}</option>
+                        <option value="footnote">{{ __('library.annotation_footnote') }}</option>
+                    </select>
+                </div>
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-link" data-bs-dismiss="modal">{{ __('library.cancel') }}</button>
@@ -427,6 +434,7 @@
         document.getElementById('uiLanguage').value = getCookie('ui_language') || 'auto';
         document.getElementById('paliScript').value = getCookie('pali_script') || 'auto';
         document.getElementById('commentary').value = getCookie('commentary') || 'none';
+        document.getElementById('annotationMode').value = getCookie('annotation_mode') || 'inline';
         toggleOriginDisplay(showOrigin);
     });
 
@@ -436,6 +444,7 @@
         setCookie('ui_language', document.getElementById('uiLanguage').value);
         setCookie('pali_script', document.getElementById('paliScript').value);
         setCookie('commentary', document.getElementById('commentary').value);
+        setCookie('annotation_mode', document.getElementById('annotationMode').value);
         // 修改 URL 的 comm 参数
         const commValue = document.getElementById('commentary').value;
         const url = new URL(window.location.href);