pgkiln 0.31.0 → 0.36.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/README.md +2 -2
- package/SECURITY.md +10 -0
- package/db/migrations/076_form_lost_update.sql +50 -0
- package/db/migrations/077_warn_unsaved.sql +41 -0
- package/db/migrations/078_url_region.sql +12 -0
- package/db/migrations/079_developer_toolbar.sql +53 -0
- package/db/migrations/080_search.sql +326 -0
- package/db/migrations/081_mail.sql +621 -0
- package/db/migrations/082_app_backups.sql +73 -0
- package/db/migrations/083_availability.sql +156 -0
- package/db/migrations/084_feedback.sql +46 -0
- package/db/migrations/085_page_behaviour.sql +65 -0
- package/db/migrations/086_data_settings.sql +599 -0
- package/db/migrations/087_login_page.sql +175 -0
- package/db/migrations/088_region_templates.sql +18 -0
- package/db/migrations/089_page_groups.sql +330 -0
- package/docs/apex-feature-parity.md +61 -39
- package/docs/guide/01-installation.md +1 -0
- package/docs/guide/03-builder.md +154 -4
- package/docs/guide/04-pages-and-regions.md +157 -3
- package/docs/guide/05-items.md +47 -0
- package/docs/guide/06-processing.md +210 -2
- package/docs/guide/07-dynamic-actions.md +60 -2
- package/docs/guide/08-security.md +56 -4
- package/docs/guide/09-reference.md +21 -0
- package/docs/guide/11-from-apex.md +9 -2
- package/docs/guide/12-development.md +40 -2
- package/docs/guide/14-globalization.md +15 -0
- package/docs/guide/15-extensions.md +1 -1
- package/docs/guide/17-mobile.md +3 -1
- package/docs/guide/18-cli.md +32 -1
- package/docs/guide/20-ai-agents.md +5 -2
- package/examples/hr/hr_50_search.sql +47 -0
- package/examples/hr/hr_51_mail.sql +70 -0
- package/examples/hr/hr_52_feedback.sql +15 -0
- package/examples/hr/hr_53_data_settings.sql +108 -0
- package/examples/hr/hr_54_region_templates.sql +67 -0
- package/examples/hr/hr_55_page_groups.sql +24 -0
- package/package.json +3 -1
- package/public/app.css +122 -3
- package/public/app.js +496 -13
- package/public/builder.css +22 -0
- package/public/builder.js +77 -0
- package/public/code-editor.css +3 -3
- package/public/sw.js +5 -3
- package/src/app.ts +10 -0
- package/src/appfiles.ts +13 -0
- package/src/automations.ts +12 -0
- package/src/builder/advisor.ts +68 -14
- package/src/builder/arrange.ts +2 -2
- package/src/builder/availability.ts +74 -0
- package/src/builder/backups.ts +134 -0
- package/src/builder/components.ts +123 -16
- package/src/builder/ddl-tools.ts +114 -0
- package/src/builder/designer.ts +35 -4
- package/src/builder/feedback.ts +82 -0
- package/src/builder/forms.ts +10 -5
- package/src/builder/home.ts +3 -0
- package/src/builder/locks.ts +15 -5
- package/src/builder/login-page.ts +82 -0
- package/src/builder/mail.ts +425 -0
- package/src/builder/newapp.ts +1 -1
- package/src/builder/region-help.ts +73 -0
- package/src/builder/region-settings.ts +19 -3
- package/src/builder/routes.ts +60 -5
- package/src/builder/search.ts +4 -4
- package/src/builder/shared.ts +3 -2
- package/src/builder/sql.ts +3 -0
- package/src/builder/ui.ts +9 -3
- package/src/cli/apps.ts +46 -5
- package/src/cli/main.ts +66 -5
- package/src/cli/mcp.ts +58 -4
- package/src/cli/replace.ts +40 -4
- package/src/ddl.ts +509 -0
- package/src/geocode.ts +44 -0
- package/src/i18n/ar.ts +56 -0
- package/src/i18n/cs.ts +56 -0
- package/src/i18n/da.ts +56 -0
- package/src/i18n/de.ts +56 -0
- package/src/i18n/el.ts +56 -0
- package/src/i18n/es.ts +56 -0
- package/src/i18n/fi.ts +56 -0
- package/src/i18n/fr.ts +56 -0
- package/src/i18n/he.ts +56 -0
- package/src/i18n/it.ts +56 -0
- package/src/i18n/ja.ts +56 -0
- package/src/i18n/ko.ts +56 -0
- package/src/i18n/nb.ts +56 -0
- package/src/i18n/pl.ts +56 -0
- package/src/i18n/pt.ts +56 -0
- package/src/i18n/ru.ts +56 -0
- package/src/i18n/sv.ts +56 -0
- package/src/i18n/tr.ts +56 -0
- package/src/i18n/uk.ts +56 -0
- package/src/i18n/zh.ts +56 -0
- package/src/i18n.ts +112 -0
- package/src/icons.ts +2 -1
- package/src/layout.ts +3 -2
- package/src/mail/mail.ts +148 -0
- package/src/mail/providers.ts +444 -0
- package/src/metadata.ts +34 -10
- package/src/runtime/ajax.ts +97 -0
- package/src/runtime/authz.ts +1 -1
- package/src/runtime/availability.ts +37 -0
- package/src/runtime/context.ts +46 -6
- package/src/runtime/data-export.ts +94 -0
- package/src/runtime/data-items.ts +194 -0
- package/src/runtime/devbar.ts +184 -0
- package/src/runtime/display-selector.ts +12 -4
- package/src/runtime/engine.ts +26 -1
- package/src/runtime/feedback.ts +128 -0
- package/src/runtime/grid.ts +170 -26
- package/src/runtime/items.ts +8 -0
- package/src/runtime/lists.ts +11 -1
- package/src/runtime/password-reset.ts +171 -0
- package/src/runtime/processes.ts +83 -2
- package/src/runtime/pwa.ts +7 -10
- package/src/runtime/regions.ts +41 -0
- package/src/runtime/render.ts +48 -7
- package/src/runtime/report-views.ts +4 -2
- package/src/runtime/report.ts +36 -22
- package/src/runtime/rest.ts +4 -0
- package/src/runtime/routes.ts +94 -37
- package/src/runtime/search.ts +252 -0
- package/src/runtime/signin.ts +80 -0
- package/src/runtime/url-region.ts +93 -0
- package/src/security.ts +11 -1
- package/src/server.ts +2 -0
- package/src/session.ts +8 -2
- package/src/static-version.ts +34 -0
- package/src/workingcopy.ts +2 -2
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- HR example, part 53: data and settings (migration 086;
|
|
3
|
+
-- docs/guide/06-processing.md "Application settings", "User preferences",
|
|
4
|
+
-- "Data export"; docs/guide/14-globalization.md "Dynamic translations";
|
|
5
|
+
-- docs/guide/05-items.md "Display image", "Geocoded address")
|
|
6
|
+
--
|
|
7
|
+
-- Application settings SUPPORT_EMAIL and LEAVE_APPROVAL (Shared
|
|
8
|
+
-- Components → Application settings), read with meta.get_app_setting.
|
|
9
|
+
-- Dynamic translations of the department names in Dutch and German:
|
|
10
|
+
-- meta.lang(dname). Page 43 "Data and settings" (Administration):
|
|
11
|
+
-- - P43_DEPTNO, a department (names through meta.lang), kept per user
|
|
12
|
+
-- across sessions ({"persist": "user"});
|
|
13
|
+
-- - P43_EMPNO, an employee of that department, also kept per user, and
|
|
14
|
+
-- P43_PHOTO, the employee's photo from hr.emp.photo (display image);
|
|
15
|
+
-- - P43_ADDRESS, a geocoded address whose position goes into P43_LOCATION
|
|
16
|
+
-- (the "Check address" button shows where the server has GEOCODER_URL);
|
|
17
|
+
-- - Excel and CSV downloads of the department's employees (the download
|
|
18
|
+
-- process with {"export": …}, APEX_DATA_EXPORT).
|
|
19
|
+
-- =====================================================================
|
|
20
|
+
|
|
21
|
+
insert into meta.app_setting (app_id, name, value, description, required, valid_values)
|
|
22
|
+
select a.id, s.name, s.value, s.description, s.required, s.valid_values
|
|
23
|
+
from meta.app a, (values
|
|
24
|
+
('SUPPORT_EMAIL', 'hr-support@example.com', 'Where users write with questions; shown on page 43.', true, null::text[]),
|
|
25
|
+
('LEAVE_APPROVAL', 'manager', 'Who approves leave requests: the manager, or the HR department.', true, '{manager,hr}'::text[])
|
|
26
|
+
) as s (name, value, description, required, valid_values)
|
|
27
|
+
where a.alias = 'hr';
|
|
28
|
+
|
|
29
|
+
insert into meta.dynamic_translation (app_id, language, source, target)
|
|
30
|
+
select a.id, t.language, t.source, t.target
|
|
31
|
+
from meta.app a, (values
|
|
32
|
+
('nl', 'ACCOUNTING', 'BOEKHOUDING'), ('nl', 'RESEARCH', 'ONDERZOEK'), ('nl', 'SALES', 'VERKOOP'), ('nl', 'OPERATIONS', 'BEDRIJFSVOERING'),
|
|
33
|
+
('de', 'ACCOUNTING', 'BUCHHALTUNG'), ('de', 'RESEARCH', 'FORSCHUNG'), ('de', 'SALES', 'VERTRIEB'), ('de', 'OPERATIONS', 'BETRIEB')
|
|
34
|
+
) as t (language, source, target)
|
|
35
|
+
where a.alias = 'hr';
|
|
36
|
+
|
|
37
|
+
insert into meta.page (app_id, page_no, name, title, parent_page)
|
|
38
|
+
select id, 43, 'Data and settings', 'Data and settings', 1 from meta.app where alias = 'hr';
|
|
39
|
+
|
|
40
|
+
insert into meta.region (page_id, seq, title, type, columns, template)
|
|
41
|
+
select p.id, r.seq, r.title, 'static', 12, 'standard'
|
|
42
|
+
from meta.page p join meta.app a on a.id = p.app_id, (values (10, 'Employee'), (20, 'Visit address')) as r (seq, title)
|
|
43
|
+
where a.alias = 'hr' and p.page_no = 43;
|
|
44
|
+
|
|
45
|
+
insert into meta.item (page_id, region_id, seq, name, label, type, lov, help, config)
|
|
46
|
+
select p.id, (select r.id from meta.region r where r.page_id = p.id and r.title = i.region), i.seq, i.name, i.label, i.type, i.lov, i.help, i.config::jsonb
|
|
47
|
+
from meta.page p join meta.app a on a.id = p.app_id, (values
|
|
48
|
+
('Employee', 10, 'P43_DEPTNO', 'Department', 'select',
|
|
49
|
+
'select meta.lang(dname), deptno from hr.dept order by 1', 'Remembered for you, also after you sign out.',
|
|
50
|
+
'{"persist": "user", "submit_on_change": true, "null_label": "- All -"}'),
|
|
51
|
+
('Employee', 20, 'P43_EMPNO', 'Employee', 'select',
|
|
52
|
+
'select initcap(ename), empno from hr.emp where :P43_DEPTNO is null or deptno = :P43_DEPTNO::int order by 1', null,
|
|
53
|
+
'{"persist": "user", "submit_on_change": true, "null_label": "- Choose -"}'),
|
|
54
|
+
('Employee', 30, 'P43_PHOTO', 'Photo', 'image', null, null,
|
|
55
|
+
'{"sql": "select photo, photo_mime from hr.emp where empno = :P43_EMPNO::int", "alt": "Photo of the employee", "width": 160}'),
|
|
56
|
+
('Employee', 40, 'P43_SUPPORT', 'Questions?', 'display', null, 'The application setting SUPPORT_EMAIL.', '{}'),
|
|
57
|
+
('Visit address', 50, 'P43_ADDRESS', 'Address', 'address', null, 'Type the address and choose it from the list.',
|
|
58
|
+
'{"coordinates_item": "P43_LOCATION"}'),
|
|
59
|
+
('Visit address', 60, 'P43_LOCATION', 'Position', 'location', null, null, '{}')
|
|
60
|
+
) as i (region, seq, name, label, type, lov, help, config)
|
|
61
|
+
where a.alias = 'hr' and p.page_no = 43;
|
|
62
|
+
|
|
63
|
+
insert into meta.button (page_id, region_id, seq, name, label, action)
|
|
64
|
+
select p.id, (select r.id from meta.region r where r.page_id = p.id and r.title = 'Employee'), b.seq, b.name, b.label, 'submit'
|
|
65
|
+
from meta.page p join meta.app a on a.id = p.app_id, (values (10, 'EXPORT_XLSX', 'Download Excel'), (20, 'EXPORT_CSV', 'Download CSV')) as b (seq, name, label)
|
|
66
|
+
where a.alias = 'hr' and p.page_no = 43;
|
|
67
|
+
|
|
68
|
+
insert into meta.process (page_id, seq, name, type, point, code, when_button, config)
|
|
69
|
+
select p.id, x.seq, x.name, x.type, x.point, x.code, x.button, x.config::jsonb
|
|
70
|
+
from meta.page p join meta.app a on a.id = p.app_id, (values
|
|
71
|
+
(10, 'Support address', 'sql', 'load', 'select meta.get_app_setting(''SUPPORT_EMAIL'') as p43_support', null, '{}'),
|
|
72
|
+
(20, 'Employees as Excel', 'download', 'submit',
|
|
73
|
+
'select e.empno, initcap(e.ename) as name, initcap(e.job) as job, e.hiredate, e.sal as salary, meta.lang(d.dname) as department
|
|
74
|
+
from hr.emp e left join hr.dept d on d.deptno = e.deptno
|
|
75
|
+
where :P43_DEPTNO is null or e.deptno = :P43_DEPTNO::int
|
|
76
|
+
order by e.ename', 'EXPORT_XLSX', '{"export": "xlsx", "file_name": "employees", "title": "Employees"}'),
|
|
77
|
+
(30, 'Employees as CSV', 'download', 'submit',
|
|
78
|
+
'select e.empno, initcap(e.ename) as name, initcap(e.job) as job, e.hiredate, e.sal as salary, meta.lang(d.dname) as department
|
|
79
|
+
from hr.emp e left join hr.dept d on d.deptno = e.deptno
|
|
80
|
+
where :P43_DEPTNO is null or e.deptno = :P43_DEPTNO::int
|
|
81
|
+
order by e.ename', 'EXPORT_CSV', '{"export": "csv", "file_name": "employees"}')
|
|
82
|
+
) as x (seq, name, type, point, code, button, config)
|
|
83
|
+
where a.alias = 'hr' and p.page_no = 43;
|
|
84
|
+
|
|
85
|
+
insert into meta.nav_entry (app_id, parent_id, seq, label, icon, target_page)
|
|
86
|
+
select a.id, n.id, 40, 'Data and settings', 'settings', 43
|
|
87
|
+
from meta.app a join meta.nav_entry n on n.app_id = a.id and n.label = 'Administration' and n.parent_id is null
|
|
88
|
+
where a.alias = 'hr';
|
|
89
|
+
|
|
90
|
+
insert into meta.translation (app_id, language, source, target)
|
|
91
|
+
select a.id, 'nl', t.source, t.target
|
|
92
|
+
from meta.app a, (values
|
|
93
|
+
('Data and settings', 'Gegevens en instellingen'),
|
|
94
|
+
('Employee', 'Medewerker'),
|
|
95
|
+
('Visit address', 'Bezoekadres'),
|
|
96
|
+
('Department', 'Afdeling'),
|
|
97
|
+
('Photo', 'Foto'),
|
|
98
|
+
('Questions?', 'Vragen?'),
|
|
99
|
+
('Address', 'Adres'),
|
|
100
|
+
('Position', 'Positie'),
|
|
101
|
+
('Download Excel', 'Excel downloaden'),
|
|
102
|
+
('Download CSV', 'CSV downloaden'),
|
|
103
|
+
('Remembered for you, also after you sign out.', 'Wordt voor u onthouden, ook na afmelden.'),
|
|
104
|
+
('The application setting SUPPORT_EMAIL.', 'De applicatie-instelling SUPPORT_EMAIL.'),
|
|
105
|
+
('Type the address and choose it from the list.', 'Typ het adres en kies het uit de lijst.')
|
|
106
|
+
) as t (source, target)
|
|
107
|
+
where a.alias = 'hr'
|
|
108
|
+
on conflict do nothing;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- HR example, part 54: region templates (migration 088;
|
|
3
|
+
-- docs/guide/04-pages-and-regions.md "Region templates")
|
|
4
|
+
--
|
|
5
|
+
-- Page 44 "Region templates" (Administration):
|
|
6
|
+
-- - a hero region (an icon, the title, text and a button);
|
|
7
|
+
-- - a title bar with buttons; a buttons container;
|
|
8
|
+
-- - "Leave policy" opens an inline dialog and "Help" an inline drawer
|
|
9
|
+
-- (dynamic actions "Open region"; a link to #R<id> opens them too);
|
|
10
|
+
-- - a carousel (a display selector with style carousel) over three
|
|
11
|
+
-- regions, turning every 8 seconds;
|
|
12
|
+
-- - the steps of onboarding as a wizard progress list above a wizard
|
|
13
|
+
-- region.
|
|
14
|
+
-- =====================================================================
|
|
15
|
+
|
|
16
|
+
insert into meta.page (app_id, page_no, name, title, parent_page)
|
|
17
|
+
select id, 44, 'Region templates', 'Region templates', 1 from meta.app where alias = 'hr';
|
|
18
|
+
|
|
19
|
+
insert into meta.list (app_id, name, type, description)
|
|
20
|
+
select id, 'HR_ONBOARDING_STEPS', 'static', 'The steps of onboarding (a wizard progress list on page 44)' from meta.app where alias = 'hr';
|
|
21
|
+
|
|
22
|
+
insert into meta.list_entry (app_id, list_name, seq, label, target_page)
|
|
23
|
+
select a.id, 'HR_ONBOARDING_STEPS', e.seq, e.label, e.page
|
|
24
|
+
from meta.app a, (values (10, 'Personal details', 44), (20, 'Contract', 3), (30, 'Equipment', 28), (40, 'Done', 1)) as e (seq, label, page)
|
|
25
|
+
where a.alias = 'hr';
|
|
26
|
+
|
|
27
|
+
insert into meta.region (page_id, seq, title, type, columns, template, source, config)
|
|
28
|
+
select p.id, r.seq, r.title, r.type, 12, r.template, r.source, r.config::jsonb
|
|
29
|
+
from meta.page p join meta.app a on a.id = p.app_id, (values
|
|
30
|
+
(10, 'Welcome to HR', 'static', 'hero',
|
|
31
|
+
'<p>Everything about the people in the company: employees, departments, leave and reviews.</p>', '{"icon": "users"}'),
|
|
32
|
+
(20, 'Employees this month', 'static', 'title_bar', '<p class="muted">Hires and departures of the current month.</p>', '{}'),
|
|
33
|
+
(30, 'Actions', 'static', 'buttons_container', null, '{}'),
|
|
34
|
+
(40, 'Leave policy', 'static', 'inline_dialog',
|
|
35
|
+
'<p>Everyone has 25 days of leave a year. Ask your manager at least two weeks ahead; approvals come by e-mail.</p>', '{}'),
|
|
36
|
+
(50, 'Help', 'static', 'inline_drawer',
|
|
37
|
+
'<p>This page shows the region templates: hero, title bar, buttons container, inline dialog and drawer, carousel and wizard.</p>', '{}'),
|
|
38
|
+
(60, 'Highlights', 'display_selector', 'plain', null, '{"style": "carousel", "interval": 8, "show_all": false}'),
|
|
39
|
+
(61, 'New colleagues', 'static', 'standard', '<p>Three people joined the Research department this month.</p>', '{"display_selector": true}'),
|
|
40
|
+
(62, 'Anniversaries', 'static', 'standard', '<p>KING celebrates 45 years with the company.</p>', '{"display_selector": true}'),
|
|
41
|
+
(63, 'Open positions', 'static', 'standard', '<p>Two analysts and a clerk: see the intranet.</p>', '{"display_selector": true}'),
|
|
42
|
+
(70, 'Onboarding steps', 'list', 'plain', null, '{"list": "HR_ONBOARDING_STEPS", "template": "wizard_progress"}'),
|
|
43
|
+
(80, 'Personal details', 'static', 'wizard', '<p>Step 1: check your name, address and bank account in My account.</p>', '{}')
|
|
44
|
+
) as r (seq, title, type, template, source, config)
|
|
45
|
+
where a.alias = 'hr' and p.page_no = 44;
|
|
46
|
+
|
|
47
|
+
insert into meta.button (page_id, region_id, seq, name, label, action, hot, target_page)
|
|
48
|
+
select p.id, (select r.id from meta.region r where r.page_id = p.id and r.title = b.region), b.seq, b.name, b.label, b.action, b.hot, b.target
|
|
49
|
+
from meta.page p join meta.app a on a.id = p.app_id, (values
|
|
50
|
+
('Welcome to HR', 10, 'EMPLOYEES', 'See the employees', 'redirect', true, 2),
|
|
51
|
+
('Employees this month', 20, 'POLICY', 'Leave policy', 'da', false, null),
|
|
52
|
+
('Actions', 30, 'HELP', 'Help', 'da', false, null)
|
|
53
|
+
) as b (region, seq, name, label, action, hot, target)
|
|
54
|
+
where a.alias = 'hr' and p.page_no = 44;
|
|
55
|
+
|
|
56
|
+
insert into meta.dynamic_action (page_id, seq, name, event, trigger_element, action, affected_region_id)
|
|
57
|
+
select p.id, d.seq, d.name, 'click', d.button, 'open_region', (select r.id from meta.region r where r.page_id = p.id and r.title = d.region)
|
|
58
|
+
from meta.page p join meta.app a on a.id = p.app_id, (values
|
|
59
|
+
(10, 'Open the leave policy', 'POLICY', 'Leave policy'),
|
|
60
|
+
(20, 'Open the help', 'HELP', 'Help')
|
|
61
|
+
) as d (seq, name, button, region)
|
|
62
|
+
where a.alias = 'hr' and p.page_no = 44;
|
|
63
|
+
|
|
64
|
+
insert into meta.nav_entry (app_id, parent_id, seq, label, icon, target_page)
|
|
65
|
+
select a.id, n.id, 50, 'Region templates', 'layers', 44
|
|
66
|
+
from meta.app a join meta.nav_entry n on n.app_id = a.id and n.label = 'Administration' and n.parent_id is null
|
|
67
|
+
where a.alias = 'hr';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- HR example, part 55: page groups (migration 089;
|
|
3
|
+
-- docs/guide/03-builder.md "Page groups")
|
|
4
|
+
--
|
|
5
|
+
-- The HR pages in four groups, shown and filtered in the App Builder's
|
|
6
|
+
-- page list (Shared Components → Page groups).
|
|
7
|
+
-- =====================================================================
|
|
8
|
+
insert into meta.page_group (app_id, name, description)
|
|
9
|
+
select a.id, g.name, g.description
|
|
10
|
+
from meta.app a, (values
|
|
11
|
+
('Employees', 'Employees, departments and their reviews'),
|
|
12
|
+
('Leave', 'Leave requests, the calendar and the planner'),
|
|
13
|
+
('Administration', 'Pages for administrators'),
|
|
14
|
+
('Showcase', 'Pages that show one feature each')
|
|
15
|
+
) as g (name, description)
|
|
16
|
+
where a.alias = 'hr';
|
|
17
|
+
|
|
18
|
+
update meta.page p set page_group = g.name
|
|
19
|
+
from meta.app a, (values
|
|
20
|
+
('Employees', array[2, 3, 4, 5, 8, 11, 19, 20, 27]),
|
|
21
|
+
('Leave', array[6, 7, 12, 22, 24]),
|
|
22
|
+
('Administration', array[9, 10, 43, 44])
|
|
23
|
+
) as g (name, pages)
|
|
24
|
+
where a.alias = 'hr' and p.app_id = a.id and p.page_no = any (g.pages);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pgkiln",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "Open source low-code, SQL-driven application builder for PostgreSQL, inspired by Oracle APEX",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"postgresql",
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"ldapts": "^9.2.0",
|
|
74
74
|
"leaflet": "^1.9.4",
|
|
75
75
|
"lucide-static": "^1.52.0",
|
|
76
|
+
"nodemailer": "^10.0.12",
|
|
76
77
|
"openai": "^7.28.0",
|
|
77
78
|
"pdfkit": "^0.20.2",
|
|
78
79
|
"pg": "^8.23.0",
|
|
@@ -81,6 +82,7 @@
|
|
|
81
82
|
},
|
|
82
83
|
"devDependencies": {
|
|
83
84
|
"@types/node": "^26.6.3",
|
|
85
|
+
"@types/nodemailer": "^8.0.2",
|
|
84
86
|
"@types/pdfkit": "^0.17.6",
|
|
85
87
|
"@types/pg": "^8.23.1",
|
|
86
88
|
"axe-core": "^4.14.0",
|
package/public/app.css
CHANGED
|
@@ -254,7 +254,43 @@ details.menu > summary::-webkit-details-marker { display: none; }
|
|
|
254
254
|
|
|
255
255
|
/* ---------- regions ---------- */
|
|
256
256
|
.region { min-width: 0; }
|
|
257
|
-
.region-standard, .region-collapsible { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); }
|
|
257
|
+
.region-standard, .region-collapsible, .region-wizard, .region-inline_dialog, .region-inline_drawer { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); }
|
|
258
|
+
/* (088) more region templates */
|
|
259
|
+
.region-hero { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 1rem 1.5rem; padding: 2rem 1.75rem; border-radius: var(--radius);
|
|
260
|
+
background: var(--accent-soft); color: var(--text); }
|
|
261
|
+
.region-hero-icon .icon { width: 3rem; height: 3rem; color: var(--accent); }
|
|
262
|
+
.region-hero-text h2 { font-size: 1.75rem; margin: 0 0 0.35rem; }
|
|
263
|
+
.region-hero-buttons, .region-title-buttons, .region-buttons { display: flex; flex-wrap: wrap; gap: 0.5rem; }
|
|
264
|
+
@media (max-width: 640px) { .region-hero { grid-template-columns: 1fr; padding: 1.25rem 1rem; } }
|
|
265
|
+
.region-title-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.5rem 1rem; padding: 0.25rem 0; border-bottom: 1px solid var(--border); margin-bottom: 0.75rem; }
|
|
266
|
+
.region-title-bar h2 { margin: 0; font-size: 1.15rem; }
|
|
267
|
+
.region-buttons_container .region-buttons { justify-content: flex-end; }
|
|
268
|
+
.region-wizard { max-width: 640px; width: 100%; margin-left: auto; margin-right: auto; }
|
|
269
|
+
dialog[data-inline-region] { padding: 0; max-width: min(640px, calc(100vw - 2rem)); width: 100%; color: var(--text); }
|
|
270
|
+
dialog[data-inline-region]::backdrop { background: rgb(0 0 0 / 45%); }
|
|
271
|
+
dialog[data-inline-region] .region-header { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
|
|
272
|
+
dialog.region-inline_drawer { margin: 0 0 0 auto; height: 100vh; max-height: 100vh; max-width: min(480px, 100vw); border-radius: 0; }
|
|
273
|
+
/* without JavaScript: a link to #R<id> shows it */
|
|
274
|
+
dialog[data-inline-region]:target { display: block; position: fixed; inset: 2rem 0 auto; margin: 0 auto; z-index: 1000; }
|
|
275
|
+
dialog.region-inline_drawer:target { inset: 0 0 0 auto; }
|
|
276
|
+
/* (088) carousel (display selector style) and wizard progress (list template) */
|
|
277
|
+
.rds-carousel { display: flex; align-items: center; justify-content: center; gap: 0.75rem; }
|
|
278
|
+
.rds-carousel .rds-list { gap: 0.4rem; }
|
|
279
|
+
.rds-carousel .rds-tab { font-size: 0; width: 0.75rem; height: 0.75rem; padding: 0; border-radius: 50%; background: var(--border); min-height: 0; }
|
|
280
|
+
.rds-carousel .rds-tab.is-current { background: var(--accent); }
|
|
281
|
+
.rds-carousel .rds-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
282
|
+
.wizard-progress ol { list-style: none; margin: 0 0 1rem; padding: 0; display: flex; flex-wrap: wrap; gap: 0.5rem 0; counter-reset: none; }
|
|
283
|
+
.wizard-step { flex: 1 1 0; min-width: 6rem; display: flex; flex-direction: column; align-items: center; text-align: center; gap: 0.3rem; position: relative; color: var(--muted); font-size: 0.85rem; }
|
|
284
|
+
.wizard-step::before { content: ""; position: absolute; top: 0.95rem; left: -50%; right: 50%; border-top: 2px solid var(--border); z-index: 0; }
|
|
285
|
+
.wizard-step:first-child::before { display: none; }
|
|
286
|
+
.wizard-step a { color: inherit; text-decoration: none; display: contents; }
|
|
287
|
+
.wizard-step-no { position: relative; z-index: 1; display: grid; place-items: center; width: 1.9rem; height: 1.9rem; border-radius: 50%; border: 2px solid var(--border); background: var(--surface); font-weight: 600; }
|
|
288
|
+
.wizard-step-no .icon { width: 1rem; height: 1rem; }
|
|
289
|
+
.wizard-step.is-done, .wizard-step.is-current { color: var(--text); }
|
|
290
|
+
.wizard-step.is-done .wizard-step-no { border-color: var(--accent); color: var(--accent); }
|
|
291
|
+
.wizard-step.is-done::before, .wizard-step.is-current::before { border-top-color: var(--accent); }
|
|
292
|
+
.wizard-step.is-current .wizard-step-no { background: var(--accent); border-color: var(--accent); color: var(--accent-text, #fff); }
|
|
293
|
+
.wizard-step.is-current .wizard-step-label { font-weight: 600; }
|
|
258
294
|
.region-header { display: flex; justify-content: space-between; align-items: center; gap: 1rem; min-height: 50px; padding: 0.6rem 1rem; border-bottom: 1px solid var(--border); }
|
|
259
295
|
.region-collapsible summary.region-header { cursor: pointer; list-style: none; }
|
|
260
296
|
.region-collapsible summary.region-header::-webkit-details-marker { display: none; }
|
|
@@ -265,6 +301,9 @@ details.menu > summary::-webkit-details-marker { display: none; }
|
|
|
265
301
|
.region-toolbar { display: flex; justify-content: flex-end; margin-bottom: 0.75rem; }
|
|
266
302
|
.region-static p:first-child { margin-top: 0; }
|
|
267
303
|
.region-static p:last-child { margin-bottom: 0; }
|
|
304
|
+
/* (078) URL region: the framed page fills the region's width; its height is a class per region (PageCss) */
|
|
305
|
+
.url-frame { display: block; width: 100%; border: 1px solid var(--border, #d0d7de); border-radius: 6px; background: var(--surface, #fff); }
|
|
306
|
+
.url-open { margin: 0.5rem 0 0; font-size: 0.875rem; }
|
|
268
307
|
.columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr)); gap: 1rem; margin-top: 1rem; }
|
|
269
308
|
.columns .region { margin: 0; }
|
|
270
309
|
|
|
@@ -807,6 +846,14 @@ fieldset.prop-group > legend { font-size: 0.75rem; text-transform: uppercase; le
|
|
|
807
846
|
.grid-table tr.deleted td { opacity: 0.5; text-decoration: line-through; }
|
|
808
847
|
.grid-table tr.grid-new { background: color-mix(in srgb, var(--accent-soft) 45%, transparent); }
|
|
809
848
|
.grid-table tr.dirty td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
|
|
849
|
+
/* the single row view: one row as a form */
|
|
850
|
+
.grid-record { padding: 0.9rem 1rem; border: 1px solid var(--border); border-radius: var(--radius, 8px); display: grid; gap: 0.9rem; }
|
|
851
|
+
.grid-record .grid-field { display: grid; gap: 0.3rem; align-content: start; }
|
|
852
|
+
.grid-record .grid-value { min-height: 1.5rem; overflow-wrap: anywhere; }
|
|
853
|
+
.grid-record.grid-new { background: color-mix(in srgb, var(--accent-soft) 45%, transparent); }
|
|
854
|
+
.grid-record.dirty { box-shadow: inset 3px 0 0 var(--accent); }
|
|
855
|
+
.grid-record.deleted .form-grid { opacity: 0.5; text-decoration: line-through; }
|
|
856
|
+
.grid-record-nav { justify-content: center; }
|
|
810
857
|
.js .grid-template { display: none; }
|
|
811
858
|
.grid-table .icon { width: 14px; height: 14px; }
|
|
812
859
|
/* columns a user hid stay in the form (their values still post) */
|
|
@@ -1003,9 +1050,19 @@ input[type=color] { width: 4rem; min-height: 2.4rem; padding: 0.15rem; }
|
|
|
1003
1050
|
/* single sign-on buttons on the login page */
|
|
1004
1051
|
.sso-buttons { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
1005
1052
|
.sso-buttons .btn { padding: 0.6rem; }
|
|
1006
|
-
.login-card .or { display: flex; align-items: center; gap: 0.75rem; color: var(--muted); font-size: 0.85rem; }
|
|
1007
|
-
.login-card .or::before, .login-card .or::after { content: ""; flex: 1; border-top: 1px solid var(--border); }
|
|
1053
|
+
.login-card .or, .login-region .or { display: flex; align-items: center; gap: 0.75rem; color: var(--muted); font-size: 0.85rem; }
|
|
1054
|
+
.login-card .or::before, .login-card .or::after, .login-region .or::before, .login-region .or::after { content: ""; flex: 1; border-top: 1px solid var(--border); }
|
|
1008
1055
|
.login-form { display: flex; flex-direction: column; gap: 1rem; }
|
|
1056
|
+
.login-forgot { align-self: center; font-size: 0.9rem; }
|
|
1057
|
+
/* (087) minimal pages (no header and navigation) and an application's own login page */
|
|
1058
|
+
.t-minimal-main { width: 100%; max-width: 1100px; margin: 0 auto; padding: 1.5rem 1rem; }
|
|
1059
|
+
.login-body .t-minimal-main { max-width: 420px; padding: 1rem; }
|
|
1060
|
+
.login-region { display: flex; flex-direction: column; gap: 1rem; }
|
|
1061
|
+
.login-region .btn { padding: 0.6rem; }
|
|
1062
|
+
.t-minimal .t-titlebar { background: none; border: 0; padding: 0 0 1rem; }
|
|
1063
|
+
.t-minimal .t-content { padding: 0; max-width: none; }
|
|
1064
|
+
/* on the sign-in backdrop the title sits on the header colour, like the built-in page's card title */
|
|
1065
|
+
.login-body .t-titlebar h1 { color: var(--header-text); text-align: center; }
|
|
1009
1066
|
|
|
1010
1067
|
/* ---------- preferences: theme switch, language picker ---------- */
|
|
1011
1068
|
.segmented { display: inline-flex; border: 1px solid var(--border); border-radius: 999px; overflow: hidden; margin-top: 0.35rem; }
|
|
@@ -1416,6 +1473,12 @@ td.tc-cell { overflow-wrap: anywhere; }
|
|
|
1416
1473
|
|
|
1417
1474
|
.qr { display: flex; flex-direction: column; align-items: flex-start; gap: 0.3rem; padding: 0.3rem 0; }
|
|
1418
1475
|
.qr-code { max-width: 100%; height: auto; border-radius: 4px; }
|
|
1476
|
+
/* (086) display image and address items */
|
|
1477
|
+
.display-image { padding: 0.3rem 0; }
|
|
1478
|
+
.display-image img { max-width: 100%; height: auto; border-radius: 4px; }
|
|
1479
|
+
.geocode-places { list-style: none; margin: 0.3rem 0 0; padding: 0.3rem 0; border: 1px solid var(--border); border-radius: 4px; background: var(--surface); }
|
|
1480
|
+
.geocode-places li { padding: 0.15rem 0.6rem; }
|
|
1481
|
+
.geocode-places .link-button { text-align: start; }
|
|
1419
1482
|
|
|
1420
1483
|
/* ---------- lists (list regions, navigation bar) ---------- */
|
|
1421
1484
|
.list-links { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.1rem; }
|
|
@@ -1550,3 +1613,59 @@ dialog.crop-dialog::backdrop { background: rgb(15 20 30 / 55%); }
|
|
|
1550
1613
|
.crop-handle { position: absolute; right: -8px; bottom: -8px; width: 16px; height: 16px; border-radius: 50%; background: #fff; border: 2px solid var(--accent); cursor: nwse-resize; }
|
|
1551
1614
|
.crop-stage { overflow: hidden; }
|
|
1552
1615
|
.crop-dialog .buttons { display: flex; justify-content: flex-end; gap: 0.5rem; }
|
|
1616
|
+
|
|
1617
|
+
/* (079) Developer toolbar: shown to developers with a builder session in the same browser.
|
|
1618
|
+
Fixed at the bottom left, collapsible (a <details>, so it works without JavaScript). */
|
|
1619
|
+
.devbar { position: fixed; left: 0.75rem; bottom: 0.75rem; z-index: 900; max-width: calc(100vw - 1.5rem);
|
|
1620
|
+
background: #1b2330; color: #fff; border-radius: var(--radius); box-shadow: 0 4px 16px rgb(0 0 0 / 25%); font-size: 0.8125rem; }
|
|
1621
|
+
.devbar summary { cursor: pointer; padding: 0.4rem 0.75rem; font-weight: 600; list-style: none; }
|
|
1622
|
+
.devbar summary::-webkit-details-marker { display: none; }
|
|
1623
|
+
.devbar summary::before { content: '▸ '; }
|
|
1624
|
+
.devbar[open] summary::before { content: '▾ '; }
|
|
1625
|
+
.devbar ul { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 0.75rem; list-style: none; margin: 0; padding: 0 0.75rem 0.5rem; }
|
|
1626
|
+
.devbar a { color: #9cc8ff; }
|
|
1627
|
+
.devbar a:focus-visible, .devbar button:focus-visible, .devbar select:focus-visible { outline: 2px solid #9cc8ff; outline-offset: 2px; }
|
|
1628
|
+
.devbar-debug { display: flex; align-items: center; gap: 0.35rem; margin: 0; }
|
|
1629
|
+
.devbar-debug select, .devbar-debug button { font: inherit; font-size: 0.8125rem; padding: 0.1rem 0.3rem; border-radius: 4px; border: 1px solid #5e6a7a; background: #2b3340; color: #fff; }
|
|
1630
|
+
.devbar-debug button { cursor: pointer; }
|
|
1631
|
+
.devbar-page .t-main { max-width: 72rem; margin: 0 auto; padding: 1rem; }
|
|
1632
|
+
.devbar-facts { display: grid; grid-template-columns: max-content 1fr; gap: 0.25rem 1rem; }
|
|
1633
|
+
.devbar-facts dd { margin: 0; overflow-wrap: anywhere; }
|
|
1634
|
+
.devbar-session code { overflow-wrap: anywhere; }
|
|
1635
|
+
@media print { .devbar { display: none; } }
|
|
1636
|
+
|
|
1637
|
+
/* (080) Search region: one field, results grouped per search configuration */
|
|
1638
|
+
.search-form { margin-bottom: 1rem; }
|
|
1639
|
+
.search-group + .search-group { margin-top: 1rem; }
|
|
1640
|
+
.search-group h3 { font-size: 0.875rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); margin: 0 0 0.4rem; }
|
|
1641
|
+
.search-hits { list-style: none; margin: 0; padding: 0; border: 1px solid var(--border); border-radius: var(--radius); }
|
|
1642
|
+
.search-hits li + li { border-top: 1px solid var(--border); }
|
|
1643
|
+
.search-hit { display: flex; align-items: flex-start; gap: 0.75rem; padding: 0.6rem 0.8rem; color: inherit; text-decoration: none; }
|
|
1644
|
+
a.search-hit:hover, a.search-hit:focus-visible { background: var(--accent-soft); }
|
|
1645
|
+
.search-icon { flex: none; color: var(--accent); }
|
|
1646
|
+
.search-text { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; flex: 1; overflow-wrap: anywhere; }
|
|
1647
|
+
.search-title { font-weight: 600; }
|
|
1648
|
+
.search-subtitle, .search-description { color: var(--muted); font-size: 0.875rem; }
|
|
1649
|
+
.search-hit mark { background: #fff3a3; color: inherit; border-radius: 2px; padding: 0 1px; }
|
|
1650
|
+
:root[data-theme="dark"] .search-hit mark { background: #6b5a00; }
|
|
1651
|
+
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .search-hit mark { background: #6b5a00; } }
|
|
1652
|
+
.search-hit .badge-pill { flex: none; }
|
|
1653
|
+
.search-more, .search-empty { margin: 0.4rem 0 0; }
|
|
1654
|
+
|
|
1655
|
+
/* (084) Feedback: the users' page and the builder's list */
|
|
1656
|
+
.feedback-form .choices { display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; }
|
|
1657
|
+
.feedback-list, .feedback-admin { list-style: none; margin: 0; padding: 0; }
|
|
1658
|
+
.feedback-list > li, .feedback-admin > li { border-top: 1px solid var(--border); padding: 0.75rem 0; }
|
|
1659
|
+
.feedback-list > li:first-child, .feedback-admin > li:first-child { border-top: 0; }
|
|
1660
|
+
.feedback-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; overflow-wrap: anywhere; }
|
|
1661
|
+
.feedback-body { white-space: pre-wrap; overflow-wrap: anywhere; margin: 0.4rem 0; }
|
|
1662
|
+
.feedback-response { background: var(--surface-2); border-radius: var(--radius); padding: 0.5rem 0.75rem; }
|
|
1663
|
+
.feedback-response p { white-space: pre-wrap; overflow-wrap: anywhere; margin: 0.25rem 0 0; }
|
|
1664
|
+
|
|
1665
|
+
/* (085) Session timeout warning */
|
|
1666
|
+
.session-dialog { max-width: min(28rem, calc(100vw - 2rem)); border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); color: var(--text); padding: 1.25rem; }
|
|
1667
|
+
.session-dialog::backdrop { background: rgb(0 0 0 / 40%); }
|
|
1668
|
+
.session-dialog p { margin: 0 0 1rem; }
|
|
1669
|
+
/* (sprint 59) Quick edit from the developer toolbar */
|
|
1670
|
+
.quick-edit .qe-target { outline: 2px dashed var(--accent); outline-offset: 2px; cursor: pointer; }
|
|
1671
|
+
.devbar-quick[aria-pressed="true"] { background: var(--accent); color: var(--accent-text); }
|