@@ -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