Stiglo je novo izdanje. Osvježi stranu.
Pravo na uvid u kod. Otvoren protokol omogućava nezavisne operatere i interoperabilnost. Manifest
Izvor
4d7883a894b3
- server.js
- lib/auth.js
- lib/build.js
- lib/cards.js
- lib/db.js
- lib/gradovi.js
- lib/icons.js
- lib/imena.js
- lib/jezik.js
- lib/kartapodaci.js
- lib/lekcije.js
- lib/md.js
- lib/mehanika.js
- lib/mreza.js
- lib/pages.js
- lib/registar.js
- lib/routes.js
- lib/schema2.js
- lib/views.js
- seed/content.mjs
- seed/seed.mjs
- public/app.js
- public/style.css
1 // Safe markdown-lite: everything is HTML-escaped first; only a small fixed set of constructs becomes markup.
2 export const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
3
4 function inline(text, mentions) {
5 const codes = [];
6 let s = esc(text).replace(/`([^`\n]{1,300})`/g, (_, c) => { codes.push(c); return `\u0000${codes.length - 1}\u0000`; });
7 s = s.replace(/\[([^\]\n]{1,200})\]\((https?:\/\/[^\s)]{1,500})\)/g, (_, label, url) =>
8 `<a href="${url}" rel="nofollow ugc noopener noreferrer">${label}</a>`);
9 s = s.replace(/(^|[\s(])(https?:\/\/[^\s<]{3,500}[^\s<.,;:!?)'"])/g, (_, pre, url) =>
10 `${pre}<a href="${url}" rel="nofollow ugc noopener noreferrer">${url}</a>`);
11 s = s.replace(/\*\*([^*\n]{1,500})\*\*/g, '<strong>$1</strong>');
12 s = s.replace(/(^|[^*\w])\*([^*\n]{1,500})\*(?!\w)/g, '$1<em>$2</em>');
13 s = s.replace(/(^|[^\w@/])@([a-z0-9_]{3,20})\b/gi, (m, pre, h) => {
14 if (mentions) mentions.add(h.toLowerCase());
15 return `${pre}<a class="mention" href="/u/${h.toLowerCase()}">@${h}</a>`;
16 });
17 return s.replace(/\u0000(\d+)\u0000/g, (_, i) => `<code>${codes[+i]}</code>`);
18 }
19
20 export function render(src, mentions) {
21 const lines = String(src ?? '').replace(/\r\n?/g, '\n').split('\n');
22 const out = [];
23 let para = [], list = [], quote = [];
24 const flushPara = () => { if (para.length) out.push(`<p>${para.map((l) => inline(l, mentions)).join('<br>')}</p>`); para = []; };
25 const flushList = () => { if (list.length) out.push(`<ul>${list.map((l) => `<li>${inline(l, mentions)}</li>`).join('')}</ul>`); list = []; };
26 const flushQuote = () => { if (quote.length) out.push(`<blockquote>${render(quote.join('\n'), mentions)}</blockquote>`); quote = []; };
27 const flush = () => { flushPara(); flushList(); flushQuote(); };
28 for (let i = 0; i < lines.length; i++) {
29 const line = lines[i];
30 if (/^```/.test(line)) {
31 flush();
32 const code = [];
33 for (i++; i < lines.length && !/^```/.test(lines[i]); i++) code.push(lines[i]);
34 out.push(`<pre><code>${esc(code.join('\n'))}</code></pre>`);
35 continue;
36 }
37 if (/^>\s?/.test(line)) { flushPara(); flushList(); quote.push(line.replace(/^>\s?/, '')); continue; }
38 if (/^\s*[-*]\s+/.test(line)) { flushPara(); flushQuote(); list.push(line.replace(/^\s*[-*]\s+/, '')); continue; }
39 if (!line.trim()) { flush(); continue; }
40 flushList(); flushQuote();
41 para.push(line);
42 }
43 flush();
44 return out.join('\n');
45 }
46
47 export function excerpt(src, n = 160) {
48 const t = String(src ?? '').replace(/```[\s\S]*?```/g, ' ').replace(/[*`>#\[\]]/g, '').replace(/\((https?:[^)]+)\)/g, '').replace(/\s+/g, ' ').trim();
49 return t.length > n ? t.slice(0, n - 1).replace(/\s+\S*$/, '') + '...' : t;
50 }
51