pgkiln 0.49.0 → 0.51.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/SECURITY.md +3 -0
- package/db/migrations/119_workspace_home.sql +30 -0
- package/db/migrations/120_datasets.sql +87 -0
- package/docs/apex-feature-parity.md +7 -6
- package/docs/guide/01-installation.md +5 -0
- package/docs/guide/03-builder.md +140 -11
- package/docs/guide/04-pages-and-regions.md +25 -1
- package/docs/guide/12-development.md +47 -11
- package/examples/hr/hr_63_datasets.sql +29 -0
- package/package.json +1 -1
- package/public/app.css +6 -0
- package/public/builder.css +95 -3
- package/public/code-editor.css +32 -79
- package/public/code-editor.js +58 -3
- package/public/data-modeler.css +17 -14
- package/public/worksheet.css +14 -7
- package/public/worksheet.js +119 -3
- package/src/app.ts +5 -1
- package/src/builder/changelog.ts +1 -1
- package/src/builder/data-modeler.ts +1 -1
- package/src/builder/datasets.ts +472 -0
- package/src/builder/db-users.ts +2 -2
- package/src/builder/home.ts +12 -7
- package/src/builder/instance.ts +2 -0
- package/src/builder/object-browser.ts +4 -4
- package/src/builder/querybuilder.ts +1 -1
- package/src/builder/quicksql.ts +1 -1
- package/src/builder/reporter.ts +52 -3
- package/src/builder/routes.ts +3 -3
- package/src/builder/scheduling.ts +2 -2
- package/src/builder/scripts.ts +1 -1
- package/src/builder/sql-charts.ts +1 -1
- package/src/builder/sql-monitor.ts +1 -1
- package/src/builder/subscriptions.ts +1 -0
- package/src/builder/table-editor.ts +2 -2
- package/src/builder/ui.ts +7 -5
- package/src/builder/worksheet.ts +171 -23
- package/src/builder/workspace-home.ts +283 -0
- package/src/builder/workspaces.ts +7 -4
- package/src/datasets.ts +367 -0
- package/src/i18n/ar.ts +16 -0
- package/src/i18n/cs.ts +16 -0
- package/src/i18n/da.ts +16 -0
- package/src/i18n/de.ts +16 -0
- package/src/i18n/el.ts +16 -0
- package/src/i18n/es.ts +16 -0
- package/src/i18n/fi.ts +16 -0
- package/src/i18n/fr.ts +16 -0
- package/src/i18n/he.ts +16 -0
- package/src/i18n/it.ts +16 -0
- package/src/i18n/ja.ts +16 -0
- package/src/i18n/ko.ts +16 -0
- package/src/i18n/nb.ts +16 -0
- package/src/i18n/pl.ts +16 -0
- package/src/i18n/pt.ts +16 -0
- package/src/i18n/ru.ts +16 -0
- package/src/i18n/sv.ts +16 -0
- package/src/i18n/tr.ts +16 -0
- package/src/i18n/uk.ts +16 -0
- package/src/i18n/zh.ts +16 -0
- package/src/i18n.ts +32 -0
- package/src/instance.ts +1 -0
- package/src/release-check.ts +139 -0
- package/src/reporting-app.ts +101 -0
- package/src/runtime/data-reporter.ts +199 -32
- package/src/session.ts +7 -0
- package/src/sqlworksheet.ts +9 -6
- package/src/subscriptions.ts +2 -1
- package/src/worksheet-txn.ts +270 -0
- package/src/workspace-export.ts +24 -4
|
@@ -21,6 +21,9 @@ src/
|
|
|
21
21
|
env.ts .env loader (imported first)
|
|
22
22
|
migrate.ts applies db/migrations and examples (scripts/migrate.ts, pgkiln migrate); logs each run that applies
|
|
23
23
|
instance.ts instance settings (meta.setting over environment variables, cached 30 s) and the configuration overview
|
|
24
|
+
release-check.ts (sprint 109) "pgkiln X is available": meta.setting 'release_check' over PGKILN_RELEASE_CHECK (off by default);
|
|
25
|
+
GitHub's releases/latest (PGKILN_RELEASE_URL in tests), 5 s timeout, 1 MB, cached a day per process,
|
|
26
|
+
never under node --test without PGKILN_RELEASE_URL; parseRelease keeps a semver and an https link only
|
|
24
27
|
or fails a file in public.pgkiln_install_log
|
|
25
28
|
appfiles.ts application export as one file per component (dir layout, static ids) and back
|
|
26
29
|
blueprint.ts blueprints (migration 063): checkBlueprint (names, types, references, pages, sample rows), tableOrder,
|
|
@@ -68,10 +71,22 @@ src/
|
|
|
68
71
|
xml.ts safe XML reader (no DTDs or entities, limits) and xmlTable(): rows from a repeating element (unit tested)
|
|
69
72
|
sqlscript.ts SQL scripts: splitScript() (statements, line numbers and offsets, psql commands), runScript() (stop/continue, transaction, savepoints)
|
|
70
73
|
sqlworksheet.ts (sprint 91) the worksheet's runs: pickStatements() (at the cursor, a selection, all), runStatements() (a log per
|
|
71
|
-
statement, psql command tags, notices, error line and column), explainStatement() (EXPLAIN / rolled-back ANALYZE
|
|
74
|
+
statement, psql command tags, notices, error line and column), explainStatement() (EXPLAIN / rolled-back ANALYZE;
|
|
75
|
+
a savepoint inside a worksheet transaction)
|
|
76
|
+
worksheet-txn.ts (sprint 107) the worksheet's transaction mode: an in-memory registry of pinned pg.Clients per (builder session,
|
|
77
|
+
worksheet tab), beginTxn() / endTxn() (caps per developer and server), accountRun() (statement count, DDL kept for
|
|
78
|
+
the Change Log until COMMIT), outsideTxnOnly(), sweepTxns() (idle timeout, ended sessions); onSessionEnd and
|
|
79
|
+
onClosePools roll back; WORKSHEET_TXN_IDLE_SECONDS / _MAX_PER_DEVELOPER / _MAX
|
|
72
80
|
datamodel.ts (sprint 98) Data Modeler diagrams: readModel() (catalog), boxSize(), fkPath() (crow's feet), autoLayout(), diagramSvg()
|
|
73
81
|
changelog.ts (sprint 106, migration 118) the change log: isSchemaDdl(), ddlTarget() (schema and object), changeName()
|
|
74
82
|
(add_bonus, create_index_x), recordChanges() (grouped per schema), Flyway / Liquibase / plain SQL writers
|
|
83
|
+
datasets.ts (sprint 110, migration 120) Data Reporter datasets: checkDataset() (names, joins to earlier tables, columns
|
|
84
|
+
and filters on its tables, whitelisted operators; with a catalog: existing objects), datasetSql() (quoted
|
|
85
|
+
identifiers, literal values), readSchemaCatalog() (tables, views, columns, foreign keys), proposeJoin() /
|
|
86
|
+
proposeDataset(), storeDataset(), appDatasets() (the runtime's read), pgkiln-dataset/1 files
|
|
87
|
+
reporting-app.ts (sprint 110) reporting applications: createReportingApp() (createApp, SELECT-only grants on the datasets'
|
|
88
|
+
objects, type reporting, roles viewer/editor/administrator as schemes EDITOR and ADMIN, page 1 a Data
|
|
89
|
+
Reporter), reportingApps(), reportingAppCount() (meta.reporting_app_count, for the workspace home)
|
|
75
90
|
tabledef.ts (sprint 95) a table as a definition: readTable(), checkTable(), tableSql() (CREATE, or ALTER from the catalog's version)
|
|
76
91
|
sqlformat.ts (sprint 93) formatSql(): the worksheet's Format (clauses per line, keyword case; strings and comments kept)
|
|
77
92
|
sqlplan.ts (sprint 92) EXPLAIN (FORMAT JSON) as a tree: parsePlan() (self time, slowest step, misestimates), planText()
|
|
@@ -180,7 +195,9 @@ src/
|
|
|
180
195
|
template-region.ts template_component region
|
|
181
196
|
tasks.ts task list region and task actions (approvals); (094) the vacation rule panel and route
|
|
182
197
|
data-reporter.ts Data Reporter region (migration 057): sources from the region's config, checkDef (offered columns, whitelists),
|
|
183
|
-
reportQuery/chartQuery, the list and editor (GET form), save/delete routes (meta.save_data_report)
|
|
198
|
+
reportQuery/chartQuery, the list and editor (GET form), save/delete routes (meta.save_data_report);
|
|
199
|
+
(sprint 110) dataset sources (resolveSources), edit_authz / admin_authz / publishing, pins (a preference),
|
|
200
|
+
publish / pin routes, the stable URL GET /a/:alias/reports/:id
|
|
184
201
|
form-designer.ts Form Designer region (migration 106): sources from the region's config, checkFormDef (offered columns, displays,
|
|
185
202
|
actions), forms as cards, a form's rows, a row's form (lost update hash), the designer; row/design/delete routes
|
|
186
203
|
workflows.ts workflow console region and its actions
|
|
@@ -212,7 +229,12 @@ src/
|
|
|
212
229
|
routes.ts sign-in, app home, settings, activity, developers, create (POST, via newapp.ts)/import (POST)
|
|
213
230
|
wizards.ts create page wizards: step 2 forms per page type (defaults from meta.wizard_defaults), POST → meta.generate_page
|
|
214
231
|
(the generators are PL/pgSQL in migration 047: catalog, defaults, form/cards/calendar/chart/map/facets/master-detail)
|
|
215
|
-
home.ts App Builder home (tiles, applications report/cards, Recent
|
|
232
|
+
home.ts App Builder home at /builder/apps and /builder (tiles, applications report/cards, Recent; /builder redirects
|
|
233
|
+
to the Workspace home when meta.workspace.builder_home = 'home'), Create, Import, Dashboard, Utilities
|
|
234
|
+
workspace-home.ts (sprint 109, migration 119) the Workspace home /builder/home, opened after sign-in: tool cards (Data Reporter
|
|
235
|
+
when GET /builder/reporter exists), workspace message edited in place (workspaceAdmin), Summary, Top
|
|
236
|
+
applications / users / developers over meta.developer.home_timeframe, System message (src/release-check.ts),
|
|
237
|
+
POST timeframe, message, release-check; releaseCheckRegion for Instance settings
|
|
216
238
|
newapp.ts creating an application (schema, role app_<alias>, Home page, first user): blank app, from a file, from tables
|
|
217
239
|
appfromfile.ts Create → From a file: upload (src/dataload.ts parsing), proposed table/columns, one transaction:
|
|
218
240
|
app + table + rows (loadRows) + pages (meta.generate_page: report and form, chart, facets)
|
|
@@ -249,7 +271,8 @@ src/
|
|
|
249
271
|
worksheet's Chart tab, SQL Workshop → Charts (meta.sql_chart, 110; read-only runs)
|
|
250
272
|
sql-tools.ts (sprint 96) SQL_TOOLS: every SQL Workshop tool with its group, tab and description (home, More menu, search)
|
|
251
273
|
worksheet.ts (sprint 91) the SQL Worksheet (Database Actions' SQL; /sql/commands too): navigator (pickSchema, objects with
|
|
252
|
-
columns, objectStatements), toolbar, output tabs, history (meta.sql_history, 108), plan trees, Run as an app's role, (sprint 93) Save / Rename / Download as SQL Scripts, bind and substitution values, POST /sql/worksheet/format; GET /sql/worksheet/navigator
|
|
274
|
+
columns, objectStatements), toolbar, output tabs, history (meta.sql_history, 108), plan trees, Run as an app's role, (sprint 93) Save / Rename / Download as SQL Scripts, bind and substitution values, POST /sql/worksheet/format; GET /sql/worksheet/navigator;
|
|
275
|
+
(sprint 107) transaction mode: the txn field, Commit / Rollback (mode=commit|rollback), txnBadge(), GET/POST /sql/worksheet/txn (JSON)
|
|
253
276
|
object-browser.ts (sprint 75) the Object Browser: objects by type, system objects hidden, a table in tabs, column actions as SQL to review
|
|
254
277
|
table-editor.ts (sprint 95) Table Properties: GET/POST /sql/objects/table (create, edit ?o=, ?template=), fromForm(), Preview / Apply
|
|
255
278
|
(one transaction) / Open in the worksheet
|
|
@@ -299,7 +322,11 @@ src/
|
|
|
299
322
|
pwa.ts Settings → Progressive Web App (icon upload)
|
|
300
323
|
themeroller.ts Settings → Theme Roller: style variants (add, edit, rename, delete), default style, users may choose
|
|
301
324
|
subscriptions.ts Shared Components → Subscriptions: subscribe, refresh, unsubscribe, subscribers and publish; the note under a component
|
|
302
|
-
reporter.ts page designer: Data Reporter settings (sources: table or view, offered columns, labels, masks; sharing
|
|
325
|
+
reporter.ts page designer: Data Reporter settings (sources: table or view, offered columns, labels, masks; sharing;
|
|
326
|
+
(sprint 110) a dataset as a source, who may edit, who may moderate, publishing)
|
|
327
|
+
datasets.ts (sprint 110) App Builder → Data Reporter (/builder/reporter): datasets (new: schema, tables; a dataset's
|
|
328
|
+
joins, columns, row filter, query and preview), files (download, export all, import), Create reporting
|
|
329
|
+
application
|
|
303
330
|
form-designer.ts page designer: Form Designer settings (designers' scheme; sources: key, actions, offered columns, labels, lists of values)
|
|
304
331
|
workingcopies.ts Working copies: list and create, compare with differences, merge or refresh with conflict choices, delete
|
|
305
332
|
rest.ts REST module endpoints list and curl example (Shared Components)
|
|
@@ -339,14 +366,18 @@ src/
|
|
|
339
366
|
public/
|
|
340
367
|
app.css theme (light/dark, responsive; --font, --font-size, --radius for style variants; template option classes to-*)
|
|
341
368
|
app.js client runtime: dialogs (dialog_closed actions), popup LOVs, dynamic actions (focus, classes, messages), grids (add/duplicate rows, master-detail refresh, move/resize columns, copy/paste of cell ranges), menus, lazy regions, maps (Leaflet layers, marker clusters, heat layer, layer legend, area/distance filter) (no inline JS)
|
|
342
|
-
code-editor.js, .css builder code editor: enhances <textarea data-code>, highlighting, suggestions (no dependencies)
|
|
369
|
+
code-editor.js, .css builder code editor: enhances <textarea data-code>, highlighting, suggestions (no dependencies);
|
|
370
|
+
(sprint 108) the current line, and read-only <pre data-code-view="sql|plpgsql|json|html"> highlighted;
|
|
371
|
+
its colours are the --ce-* tokens in builder.css
|
|
343
372
|
palette.js (sprint 96) the builder's search dialog (Ctrl+K), recently visited SQL Workshop pages, the Launchpad's tabs
|
|
344
373
|
sql-monitor.js (sprint 102) the SQL Monitor: reloads the sessions' live part every 5 / 15 / 60 s, Pause (localStorage pgkiln.sqlMonitor)
|
|
345
374
|
data-modeler.js, .css (sprint 98) the Data Modeler: drag boxes, undo/redo, save, zoom/fit, auto layout, mini-map, SVG/PNG export, print
|
|
346
375
|
worksheet.js, .css (sprint 91) the SQL Worksheet: run keys with the cursor, output tabs, navigator swap and search, drag and drop, splitters;
|
|
347
|
-
(92) grid sort, value dialog, history filter; (93) worksheet tabs in localStorage, Files, binds dialog, format, find, case
|
|
376
|
+
(92) grid sort, value dialog, history filter; (93) worksheet tabs in localStorage, Files, binds dialog, format, find, case;
|
|
377
|
+
(107) Autocommit / Transaction per tab, the badge from the server, beforeunload, rollback on closing a tab
|
|
348
378
|
builder.css builder only: IDE look (dark chrome, icon rail, panes); design tokens at the top (colour,
|
|
349
|
-
elevation, radius, type, motion; light and dark sets, sprint 105
|
|
379
|
+
elevation, radius, type, motion; light and dark sets, sprint 105; code editor --ce-*, sprint 108),
|
|
380
|
+
then the shared controls
|
|
350
381
|
builder.js builder only: tabs, component tree, property filter, drag and drop on the layout
|
|
351
382
|
builder-icons.svg builder only: icons of the rail, toolbar and designer (b-*); b-kiln is the brand mark (brandMark() in ui.ts)
|
|
352
383
|
test/
|
|
@@ -377,6 +408,10 @@ test/
|
|
|
377
408
|
object-actions.test.ts (sprint 94) object menus: Quick DDL, Drop with dependents, Add index / trigger / policy, the panel, not-found
|
|
378
409
|
data-modeler.test.ts (sprint 98) Data Modeler: "hr core" with its FK lines, saved positions, SVG, objects, DDL, dictionary, geometry
|
|
379
410
|
worksheet.test.ts (sprint 91) the SQL Worksheet: statement at the cursor, Run Script log, plans, notices, history, navigator, Run as
|
|
411
|
+
worksheet-txn.test.ts (sprint 107) transaction mode: the tab's transaction, Commit / Rollback, typed COMMIT, aborted, idle timeout, refusals,
|
|
412
|
+
Explain in it, Change Log at COMMIT, the JSON endpoint, sign-out
|
|
413
|
+
datasets.test.ts (sprint 110) datasets: checks and SQL, proposed joins, the builder's pages, files, the workspace export;
|
|
414
|
+
a reporting application in two clicks, an editor publishes, a viewer opens the stable URL, pins
|
|
380
415
|
quicksql.test.ts Quick SQL parser and DDL generator
|
|
381
416
|
xml.test.ts XML reader: rows, attributes, paths, refused DTDs and entities, limits
|
|
382
417
|
printing.test.ts report PDFs
|
|
@@ -422,9 +457,10 @@ test/
|
|
|
422
457
|
ai-mock.ts a local mock of the Claude Messages API (SSE stream) and OpenAI Chat Completions
|
|
423
458
|
helpers.ts a cookie-keeping test browser
|
|
424
459
|
e2e/responsive.test.ts browser tests at phone/tablet/desktop widths (Playwright)
|
|
425
|
-
e2e/code-editor.test.ts the code editor in a browser: highlighting, keys, suggestions, touch, screen readers
|
|
460
|
+
e2e/code-editor.test.ts the code editor in a browser: highlighting, keys, suggestions, touch, screen readers, syntax colours at AA in both themes (sprint 108)
|
|
426
461
|
e2e/data-modeler.test.ts (sprint 98) the Data Modeler in a browser: drag, save, reload, undo/redo, zoom, mini-map, exports, phone
|
|
427
|
-
e2e/worksheet.test.ts (sprint 91) the SQL Worksheet in a browser: run keys, tabs, navigator, drag and drop, splitters, phone, no JavaScript
|
|
462
|
+
e2e/worksheet.test.ts (sprint 91) the SQL Worksheet in a browser: run keys, tabs, navigator, drag and drop, splitters, phone, no JavaScript;
|
|
463
|
+
(107) transaction mode: badge, reload, Rollback, closing a tab, Autocommit switch, beforeunload
|
|
428
464
|
e2e/items.test.ts sprint 26 item types in a browser: editors, tags, stars, dates, reveal; without JavaScript
|
|
429
465
|
e2e/designer.test.ts page designer: panes per width, drag and drop, keyboard, Arrange buttons, builder theme
|
|
430
466
|
e2e/calendar.test.ts calendar drag and drop and create on click, view switching, chart drill-down
|
|
@@ -475,7 +511,7 @@ CI (`.github/workflows/ci.yml`) runs these jobs against PostgreSQL 17:
|
|
|
475
511
|
- **test**: typecheck and `npm test` on a fresh database;
|
|
476
512
|
- **e2e**: the browser tests, uploading the screenshots as an artifact; `test/e2e/accessibility.test.ts` runs
|
|
477
513
|
axe-core (WCAG 2.1 A and AA rules) on every page of the HR example (light, dark, Iris) and the builder's main pages
|
|
478
|
-
and allows no violation;
|
|
514
|
+
(in both builder themes, with the SQL Workshop and the wizards, since sprint 108) and allows no violation;
|
|
479
515
|
- **docker**: builds the image and starts the compose stack as a user would.
|
|
480
516
|
|
|
481
517
|
- **upgrade**: installs each earlier release (`v0.31.0` …) with its HR example (`git archive <tag> | tar -x`,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
-- =====================================================================
|
|
2
|
+
-- HR example, part 63: a Data Reporter dataset (docs/guide/04-pages-and-
|
|
3
|
+
-- regions.md, "Datasets and reporting applications")
|
|
4
|
+
--
|
|
5
|
+
-- The dataset "hr" of the Default workspace: employees (hr.emp) joined to
|
|
6
|
+
-- their department (hr.dept, a left join on the foreign key deptno), the
|
|
7
|
+
-- columns business users may see with labels, format masks and
|
|
8
|
+
-- descriptions, and a row filter: active employees only. In the builder,
|
|
9
|
+
-- App Builder → Data Reporter → Create reporting application makes a
|
|
10
|
+
-- reporting application on it in two clicks; datasets are installation
|
|
11
|
+
-- data (a file of their own, and the workspace export), not part of the
|
|
12
|
+
-- HR application's export.
|
|
13
|
+
-- =====================================================================
|
|
14
|
+
|
|
15
|
+
insert into meta.dataset (workspace_id, name, label, description, schema_name, tables, columns, filters, created_by)
|
|
16
|
+
values (1, 'hr', 'HR', 'Active employees with their department.', 'hr',
|
|
17
|
+
'[{"alias": "t1", "table": "emp"},
|
|
18
|
+
{"alias": "t2", "table": "dept", "join": {"to": "t1", "type": "left", "on": [{"column": "deptno", "ref": "deptno"}]}}]',
|
|
19
|
+
'[{"name": "empno", "table": "t1", "column": "empno", "label": "No."},
|
|
20
|
+
{"name": "ename", "table": "t1", "column": "ename", "label": "Name"},
|
|
21
|
+
{"name": "job", "table": "t1", "column": "job", "label": "Job"},
|
|
22
|
+
{"name": "hiredate", "table": "t1", "column": "hiredate", "label": "Hire date"},
|
|
23
|
+
{"name": "sal", "table": "t1", "column": "sal", "label": "Salary", "format": "FML999G990D00", "description": "Monthly salary"},
|
|
24
|
+
{"name": "comm", "table": "t1", "column": "comm", "label": "Commission", "format": "FML999G990D00"},
|
|
25
|
+
{"name": "dname", "table": "t2", "column": "dname", "label": "Department"},
|
|
26
|
+
{"name": "loc", "table": "t2", "column": "loc", "label": "Location", "description": "Where the department is"}]',
|
|
27
|
+
'[{"table": "t1", "column": "active", "op": "eq", "value": "true"}]',
|
|
28
|
+
'admin')
|
|
29
|
+
on conflict (workspace_id, name) do nothing;
|
package/package.json
CHANGED
package/public/app.css
CHANGED
|
@@ -2042,3 +2042,9 @@ a.search-hit:hover, a.search-hit:focus-visible { background: var(--accent-soft);
|
|
|
2042
2042
|
.t-footer .icon { width: 1rem; height: 1rem; }
|
|
2043
2043
|
@media print { .t-footer { display: none; } }
|
|
2044
2044
|
.list-card:hover { border-top-color: var(--card-color); }
|
|
2045
|
+
|
|
2046
|
+
/* (sprint 110) Data Reporter: a saved report's link and actions, column descriptions */
|
|
2047
|
+
.reporter-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem 0.6rem; margin: 0 0 0.75rem; min-width: 0; }
|
|
2048
|
+
.reporter-actions .buttons { margin: 0; }
|
|
2049
|
+
.reporter-link { flex: 1 1 14rem; min-width: 0; font-family: var(--mono, monospace); font-size: 0.85rem; }
|
|
2050
|
+
.reporter-coldesc { display: block; font-size: 0.78rem; }
|
package/public/builder.css
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
--accent ≥ 4.8:1 on every surface, --muted ≥ 5.6:1, in both themes
|
|
10
10
|
- elevation: --shadow-xs … --shadow-lg; radius: --radius-sm / --radius / --radius-lg
|
|
11
11
|
- type: --fs-xs … --fs-xl; motion: --ease and --dur (none under prefers-reduced-motion)
|
|
12
|
+
- code (sprint 108): --ce-* for public/code-editor.js and read-only <pre data-code-view>; every
|
|
13
|
+
syntax colour ≥ 4.5:1 on --ce-bg in both themes (test/e2e/code-editor.test.ts checks it)
|
|
12
14
|
The light set first, the dark set for data-theme="dark" and for "System" when
|
|
13
15
|
the OS prefers dark. The chrome (rail, toolbar, status bar) is dark in both
|
|
14
16
|
themes (the same colours), a shade deeper than the page in the dark one. */
|
|
@@ -59,10 +61,31 @@
|
|
|
59
61
|
--block: #ffffff;
|
|
60
62
|
--block-head: #f7f5f2;
|
|
61
63
|
--code-bg: #fbfaf8;
|
|
64
|
+
--field-border: var(--border-strong);
|
|
65
|
+
|
|
66
|
+
/* code editor (sprint 108, public/code-editor.js): every token colour is at least 4.5:1 on --ce-bg
|
|
67
|
+
(keywords take the ember; the others cool hues so they stay apart) */
|
|
62
68
|
--ce-bg: #fdfcfb;
|
|
63
69
|
--ce-gutter: #f5f3f0;
|
|
64
70
|
--ce-gutter-text: #6b645d;
|
|
65
|
-
--
|
|
71
|
+
--ce-gutter-active: #1c1917;
|
|
72
|
+
--ce-text: #1c1917;
|
|
73
|
+
--ce-kw: #b23a0a;
|
|
74
|
+
--ce-type: #0e6b66;
|
|
75
|
+
--ce-fn: #2457a6;
|
|
76
|
+
--ce-str: #3a7516;
|
|
77
|
+
--ce-num: #8a3ea0;
|
|
78
|
+
--ce-com: #736c66;
|
|
79
|
+
--ce-bind: #b02a5c;
|
|
80
|
+
--ce-qid: #80590a;
|
|
81
|
+
--ce-op: #5c5650;
|
|
82
|
+
--ce-tag: #2457a6;
|
|
83
|
+
--ce-attr: #b23a0a;
|
|
84
|
+
--ce-err: #c0262d;
|
|
85
|
+
--ce-match: rgb(234 88 12 / 16%);
|
|
86
|
+
--ce-match-ring: rgb(194 65 12 / 55%);
|
|
87
|
+
--ce-line: rgb(28 25 23 / 4.5%);
|
|
88
|
+
--ce-select: rgb(234 88 12 / 20%);
|
|
66
89
|
|
|
67
90
|
/* shape, elevation, type, motion */
|
|
68
91
|
--radius-sm: 6px;
|
|
@@ -91,7 +114,10 @@
|
|
|
91
114
|
--bg: #181715; --surface: #1f1d1b; --surface-2: #242220; --surface-3: #2d2b28; --text: #edebe8; --muted: #a8a19a;
|
|
92
115
|
--border: #312e2b; --border-strong: #423e39; --accent: #ff9152; --accent-text: #1a0b03; --accent-soft: #3a1f12; --ring: rgb(255 145 82 / 30%); --sel-bg: #4a2816; --sel-bar: #ff9152;
|
|
93
116
|
--gold: #ff9152; --canvas: #151412; --canvas-dot: #2a2825; --block: #222120; --block-head: #292725; --code-bg: #1a1917; --warn: #e0a740;
|
|
94
|
-
--ce-bg: #1a1917; --ce-gutter: #1f1d1b; --ce-gutter-text: #8f8880;
|
|
117
|
+
--ce-bg: #1a1917; --ce-gutter: #1f1d1b; --ce-gutter-text: #8f8880; --ce-gutter-active: #e9e6e3; --ce-text: #e9e6e3;
|
|
118
|
+
--ce-kw: #ff9152; --ce-type: #5cc8bd; --ce-fn: #8ab4ff; --ce-str: #a5d68a; --ce-num: #d4a8ff; --ce-com: #938b83; --ce-bind: #ff8fb8;
|
|
119
|
+
--ce-qid: #e6c26a; --ce-op: #b8b0a8; --ce-tag: #8ab4ff; --ce-attr: #ff9152; --ce-err: #ff7a70;
|
|
120
|
+
--ce-match: rgb(255 145 82 / 22%); --ce-match-ring: rgb(255 145 82 / 60%); --ce-line: rgb(255 255 255 / 4.5%); --ce-select: rgb(255 145 82 / 28%);
|
|
95
121
|
--shadow-xs: 0 1px 1px rgb(0 0 0 / 30%);
|
|
96
122
|
--shadow-sm: 0 1px 2px rgb(0 0 0 / 40%), inset 0 1px 0 rgb(255 255 255 / 3%);
|
|
97
123
|
--shadow-md: 0 4px 16px -2px rgb(0 0 0 / 50%), inset 0 1px 0 rgb(255 255 255 / 4%);
|
|
@@ -105,7 +131,10 @@
|
|
|
105
131
|
--bg: #181715; --surface: #1f1d1b; --surface-2: #242220; --surface-3: #2d2b28; --text: #edebe8; --muted: #a8a19a;
|
|
106
132
|
--border: #312e2b; --border-strong: #423e39; --accent: #ff9152; --accent-text: #1a0b03; --accent-soft: #3a1f12; --ring: rgb(255 145 82 / 30%); --sel-bg: #4a2816; --sel-bar: #ff9152;
|
|
107
133
|
--gold: #ff9152; --canvas: #151412; --canvas-dot: #2a2825; --block: #222120; --block-head: #292725; --code-bg: #1a1917; --warn: #e0a740;
|
|
108
|
-
--ce-bg: #1a1917; --ce-gutter: #1f1d1b; --ce-gutter-text: #8f8880;
|
|
134
|
+
--ce-bg: #1a1917; --ce-gutter: #1f1d1b; --ce-gutter-text: #8f8880; --ce-gutter-active: #e9e6e3; --ce-text: #e9e6e3;
|
|
135
|
+
--ce-kw: #ff9152; --ce-type: #5cc8bd; --ce-fn: #8ab4ff; --ce-str: #a5d68a; --ce-num: #d4a8ff; --ce-com: #938b83; --ce-bind: #ff8fb8;
|
|
136
|
+
--ce-qid: #e6c26a; --ce-op: #b8b0a8; --ce-tag: #8ab4ff; --ce-attr: #ff9152; --ce-err: #ff7a70;
|
|
137
|
+
--ce-match: rgb(255 145 82 / 22%); --ce-match-ring: rgb(255 145 82 / 60%); --ce-line: rgb(255 255 255 / 4.5%); --ce-select: rgb(255 145 82 / 28%);
|
|
109
138
|
--shadow-xs: 0 1px 1px rgb(0 0 0 / 30%);
|
|
110
139
|
--shadow-sm: 0 1px 2px rgb(0 0 0 / 40%), inset 0 1px 0 rgb(255 255 255 / 3%);
|
|
111
140
|
--shadow-md: 0 4px 16px -2px rgb(0 0 0 / 50%), inset 0 1px 0 rgb(255 255 255 / 4%);
|
|
@@ -1301,3 +1330,66 @@ pre.wrap-pre { white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
|
1301
1330
|
.rm-main { display: grid; gap: 1rem; min-width: 0; }
|
|
1302
1331
|
.rm-tree .tree code { font-size: 0.75rem; min-width: 3.4rem; }
|
|
1303
1332
|
@media (max-width: 760px) { .rm-layout { grid-template-columns: minmax(0, 1fr); } }
|
|
1333
|
+
|
|
1334
|
+
/* ------------------------------------------------------------ page polish (sprint 108): Object Browser, wizards, SQL Monitor */
|
|
1335
|
+
/* Object Browser: the tree in the builder's type scale, the selected object with the ember bar */
|
|
1336
|
+
.ob-folder .tree { font-size: var(--fs-base); }
|
|
1337
|
+
.ob-folder .tree a { padding-block: 0.28rem; }
|
|
1338
|
+
.ob-folder .tree a[aria-current="page"] { background: var(--sel-bg); color: var(--sel-text); box-shadow: inset 2px 0 0 var(--sel-bar); font-weight: 600; }
|
|
1339
|
+
.ob-folder > summary { border-radius: var(--radius-sm); }
|
|
1340
|
+
.ob-folder > summary:hover { background: var(--surface-3); }
|
|
1341
|
+
.ob-folder > summary .icon { color: var(--muted); }
|
|
1342
|
+
/* wizards: the icon picker looks like a select; a colour field is a swatch, not a bar */
|
|
1343
|
+
.ide .icon-picker > summary { border-color: var(--field-border); border-radius: var(--radius-sm); box-shadow: var(--shadow-xs); transition: border-color var(--dur) var(--ease); }
|
|
1344
|
+
.ide .icon-picker > summary:hover { border-color: color-mix(in srgb, var(--text) 22%, var(--border-strong)); }
|
|
1345
|
+
.ide .icon-picker > summary::after {
|
|
1346
|
+
content: ""; width: 14px; height: 14px; margin-left: 0.15rem;
|
|
1347
|
+
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23857d75' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m7 10 5 5 5-5'/%3E%3C/svg%3E") center / 14px no-repeat;
|
|
1348
|
+
}
|
|
1349
|
+
.ide .icon-picker[open] > summary { border-color: var(--accent); box-shadow: 0 0 0 3px var(--ring); }
|
|
1350
|
+
.ide .icon-picker-panel { border-color: var(--border-strong); border-radius: var(--radius); box-shadow: var(--shadow-md); background: var(--surface); }
|
|
1351
|
+
.ide .field input[type=color]:not(.swatch) {
|
|
1352
|
+
width: 4.5rem; height: 34px; min-height: 0; padding: 3px; cursor: pointer;
|
|
1353
|
+
border: 1px solid var(--field-border); border-radius: var(--radius-sm); background: var(--surface); box-shadow: var(--shadow-xs);
|
|
1354
|
+
}
|
|
1355
|
+
.ide .field input[type=color]::-webkit-color-swatch-wrapper { padding: 0; }
|
|
1356
|
+
.ide .field input[type=color]::-webkit-color-swatch { border: 0; border-radius: 4px; }
|
|
1357
|
+
.ide .field input[type=color]::-moz-color-swatch { border: 0; border-radius: 4px; }
|
|
1358
|
+
/* SQL Monitor: the refresh controls on one row */
|
|
1359
|
+
.sm-toolbar:not([hidden]) { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin: 0.25rem 0 0.75rem; }
|
|
1360
|
+
.sm-toolbar select { width: auto; }
|
|
1361
|
+
.sm-toolbar [aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); }
|
|
1362
|
+
|
|
1363
|
+
/* (sprint 109, WS-1) the Workspace home (/builder/home) */
|
|
1364
|
+
.wh-title { margin: 0 0 14px; font-size: var(--fs-xl); overflow-wrap: anywhere; }
|
|
1365
|
+
.wh-summary h2, .wh-top-head h2 { margin: 0 0 10px; }
|
|
1366
|
+
.wh-stats a.stat { color: inherit; text-decoration: none; }
|
|
1367
|
+
.wh-stats a.stat:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
|
|
1368
|
+
.wh-stats a.stat span { color: var(--accent); }
|
|
1369
|
+
.wh-top-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px; margin-top: 8px; }
|
|
1370
|
+
.wh-top-head h2 { margin: 0; }
|
|
1371
|
+
.wh-timeframe { display: inline-flex; }
|
|
1372
|
+
.wh-top { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); gap: 16px; margin-top: 10px; }
|
|
1373
|
+
.wh-top > .region { margin: 0; min-width: 0; }
|
|
1374
|
+
.wh-message { margin-bottom: 16px; }
|
|
1375
|
+
.wh-message-text { margin: 0 0 8px; white-space: pre-line; overflow-wrap: anywhere; }
|
|
1376
|
+
.wh-edit > summary { list-style: none; display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
|
|
1377
|
+
.wh-edit > summary::-webkit-details-marker { display: none; }
|
|
1378
|
+
.wh-edit[open] > summary { margin-bottom: 10px; }
|
|
1379
|
+
.wh-release { display: flex; gap: 0.6rem; align-items: flex-start; }
|
|
1380
|
+
.wh-release p { margin: 0.2rem 0 0; overflow-wrap: anywhere; }
|
|
1381
|
+
.wh-release-form { margin-top: 10px; }
|
|
1382
|
+
@media (max-width: 640px) {
|
|
1383
|
+
.wh-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
|
|
1384
|
+
.wh-stats .stat { padding: 0.6rem 0.75rem; }
|
|
1385
|
+
.wh-stats .stat b { font-size: 1.2rem; }
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/* (sprint 110) App Builder → Data Reporter: datasets */
|
|
1389
|
+
.dataset-checks { display: flex; flex-direction: column; gap: 0.35rem; margin: 0.25rem 0 0.5rem; overflow-wrap: anywhere; }
|
|
1390
|
+
.dataset-filter { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-bottom: 0.4rem; }
|
|
1391
|
+
.dataset-filter > select, .dataset-filter > input { flex: 1 1 10rem; min-width: 0; }
|
|
1392
|
+
.dataset-columns input { min-width: 0; width: 100%; }
|
|
1393
|
+
.dataset-page pre.code { white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
1394
|
+
.dataset-schema { flex-wrap: wrap; }
|
|
1395
|
+
.dataset-schema > select { flex: 1 1 10rem; min-width: 0; }
|
package/public/code-editor.css
CHANGED
|
@@ -2,79 +2,23 @@
|
|
|
2
2
|
transparent and lies exactly over the highlighted copy (.ce-code), so both
|
|
3
3
|
must share font, size, line height, padding and tab size. */
|
|
4
4
|
:root {
|
|
5
|
-
--ce-bg
|
|
6
|
-
--ce-gutter: #f1f3f6;
|
|
7
|
-
--ce-gutter-text: #5d6876;
|
|
8
|
-
--ce-text: #1b2330;
|
|
9
|
-
--ce-kw: #8b2bb9;
|
|
10
|
-
--ce-type: #0b6e8a;
|
|
11
|
-
--ce-fn: #1d5fc4;
|
|
12
|
-
--ce-str: #1d7a3a;
|
|
13
|
-
--ce-num: #b35400;
|
|
14
|
-
--ce-com: #6b7685;
|
|
15
|
-
--ce-bind: #b0306a;
|
|
16
|
-
--ce-qid: #7a5b00;
|
|
17
|
-
--ce-tag: #1d5fc4;
|
|
18
|
-
--ce-attr: #8b2bb9;
|
|
19
|
-
--ce-err: #c23028;
|
|
20
|
-
--ce-match: rgb(11 99 197 / 18%);
|
|
21
|
-
--ce-select: rgb(11 99 197 / 22%);
|
|
5
|
+
/* the colours (--ce-bg, --ce-kw, …, --ce-line, --ce-select) are tokens in builder.css, light and dark */
|
|
22
6
|
--ce-font: 0.84rem;
|
|
23
7
|
--ce-lh: 20px; /* whole pixels: fractional line heights drift apart over thousands of lines */
|
|
24
8
|
}
|
|
25
|
-
@media (prefers-color-scheme: dark) {
|
|
26
|
-
:root:not([data-theme="light"]) {
|
|
27
|
-
--ce-bg: #12161d;
|
|
28
|
-
--ce-gutter: #171c24;
|
|
29
|
-
--ce-gutter-text: #8f99a8;
|
|
30
|
-
--ce-text: #e3e7ed;
|
|
31
|
-
--ce-kw: #d39bff;
|
|
32
|
-
--ce-type: #5fd0e6;
|
|
33
|
-
--ce-fn: #82b6ff;
|
|
34
|
-
--ce-str: #8fd694;
|
|
35
|
-
--ce-num: #ffb46b;
|
|
36
|
-
--ce-com: #8591a3;
|
|
37
|
-
--ce-bind: #ff8cc0;
|
|
38
|
-
--ce-qid: #e8c66a;
|
|
39
|
-
--ce-tag: #82b6ff;
|
|
40
|
-
--ce-attr: #d39bff;
|
|
41
|
-
--ce-err: #ff7a70;
|
|
42
|
-
--ce-match: rgb(105 164 255 / 25%);
|
|
43
|
-
--ce-select: rgb(105 164 255 / 30%);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
:root[data-theme="dark"] {
|
|
47
|
-
--ce-bg: #12161d;
|
|
48
|
-
--ce-gutter: #171c24;
|
|
49
|
-
--ce-gutter-text: #8f99a8;
|
|
50
|
-
--ce-text: #e3e7ed;
|
|
51
|
-
--ce-kw: #d39bff;
|
|
52
|
-
--ce-type: #5fd0e6;
|
|
53
|
-
--ce-fn: #82b6ff;
|
|
54
|
-
--ce-str: #8fd694;
|
|
55
|
-
--ce-num: #ffb46b;
|
|
56
|
-
--ce-com: #8591a3;
|
|
57
|
-
--ce-bind: #ff8cc0;
|
|
58
|
-
--ce-qid: #e8c66a;
|
|
59
|
-
--ce-tag: #82b6ff;
|
|
60
|
-
--ce-attr: #d39bff;
|
|
61
|
-
--ce-err: #ff7a70;
|
|
62
|
-
--ce-match: rgb(105 164 255 / 25%);
|
|
63
|
-
--ce-select: rgb(105 164 255 / 30%);
|
|
64
|
-
}
|
|
65
9
|
@media (pointer: coarse) {
|
|
66
10
|
:root { --ce-font: 16px; --ce-lh: 24px; } /* the same as other fields: no zoom on focus in iOS */
|
|
67
11
|
}
|
|
68
12
|
|
|
69
13
|
.ce {
|
|
70
14
|
position: relative; display: grid; grid-template-columns: auto minmax(0, 1fr);
|
|
71
|
-
min-width: 0; max-width: 100%; border: 1px solid var(--border); border-radius: 6px; background: var(--ce-bg);
|
|
15
|
+
min-width: 0; max-width: 100%; border: 1px solid var(--field-border, var(--border)); border-radius: var(--radius-sm, 6px); background: var(--ce-bg);
|
|
72
16
|
}
|
|
73
|
-
.ce:focus-within { border-color: var(--accent); box-shadow: 0 0 0
|
|
17
|
+
.ce:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--ring, transparent); }
|
|
74
18
|
.ce-gutter {
|
|
75
19
|
--ce-digits: 2;
|
|
76
20
|
width: calc(var(--ce-digits) * 1ch + 1.1rem); overflow: hidden; position: relative;
|
|
77
|
-
background: var(--ce-gutter); border-right: 1px solid var(--border); border-radius: 6px 0 0 0;
|
|
21
|
+
background: var(--ce-gutter); border-right: 1px solid var(--border); border-radius: var(--radius-sm, 6px) 0 0 0;
|
|
78
22
|
color: var(--ce-gutter-text); user-select: none;
|
|
79
23
|
}
|
|
80
24
|
.ce-gutter, .ce .ce-code, .ce textarea.ce-input {
|
|
@@ -82,6 +26,11 @@
|
|
|
82
26
|
font-variant-ligatures: none; letter-spacing: 0; font-kerning: none;
|
|
83
27
|
}
|
|
84
28
|
.ce-nums { position: absolute; top: 0; left: 0; right: 0; white-space: pre; text-align: right; padding: 0.5rem 0.45rem 0 0; will-change: transform; }
|
|
29
|
+
/* the caret's line (sprint 108): a band behind the text and its number in the gutter, while the editor has the focus */
|
|
30
|
+
.ce-cur, .ce-cur-num { position: absolute; left: 0; right: 0; top: 0; height: var(--ce-lh); will-change: transform; display: none; }
|
|
31
|
+
.ce-cur { background: var(--ce-line); }
|
|
32
|
+
.ce-cur-num { padding-right: 0.45rem; text-align: right; color: var(--ce-gutter-active); background: var(--ce-gutter); font-weight: 650; }
|
|
33
|
+
.ce:focus-within :is(.ce-cur, .ce-cur-num):not([hidden]) { display: block; }
|
|
85
34
|
.ce-main { position: relative; min-width: 0; }
|
|
86
35
|
.ce .ce-hl { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
|
|
87
36
|
.ce .ce-code {
|
|
@@ -99,29 +48,33 @@
|
|
|
99
48
|
.ce textarea.ce-input:focus-visible { outline: none; }
|
|
100
49
|
.ce textarea.sql-editor { min-height: 18rem; }
|
|
101
50
|
|
|
102
|
-
/* tokens */
|
|
103
|
-
.ce-code .t-kw { color: var(--ce-kw); font-weight: 600; }
|
|
104
|
-
.ce-code .t-type { color: var(--ce-type); }
|
|
105
|
-
.ce-code .t-fn { color: var(--ce-fn); }
|
|
106
|
-
.ce-code .t-str { color: var(--ce-str); }
|
|
107
|
-
.ce-code .t-key { color: var(--ce-fn); }
|
|
108
|
-
.ce-code .t-num { color: var(--ce-num); }
|
|
109
|
-
.ce-code .t-com { color: var(--ce-com); font-style: italic; }
|
|
110
|
-
.ce-code .t-bind, .ce-code .t-sub { color: var(--ce-bind); font-weight: 600; }
|
|
111
|
-
.ce-code .t-qid { color: var(--ce-qid); }
|
|
112
|
-
.ce-code .t-dol { color: var(--ce-str); font-weight: 600; }
|
|
113
|
-
.ce-code .t-tag { color: var(--ce-tag); }
|
|
114
|
-
.ce-code .t-attr { color: var(--ce-attr); }
|
|
115
|
-
.ce-code .t-ent { color: var(--ce-num); }
|
|
116
|
-
.ce-code .t-err { color: var(--ce-err); text-decoration: wavy underline; }
|
|
117
|
-
.ce-code
|
|
118
|
-
.ce-code .t-
|
|
51
|
+
/* tokens: in the editor and in read-only code (<pre data-code-view>, sprint 108) */
|
|
52
|
+
:is(.ce-code, .ce-view) .t-kw { color: var(--ce-kw); font-weight: 600; }
|
|
53
|
+
:is(.ce-code, .ce-view) .t-type { color: var(--ce-type); }
|
|
54
|
+
:is(.ce-code, .ce-view) .t-fn { color: var(--ce-fn); }
|
|
55
|
+
:is(.ce-code, .ce-view) .t-str { color: var(--ce-str); }
|
|
56
|
+
:is(.ce-code, .ce-view) .t-key { color: var(--ce-fn); }
|
|
57
|
+
:is(.ce-code, .ce-view) .t-num { color: var(--ce-num); }
|
|
58
|
+
:is(.ce-code, .ce-view) .t-com { color: var(--ce-com); font-style: italic; }
|
|
59
|
+
:is(.ce-code, .ce-view) .t-bind, :is(.ce-code, .ce-view) .t-sub { color: var(--ce-bind); font-weight: 600; }
|
|
60
|
+
:is(.ce-code, .ce-view) .t-qid { color: var(--ce-qid); }
|
|
61
|
+
:is(.ce-code, .ce-view) .t-dol { color: var(--ce-str); font-weight: 600; }
|
|
62
|
+
:is(.ce-code, .ce-view) .t-tag { color: var(--ce-tag); }
|
|
63
|
+
:is(.ce-code, .ce-view) .t-attr { color: var(--ce-attr); }
|
|
64
|
+
:is(.ce-code, .ce-view) .t-ent { color: var(--ce-num); }
|
|
65
|
+
:is(.ce-code, .ce-view) .t-err { color: var(--ce-err); text-decoration: wavy underline; }
|
|
66
|
+
:is(.ce-code, .ce-view) .t-op { color: var(--ce-op); }
|
|
67
|
+
:is(.ce-code, .ce-view) .t-pun, :is(.ce-code, .ce-view) .t-br, :is(.ce-code, .ce-view) .t-id { color: var(--ce-text); }
|
|
68
|
+
.ce-code .t-match { background: var(--ce-match); border-radius: 2px; outline: 1px solid var(--ce-match-ring); }
|
|
69
|
+
|
|
70
|
+
pre.ce-view { background: var(--ce-bg); color: var(--ce-text); border-color: var(--border); }
|
|
71
|
+
pre.ce-view::selection, pre.ce-view ::selection { background: var(--ce-select); }
|
|
119
72
|
|
|
120
73
|
/* status bar: language, hint, caret position, check */
|
|
121
74
|
.ce-bar {
|
|
122
75
|
grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: 0.2rem 0.75rem;
|
|
123
76
|
padding: 0.2rem 0.5rem; border-top: 1px solid var(--border); background: var(--ce-gutter);
|
|
124
|
-
border-radius: 0 0 6px 6px; font-size: 0.75rem; color: var(--muted); min-width: 0;
|
|
77
|
+
border-radius: 0 0 var(--radius-sm, 6px) var(--radius-sm, 6px); font-size: 0.75rem; color: var(--muted); min-width: 0;
|
|
125
78
|
}
|
|
126
79
|
.ce-lang { font-weight: 650; letter-spacing: 0.02em; }
|
|
127
80
|
.ce-hint { flex: 1 1 12rem; min-width: 0; }
|
|
@@ -138,7 +91,7 @@
|
|
|
138
91
|
position: absolute; z-index: 30; left: 0; top: 0; margin: 0; padding: 0.25rem 0; list-style: none;
|
|
139
92
|
width: max-content; min-width: 14rem; max-width: min(34rem, calc(100% - 0.5rem)); max-height: 15rem; overflow: auto;
|
|
140
93
|
background: var(--surface); color: var(--text); border: 1px solid var(--border); border-radius: 6px;
|
|
141
|
-
box-shadow: 0 8px 24px rgb(16 24 40 / 18%); font-size: 0.82rem;
|
|
94
|
+
box-shadow: var(--shadow-lg, 0 8px 24px rgb(16 24 40 / 18%)); font-size: 0.82rem;
|
|
142
95
|
}
|
|
143
96
|
.ce-opt { display: flex; align-items: baseline; gap: 0.75rem; padding: 0.2rem 0.6rem 0.2rem 0.4rem; cursor: pointer; white-space: nowrap; }
|
|
144
97
|
.ce-opt::before {
|
package/public/code-editor.js
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
// - "Check" plans the SQL like the Advisor (POST /builder/code/check)
|
|
16
16
|
// - screen readers keep the plain textarea; the list is announced through
|
|
17
17
|
// aria-activedescendant and a polite live region
|
|
18
|
+
// - (sprint 108) read-only code, <pre data-code-view="sql|plpgsql|json|html">, is
|
|
19
|
+
// highlighted with the same lexer and colours (DDL, generated SQL)
|
|
18
20
|
//
|
|
19
21
|
// Strict CSP: no inline styles; geometry is set through the CSSOM. Suggestions
|
|
20
22
|
// and highlighted text are built as text nodes, never parsed as HTML.
|
|
@@ -437,15 +439,20 @@
|
|
|
437
439
|
gutter.setAttribute('aria-hidden', 'true');
|
|
438
440
|
const nums = document.createElement('div');
|
|
439
441
|
nums.className = 'ce-nums';
|
|
440
|
-
gutter
|
|
442
|
+
// the caret's line: a band behind the text, its number over the gutter's
|
|
443
|
+
const curNum = document.createElement('div');
|
|
444
|
+
curNum.className = 'ce-cur-num';
|
|
445
|
+
gutter.append(nums, curNum);
|
|
441
446
|
const main = document.createElement('div');
|
|
442
447
|
main.className = 'ce-main';
|
|
443
448
|
const hl = document.createElement('div');
|
|
444
449
|
hl.className = 'ce-hl';
|
|
445
450
|
hl.setAttribute('aria-hidden', 'true');
|
|
451
|
+
const cur = document.createElement('div');
|
|
452
|
+
cur.className = 'ce-cur';
|
|
446
453
|
const inner = document.createElement('pre');
|
|
447
454
|
inner.className = 'ce-code';
|
|
448
|
-
hl.append(inner);
|
|
455
|
+
hl.append(cur, inner);
|
|
449
456
|
ta.parentNode.insertBefore(wrap, ta);
|
|
450
457
|
main.append(hl, ta);
|
|
451
458
|
wrap.append(gutter, main);
|
|
@@ -502,7 +509,7 @@
|
|
|
502
509
|
ta.setAttribute('aria-autocomplete', 'list');
|
|
503
510
|
ta.setAttribute('aria-haspopup', 'listbox');
|
|
504
511
|
ta.setAttribute('aria-controls', list.id);
|
|
505
|
-
Object.assign(this, { wrap, gutter, nums, main, hl, inner, list, bar, langEl: lang, hint, pos, msg, check, live, suggest });
|
|
512
|
+
Object.assign(this, { wrap, gutter, nums, cur, curNum, main, hl, inner, list, bar, langEl: lang, hint, pos, msg, check, live, suggest });
|
|
506
513
|
this.applyLang();
|
|
507
514
|
}
|
|
508
515
|
|
|
@@ -661,9 +668,27 @@
|
|
|
661
668
|
this.digits = digits;
|
|
662
669
|
this.gutter.style.setProperty('--ce-digits', String(Math.max(2, digits)));
|
|
663
670
|
}
|
|
671
|
+
this.placeLine();
|
|
664
672
|
if (this.pop) this.place();
|
|
665
673
|
}
|
|
666
674
|
|
|
675
|
+
/** The current-line band and number: on the caret's line; hidden while a selection spans lines. */
|
|
676
|
+
placeLine() {
|
|
677
|
+
const ta = this.ta;
|
|
678
|
+
const lines = this.lineStarts;
|
|
679
|
+
const a = lowerBound(lines, ta.selectionStart + 1);
|
|
680
|
+
const b = lowerBound(lines, ta.selectionEnd + 1);
|
|
681
|
+
const hide = a !== b;
|
|
682
|
+
this.cur.hidden = hide;
|
|
683
|
+
this.curNum.hidden = hide;
|
|
684
|
+
if (hide) return;
|
|
685
|
+
const { lh, padTop } = this.metrics();
|
|
686
|
+
const y = padTop + (a - 1) * lh - ta.scrollTop;
|
|
687
|
+
this.cur.style.transform = `translateY(${y}px)`;
|
|
688
|
+
this.curNum.style.transform = `translateY(${y}px)`;
|
|
689
|
+
this.curNum.textContent = String(a);
|
|
690
|
+
}
|
|
691
|
+
|
|
667
692
|
tokenAt(off) {
|
|
668
693
|
this.sync();
|
|
669
694
|
const { T, S, E } = this.toks;
|
|
@@ -703,6 +728,7 @@
|
|
|
703
728
|
this.sync();
|
|
704
729
|
if (this.pop && this.ta.selectionStart !== this.ta.selectionEnd) this.close();
|
|
705
730
|
if (this.updateMatch()) this.schedule(true);
|
|
731
|
+
else this.placeLine();
|
|
706
732
|
const c = this.ta.selectionStart;
|
|
707
733
|
const line = lowerBound(this.lineStarts, c + 1);
|
|
708
734
|
this.pos.textContent = `Ln ${line}, Col ${c - this.lineStarts[line - 1] + 1}`;
|
|
@@ -997,12 +1023,41 @@
|
|
|
997
1023
|
return lo;
|
|
998
1024
|
}
|
|
999
1025
|
|
|
1026
|
+
/** Highlight a read-only <pre data-code-view>: its text, as spans (text nodes only). Long ones stay plain. */
|
|
1027
|
+
function colorize(pre) {
|
|
1028
|
+
const lang = pre.dataset.codeView in LANG_LABEL ? pre.dataset.codeView : 'text';
|
|
1029
|
+
pre.classList.add('ce-view');
|
|
1030
|
+
const text = pre.textContent;
|
|
1031
|
+
if (lang === 'text' || text.length > 200000) return;
|
|
1032
|
+
const { T, S, E } = lex(lang, text);
|
|
1033
|
+
const frag = document.createDocumentFragment();
|
|
1034
|
+
let pos = 0;
|
|
1035
|
+
for (let k = 0; k < T.length; k++) {
|
|
1036
|
+
const st = Math.max(S[k], pos);
|
|
1037
|
+
if (E[k] <= st) continue;
|
|
1038
|
+
if (st > pos) frag.append(text.slice(pos, st));
|
|
1039
|
+
const span = document.createElement('span');
|
|
1040
|
+
span.className = `t-${T[k]}`;
|
|
1041
|
+
span.textContent = text.slice(st, E[k]);
|
|
1042
|
+
frag.append(span);
|
|
1043
|
+
pos = E[k];
|
|
1044
|
+
}
|
|
1045
|
+
if (pos < text.length) frag.append(text.slice(pos));
|
|
1046
|
+
pre.replaceChildren(frag);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1000
1049
|
const editors = new WeakMap();
|
|
1050
|
+
const viewed = new WeakSet();
|
|
1001
1051
|
function enhance(root) {
|
|
1002
1052
|
for (const ta of (root || document).querySelectorAll('textarea[data-code]')) {
|
|
1003
1053
|
if (editors.has(ta) || ta.closest('.ce')) continue;
|
|
1004
1054
|
editors.set(ta, new Editor(ta));
|
|
1005
1055
|
}
|
|
1056
|
+
for (const pre of (root || document).querySelectorAll('pre[data-code-view]')) {
|
|
1057
|
+
if (viewed.has(pre)) continue;
|
|
1058
|
+
viewed.add(pre);
|
|
1059
|
+
colorize(pre);
|
|
1060
|
+
}
|
|
1006
1061
|
}
|
|
1007
1062
|
window.pgkilnCodeEditor = { enhance, editorOf: (ta) => editors.get(ta) };
|
|
1008
1063
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => enhance());
|