|
2 | 2 | ```{=html} |
3 | 3 | <ul class="pub-list list"> |
4 | 4 | <% for (const item of items) { %> |
| 5 | + <% const decodeEntitiesOnce = (value) => (value || '') |
| 6 | + .replace(/</g, '<') |
| 7 | + .replace(/>/g, '>') |
| 8 | + .replace(/"/g, '"') |
| 9 | + .replace(/'/g, "'") |
| 10 | + .replace(/&/g, '&') |
| 11 | + .replace(/&#(\d+);/g, (_m, code) => String.fromCharCode(Number(code))) |
| 12 | + .replace(/&#x([0-9a-fA-F]+);/g, (_m, code) => String.fromCharCode(parseInt(code, 16))); |
| 13 | + const decodeEntities = (value) => { |
| 14 | + let current = value || ''; |
| 15 | + for (let i = 0; i < 3; i += 1) { |
| 16 | + const next = decodeEntitiesOnce(current); |
| 17 | + if (next === current) break; |
| 18 | + current = next; |
| 19 | + } |
| 20 | + return current; |
| 21 | + }; |
| 22 | + const titleDecoded = decodeEntities(item.title || ''); |
| 23 | + const titleHtml = titleDecoded.replace(/\$\\mathrm\{([^}]+)\}\$/g, (_m, formula) => { |
| 24 | + const plain = formula.replace(/_\{?(\d+)\}?/g, '$1'); |
| 25 | + return plain.replace(/([A-Za-z])([0-9]+)/g, '$1<sub>$2</sub>'); |
| 26 | + }).replace(/\b([A-Z][a-z]?)(\d+)\b/g, '$1<sub>$2</sub>'); %> |
| 27 | + <% const formatAuthors = (value) => { |
| 28 | + const raw = (value || '').trim(); |
| 29 | + if (!raw) return ''; |
| 30 | + const parts = raw.split(/\s+and\s+/i).map((p) => p.trim()).filter(Boolean); |
| 31 | + const formatted = parts.map((p) => { |
| 32 | + const cleaned = p.replace(/\s+/g, ' ').replace(/[{}]/g, '').trim(); |
| 33 | + if (!cleaned) return ''; |
| 34 | + if (cleaned.includes(',')) { |
| 35 | + const [last, rest] = cleaned.split(',').map((s) => s.trim()); |
| 36 | + const initials = (rest || '') |
| 37 | + .split(/\s+/) |
| 38 | + .map((token) => token.replace(/[^A-Za-z-]/g, '')) |
| 39 | + .filter(Boolean) |
| 40 | + .map((token) => `${token[0]}.`) |
| 41 | + .join(' '); |
| 42 | + return `${last}${initials ? `, ${initials}` : ''}`; |
| 43 | + } |
| 44 | + const tokens = cleaned.split(/\s+/).filter(Boolean); |
| 45 | + if (tokens.length === 1) return tokens[0]; |
| 46 | + const last = tokens[tokens.length - 1]; |
| 47 | + const initials = tokens.slice(0, -1).map((token) => `${token[0]}.`).join(' '); |
| 48 | + return `${last}${initials ? `, ${initials}` : ''}`; |
| 49 | + }).filter(Boolean); |
| 50 | + if (formatted.length <= 1) return formatted.join(''); |
| 51 | + return `${formatted.slice(0, -1).join(', ')} and ${formatted[formatted.length - 1]}`; |
| 52 | + }; |
| 53 | + const authorsFormatted = formatAuthors(item.author); %> |
5 | 54 | <li <%= metadataAttrs(item) %>> |
6 | | - <span class="listing-title lead" style="color:orange;font-weight:bold"><%= item.title %></span><br /> |
7 | | - <span class="listing-author"><%= item.author %></span> |
8 | | - <span class="listing-publication fst-italic "><%= item.publication %></span> |
9 | | - <span class="listing-year">(<%= item.year %>)</span> |
| 55 | + <div class="pub-item"> |
| 56 | + <div class="listing-title pub-title"><%- titleHtml %></div> |
| 57 | + <div class="listing-author pub-authors"><%= authorsFormatted %></div> |
| 58 | + <span class="listing-type pub-type" style="display:none"><%= item.type %></span> |
| 59 | + <div class="pub-meta"> |
| 60 | + <span class="listing-publication pub-publication"><%= item.publication %></span> |
| 61 | + <span class="listing-year pub-year">(<%= item.year %>)</span> |
| 62 | + </div> |
| 63 | + <div class="pub-links"> |
10 | 64 | <% if (item.doi) { %> |
11 | 65 | <a href="https://doi.org/<%- item.doi %>"> |
12 | 66 | DOI |
13 | 67 | </a> |
14 | 68 | <% } %> |
| 69 | + <% if (item.materials) { %> |
| 70 | + <a href="<%- item.materials %>"> |
| 71 | + PDF |
| 72 | + </a> |
| 73 | + <% } %> |
| 74 | + <% if (item.supplement) { %> |
| 75 | + <a href="<%- item.supplement %>"> |
| 76 | + Supplement |
| 77 | + </a> |
| 78 | + <% } %> |
| 79 | + </div> |
| 80 | + </div> |
15 | 81 | </li> |
16 | 82 | <% } %> |
17 | 83 | </ul> |
|
0 commit comments