@@ -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,101 @@ <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+ < div class ="citation-body ">
246+ < div id ="citationContent "> Loading…</ div >
247+ </ div >
248+ < div class ="citation-actions ">
249+ < button type ="button " id ="copyCitation "> Copy</ button >
250+ < button type ="button " class ="citation-close "> Close</ button >
251+ </ div >
252+ </ div >
253+ < div class ="citation-backdrop "> </ div >
254+ </ div >
255+
256+ < style >
257+ .citation-modal [hidden ] { display : none; }
258+ .citation-modal { position : fixed; inset : 0 ; z-index : 1050 ; }
259+ .citation-dialog {
260+ position : absolute; top : 10% ; left : 50% ; transform : translateX (-50% );
261+ max-width : 720px ; width : calc (100% - 2rem );
262+ background : # fff ; border-radius : 6px ; box-shadow : 0 10px 30px rgba (0 , 0 , 0 , .2 );
263+ overflow : hidden;
264+ }
265+ .citation-header { display : flex; justify-content : space-between; align-items : center; padding : .75rem 1rem ; border-bottom : 1px solid # eee ; }
266+ .citation-close { background : none; border : 0 ; font-size : 1.25rem ; line-height : 1 ; cursor : pointer; }
267+ .citation-body { padding : 1rem ; max-height : 50vh ; overflow : auto; }
268+ # citationContent { font-size : 1rem ; line-height : 1.4 ; }
269+ .citation-actions { display : flex; gap : .5rem ; justify-content : flex-end; padding : .75rem 1rem ; border-top : 1px solid # eee ; }
270+ </ style >
271+
272+ < script >
273+ ( function ( ) {
274+ function openModal ( ) { document . getElementById ( 'citationModal' ) . hidden = false ; }
275+ function closeModal ( ) { document . getElementById ( 'citationModal' ) . hidden = true ; }
276+
277+ document . addEventListener ( 'click' , function ( e ) {
278+ if ( e . target . matches ( '#citeBtn' ) ) {
279+ e . preventDefault ( ) ;
280+ const btn = e . target ;
281+ const gid = btn . dataset . gid ;
282+ const key = btn . dataset . key ;
283+ const style = btn . dataset . style || 'apa' ;
284+ const url = `https://api.zotero.org/groups/${ encodeURIComponent ( gid ) } /items/${ encodeURIComponent ( key ) } ?format=bib&style=${ encodeURIComponent ( style ) } &linkwrap=1` ;
285+
286+ const box = document . getElementById ( 'citationContent' ) ;
287+ box . textContent = 'Loading…' ;
288+ openModal ( ) ;
289+
290+ fetch ( url , { headers : { 'Accept' : 'text/html' } } )
291+ . then ( r => {
292+ if ( ! r . ok ) throw new Error ( `HTTP ${ r . status } ` ) ;
293+ return r . text ( ) ;
294+ } )
295+ . then ( html => {
296+ // API returns HTML (span with formatted citation)
297+ box . innerHTML = html ;
298+ } )
299+ . catch ( err => {
300+ box . textContent = `Failed to load citation (${ err } )` ;
301+ } ) ;
302+ }
303+ if ( e . target . matches ( '.citation-close' ) ) {
304+ e . preventDefault ( ) ;
305+ closeModal ( ) ;
306+ }
307+ if ( e . target . matches ( '#copyCitation' ) ) {
308+ e . preventDefault ( ) ;
309+ const el = document . getElementById ( 'citationContent' ) ;
310+ const text = el . innerText . trim ( ) ;
311+ if ( navigator . clipboard && window . isSecureContext ) {
312+ navigator . clipboard . writeText ( text ) . then ( ( ) => {
313+ e . target . textContent = 'Copied' ;
314+ setTimeout ( ( ) => e . target . textContent = 'Copy' , 1200 ) ;
315+ } ) ;
316+ } else {
317+ const ta = document . createElement ( 'textarea' ) ;
318+ ta . value = text ; document . body . appendChild ( ta ) ;
319+ ta . select ( ) ; try { document . execCommand ( 'copy' ) ; } catch ( e ) { }
320+ document . body . removeChild ( ta ) ;
321+ }
322+ }
323+ } ) ;
324+
325+ // Close when clicking backdrop or pressing Escape
326+ document . getElementById ( 'citationModal' ) . addEventListener ( 'click' , function ( e ) {
327+ if ( e . target . classList . contains ( 'citation-backdrop' ) ) closeModal ( ) ;
328+ } ) ;
329+ document . addEventListener ( 'keydown' , function ( e ) {
330+ if ( e . key === 'Escape' ) closeModal ( ) ;
331+ } ) ;
332+ } ) ( ) ;
333+ </ script >
334+
221335{{ end }}
0 commit comments