pgkiln 0.44.0 → 0.46.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/102_app_computations.sql +345 -0
- package/db/migrations/103_shortcuts.sql +341 -0
- package/db/migrations/104_report_queries.sql +413 -0
- package/db/migrations/105_component_settings_groups.sql +367 -0
- package/db/migrations/106_form_designer.sql +102 -0
- package/db/migrations/107_app_version.sql +9 -0
- package/docs/apex-feature-parity.md +15 -12
- package/docs/guide/02-concepts.md +24 -0
- package/docs/guide/03-builder.md +69 -13
- package/docs/guide/04-pages-and-regions.md +84 -3
- package/docs/guide/05-items.md +14 -2
- package/docs/guide/06-processing.md +22 -0
- package/docs/guide/09-reference.md +14 -1
- package/docs/guide/11-from-apex.md +2 -1
- package/docs/guide/12-development.md +8 -2
- package/docs/guide/16-files.md +23 -0
- package/docs/guide/18-cli.md +4 -0
- package/examples/hr/hr_61_form_designer.sql +80 -0
- package/package.json +1 -1
- package/public/app.css +79 -2
- package/public/app.js +307 -35
- package/public/builder.css +21 -1
- package/public/builder.js +67 -0
- package/public/sw.js +2 -1
- package/src/app.ts +9 -1
- package/src/appfiles.ts +18 -2
- package/src/builder/advisor.ts +7 -2
- package/src/builder/appwizard.ts +22 -6
- package/src/builder/arrange.ts +33 -12
- package/src/builder/code-editor.ts +7 -2
- package/src/builder/components.ts +86 -4
- package/src/builder/designer.ts +44 -17
- package/src/builder/errors.ts +65 -0
- package/src/builder/form-designer.ts +229 -0
- package/src/builder/forms.ts +1 -1
- package/src/builder/locks.ts +6 -0
- package/src/builder/newapp.ts +2 -1
- package/src/builder/object-browser.ts +81 -18
- package/src/builder/region-help.ts +2 -0
- package/src/builder/region-settings.ts +2 -0
- package/src/builder/reporter.ts +1 -1
- package/src/builder/routes.ts +7 -5
- package/src/builder/search.ts +3 -3
- package/src/builder/shared.ts +41 -8
- package/src/builder/subscriptions.ts +30 -4
- package/src/builder/ui.ts +2 -0
- package/src/cli/replace.ts +16 -2
- package/src/i18n/ar.ts +62 -0
- package/src/i18n/cs.ts +62 -0
- package/src/i18n/da.ts +62 -0
- package/src/i18n/de.ts +62 -0
- package/src/i18n/el.ts +62 -0
- package/src/i18n/es.ts +62 -0
- package/src/i18n/fi.ts +62 -0
- package/src/i18n/fr.ts +62 -0
- package/src/i18n/he.ts +62 -0
- package/src/i18n/it.ts +62 -0
- package/src/i18n/ja.ts +62 -0
- package/src/i18n/ko.ts +62 -0
- package/src/i18n/nb.ts +62 -0
- package/src/i18n/pl.ts +62 -0
- package/src/i18n/pt.ts +62 -0
- package/src/i18n/ru.ts +62 -0
- package/src/i18n/sv.ts +62 -0
- package/src/i18n/tr.ts +62 -0
- package/src/i18n/uk.ts +62 -0
- package/src/i18n/zh.ts +62 -0
- package/src/i18n.ts +124 -0
- package/src/metadata.ts +18 -3
- package/src/runtime/component-settings.ts +42 -0
- package/src/runtime/context.ts +4 -0
- package/src/runtime/devbar.ts +1 -1
- package/src/runtime/documents.ts +29 -3
- package/src/runtime/engine.ts +2 -2
- package/src/runtime/facet-state.ts +10 -5
- package/src/runtime/facets.ts +29 -5
- package/src/runtime/form-designer.ts +676 -0
- package/src/runtime/grid.ts +36 -11
- package/src/runtime/items.ts +47 -32
- package/src/runtime/lists.ts +1 -1
- package/src/runtime/logic.ts +12 -7
- package/src/runtime/pdf.ts +2 -1
- package/src/runtime/regions.ts +8 -3
- package/src/runtime/render.ts +8 -3
- package/src/runtime/report.ts +4 -3
- package/src/runtime/routes.ts +7 -3
- package/src/runtime/shortcuts.ts +43 -0
- package/src/runtime/smart-filters.ts +2 -1
- package/src/runtime/tasks.ts +6 -3
- package/src/security.ts +2 -1
- package/src/subscriptions.ts +67 -0
package/src/builder/search.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { owner } from '../db.ts';
|
|
|
3
3
|
import { html, type Raw } from '../html.ts';
|
|
4
4
|
import { icon } from '../icons.ts';
|
|
5
5
|
import { COMPONENTS, type FieldKind } from './components.ts';
|
|
6
|
-
import { appHeader, BASE, developer, region, send, shell, type Req } from './ui.ts';
|
|
6
|
+
import { appHeader, BASE, designerUrl, developer, region, send, shell, type Req } from './ui.ts';
|
|
7
7
|
|
|
8
8
|
// App Builder → Search and "Where used": every component of an application
|
|
9
9
|
// (pages, regions, items, buttons, dynamic actions, validations, processes
|
|
@@ -46,7 +46,7 @@ export async function appEntries(appId: number, query: (sql: string, params?: un
|
|
|
46
46
|
const pageById = new Map(pages.map((p) => [p.id, p]));
|
|
47
47
|
const out: Entry[] = pages.map((p) => ({
|
|
48
48
|
kind: 'page', id: p.id, label: `Page ${p.page_no}: ${p.name}`, kindLabel: 'Page', icon: 'file', pageNo: p.page_no,
|
|
49
|
-
url:
|
|
49
|
+
url: designerUrl(appId, p.page_no, 'c=page'),
|
|
50
50
|
fields: PAGE_FIELDS.map((f) => ({ ...f, value: asText(p[f.name]) })),
|
|
51
51
|
}));
|
|
52
52
|
for (const [kind, spec] of Object.entries(COMPONENTS)) {
|
|
@@ -57,7 +57,7 @@ export async function appEntries(appId: number, query: (sql: string, params?: un
|
|
|
57
57
|
const page = spec.scope === 'page' ? pageById.get(row.page_id) : null;
|
|
58
58
|
out.push({
|
|
59
59
|
kind, id: row.id, label: spec.summary(row), kindLabel: spec.label, icon: spec.icon, pageNo: page?.page_no ?? null,
|
|
60
|
-
url: page ?
|
|
60
|
+
url: page ? designerUrl(appId, page.page_no, `c=${kind}-${row.id}`) : `${BASE}/apps/${appId}/shared?c=${kind}-${row.id}`,
|
|
61
61
|
fields: spec.fields.filter((f) => TEXT_KINDS.has(f.kind)).map((f) => ({ name: f.name, label: f.label, kind: f.kind, value: asText(row[f.name]) })),
|
|
62
62
|
});
|
|
63
63
|
}
|
package/src/builder/shared.ts
CHANGED
|
@@ -13,10 +13,11 @@ import { layoutExtras } from './layouts.ts';
|
|
|
13
13
|
import { templateExtras, templateImport } from './templates.ts';
|
|
14
14
|
import { credentialExtras, needsSecret, restSourceExtras } from './websources.ts';
|
|
15
15
|
import { dataLoadDefExtras } from './dataload.ts';
|
|
16
|
-
import { appHeader, back, BASE, csrf, developer, flash, input, region, select, send, shell, type Req } from './ui.ts';
|
|
16
|
+
import { appHeader, back, BASE, designerUrl, csrf, developer, flash, input, region, select, send, shell, type Req } from './ui.ts';
|
|
17
17
|
import { endSessions, grantAccess, roleHints, roleHintsHtml, splitRoles } from './users.ts';
|
|
18
18
|
import { appOr404, componentForm, lookups, saveComponent } from './forms.ts';
|
|
19
19
|
import { catalogPicker } from './rest-catalogs.ts';
|
|
20
|
+
import { COMPONENT_SETTINGS, componentSetting, componentSettingsFrom, type ComponentSettings } from '../runtime/component-settings.ts';
|
|
20
21
|
|
|
21
22
|
// Shared Components: navigation, authorization schemes, LOVs, application
|
|
22
23
|
// items and processes, report layouts, and access control.
|
|
@@ -61,12 +62,12 @@ export function sharedGroups(appId: number): { key: string; title: string; icon:
|
|
|
61
62
|
const settings = (tab: string, label: string): ScItem => ({ label, href: `${BASE}/apps/${appId}/settings?tab=${tab}` });
|
|
62
63
|
const page = (path: string, label: string): ScItem => ({ label, href: `${BASE}/apps/${appId}/${path}` });
|
|
63
64
|
return [
|
|
64
|
-
{ key: 'logic', title: 'Application Logic', icon: 'code', items: [t('app_item', 'Application Items'), t('app_process', 'Application Processes'), t('app_setting', 'Application Settings'), t('build_option', 'Build Options'), page('settings?tab=definition', 'Application Definition')] },
|
|
65
|
+
{ key: 'logic', title: 'Application Logic', icon: 'code', items: [t('app_item', 'Application Items'), t('app_process', 'Application Processes'), t('app_computation', 'Application Computations'), t('app_setting', 'Application Settings'), t('build_option', 'Build Options'), page('settings?tab=definition', 'Application Definition')] },
|
|
65
66
|
{ key: 'security', title: 'Security', icon: 'shield', items: [{ label: 'Access Control', href: `${BASE}/apps/${appId}/shared?view=tree`, kind: 'access' }, settings('security', 'Authentication'), t('authz_scheme', 'Authorization Schemes'), settings('security', 'Security Attributes'), settings('signin', 'Sign-in and Session Management')] },
|
|
66
|
-
{ key: 'other', title: 'Other Components', icon: 'layers', items: [t('lov', 'Lists of Values'), t('list', 'Lists'), t('list_entry', 'List Entries'), t('email_template', 'Email Templates'), t('supporting_script', 'Supporting Objects'), page('subscriptions', 'Subscriptions')] },
|
|
67
|
+
{ key: 'other', title: 'Other Components', icon: 'layers', items: [t('lov', 'Lists of Values'), t('list', 'Lists'), t('list_entry', 'List Entries'), t('email_template', 'Email Templates'), t('shortcut', 'Shortcuts'), t('component_group', 'Component Groups'), t('supporting_script', 'Supporting Objects'), page('subscriptions', 'Subscriptions')] },
|
|
67
68
|
{ key: 'navigation', title: 'Navigation and Search', icon: 'compass', items: [t('nav_entry', 'Navigation Menu'), settings('ui', 'Navigation Bar List'), page('breadcrumbs', 'Breadcrumbs'), t('page_group', 'Page Groups'), t('search_config', 'Search Configurations')] },
|
|
68
|
-
{ key: 'ui', title: 'User Interface', icon: 'grid', items: [page('theme', 'Themes and Theme Roller'), settings('ui', 'User Interface Attributes'), t('template_component', 'Template Components'), page('plugins', 'Plug-ins'), settings('pwa', 'Progressive Web App')] },
|
|
69
|
-
{ key: 'files', title: 'Files and Reports', icon: 'file', items: [page('static-files', 'Static Application Files'), t('report_layout', 'Report Layouts'), t('document_template', 'Document Templates')] },
|
|
69
|
+
{ key: 'ui', title: 'User Interface', icon: 'grid', items: [page('theme', 'Themes and Theme Roller'), settings('ui', 'User Interface Attributes'), t('template_component', 'Template Components'), page('plugins', 'Plug-ins'), page('component-settings', 'Component Settings'), settings('pwa', 'Progressive Web App')] },
|
|
70
|
+
{ key: 'files', title: 'Files and Reports', icon: 'file', items: [page('static-files', 'Static Application Files'), t('report_layout', 'Report Layouts'), t('report_query', 'Report Queries'), t('document_template', 'Document Templates')] },
|
|
70
71
|
{ key: 'data', title: 'Data Sources', icon: 'database', items: [t('rest_source', 'REST Data Sources'), t('web_credential', 'Web Credentials'), t('rest_module', 'RESTful Services'), t('data_load_def', 'Data Load Definitions')] },
|
|
71
72
|
{ key: 'workflows', title: 'Workflows and Automations', icon: 'activity', items: [t('workflow_definition', 'Workflows'), t('task_definition', 'Task Definitions'), t('automation', 'Automations')] },
|
|
72
73
|
{ key: 'globalization', title: 'Globalization', icon: 'globe', items: [page('globalization', 'Translations and Text Messages'), t('dynamic_translation', 'Dynamic Translations'), settings('globalization', 'Globalization Attributes')] },
|
|
@@ -88,7 +89,7 @@ export async function sharedRoutes(app: FastifyInstance) {
|
|
|
88
89
|
if (!kids.length) return html``;
|
|
89
90
|
return html`<ul class="tree">${kids.map((p) => {
|
|
90
91
|
const next = new Set(seen).add(p.page_no);
|
|
91
|
-
return html`<li><a href="${
|
|
92
|
+
return html`<li><a href="${designerUrl(a.id, p.page_no)}">${icon('file')}<span>${p.name}</span><span class="kind">p${p.page_no}</span></a>${tree(p.page_no, next)}</li>`;
|
|
92
93
|
})}</ul>`;
|
|
93
94
|
};
|
|
94
95
|
const main = html`${appHeader(a, 'shared')}
|
|
@@ -96,12 +97,44 @@ export async function sharedRoutes(app: FastifyInstance) {
|
|
|
96
97
|
return send(reply, s, shell(s, 'Breadcrumbs', [['App Builder', BASE], [a.name, `${BASE}/apps/${a.id}`], ['Shared Components', `${BASE}/apps/${a.id}/shared`], ['Breadcrumbs']], main));
|
|
97
98
|
});
|
|
98
99
|
|
|
100
|
+
// (105) Component Settings (APEX): the application's defaults of its built-in component types
|
|
101
|
+
app.get(`${BASE}/apps/:id/component-settings`, async (req: Req, reply) => {
|
|
102
|
+
const s = await developer(req, reply);
|
|
103
|
+
if (!s) return;
|
|
104
|
+
const a = await appOr404(req.params.id);
|
|
105
|
+
if (!a) return reply.code(404).send('Not found');
|
|
106
|
+
const cur = (a.component_settings ?? {}) as ComponentSettings;
|
|
107
|
+
const groups = [...new Set(COMPONENT_SETTINGS.map((x) => x.group))];
|
|
108
|
+
const main = html`${appHeader(a, 'shared')}
|
|
109
|
+
${region('Component Settings', html`<p class="muted u-mt0">Defaults for the application's built-in component types, used where a component sets nothing itself. Empty: pgkiln's default.</p>
|
|
110
|
+
<form method="post" action="${BASE}/apps/${a.id}/component-settings">${csrf(s)}
|
|
111
|
+
${groups.map((g) => html`<fieldset class="u-mb1"><legend>${g}</legend>${COMPONENT_SETTINGS.filter((x) => x.group === g).map((x) =>
|
|
112
|
+
input(x.key, x.label, cur[x.key] ?? '', { type: 'number', placeholder: String(x.def), help: `${x.help} ${x.min}–${x.max.toLocaleString('en')}; now ${componentSetting(cur, x.key).toLocaleString('en')}.` }))}</fieldset>`)}
|
|
113
|
+
<div class="buttons"><button class="btn btn-hot">Save</button></div>
|
|
114
|
+
</form>`)}`;
|
|
115
|
+
return send(reply, s, shell(s, 'Component Settings', [['App Builder', BASE], [a.name, `${BASE}/apps/${a.id}`], ['Shared Components', `${BASE}/apps/${a.id}/shared`], ['Component Settings']], main));
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
app.post(`${BASE}/apps/:id/component-settings`, async (req: Req, reply) => {
|
|
119
|
+
const s = await developer(req, reply);
|
|
120
|
+
if (!s) return;
|
|
121
|
+
const a = await appOr404(req.params.id);
|
|
122
|
+
if (!a) return reply.code(404).send('Not found');
|
|
123
|
+
const { settings, problems } = componentSettingsFrom(req.body ?? {});
|
|
124
|
+
if (problems.length) flash(s, problems.join(' '), 'error');
|
|
125
|
+
else {
|
|
126
|
+
await owner.query('update meta.app set component_settings = $2 where id = $1', [a.id, JSON.stringify(settings)]);
|
|
127
|
+
flash(s, 'Component settings saved.');
|
|
128
|
+
}
|
|
129
|
+
return back(reply, s, `${BASE}/apps/${a.id}/component-settings`);
|
|
130
|
+
});
|
|
131
|
+
|
|
99
132
|
// ---------------------------------------------------------------- shared components
|
|
100
|
-
const SHARED = ['nav_entry', 'authz_scheme', 'build_option', 'page_group', 'lov', 'app_item', 'app_process', 'automation', 'automation_action', 'report_layout', 'document_template', 'task_definition', 'workflow_definition', 'rest_module', 'template_component', 'web_credential', 'rest_source', 'data_load_def', 'list', 'list_entry', 'supporting_script', 'search_config', 'email_template', 'app_setting', 'dynamic_translation'];
|
|
133
|
+
const SHARED = ['nav_entry', 'authz_scheme', 'build_option', 'page_group', 'lov', 'app_item', 'app_process', 'app_computation', 'shortcut', 'report_query', 'component_group', 'automation', 'automation_action', 'report_layout', 'document_template', 'task_definition', 'workflow_definition', 'rest_module', 'template_component', 'web_credential', 'rest_source', 'data_load_def', 'list', 'list_entry', 'supporting_script', 'search_config', 'email_template', 'app_setting', 'dynamic_translation'];
|
|
101
134
|
|
|
102
135
|
const ORDER: Record<string, string> = {
|
|
103
136
|
search_config: 'seq, name', dynamic_translation: 'language, source',
|
|
104
|
-
nav_entry: 'parent_id nulls first, seq, id', app_process: 'seq, id', list_entry: 'list_name, parent_id nulls first, seq, id', automation_action: 'automation_name, seq, name', supporting_script: 'kind, seq, name',
|
|
137
|
+
nav_entry: 'parent_id nulls first, seq, id', app_process: 'seq, id', app_computation: 'seq, id', list_entry: 'list_name, parent_id nulls first, seq, id', automation_action: 'automation_name, seq, name', supporting_script: 'kind, seq, name',
|
|
105
138
|
};
|
|
106
139
|
|
|
107
140
|
app.get(`${BASE}/apps/:id/shared`, async (req: Req, reply) => {
|
|
@@ -4,8 +4,8 @@ import { html, type Raw } from '../html.ts';
|
|
|
4
4
|
import { icon } from '../icons.ts';
|
|
5
5
|
import { clientIp, logActivity, type Session } from '../session.ts';
|
|
6
6
|
import {
|
|
7
|
-
APP_TYPE_LABELS, inSync, isKind, KINDS, offers, publish, refresh, subscribe, subscribersOf, subscriptionOf, subscriptionsOf,
|
|
8
|
-
SubscriptionError, unsubscribe, type AppType, type Kind,
|
|
7
|
+
APP_TYPE_LABELS, groupsOffered, inSync, isKind, KINDS, offers, publish, refresh, subscribe, subscribersOf, subscriptionOf, subscriptionsOf,
|
|
8
|
+
SubscriptionError, subscribeGroup, unsubscribe, type AppType, type Kind,
|
|
9
9
|
} from '../subscriptions.ts';
|
|
10
10
|
import { appHeader, back, BASE, csrf, developer, flash, region, send, shell, type Req } from './ui.ts';
|
|
11
11
|
|
|
@@ -59,7 +59,7 @@ export async function subscriptionRoutes(app: FastifyInstance) {
|
|
|
59
59
|
if (!s) return;
|
|
60
60
|
const a = await appRow(req.params.id);
|
|
61
61
|
if (!a) return reply.code(404).send('Not found');
|
|
62
|
-
const [subs, subscribers, offered] = await Promise.all([subscriptionsOf(a.id), subscribersOf(a.id), offers(a.id)]);
|
|
62
|
+
const [subs, subscribers, offered, groupList] = await Promise.all([subscriptionsOf(a.id), subscribersOf(a.id), offers(a.id), groupsOffered(a.id)]);
|
|
63
63
|
const sync = await Promise.all(subs.map((x) => inSync(x)));
|
|
64
64
|
const subSync = await Promise.all(subscribers.map((x) => inSync(x)));
|
|
65
65
|
const hidden = (kind: string, name: string) => html`<input type="hidden" name="kind" value="${kind}"><input type="hidden" name="name" value="${name}">`;
|
|
@@ -93,7 +93,17 @@ export async function subscriptionRoutes(app: FastifyInstance) {
|
|
|
93
93
|
</select>
|
|
94
94
|
<small class="help">The component is copied now. A component of this application with the same name is replaced.</small></div>
|
|
95
95
|
<div class="buttons"><button class="btn btn-hot">Subscribe</button></div></form>`
|
|
96
|
-
: html`<p class="muted">No theme or library applications offer components. Set an application's type under its Settings.</p>`}
|
|
96
|
+
: html`<p class="muted">No theme or library applications offer components. Set an application's type under its Settings.</p>`}
|
|
97
|
+
${groupList.length
|
|
98
|
+
? html`<h3>Subscribe to a component group</h3>
|
|
99
|
+
<form method="post" action="${BASE}/apps/${a.id}/subscriptions/group">${csrf(s)}
|
|
100
|
+
<div class="field"><label class="label" for="f_group">Component group</label>
|
|
101
|
+
<select id="f_group" name="group" required><option value="">- choose -</option>
|
|
102
|
+
${groupList.map((g) => html`<option value="${g.master_id}|${g.name}">${g.master_name}: ${g.name} (${g.members.length})</option>`)}
|
|
103
|
+
</select>
|
|
104
|
+
<small class="help">Every component of the group is copied now and subscribed (Shared Components → Component Groups of a theme or library application).</small></div>
|
|
105
|
+
<div class="buttons"><button class="btn btn-hot">Subscribe to the group</button></div></form>`
|
|
106
|
+
: ''}`)}
|
|
97
107
|
${a.app_type === 'theme' || a.app_type === 'library' || subscribers.length
|
|
98
108
|
? region('Subscribers', subscribers.length
|
|
99
109
|
? html`<div class="table-wrap"><table class="report"><thead><tr><th>Component</th><th>Application</th><th>Refreshed</th><th>State</th></tr></thead><tbody>
|
|
@@ -138,6 +148,22 @@ export async function subscriptionRoutes(app: FastifyInstance) {
|
|
|
138
148
|
});
|
|
139
149
|
});
|
|
140
150
|
|
|
151
|
+
// (105) all the components of a master's component group
|
|
152
|
+
app.post(`${BASE}/apps/:id/subscriptions/group`, async (req: Req, reply) => {
|
|
153
|
+
const s = await developer(req, reply);
|
|
154
|
+
if (!s) return;
|
|
155
|
+
const a = await appRow(req.params.id);
|
|
156
|
+
if (!a) return reply.code(404).send('Not found');
|
|
157
|
+
const m = /^(\d{1,9})\|(.{1,60})$/s.exec(String(req.body?.group ?? ''));
|
|
158
|
+
return handle(req, reply, s, a.id, async () => {
|
|
159
|
+
if (!m) throw new SubscriptionError('Choose a component group.');
|
|
160
|
+
if (!(await groupsOffered(a.id)).some((g) => g.master_id === Number(m[1]) && g.name === m[2])) throw new SubscriptionError('Choose a component group.');
|
|
161
|
+
const { done, skipped } = await subscribeGroup(a.id, Number(m[1]), m[2], s.username!);
|
|
162
|
+
await logActivity({ appId: a.id, username: s.username, event: 'subscription', ip: clientIp(req), detail: `subscribed component group ${m[2]} from application ${m[1]}: ${done.join(', ')}` });
|
|
163
|
+
return `Subscribed to ${m[2]}: ${done.length} component${done.length === 1 ? '' : 's'} copied${skipped.length ? `; skipped ${skipped.join(', ')}` : ''}.`;
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
141
167
|
app.post(`${BASE}/apps/:id/subscriptions/refresh`, async (req: Req, reply) => {
|
|
142
168
|
const s = await developer(req, reply);
|
|
143
169
|
if (!s) return;
|
package/src/builder/ui.ts
CHANGED
|
@@ -12,6 +12,8 @@ import { ensureDevbar } from '../runtime/devbar.ts';
|
|
|
12
12
|
// Shared building blocks of the builder UI.
|
|
13
13
|
|
|
14
14
|
export const BASE = '/builder';
|
|
15
|
+
/** (sprint 86) The page designer's address: the application's id and the page's number (/builder/pages/:id redirects here). */
|
|
16
|
+
export const designerUrl = (appId: number, pageNo: number, query = '') => `${BASE}/apps/${appId}/pages/${pageNo}${query ? `?${query}` : ''}`;
|
|
15
17
|
|
|
16
18
|
/** pgkiln's version and the database name, for the builder's status bar. */
|
|
17
19
|
const VERSION = (JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8')) as { version: string }).version;
|
package/src/cli/replace.ts
CHANGED
|
@@ -26,8 +26,12 @@ export const REPLACED = [
|
|
|
26
26
|
'search_config', 'email_template',
|
|
27
27
|
// (086) application settings (installed values kept, see below), dynamic translations
|
|
28
28
|
'app_setting', 'dynamic_translation',
|
|
29
|
-
// (089) page groups
|
|
30
|
-
'page_group',
|
|
29
|
+
// (089) page groups, (102) application computations
|
|
30
|
+
'page_group', 'app_computation',
|
|
31
|
+
// (103) shortcuts, (104) report queries
|
|
32
|
+
'shortcut', 'report_query',
|
|
33
|
+
// (105) component groups
|
|
34
|
+
'component_group',
|
|
31
35
|
];
|
|
32
36
|
/** Tables of an application that belong to the installation: kept. */
|
|
33
37
|
export const KEPT = ['app_access', 'api_client', 'session', 'sso_pending', 'saved_report', 'persistent_login', 'task', 'workflow', 'process_job',
|
|
@@ -47,6 +51,8 @@ export const KEPT = ['app_access', 'api_client', 'session', 'sso_pending', 'save
|
|
|
47
51
|
'subscription',
|
|
48
52
|
// (057) users' Data Reporter reports (user data, like saved reports)
|
|
49
53
|
'data_report',
|
|
54
|
+
// (106) Form Designer forms, built by the application's administrators (installation data, like reports)
|
|
55
|
+
'user_form',
|
|
50
56
|
// (060) which AI services an application may use (installation data), its AI usage log and AI requests from SQL
|
|
51
57
|
'app_ai_service', 'ai_usage', 'ai_request',
|
|
52
58
|
// (061) AI assistant conversations (per session, follow their region)
|
|
@@ -75,6 +81,8 @@ const REPOINTED = ['saved_report.region_id', 'task.definition_id', 'workflow.def
|
|
|
75
81
|
'rest_sync_log.source_id',
|
|
76
82
|
// (057) Data Reporter reports follow their region like saved reports
|
|
77
83
|
'data_report.region_id',
|
|
84
|
+
// (106) Form Designer forms follow their region
|
|
85
|
+
'user_form.region_id',
|
|
78
86
|
// (061) AI assistant conversations follow their region
|
|
79
87
|
'ai_conversation.region_id'];
|
|
80
88
|
|
|
@@ -197,6 +205,12 @@ export async function replaceApp(db: Db, doc: unknown, alias: string, opts: Repl
|
|
|
197
205
|
from unnest($1::int[], $2::int[]) as m(old, neu) where s.region_id = m.old`,
|
|
198
206
|
[pairs.map((p) => p[0]), pairs.map((p) => p[1])],
|
|
199
207
|
);
|
|
208
|
+
if (pairs.length)
|
|
209
|
+
await db.query(
|
|
210
|
+
`update meta.user_form s set region_id = m.neu
|
|
211
|
+
from unnest($1::int[], $2::int[]) as m(old, neu) where s.region_id = m.old`,
|
|
212
|
+
[pairs.map((p) => p[0]), pairs.map((p) => p[1])],
|
|
213
|
+
);
|
|
200
214
|
if (pairs.length)
|
|
201
215
|
await db.query(
|
|
202
216
|
`update meta.ai_conversation s set region_id = m.neu
|
package/src/i18n/ar.ts
CHANGED
|
@@ -63,6 +63,7 @@ export const ar: Record<MessageKey, string> = {
|
|
|
63
63
|
'common.back': "رجوع",
|
|
64
64
|
'common.home': "الانتقال إلى الصفحة الرئيسية",
|
|
65
65
|
'common.skip': "تخطَّ إلى المحتوى",
|
|
66
|
+
'common.back_to_top': "العودة إلى الأعلى",
|
|
66
67
|
'common.toggle_nav': "إظهار التنقل أو إخفاؤه",
|
|
67
68
|
'common.dismiss': "إغلاق",
|
|
68
69
|
// messages and errors
|
|
@@ -390,6 +391,9 @@ export const ar: Record<MessageKey, string> = {
|
|
|
390
391
|
'facets.stars_up': "{n} نجوم فأكثر",
|
|
391
392
|
'facets.star_up': "نجمة واحدة فأكثر",
|
|
392
393
|
'facets.clear_short': "مسح",
|
|
394
|
+
'facets.menu': "خيارات {facet}",
|
|
395
|
+
'facets.sort_count': "الترتيب حسب العدد",
|
|
396
|
+
'facets.sort_name': "الترتيب حسب الاسم",
|
|
393
397
|
'facets.show_all': "عرض الكل ({n})",
|
|
394
398
|
'facets.total': "إجمالي عدد الصفوف {n}",
|
|
395
399
|
'smart.placeholder': "بحث أو تصفية…",
|
|
@@ -685,6 +689,64 @@ export const ar: Record<MessageKey, string> = {
|
|
|
685
689
|
'reporter.changed': "عُدِّل في {date}",
|
|
686
690
|
'reporter.no_match': "لا توجد تقارير مطابقة للبحث.",
|
|
687
691
|
'reporter.chart_tag': "مخطط",
|
|
692
|
+
// Form Designer (form-designer.ts)
|
|
693
|
+
'formdesigner.no_sources': "لم تُعدّ أي جداول لهذه المنطقة بعد.",
|
|
694
|
+
'formdesigner.no_forms': "لا توجد نماذج بعد.",
|
|
695
|
+
'formdesigner.new': "نموذج جديد",
|
|
696
|
+
'formdesigner.create': "تصميم",
|
|
697
|
+
'formdesigner.draft': "مسودة",
|
|
698
|
+
'formdesigner.design': "تصميم النموذج",
|
|
699
|
+
'formdesigner.design_of': "تصميم {name}",
|
|
700
|
+
'formdesigner.back': "كل النماذج",
|
|
701
|
+
'formdesigner.add': "إضافة",
|
|
702
|
+
'formdesigner.edit_row': "تعديل",
|
|
703
|
+
'formdesigner.rows': "صفوف {form}",
|
|
704
|
+
'formdesigner.save': "حفظ",
|
|
705
|
+
'formdesigner.delete': "حذف",
|
|
706
|
+
'formdesigner.delete_confirm': "حذف هذا الصف؟",
|
|
707
|
+
'formdesigner.cancel': "إلغاء",
|
|
708
|
+
'formdesigner.saved_row': "تم الحفظ.",
|
|
709
|
+
'formdesigner.deleted_row': "تم الحذف.",
|
|
710
|
+
'formdesigner.required': "الحقل {field} مطلوب.",
|
|
711
|
+
'formdesigner.not_in_list': "{field}: اختر قيمة من القائمة.",
|
|
712
|
+
'formdesigner.changed': "غيّر شخص آخر هذا الصف في هذه الأثناء. راجعه واحفظ مرة أخرى.",
|
|
713
|
+
'formdesigner.row_not_found': "هذا الصف غير موجود، أو لا يُسمح لك برؤيته.",
|
|
714
|
+
'formdesigner.not_allowed': "لا يُسمح لك بفعل ذلك بهذا الصف.",
|
|
715
|
+
'formdesigner.form_not_found': "هذا النموذج غير موجود (بعد الآن).",
|
|
716
|
+
'formdesigner.unknown_source': "لم يعد جدول هذا النموذج معروضًا.",
|
|
717
|
+
'formdesigner.nothing': "لا يحتوي هذا النموذج على حقول بعد.",
|
|
718
|
+
'formdesigner.choose': "- اختر -",
|
|
719
|
+
'formdesigner.name': "الاسم",
|
|
720
|
+
'formdesigner.description': "الوصف",
|
|
721
|
+
'formdesigner.published': "منشور: يمكن لكل من يرى هذه الصفحة استخدام النموذج",
|
|
722
|
+
'formdesigner.fields': "الحقول",
|
|
723
|
+
'formdesigner.field_on': "في النموذج",
|
|
724
|
+
'formdesigner.column': "العمود",
|
|
725
|
+
'formdesigner.key': "مفتاح",
|
|
726
|
+
'formdesigner.order': "الترتيب",
|
|
727
|
+
'formdesigner.label': "التسمية",
|
|
728
|
+
'formdesigner.help': "نص المساعدة",
|
|
729
|
+
'formdesigner.required_short': "مطلوب",
|
|
730
|
+
'formdesigner.display': "العرض",
|
|
731
|
+
'formdesigner.default': "الافتراضي",
|
|
732
|
+
'formdesigner.in_list': "في القائمة",
|
|
733
|
+
'formdesigner.display_auto': "حقل",
|
|
734
|
+
'formdesigner.display_textarea': "منطقة نص",
|
|
735
|
+
'formdesigner.display_select': "قائمة اختيار",
|
|
736
|
+
'formdesigner.display_radio': "مجموعة خيارات",
|
|
737
|
+
'formdesigner.actions': "يمكن للمستخدمين",
|
|
738
|
+
'formdesigner.allow_insert': "إضافة صفوف",
|
|
739
|
+
'formdesigner.allow_update': "تغيير صفوف",
|
|
740
|
+
'formdesigner.allow_delete': "حذف صفوف",
|
|
741
|
+
'formdesigner.list_help': "تعرض قائمة الصفوف الأعمدة المحددة «في القائمة»؛ وبدونها يضيف النموذج صفوفًا فقط.",
|
|
742
|
+
'formdesigner.save_form': "حفظ النموذج",
|
|
743
|
+
'formdesigner.delete_form': "حذف النموذج",
|
|
744
|
+
'formdesigner.delete_form_confirm': "حذف النموذج «{name}»؟ تبقى الصفوف التي حفظها.",
|
|
745
|
+
'formdesigner.form_saved': "تم حفظ النموذج «{name}».",
|
|
746
|
+
'formdesigner.form_deleted': "تم حذف النموذج.",
|
|
747
|
+
'formdesigner.name_required': "أعطِ النموذج اسمًا.",
|
|
748
|
+
'formdesigner.no_fields': "ضع عمودًا واحدًا على الأقل في النموذج.",
|
|
749
|
+
'formdesigner.changed_by': "غيّره {user} في {date}",
|
|
688
750
|
// AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
|
|
689
751
|
'assistant.conversation': "المحادثة",
|
|
690
752
|
'assistant.you': "أنت",
|
package/src/i18n/cs.ts
CHANGED
|
@@ -63,6 +63,7 @@ export const cs: Record<MessageKey, string> = {
|
|
|
63
63
|
'common.back': "Zpět",
|
|
64
64
|
'common.home': "Na úvodní stránku",
|
|
65
65
|
'common.skip': "Přejít na obsah",
|
|
66
|
+
'common.back_to_top': "Zpět nahoru",
|
|
66
67
|
'common.toggle_nav': "Zobrazit nebo skrýt navigaci",
|
|
67
68
|
'common.dismiss': "Zavřít",
|
|
68
69
|
// messages and errors
|
|
@@ -390,6 +391,9 @@ export const cs: Record<MessageKey, string> = {
|
|
|
390
391
|
'facets.stars_up': "{n} hvězdičky a více",
|
|
391
392
|
'facets.star_up': "1 hvězdička a více",
|
|
392
393
|
'facets.clear_short': "Vymazat",
|
|
394
|
+
'facets.menu': "Možnosti: {facet}",
|
|
395
|
+
'facets.sort_count': "Seřadit podle počtu",
|
|
396
|
+
'facets.sort_name': "Seřadit podle názvu",
|
|
393
397
|
'facets.show_all': "Zobrazit vše ({n})",
|
|
394
398
|
'facets.total': "Celkový počet řádků {n}",
|
|
395
399
|
'smart.placeholder': "Hledat nebo filtrovat…",
|
|
@@ -685,6 +689,64 @@ export const cs: Record<MessageKey, string> = {
|
|
|
685
689
|
'reporter.changed': "změněno {date}",
|
|
686
690
|
'reporter.no_match': "Hledání neodpovídá žádná sestava.",
|
|
687
691
|
'reporter.chart_tag': "graf",
|
|
692
|
+
// Form Designer (form-designer.ts)
|
|
693
|
+
'formdesigner.no_sources': "Pro tuto oblast zatím nejsou nastaveny žádné tabulky.",
|
|
694
|
+
'formdesigner.no_forms': "Zatím nejsou žádné formuláře.",
|
|
695
|
+
'formdesigner.new': "Nový formulář",
|
|
696
|
+
'formdesigner.create': "Navrhnout",
|
|
697
|
+
'formdesigner.draft': "Koncept",
|
|
698
|
+
'formdesigner.design': "Navrhnout formulář",
|
|
699
|
+
'formdesigner.design_of': "Návrh: {name}",
|
|
700
|
+
'formdesigner.back': "Všechny formuláře",
|
|
701
|
+
'formdesigner.add': "Přidat",
|
|
702
|
+
'formdesigner.edit_row': "Upravit",
|
|
703
|
+
'formdesigner.rows': "Řádky: {form}",
|
|
704
|
+
'formdesigner.save': "Uložit",
|
|
705
|
+
'formdesigner.delete': "Smazat",
|
|
706
|
+
'formdesigner.delete_confirm': "Smazat tento řádek?",
|
|
707
|
+
'formdesigner.cancel': "Zrušit",
|
|
708
|
+
'formdesigner.saved_row': "Uloženo.",
|
|
709
|
+
'formdesigner.deleted_row': "Smazáno.",
|
|
710
|
+
'formdesigner.required': "{field} je povinné.",
|
|
711
|
+
'formdesigner.not_in_list': "{field}: vyberte hodnotu ze seznamu.",
|
|
712
|
+
'formdesigner.changed': "Někdo jiný mezitím tento řádek změnil. Zkontrolujte ho a uložte znovu.",
|
|
713
|
+
'formdesigner.row_not_found': "Tento řádek neexistuje, nebo ho nesmíte vidět.",
|
|
714
|
+
'formdesigner.not_allowed': "To s tímto řádkem dělat nesmíte.",
|
|
715
|
+
'formdesigner.form_not_found': "Tento formulář neexistuje (už).",
|
|
716
|
+
'formdesigner.unknown_source': "Tabulka tohoto formuláře se už nenabízí.",
|
|
717
|
+
'formdesigner.nothing': "Tento formulář zatím nemá žádná pole.",
|
|
718
|
+
'formdesigner.choose': "- Vyberte -",
|
|
719
|
+
'formdesigner.name': "Název",
|
|
720
|
+
'formdesigner.description': "Popis",
|
|
721
|
+
'formdesigner.published': "Zveřejněno: formulář může používat každý, kdo vidí tuto stránku",
|
|
722
|
+
'formdesigner.fields': "Pole",
|
|
723
|
+
'formdesigner.field_on': "Ve formuláři",
|
|
724
|
+
'formdesigner.column': "Sloupec",
|
|
725
|
+
'formdesigner.key': "klíč",
|
|
726
|
+
'formdesigner.order': "Pořadí",
|
|
727
|
+
'formdesigner.label': "Popisek",
|
|
728
|
+
'formdesigner.help': "Nápověda",
|
|
729
|
+
'formdesigner.required_short': "Povinné",
|
|
730
|
+
'formdesigner.display': "Zobrazení",
|
|
731
|
+
'formdesigner.default': "Výchozí",
|
|
732
|
+
'formdesigner.in_list': "V seznamu",
|
|
733
|
+
'formdesigner.display_auto': "Pole",
|
|
734
|
+
'formdesigner.display_textarea': "Textová oblast",
|
|
735
|
+
'formdesigner.display_select': "Výběrový seznam",
|
|
736
|
+
'formdesigner.display_radio': "Přepínače",
|
|
737
|
+
'formdesigner.actions': "Uživatelé smějí",
|
|
738
|
+
'formdesigner.allow_insert': "přidávat řádky",
|
|
739
|
+
'formdesigner.allow_update': "měnit řádky",
|
|
740
|
+
'formdesigner.allow_delete': "mazat řádky",
|
|
741
|
+
'formdesigner.list_help': "Seznam řádků ukazuje sloupce zaškrtnuté „V seznamu“; bez nich formulář jen přidává řádky.",
|
|
742
|
+
'formdesigner.save_form': "Uložit formulář",
|
|
743
|
+
'formdesigner.delete_form': "Smazat formulář",
|
|
744
|
+
'formdesigner.delete_form_confirm': "Smazat formulář „{name}“? Řádky jím uložené zůstanou.",
|
|
745
|
+
'formdesigner.form_saved': "Formulář „{name}“ uložen.",
|
|
746
|
+
'formdesigner.form_deleted': "Formulář smazán.",
|
|
747
|
+
'formdesigner.name_required': "Pojmenujte formulář.",
|
|
748
|
+
'formdesigner.no_fields': "Dejte do formuláře alespoň jeden sloupec.",
|
|
749
|
+
'formdesigner.changed_by': "změnil(a) {user} dne {date}",
|
|
688
750
|
// AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
|
|
689
751
|
'assistant.conversation': "Konverzace",
|
|
690
752
|
'assistant.you': "Vy",
|
package/src/i18n/da.ts
CHANGED
|
@@ -63,6 +63,7 @@ export const da: Record<MessageKey, string> = {
|
|
|
63
63
|
'common.back': "Tilbage",
|
|
64
64
|
'common.home': "Gå til forsiden",
|
|
65
65
|
'common.skip': "Gå til indholdet",
|
|
66
|
+
'common.back_to_top': "Til toppen",
|
|
66
67
|
'common.toggle_nav': "Vis eller skjul navigationen",
|
|
67
68
|
'common.dismiss': "Luk",
|
|
68
69
|
// messages and errors
|
|
@@ -390,6 +391,9 @@ export const da: Record<MessageKey, string> = {
|
|
|
390
391
|
'facets.stars_up': "{n} stjerner og op",
|
|
391
392
|
'facets.star_up': "1 stjerne og op",
|
|
392
393
|
'facets.clear_short': "Ryd",
|
|
394
|
+
'facets.menu': "Indstillinger for {facet}",
|
|
395
|
+
'facets.sort_count': "Sortér efter antal",
|
|
396
|
+
'facets.sort_name': "Sortér efter navn",
|
|
393
397
|
'facets.show_all': "Vis alle ({n})",
|
|
394
398
|
'facets.total': "Antal rækker i alt {n}",
|
|
395
399
|
'smart.placeholder': "Søg eller filtrér…",
|
|
@@ -685,6 +689,64 @@ export const da: Record<MessageKey, string> = {
|
|
|
685
689
|
'reporter.changed': "ændret {date}",
|
|
686
690
|
'reporter.no_match': "Ingen rapporter matcher søgningen.",
|
|
687
691
|
'reporter.chart_tag': "diagram",
|
|
692
|
+
// Form Designer (form-designer.ts)
|
|
693
|
+
'formdesigner.no_sources': "Der er endnu ikke sat tabeller op for denne region.",
|
|
694
|
+
'formdesigner.no_forms': "Der er endnu ingen formularer.",
|
|
695
|
+
'formdesigner.new': "Ny formular",
|
|
696
|
+
'formdesigner.create': "Design",
|
|
697
|
+
'formdesigner.draft': "Kladde",
|
|
698
|
+
'formdesigner.design': "Design formular",
|
|
699
|
+
'formdesigner.design_of': "Design af {name}",
|
|
700
|
+
'formdesigner.back': "Alle formularer",
|
|
701
|
+
'formdesigner.add': "Tilføj",
|
|
702
|
+
'formdesigner.edit_row': "Rediger",
|
|
703
|
+
'formdesigner.rows': "Rækker i {form}",
|
|
704
|
+
'formdesigner.save': "Gem",
|
|
705
|
+
'formdesigner.delete': "Slet",
|
|
706
|
+
'formdesigner.delete_confirm': "Slet denne række?",
|
|
707
|
+
'formdesigner.cancel': "Annuller",
|
|
708
|
+
'formdesigner.saved_row': "Gemt.",
|
|
709
|
+
'formdesigner.deleted_row': "Slettet.",
|
|
710
|
+
'formdesigner.required': "{field} er påkrævet.",
|
|
711
|
+
'formdesigner.not_in_list': "{field}: vælg en værdi fra listen.",
|
|
712
|
+
'formdesigner.changed': "En anden har ændret denne række i mellemtiden. Kontrollér den, og gem igen.",
|
|
713
|
+
'formdesigner.row_not_found': "Denne række findes ikke, eller du må ikke se den.",
|
|
714
|
+
'formdesigner.not_allowed': "Det må du ikke gøre med denne række.",
|
|
715
|
+
'formdesigner.form_not_found': "Denne formular findes ikke (længere).",
|
|
716
|
+
'formdesigner.unknown_source': "Denne formulars tabel tilbydes ikke længere.",
|
|
717
|
+
'formdesigner.nothing': "Denne formular har endnu ingen felter.",
|
|
718
|
+
'formdesigner.choose': "- Vælg -",
|
|
719
|
+
'formdesigner.name': "Navn",
|
|
720
|
+
'formdesigner.description': "Beskrivelse",
|
|
721
|
+
'formdesigner.published': "Udgivet: alle, der kan se denne side, må bruge formularen",
|
|
722
|
+
'formdesigner.fields': "Felter",
|
|
723
|
+
'formdesigner.field_on': "På formularen",
|
|
724
|
+
'formdesigner.column': "Kolonne",
|
|
725
|
+
'formdesigner.key': "nøgle",
|
|
726
|
+
'formdesigner.order': "Rækkefølge",
|
|
727
|
+
'formdesigner.label': "Etiket",
|
|
728
|
+
'formdesigner.help': "Hjælpetekst",
|
|
729
|
+
'formdesigner.required_short': "Påkrævet",
|
|
730
|
+
'formdesigner.display': "Visning",
|
|
731
|
+
'formdesigner.default': "Standard",
|
|
732
|
+
'formdesigner.in_list': "I listen",
|
|
733
|
+
'formdesigner.display_auto': "Felt",
|
|
734
|
+
'formdesigner.display_textarea': "Tekstområde",
|
|
735
|
+
'formdesigner.display_select': "Valgliste",
|
|
736
|
+
'formdesigner.display_radio': "Radioknapper",
|
|
737
|
+
'formdesigner.actions': "Brugere må",
|
|
738
|
+
'formdesigner.allow_insert': "tilføje rækker",
|
|
739
|
+
'formdesigner.allow_update': "ændre rækker",
|
|
740
|
+
'formdesigner.allow_delete': "slette rækker",
|
|
741
|
+
'formdesigner.list_help': "Listen over rækker viser kolonnerne med flueben i \"I listen\"; uden nogen tilføjer formularen kun rækker.",
|
|
742
|
+
'formdesigner.save_form': "Gem formular",
|
|
743
|
+
'formdesigner.delete_form': "Slet formular",
|
|
744
|
+
'formdesigner.delete_form_confirm': "Slet formularen \"{name}\"? Rækkerne, den har gemt, bliver.",
|
|
745
|
+
'formdesigner.form_saved': "Formularen \"{name}\" er gemt.",
|
|
746
|
+
'formdesigner.form_deleted': "Formularen er slettet.",
|
|
747
|
+
'formdesigner.name_required': "Giv formularen et navn.",
|
|
748
|
+
'formdesigner.no_fields': "Sæt mindst én kolonne på formularen.",
|
|
749
|
+
'formdesigner.changed_by': "ændret af {user} den {date}",
|
|
688
750
|
// AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
|
|
689
751
|
'assistant.conversation': "Samtale",
|
|
690
752
|
'assistant.you': "Dig",
|
package/src/i18n/de.ts
CHANGED
|
@@ -63,6 +63,7 @@ export const de: Record<MessageKey, string> = {
|
|
|
63
63
|
'common.back': 'Zurück',
|
|
64
64
|
'common.home': 'Zur Startseite',
|
|
65
65
|
'common.skip': 'Zum Inhalt springen',
|
|
66
|
+
'common.back_to_top': "Nach oben",
|
|
66
67
|
'common.toggle_nav': 'Navigation ein-/ausblenden',
|
|
67
68
|
'common.dismiss': 'Schließen',
|
|
68
69
|
// messages and errors
|
|
@@ -390,6 +391,9 @@ export const de: Record<MessageKey, string> = {
|
|
|
390
391
|
'facets.stars_up': '{n} Sterne und mehr',
|
|
391
392
|
'facets.star_up': '1 Stern und mehr',
|
|
392
393
|
'facets.clear_short': 'Löschen',
|
|
394
|
+
'facets.menu': 'Optionen für {facet}',
|
|
395
|
+
'facets.sort_count': 'Nach Anzahl sortieren',
|
|
396
|
+
'facets.sort_name': 'Nach Name sortieren',
|
|
393
397
|
'facets.show_all': 'Alle anzeigen ({n})',
|
|
394
398
|
'facets.total': 'Zeilen insgesamt {n}',
|
|
395
399
|
'smart.placeholder': 'Suchen oder filtern…',
|
|
@@ -685,6 +689,64 @@ export const de: Record<MessageKey, string> = {
|
|
|
685
689
|
'reporter.changed': "geändert {date}",
|
|
686
690
|
'reporter.no_match': "Keine Berichte gefunden.",
|
|
687
691
|
'reporter.chart_tag': "Diagramm",
|
|
692
|
+
// Form Designer (form-designer.ts)
|
|
693
|
+
'formdesigner.no_sources': "Für diese Region sind noch keine Tabellen eingerichtet.",
|
|
694
|
+
'formdesigner.no_forms': "Es gibt noch keine Formulare.",
|
|
695
|
+
'formdesigner.new': "Neues Formular",
|
|
696
|
+
'formdesigner.create': "Entwerfen",
|
|
697
|
+
'formdesigner.draft': "Entwurf",
|
|
698
|
+
'formdesigner.design': "Formular entwerfen",
|
|
699
|
+
'formdesigner.design_of': "Entwurf von {name}",
|
|
700
|
+
'formdesigner.back': "Alle Formulare",
|
|
701
|
+
'formdesigner.add': "Hinzufügen",
|
|
702
|
+
'formdesigner.edit_row': "Bearbeiten",
|
|
703
|
+
'formdesigner.rows': "Zeilen von {form}",
|
|
704
|
+
'formdesigner.save': "Speichern",
|
|
705
|
+
'formdesigner.delete': "Löschen",
|
|
706
|
+
'formdesigner.delete_confirm': "Diese Zeile löschen?",
|
|
707
|
+
'formdesigner.cancel': "Abbrechen",
|
|
708
|
+
'formdesigner.saved_row': "Gespeichert.",
|
|
709
|
+
'formdesigner.deleted_row': "Gelöscht.",
|
|
710
|
+
'formdesigner.required': "{field} ist erforderlich.",
|
|
711
|
+
'formdesigner.not_in_list': "{field}: Wählen Sie einen Wert aus der Liste.",
|
|
712
|
+
'formdesigner.changed': "Jemand anderes hat diese Zeile inzwischen geändert. Prüfen Sie sie und speichern Sie erneut.",
|
|
713
|
+
'formdesigner.row_not_found': "Diese Zeile gibt es nicht, oder Sie dürfen sie nicht sehen.",
|
|
714
|
+
'formdesigner.not_allowed': "Das dürfen Sie mit dieser Zeile nicht tun.",
|
|
715
|
+
'formdesigner.form_not_found': "Dieses Formular gibt es nicht (mehr).",
|
|
716
|
+
'formdesigner.unknown_source': "Die Tabelle dieses Formulars wird nicht mehr angeboten.",
|
|
717
|
+
'formdesigner.nothing': "Dieses Formular hat noch keine Felder.",
|
|
718
|
+
'formdesigner.choose': "- Auswählen -",
|
|
719
|
+
'formdesigner.name': "Name",
|
|
720
|
+
'formdesigner.description': "Beschreibung",
|
|
721
|
+
'formdesigner.published': "Veröffentlicht: Alle, die diese Seite sehen, dürfen das Formular verwenden",
|
|
722
|
+
'formdesigner.fields': "Felder",
|
|
723
|
+
'formdesigner.field_on': "Im Formular",
|
|
724
|
+
'formdesigner.column': "Spalte",
|
|
725
|
+
'formdesigner.key': "Schlüssel",
|
|
726
|
+
'formdesigner.order': "Reihenfolge",
|
|
727
|
+
'formdesigner.label': "Bezeichnung",
|
|
728
|
+
'formdesigner.help': "Hilfetext",
|
|
729
|
+
'formdesigner.required_short': "Erforderlich",
|
|
730
|
+
'formdesigner.display': "Anzeige",
|
|
731
|
+
'formdesigner.default': "Standardwert",
|
|
732
|
+
'formdesigner.in_list': "In der Liste",
|
|
733
|
+
'formdesigner.display_auto': "Feld",
|
|
734
|
+
'formdesigner.display_textarea': "Textbereich",
|
|
735
|
+
'formdesigner.display_select': "Auswahlliste",
|
|
736
|
+
'formdesigner.display_radio': "Optionsgruppe",
|
|
737
|
+
'formdesigner.actions': "Benutzer dürfen",
|
|
738
|
+
'formdesigner.allow_insert': "Zeilen hinzufügen",
|
|
739
|
+
'formdesigner.allow_update': "Zeilen ändern",
|
|
740
|
+
'formdesigner.allow_delete': "Zeilen löschen",
|
|
741
|
+
'formdesigner.list_help': "Die Zeilenliste zeigt die bei „In der Liste“ angekreuzten Spalten; ohne solche fügt das Formular nur Zeilen hinzu.",
|
|
742
|
+
'formdesigner.save_form': "Formular speichern",
|
|
743
|
+
'formdesigner.delete_form': "Formular löschen",
|
|
744
|
+
'formdesigner.delete_form_confirm': "Das Formular „{name}“ löschen? Die damit gespeicherten Zeilen bleiben.",
|
|
745
|
+
'formdesigner.form_saved': "Formular „{name}“ gespeichert.",
|
|
746
|
+
'formdesigner.form_deleted': "Formular gelöscht.",
|
|
747
|
+
'formdesigner.name_required': "Geben Sie dem Formular einen Namen.",
|
|
748
|
+
'formdesigner.no_fields': "Setzen Sie mindestens eine Spalte in das Formular.",
|
|
749
|
+
'formdesigner.changed_by': "geändert von {user} am {date}",
|
|
688
750
|
// AI assistant region and natural-language report filters (assistant.ts, ai-filter.ts)
|
|
689
751
|
'assistant.conversation': "Unterhaltung",
|
|
690
752
|
'assistant.you': "Sie",
|