pgkiln 0.83.0 → 0.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/db/migrations/154_notifications.sql +186 -0
- package/docs/apex-feature-parity.md +3 -2
- package/docs/guide/03-builder.md +16 -5
- package/docs/guide/04-pages-and-regions.md +42 -0
- package/docs/guide/06-processing.md +22 -1
- package/docs/guide/07-dynamic-actions.md +1 -0
- package/docs/guide/09-reference.md +15 -0
- package/docs/guide/11-from-apex.md +1 -0
- package/docs/guide/12-development.md +13 -1
- package/package.json +1 -1
- package/public/app.css +29 -0
- package/public/app.js +78 -0
- package/public/sw.js +5 -1
- package/src/app.ts +11 -0
- package/src/builder/ai-agents.ts +86 -85
- package/src/builder/ai-builder.ts +30 -30
- package/src/builder/ai.ts +93 -92
- package/src/builder/api.ts +47 -47
- package/src/builder/app-groups.ts +33 -32
- package/src/builder/appfromfile.ts +57 -58
- package/src/builder/appsheets.ts +33 -32
- package/src/builder/appwizard.ts +65 -65
- package/src/builder/arrange.ts +23 -22
- package/src/builder/assistant.ts +39 -38
- package/src/builder/automations.ts +32 -31
- package/src/builder/availability.ts +24 -24
- package/src/builder/backups.ts +30 -31
- package/src/builder/blueprints.ts +62 -61
- package/src/builder/code-editor.ts +1 -1
- package/src/builder/components.ts +10 -6
- package/src/builder/create-page.ts +52 -50
- package/src/builder/cross-app.ts +37 -35
- package/src/builder/dataload.ts +2 -2
- package/src/builder/datasets.ts +107 -106
- package/src/builder/dependencies.ts +20 -19
- package/src/builder/designer.ts +36 -36
- package/src/builder/diagnostics.ts +58 -57
- package/src/builder/features.ts +39 -35
- package/src/builder/feedback.ts +23 -20
- package/src/builder/find-workspace.ts +28 -24
- package/src/builder/form-designer.ts +36 -35
- package/src/builder/gallery.ts +25 -29
- package/src/builder/globalization.ts +50 -49
- package/src/builder/i18n/de.ts +3461 -0
- package/src/builder/i18n/en.ts +3461 -0
- package/src/builder/i18n/es.ts +3461 -0
- package/src/builder/i18n/fr.ts +3461 -0
- package/src/builder/i18n/it.ts +3461 -0
- package/src/builder/i18n/ja.ts +3461 -0
- package/src/builder/i18n/ko.ts +3461 -0
- package/src/builder/i18n/nl.ts +3461 -0
- package/src/builder/i18n/pt.ts +3461 -0
- package/src/builder/i18n/zh-cn.ts +3461 -0
- package/src/builder/i18n/zh-tw.ts +3461 -0
- package/src/builder/i18n.ts +17 -3
- package/src/builder/json-sources.ts +63 -62
- package/src/builder/layouts.ts +12 -11
- package/src/builder/locks.ts +29 -28
- package/src/builder/login-page.ts +26 -25
- package/src/builder/mail.ts +148 -147
- package/src/builder/newapp.ts +15 -12
- package/src/builder/notification-settings.ts +27 -0
- package/src/builder/plugins.ts +47 -44
- package/src/builder/process-jobs.ts +11 -10
- package/src/builder/pwa.ts +22 -21
- package/src/builder/remote-databases.ts +55 -56
- package/src/builder/report-settings.ts +1 -1
- package/src/builder/reporter.ts +43 -42
- package/src/builder/rest-catalogs.ts +87 -86
- package/src/builder/rest.ts +9 -8
- package/src/builder/routes.ts +30 -27
- package/src/builder/search.ts +14 -13
- package/src/builder/static-files.ts +31 -32
- package/src/builder/subscriptions.ts +47 -45
- package/src/builder/supporting.ts +23 -18
- package/src/builder/team.ts +179 -179
- package/src/builder/telemetry.ts +59 -62
- package/src/builder/templates.ts +66 -65
- package/src/builder/themeroller.ts +60 -63
- package/src/builder/ui-defaults.ts +21 -20
- package/src/builder/ui.ts +2 -1
- package/src/builder/upgrade.ts +25 -24
- package/src/builder/utilities.ts +48 -47
- package/src/builder/vector-providers.ts +67 -66
- package/src/builder/websources.ts +43 -42
- package/src/builder/wizards.ts +95 -89
- package/src/builder/workflow-migrate.ts +30 -31
- package/src/builder/workflows.ts +40 -39
- package/src/builder/workingcopies.ts +41 -49
- package/src/builder/workspace-backups.ts +21 -20
- package/src/builder/workspace-mail.ts +17 -16
- package/src/builder/workspace-resources.ts +104 -112
- package/src/builder/workspace-transfer.ts +34 -26
- package/src/cli/replace.ts +2 -0
- package/src/i18n/ar.ts +9 -0
- package/src/i18n/cs.ts +9 -0
- package/src/i18n/da.ts +9 -0
- package/src/i18n/de.ts +9 -0
- package/src/i18n/el.ts +9 -0
- package/src/i18n/es.ts +9 -0
- package/src/i18n/fi.ts +9 -0
- package/src/i18n/fr.ts +9 -0
- package/src/i18n/he.ts +9 -0
- package/src/i18n/it.ts +9 -0
- package/src/i18n/ja.ts +9 -0
- package/src/i18n/ko.ts +9 -0
- package/src/i18n/nb.ts +9 -0
- package/src/i18n/pl.ts +9 -0
- package/src/i18n/pt.ts +9 -0
- package/src/i18n/ru.ts +9 -0
- package/src/i18n/sv.ts +9 -0
- package/src/i18n/tr.ts +9 -0
- package/src/i18n/uk.ts +9 -0
- package/src/i18n/zh.ts +9 -0
- package/src/i18n.ts +18 -0
- package/src/metadata.ts +9 -3
- package/src/runtime/engine.ts +2 -1
- package/src/runtime/feedback.ts +1 -1
- package/src/runtime/lists.ts +43 -11
- package/src/runtime/notifications.ts +237 -0
- package/src/runtime/processes.ts +45 -1
- package/src/runtime/regions.ts +11 -15
- package/src/runtime/render.ts +12 -3
- package/src/runtime/routes.ts +12 -4
- package/src/runtime/signin.ts +2 -1
- package/src/session.ts +13 -4
|
@@ -4,6 +4,7 @@ import { html, raw } from '../html.ts';
|
|
|
4
4
|
import { baseLanguage, BUILTIN_LANGUAGES, LANGUAGE_NAMES } from '../i18n.ts';
|
|
5
5
|
import { clearLocaleCache, translatableTexts } from '../runtime/locale.ts';
|
|
6
6
|
import { appHeader, back, BASE, csrf, developer, flash, input, region, select, send, shell, type Req } from './ui.ts';
|
|
7
|
+
import { scopedTx as tx } from './i18n.ts';
|
|
7
8
|
|
|
8
9
|
// Shared Components → Globalization (APEX: Translate Application, Text
|
|
9
10
|
// Messages). Translations live in the application itself: one text in the
|
|
@@ -84,19 +85,19 @@ export async function timeZoneSettings(a: { time_zone: string | null; time_zone_
|
|
|
84
85
|
const zones = (await owner.query<{ name: string }>(`select name from pg_timezone_names where name !~ '^(posix|right)/' order by name`)).rows.map((r) => r.name);
|
|
85
86
|
const dbZone = (await owner.one<{ tz: string }>(`select current_setting('TimeZone') as tz`))?.tz ?? '';
|
|
86
87
|
return html`<div class="form-grid">
|
|
87
|
-
${input('currency', '
|
|
88
|
-
${select('time_zone', '
|
|
88
|
+
${input('currency', tx('glob.currency'), a.currency ?? '', { placeholder: tx('glob.e_g_eur_empty_per'), help: tx('glob.iso_4217_code_for_l') })}
|
|
89
|
+
${select('time_zone', tx('glob.time_zone'), a.time_zone ?? '', [['', `- the database's (${dbZone}) -`], ...zones], tx('glob.queries_run_in_this_time'))}
|
|
89
90
|
</div>
|
|
90
|
-
<div class="field"><label class="check"><input type="checkbox" name="time_zone_auto" value="true"${a.time_zone_auto ? raw(' checked') : ''}>
|
|
91
|
-
<small class="help"
|
|
91
|
+
<div class="field"><label class="check"><input type="checkbox" name="time_zone_auto" value="true"${a.time_zone_auto ? raw(' checked') : ''}> ${tx('glob.automatic_time_zone')}</label>
|
|
92
|
+
<small class="help">${tx('glob.each_user_sees_times_in')}</small></div>`;
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
/** Save the currency and time zone settings; an unknown time zone or currency is an error. */
|
|
95
96
|
export async function saveTimeZoneSettings(appId: string, b: Record<string, string | undefined>) {
|
|
96
97
|
const zone = b.time_zone?.trim() || null;
|
|
97
|
-
if (zone && !(await owner.one('select 1 from pg_timezone_names where name = $1', [zone]))) throw new Error(
|
|
98
|
+
if (zone && !(await owner.one('select 1 from pg_timezone_names where name = $1', [zone]))) throw new Error(tx('glob.unknown_zone', { zone }));
|
|
98
99
|
const currency = b.currency?.trim().toUpperCase() || null;
|
|
99
|
-
if (currency && !/^[A-Z]{3}$/.test(currency)) throw new Error('
|
|
100
|
+
if (currency && !/^[A-Z]{3}$/.test(currency)) throw new Error(tx('glob.currency_a_three_letter_iso'));
|
|
100
101
|
await owner.query('update meta.app set time_zone = $2, time_zone_auto = $3, currency = $4 where id = $1', [appId, zone, b.time_zone_auto === 'true', currency]);
|
|
101
102
|
}
|
|
102
103
|
|
|
@@ -143,75 +144,75 @@ export async function globalizationRoutes(app: FastifyInstance) {
|
|
|
143
144
|
const all = [...new Set([a.language, ...langs])];
|
|
144
145
|
|
|
145
146
|
const main = html`${appHeader(a, 'shared', s)}
|
|
146
|
-
<p class="muted u-mt0"><a href="${BASE}/apps/${a.id}/shared"
|
|
147
|
+
<p class="muted u-mt0"><a href="${BASE}/apps/${a.id}/shared">${tx('glob.shared_components')}</a> / Globalization</p>
|
|
147
148
|
<div class="columns">
|
|
148
|
-
${region('
|
|
149
|
-
<li
|
|
150
|
-
<li
|
|
151
|
-
<li
|
|
152
|
-
<li
|
|
149
|
+
${region(tx('glob.languages'), html`<ul class="checklist">
|
|
150
|
+
<li>${tx.rich('glob.primary', { lang: html`<b>${langName(a.language)}</b>` })}</li>
|
|
151
|
+
<li>${tx.rich('glob.translated_into', { langs: langs.length ? html`${langs.map((l, i) => html`${i ? ', ' : ''}<b>${langName(l)}</b>`)}` : html`<span class="muted">${tx('glob.none_yet')}</span>` })}</li>
|
|
152
|
+
<li>${tx.rich('glob.derived', { from: html`<b>${tx.dyn(`glob.from.${a.language_from}`, String(a.language_from))}</b>` })}</li>
|
|
153
|
+
<li>${tx('glob.builtin', { list: BUILTIN_LANGUAGES.slice(0, -1).map(([, n]) => n).join(', '), last: BUILTIN_LANGUAGES.at(-1)?.[1] ?? '' })}</li>
|
|
153
154
|
</ul>
|
|
154
|
-
<p><a class="btn" href="${BASE}/apps/${a.id}/settings"
|
|
155
|
-
${lang ? region('
|
|
156
|
-
<p class="muted u-mt0"
|
|
155
|
+
<p><a class="btn" href="${BASE}/apps/${a.id}/settings">${tx('glob.change_languages_in_settings')}</a></p>`)}
|
|
156
|
+
${lang ? region(tx('glob.export_and_import'), html`
|
|
157
|
+
<p class="muted u-mt0">${tx('glob.give_translators_an_xliff_file')}</p>
|
|
157
158
|
<div class="buttons">
|
|
158
159
|
<a class="btn" href="${BASE}/apps/${a.id}/globalization/export?lang=${lang}&format=xliff">${raw('↓')} XLIFF (${lang})</a>
|
|
159
160
|
<a class="btn" href="${BASE}/apps/${a.id}/globalization/export?lang=${lang}&format=csv">${raw('↓')} CSV (${lang})</a>
|
|
160
161
|
</div>
|
|
161
162
|
<form method="post" action="${BASE}/apps/${a.id}/globalization/import" class="u-mt1">${csrf(s)}
|
|
162
163
|
<input type="hidden" name="lang" value="${lang}">
|
|
163
|
-
<div class="field" data-wide><label class="label" for="f_file"
|
|
164
|
-
<div class="field" data-wide><label class="label" for="f_doc"
|
|
164
|
+
<div class="field" data-wide><label class="label" for="f_file">${tx('glob.file')}</label><input id="f_file" type="file" accept=".xlf,.xliff,.csv,.xml,text/csv,application/xml" data-fill="f_doc"></div>
|
|
165
|
+
<div class="field" data-wide><label class="label" for="f_doc">${tx('glob.or_paste_xliff_csv')}</label><textarea id="f_doc" name="doc" class="code" rows="4" required></textarea></div>
|
|
165
166
|
<div class="buttons"><button class="btn btn-hot">Import into ${lang}</button></div>
|
|
166
167
|
</form>`) : ''}
|
|
167
168
|
</div>
|
|
168
169
|
${lang
|
|
169
|
-
? region(
|
|
170
|
-
${langs.length > 1 ? html`<nav class="chips" aria-label="
|
|
171
|
-
<p class="muted u-mt0">${done
|
|
170
|
+
? region(tx('glob.translate_into', { lang: langName(lang) }), html`
|
|
171
|
+
${langs.length > 1 ? html`<nav class="chips" aria-label="${tx('glob.language')}">${langs.map((l) => html`<a class="chip" href="?lang=${l}"${l === lang ? raw(' aria-current="page"') : ''}>${langName(l)}</a> `)}</nav>` : ''}
|
|
172
|
+
<p class="muted u-mt0">${tx('glob.progress', { done, total: texts.length, language: a.language })}</p>
|
|
172
173
|
<form method="post" action="${BASE}/apps/${a.id}/globalization/save">${csrf(s)}
|
|
173
174
|
<input type="hidden" name="lang" value="${lang}">
|
|
174
175
|
<div class="table-wrap"><table class="report report-reflow translate-table">
|
|
175
|
-
<thead><tr><th>${langName(a.language)}</th><th>${langName(lang)}</th><th
|
|
176
|
+
<thead><tr><th>${langName(a.language)}</th><th>${langName(lang)}</th><th>${tx('glob.used_in')}</th></tr></thead>
|
|
176
177
|
<tbody>${[...texts, ...extra.map((r: { source: string }) => ({ text: r.source, places: 'added by hand' }))].map((t, i) => html`<tr>
|
|
177
178
|
<td data-label="${a.language}"><input type="hidden" name="s${i}" value="${t.text}"><span class="source-text">${t.text}</span></td>
|
|
178
179
|
<td data-label="${lang}">${t.text.length > 80 || t.text.includes('\n')
|
|
179
|
-
? html`<textarea name="t${i}" rows="3" aria-label="
|
|
180
|
-
: html`<input name="t${i}" value="${map.get(t.text) ?? ''}" aria-label="
|
|
181
|
-
<td data-label="
|
|
180
|
+
? html`<textarea name="t${i}" rows="3" aria-label="${tx('glob.aria_translation', { text: t.text.slice(0, 60) })}" lang="${lang}">${map.get(t.text) ?? ''}</textarea>`
|
|
181
|
+
: html`<input name="t${i}" value="${map.get(t.text) ?? ''}" aria-label="${tx('glob.aria_translation', { text: t.text })}" lang="${lang}">`}</td>
|
|
182
|
+
<td data-label="${tx('glob.used_in')}" class="muted">${t.places}</td></tr>`)}</tbody>
|
|
182
183
|
</table></div>
|
|
183
|
-
<div class="buttons"><button class="btn btn-hot"
|
|
184
|
+
<div class="buttons"><button class="btn btn-hot">${tx('glob.save_translations')}</button></div>
|
|
184
185
|
</form>
|
|
185
|
-
<h3
|
|
186
|
-
<p class="muted"
|
|
186
|
+
<h3>${tx('glob.add_a_text')}</h3>
|
|
187
|
+
<p class="muted">${tx.code('glob.for_texts_pgkiln_derives_itself')}</p>
|
|
187
188
|
<form method="post" action="${BASE}/apps/${a.id}/globalization/save" class="form-grid">${csrf(s)}
|
|
188
189
|
<input type="hidden" name="lang" value="${lang}">
|
|
189
|
-
${input('s0',
|
|
190
|
-
${input('t0',
|
|
191
|
-
<div class="buttons"><button class="btn"
|
|
190
|
+
${input('s0', tx('glob.text_lang', { lang: a.language }), '', { required: true })}
|
|
191
|
+
${input('t0', tx('glob.translation_lang', { lang }), '', { required: true })}
|
|
192
|
+
<div class="buttons"><button class="btn">${tx('glob.add')}</button></div>
|
|
192
193
|
</form>`)
|
|
193
|
-
: region('
|
|
194
|
-
${region('
|
|
195
|
-
<p class="muted u-mt0"
|
|
196
|
-
<div class="table-wrap"><table class="report report-reflow"><thead><tr><th
|
|
194
|
+
: region(tx('glob.translate'), html`<p>${tx.rich('glob.add_lang_first', { link: html`<a href="${BASE}/apps/${a.id}/settings">${tx('glob.settings_globalization')}</a>`, example: html`<code>nl</code>` })}</p>`)}
|
|
195
|
+
${region(tx('glob.text_messages'), html`
|
|
196
|
+
<p class="muted u-mt0">${tx.code('glob.use_them_in_sql_with')}</p>
|
|
197
|
+
<div class="table-wrap"><table class="report report-reflow"><thead><tr><th>${tx('glob.name')}</th><th>${tx('glob.language')}</th><th>${tx('glob.text')}</th><th></th></tr></thead><tbody>
|
|
197
198
|
${messages.rows.length
|
|
198
199
|
? messages.rows.map((m) => html`<tr>
|
|
199
|
-
<td data-label="
|
|
200
|
-
<td data-label="
|
|
200
|
+
<td data-label="${tx('glob.name')}"><code>${m.name}</code></td><td data-label="${tx('glob.language')}">${m.language}</td>
|
|
201
|
+
<td data-label="${tx('glob.text')}"><form method="post" action="${BASE}/apps/${a.id}/globalization/message" class="search u-m0 u-mwnone">${csrf(s)}
|
|
201
202
|
<input type="hidden" name="name" value="${m.name}"><input type="hidden" name="language" value="${m.language}">
|
|
202
|
-
<input name="text" value="${m.text}" aria-label="
|
|
203
|
+
<input name="text" value="${m.text}" aria-label="${tx('glob.aria_text_of', { name: m.name, lang: m.language })}"><button class="btn">${tx('glob.save')}</button></form></td>
|
|
203
204
|
<td data-label=""><form method="post" action="${BASE}/apps/${a.id}/globalization/message/delete">${csrf(s)}
|
|
204
|
-
<input type="hidden" name="name" value="${m.name}"><input type="hidden" name="language" value="${m.language}"><button class="link-button" data-confirm="
|
|
205
|
-
: html`<tr><td colspan="4" class="empty"
|
|
205
|
+
<input type="hidden" name="name" value="${m.name}"><input type="hidden" name="language" value="${m.language}"><button class="link-button" data-confirm="${tx('glob.delete_confirm', { name: m.name, lang: m.language })}">${tx('glob.delete')}</button></form></td></tr>`)
|
|
206
|
+
: html`<tr><td colspan="4" class="empty">${tx('glob.no_text_messages')}</td></tr>`}
|
|
206
207
|
</tbody></table></div>
|
|
207
|
-
<h3
|
|
208
|
+
<h3>${tx('glob.add_a_text_message')}</h3>
|
|
208
209
|
<form method="post" action="${BASE}/apps/${a.id}/globalization/message" class="form-grid">${csrf(s)}
|
|
209
|
-
${input('name', '
|
|
210
|
-
${select('language', '
|
|
211
|
-
${input('text', '
|
|
212
|
-
<div class="buttons"><button class="btn btn-hot"
|
|
210
|
+
${input('name', tx('glob.name'), '', { required: true, placeholder: tx('glob.e_g_greeting_or_login') })}
|
|
211
|
+
${select('language', tx('glob.language'), a.language, all.map((l): [string, string] => [l, langName(l)]))}
|
|
212
|
+
${input('text', tx('glob.text'), '', { required: true, placeholder: tx('glob.hello_0') })}
|
|
213
|
+
<div class="buttons"><button class="btn btn-hot">${tx('glob.add_message')}</button></div>
|
|
213
214
|
</form>`)}`;
|
|
214
|
-
return send(reply, s, shell(s, `${a.name} globalization`, [['
|
|
215
|
+
return send(reply, s, shell(s, `${a.name} globalization`, [[tx('glob.app_builder'), BASE], [a.name, `${BASE}/apps/${a.id}`], [tx('glob.shared_components'), `${BASE}/apps/${a.id}/shared`], [tx('glob.globalization')]], main));
|
|
215
216
|
});
|
|
216
217
|
|
|
217
218
|
app.post(`${BASE}/apps/:id/globalization/save`, async (req: Req, reply) => {
|
|
@@ -267,7 +268,7 @@ export async function globalizationRoutes(app: FastifyInstance) {
|
|
|
267
268
|
const n = await saveTranslations(a.id, lang, pairs.filter(([, t]) => t.trim() !== ''));
|
|
268
269
|
flash(s, `${n} translation(s) imported into ${lang}.`);
|
|
269
270
|
} catch (e) {
|
|
270
|
-
flash(s,
|
|
271
|
+
flash(s, tx('glob.import_failed', { error: (e as Error).message }), 'error');
|
|
271
272
|
}
|
|
272
273
|
return back(reply, s, `${BASE}/apps/${req.params.id}/globalization?lang=${encodeURIComponent(lang)}`);
|
|
273
274
|
});
|
|
@@ -283,9 +284,9 @@ export async function globalizationRoutes(app: FastifyInstance) {
|
|
|
283
284
|
[req.params.id, b.name?.trim(), b.language?.trim().toLowerCase(), b.text ?? ''],
|
|
284
285
|
);
|
|
285
286
|
clearLocaleCache(Number(req.params.id));
|
|
286
|
-
flash(s, '
|
|
287
|
+
flash(s, tx('glob.text_message_saved'));
|
|
287
288
|
} catch (e) {
|
|
288
|
-
flash(s, /text_message_name_check/.test((e as Error).message) ? '
|
|
289
|
+
flash(s, /text_message_name_check/.test((e as Error).message) ? tx('glob.names_start_with_a_letter') : (e as Error).message, 'error');
|
|
289
290
|
}
|
|
290
291
|
return back(reply, s, `${BASE}/apps/${req.params.id}/globalization`);
|
|
291
292
|
});
|
|
@@ -295,7 +296,7 @@ export async function globalizationRoutes(app: FastifyInstance) {
|
|
|
295
296
|
if (!s) return;
|
|
296
297
|
await owner.query('delete from meta.text_message where app_id = $1 and name = $2 and language = $3', [req.params.id, req.body?.name, req.body?.language]);
|
|
297
298
|
clearLocaleCache(Number(req.params.id));
|
|
298
|
-
flash(s, '
|
|
299
|
+
flash(s, tx('glob.text_message_deleted'));
|
|
299
300
|
return back(reply, s, `${BASE}/apps/${req.params.id}/globalization`);
|
|
300
301
|
});
|
|
301
302
|
}
|