@galadrim/galadmin 0.2.11 → 0.3.0-alpha.2.1
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/.agents/report/blockers.json +1 -0
- package/.agents/report/failures/2026-09-23-api-user-filter-contract.md +31 -0
- package/.agents/report/failures/2026-09-23-api-values-contract.md +25 -0
- package/.agents/report/failures/2026-10-01-cycle-02-create-callback-contract.md +17 -0
- package/.agents/report/failures/2026-10-01-cycle-06-delete-callback-id-contract.md +17 -0
- package/.agents/report/failures/2026-10-01-cycle-07-panel-delete-callback-id-contract.md +24 -0
- package/.agents/report/failures/2026-10-01-cycle-13-update-callback-id-contract.md +17 -0
- package/.agents/report/failures/2026-10-01-cycle-15-rich-text-upload-boundary-contract.md +24 -0
- package/.agents/report/failures/2026-10-03-cycle-06-record-import-parser-contract.md +11 -0
- package/.agents/report/failures/2026-10-03-cycle-07-panel-import-parser-contract.md +24 -0
- package/.agents/report/failures/2026-10-07-resume-cycle-02-api-assembly-contract.md +46 -0
- package/.agents/report/port/2026-09-23-api-config-enrichment.md +13 -0
- package/.agents/report/port/2026-09-23-api-file-upload.md +11 -0
- package/.agents/report/port/2026-09-23-api-query-authorization.md +14 -0
- package/.agents/report/port/2026-09-23-api-request-configuration.md +11 -0
- package/.agents/report/port/2026-09-23-database-manager.md +13 -0
- package/.agents/report/port/2026-10-01-api-adjacent-record-id.md +9 -0
- package/.agents/report/port/2026-10-01-cycle-01-api-config-route.md +9 -0
- package/.agents/report/port/2026-10-01-cycle-03-custom-action.md +9 -0
- package/.agents/report/port/2026-10-01-cycle-04-custom-component.md +7 -0
- package/.agents/report/port/2026-10-01-cycle-05-custom-query.md +7 -0
- package/.agents/report/port/2026-10-01-cycle-08-bulk-delete.md +9 -0
- package/.agents/report/port/2026-10-01-cycle-09-duplicate-record.md +9 -0
- package/.agents/report/port/2026-10-01-cycle-10-notification-badges.md +9 -0
- package/.agents/report/port/2026-10-01-cycle-11-record.md +7 -0
- package/.agents/report/port/2026-10-01-cycle-12-search-companies.md +11 -0
- package/.agents/report/port/2026-10-01-cycle-14-upload-file.md +7 -0
- package/.agents/report/port/2026-10-03-cycle-01-create-record.md +11 -0
- package/.agents/report/port/2026-10-03-cycle-02-delete-record.md +11 -0
- package/.agents/report/port/2026-10-03-cycle-03-panel-delete-record.md +11 -0
- package/.agents/report/port/2026-10-03-cycle-04-update-record.md +11 -0
- package/.agents/report/port/2026-10-03-cycle-05-api-values.md +11 -0
- package/.agents/report/port/2026-10-06-cycle-01-record-import.md +9 -0
- package/.agents/report/port/2026-10-06-cycle-03-rich-text-upload.md +7 -0
- package/.agents/report/port/2026-10-06-cycle-04-user-filters.md +11 -0
- package/.agents/report/port/2026-10-06-resume-cycle-01-panel-import.md +9 -0
- package/.agents/report/port/2026-10-07-cycle-01-api-assembly.md +16 -0
- package/.agents/report/port/2026-10-07-cycle-02-index-entry-point.md +13 -0
- package/.agents/report/remediation/2026-10-02-api-user-filter-remediation.md +71 -0
- package/.agents/report/remediation/2026-10-02-api-values-remediation.md +17 -0
- package/.agents/report/remediation/2026-10-03-callback-id-contract-remediation.md +36 -0
- package/.agents/report/remediation/2026-10-06-import-parser-remediation.md +41 -0
- package/.agents/report/remediation/2026-10-06-rich-text-upload-remediation.md +33 -0
- package/.agents/report/remediation/2026-10-07-api-assembly-remediation.md +32 -0
- package/.agents/skills/port-javascript-to-typescript/SKILL.md +42 -0
- package/.agents/skills/port-javascript-to-typescript/agents/openai.yaml +3 -0
- package/.agents/skills/port-javascript-to-typescript/references/blockers.md +31 -0
- package/.agents/skills/port-javascript-to-typescript/references/planning.md +43 -0
- package/.agents/skills/port-javascript-to-typescript/references/porting.md +9 -0
- package/.agents/skills/port-javascript-to-typescript/references/reporting.md +24 -0
- package/.agents/skills/port-javascript-to-typescript/references/rules.md +8 -0
- package/.agents/skills/port-javascript-to-typescript/references/selection.md +16 -0
- package/.agents/skills/port-javascript-to-typescript/references/tools.md +20 -0
- package/.agents/skills/port-javascript-to-typescript/scripts/check.sh +17 -0
- package/.agents/skills/port-javascript-to-typescript/scripts/graph.sh +30 -0
- package/.agents/skills/port-javascript-to-typescript/scripts/madge.json +12 -0
- package/.agents/skills/port-javascript-to-typescript/scripts/oxlint.json +14 -0
- package/.agents/skills/port-javascript-to-typescript/scripts/test-tools.cjs +76 -0
- package/.github/actions/version-bump/action.yml +41 -0
- package/.github/workflows/alpha.yml +53 -0
- package/.github/workflows/ci.yml +8 -9
- package/.github/workflows/main.yml +13 -14
- package/AGENTS.md +5 -0
- package/dist/api/authorization.d.ts +38 -0
- package/dist/api/authorization.js +74 -0
- package/dist/api/authorization.js.map +1 -0
- package/dist/api/config.d.ts +42 -0
- package/dist/api/config.js +105 -0
- package/dist/api/config.js.map +1 -0
- package/dist/api/filterUser.d.ts +2 -0
- package/dist/api/filterUser.js +24 -0
- package/dist/api/filterUser.js.map +1 -0
- package/dist/api/importRecords.d.ts +1 -0
- package/dist/api/importRecords.js +16 -0
- package/dist/api/importRecords.js.map +1 -0
- package/dist/api/queries.d.ts +48 -0
- package/dist/api/queries.js +139 -0
- package/dist/api/queries.js.map +1 -0
- package/dist/api/request.d.ts +29 -0
- package/dist/api/request.js +3 -0
- package/dist/api/request.js.map +1 -0
- package/dist/api/routes/adjacentRecordId.d.ts +10 -0
- package/dist/api/routes/adjacentRecordId.js +13 -0
- package/dist/api/routes/adjacentRecordId.js.map +1 -0
- package/dist/api/routes/config.d.ts +3 -0
- package/dist/api/routes/config.js +27 -0
- package/dist/api/routes/config.js.map +1 -0
- package/dist/api/routes/createRecord.d.ts +19 -0
- package/dist/api/routes/createRecord.js +93 -0
- package/dist/api/routes/createRecord.js.map +1 -0
- package/dist/api/routes/customAction.d.ts +17 -0
- package/dist/api/routes/customAction.js +35 -0
- package/dist/api/routes/customAction.js.map +1 -0
- package/dist/api/routes/customComponent.d.ts +3 -0
- package/dist/api/routes/customComponent.js +34 -0
- package/dist/api/routes/customComponent.js.map +1 -0
- package/dist/api/routes/customQuery.d.ts +7 -0
- package/dist/api/routes/customQuery.js +33 -0
- package/dist/api/routes/customQuery.js.map +1 -0
- package/dist/api/routes/deleteRecord.d.ts +9 -0
- package/dist/api/routes/deleteRecord.js +46 -0
- package/dist/api/routes/deleteRecord.js.map +1 -0
- package/dist/api/routes/deleteRecordFromPanel.d.ts +10 -0
- package/dist/api/routes/deleteRecordFromPanel.js +36 -0
- package/dist/api/routes/deleteRecordFromPanel.js.map +1 -0
- package/dist/api/routes/deleteRecords.d.ts +9 -0
- package/dist/api/routes/deleteRecords.js +12 -0
- package/dist/api/routes/deleteRecords.js.map +1 -0
- package/dist/api/routes/duplicateRecord.d.ts +17 -0
- package/dist/api/routes/duplicateRecord.js +55 -0
- package/dist/api/routes/duplicateRecord.js.map +1 -0
- package/dist/api/routes/foreignSelectRecords.d.ts +10 -0
- package/dist/api/routes/foreignSelectRecords.js +61 -0
- package/dist/api/routes/foreignSelectRecords.js.map +1 -0
- package/dist/api/routes/notificationBadges.d.ts +6 -0
- package/dist/api/routes/notificationBadges.js +31 -0
- package/dist/api/routes/notificationBadges.js.map +1 -0
- package/dist/api/routes/record.d.ts +7 -0
- package/dist/api/routes/record.js +32 -0
- package/dist/api/routes/record.js.map +1 -0
- package/dist/api/routes/records.d.ts +14 -0
- package/dist/api/routes/records.js +57 -0
- package/dist/api/routes/records.js.map +1 -0
- package/dist/api/routes/recordsExport.d.ts +13 -0
- package/dist/api/routes/recordsExport.js +67 -0
- package/dist/api/routes/recordsExport.js.map +1 -0
- package/dist/api/routes/recordsImport.d.ts +4 -0
- package/dist/api/routes/recordsImport.js +100 -0
- package/dist/api/routes/recordsImport.js.map +1 -0
- package/dist/api/routes/searchCompanies.d.ts +17 -0
- package/dist/api/routes/searchCompanies.js +72 -0
- package/dist/api/routes/searchCompanies.js.map +1 -0
- package/dist/api/routes/subrecordsImport.d.ts +4 -0
- package/dist/api/routes/subrecordsImport.js +94 -0
- package/dist/api/routes/subrecordsImport.js.map +1 -0
- package/dist/api/routes/updateRecord.d.ts +13 -0
- package/dist/api/routes/updateRecord.js +55 -0
- package/dist/api/routes/updateRecord.js.map +1 -0
- package/dist/api/routes/uploadFile.d.ts +3 -0
- package/dist/api/routes/uploadFile.js +13 -0
- package/dist/api/routes/uploadFile.js.map +1 -0
- package/dist/api/routes/uploadFileFromBetterRichTextInput.d.ts +3 -0
- package/dist/api/routes/uploadFileFromBetterRichTextInput.js +33 -0
- package/dist/api/routes/uploadFileFromBetterRichTextInput.js.map +1 -0
- package/dist/api/uploadFile.d.ts +4 -0
- package/dist/api/uploadFile.js +209 -0
- package/dist/api/uploadFile.js.map +1 -0
- package/dist/api/values.d.ts +6 -0
- package/dist/api/values.js +88 -0
- package/dist/api/values.js.map +1 -0
- package/dist/api.d.ts +8 -27
- package/dist/api.js +80 -1387
- package/dist/api.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.strict.tsbuildinfo +1 -0
- package/dist/types/config.d.ts +15 -5
- package/dist/types/config.js +4 -0
- package/dist/types/config.js.map +1 -1
- package/dist/types/database.d.ts +1 -0
- package/dist/types/database.js +3 -0
- package/dist/types/database.js.map +1 -0
- package/dist/utils/dbManager.d.ts +32 -7
- package/dist/utils/dbManager.js +59 -7
- package/dist/utils/dbManager.js.map +1 -1
- package/package.json +12 -3
- package/src/api/authorization.ts +129 -0
- package/src/api/config.ts +152 -0
- package/src/api/filterUser.ts +28 -0
- package/src/api/importRecords.ts +16 -0
- package/src/api/queries.ts +194 -0
- package/src/api/request.ts +31 -0
- package/src/api/routes/adjacentRecordId.ts +20 -0
- package/src/api/routes/config.ts +15 -0
- package/src/api/routes/createRecord.ts +131 -0
- package/src/api/routes/customAction.ts +60 -0
- package/src/api/routes/customComponent.ts +44 -0
- package/src/api/routes/customQuery.ts +53 -0
- package/src/api/routes/deleteRecord.ts +59 -0
- package/src/api/routes/deleteRecordFromPanel.ts +58 -0
- package/src/api/routes/deleteRecords.ts +28 -0
- package/src/api/routes/duplicateRecord.ts +73 -0
- package/src/api/routes/foreignSelectRecords.ts +80 -0
- package/src/api/routes/notificationBadges.ts +47 -0
- package/src/api/routes/record.ts +48 -0
- package/src/api/routes/records.ts +91 -0
- package/src/api/routes/recordsExport.ts +92 -0
- package/src/api/routes/recordsImport.ts +115 -0
- package/src/api/routes/searchCompanies.ts +96 -0
- package/src/api/routes/subrecordsImport.ts +114 -0
- package/src/api/routes/updateRecord.ts +74 -0
- package/src/api/routes/uploadFile.ts +16 -0
- package/src/api/routes/uploadFileFromBetterRichTextInput.ts +41 -0
- package/src/api/uploadFile.ts +249 -0
- package/src/api/values.ts +96 -0
- package/src/api.ts +94 -1580
- package/src/front/ckeditor5-build/node_modules/semver/bin/semver.js +0 -0
- package/src/index.ts +42 -25
- package/src/types/config.ts +15 -5
- package/src/types/database.ts +2 -0
- package/src/utils/dbManager.ts +99 -14
- package/tests/api-helpers.test.ts +59 -0
- package/tests/dbManager.test.ts +16 -1
- package/tests/helpers/fakeDb.ts +9 -7
- package/tests/integration/authorizationBoundary.integration.test.ts +57 -0
- package/tests/integration/configEnrichment.integration.test.ts +79 -0
- package/tests/integration/customComponentBoundary.integration.test.ts +58 -0
- package/tests/integration/dbManager.integration.test.ts +105 -3
- package/tests/integration/importParserChild.cjs +55 -0
- package/tests/integration/indexRuntime.integration.test.ts +22 -0
- package/tests/integration/indexRuntimeChild.cjs +110 -0
- package/tests/integration/queries.integration.test.ts +88 -0
- package/tests/integration/requestConfig.integration.test.ts +118 -0
- package/tests/integration/routes.authorization.integration.test.ts +1 -0
- package/tests/integration/routes.config.integration.test.ts +39 -1
- package/tests/integration/routes.crud.integration.test.ts +233 -1
- package/tests/integration/routes.custom.integration.test.ts +119 -0
- package/tests/integration/routes.foreign.integration.test.ts +3 -2
- package/tests/integration/routes.importExport.integration.test.ts +308 -6
- package/tests/integration/routes.integration.test.ts +4 -0
- package/tests/integration/routes.records.integration.test.ts +82 -6
- package/tests/integration/routes.upload.integration.test.ts +123 -0
- package/tests/integration/userFilters.integration.test.ts +225 -0
- package/tsconfig.json +1 -1
- package/tsconfig.strict.json +45 -0
- package/dist/front/ckeditor5-build/src/ckeditor.d.ts +0 -43
- package/dist/front/ckeditor5-build/src/ckeditor.js +0 -130
- package/dist/front/ckeditor5-build/src/ckeditor.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/front/ckeditor5-build/node_modules/.bin/semver +0 -12
- package/src/front/ckeditor5-build/node_modules/.bin/semver.cmd +0 -17
- package/src/front/ckeditor5-build/node_modules/.bin/semver.ps1 +0 -28
|
@@ -3,6 +3,7 @@ import * as os from "os";
|
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import * as request from "supertest";
|
|
5
5
|
import { generateUniqueIdForObject } from "../../src/api";
|
|
6
|
+
import { uploadFile } from "../../src/api/uploadFile";
|
|
6
7
|
import { buildAppForManager } from "../helpers/app";
|
|
7
8
|
import { baseConfig, fruitView } from "../helpers/fixtures";
|
|
8
9
|
import {
|
|
@@ -119,7 +120,49 @@ describeIntegration("upload routes against real databases", () => {
|
|
|
119
120
|
contentType: "image/png",
|
|
120
121
|
});
|
|
121
122
|
|
|
123
|
+
it.each([
|
|
124
|
+
{ s3Config: { host: "s3.example.com", accessKeyId: "k", secretAccessKey: "s", bucket: "b" } },
|
|
125
|
+
{ gcpConfig: { host: "storage.googleapis.com", bucket: "b" } },
|
|
126
|
+
{ publicPath: "/srv/public", sftpConfig: { host: "h", user: "u", password: "p" } },
|
|
127
|
+
{ publicPath: os.tmpdir(), subdirectory: "" },
|
|
128
|
+
])("rejects a missing file for storage config %j", async (storage) => {
|
|
129
|
+
const { app, colId } = appWithColumn(fileColumn(storage));
|
|
130
|
+
const res = await request(app)
|
|
131
|
+
.post("/api/uploadFile")
|
|
132
|
+
.query({ viewIndex: 0, columnId: colId })
|
|
133
|
+
.field("description", "no file")
|
|
134
|
+
.expect(400);
|
|
135
|
+
expect(res.text).toBe("Missing upload file");
|
|
136
|
+
expect(jest.requireMock("@aws-sdk/client-s3").S3Client).not.toHaveBeenCalled();
|
|
137
|
+
expect(jest.requireMock("@google-cloud/storage").Storage).not.toHaveBeenCalled();
|
|
138
|
+
expect(jest.requireMock("ssh2-sftp-client").__sftp.connect).not.toHaveBeenCalled();
|
|
139
|
+
});
|
|
140
|
+
|
|
122
141
|
describe("S3 backend", () => {
|
|
142
|
+
it.each(["view", "panel"])("finds %s upload columns and prefers view columns for duplicate IDs", async (location) => {
|
|
143
|
+
const sharedId = "upload-target";
|
|
144
|
+
const storage = (bucket: string) => ({
|
|
145
|
+
host: "s3.example.com", accessKeyId: "k", secretAccessKey: "s", bucket,
|
|
146
|
+
});
|
|
147
|
+
const mainColumn = fileColumn({
|
|
148
|
+
__id__: location === "view" ? sharedId : "different-view-column",
|
|
149
|
+
s3Config: storage("view-bucket"),
|
|
150
|
+
});
|
|
151
|
+
const panelColumn = fileColumn({ __id__: sharedId, s3Config: storage("panel-bucket") });
|
|
152
|
+
const instance = buildAppForManager(harness.manager, baseConfig({ views: [fruitView({
|
|
153
|
+
primaryId: "id", columns: [mainColumn],
|
|
154
|
+
recordViewPanels: [
|
|
155
|
+
{ type: "table", label: "No creation", query: "SELECT * FROM fruits" },
|
|
156
|
+
{ type: "table", label: "Creation", query: "SELECT * FROM fruits",
|
|
157
|
+
createConfig: { label: "Create", recordIdKey: "id", columns: [panelColumn] } },
|
|
158
|
+
],
|
|
159
|
+
})] }));
|
|
160
|
+
await s3Request(instance, sharedId).expect(200);
|
|
161
|
+
expect(jest.requireMock("@aws-sdk/client-s3").PutObjectCommand).toHaveBeenCalledWith(
|
|
162
|
+
expect.objectContaining({ Bucket: `${location}-bucket` }),
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
123
166
|
it("uploads with a public-read PutObjectCommand and returns the public URL", async () => {
|
|
124
167
|
const s3Mock = jest.requireMock("@aws-sdk/client-s3");
|
|
125
168
|
const column = fileColumn({
|
|
@@ -217,6 +260,28 @@ describeIntegration("upload routes against real databases", () => {
|
|
|
217
260
|
);
|
|
218
261
|
});
|
|
219
262
|
|
|
263
|
+
it.each([
|
|
264
|
+
["document", Buffer.from("text"), /^document-\d+\.undefined$/, "application/octet-stream"],
|
|
265
|
+
["broken.png", Buffer.from("not an image"), /^broken-\d+\.png$/, "image/png"],
|
|
266
|
+
["empty.txt", Buffer.alloc(0), /^empty-\d+\.txt$/, "text/plain"],
|
|
267
|
+
["photo.PNG", PNG_BUFFER, /^photo-\d+\.PNG$/, "image/png"],
|
|
268
|
+
])("preserves filename and image handling for %s", async (filename, content, expectedPath, contentType) => {
|
|
269
|
+
const { app, colId } = appWithColumn(fileColumn({
|
|
270
|
+
s3Config: { host: "s3.example.com", accessKeyId: "k", secretAccessKey: "s", bucket: "b" },
|
|
271
|
+
}));
|
|
272
|
+
const res = await request(app)
|
|
273
|
+
.post("/api/uploadFile")
|
|
274
|
+
.query({ viewIndex: 0, columnId: colId })
|
|
275
|
+
.attach("file", content, { filename })
|
|
276
|
+
.expect(200);
|
|
277
|
+
expect(res.body.originalFilename).toBe(filename);
|
|
278
|
+
expect(res.body.path).toMatch(expectedPath);
|
|
279
|
+
expect(res.body.aspectRatio).toBeNull();
|
|
280
|
+
expect(jest.requireMock("@aws-sdk/client-s3").PutObjectCommand).toHaveBeenCalledWith(
|
|
281
|
+
expect.objectContaining({ ContentType: contentType, Body: content }),
|
|
282
|
+
);
|
|
283
|
+
});
|
|
284
|
+
|
|
220
285
|
it("prefixes https:// to hosts missing a scheme", async () => {
|
|
221
286
|
const s3Mock = jest.requireMock("@aws-sdk/client-s3");
|
|
222
287
|
const column = fileColumn({
|
|
@@ -358,6 +423,18 @@ describeIntegration("upload routes against real databases", () => {
|
|
|
358
423
|
expect(res.body.aspectRatio).toBe(1); // PNG_BUFFER is a real 1x1 PNG header
|
|
359
424
|
});
|
|
360
425
|
|
|
426
|
+
it("prefers GCP when all remote storage settings are present", async () => {
|
|
427
|
+
const { app, colId } = appWithColumn(fileColumn({
|
|
428
|
+
s3Config: { host: "s3.example.com", accessKeyId: "k", secretAccessKey: "s", bucket: "s3" },
|
|
429
|
+
gcpConfig: { host: "storage.googleapis.com", bucket: "gcp" },
|
|
430
|
+
sftpConfig: { host: "sftp.example.com", user: "u", password: "p" },
|
|
431
|
+
}));
|
|
432
|
+
await s3Request(app, colId).expect(200);
|
|
433
|
+
expect(jest.requireMock("@google-cloud/storage").__bucket).toHaveBeenCalledWith("gcp");
|
|
434
|
+
expect(jest.requireMock("@aws-sdk/client-s3").S3Client).not.toHaveBeenCalled();
|
|
435
|
+
expect(jest.requireMock("ssh2-sftp-client").__sftp.connect).not.toHaveBeenCalled();
|
|
436
|
+
});
|
|
437
|
+
|
|
361
438
|
it("returns 500 when the upload fails", async () => {
|
|
362
439
|
const gcpMock = jest.requireMock("@google-cloud/storage");
|
|
363
440
|
gcpMock.__upload.mockRejectedValueOnce(new Error("gcp down"));
|
|
@@ -379,6 +456,30 @@ describeIntegration("upload routes against real databases", () => {
|
|
|
379
456
|
expect(res.text).toContain("publicPath");
|
|
380
457
|
});
|
|
381
458
|
|
|
459
|
+
it("reads the current SFTP config once per credential inside the callback", async () => {
|
|
460
|
+
const column = fileColumn({ publicPath: "/srv/public" });
|
|
461
|
+
const { app } = appWithColumn(column);
|
|
462
|
+
let reads = 0;
|
|
463
|
+
Object.defineProperty(column, "sftpConfig", {
|
|
464
|
+
get: () => {
|
|
465
|
+
reads += 1;
|
|
466
|
+
return { host: `host-${reads}`, user: `user-${reads}`, password: `password-${reads}` };
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
app.post("/upload-read-order", (req, res) => {
|
|
470
|
+
uploadFile(column, "file", req, res);
|
|
471
|
+
reads = 0;
|
|
472
|
+
});
|
|
473
|
+
await request(app)
|
|
474
|
+
.post("/upload-read-order")
|
|
475
|
+
.attach("file", PNG_BUFFER, { filename: "photo.png" })
|
|
476
|
+
.expect(200);
|
|
477
|
+
expect(reads).toBe(3);
|
|
478
|
+
expect(jest.requireMock("ssh2-sftp-client").__sftp.connect).toHaveBeenCalledWith({
|
|
479
|
+
host: "host-1", port: 22, username: "user-2", password: "password-3",
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
|
|
382
483
|
it("fastPuts the temp file to the remote path and cleans it up", async () => {
|
|
383
484
|
const sftpMock = jest.requireMock("ssh2-sftp-client");
|
|
384
485
|
const column = fileColumn({
|
|
@@ -435,6 +536,18 @@ describeIntegration("upload routes against real databases", () => {
|
|
|
435
536
|
expect(fs.existsSync(stored)).toBe(true);
|
|
436
537
|
});
|
|
437
538
|
|
|
539
|
+
it("uses the default temporary directory when both path properties are absent", async () => {
|
|
540
|
+
const { app, colId } = appWithColumn(fileColumn());
|
|
541
|
+
const res = await s3Request(app, colId).expect(200);
|
|
542
|
+
try {
|
|
543
|
+
expect(path.dirname(res.body.path)).toBe(os.tmpdir());
|
|
544
|
+
expect(res.body.originalFilename).toBe("photo.png");
|
|
545
|
+
expect(fs.readFileSync(res.body.path)).toEqual(PNG_BUFFER);
|
|
546
|
+
} finally {
|
|
547
|
+
fs.rmSync(res.body.path, { force: true });
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
|
|
438
551
|
it("fails without a subdirectory (publicPath + undefined concat — documented quirk)", async () => {
|
|
439
552
|
// uploadDir becomes "<publicPath>undefined", which never exists, so
|
|
440
553
|
// the multipart parse fails with 500. A subdirectory is effectively
|
|
@@ -555,6 +668,16 @@ describeIntegration("upload routes against real databases", () => {
|
|
|
555
668
|
expectNoStorage();
|
|
556
669
|
});
|
|
557
670
|
|
|
671
|
+
it("rejects a role-restricted panel column when the session has no user", async () => {
|
|
672
|
+
const column = richColumn({ roles: ["editor"] });
|
|
673
|
+
const app = buildAppForManager(harness.manager, baseConfig({ views: [fruitView({
|
|
674
|
+
recordViewPanels: [{ type: "table", label: "Notes", query: "SELECT * FROM fruit_notes",
|
|
675
|
+
createConfig: { tableName: "fruit_notes", recordIdKey: "fruitId", columns: [column] } }],
|
|
676
|
+
})] }), null);
|
|
677
|
+
await upload(app, { viewIndex: 0, columnId: generateUniqueIdForObject(column) }).expect(403);
|
|
678
|
+
expectNoStorage();
|
|
679
|
+
});
|
|
680
|
+
|
|
558
681
|
it("uses indexes from the role-filtered views", async () => {
|
|
559
682
|
const column = richColumn();
|
|
560
683
|
const app = buildAppForManager(harness.manager, baseConfig({ views: [
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import * as express from "express";
|
|
2
|
+
import * as request from "supertest";
|
|
3
|
+
import type { RequestHandler } from "express";
|
|
4
|
+
import createRecordQueries from "../../src/api/queries";
|
|
5
|
+
import recordsRoute from "../../src/api/routes/records";
|
|
6
|
+
import exportRoute from "../../src/api/routes/recordsExport";
|
|
7
|
+
import foreignRoute from "../../src/api/routes/foreignSelectRecords";
|
|
8
|
+
import { baseConfig, fruitView } from "../helpers/fixtures";
|
|
9
|
+
import { buildAppForManager } from "../helpers/app";
|
|
10
|
+
import { DbHarness, describeIntegration, getMysqlHarness, getMysql2Harness, getPgHarness, resetTables } from "./dbHarness";
|
|
11
|
+
|
|
12
|
+
type CallbackUser = { role: string; userId?: string | number };
|
|
13
|
+
type Filter = (user: CallbackUser) => string;
|
|
14
|
+
type RouteName = "records" | "recordsExport" | "foreignSelectRecords";
|
|
15
|
+
|
|
16
|
+
// The forwarding recorder below runs the real query helper and database. It
|
|
17
|
+
// observes whether a data query was invoked; it does not replace query results.
|
|
18
|
+
describeIntegration("configured user filters against real databases", () => {
|
|
19
|
+
let harnesses: DbHarness[];
|
|
20
|
+
beforeAll(async () => { harnesses = [await getMysqlHarness(), await getMysql2Harness(), await getPgHarness()]; });
|
|
21
|
+
afterAll(async () => { for (const harness of harnesses || []) await harness.stop(); });
|
|
22
|
+
|
|
23
|
+
describe.each([0, 1, 2])("backend %s", (backend) => {
|
|
24
|
+
let harness: DbHarness;
|
|
25
|
+
beforeEach(async () => { harness = harnesses[backend]; await resetTables(harness); });
|
|
26
|
+
|
|
27
|
+
const literal = (value: string | number) => "'" + (harness.dbType === "POSTGRESQL"
|
|
28
|
+
? String(value).replace(/'/g, "''")
|
|
29
|
+
: String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'")) + "'";
|
|
30
|
+
const scoped = (user: CallbackUser) => user.role === "root" ? ""
|
|
31
|
+
: user.userId === undefined ? "1 = 0" : `slug = ${literal(user.userId)}`;
|
|
32
|
+
|
|
33
|
+
const seed = async (id: string | number) => {
|
|
34
|
+
for (const row of [
|
|
35
|
+
["keep mine", String(id), 1], ["keep other", "someone-else", 1],
|
|
36
|
+
["blocked mine", String(id), 1], ["keep inactive", String(id), 0],
|
|
37
|
+
]) {
|
|
38
|
+
const { err } = await harness.query("INSERT INTO fruits (fruitName, slug, acidity) VALUES (?, ?, ?)", row);
|
|
39
|
+
expect(err).toBeNull();
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const build = (route: RouteName, user: unknown, filter?: Filter, events: string[] = []) => {
|
|
44
|
+
const column = {
|
|
45
|
+
__id__: "foreign", label: "Name", dataType: "foreign", name: "fruitName",
|
|
46
|
+
table: "fruits", foreignName: "id", searchableName: "fruitName", filters: "acidity > 0",
|
|
47
|
+
};
|
|
48
|
+
const view = fruitView({
|
|
49
|
+
tableName: "fruits", primaryId: "id", filters: "acidity > 0", columns: [column],
|
|
50
|
+
});
|
|
51
|
+
const target = route === "foreignSelectRecords" ? column : view;
|
|
52
|
+
if (filter) Object.defineProperty(target, "filtersFromUser", { value: filter, configurable: true });
|
|
53
|
+
const config = baseConfig({ enriched: true, views: [view] });
|
|
54
|
+
const session = { user };
|
|
55
|
+
const actual = createRecordQueries(harness.manager).getRecords;
|
|
56
|
+
let queries = 0;
|
|
57
|
+
const getRecords: typeof actual = async (...args) => {
|
|
58
|
+
queries++;
|
|
59
|
+
events.push("query");
|
|
60
|
+
return actual(...args);
|
|
61
|
+
};
|
|
62
|
+
const handler = route === "records" ? recordsRoute(getRecords)
|
|
63
|
+
: route === "recordsExport" ? exportRoute(getRecords)
|
|
64
|
+
: foreignRoute(harness.manager, getRecords);
|
|
65
|
+
const errors: unknown[] = [];
|
|
66
|
+
const app = express();
|
|
67
|
+
app.use(express.json());
|
|
68
|
+
app.use((req, _res, next) => {
|
|
69
|
+
req.config = config;
|
|
70
|
+
Object.defineProperty(req, "session", { value: session });
|
|
71
|
+
next();
|
|
72
|
+
});
|
|
73
|
+
const observe: RequestHandler = async (req, res, next) => {
|
|
74
|
+
try { await handler(req, res, next); }
|
|
75
|
+
catch (error) { errors.push(error); res.sendStatus(599); }
|
|
76
|
+
};
|
|
77
|
+
app.post("/filter", observe);
|
|
78
|
+
const send = () => request(app).post("/filter").send({
|
|
79
|
+
viewIndex: 0, columnId: "foreign", page: 1,
|
|
80
|
+
filters: route === "records" ? ["fruitName LIKE 'keep%'"] : "fruitName LIKE 'keep%'",
|
|
81
|
+
...(route === "foreignSelectRecords" ? { query: "keep" } : {}),
|
|
82
|
+
});
|
|
83
|
+
return { target, session, errors, send, queries: () => queries };
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const names = (route: RouteName, body: { records?: { fruitName: string }[]; content?: string } | { label: string }[]) => {
|
|
87
|
+
if (Array.isArray(body)) return body.map((row) => row.label);
|
|
88
|
+
return route === "records" ? body.records?.map((row) => row.fruitName) : body.content?.split("\r\n").slice(1);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
describe.each<RouteName>(["records", "recordsExport", "foreignSelectRecords"])("route %s", (route) => {
|
|
92
|
+
it.each([
|
|
93
|
+
["number", 2], ["zero", 0], ["numeric string", "002"],
|
|
94
|
+
["UUID", "00000000-0000-4000-8000-000000000001"], ["SQL punctuation", "owner' OR '1'='1"],
|
|
95
|
+
])("FILTER USER POLICY: scopes an exact %s ID and keeps the session", async (_label, id) => {
|
|
96
|
+
await seed(id);
|
|
97
|
+
const user = Object.freeze({ role: "editor", userId: id, extra: "stored-only" });
|
|
98
|
+
const received: unknown[] = [];
|
|
99
|
+
const instance = build(route, user, function (input) {
|
|
100
|
+
expect(this).toBe(instance.target);
|
|
101
|
+
expect(arguments).toHaveLength(1);
|
|
102
|
+
received.push(input);
|
|
103
|
+
return scoped(input);
|
|
104
|
+
});
|
|
105
|
+
const response = await instance.send();
|
|
106
|
+
expect(response.status).toBe(200);
|
|
107
|
+
expect(received).toEqual([{ role: "editor", userId: id }]);
|
|
108
|
+
expect(received[0]).not.toBe(user);
|
|
109
|
+
expect(instance.session.user).toBe(user);
|
|
110
|
+
expect(names(route, response.body)).toEqual(["keep mine"]);
|
|
111
|
+
expect(instance.queries()).toBe(1);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("FILTER USER POLICY: invokes root filters without reading or forwarding an ID", async () => {
|
|
115
|
+
await seed(2);
|
|
116
|
+
let idReads = 0;
|
|
117
|
+
const user = { role: "root", get userId() { idReads++; throw new Error("root ID read"); } };
|
|
118
|
+
let calls = 0;
|
|
119
|
+
const instance = build(route, user, function (input) {
|
|
120
|
+
expect(this).toBe(instance.target);
|
|
121
|
+
expect(arguments).toHaveLength(1);
|
|
122
|
+
expect(input).toEqual({ role: "root" });
|
|
123
|
+
calls++;
|
|
124
|
+
return "fruitName = 'keep other'";
|
|
125
|
+
});
|
|
126
|
+
const response = await instance.send();
|
|
127
|
+
expect(response.status).toBe(200);
|
|
128
|
+
expect(names(route, response.body)).toEqual(["keep other"]);
|
|
129
|
+
expect({ calls, idReads, queries: instance.queries() }).toEqual({ calls: 1, idReads: 0, queries: 1 });
|
|
130
|
+
expect(instance.session.user).toBe(user);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it.each([{}, { userId: undefined }])("FILTER USER POLICY: lets a regular user without an ID choose a role policy %j", async (idFields) => {
|
|
134
|
+
await seed(2);
|
|
135
|
+
const user = Object.freeze({ role: "editor", ...idFields });
|
|
136
|
+
let calls = 0;
|
|
137
|
+
const instance = build(route, user, (input) => {
|
|
138
|
+
expect(input).toEqual({ role: "editor" });
|
|
139
|
+
expect(Object.prototype.hasOwnProperty.call(input, "userId")).toBe(false);
|
|
140
|
+
calls++;
|
|
141
|
+
return "fruitName = 'keep mine'";
|
|
142
|
+
});
|
|
143
|
+
const response = await instance.send();
|
|
144
|
+
expect(response.status).toBe(200);
|
|
145
|
+
expect(names(route, response.body)).toEqual(["keep mine"]);
|
|
146
|
+
expect(calls).toBe(1);
|
|
147
|
+
expect(instance.session.user).toBe(user);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("FILTER USER POLICY: accepts unnormalized numeric values and empty identifiers", async () => {
|
|
151
|
+
for (const id of [-2.5, NaN, Infinity, ""]) {
|
|
152
|
+
let received: unknown;
|
|
153
|
+
const instance = build(route, { role: "", userId: id }, (input) => {
|
|
154
|
+
received = input;
|
|
155
|
+
return "1 = 0";
|
|
156
|
+
});
|
|
157
|
+
await instance.send().expect(200);
|
|
158
|
+
expect(received).toEqual({ role: "", userId: id });
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("FILTER USER POLICY: rejects malformed sessions before callback or data query", async () => {
|
|
163
|
+
const invalid: unknown[] = [undefined, null, [], "editor", 7, false, () => {}, {},
|
|
164
|
+
{ role: null }, { role: 1 }, { role: [] },
|
|
165
|
+
...[null, true, [], {}, BigInt(1), Symbol("id"), () => {}].map((userId) => ({ role: "editor", userId }))];
|
|
166
|
+
for (const user of invalid) {
|
|
167
|
+
let calls = 0;
|
|
168
|
+
const instance = build(route, user, () => { calls++; return ""; });
|
|
169
|
+
const response = await instance.send();
|
|
170
|
+
expect({ status: response.status, calls, queries: instance.queries(), errors: instance.errors }).toEqual({
|
|
171
|
+
status: 401, calls: 0, queries: 0, errors: [],
|
|
172
|
+
});
|
|
173
|
+
expect(instance.session.user).toBe(user);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("does not read or validate the session when no user filter is configured", async () => {
|
|
178
|
+
await seed(2);
|
|
179
|
+
const instance = build(route, null);
|
|
180
|
+
Object.defineProperty(instance.session, "user", { get() { throw new Error("unexpected session read"); } });
|
|
181
|
+
const response = await instance.send().expect(200);
|
|
182
|
+
expect(names(route, response.body)).toEqual(["keep mine", "keep other"]);
|
|
183
|
+
expect(instance.queries()).toBe(1);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("preserves callback receiver, count, thrown error and no-query behavior", async () => {
|
|
187
|
+
const failure = new Error("filter failure");
|
|
188
|
+
let calls = 0;
|
|
189
|
+
const instance = build(route, { role: "editor", userId: 2 }, function () {
|
|
190
|
+
expect(this).toBe(instance.target);
|
|
191
|
+
calls++;
|
|
192
|
+
throw failure;
|
|
193
|
+
});
|
|
194
|
+
await instance.send().expect(599);
|
|
195
|
+
expect(instance.errors).toEqual([failure]);
|
|
196
|
+
expect(instance.errors[0]).toBe(failure);
|
|
197
|
+
expect(calls).toBe(1);
|
|
198
|
+
expect(instance.queries()).toBe(0);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("preserves table/filter/callback property evaluation order", async () => {
|
|
202
|
+
const events: string[] = [];
|
|
203
|
+
const filter = function () { events.push("callback"); return "1 = 0"; };
|
|
204
|
+
const instance = build(route, { role: "editor", userId: 2 }, filter, events);
|
|
205
|
+
Object.defineProperty(instance.target, route === "foreignSelectRecords" ? "table" : "tableName", {
|
|
206
|
+
get() { events.push("table"); return "fruits"; },
|
|
207
|
+
});
|
|
208
|
+
Object.defineProperty(instance.target, "filters", { get() { events.push("filters"); return "acidity > 0"; } });
|
|
209
|
+
Object.defineProperty(instance.target, "filtersFromUser", { get() { events.push("filter property"); return filter; } });
|
|
210
|
+
await instance.send().expect(200);
|
|
211
|
+
expect(events).toEqual(["table", "filters", "filter property", "filter property", "callback", "query"]);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("keeps API request-filter authorization before a configured user filter", async () => {
|
|
216
|
+
let calls = 0;
|
|
217
|
+
const app = buildAppForManager(harness.manager, baseConfig({ views: [fruitView({
|
|
218
|
+
filtersFromUser: () => { calls++; return ""; },
|
|
219
|
+
})] }), null);
|
|
220
|
+
await request(app).post("/api/records").send({ viewIndex: 0, page: 1, filters: ["1=1; DROP TABLE fruits"] }).expect(400);
|
|
221
|
+
expect(calls).toBe(0);
|
|
222
|
+
expect((await harness.query("SELECT * FROM fruits")).rows).toEqual([]);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
package/tsconfig.json
CHANGED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"strict": true
|
|
5
|
+
},
|
|
6
|
+
"include": [
|
|
7
|
+
"src/types/**/*.ts",
|
|
8
|
+
"src/utils/imageDimensions.ts",
|
|
9
|
+
"src/utils/dbManager.ts",
|
|
10
|
+
"src/api/config.ts",
|
|
11
|
+
"src/api/queries.ts",
|
|
12
|
+
"src/api/authorization.ts",
|
|
13
|
+
"src/api/uploadFile.ts",
|
|
14
|
+
"src/api/request.ts",
|
|
15
|
+
"src/api/routes/adjacentRecordId.ts",
|
|
16
|
+
"src/api/routes/config.ts",
|
|
17
|
+
"src/api/routes/customAction.ts",
|
|
18
|
+
"src/api/routes/customComponent.ts",
|
|
19
|
+
"src/api/routes/customQuery.ts",
|
|
20
|
+
"src/api/routes/deleteRecords.ts",
|
|
21
|
+
"src/api/routes/duplicateRecord.ts",
|
|
22
|
+
"src/api/routes/notificationBadges.ts",
|
|
23
|
+
"src/api/routes/record.ts",
|
|
24
|
+
"src/api/routes/searchCompanies.ts",
|
|
25
|
+
"src/api/routes/uploadFile.ts",
|
|
26
|
+
"src/api/routes/createRecord.ts",
|
|
27
|
+
"src/api/routes/deleteRecord.ts",
|
|
28
|
+
"src/api/routes/deleteRecordFromPanel.ts",
|
|
29
|
+
"src/api/routes/updateRecord.ts",
|
|
30
|
+
"src/api/values.ts",
|
|
31
|
+
"src/api/importRecords.ts",
|
|
32
|
+
"src/api/routes/recordsImport.ts",
|
|
33
|
+
"src/api/routes/subrecordsImport.ts",
|
|
34
|
+
"src/api/routes/uploadFileFromBetterRichTextInput.ts",
|
|
35
|
+
"src/api/filterUser.ts",
|
|
36
|
+
"src/api/routes/records.ts",
|
|
37
|
+
"src/api/routes/recordsExport.ts",
|
|
38
|
+
"src/api/routes/foreignSelectRecords.ts",
|
|
39
|
+
"src/api.ts",
|
|
40
|
+
"src/index.ts"
|
|
41
|
+
],
|
|
42
|
+
"exclude": [
|
|
43
|
+
"src/front"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { ClassicEditor } from '@ckeditor/ckeditor5-editor-classic';
|
|
2
|
-
import { Alignment } from '@ckeditor/ckeditor5-alignment';
|
|
3
|
-
import { Autoformat } from '@ckeditor/ckeditor5-autoformat';
|
|
4
|
-
import { Bold, Italic, Strikethrough, Underline } from '@ckeditor/ckeditor5-basic-styles';
|
|
5
|
-
import { BlockQuote } from '@ckeditor/ckeditor5-block-quote';
|
|
6
|
-
import { CloudServices } from '@ckeditor/ckeditor5-cloud-services';
|
|
7
|
-
import { Essentials } from '@ckeditor/ckeditor5-essentials';
|
|
8
|
-
import { FindAndReplace } from '@ckeditor/ckeditor5-find-and-replace';
|
|
9
|
-
import { FontBackgroundColor, FontColor } from '@ckeditor/ckeditor5-font';
|
|
10
|
-
import { Heading } from '@ckeditor/ckeditor5-heading';
|
|
11
|
-
import { Highlight } from '@ckeditor/ckeditor5-highlight';
|
|
12
|
-
import { HorizontalLine } from '@ckeditor/ckeditor5-horizontal-line';
|
|
13
|
-
import { HtmlEmbed } from '@ckeditor/ckeditor5-html-embed';
|
|
14
|
-
import { AutoImage, Image, ImageCaption, ImageInsert, ImageResize, ImageStyle, ImageToolbar, ImageUpload } from '@ckeditor/ckeditor5-image';
|
|
15
|
-
import { Indent, IndentBlock } from '@ckeditor/ckeditor5-indent';
|
|
16
|
-
import { AutoLink, Link, LinkImage } from '@ckeditor/ckeditor5-link';
|
|
17
|
-
import { List } from '@ckeditor/ckeditor5-list';
|
|
18
|
-
import { MediaEmbed } from '@ckeditor/ckeditor5-media-embed';
|
|
19
|
-
import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
|
|
20
|
-
import { PasteFromOffice } from '@ckeditor/ckeditor5-paste-from-office';
|
|
21
|
-
import { RemoveFormat } from '@ckeditor/ckeditor5-remove-format';
|
|
22
|
-
import { SelectAll } from '@ckeditor/ckeditor5-select-all';
|
|
23
|
-
import { SourceEditing } from '@ckeditor/ckeditor5-source-editing';
|
|
24
|
-
import { Table, TableToolbar } from '@ckeditor/ckeditor5-table';
|
|
25
|
-
import { TextTransformation } from '@ckeditor/ckeditor5-typing';
|
|
26
|
-
import { SimpleUploadAdapter } from '@ckeditor/ckeditor5-upload';
|
|
27
|
-
import { WordCount } from '@ckeditor/ckeditor5-word-count';
|
|
28
|
-
declare class Editor extends ClassicEditor {
|
|
29
|
-
static builtinPlugins: (typeof Alignment | typeof AutoImage | typeof AutoLink | typeof Autoformat | typeof BlockQuote | typeof Bold | typeof CloudServices | typeof Essentials | typeof FindAndReplace | typeof FontBackgroundColor | typeof FontColor | typeof Heading | typeof Highlight | typeof HorizontalLine | typeof HtmlEmbed | typeof Image | typeof ImageCaption | typeof ImageInsert | typeof ImageResize | typeof ImageStyle | typeof ImageToolbar | typeof ImageUpload | typeof Indent | typeof IndentBlock | typeof Italic | typeof Link | typeof LinkImage | typeof List | typeof MediaEmbed | typeof Paragraph | typeof PasteFromOffice | typeof RemoveFormat | typeof SelectAll | typeof SimpleUploadAdapter | typeof SourceEditing | typeof Strikethrough | typeof Table | typeof TableToolbar | typeof TextTransformation | typeof Underline | typeof WordCount)[];
|
|
30
|
-
static defaultConfig: {
|
|
31
|
-
toolbar: {
|
|
32
|
-
items: string[];
|
|
33
|
-
};
|
|
34
|
-
language: string;
|
|
35
|
-
image: {
|
|
36
|
-
toolbar: string[];
|
|
37
|
-
};
|
|
38
|
-
table: {
|
|
39
|
-
contentToolbar: string[];
|
|
40
|
-
};
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
export default Editor;
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const ckeditor5_editor_classic_1 = require("@ckeditor/ckeditor5-editor-classic");
|
|
4
|
-
const ckeditor5_alignment_1 = require("@ckeditor/ckeditor5-alignment");
|
|
5
|
-
const ckeditor5_autoformat_1 = require("@ckeditor/ckeditor5-autoformat");
|
|
6
|
-
const ckeditor5_basic_styles_1 = require("@ckeditor/ckeditor5-basic-styles");
|
|
7
|
-
const ckeditor5_block_quote_1 = require("@ckeditor/ckeditor5-block-quote");
|
|
8
|
-
const ckeditor5_cloud_services_1 = require("@ckeditor/ckeditor5-cloud-services");
|
|
9
|
-
const ckeditor5_essentials_1 = require("@ckeditor/ckeditor5-essentials");
|
|
10
|
-
const ckeditor5_find_and_replace_1 = require("@ckeditor/ckeditor5-find-and-replace");
|
|
11
|
-
const ckeditor5_font_1 = require("@ckeditor/ckeditor5-font");
|
|
12
|
-
const ckeditor5_heading_1 = require("@ckeditor/ckeditor5-heading");
|
|
13
|
-
const ckeditor5_highlight_1 = require("@ckeditor/ckeditor5-highlight");
|
|
14
|
-
const ckeditor5_horizontal_line_1 = require("@ckeditor/ckeditor5-horizontal-line");
|
|
15
|
-
const ckeditor5_html_embed_1 = require("@ckeditor/ckeditor5-html-embed");
|
|
16
|
-
const ckeditor5_image_1 = require("@ckeditor/ckeditor5-image");
|
|
17
|
-
const ckeditor5_indent_1 = require("@ckeditor/ckeditor5-indent");
|
|
18
|
-
const ckeditor5_link_1 = require("@ckeditor/ckeditor5-link");
|
|
19
|
-
const ckeditor5_list_1 = require("@ckeditor/ckeditor5-list");
|
|
20
|
-
const ckeditor5_media_embed_1 = require("@ckeditor/ckeditor5-media-embed");
|
|
21
|
-
const ckeditor5_paragraph_1 = require("@ckeditor/ckeditor5-paragraph");
|
|
22
|
-
const ckeditor5_paste_from_office_1 = require("@ckeditor/ckeditor5-paste-from-office");
|
|
23
|
-
const ckeditor5_remove_format_1 = require("@ckeditor/ckeditor5-remove-format");
|
|
24
|
-
const ckeditor5_select_all_1 = require("@ckeditor/ckeditor5-select-all");
|
|
25
|
-
const ckeditor5_source_editing_1 = require("@ckeditor/ckeditor5-source-editing");
|
|
26
|
-
const ckeditor5_table_1 = require("@ckeditor/ckeditor5-table");
|
|
27
|
-
const ckeditor5_typing_1 = require("@ckeditor/ckeditor5-typing");
|
|
28
|
-
const ckeditor5_upload_1 = require("@ckeditor/ckeditor5-upload");
|
|
29
|
-
const ckeditor5_word_count_1 = require("@ckeditor/ckeditor5-word-count");
|
|
30
|
-
class Editor extends ckeditor5_editor_classic_1.ClassicEditor {
|
|
31
|
-
}
|
|
32
|
-
Editor.builtinPlugins = [
|
|
33
|
-
ckeditor5_alignment_1.Alignment,
|
|
34
|
-
ckeditor5_image_1.AutoImage,
|
|
35
|
-
ckeditor5_link_1.AutoLink,
|
|
36
|
-
ckeditor5_autoformat_1.Autoformat,
|
|
37
|
-
ckeditor5_block_quote_1.BlockQuote,
|
|
38
|
-
ckeditor5_basic_styles_1.Bold,
|
|
39
|
-
ckeditor5_cloud_services_1.CloudServices,
|
|
40
|
-
ckeditor5_essentials_1.Essentials,
|
|
41
|
-
ckeditor5_find_and_replace_1.FindAndReplace,
|
|
42
|
-
ckeditor5_font_1.FontBackgroundColor,
|
|
43
|
-
ckeditor5_font_1.FontColor,
|
|
44
|
-
ckeditor5_heading_1.Heading,
|
|
45
|
-
ckeditor5_highlight_1.Highlight,
|
|
46
|
-
ckeditor5_horizontal_line_1.HorizontalLine,
|
|
47
|
-
ckeditor5_html_embed_1.HtmlEmbed,
|
|
48
|
-
ckeditor5_image_1.Image,
|
|
49
|
-
ckeditor5_image_1.ImageCaption,
|
|
50
|
-
ckeditor5_image_1.ImageInsert,
|
|
51
|
-
ckeditor5_image_1.ImageResize,
|
|
52
|
-
ckeditor5_image_1.ImageStyle,
|
|
53
|
-
ckeditor5_image_1.ImageToolbar,
|
|
54
|
-
ckeditor5_image_1.ImageUpload,
|
|
55
|
-
ckeditor5_indent_1.Indent,
|
|
56
|
-
ckeditor5_indent_1.IndentBlock,
|
|
57
|
-
ckeditor5_basic_styles_1.Italic,
|
|
58
|
-
ckeditor5_link_1.Link,
|
|
59
|
-
ckeditor5_link_1.LinkImage,
|
|
60
|
-
ckeditor5_list_1.List,
|
|
61
|
-
ckeditor5_media_embed_1.MediaEmbed,
|
|
62
|
-
ckeditor5_paragraph_1.Paragraph,
|
|
63
|
-
ckeditor5_paste_from_office_1.PasteFromOffice,
|
|
64
|
-
ckeditor5_remove_format_1.RemoveFormat,
|
|
65
|
-
ckeditor5_select_all_1.SelectAll,
|
|
66
|
-
ckeditor5_upload_1.SimpleUploadAdapter,
|
|
67
|
-
ckeditor5_source_editing_1.SourceEditing,
|
|
68
|
-
ckeditor5_basic_styles_1.Strikethrough,
|
|
69
|
-
ckeditor5_table_1.Table,
|
|
70
|
-
ckeditor5_table_1.TableToolbar,
|
|
71
|
-
ckeditor5_typing_1.TextTransformation,
|
|
72
|
-
ckeditor5_basic_styles_1.Underline,
|
|
73
|
-
ckeditor5_word_count_1.WordCount
|
|
74
|
-
];
|
|
75
|
-
Editor.defaultConfig = {
|
|
76
|
-
toolbar: {
|
|
77
|
-
items: [
|
|
78
|
-
'heading',
|
|
79
|
-
'|',
|
|
80
|
-
'bold',
|
|
81
|
-
'italic',
|
|
82
|
-
'underline',
|
|
83
|
-
'strikethrough',
|
|
84
|
-
'link',
|
|
85
|
-
'bulletedList',
|
|
86
|
-
'numberedList',
|
|
87
|
-
'removeFormat',
|
|
88
|
-
'|',
|
|
89
|
-
'outdent',
|
|
90
|
-
'indent',
|
|
91
|
-
'|',
|
|
92
|
-
'imageUpload',
|
|
93
|
-
'imageInsert',
|
|
94
|
-
'blockQuote',
|
|
95
|
-
'insertTable',
|
|
96
|
-
'mediaEmbed',
|
|
97
|
-
'undo',
|
|
98
|
-
'redo',
|
|
99
|
-
'alignment',
|
|
100
|
-
'fontColor',
|
|
101
|
-
'fontBackgroundColor',
|
|
102
|
-
'horizontalLine',
|
|
103
|
-
'highlight',
|
|
104
|
-
'selectAll',
|
|
105
|
-
'findAndReplace',
|
|
106
|
-
'htmlEmbed',
|
|
107
|
-
'sourceEditing'
|
|
108
|
-
]
|
|
109
|
-
},
|
|
110
|
-
language: 'fr',
|
|
111
|
-
image: {
|
|
112
|
-
toolbar: [
|
|
113
|
-
'imageTextAlternative',
|
|
114
|
-
'toggleImageCaption',
|
|
115
|
-
'imageStyle:inline',
|
|
116
|
-
'imageStyle:block',
|
|
117
|
-
'imageStyle:side',
|
|
118
|
-
'linkImage'
|
|
119
|
-
]
|
|
120
|
-
},
|
|
121
|
-
table: {
|
|
122
|
-
contentToolbar: [
|
|
123
|
-
'tableColumn',
|
|
124
|
-
'tableRow',
|
|
125
|
-
'mergeTableCells'
|
|
126
|
-
]
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
exports.default = Editor;
|
|
130
|
-
//# sourceMappingURL=ckeditor.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ckeditor.js","sourceRoot":"","sources":["../../../../src/front/ckeditor5-build/src/ckeditor.ts"],"names":[],"mappings":";;AAKA,iFAAmE;AAEnE,uEAA0D;AAC1D,yEAA4D;AAC5D,6EAA0F;AAC1F,2EAA6D;AAC7D,iFAAmE;AACnE,yEAA4D;AAC5D,qFAAsE;AACtE,6DAA0E;AAC1E,mEAAsD;AACtD,uEAA0D;AAC1D,mFAAqE;AACrE,yEAA2D;AAC3D,+DASmC;AACnC,iEAAiE;AACjE,6DAAqE;AACrE,6DAAgD;AAChD,2EAA6D;AAC7D,uEAA0D;AAC1D,uFAAwE;AACxE,+EAAiE;AACjE,yEAA2D;AAC3D,iFAAmE;AACnE,+DAAgE;AAChE,iEAAgE;AAChE,iEAAiE;AACjE,yEAA2D;AAK3D,MAAM,MAAO,SAAQ,wCAAa;;AACV,qBAAc,GAAG;IACvC,+BAAS;IACT,2BAAS;IACT,yBAAQ;IACR,iCAAU;IACV,kCAAU;IACV,6BAAI;IACJ,wCAAa;IACb,iCAAU;IACV,2CAAc;IACd,oCAAmB;IACnB,0BAAS;IACT,2BAAO;IACP,+BAAS;IACT,0CAAc;IACd,gCAAS;IACT,uBAAK;IACL,8BAAY;IACZ,6BAAW;IACX,6BAAW;IACX,4BAAU;IACV,8BAAY;IACZ,6BAAW;IACX,yBAAM;IACN,8BAAW;IACX,+BAAM;IACN,qBAAI;IACJ,0BAAS;IACT,qBAAI;IACJ,kCAAU;IACV,+BAAS;IACT,6CAAe;IACf,sCAAY;IACZ,gCAAS;IACT,sCAAmB;IACnB,wCAAa;IACb,sCAAa;IACb,uBAAK;IACL,8BAAY;IACZ,qCAAkB;IAClB,kCAAS;IACT,gCAAS;CACT,CAAC;AAEqB,oBAAa,GAAG;IACtC,OAAO,EAAE;QACR,KAAK,EAAE;YACN,SAAS;YACT,GAAG;YACH,MAAM;YACN,QAAQ;YACR,WAAW;YACX,eAAe;YACf,MAAM;YACN,cAAc;YACd,cAAc;YACd,cAAc;YACd,GAAG;YACH,SAAS;YACT,QAAQ;YACR,GAAG;YACH,aAAa;YACb,aAAa;YACb,YAAY;YACZ,aAAa;YACb,YAAY;YACZ,MAAM;YACN,MAAM;YACN,WAAW;YACX,WAAW;YACX,qBAAqB;YACrB,gBAAgB;YAChB,WAAW;YACX,WAAW;YACX,gBAAgB;YAChB,WAAW;YACX,eAAe;SACf;KACD;IACD,QAAQ,EAAE,IAAI;IACd,KAAK,EAAE;QACN,OAAO,EAAE;YACR,sBAAsB;YACtB,oBAAoB;YACpB,mBAAmB;YACnB,kBAAkB;YAClB,iBAAiB;YACjB,WAAW;SACX;KACD;IACD,KAAK,EAAE;QACN,cAAc,EAAE;YACf,aAAa;YACb,UAAU;YACV,iBAAiB;SACjB;KACD;CACD,CAAC;AAGH,kBAAe,MAAM,CAAC"}
|