/* BIOTEN Observer V7.31.8-R8: hub preview limits, date-sorted pagination, language and PDF routing. */ (() => { 'use strict'; const DEFAULT_PAGE_SIZE = 12; const INDUSTRY_PAGE_SIZE = 8; const DEFAULT_COMPANY_COVER = 'assets/images/bioten-observer/company-news-default.jpg'; const DEFAULT_INDUSTRY_COVER = 'assets/images/bioten-observer/industry-insights-default.jpg'; const isEnglish = () => document.documentElement.lang.toLowerCase().startsWith('en'); const text = (cn, en) => (isEnglish() ? en : cn); function updateLanguageAttributes(root = document) { root.querySelectorAll('[data-aria-cn][data-aria-en]').forEach((element) => { element.setAttribute('aria-label', isEnglish() ? element.dataset.ariaEn : element.dataset.ariaCn); }); root.querySelectorAll('[data-v7301-en-review]').forEach((notice) => { notice.hidden = !isEnglish(); }); } function updateObserverChrome() { const title = document.querySelector('title[data-cn][data-en]'); if (title) document.title = isEnglish() ? title.dataset.en : title.dataset.cn; const context = document.querySelector('.v500-context'); if (!context) return; context.dataset.ariaCn = '博碳观察页面层级'; context.dataset.ariaEn = 'BIOTEN Observer context'; context.setAttribute('aria-label', text(context.dataset.ariaCn, context.dataset.ariaEn)); const first = context.querySelector('ol > li:first-child :is(a, span)'); if (first) { first.dataset.newsCn = '博碳观察'; first.dataset.newsEn = 'BIOTEN Observer'; first.textContent = text(first.dataset.newsCn, first.dataset.newsEn); } const current = context.querySelector('ol > li:last-child :is(a, span)'); if (!current) return; const view = document.body.dataset.observerView; const labels = { hub: ['公司动态 · 行业洞察', 'Company News · Industry Insights'], company: ['公司动态', 'Company News'], industry: ['行业洞察', 'Industry Insights'], article: ['文章阅读', 'Article'], document: ['PDF 阅读', 'PDF Reader'], legacy: ['内容频道', 'Content Channels'], }; const [cn, en] = labels[view] || labels.article; current.dataset.newsCn = cn; current.dataset.newsEn = en; current.textContent = text(cn, en); } function protectObserverTitle() { const title = document.querySelector('title[data-cn][data-en]'); if (!title) return; const observer = new MutationObserver(() => { const preferred = isEnglish() ? title.dataset.en : title.dataset.cn; if (document.title !== preferred) document.title = preferred; }); observer.observe(title, { childList: true, characterData: true, subtree: true }); } function normalizeLegacyParameters() { const url = new URL(window.location.href); let changed = false; ['q', 'tag', 'category'].forEach((key) => { if (url.searchParams.has(key)) { url.searchParams.delete(key); changed = true; } }); if (changed) window.history.replaceState({}, '', `${url.pathname}${url.search}${url.hash}`); } function installCoverFallbacks() { document.querySelectorAll('.observer-publication-card__media img, .observer-card__media img, .observer-home-card__media img, .observer-article-cover img').forEach((image) => { image.addEventListener('error', () => { const industry = image.closest('[data-observer-category="industry"], [data-observer-mode="industry"]'); const fallback = industry ? DEFAULT_INDUSTRY_COVER : DEFAULT_COMPANY_COVER; if (!image.src.endsWith(fallback)) image.src = fallback; }, { once: true }); }); } function initializeHubPreviews() { const hub = document.querySelector('[data-observer-mode="hub"]'); if (!hub) return; hub.querySelectorAll('[data-observer-preview-limit]').forEach((section) => { const grid = section.querySelector('.observer-home-publications'); const limit = Math.max(1, Number.parseInt(section.dataset.observerPreviewLimit || '', 10) || 1); if (!grid) return; const observer = new MutationObserver(() => sortAndLimit()); function sortAndLimit() { observer.disconnect(); const cards = Array.from(grid.querySelectorAll(':scope > [data-observer-item]')) .map((card, sourceIndex) => { const publishedAt = Date.parse(card.querySelector('time[datetime]')?.getAttribute('datetime') || ''); return { card, sourceIndex, publishedAt: Number.isFinite(publishedAt) ? publishedAt : 0, }; }) .sort((left, right) => right.publishedAt - left.publishedAt || left.sourceIndex - right.sourceIndex); cards.forEach(({ card }, index) => { card.hidden = index >= limit; card.setAttribute('aria-posinset', String(index + 1)); card.setAttribute('aria-setsize', String(Math.min(cards.length, limit))); grid.append(card); }); observer.observe(grid, { childList: true }); } sortAndLimit(); }); } function pageButton(labelCn, labelEn, pageNumber, options = {}) { const control = document.createElement('button'); control.type = 'button'; control.textContent = text(labelCn, labelEn); control.dataset.labelCn = labelCn; control.dataset.labelEn = labelEn; control.dataset.page = String(pageNumber); if (options.current) control.setAttribute('aria-current', 'page'); if (options.disabled) control.disabled = true; if (options.ariaCn) { control.dataset.ariaCn = options.ariaCn; control.dataset.ariaEn = options.ariaEn; control.setAttribute('aria-label', text(options.ariaCn, options.ariaEn)); } return control; } function initializeList(page) { const pagination = page.querySelector('[data-observer-pagination]'); const status = page.querySelector('[data-observer-status]'); const grid = page.querySelector('[data-observer-grid]'); if (!pagination || !grid) return; const items = Array.from(grid.querySelectorAll(':scope > [data-observer-item]')) .map((item, sourceIndex) => { const publishedAt = Date.parse(item.querySelector('time[datetime]')?.getAttribute('datetime') || ''); return { item, sourceIndex, publishedAt: Number.isFinite(publishedAt) ? publishedAt : 0, }; }) .sort((left, right) => right.publishedAt - left.publishedAt || left.sourceIndex - right.sourceIndex) .map(({ item }) => item); if (!items.length || !pagination || !grid) return; items.forEach((item) => grid.append(item)); const configuredPageSize = Math.max( 1, Number.parseInt(page.dataset.observerPageSize || String(DEFAULT_PAGE_SIZE), 10) || DEFAULT_PAGE_SIZE, ); const pageSize = page.dataset.observerMode === 'industry' ? INDUSTRY_PAGE_SIZE : configuredPageSize; page.dataset.observerPageSize = String(pageSize); const state = { page: 1 }; function readUrlState() { const params = new URLSearchParams(window.location.search); state.page = Math.max(1, Number.parseInt(params.get('page') || '1', 10) || 1); } function writeUrlState() { const url = new URL(window.location.href); if (state.page > 1) url.searchParams.set('page', String(state.page)); else url.searchParams.delete('page'); window.history.pushState({}, '', `${url.pathname}${url.search}#observer-list`); } function renderPagination(totalPages) { pagination.replaceChildren(); pagination.hidden = totalPages <= 1; if (totalPages <= 1) return; pagination.append(pageButton('上一页', 'Previous', Math.max(1, state.page - 1), { disabled: state.page === 1, ariaCn: '上一页', ariaEn: 'Previous page', })); const values = totalPages <= 7 ? Array.from({ length: totalPages }, (_, index) => index + 1) : Array.from(new Set([1, totalPages, state.page - 1, state.page, state.page + 1])) .filter((value) => value > 0 && value <= totalPages) .sort((left, right) => left - right); let previous = 0; values.forEach((value) => { if (previous && value - previous > 1) { const gap = document.createElement('span'); gap.textContent = '…'; gap.setAttribute('aria-hidden', 'true'); pagination.append(gap); } pagination.append(pageButton(String(value), String(value), value, { current: value === state.page, ariaCn: `第${value}页`, ariaEn: `Page ${value}`, })); previous = value; }); pagination.append(pageButton('下一页', 'Next', Math.min(totalPages, state.page + 1), { disabled: state.page === totalPages, ariaCn: '下一页', ariaEn: 'Next page', })); } function render({ updateUrl = false, focusList = false } = {}) { const totalPages = Math.max(1, Math.ceil(items.length / pageSize)); state.page = Math.min(state.page, totalPages); const first = (state.page - 1) * pageSize; const visibleItems = items.slice(first, first + pageSize); const visible = new Set(visibleItems); items.forEach((item) => { item.hidden = !visible.has(item); }); if (status) { const start = items.length ? first + 1 : 0; const end = Math.min(first + pageSize, items.length); const statusCn = `第 ${start}-${end} 篇 · 共 ${items.length} 篇`; const statusEn = `${start}-${end} of ${items.length}`; status.dataset.newsCn = statusCn; status.dataset.newsEn = statusEn; status.textContent = text(statusCn, statusEn); } renderPagination(totalPages); updateLanguageAttributes(page); if (updateUrl) writeUrlState(); if (focusList) visibleItems[0]?.querySelector('h2 a, h3 a')?.focus({ preventScroll: false }); } pagination.addEventListener('click', (event) => { const button = event.target.closest('button[data-page]'); if (!button || button.disabled) return; state.page = Number.parseInt(button.dataset.page, 10) || 1; render({ updateUrl: true, focusList: true }); }); window.addEventListener('popstate', () => { readUrlState(); render(); }); document.addEventListener('dbc:languagechange', () => render()); readUrlState(); render(); } function initializeDocument() { const main = document.querySelector('[data-observer-view="document"], .observer-document-main'); if (!main) return; const slug = new URLSearchParams(window.location.search).get('doc'); const catalog = Array.isArray(window.BiotenObserverCatalogV7318) ? window.BiotenObserverCatalogV7318 : []; const article = catalog.find((entry) => entry.slug === slug); const error = document.querySelector('[data-document-error]'); if (!article) { if (error) error.hidden = false; return; } const categoryPage = article.observerCategory === 'industry' ? 'news-insights.html' : 'news-company.html'; const categoryLink = document.querySelector('[data-document-category-link]'); const articleLink = document.querySelector('[data-document-article-link]'); const back = document.querySelector('[data-document-back]'); const open = document.querySelector('[data-document-open]'); const download = document.querySelector('[data-document-download]'); const frame = document.querySelector('[data-document-frame]'); const title = document.querySelector('[data-document-title]'); const category = document.querySelector('[data-document-category]'); if (categoryLink) categoryLink.href = categoryPage; if (articleLink) articleLink.href = article.file; if (back) back.href = article.file; if (open) open.href = article.pdf; if (download) { download.href = article.pdf; download.download = article.pdfDownloadName; } if (frame) frame.data = article.pdf; if (title) { title.dataset.newsCn = article.title; title.dataset.newsEn = article.titleEn; title.textContent = text(article.title, article.titleEn); } if (category) { category.dataset.newsCn = article.observerCategoryCn; category.dataset.newsEn = article.observerCategoryEn; category.textContent = text(article.observerCategoryCn, article.observerCategoryEn); } if (error) error.hidden = true; } function initializeArticleShare(main) { const meta = main.querySelector('.observer-article-hero__meta'); const pdfAction = meta?.querySelector('.observer-article-hero__pdf-action'); if (!meta || !pdfAction) return; let button = meta.querySelector('[data-article-share]'); if (!button) { button = document.createElement('button'); button.className = 'observer-article-hero__share-action'; button.type = 'button'; button.dataset.articleShare = ''; button.dataset.ariaCn = '分享阅读'; button.dataset.ariaEn = 'Share article'; button.setAttribute('aria-label', text('分享阅读', 'Share article')); button.setAttribute('title', text('分享阅读', 'Share article')); button.innerHTML = '分享阅读'; pdfAction.insertAdjacentElement('afterend', button); } if (button.dataset.shareReady === 'true') return; button.dataset.shareReady = 'true'; const label = button.querySelector('[data-article-share-label]'); const setLabel = (cn, en) => { const value = text(cn, en); if (label) label.textContent = value; button.setAttribute('aria-label', value); button.setAttribute('title', value); }; const restoreLabel = () => setLabel('分享阅读', 'Share article'); const flashLabel = (cn, en) => { setLabel(cn, en); button.dataset.shareMessage = text(cn, en); window.setTimeout(() => { delete button.dataset.shareMessage; restoreLabel(); }, 2200); }; const copyCurrentUrl = async () => { const url = window.location.href; try { if (navigator.clipboard?.writeText) { await navigator.clipboard.writeText(url); return true; } } catch (_) { // Continue to the browser-compatible copy fallback. } const field = document.createElement('textarea'); field.value = url; field.setAttribute('readonly', ''); field.style.cssText = 'position:fixed;left:-9999px;top:0;opacity:0;'; document.body.appendChild(field); field.select(); const copied = document.execCommand('copy'); field.remove(); return copied; }; button.addEventListener('click', async () => { const title = main.querySelector('.observer-article-hero h1')?.textContent?.trim() || document.title; const url = window.location.href; if (navigator.share) { try { await navigator.share({ title, url }); return; } catch (error) { if (error instanceof DOMException && error.name === 'AbortError') return; } } const copied = await copyCurrentUrl(); flashLabel(copied ? '链接已复制' : '请复制当前链接', copied ? 'Link copied' : 'Copy this page URL'); }); document.addEventListener('dbc:languagechange', restoreLabel); } function initializeArticle() { const main = document.querySelector('[data-observer-article]'); if (!main) return; initializeArticleShare(main); const catalog = Array.isArray(window.BiotenObserverCatalogV7318) ? window.BiotenObserverCatalogV7318 : []; const article = catalog.find((entry) => entry.slug === main.dataset.observerArticle); if (!article) return; const publisher = main.querySelector('.observer-article-hero__meta > span:nth-child(2)'); if (publisher) { const label = publisher.querySelector('b'); Array.from(publisher.childNodes).forEach((node) => { if (node !== label) node.remove(); }); const value = document.createElement('span'); value.dataset.observerAuthor = ''; value.dataset.newsCn = article.authorCn; value.dataset.newsEn = article.authorEn; value.textContent = text(article.authorCn, article.authorEn); publisher.append(value); } const authorLabel = Array.from(main.querySelectorAll('.observer-article-data th')).find((cell) => cell.dataset.newsCn === '发布主体'); const authorCell = authorLabel?.closest('tr')?.querySelector('td'); if (authorCell) { authorCell.dataset.newsCn = article.authorCn; authorCell.dataset.newsEn = article.authorEn; authorCell.textContent = text(article.authorCn, article.authorEn); } } function initialize() { normalizeLegacyParameters(); updateLanguageAttributes(); updateObserverChrome(); protectObserverTitle(); installCoverFallbacks(); initializeHubPreviews(); const list = document.querySelector('[data-observer-list-page]'); if (list) initializeList(list); initializeDocument(); initializeArticle(); document.addEventListener('dbc:languagechange', () => { updateLanguageAttributes(); updateObserverChrome(); initializeDocument(); initializeArticle(); }); window.addEventListener('load', updateObserverChrome, { once: true }); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initialize, { once: true }); else initialize(); })();