Changeset 4885eab in Klonkt for src/views/pages
- Timestamp:
- 06/21/2026 03:05:14 PM (3 months ago)
- Branches:
- main
- Children:
- 5e61b17
- Parents:
- bbf9d1a
- Location:
- src/views/pages
- Files:
-
- 26 edited
-
404.ejs (modified) (1 diff)
-
account.ejs (modified) (10 diffs)
-
archive.ejs (modified) (1 diff)
-
artists-directory.ejs (modified) (5 diffs)
-
auth-login.ejs (modified) (1 diff)
-
auth-reset-request.ejs (modified) (3 diffs)
-
auth-reset.ejs (modified) (2 diffs)
-
changelog.ejs (modified) (1 diff)
-
circle-feed.ejs (modified) (4 diffs)
-
circle-post.ejs (modified) (3 diffs)
-
download.ejs (modified) (3 diffs)
-
epk.ejs (modified) (3 diffs)
-
fan-gate.ejs (modified) (1 diff)
-
favorites.ejs (modified) (1 diff)
-
home.ejs (modified) (3 diffs)
-
hub-home.ejs (modified) (5 diffs)
-
linkbio.ejs (modified) (2 diffs)
-
my-site.ejs (modified) (1 diff)
-
newsletter.ejs (modified) (2 diffs)
-
post-edit.ejs (modified) (27 diffs)
-
prutter-conversation.ejs (modified) (3 diffs)
-
prutter-inbox.ejs (modified) (2 diffs)
-
tag.ejs (modified) (2 diffs)
-
type.ejs (modified) (2 diffs)
-
user.ejs (modified) (3 diffs)
-
viewer-blocked.ejs (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
src/views/pages/404.ejs
rbbf9d1a r4885eab 1 1 <section class="nf"> 2 2 <div class="nf-code">404</div> 3 <h1 class="nf-title"> Pagina niet gevonden</h1>4 <p class="nf-sub"> Deze pagina bestaat niet (meer). Misschien is de link verouderd of verkeerd getypt.</p>3 <h1 class="nf-title"><%= t('e404.title') %></h1> 4 <p class="nf-sub"><%= t('e404.sub') %></p> 5 5 <div class="nf-actions"> 6 <a class="nf-btn nf-btn-primary" href="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/">← Naar de homepagina</a>6 <a class="nf-btn nf-btn-primary" href="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/">← <%= t('e404.home') %></a> 7 7 <% if (typeof site !== 'undefined' && site && site.show_archive_link !== 0) { %> 8 <a class="nf-btn" href="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/archive"> Archief</a>8 <a class="nf-btn" href="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/archive"><%= t('e404.archive') %></a> 9 9 <% } %> 10 10 </div> -
src/views/pages/account.ejs
rbbf9d1a r4885eab 2 2 3 3 <header class="ax-header"> 4 <a href="/" class="ax-back" aria-label=" Terug naar home">←</a>4 <a href="/" class="ax-back" aria-label="<%= t('acct.back_home') %>">←</a> 5 5 <div> 6 <h1> Account</h1>7 <p class="ax-tagline"> Profiel en avatar.</p>6 <h1><%= t('acct.title') %></h1> 7 <p class="ax-tagline"><%= t('acct.subtitle') %></p> 8 8 </div> 9 9 </header> … … 16 16 <span class="ax-viewer-ico" aria-hidden="true">👁️</span> 17 17 <div> 18 <strong> Kijker-modus</strong>19 <span> Dit is een demo-account. Je kunt alles bekijken, maar niets wijzigen — ook geen foto of bio.</span>18 <strong><%= t('acct.viewer_mode') %></strong> 19 <span><%= t('acct.viewer_note') %></span> 20 20 </div> 21 21 </div> … … 24 24 <%# ── PROFILE ────────────────────────────────────────────── %> 25 25 <section class="ax-card"> 26 <div class="ax-card-title"> Profiel</div>26 <div class="ax-card-title"><%= t('acct.profile') %></div> 27 27 28 28 <div class="ax-profile-id"> 29 29 <% if (canMutate) { %> 30 30 <form action="/account/avatar" method="post" enctype="multipart/form-data" class="ax-avatar-form"> 31 <label class="ax-avatar-wrap" title=" Klik om je foto te wijzigen">31 <label class="ax-avatar-wrap" title="<%= t('acct.avatar_change') %>"> 32 32 <span class="ax-profile-avatar"> 33 33 <% if (account.avatar_url) { %> … … 56 56 <div class="ax-profile-role"><%= account.role %> · <%= account.email %></div> 57 57 <% if (account.created_at) { %> 58 <div class="ax-profile-joined"> Lid sinds<%= formatDate(account.created_at) %></div>58 <div class="ax-profile-joined"><%= t('acct.member_since') %> <%= formatDate(account.created_at) %></div> 59 59 <% } %> 60 60 <% if (account.avatar_url && canMutate) { %> 61 61 <form action="/account/avatar/remove" method="post" class="ax-avatar-remove"> 62 <button type="submit" class="ax-linkbtn"> Foto verwijderen</button>62 <button type="submit" class="ax-linkbtn"><%= t('acct.avatar_remove') %></button> 63 63 </form> 64 64 <% } %> … … 69 69 <form action="/account/profile" method="post" class="ax-form ax-form-bio"> 70 70 <label class="ax-field"> 71 <span> E-mailadres</span>72 <input type="email" name="email" value="<%= account.email || '' %>" maxlength="254" autocomplete="email" placeholder=" jij@email.nl">73 </label> 74 <label class="ax-field"> 75 <span> Bio</span>76 <textarea name="bio" rows="3" maxlength="500" placeholder=" Een korte regel over jezelf"><%= account.bio || '' %></textarea>71 <span><%= t('acct.email') %></span> 72 <input type="email" name="email" value="<%= account.email || '' %>" maxlength="254" autocomplete="email" placeholder="<%= t('acct.email_ph') %>"> 73 </label> 74 <label class="ax-field"> 75 <span><%= t('acct.bio') %></span> 76 <textarea name="bio" rows="3" maxlength="500" placeholder="<%= t('acct.bio_ph') %>"><%= account.bio || '' %></textarea> 77 77 </label> 78 78 <div class="ax-form-actions"> 79 <button type="submit" class="ax-btn ax-btn-primary"> Opslaan</button>79 <button type="submit" class="ax-btn ax-btn-primary"><%= t('acct.save') %></button> 80 80 </div> 81 81 </form> … … 83 83 <div class="ax-form ax-form-bio"> 84 84 <label class="ax-field"> 85 <span> Bio</span>86 <textarea rows="3" disabled placeholder=" Geen bio"><%= account.bio || '' %></textarea>85 <span><%= t('acct.bio') %></span> 86 <textarea rows="3" disabled placeholder="<%= t('acct.bio_empty') %>"><%= account.bio || '' %></textarea> 87 87 </label> 88 88 </div> … … 93 93 <% if (typeof editableSite !== 'undefined' && editableSite && canMutate) { %> 94 94 <section class="ax-card"> 95 <div class="ax-card-title"> Site</div>95 <div class="ax-card-title"><%= t('acct.site') %></div> 96 96 <form action="/account/site" method="post" class="ax-form"> 97 97 <label class="ax-field"> 98 <span> Site-naam <small>— getoond in de kop van je site</small></span>98 <span><%= t('acct.site_name') %> <small><%= t('acct.site_name_hint') %></small></span> 99 99 <input type="text" name="site_title" value="<%= editableSite.title || '' %>" maxlength="200" required> 100 100 </label> 101 101 <label class="ax-field"> 102 <span> Tagline <small>— korte oneliner (optioneel)</small></span>102 <span><%= t('acct.tagline') %> <small><%= t('acct.tagline_hint') %></small></span> 103 103 <input type="text" name="site_tagline" value="<%= editableSite.tagline || '' %>" maxlength="200"> 104 104 </label> 105 105 <div class="ax-form-actions"> 106 <button type="submit" class="ax-btn ax-btn-primary"> Site opslaan</button>106 <button type="submit" class="ax-btn ax-btn-primary"><%= t('acct.site_save') %></button> 107 107 </div> 108 108 </form> … … 115 115 <% if (hasPassword) { %> 116 116 <section class="ax-card"> 117 <div class="ax-card-title"> Wachtwoord wijzigen</div>117 <div class="ax-card-title"><%= t('acct.password_change') %></div> 118 118 <form action="/account/password" method="post" class="ax-form"> 119 119 <label class="ax-field"> 120 <span> Huidig wachtwoord</span>120 <span><%= t('acct.password_current') %></span> 121 121 <input type="password" name="current" required autocomplete="current-password"> 122 122 </label> 123 123 <div class="ax-row ax-row-2"> 124 124 <label class="ax-field"> 125 <span> Nieuw wachtwoord <small>(min 8 tekens)</small></span>125 <span><%= t('acct.password_new') %> <small><%= t('acct.password_min') %></small></span> 126 126 <input type="password" name="new_password" required minlength="8" autocomplete="new-password"> 127 127 </label> 128 128 <label class="ax-field"> 129 <span> Bevestig nieuw wachtwoord</span>129 <span><%= t('acct.password_confirm') %></span> 130 130 <input type="password" name="confirm" required minlength="8" autocomplete="new-password"> 131 131 </label> 132 132 </div> 133 133 <div class="ax-form-actions"> 134 <button type="submit" class="ax-btn ax-btn-primary"> Wachtwoord wijzigen</button>134 <button type="submit" class="ax-btn ax-btn-primary"><%= t('acct.password_change') %></button> 135 135 </div> 136 136 </form> … … 139 139 <% if (typeof googleAvailable !== 'undefined' && googleAvailable) { %> 140 140 <section class="ax-card"> 141 <div class="ax-card-title"> Inloggen met Google</div>141 <div class="ax-card-title"><%= t('acct.google_login') %></div> 142 142 <% if (typeof googleLinked !== 'undefined' && googleLinked) { %> 143 <p class="ax-tagline" style="margin:0 0 1rem"> ✓ Je Google-account is gekoppeld — je kunt ook met Google inloggen.</p>143 <p class="ax-tagline" style="margin:0 0 1rem"><%= t('acct.google_linked') %></p> 144 144 <form action="/account/google/unlink" method="post"> 145 <button type="submit" class="ax-btn"> Google ontkoppelen</button>145 <button type="submit" class="ax-btn"><%= t('acct.google_unlink') %></button> 146 146 </form> 147 147 <% } else { %> 148 <p class="ax-tagline" style="margin:0 0 1rem"> Koppel je Google-account zodat je voortaan ook met één klik via Google kunt inloggen (naast je wachtwoord).</p>149 <a href="/auth/google/link" class="ax-btn ax-btn-primary" data-full-load> Koppel Google-account</a>148 <p class="ax-tagline" style="margin:0 0 1rem"><%= t('acct.google_link_hint') %></p> 149 <a href="/auth/google/link" class="ax-btn ax-btn-primary" data-full-load><%= t('acct.google_link') %></a> 150 150 <% } %> 151 151 </section> … … 153 153 <% } else { %> 154 154 <section class="ax-card"> 155 <div class="ax-card-title"> Inloggen</div>156 <p class="ax-tagline" style="margin:0"> Je bent ingelogd via Google (<%= account.email %>). Er is geen apart wachtwoord.</p>155 <div class="ax-card-title"><%= t('acct.login') %></div> 156 <p class="ax-tagline" style="margin:0"><%= t('acct.login_google_only', { email: account.email }) %></p> 157 157 </section> 158 158 <% } %> -
src/views/pages/archive.ejs
rbbf9d1a r4885eab 9 9 <a href="<%= _archBase %>/" class="btn archive-back" 10 10 hx-get="<%= _archBase %>/?partial=1" hx-target="#pcms-main" 11 hx-push-url="<%= _archBase %>/" hx-indicator="#pcms-loading">← Terug</a>11 hx-push-url="<%= _archBase %>/" hx-indicator="#pcms-loading">← <%= t('parch.back') %></a> 12 12 </p> 13 <h1> Archief</h1>14 <p class="archive-meta"><%= totalPosts %> bericht<%= totalPosts !== 1 ? 'en' : ''%></p>13 <h1><%= t('parch.title') %></h1> 14 <p class="archive-meta"><%= totalPosts === 1 ? t('parch.count_one', { n: totalPosts }) : t('parch.count_many', { n: totalPosts }) %></p> 15 15 16 16 <% if (totalPosts === 0) { %> 17 <p class="empty-state"> Nog geen berichten.</p>17 <p class="empty-state"><%= t('parch.empty') %></p> 18 18 <% } %> 19 19 -
src/views/pages/artists-directory.ejs
rbbf9d1a r4885eab 15 15 <nav class="adir-nav"> 16 16 <div class="adir-nav-inner container"> 17 <a class="adir-nav-home" href="/"><span aria-hidden="true">←</span> <%= (typeof hubTitle !== 'undefined' && hubTitle) ? hubTitle : 'Home'%></a>17 <a class="adir-nav-home" href="/"><span aria-hidden="true">←</span> <%= (typeof hubTitle !== 'undefined' && hubTitle) ? hubTitle : t('adir.home') %></a> 18 18 <span class="adir-nav-spacer"></span> 19 19 <% if (user) { %> 20 20 <a class="adir-nav-link" href="/account"><%= user.username %></a> 21 <a class="adir-nav-link" href="/auth/logout"> Uitloggen</a>21 <a class="adir-nav-link" href="/auth/logout"><%= t('adir.logout') %></a> 22 22 <% } else { %> 23 <a class="adir-nav-link" href="/auth/login"> Inloggen</a>23 <a class="adir-nav-link" href="/auth/login"><%= t('adir.login') %></a> 24 24 <% } %> 25 25 </div> … … 27 27 28 28 <header class="adir-head container"> 29 <h1 class="adir-title"> Leden</h1>30 <p class="adir-sub"><%= t otal %> Klonkt<%= total === 1 ? '' : "'s"%></p>29 <h1 class="adir-title"><%= t('adir.title') %></h1> 30 <p class="adir-sub"><%= t(total === 1 ? 'adir.count_one' : 'adir.count_many', { n: total }) %></p> 31 31 32 32 <form class="adir-search" method="get" action="/leden" role="search"> 33 <input type="search" name="q" value="<%= q %>" placeholder=" Zoek op naam…"34 autocomplete="off" aria-label=" Zoek leden">35 <button type="submit" class="adir-search-btn" aria-label=" Zoeken">33 <input type="search" name="q" value="<%= q %>" placeholder="<%= t('adir.search_ph') %>" 34 autocomplete="off" aria-label="<%= t('adir.search_aria') %>"> 35 <button type="submit" class="adir-search-btn" aria-label="<%= t('adir.search_btn') %>"> 36 36 <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg> 37 37 </button> 38 <% if (q) { %><a class="adir-search-clear" href="/leden"> Wissen</a><% } %>38 <% if (q) { %><a class="adir-search-clear" href="/leden"><%= t('adir.clear') %></a><% } %> 39 39 </form> 40 40 </header> … … 43 43 <% if (!artists.length) { %> 44 44 <p class="adir-empty"> 45 <% if (q) { %> Geen leden gevonden voor "<strong><%= q %></strong>".<% } else { %>Nog geen leden.<% } %>45 <% if (q) { %><%- t('adir.empty_q', { q: '<strong>' + q + '</strong>' }) %><% } else { %><%= t('adir.empty') %><% } %> 46 46 </p> 47 47 <% } else { %> … … 55 55 <span class="roster-name"><%= a.title %></span> 56 56 <% if (a.tagline) { %><span class="roster-tag"><%= a.tagline %></span><% } %> 57 <span class="roster-count"><%= a.post_count %> post<%= a.post_count === 1 ? '' : 's'%></span>57 <span class="roster-count"><%= t(a.post_count === 1 ? 'adir.posts_one' : 'adir.posts_many', { n: a.post_count }) %></span> 58 58 </a> 59 59 <% }); %> … … 61 61 62 62 <% if (pages > 1) { %> 63 <nav class="adir-pager" aria-label=" Paginering">63 <nav class="adir-pager" aria-label="<%= t('adir.pager_aria') %>"> 64 64 <% if (page > 1) { %> 65 <a class="adir-page" href="<%= _qs({ page: page - 1 }) %>" rel="prev"> ← Vorige</a>65 <a class="adir-page" href="<%= _qs({ page: page - 1 }) %>" rel="prev"><%= t('adir.prev') %></a> 66 66 <% } else { %> 67 <span class="adir-page is-disabled"> ← Vorige</span>67 <span class="adir-page is-disabled"><%= t('adir.prev') %></span> 68 68 <% } %> 69 <span class="adir-page-info"> Pagina <%= page %> van <%= pages%></span>69 <span class="adir-page-info"><%= t('adir.page_info', { page: page, pages: pages }) %></span> 70 70 <% if (page < pages) { %> 71 <a class="adir-page" href="<%= _qs({ page: page + 1 }) %>" rel="next"> Volgende →</a>71 <a class="adir-page" href="<%= _qs({ page: page + 1 }) %>" rel="next"><%= t('adir.next') %></a> 72 72 <% } else { %> 73 <span class="adir-page is-disabled"> Volgende →</span>73 <span class="adir-page is-disabled"><%= t('adir.next') %></span> 74 74 <% } %> 75 75 </nav> -
src/views/pages/auth-login.ejs
rbbf9d1a r4885eab 11 11 <% 12 12 var GMAP = { 13 admin: { icon: '🔐', title: 'Dit is een beheerders-account',14 body: 'Je logde in met Google, maar dit e-mailadres hoort bij de beheerder van deze site. Inloggen met Google is alleen voor luisteraars/fans — beheerders gebruiken altijd hun wachtwoord (zo kan een Google-login nooit per ongeluk beheerrechten geven).',15 list: [ '<strong>Ben je de beheerder?</strong> <a href="/auth/admin">Log in met je gebruikersnaam + wachtwoord</a>.', '<strong>Wil je voortaan met Google inloggen?</strong> Log eerst in met wachtwoord en <strong>koppel je Google-account</strong> onder Account → daarna werkt Google-login ook voor jou.', '<strong>Wil je als fan inloggen?</strong> Gebruik een <strong>ander Google-account</strong> (niet je beheerdersadres).'] },16 session: { icon: '⏱️', title: 'Login afgebroken', body: 'De Google-login is afgebroken of de sessie was verlopen. Probeer het gewoon opnieuw.'},17 email: { icon: '✉️', title: 'Geen geverifieerd e-mailadres', body: 'Je Google-account heeft geen geverifieerd e-mailadres. Verifieer je adres bij Google.'},18 linked: { icon: '🔗', title: 'Adres al gekoppeld', body: 'Dit e-mailadres is al aan een ander Google-account gekoppeld. Gebruik dat account, of neem contact op met de beheerder.'},19 unavailable: { icon: '🚫', title: 'Google-login niet beschikbaar', body: 'Inloggen met Google is op deze site (nog) niet ingesteld.'},20 failed: { icon: '⚠️', title: 'Google-login mislukt', body: 'Er ging iets mis bij het inloggen met Google. Probeer het opnieuw.'},13 admin: { icon: '🔐', title: t('glog.admin_title'), 14 body: t('glog.admin_body'), 15 list: [t('glog.admin_li1'), t('glog.admin_li2'), t('glog.admin_li3')] }, 16 session: { icon: '⏱️', title: t('glog.session_title'), body: t('glog.session_body') }, 17 email: { icon: '✉️', title: t('glog.email_title'), body: t('glog.email_body') }, 18 linked: { icon: '🔗', title: t('glog.linked_title'), body: t('glog.linked_body') }, 19 unavailable: { icon: '🚫', title: t('glog.unavailable_title'), body: t('glog.unavailable_body') }, 20 failed: { icon: '⚠️', title: t('glog.failed_title'), body: t('glog.failed_body') }, 21 21 }; 22 22 var _g = (typeof gerr !== 'undefined' && gerr && GMAP[gerr]) ? GMAP[gerr] : null; -
src/views/pages/auth-reset-request.ejs
rbbf9d1a r4885eab 1 1 <div class="container auth-page"> 2 <h1> Wachtwoord resetten</h1>2 <h1><%= t('arrq.title') %></h1> 3 3 4 4 <% if (sent) { %> 5 5 <div class="alert alert-success"> 6 Als er een account bestaat voor dat e-mailadres, is er een reset-link verstuurd.6 <%= t('arrq.sent') %> 7 7 </div> 8 8 <% if (devResetUrl) { %> 9 9 <div class="alert alert-info"> 10 < strong>Geen e-mailserver ingesteld</strong> — reset-link hieronder.10 <%- t('arrq.no_mailserver') %> 11 11 <p style="margin: 0.5rem 0 0; word-break: break-all;"> 12 12 <a href="<%= devResetUrl %>"><%= devResetUrl %></a> … … 15 15 <% } %> 16 16 <% if (typeof mailer !== 'undefined' && !mailer) { %> 17 <p class="auth-link-muted"> Geen e-mail ingesteld? De beheerder kan ook <code>npm run reset-admin</code> draaien op de server.</p>17 <p class="auth-link-muted"><%- t('arrq.no_mail_cli') %></p> 18 18 <% } %> 19 <p class="auth-link"><a href="/auth/login"> ← Terug naar inloggen</a></p>19 <p class="auth-link"><a href="/auth/login"><%= t('arrq.back_login') %></a></p> 20 20 <% } else { %> 21 <p class="auth-tagline"> Vul je e-mail in; we sturen een reset-link.</p>21 <p class="auth-tagline"><%= t('arrq.tagline') %></p> 22 22 23 23 <% if (error) { %><div class="alert alert-error"><%= error %></div><% } %> … … 25 25 <form method="post" action="/auth/reset-request" class="auth-form"> 26 26 <label> 27 <span> E-mail</span>27 <span><%= t('arrq.email') %></span> 28 28 <input type="email" name="email" required autofocus autocomplete="email"> 29 29 </label> 30 <button type="submit" class="btn btn-primary"> Stuur reset-link</button>31 <p class="auth-link"><a href="/auth/login"> ← Terug naar inloggen</a></p>30 <button type="submit" class="btn btn-primary"><%= t('arrq.submit') %></button> 31 <p class="auth-link"><a href="/auth/login"><%= t('arrq.back_login') %></a></p> 32 32 </form> 33 33 <% } %> -
src/views/pages/auth-reset.ejs
rbbf9d1a r4885eab 1 1 <div class="container auth-page"> 2 <h1> Wachtwoord resetten</h1>2 <h1><%= t('arst.title') %></h1> 3 3 4 4 <% if (!token) { %> 5 5 <% if (error) { %><div class="alert alert-error"><%= error %></div><% } %> 6 6 <p class="auth-link"> 7 <a href="/auth/reset-request"> Vraag een nieuwe reset-link aan</a>7 <a href="/auth/reset-request"><%= t('arst.request_new') %></a> 8 8 · 9 <a href="/auth/login"> Terug naar inloggen</a>9 <a href="/auth/login"><%= t('arst.back_login') %></a> 10 10 </p> 11 11 <% } else { %> 12 <p class="auth-tagline">Stel een nieuw wachtwoord in voor <strong><%= username %></strong>.</p> 12 <p class="auth-tagline"><%- t('arst.set_for', { username: '<strong>' + username + '</strong>' }) %></p> 13 <%# arst.set_for bevat {username} → rendered met <%- omdat username in <strong> staat %> 13 14 14 15 <% if (error) { %><div class="alert alert-error"><%= error %></div><% } %> … … 16 17 <form method="post" action="/auth/reset/<%= token %>" class="auth-form"> 17 18 <label> 18 <span> Nieuw wachtwoord (min 8 tekens)</span>19 <span><%= t('arst.new_pw') %></span> 19 20 <input type="password" name="new_password" required minlength="8" autofocus autocomplete="new-password"> 20 21 </label> 21 22 <label> 22 <span> Bevestig nieuw wachtwoord</span>23 <span><%= t('arst.confirm_pw') %></span> 23 24 <input type="password" name="confirm" required minlength="8" autocomplete="new-password"> 24 25 </label> 25 <button type="submit" class="btn btn-primary"> Wachtwoord instellen</button>26 <button type="submit" class="btn btn-primary"><%= t('arst.submit') %></button> 26 27 </form> 27 28 <% } %> -
src/views/pages/changelog.ejs
rbbf9d1a r4885eab 1 1 <div class="container changelog-page"> 2 <p><a href="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/" class="btn">← Terug</a></p>3 <h1> Wijzigingen</h1>2 <p><a href="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/" class="btn">← <%= t('chlog.back') %></a></p> 3 <h1><%= t('chlog.title') %></h1> 4 4 <p class="cl-meta"> 5 App-versie<strong>v<%= appVersion %></strong>5 <%= t('chlog.app_version') %> <strong>v<%= appVersion %></strong> 6 6 <span class="cl-sep">·</span> 7 Federatie-proto<strong><%= proto %></strong>7 <%= t('chlog.fed_proto') %> <strong><%= proto %></strong> 8 8 <% if (typeof user !== 'undefined' && user && user.role === 'god') { %> 9 <span class="cl-sep">·</span> <a href="/admin/updates"> Updates beheren</a>9 <span class="cl-sep">·</span> <a href="/admin/updates"><%= t('chlog.manage_updates') %></a> 10 10 <% } %> 11 11 </p> -
src/views/pages/circle-feed.ejs
rbbf9d1a r4885eab 10 10 </svg> 11 11 </span> 12 <h1> Cirkel</h1>13 <p class="cirkel-sub"> Wat er speelt bij de andere sites in mijn cirkel.</p>12 <h1><%= t('cfeed.title') %></h1> 13 <p class="cirkel-sub"><%= t('cfeed.sub') %></p> 14 14 <% if (typeof sites !== 'undefined' && sites && sites.length) { %> 15 15 <% if (sites.length <= 5) { %> … … 24 24 <% } else { %> 25 25 <%# Meer dan 5: geen chips (zou een muur worden) — alleen het aantal. %> 26 <span class="cirkel-count"><%= sites.length %> sites in mijn cirkel</span>26 <span class="cirkel-count"><%= t('cfeed.count', { n: sites.length }) %></span> 27 27 <% } %> 28 28 <% } %> … … 31 31 <% if (!posts.length) { %> 32 32 <div class="container"> 33 <p class="cirkel-empty"> Nog niets in mijn cirkel.</p>33 <p class="cirkel-empty"><%= t('cfeed.empty') %></p> 34 34 </div> 35 35 <% } else { %> … … 50 50 51 51 <!-- ===== GRID (body[data-feed-view="grid"]) ===== --> 52 <section class="feed-grid container" aria-label=" Grid-weergave">52 <section class="feed-grid container" aria-label="<%= t('cfeed.grid_view') %>"> 53 53 <div class="grid-tiles"> 54 54 <% posts.forEach(function(post){ %> -
src/views/pages/circle-post.ejs
rbbf9d1a r4885eab 2 2 <p class="cirkel-post-back"> 3 3 <a href="/cirkel" hx-get="/cirkel?partial=1" hx-target="#pcms-main" hx-swap="innerHTML" 4 hx-push-url="/cirkel" hx-indicator="#pcms-loading">← Cirkel</a>4 hx-push-url="/cirkel" hx-indicator="#pcms-loading">← <%= t('cpost.back') %></a> 5 5 </p> 6 6 … … 8 8 <div class="cirkel-post-src"> 9 9 <% if (post.sourceAvatar) { %><img class="cirkel-post-avatar" src="<%= post.sourceAvatar %>" alt=""><% } %> 10 <span> via10 <span><%= t('cpost.via') %> 11 11 <% if (post.sourceUrl) { %><a href="<%= post.sourceUrl %>" target="_blank" rel="noopener"><%= post.sourceName %></a> 12 12 <% } else { %><%= post.sourceName %><% } %> … … 37 37 <% if (post.originalUrl) { %> 38 38 <p class="cirkel-post-readmore"> 39 <a href="<%= post.originalUrl %>" target="_blank" rel="noopener"> Lees verder bij <%= post.sourceName%> →</a>39 <a href="<%= post.originalUrl %>" target="_blank" rel="noopener"><%= t('cpost.read_more', { source: post.sourceName }) %> →</a> 40 40 </p> 41 41 <% } %> -
src/views/pages/download.ejs
rbbf9d1a r4885eab 1 <% 2 var _ready_h1 = t('dl.ready_title'); 3 var _ready_sub = t('dl.ready_sub'); 4 var _manual = t('dl.manual'); 5 var _capture_sub = t('dl.capture_sub'); 6 var _email_ph = t('dl.email_ph'); 7 var _download_btn = t('dl.download_btn'); 8 %> 1 9 <% 2 10 var t = (typeof dlTrack !== 'undefined') ? dlTrack : {}; … … 15 23 16 24 <% if (st === 'ready') { %> 17 <h1 class="dl-h1"> Bedankt! ⬇</h1>18 <p class="dl-sub"> Je download zou nu moeten starten. Gebeurt er niets?</p>19 <p><a class="dl-go" href="<%= fileUrl %>"> Download handmatig starten</a></p>25 <h1 class="dl-h1"><%= _ready_h1 %></h1> 26 <p class="dl-sub"><%= _ready_sub %></p> 27 <p><a class="dl-go" href="<%= fileUrl %>"><%= _manual %></a></p> 20 28 <script> 21 29 // Auto-start de download (zelfde-origin attachment-link). … … 23 31 </script> 24 32 <% } else { %> 25 <h1 class="dl-h1"> Download<%= t.title %></h1>26 <p class="dl-sub"> Laat je e-mailadres achter en je krijgt het bestand. Je komt dan ook op de nieuwsbrieflijst — uitschrijven kan altijd.</p>33 <h1 class="dl-h1"><%= _download_btn %> <%= t.title %></h1> 34 <p class="dl-sub"><%= _capture_sub %></p> 27 35 <% if (typeof dlError !== 'undefined' && dlError) { %><p class="dl-err"><%= dlError %></p><% } %> 28 36 <form method="POST" action="<%= siteUrlBase %>/download/<%= t.id %>" class="dl-form"> 29 <input type="email" name="email" required placeholder=" jouw@email.nl" value="<%= (typeof dlPrefill!=='undefined')?dlPrefill:'' %>" autocomplete="email">30 <button type="submit" class="dl-go">⬇ Download</button>37 <input type="email" name="email" required placeholder="<%= _email_ph %>" value="<%= (typeof dlPrefill!=='undefined')?dlPrefill:'' %>" autocomplete="email"> 38 <button type="submit" class="dl-go">⬇ <%= _download_btn %></button> 31 39 </form> 32 40 <% } %> -
src/views/pages/epk.ejs
rbbf9d1a r4885eab 11 11 <% if (_photo) { %><div class="epk-photo"><img src="<%= _photo %>" alt="<%= site.title %>"></div><% } %> 12 12 <div class="epk-hero-text"> 13 <div class="epk-kicker"> Perskit<% if (typeof user !== 'undefined' && user && user.role === 'god') { %> <a class="epk-edit-link" href="/admin/epk">✎ Bewerken</a><% } %></div>13 <div class="epk-kicker"><%= t('epk.kicker') %><% if (typeof user !== 'undefined' && user && user.role === 'god') { %> <a class="epk-edit-link" href="/admin/epk">✎ <%= t('epk.edit') %></a><% } %></div> 14 14 <h1 class="epk-title"><%= site.title %></h1> 15 15 <% if (epkBio) { %><p class="epk-bio"><%= epkBio %></p><% } %> 16 16 <div class="epk-actions"> 17 17 <% if (_isMail) { %> 18 <a class="epk-btn epk-btn-primary" href="mailto:<%= epkContact %>">✉ Contact / boeking</a>18 <a class="epk-btn epk-btn-primary" href="mailto:<%= epkContact %>">✉ <%= t('epk.contact_booking') %></a> 19 19 <% } else if (_isUrl) { %> 20 <a class="epk-btn epk-btn-primary" href="<%= epkContact %>" target="_blank" rel="noopener"> Contact / boeking</a>20 <a class="epk-btn epk-btn-primary" href="<%= epkContact %>" target="_blank" rel="noopener"><%= t('epk.contact_booking') %></a> 21 21 <% } %> 22 <a class="epk-btn" href="<%= siteUrlBase %>/"> Bekijk de site →</a>23 <% if (_photo) { %><a class="epk-btn" href="<%= _photo %>" download>⬇ Persfoto</a><% } %>22 <a class="epk-btn" href="<%= siteUrlBase %>/"><%= t('epk.view_site') %></a> 23 <% if (_photo) { %><a class="epk-btn" href="<%= _photo %>" download>⬇ <%= t('epk.press_photo') %></a><% } %> 24 24 </div> 25 25 </div> … … 28 28 <% if (epkTracks && epkTracks.length) { %> 29 29 <div class="epk-block"> 30 <h2 class="epk-h2"> Meest beluisterd</h2>30 <h2 class="epk-h2"><%= t('epk.most_played') %></h2> 31 31 <ul class="epk-tracks"> 32 32 <% epkTracks.forEach(function(t, i){ %> … … 47 47 <% if (epkPosts && epkPosts.length) { %> 48 48 <div class="epk-block"> 49 <h2 class="epk-h2"> Recent</h2>49 <h2 class="epk-h2"><%= t('epk.recent') %></h2> 50 50 <ul class="epk-posts"> 51 51 <% epkPosts.forEach(function(p){ %> -
src/views/pages/fan-gate.ejs
rbbf9d1a r4885eab 9 9 <div class="fg-card"> 10 10 <div class="fg-lock">🔒</div> 11 <h1 class="fg-h1"> Alleen voor fans</h1>11 <h1 class="fg-h1"><%= t('fgate.title') %></h1> 12 12 <% if (typeof fgTitle !== 'undefined' && fgTitle) { %><p class="fg-which">"<%= fgTitle %>"</p><% } %> 13 <p class="fg-sub"> Dit is exclusief voor ingelogde fans. Log in om het te bekijken.</p>14 <p><a class="fg-btn" href="/auth/login?next=<%= encodeURIComponent(fgNext || '/') %>"> Inloggen / aanmelden</a></p>13 <p class="fg-sub"><%= t('fgate.sub') %></p> 14 <p><a class="fg-btn" href="/auth/login?next=<%= encodeURIComponent(fgNext || '/') %>"><%= t('fgate.login') %></a></p> 15 15 </div> 16 16 </section> -
src/views/pages/favorites.ejs
rbbf9d1a r4885eab 1 1 <div class="container favorites-page"> 2 <h1> Favorieten</h1>3 <p class="fav-sub"> Posts die je hebt geliket. Tik op het ♥ bij een post om 'm hier te bewaren.</p>2 <h1><%= t('fav.title') %></h1> 3 <p class="fav-sub"><%= t('fav.sub') %></p> 4 4 5 5 <% if (!posts || !posts.length) { %> 6 <p class="fav-empty"> Je hebt nog geen favorieten. Open een post en tik op het hartje ♡.</p>6 <p class="fav-empty"><%= t('fav.empty') %></p> 7 7 <% } else { %> 8 8 <ul class="post-list"> -
src/views/pages/home.ejs
rbbf9d1a r4885eab 23 23 <% if (allPosts.length === 0) { %> 24 24 <div class="empty"> 25 <h1> Hier is het nog stil.</h1>26 <p> Nog geen posts. Spannend.</p>25 <h1><%= t('phome.empty_title') %></h1> 26 <p><%= t('phome.empty_sub') %></p> 27 27 <% if (user && canMutate && permissions.canCreatePost(user, site)) { %> 28 28 <p style="margin-top:2rem"> … … 30 30 hx-get="<%= _base %>/posts/new?partial=1" hx-target="#pcms-main" hx-swap="innerHTML" 31 31 hx-push-url="<%= _base %>/posts/new" hx-indicator="#pcms-loading"> 32 Schrijf je eerste post→32 <%= t('phome.write_first') %> → 33 33 </a> 34 34 </p> … … 47 47 <!-- ========== GRID (only visible when body[data-feed-view="grid"]) ========== --> 48 48 <% if (allPosts.length > 0) { %> 49 <section class="feed-grid container" aria-label=" Grid-weergave">49 <section class="feed-grid container" aria-label="<%= t('phome.grid_view') %>"> 50 50 <div class="grid-tiles"> 51 51 <% allPosts.forEach(function(post) { %> -
src/views/pages/hub-home.ejs
rbbf9d1a r4885eab 28 28 </span> 29 29 <span class="hub-main-info"> 30 <span class="hub-main-badge"> Hoofdpagina</span>30 <span class="hub-main-badge"><%= t('phub.main_badge') %></span> 31 31 <span class="hub-main-name"><%= mainSite.title %></span> 32 32 <% if (mainSite.tagline) { %><span class="hub-main-tag"><%= mainSite.tagline %></span><% } %> 33 33 </span> 34 <span class="hub-main-cta"> Bekijk de pagina<span aria-hidden="true">→</span></span>34 <span class="hub-main-cta"><%= t('phub.view_page') %> <span aria-hidden="true">→</span></span> 35 35 </a> 36 36 </section> … … 43 43 <section class="hub-sec"> 44 44 <h2 class="hub-sec-title"> 45 Leden45 <%= t('phub.members') %> 46 46 <% if (_hasMore) { %><span class="hub-sec-count"><%= _total %></span><% } %> 47 47 </h2> … … 57 57 <span class="roster-name"><%= a.title %></span> 58 58 <% if (a.tagline) { %><span class="roster-tag"><%= a.tagline %></span><% } %> 59 <span class="roster-count"><%= a.post_count %> post<%= a.post_count === 1 ? '' : 's'%></span>59 <span class="roster-count"><%= a.post_count %> <%= a.post_count === 1 ? t('phub.post_one') : t('phub.post_many') %></span> 60 60 </a> 61 61 <% }); %> … … 63 63 <% if (_hasMore) { %> 64 64 <div class="hub-more"> 65 <a class="hub-more-link" href="/leden"> Alle <%= _total %> leden<span aria-hidden="true">→</span></a>65 <a class="hub-more-link" href="/leden"><%= t('phub.all_members', { n: _total }) %> <span aria-hidden="true">→</span></a> 66 66 </div> 67 67 <% } %> … … 72 72 <% if (posts && posts.length) { %> 73 73 <section class="hub-sec"> 74 <h2 class="hub-sec-title"> Laatste posts van onze leden.</h2>74 <h2 class="hub-sec-title"><%= t('phub.latest_posts') %></h2> 75 75 <div class="hub-feed"> 76 76 <% posts.forEach(function(p){ %> -
src/views/pages/linkbio.ejs
rbbf9d1a r4885eab 13 13 14 14 <% if (!links.length) { %> 15 <p class="lb-empty"> Nog geen links ingesteld.</p>15 <p class="lb-empty"><%= t('lbio.empty') %></p> 16 16 <% } else { %> 17 17 <div class="lb-links"> … … 25 25 <% } %> 26 26 27 <p class="lb-foot"><a href="<%= siteUrlBase %>/">← naar de site</a></p>27 <p class="lb-foot"><a href="<%= siteUrlBase %>/">← <%= t('lbio.back_to_site') %></a></p> 28 28 </section> 29 29 -
src/views/pages/my-site.ejs
rbbf9d1a r4885eab 1 1 <div class="container admin-page"> 2 <p><a href="/" class="btn">← Overzicht</a></p>3 <h1> Mijn Klonkt Hub</h1>2 <p><a href="/" class="btn">← <%= t('myst.overview') %></a></p> 3 <h1><%= t('myst.title') %></h1> 4 4 <p class="admin-tagline"><%= mySite.title %></p> 5 5 6 <nav class="admin-quick-links" aria-label=" Beheer-snelkoppelingen">7 <a href="/user/<%= mySite.slug %>/posts/new" class="btn">✍️ Nieuwe post</a>8 <a href="/admin/sites/<%= mySite.slug %>/edit" class="btn">🎨 Uiterlijk</a>9 <a href="/user/<%= mySite.slug %>/admin/comments" class="btn">💬 Reacties</a>10 <a href="/user/<%= mySite.slug %>" class="btn">👁️ Bekijk mijn site</a>11 <a href="/account" class="btn">👤 Account</a>6 <nav class="admin-quick-links" aria-label="<%= t('myst.quick_links_aria') %>"> 7 <a href="/user/<%= mySite.slug %>/posts/new" class="btn">✍️ <%= t('myst.new_post') %></a> 8 <a href="/admin/sites/<%= mySite.slug %>/edit" class="btn">🎨 <%= t('myst.appearance') %></a> 9 <a href="/user/<%= mySite.slug %>/admin/comments" class="btn">💬 <%= t('myst.comments') %></a> 10 <a href="/user/<%= mySite.slug %>" class="btn">👁️ <%= t('myst.view_site') %></a> 11 <a href="/account" class="btn">👤 <%= t('myst.account') %></a> 12 12 </nav> 13 13 14 14 <div class="admin-stats"> 15 <div class="stat-card"><div class="stat-num"><%= mine.posts %></div><div class="stat-label"> Posts</div></div>16 <div class="stat-card"><div class="stat-num"><%= mine.published %></div><div class="stat-label"> Gepubliceerd</div></div>15 <div class="stat-card"><div class="stat-num"><%= mine.posts %></div><div class="stat-label"><%= t('myst.posts') %></div></div> 16 <div class="stat-card"><div class="stat-num"><%= mine.published %></div><div class="stat-label"><%= t('myst.published') %></div></div> 17 17 </div> 18 18 19 19 <% if (typeof posts !== 'undefined' && posts && posts.length) { %> 20 20 <% var _drafts = posts.filter(function(p){ return p.isDraft; }).length; %> 21 <h2 class="ms-posts-title">Posts<% if (_drafts) { %> <span class="pl-draftcount"><%= _drafts %> concept<%= _drafts === 1 ? '' : 'en' %></span><% } %></h2> 21 <% 22 var _lblBadge = t('myst.draft_badge'); 23 var _lblUntitled = t('myst.untitled'); 24 var _lblEdit = t('myst.edit'); 25 var _lblView = t('myst.view'); 26 %> 27 <h2 class="ms-posts-title"><%= t('myst.posts') %><% if (_drafts) { %> <span class="pl-draftcount"><%= _drafts === 1 ? t('myst.draft_count_one', { n: _drafts }) : t('myst.draft_count_many', { n: _drafts }) %></span><% } %></h2> 22 28 <ul class="post-admin-list"> 23 29 <% posts.forEach(function(p) { %> 24 30 <li class="pal-row<%= p.isDraft ? ' is-draft' : '' %>"> 25 31 <a class="pal-title" href="<%= p.editUrl %>"> 26 <% if (p.isDraft) { %><span class="pal-badge"> Concept</span><% } %>27 <span class="pal-name"><%= p.title || '(zonder titel)'%></span>32 <% if (p.isDraft) { %><span class="pal-badge"><%= _lblBadge %></span><% } %> 33 <span class="pal-name"><%= p.title || _lblUntitled %></span> 28 34 </a> 29 35 <span class="pal-actions"> 30 <a class="pal-link" href="<%= p.editUrl %>"> Bewerken</a>31 <% if (!p.isDraft) { %><a class="pal-link" href="<%= p.viewUrl %>"> Bekijk</a><% } %>36 <a class="pal-link" href="<%= p.editUrl %>"><%= _lblEdit %></a> 37 <% if (!p.isDraft) { %><a class="pal-link" href="<%= p.viewUrl %>"><%= _lblView %></a><% } %> 32 38 </span> 33 39 </li> -
src/views/pages/newsletter.ejs
rbbf9d1a r4885eab 1 1 <% 2 2 var _site = site || {}; 3 var _title = _site.title || 'deze artiest';3 var _title = _site.title || t('news.this_artist'); 4 4 var st = (typeof nlState !== 'undefined') ? nlState : 'form'; 5 5 %> … … 7 7 <div class="nl-card"> 8 8 <% if (st === 'form') { %> 9 <h1 class="nl-h1"> Blijf op de hoogte</h1>10 <p class="nl-sub"> Schrijf je in voor de nieuwsbrief van <strong><%= _title %></strong> — nieuwe muziek, shows en updates, rechtstreeks in je inbox. Uitschrijven kan altijd met één klik.</p>9 <h1 class="nl-h1"><%= t('news.form_title') %></h1> 10 <p class="nl-sub"><%= t('news.form_sub_before') %><strong><%= _title %></strong><%= t('news.form_sub_after') %></p> 11 11 <form method="POST" action="<%= siteUrlBase %>/nieuwsbrief" class="nl-form"> 12 <input type="email" name="email" required placeholder=" jouw@email.nl" value="<%= (typeof nlPrefill !== 'undefined') ? nlPrefill : '' %>" autocomplete="email">13 <button type="submit" class="nl-btn"> Inschrijven</button>12 <input type="email" name="email" required placeholder="<%= t('news.email_ph') %>" value="<%= (typeof nlPrefill !== 'undefined') ? nlPrefill : '' %>" autocomplete="email"> 13 <button type="submit" class="nl-btn"><%= t('news.subscribe') %></button> 14 14 </form> 15 15 <% } else if (st === 'check') { %> 16 <h1 class="nl-h1"> Bijna klaar ✉</h1>17 <p class="nl-sub"> We hebben een bevestigingsmail gestuurd naar <strong><%= (typeof nlEmail!=='undefined')?nlEmail:'je adres' %></strong>. Klik op de link in die mail om je inschrijving te bevestigen.</p>16 <h1 class="nl-h1"><%= t('news.check_title') %></h1> 17 <p class="nl-sub"><%= t('news.check_sub_before') %><strong><%= (typeof nlEmail!=='undefined')?nlEmail:t('news.your_address') %></strong><%= t('news.check_sub_after') %></p> 18 18 <% } else if (st === 'done') { %> 19 <h1 class="nl-h1"> Je bent ingeschreven ✓</h1>20 <p class="nl-sub"> Bedankt — je staat op de lijst van <strong><%= _title %></strong>.</p>19 <h1 class="nl-h1"><%= t('news.done_title') %></h1> 20 <p class="nl-sub"><%= t('news.done_sub_before') %><strong><%= _title %></strong><%= t('news.done_sub_after') %></p> 21 21 <% } else if (st === 'confirmed') { %> 22 <h1 class="nl-h1"> Inschrijving bevestigd ✓</h1>23 <p class="nl-sub"> Top! Je ontvangt voortaan de nieuwsbrief van <strong><%= _title %></strong>.</p>22 <h1 class="nl-h1"><%= t('news.confirmed_title') %></h1> 23 <p class="nl-sub"><%= t('news.confirmed_sub_before') %><strong><%= _title %></strong><%= t('news.confirmed_sub_after') %></p> 24 24 <% } else if (st === 'unsubbed') { %> 25 <h1 class="nl-h1"> Uitgeschreven</h1>26 <p class="nl-sub"> Je bent afgemeld. Je ontvangt geen nieuwsbrieven meer. Van gedachten veranderd? Je kunt je altijd opnieuw inschrijven.</p>25 <h1 class="nl-h1"><%= t('news.unsubbed_title') %></h1> 26 <p class="nl-sub"><%= t('news.unsubbed_sub') %></p> 27 27 <% } else if (st === 'invalid') { %> 28 <h1 class="nl-h1"> Ongeldig e-mailadres</h1>29 <p class="nl-sub"> Controleer je adres en probeer 't opnieuw.</p>30 <p><a class="nl-btn nl-btn-ghost" href="<%= siteUrlBase %>/nieuwsbrief"> ← Terug</a></p>28 <h1 class="nl-h1"><%= t('news.invalid_title') %></h1> 29 <p class="nl-sub"><%= t('news.invalid_sub') %></p> 30 <p><a class="nl-btn nl-btn-ghost" href="<%= siteUrlBase %>/nieuwsbrief"><%= t('news.back') %></a></p> 31 31 <% } else if (st === 'smtperror') { %> 32 <h1 class="nl-h1"> Even niet gelukt</h1>33 <p class="nl-sub"> De bevestigingsmail kon niet verstuurd worden. Probeer 't later nog eens.</p>32 <h1 class="nl-h1"><%= t('news.smtperror_title') %></h1> 33 <p class="nl-sub"><%= t('news.smtperror_sub') %></p> 34 34 <% } else if (st === 'badtoken') { %> 35 <h1 class="nl-h1"> Link ongeldig of verlopen</h1>36 <p class="nl-sub"> Deze link werkt niet meer. Schrijf je eventueel opnieuw in.</p>37 <p><a class="nl-btn nl-btn-ghost" href="<%= siteUrlBase %>/nieuwsbrief"> Naar inschrijven</a></p>35 <h1 class="nl-h1"><%= t('news.badtoken_title') %></h1> 36 <p class="nl-sub"><%= t('news.badtoken_sub') %></p> 37 <p><a class="nl-btn nl-btn-ghost" href="<%= siteUrlBase %>/nieuwsbrief"><%= t('news.to_subscribe') %></a></p> 38 38 <% } else { %> 39 <h1 class="nl-h1"> Er ging iets mis</h1>40 <p class="nl-sub"> Probeer 't later nog eens.</p>39 <h1 class="nl-h1"><%= t('news.error_title') %></h1> 40 <p class="nl-sub"><%= t('news.error_sub') %></p> 41 41 <% } %> 42 42 </div> -
src/views/pages/post-edit.ejs
rbbf9d1a r4885eab 15 15 %> 16 16 <div class="container post-edit-page"> 17 <h1><%= isNew ? 'Nieuwe post' : 'Post bewerken'%></h1>17 <h1><%= isNew ? t('pedit.title_new') : t('pedit.title_edit') %></h1> 18 18 19 19 <form method="post" action="<%= _base %><%= isNew ? '/posts/create' : '/posts/' + post.slug + '/save' %>" class="post-edit-form"> … … 22 22 <section class="pe-card"> 23 23 <label class="pe-field"> 24 <span> Titel</span>24 <span><%= t('pedit.f_title') %></span> 25 25 <input type="text" name="title" value="<%= post.title %>" required> 26 26 </label> 27 27 <div class="pe-row pe-row-2"> 28 28 <label class="pe-field"> 29 <span> Slug (URL)</span>30 <input type="text" name="slug" value="<%= post.slug %>" placeholder=" auto van titel als leeg">29 <span><%= t('pedit.f_slug') %></span> 30 <input type="text" name="slug" value="<%= post.slug %>" placeholder="<%= t('pedit.slug_placeholder') %>"> 31 31 </label> 32 32 <label class="pe-field"> 33 <span> Tags <small>(komma-gescheiden)</small></span>33 <span><%= t('pedit.f_tags') %> <small><%= t('pedit.tags_hint') %></small></span> 34 34 <input type="text" name="tags" value="<%= Array.isArray(post.tags) ? post.tags.join(', ') : '' %>"> 35 35 </label> 36 36 </div> 37 37 <label class="pe-field"> 38 <span> Samenvatting & Cirkel Preview</span>38 <span><%= t('pedit.f_excerpt') %></span> 39 39 <textarea name="excerpt" rows="2"><%= post.excerpt %></textarea> 40 <small class="pe-hint" style="opacity:.7"> Wordt ook gebruikt als samenvatting in <strong>Cirkels</strong> (andere sites die je post tonen). Leeg laten = begin van de post.</small>40 <small class="pe-hint" style="opacity:.7"><%- t('pedit.excerpt_hint') %></small> 41 41 </label> 42 42 </section> … … 44 44 <%# ── COVER ────────────────────────────────────────────────── %> 45 45 <section class="pe-card"> 46 <div class="pe-section-title"> Cover</div>46 <div class="pe-section-title"><%= t('pedit.s_cover') %></div> 47 47 <div class="pe-cover-row"> 48 48 <div class="pe-cover-thumb" id="cover-preview-wrap" <%= _hasCover ? '' : 'data-empty' %>> … … 52 52 <div class="pe-cover-actions"> 53 53 <label class="pe-field"> 54 <span> Cover URL</span>54 <span><%= t('pedit.f_cover_url') %></span> 55 55 <input type="text" name="cover_image_url" id="cover-url-field" 56 56 value="<%= post.cover_image_url || '' %>" 57 57 inputmode="url" autocapitalize="none" spellcheck="false" 58 placeholder=" /media/… of https://… (of upload met de knop)">58 placeholder="<%= t('pedit.cover_url_placeholder') %>"> 59 59 </label> 60 60 <div class="pe-cover-upload"> 61 61 <button type="button" class="pe-btn pe-btn-secondary" id="cover-upload-trigger"> 62 📷 Upload nieuwe cover62 📷 <%= t('pedit.cover_upload_btn') %> 63 63 </button> 64 64 <input type="file" id="cover-upload-field" accept="image/jpeg,image/png,image/webp,image/gif" hidden> … … 72 72 <section class="pe-card pe-card-content"> 73 73 <div class="pe-section-title"> 74 Content75 <small class="pe-content-hint"> Sleep een afbeelding om in te voegen · selecteer tekst om op te maken</small>74 <%= t('pedit.s_content') %> 75 <small class="pe-content-hint"><%= t('pedit.content_hint') %></small> 76 76 </div> 77 77 <div class="pe-editor-frame"> 78 78 <div class="pe-toolbar" id="pe-toolbar" role="toolbar" aria-label="Format"> 79 <button type="button" id="pe-fs-done" class="pe-fs-done" title=" Klaar met bewerken">✓ Klaar</button>80 <button type="button" data-cmd="bold" title=" Vet (Ctrl+B)" aria-label="Vet">79 <button type="button" id="pe-fs-done" class="pe-fs-done" title="<%= t('pedit.tb_done_title') %>">✓ <%= t('pedit.tb_done') %></button> 80 <button type="button" data-cmd="bold" title="<%= t('pedit.tb_bold_title') %>" aria-label="<%= t('pedit.tb_bold') %>"> 81 81 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 4h8a4 4 0 0 1 0 8H6z"/><path d="M6 12h9a4 4 0 0 1 0 8H6z"/></svg> 82 82 </button> 83 <button type="button" data-cmd="italic" title=" Cursief (Ctrl+I)" aria-label="Cursief">83 <button type="button" data-cmd="italic" title="<%= t('pedit.tb_italic_title') %>" aria-label="<%= t('pedit.tb_italic') %>"> 84 84 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/></svg> 85 85 </button> 86 <button type="button" data-cmd="underline" title=" Onderstreept" aria-label="Onderstreept">86 <button type="button" data-cmd="underline" title="<%= t('pedit.tb_underline') %>" aria-label="<%= t('pedit.tb_underline') %>"> 87 87 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 4v6a6 6 0 0 0 12 0V4"/><line x1="4" y1="20" x2="20" y2="20"/></svg> 88 88 </button> 89 89 <span class="pe-toolbar-sep" aria-hidden="true"></span> 90 <button type="button" class="pe-tb-text" data-cmd="formatBlock" data-arg="h2" title=" Kop">H2</button>91 <button type="button" class="pe-tb-text" data-cmd="formatBlock" data-arg="h3" title=" Subkop">H3</button>92 <button type="button" class="pe-tb-text" data-cmd="formatBlock" data-arg="p" title=" Paragraaf">¶</button>90 <button type="button" class="pe-tb-text" data-cmd="formatBlock" data-arg="h2" title="<%= t('pedit.tb_h2') %>">H2</button> 91 <button type="button" class="pe-tb-text" data-cmd="formatBlock" data-arg="h3" title="<%= t('pedit.tb_h3') %>">H3</button> 92 <button type="button" class="pe-tb-text" data-cmd="formatBlock" data-arg="p" title="<%= t('pedit.tb_p') %>">¶</button> 93 93 <span class="pe-toolbar-sep" aria-hidden="true"></span> 94 <button type="button" data-cmd="insertUnorderedList" title=" Lijst" aria-label="Lijst">94 <button type="button" data-cmd="insertUnorderedList" title="<%= t('pedit.tb_ul') %>" aria-label="<%= t('pedit.tb_ul') %>"> 95 95 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="9" y1="6" x2="20" y2="6"/><line x1="9" y1="12" x2="20" y2="12"/><line x1="9" y1="18" x2="20" y2="18"/><circle cx="4.5" cy="6" r="1.2"/><circle cx="4.5" cy="12" r="1.2"/><circle cx="4.5" cy="18" r="1.2"/></svg> 96 96 </button> 97 <button type="button" data-cmd="insertOrderedList" title=" Genummerde lijst" aria-label="Genummerde lijst">97 <button type="button" data-cmd="insertOrderedList" title="<%= t('pedit.tb_ol') %>" aria-label="<%= t('pedit.tb_ol') %>"> 98 98 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/></svg> 99 99 </button> 100 <button type="button" data-cmd="formatBlock" data-arg="blockquote" title=" Quote" aria-label="Quote">100 <button type="button" data-cmd="formatBlock" data-arg="blockquote" title="<%= t('pedit.tb_quote') %>" aria-label="<%= t('pedit.tb_quote') %>"> 101 101 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.75-2-2-2H4c-1.25 0-2 .75-2 2v6c0 1.25.75 2 2 2h2.5C6 17.5 4 19 3 19v2z"/><path d="M14 21c3 0 7-1 7-8V5c0-1.25-.75-2-2-2h-4c-1.25 0-2 .75-2 2v6c0 1.25.75 2 2 2h2.5c-.5 4.5-2.5 6-3.5 6v2z"/></svg> 102 102 </button> 103 <button type="button" data-cmd="link-prompt" title=" Link (Ctrl+K)" aria-label="Link">103 <button type="button" data-cmd="link-prompt" title="<%= t('pedit.tb_link_title') %>" aria-label="<%= t('pedit.tb_link') %>"> 104 104 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg> 105 105 </button> 106 <button type="button" data-cmd="code-wrap" title=" Code (inline)" aria-label="Code">106 <button type="button" data-cmd="code-wrap" title="<%= t('pedit.tb_code_title') %>" aria-label="<%= t('pedit.tb_code') %>"> 107 107 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg> 108 108 </button> 109 109 <span class="pe-toolbar-sep" aria-hidden="true"></span> 110 <button type="button" id="insert-image-btn" title=" Afbeelding invoegen" aria-label="Afbeelding">110 <button type="button" id="insert-image-btn" title="<%= t('pedit.tb_image_title') %>" aria-label="<%= t('pedit.tb_image') %>"> 111 111 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg> 112 112 </button> 113 <button type="button" id="insert-track-btn" title=" Track invoegen" aria-label="Track">113 <button type="button" id="insert-track-btn" title="<%= t('pedit.tb_track_title') %>" aria-label="<%= t('pedit.tb_track') %>"> 114 114 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg> 115 115 </button> 116 <button type="button" id="insert-playlist-btn" title=" Playlist invoegen" aria-label="Playlist">116 <button type="button" id="insert-playlist-btn" title="<%= t('pedit.tb_playlist_title') %>" aria-label="<%= t('pedit.tb_playlist') %>"> 117 117 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="15" y2="18"/><polygon points="3 5 3 13 9 9"/></svg> 118 118 </button> 119 <button type="button" id="insert-embed-btn" title=" Embed (YouTube, Spotify, SoundCloud, Vimeo…)" aria-label="Media embedden">119 <button type="button" id="insert-embed-btn" title="<%= t('pedit.tb_embed_title') %>" aria-label="<%= t('pedit.tb_embed') %>"> 120 120 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="4" width="20" height="16" rx="2"/><polygon points="10 9 15.5 12 10 15"/></svg> 121 121 </button> 122 122 <span class="pe-toolbar-spacer"></span> 123 <button type="button" data-cmd="removeFormat" title=" Wis opmaak" aria-label="Wis opmaak">123 <button type="button" data-cmd="removeFormat" title="<%= t('pedit.tb_clear') %>" aria-label="<%= t('pedit.tb_clear') %>"> 124 124 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M5 5l14 14" stroke-linecap="round"/></svg> 125 125 </button> 126 <button type="button" id="pe-fullscreen-btn" title=" Volledig scherm" aria-label="Volledig scherm" aria-pressed="false">126 <button type="button" id="pe-fullscreen-btn" title="<%= t('pedit.tb_fullscreen') %>" aria-label="<%= t('pedit.tb_fullscreen') %>" aria-pressed="false"> 127 127 <svg class="fs-icon-expand" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg> 128 128 <svg class="fs-icon-compress" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="4 14 10 14 10 20"/><polyline points="20 10 14 10 14 4"/><line x1="14" y1="10" x2="21" y2="3"/><line x1="3" y1="21" x2="10" y2="14"/></svg> … … 135 135 role="textbox" 136 136 aria-multiline="true" 137 aria-label=" Content"138 data-placeholder=" Begin met schrijven…"></div>137 aria-label="<%= t('pedit.editor_aria') %>" 138 data-placeholder="<%= t('pedit.editor_placeholder') %>"></div> 139 139 140 140 <%# Sticky "tik om te bewerken"-hint (alleen zichtbaar op touch + niet-fullscreen 141 141 via CSS). Puur visueel (pointer-events:none); de tik op het veld opent fullscreen. %> 142 <div class="pe-edit-hint" aria-hidden="true">✎ Tik om te bewerken</div>142 <div class="pe-edit-hint" aria-hidden="true">✎ <%= t('pedit.tap_to_edit') %></div> 143 143 144 144 <%# Hidden field is what actually submits to the server. The visible … … 153 153 <div class="pe-editor-status"> 154 154 <small id="content-upload-status" class="pe-status"></small> 155 <span class="pe-char-count"><span id="char-count">0</span> tekens</span>155 <span class="pe-char-count"><span id="char-count">0</span> <%= t('pedit.chars') %></span> 156 156 </div> 157 157 … … 162 162 <%# ── META ─────────────────────────────────────────────────── %> 163 163 <section class="pe-card"> 164 <div class="pe-section-title"> Publicatie</div>164 <div class="pe-section-title"><%= t('pedit.s_publication') %></div> 165 165 <div class="pe-row pe-row-2"> 166 166 <label class="pe-field"> 167 <span> Status</span>167 <span><%= t('pedit.f_status') %></span> 168 168 <% var _st = isNew ? 'published' : (post.status || 'draft'); %> 169 169 <select name="status"> 170 <option value="published" <%= _st === 'published' ? 'selected' : '' %>> Gepubliceerd</option>171 <option value="draft" <%= _st === 'draft' ? 'selected' : '' %>> Concept</option>172 <option value="archived" <%= _st === 'archived' ? 'selected' : '' %>> Gearchiveerd</option>170 <option value="published" <%= _st === 'published' ? 'selected' : '' %>><%= t('pedit.status_published') %></option> 171 <option value="draft" <%= _st === 'draft' ? 'selected' : '' %>><%= t('pedit.status_draft') %></option> 172 <option value="archived" <%= _st === 'archived' ? 'selected' : '' %>><%= t('pedit.status_archived') %></option> 173 173 </select> 174 174 </label> 175 175 <label class="pe-field"> 176 <span> Type</span>176 <span><%= t('pedit.f_type') %></span> 177 177 <select name="type"> 178 178 <% var ptype = post.type || 'post'; %> 179 <option value="post" <%= ptype === 'post' ? 'selected' : '' %>> Post</option>180 <option value="foto" <%= ptype === 'foto' ? 'selected' : '' %>> Foto</option>181 <option value="video" <%= ptype === 'video' ? 'selected' : '' %>> Video</option>182 <option value="audio" <%= ptype === 'audio' ? 'selected' : '' %>> Audio</option>179 <option value="post" <%= ptype === 'post' ? 'selected' : '' %>><%= t('pedit.type_post') %></option> 180 <option value="foto" <%= ptype === 'foto' ? 'selected' : '' %>><%= t('pedit.type_foto') %></option> 181 <option value="video" <%= ptype === 'video' ? 'selected' : '' %>><%= t('pedit.type_video') %></option> 182 <option value="audio" <%= ptype === 'audio' ? 'selected' : '' %>><%= t('pedit.type_audio') %></option> 183 183 </select> 184 184 </label> … … 188 188 <label class="pe-checkbox"> 189 189 <input type="checkbox" id="pin-toggle" <%= (Number(post.pinned) > 0) ? 'checked' : '' %>> 190 <span>📌 Vastpinnen bovenaan</span>190 <span>📌 <%= t('pedit.pin_label') %></span> 191 191 </label> 192 192 <input type="hidden" name="pinned" id="pin-rank" value="<%= post.pinned || 0 %>"> 193 193 <div class="pe-pin-pos" id="pin-pos" <%= (Number(post.pinned) > 0) ? '' : 'hidden' %>> 194 194 <span class="pe-pin-steps"> 195 <button type="button" class="pe-pin-btn" id="pin-up" aria-label=" Hoger zetten">▲</button>196 <button type="button" class="pe-pin-btn" id="pin-down" aria-label=" Lager zetten">▼</button>195 <button type="button" class="pe-pin-btn" id="pin-up" aria-label="<%= t('pedit.pin_up') %>">▲</button> 196 <button type="button" class="pe-pin-btn" id="pin-down" aria-label="<%= t('pedit.pin_down') %>">▼</button> 197 197 </span> 198 198 <span class="pe-pin-label" id="pin-label"></span> … … 201 201 <label class="pe-checkbox"> 202 202 <input type="checkbox" name="noindex" value="1" <%= post.noindex ? 'checked' : '' %>> 203 <span>🚫 noindex (verberg voor zoekmachines)</span>203 <span>🚫 <%= t('pedit.noindex_label') %></span> 204 204 </label> 205 205 <% if (typeof premiumUnlocked === 'undefined' || premiumUnlocked) { %> 206 206 <label class="pe-checkbox"> 207 207 <input type="checkbox" name="fan_only" value="1" <%= post.fan_only ? 'checked' : '' %>> 208 <span>🔒 Alleen voor ingelogde fans</span>208 <span>🔒 <%= t('pedit.fan_only_label') %></span> 209 209 </label> 210 210 <div style="margin-top:8px"> 211 <label style="display:block;font-size:12.5px;opacity:.8;margin-bottom:4px">📅 Publiceren op (laat leeg = direct)</label>211 <label style="display:block;font-size:12.5px;opacity:.8;margin-bottom:4px">📅 <%= t('pedit.publish_at_label') %></label> 212 212 <input type="datetime-local" name="publish_at" value="<%= post.publish_at ? String(post.publish_at).replace(' ','T').slice(0,16) : '' %>" style="padding:8px 10px;border-radius:8px;border:1px solid var(--rule,rgba(128,128,128,.4));background:transparent;color:inherit"> 213 <% if (post.status === 'scheduled' && post.publish_at) { %><div style="font-size:12px;opacity:.7;margin-top:4px">⏳ Ingepland voor <%= post.publish_at%></div><% } %>213 <% if (post.status === 'scheduled' && post.publish_at) { %><div style="font-size:12px;opacity:.7;margin-top:4px">⏳ <%= t('pedit.scheduled_for', { d: post.publish_at }) %></div><% } %> 214 214 </div> 215 215 <% } %> … … 219 219 <%# ── ACTIONS (sticky at bottom) ──────────────────────────── %> 220 220 <div class="pe-actions"> 221 <a href="<%= _base %><%= isNew ? '/' : '/' + post.slug %>" class="pe-btn"> Annuleren</a>221 <a href="<%= _base %><%= isNew ? '/' : '/' + post.slug %>" class="pe-btn"><%= t('pedit.cancel') %></a> 222 222 <div class="pe-actions-spacer"></div> 223 223 <% if (!isNew && post.status !== 'published') { %> 224 <button type="submit" name="action" value="publish" class="pe-btn pe-btn-success">📤 Publish</button>224 <button type="submit" name="action" value="publish" class="pe-btn pe-btn-success">📤 <%= t('pedit.publish') %></button> 225 225 <% } %> 226 <button type="submit" class="pe-btn pe-btn-primary">💾 Opslaan</button>226 <button type="submit" class="pe-btn pe-btn-primary">💾 <%= t('pedit.save') %></button> 227 227 </div> 228 228 </form> … … 983 983 if (!coverField.files[0]) return; 984 984 coverStatus.classList.remove('is-error'); 985 coverStatus.textContent = ' Uploaden…';985 coverStatus.textContent = '<%= t('pedit.js_uploading') %>'; 986 986 try { 987 987 const j = await uploadImage(coverField.files[0]); 988 988 coverUrl.value = j.url; 989 989 showCoverPreview(j.url); 990 coverStatus.textContent = ' Geüpload✓';990 coverStatus.textContent = '<%= t('pedit.js_uploaded') %> ✓'; 991 991 setTimeout(() => { coverStatus.textContent = ''; }, 2000); 992 992 } catch (e) { 993 993 coverStatus.classList.add('is-error'); 994 coverStatus.textContent = ' Mislukt: ' + e.message;994 coverStatus.textContent = '<%= t('pedit.js_failed') %>: ' + e.message; 995 995 } 996 996 }); … … 1053 1053 // UUIDs are noisy — show a 6-char prefix for visual hint 1054 1054 const v = String(value || ''); 1055 return ' Track' + (v.length > 8 ? v.slice(0, 6) + '…' : v);1056 } 1057 if (kind === 'album') return ' Album:' + value;1058 if (kind === 'playlist') return ' Playlist:' + value;1055 return '<%= t('pedit.chip_track') %> ' + (v.length > 8 ? v.slice(0, 6) + '…' : v); 1056 } 1057 if (kind === 'album') return '<%= t('pedit.chip_album') %> ' + value; 1058 if (kind === 'playlist') return '<%= t('pedit.chip_playlist') %> ' + value; 1059 1059 if (kind === 'embed') { 1060 1060 const clean = String(value || '').replace(/^https?:\/\/(www\.)?/, ''); … … 1187 1187 } 1188 1188 function linkPrompt() { 1189 const url = window.prompt(' Link URL (https://… of /pad)');1189 const url = window.prompt('<%= t('pedit.js_link_prompt') %>'); 1190 1190 if (!url) return; 1191 1191 execCmd('createLink', url); … … 1288 1288 if (fsBtn) { 1289 1289 fsBtn.setAttribute('aria-pressed', on ? 'true' : 'false'); 1290 fsBtn.title = on ? ' Klaar' : 'Volledig scherm';1290 fsBtn.title = on ? '<%= t('pedit.tb_done') %>' : '<%= t('pedit.tb_fullscreen') %>'; 1291 1291 } 1292 1292 if (window.visualViewport) { … … 1342 1342 editor.setAttribute('contenteditable', 'false'); 1343 1343 editor.classList.add('pe-tap-to-edit'); 1344 editor.setAttribute('data-placeholder', ' Tik om te schrijven…');1344 editor.setAttribute('data-placeholder', '<%= t('pedit.tap_to_write') %>'); 1345 1345 editor.addEventListener('click', function () { 1346 1346 if (!isFs()) openFs(); … … 1390 1390 async function uploadAndInsertImage(file) { 1391 1391 contentStatus.classList.remove('is-error'); 1392 contentStatus.textContent = ' Uploaden…';1392 contentStatus.textContent = '<%= t('pedit.js_uploading') %>'; 1393 1393 try { 1394 1394 const j = await uploadImage(file); … … 1396 1396 editor.focus({ preventScroll: true }); 1397 1397 document.execCommand('insertHTML', false, img); 1398 contentStatus.textContent = ' Ingevoegd✓';1398 contentStatus.textContent = '<%= t('pedit.js_inserted') %> ✓'; 1399 1399 setTimeout(() => { contentStatus.textContent = ''; }, 2000); 1400 1400 updateCharCount(); 1401 1401 } catch (e) { 1402 1402 contentStatus.classList.add('is-error'); 1403 contentStatus.textContent = ' Mislukt: ' + e.message;1403 contentStatus.textContent = '<%= t('pedit.js_failed') %>: ' + e.message; 1404 1404 } 1405 1405 } … … 1459 1459 if (embedBtn) { 1460 1460 embedBtn.addEventListener('click', () => { 1461 const raw = window.prompt(' Plak een media-URL om in te sluiten\n(YouTube, Spotify, SoundCloud, Vimeo, Apple Music, Bandcamp):');1461 const raw = window.prompt('<%= t('pedit.js_embed_prompt') %>'); 1462 1462 if (!raw) return; 1463 1463 const url = raw.trim(); 1464 if (!/^https?:\/\//i.test(url)) { alert(' Geef een volledige URL (https://…).'); return; }1464 if (!/^https?:\/\//i.test(url)) { alert('<%= t('pedit.js_embed_invalid') %>'); return; } 1465 1465 insertChip('embed', url); 1466 1466 }); … … 1501 1501 if (!filtered.length) { 1502 1502 tpList.innerHTML = ''; 1503 tpEmpty.textContent = q ? ' Geen tracks gevonden voor "' + q + '"' : 'Nog geen tracks. Upload via Beheer → Audio.';1503 tpEmpty.textContent = q ? '<%= t('pedit.js_no_tracks_found') %> ' + q : '<%= t('pedit.js_no_tracks_yet') %>'; 1504 1504 tpList.appendChild(tpEmpty); 1505 1505 return; … … 1527 1527 async function loadTracks() { 1528 1528 if (Array.isArray(tpCache)) return tpCache; 1529 tpEmpty.textContent = ' Tracks laden…';1529 tpEmpty.textContent = '<%= t('pedit.js_tracks_loading') %>'; 1530 1530 try { 1531 1531 const r = await fetch('/admin/playlists/api/tracks', { credentials: 'same-origin' }); … … 1534 1534 } catch (e) { 1535 1535 tpCache = []; 1536 tpEmpty.textContent = ' Kon tracks niet laden: ' + e.message;1536 tpEmpty.textContent = '<%= t('pedit.js_tracks_load_fail') %>: ' + e.message; 1537 1537 } 1538 1538 return tpCache; … … 1596 1596 playlistBtn.addEventListener('click', async () => { 1597 1597 if (typeof window.openPlaylistEditor !== 'function') { 1598 alert(' Playlist editor niet geladen');1598 alert('<%= t('pedit.js_playlist_editor_missing') %>'); 1599 1599 return; 1600 1600 } … … 1604 1604 if (j.ok && Array.isArray(j.playlists) && j.playlists.length > 0) { 1605 1605 const choice = prompt( 1606 ' Bestaande playlists:\n\n' +1606 '<%= t('pedit.js_playlist_existing') %>\n\n' + 1607 1607 j.playlists.map((p, i) => `${i + 1}. ${p.title} (${p.track_count} tracks)`).join('\n') + 1608 '\n\n Kies een nummer om in te voegen, leeg = nieuwe maken:'1608 '\n\n<%= t('pedit.js_playlist_choose') %>' 1609 1609 ); 1610 1610 if (choice && /^\d+$/.test(choice.trim())) { … … 1644 1644 <div class="tp-sheet" role="dialog" aria-modal="true" aria-labelledby="tp-title"> 1645 1645 <header class="tp-header"> 1646 <h2 id="tp-title" class="tp-h2"> Track invoegen</h2>1647 <button type="button" class="tp-close" data-tp-close aria-label=" Sluiten">1646 <h2 id="tp-title" class="tp-h2"><%= t('pedit.tp_title') %></h2> 1647 <button type="button" class="tp-close" data-tp-close aria-label="<%= t('pedit.tp_close') %>"> 1648 1648 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg> 1649 1649 </button> … … 1653 1653 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg> 1654 1654 </span> 1655 <input type="search" class="tp-search" id="tp-search" placeholder=" Zoek op titel of artiest…" autocomplete="off" inputmode="search">1655 <input type="search" class="tp-search" id="tp-search" placeholder="<%= t('pedit.tp_search_placeholder') %>" autocomplete="off" inputmode="search"> 1656 1656 </div> 1657 <div class="tp-list" id="tp-list" role="listbox" aria-label=" Tracks">1658 <div class="tp-empty" id="tp-empty"> Tracks laden…</div>1657 <div class="tp-list" id="tp-list" role="listbox" aria-label="<%= t('pedit.tp_list_aria') %>"> 1658 <div class="tp-empty" id="tp-empty"><%= t('pedit.js_tracks_loading') %></div> 1659 1659 </div> 1660 1660 </div> … … 1682 1682 function descr(n) { 1683 1683 n = Number(n) || 0; 1684 if (n <= 1) return ' bovenaan';1685 return n + ' e van boven';1684 if (n <= 1) return '<%= t('pedit.pin_top') %>'; 1685 return n + '<%= t('pedit.pin_nth_suffix') %>'; 1686 1686 } 1687 1687 function render() { -
src/views/pages/prutter-conversation.ejs
rbbf9d1a r4885eab 1 1 <div class="prutter-conv-page" data-conversation-id="<%= conversation.id %>" data-me="<%= user.id %>"> 2 2 <header class="prutter-conv-header"> 3 <a class="prutter-back" href="<%= siteUrlBase %>/prutter" aria-label=" Terug naar inbox" title="Inbox">3 <a class="prutter-back" href="<%= siteUrlBase %>/prutter" aria-label="<%= t('prcv.back_aria') %>" title="<%= t('prcv.inbox') %>"> 4 4 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg> 5 5 </a> … … 12 12 </div> 13 13 <div class="prutter-conv-id"> 14 <span class="prutter-conv-name"><%= other ? other.username : 'Onbekend'%></span>14 <span class="prutter-conv-name"><%= other ? other.username : t('prcv.unknown') %></span> 15 15 <% if (other) { %> 16 <a class="prutter-conv-sub" href="/users/<%= encodeURIComponent(other.username) %>"> Bekijk profiel</a>16 <a class="prutter-conv-sub" href="/users/<%= encodeURIComponent(other.username) %>"><%= t('prcv.view_profile') %></a> 17 17 <% } %> 18 18 </div> … … 34 34 hx-target="#prutter-thread" 35 35 hx-swap="beforeend"> 36 <textarea name="content" rows="1" maxlength="2000" placeholder=" Bericht…" required></textarea>37 <button type="submit" class="btn btn-primary"> Stuur</button>36 <textarea name="content" rows="1" maxlength="2000" placeholder="<%= t('prcv.placeholder') %>" required></textarea> 37 <button type="submit" class="btn btn-primary"><%= t('prcv.send') %></button> 38 38 </form> 39 39 </div> -
src/views/pages/prutter-inbox.ejs
rbbf9d1a r4885eab 2 2 <header class="prutter-header"> 3 3 <h1>Prutter</h1> 4 <p class="prutter-tagline"> Your direct messages on this site.</p>4 <p class="prutter-tagline"><%= t('prin.tagline') %></p> 5 5 </header> 6 6 7 7 <% if (!conversations.length) { %> 8 8 <p class="prutter-empty"> 9 No conversations yet. Open someone's profile and click <em>Send DM</em> to start one.9 <%= t('prin.empty') %> 10 10 </p> 11 11 <% } else { %> … … 31 31 <div class="prutter-conv-preview"><%= c.last_message_preview %></div> 32 32 <% } else { %> 33 <div class="prutter-conv-preview muted"> Empty conversation</div>33 <div class="prutter-conv-preview muted"><%= t('prin.empty_conv') %></div> 34 34 <% } %> 35 35 </div> -
src/views/pages/tag.ejs
rbbf9d1a r4885eab 1 1 <div class="container tag-page"> 2 2 <header class="tag-page-header"> 3 <p class="tag-eyebrow"> Tag</p>3 <p class="tag-eyebrow"><%= t('ptag.eyebrow') %></p> 4 4 <h1>#<%= tag %></h1> 5 5 <p class="tag-meta"> 6 <%= posts.length %> post<%= posts.length === 1 ? '' : 's'%>6 <%= posts.length === 1 ? t('ptag.count_one', { n: posts.length }) : t('ptag.count_many', { n: posts.length }) %> 7 7 </p> 8 8 </header> 9 9 10 10 <% if (!posts.length) { %> 11 <p class="tag-empty"> No posts with this tag yet.</p>11 <p class="tag-empty"><%= t('ptag.empty') %></p> 12 12 <% } else { %> 13 13 <ol class="tag-results"> … … 26 26 hx-get="/<%= p.slug %>?partial=1" hx-target="#pcms-main" hx-swap="innerHTML" 27 27 hx-push-url="/<%= p.slug %>" hx-indicator="#pcms-loading"> 28 <%= p.title || '(untitled)'%>28 <%= p.title || t('ptag.untitled') %> 29 29 </a> 30 30 </h2> -
src/views/pages/type.ejs
rbbf9d1a r4885eab 1 1 <div class="container type-page"> 2 2 <header class="type-page-header"> 3 <p class="type-eyebrow"> Type</p>3 <p class="type-eyebrow"><%= t('ptype.eyebrow') %></p> 4 4 <h1><%= type.charAt(0).toUpperCase() + type.slice(1) %></h1> 5 <p class="type-meta"><%= posts.length %> post<%= posts.length === 1 ? '' : 's'%></p>5 <p class="type-meta"><%= posts.length === 1 ? t('ptype.count_one', { n: posts.length }) : t('ptype.count_many', { n: posts.length }) %></p> 6 6 </header> 7 7 8 8 <% if (!posts.length) { %> 9 <p class="type-empty"> No posts of this type yet.</p>9 <p class="type-empty"><%= t('ptype.empty') %></p> 10 10 <% } else { %> 11 11 <ol class="type-results"> … … 24 24 hx-get="/<%= p.slug %>?partial=1" hx-target="#pcms-main" hx-swap="innerHTML" 25 25 hx-push-url="/<%= p.slug %>" hx-indicator="#pcms-loading"> 26 <%= p.title || '(untitled)'%>26 <%= p.title || t('ptype.untitled') %> 27 27 </a> 28 28 </h2> -
src/views/pages/user.ejs
rbbf9d1a r4885eab 19 19 <% } %> 20 20 <p class="user-stats"> 21 <%= posts.length %> post<%= posts.length === 1 ? '' : 's' %> on this site21 <%= posts.length %> <%= posts.length === 1 ? t('pusr.post_one') : t('pusr.post_many') %> <%= t('pusr.on_this_site') %> 22 22 <% if (totalPosts > posts.length) { %> 23 · <%= t otalPosts %> total23 · <%= t('pusr.total', { n: totalPosts }) %> 24 24 <% } %> 25 25 <% if (author.created_at) { %> 26 · joined <%= formatDate(author.created_at) %>26 · <%= t('pusr.joined', { date: formatDate(author.created_at) }) %> 27 27 <% } %> 28 28 </p> 29 29 <% if (user && user.id !== author.id && site && site.enable_prutter && (typeof tenancy !== 'undefined' && tenancy === 'hub') && (typeof premiumUnlocked === 'undefined' || premiumUnlocked)) { %> 30 30 <p class="user-actions"> 31 <a href="<%= siteUrlBase %>/prutter/new?to=<%= encodeURIComponent(author.username) %>" class="btn btn-primary">💬 Send DM</a>31 <a href="<%= siteUrlBase %>/prutter/new?to=<%= encodeURIComponent(author.username) %>" class="btn btn-primary">💬 <%= t('pusr.send_dm') %></a> 32 32 </p> 33 33 <% } %> … … 36 36 37 37 <% if (!posts.length) { %> 38 <p class="user-empty"> No posts on this site yet.</p>38 <p class="user-empty"><%= t('pusr.empty') %></p> 39 39 <% } else { %> 40 <h2 class="user-posts-heading"> Posts</h2>40 <h2 class="user-posts-heading"><%= t('pusr.posts_heading') %></h2> 41 41 <ol class="user-posts"> 42 42 <% posts.forEach(function(p) { %> … … 54 54 hx-get="/<%= p.slug %>?partial=1" hx-target="#pcms-main" hx-swap="innerHTML" 55 55 hx-push-url="/<%= p.slug %>" hx-indicator="#pcms-loading"> 56 <%= p.title || '(untitled)'%>56 <%= p.title || t('pusr.untitled') %> 57 57 </a> 58 58 </h3> -
src/views/pages/viewer-blocked.ejs
rbbf9d1a r4885eab 2 2 <div class="vb-card"> 3 3 <div class="vb-ico" aria-hidden="true">👁️</div> 4 <h1 class="vb-title"> Kijker-modus</h1>4 <h1 class="vb-title"><%= t('vblk.title') %></h1> 5 5 <p class="vb-text"> 6 Dit is een alleen-lezen account. Je kunt alles bekijken, maar 7 <strong>niets wijzigen</strong> — opslaan, uploaden, verwijderen en 8 reageren zijn uitgeschakeld. 6 <%= t('vblk.text_before') %> 7 <strong><%= t('vblk.text_strong') %></strong> <%= t('vblk.text_after') %> 9 8 </p> 10 9 <div class="vb-actions"> 11 <button type="button" class="vb-btn vb-btn-primary" onclick="history.back()">← Terug</button>12 <a class="vb-btn" href="/"> Naar de hoofdpagina</a>10 <button type="button" class="vb-btn vb-btn-primary" onclick="history.back()">← <%= t('vblk.back') %></button> 11 <a class="vb-btn" href="/"><%= t('vblk.to_home') %></a> 13 12 </div> 14 13 </div>
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)