Skip to content

Commit bdb0519

Browse files
authored
Merge pull request #182 from stumbo/bs7_citations
Bs7 citations
2 parents fb4f511 + 17ca929 commit bdb0519

File tree

3 files changed

+256
-0
lines changed

3 files changed

+256
-0
lines changed

layouts/bibliography/single.html

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,23 @@ <h1>{{ .Title }}</h1>
5252
No URL to the document is available.
5353
{{ end }}
5454
<br>
55+
56+
<!-- Citation -->
57+
{{/* Build Zotero API params (group id + item key + style) */}}
58+
{{ $gid := "2914042" }}
59+
60+
{{ $key := .File.TranslationBaseName }}
61+
62+
{{ $style := or site.Params.zotero.style "apa" }}
63+
64+
{{ if and $gid $key }}
65+
<br>
66+
<a href="#" id="citeBtn"
67+
data-gid="{{ $gid }}"
68+
data-key="{{ $key }}"
69+
data-style="{{ $style }}"
70+
>Citation</a>
71+
{{ end }}
5572
</p>
5673
<p>
5774
<strong>Abstract</strong>
@@ -218,4 +235,186 @@ <h1>{{ .Title }}</h1>
218235
{{ .Content }}
219236
</div>
220237
</article>
238+
<!-- Citation modal -->
239+
<div id="citationModal" class="citation-modal" hidden>
240+
<div class="citation-dialog">
241+
<div class="citation-header">
242+
<strong>Citation</strong>
243+
<button type="button" class="citation-close" aria-label="Close">×</button>
244+
</div>
245+
246+
<!-- Tabs -->
247+
<div class="citation-tabs" role="tablist" aria-label="Citation views">
248+
<button type="button" class="tab-btn is-active" data-tab="formatted" role="tab" aria-selected="true" aria-controls="tab-formatted">Formatted</button>
249+
<button type="button" class="tab-btn" data-tab="raw" role="tab" aria-selected="false" aria-controls="tab-raw">HTML</button>
250+
</div>
251+
252+
<div class="citation-body">
253+
<!-- Formatted view -->
254+
<div id="tab-formatted" class="tab-panel is-active" role="tabpanel" aria-labelledby="tabbtn-formatted">
255+
<div id="citationContent">Loading…</div>
256+
</div>
257+
258+
<!-- Raw HTML view -->
259+
<div id="tab-raw" class="tab-panel" role="tabpanel" aria-labelledby="tabbtn-raw">
260+
<pre class="codebox"><code id="citationRaw" class="language-html"></code></pre>
261+
</div>
262+
</div>
263+
264+
<div class="citation-actions">
265+
<button type="button" id="copyCitation">Copy Text</button>
266+
<button type="button" id="copyCitationHtml">Copy HTML</button>
267+
<button type="button" class="citation-close">Close</button>
268+
</div>
269+
</div>
270+
<div class="citation-backdrop"></div>
271+
</div>
272+
273+
274+
<style>
275+
.citation-modal[hidden] { display: none; }
276+
.citation-modal { position: fixed; inset: 0; z-index: 1050; }
277+
.citation-dialog {
278+
position: absolute; top: 10%; left: 50%; transform: translateX(-50%);
279+
max-width: 720px; width: calc(100% - 2rem);
280+
background: #fff; border-radius: 6px; box-shadow: 0 10px 30px rgba(0,0,0,.2);
281+
overflow: hidden;
282+
}
283+
.citation-header { display:flex; justify-content:space-between; align-items:center; padding:.75rem 1rem; border-bottom:1px solid #eee; }
284+
.citation-close { background:none; border:0; font-size:1.25rem; line-height:1; cursor:pointer; }
285+
.citation-tabs { display:flex; gap:.25rem; padding:.5rem 1rem; border-bottom:1px solid #eee; }
286+
.tab-btn {
287+
border: 1px solid #ddd; background:#f8f9fa; border-radius:4px; padding:.35rem .6rem; cursor:pointer;
288+
}
289+
.tab-btn.is-active { background:#fff; border-color:#bbb; }
290+
.citation-body { padding:1rem; max-height:50vh; overflow:auto; }
291+
#citationContent { font-size:1rem; line-height:1.4; }
292+
.codebox {
293+
background:#f6f8fa; border:1px solid #e1e4e8; border-radius:6px;
294+
padding:.75rem; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
295+
font-size:.9rem; line-height:1.4; white-space:pre-wrap; word-break:break-word;
296+
}
297+
.tab-panel { display:none; }
298+
.tab-panel.is-active { display:block; }
299+
.citation-actions { display:flex; gap:.5rem; justify-content:flex-end; padding:.75rem 1rem; border-top:1px solid #eee; }
300+
</style>
301+
302+
<script src="/js/vendor/prettier/standalone.js" defer></script>
303+
<script src="/js/vendor/prettier/plugins/html.js" defer></script>
304+
305+
<script>
306+
(function () {
307+
const modal = document.getElementById('citationModal');
308+
const contentBox = document.getElementById('citationContent');
309+
const rawBox = document.getElementById('citationRaw');
310+
311+
function openModal() { modal.hidden = false; }
312+
function closeModal() { modal.hidden = true; }
313+
314+
function setActiveTab(name) {
315+
document.querySelectorAll('.tab-btn').forEach(btn => {
316+
const isActive = btn.dataset.tab === name;
317+
btn.classList.toggle('is-active', isActive);
318+
btn.setAttribute('aria-selected', isActive ? 'true' : 'false');
319+
});
320+
document.querySelectorAll('.tab-panel').forEach(panel => {
321+
const isActive = panel.id === ('tab-' + name);
322+
panel.classList.toggle('is-active', isActive);
323+
});
324+
}
325+
326+
function copyTextToClipboard(text, button) {
327+
const done = () => {
328+
if (button) {
329+
const original = button.textContent;
330+
button.textContent = 'Copied';
331+
setTimeout(() => (button.textContent = original), 1200);
332+
}
333+
};
334+
if (navigator.clipboard && window.isSecureContext) {
335+
navigator.clipboard.writeText(text).then(done).catch(done);
336+
} else {
337+
const ta = document.createElement('textarea');
338+
ta.value = text; document.body.appendChild(ta);
339+
ta.select(); try { document.execCommand('copy'); } catch(e) {}
340+
document.body.removeChild(ta); done();
341+
}
342+
}
343+
344+
document.addEventListener('click', function (e) {
345+
// Open + fetch
346+
if (e.target.matches('#citeBtn')) {
347+
e.preventDefault();
348+
const btn = e.target;
349+
const gid = btn.dataset.gid;
350+
const key = btn.dataset.key;
351+
const style = btn.dataset.style || 'apa';
352+
// Use Zotero API; format=bib returns HTML bibliography item(s)
353+
const url = `https://api.zotero.org/groups/${encodeURIComponent(gid)}/items/${encodeURIComponent(key)}?format=bib&style=${encodeURIComponent(style)}&linkwrap=1`;
354+
355+
contentBox.textContent = 'Loading…';
356+
rawBox.textContent = '';
357+
setActiveTab('formatted');
358+
openModal();
359+
360+
fetch(url, { headers: { 'Accept': 'text/html' }})
361+
.then(r => {
362+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
363+
return r.text();
364+
})
365+
.then(html => {
366+
contentBox.innerHTML = html;
367+
try {
368+
const formatted = window.prettier.format(html, { parser: "html", plugins: window.prettierPlugins, tabWidth: 2 });
369+
// prettier.format may return a string or a Promise depending on plugin loading; normalize to a Promise
370+
return Promise.resolve(formatted).then(pretty => {
371+
rawBox.textContent = pretty;
372+
return html;
373+
});
374+
} catch (err) {
375+
// synchronous error formatting
376+
rawBox.textContent = String(err);
377+
return html;
378+
}
379+
})
380+
.catch(err => {
381+
const msg = `Failed to load citation (${err})`;
382+
contentBox.textContent = msg;
383+
rawBox.textContent = msg;
384+
});
385+
}
386+
387+
// Close
388+
if (e.target.matches('.citation-close')) {
389+
e.preventDefault();
390+
closeModal();
391+
}
392+
393+
// Copy buttons
394+
if (e.target.matches('#copyCitation')) {
395+
e.preventDefault();
396+
copyTextToClipboard(contentBox.innerText.trim(), e.target);
397+
}
398+
if (e.target.matches('#copyCitationHtml')) {
399+
e.preventDefault();
400+
copyTextToClipboard(rawBox.textContent.trim(), e.target);
401+
}
402+
403+
// Tabs
404+
if (e.target.matches('.tab-btn')) {
405+
e.preventDefault();
406+
setActiveTab(e.target.dataset.tab);
407+
}
408+
});
409+
410+
// Close when clicking backdrop or pressing Escape
411+
modal.addEventListener('click', function (e) {
412+
if (e.target.classList.contains('citation-backdrop')) closeModal();
413+
});
414+
document.addEventListener('keydown', function (e) {
415+
if (e.key === 'Escape') closeModal();
416+
});
417+
})();
418+
</script>
419+
221420
{{ end }}

0 commit comments

Comments
 (0)