@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.
Files changed (233) hide show
  1. package/.agents/report/blockers.json +1 -0
  2. package/.agents/report/failures/2026-09-23-api-user-filter-contract.md +31 -0
  3. package/.agents/report/failures/2026-09-23-api-values-contract.md +25 -0
  4. package/.agents/report/failures/2026-10-01-cycle-02-create-callback-contract.md +17 -0
  5. package/.agents/report/failures/2026-10-01-cycle-06-delete-callback-id-contract.md +17 -0
  6. package/.agents/report/failures/2026-10-01-cycle-07-panel-delete-callback-id-contract.md +24 -0
  7. package/.agents/report/failures/2026-10-01-cycle-13-update-callback-id-contract.md +17 -0
  8. package/.agents/report/failures/2026-10-01-cycle-15-rich-text-upload-boundary-contract.md +24 -0
  9. package/.agents/report/failures/2026-10-03-cycle-06-record-import-parser-contract.md +11 -0
  10. package/.agents/report/failures/2026-10-03-cycle-07-panel-import-parser-contract.md +24 -0
  11. package/.agents/report/failures/2026-10-07-resume-cycle-02-api-assembly-contract.md +46 -0
  12. package/.agents/report/port/2026-09-23-api-config-enrichment.md +13 -0
  13. package/.agents/report/port/2026-09-23-api-file-upload.md +11 -0
  14. package/.agents/report/port/2026-09-23-api-query-authorization.md +14 -0
  15. package/.agents/report/port/2026-09-23-api-request-configuration.md +11 -0
  16. package/.agents/report/port/2026-09-23-database-manager.md +13 -0
  17. package/.agents/report/port/2026-10-01-api-adjacent-record-id.md +9 -0
  18. package/.agents/report/port/2026-10-01-cycle-01-api-config-route.md +9 -0
  19. package/.agents/report/port/2026-10-01-cycle-03-custom-action.md +9 -0
  20. package/.agents/report/port/2026-10-01-cycle-04-custom-component.md +7 -0
  21. package/.agents/report/port/2026-10-01-cycle-05-custom-query.md +7 -0
  22. package/.agents/report/port/2026-10-01-cycle-08-bulk-delete.md +9 -0
  23. package/.agents/report/port/2026-10-01-cycle-09-duplicate-record.md +9 -0
  24. package/.agents/report/port/2026-10-01-cycle-10-notification-badges.md +9 -0
  25. package/.agents/report/port/2026-10-01-cycle-11-record.md +7 -0
  26. package/.agents/report/port/2026-10-01-cycle-12-search-companies.md +11 -0
  27. package/.agents/report/port/2026-10-01-cycle-14-upload-file.md +7 -0
  28. package/.agents/report/port/2026-10-03-cycle-01-create-record.md +11 -0
  29. package/.agents/report/port/2026-10-03-cycle-02-delete-record.md +11 -0
  30. package/.agents/report/port/2026-10-03-cycle-03-panel-delete-record.md +11 -0
  31. package/.agents/report/port/2026-10-03-cycle-04-update-record.md +11 -0
  32. package/.agents/report/port/2026-10-03-cycle-05-api-values.md +11 -0
  33. package/.agents/report/port/2026-10-06-cycle-01-record-import.md +9 -0
  34. package/.agents/report/port/2026-10-06-cycle-03-rich-text-upload.md +7 -0
  35. package/.agents/report/port/2026-10-06-cycle-04-user-filters.md +11 -0
  36. package/.agents/report/port/2026-10-06-resume-cycle-01-panel-import.md +9 -0
  37. package/.agents/report/port/2026-10-07-cycle-01-api-assembly.md +16 -0
  38. package/.agents/report/port/2026-10-07-cycle-02-index-entry-point.md +13 -0
  39. package/.agents/report/remediation/2026-10-02-api-user-filter-remediation.md +71 -0
  40. package/.agents/report/remediation/2026-10-02-api-values-remediation.md +17 -0
  41. package/.agents/report/remediation/2026-10-03-callback-id-contract-remediation.md +36 -0
  42. package/.agents/report/remediation/2026-10-06-import-parser-remediation.md +41 -0
  43. package/.agents/report/remediation/2026-10-06-rich-text-upload-remediation.md +33 -0
  44. package/.agents/report/remediation/2026-10-07-api-assembly-remediation.md +32 -0
  45. package/.agents/skills/port-javascript-to-typescript/SKILL.md +42 -0
  46. package/.agents/skills/port-javascript-to-typescript/agents/openai.yaml +3 -0
  47. package/.agents/skills/port-javascript-to-typescript/references/blockers.md +31 -0
  48. package/.agents/skills/port-javascript-to-typescript/references/planning.md +43 -0
  49. package/.agents/skills/port-javascript-to-typescript/references/porting.md +9 -0
  50. package/.agents/skills/port-javascript-to-typescript/references/reporting.md +24 -0
  51. package/.agents/skills/port-javascript-to-typescript/references/rules.md +8 -0
  52. package/.agents/skills/port-javascript-to-typescript/references/selection.md +16 -0
  53. package/.agents/skills/port-javascript-to-typescript/references/tools.md +20 -0
  54. package/.agents/skills/port-javascript-to-typescript/scripts/check.sh +17 -0
  55. package/.agents/skills/port-javascript-to-typescript/scripts/graph.sh +30 -0
  56. package/.agents/skills/port-javascript-to-typescript/scripts/madge.json +12 -0
  57. package/.agents/skills/port-javascript-to-typescript/scripts/oxlint.json +14 -0
  58. package/.agents/skills/port-javascript-to-typescript/scripts/test-tools.cjs +76 -0
  59. package/.github/actions/version-bump/action.yml +41 -0
  60. package/.github/workflows/alpha.yml +53 -0
  61. package/.github/workflows/ci.yml +8 -9
  62. package/.github/workflows/main.yml +13 -14
  63. package/AGENTS.md +5 -0
  64. package/dist/api/authorization.d.ts +38 -0
  65. package/dist/api/authorization.js +74 -0
  66. package/dist/api/authorization.js.map +1 -0
  67. package/dist/api/config.d.ts +42 -0
  68. package/dist/api/config.js +105 -0
  69. package/dist/api/config.js.map +1 -0
  70. package/dist/api/filterUser.d.ts +2 -0
  71. package/dist/api/filterUser.js +24 -0
  72. package/dist/api/filterUser.js.map +1 -0
  73. package/dist/api/importRecords.d.ts +1 -0
  74. package/dist/api/importRecords.js +16 -0
  75. package/dist/api/importRecords.js.map +1 -0
  76. package/dist/api/queries.d.ts +48 -0
  77. package/dist/api/queries.js +139 -0
  78. package/dist/api/queries.js.map +1 -0
  79. package/dist/api/request.d.ts +29 -0
  80. package/dist/api/request.js +3 -0
  81. package/dist/api/request.js.map +1 -0
  82. package/dist/api/routes/adjacentRecordId.d.ts +10 -0
  83. package/dist/api/routes/adjacentRecordId.js +13 -0
  84. package/dist/api/routes/adjacentRecordId.js.map +1 -0
  85. package/dist/api/routes/config.d.ts +3 -0
  86. package/dist/api/routes/config.js +27 -0
  87. package/dist/api/routes/config.js.map +1 -0
  88. package/dist/api/routes/createRecord.d.ts +19 -0
  89. package/dist/api/routes/createRecord.js +93 -0
  90. package/dist/api/routes/createRecord.js.map +1 -0
  91. package/dist/api/routes/customAction.d.ts +17 -0
  92. package/dist/api/routes/customAction.js +35 -0
  93. package/dist/api/routes/customAction.js.map +1 -0
  94. package/dist/api/routes/customComponent.d.ts +3 -0
  95. package/dist/api/routes/customComponent.js +34 -0
  96. package/dist/api/routes/customComponent.js.map +1 -0
  97. package/dist/api/routes/customQuery.d.ts +7 -0
  98. package/dist/api/routes/customQuery.js +33 -0
  99. package/dist/api/routes/customQuery.js.map +1 -0
  100. package/dist/api/routes/deleteRecord.d.ts +9 -0
  101. package/dist/api/routes/deleteRecord.js +46 -0
  102. package/dist/api/routes/deleteRecord.js.map +1 -0
  103. package/dist/api/routes/deleteRecordFromPanel.d.ts +10 -0
  104. package/dist/api/routes/deleteRecordFromPanel.js +36 -0
  105. package/dist/api/routes/deleteRecordFromPanel.js.map +1 -0
  106. package/dist/api/routes/deleteRecords.d.ts +9 -0
  107. package/dist/api/routes/deleteRecords.js +12 -0
  108. package/dist/api/routes/deleteRecords.js.map +1 -0
  109. package/dist/api/routes/duplicateRecord.d.ts +17 -0
  110. package/dist/api/routes/duplicateRecord.js +55 -0
  111. package/dist/api/routes/duplicateRecord.js.map +1 -0
  112. package/dist/api/routes/foreignSelectRecords.d.ts +10 -0
  113. package/dist/api/routes/foreignSelectRecords.js +61 -0
  114. package/dist/api/routes/foreignSelectRecords.js.map +1 -0
  115. package/dist/api/routes/notificationBadges.d.ts +6 -0
  116. package/dist/api/routes/notificationBadges.js +31 -0
  117. package/dist/api/routes/notificationBadges.js.map +1 -0
  118. package/dist/api/routes/record.d.ts +7 -0
  119. package/dist/api/routes/record.js +32 -0
  120. package/dist/api/routes/record.js.map +1 -0
  121. package/dist/api/routes/records.d.ts +14 -0
  122. package/dist/api/routes/records.js +57 -0
  123. package/dist/api/routes/records.js.map +1 -0
  124. package/dist/api/routes/recordsExport.d.ts +13 -0
  125. package/dist/api/routes/recordsExport.js +67 -0
  126. package/dist/api/routes/recordsExport.js.map +1 -0
  127. package/dist/api/routes/recordsImport.d.ts +4 -0
  128. package/dist/api/routes/recordsImport.js +100 -0
  129. package/dist/api/routes/recordsImport.js.map +1 -0
  130. package/dist/api/routes/searchCompanies.d.ts +17 -0
  131. package/dist/api/routes/searchCompanies.js +72 -0
  132. package/dist/api/routes/searchCompanies.js.map +1 -0
  133. package/dist/api/routes/subrecordsImport.d.ts +4 -0
  134. package/dist/api/routes/subrecordsImport.js +94 -0
  135. package/dist/api/routes/subrecordsImport.js.map +1 -0
  136. package/dist/api/routes/updateRecord.d.ts +13 -0
  137. package/dist/api/routes/updateRecord.js +55 -0
  138. package/dist/api/routes/updateRecord.js.map +1 -0
  139. package/dist/api/routes/uploadFile.d.ts +3 -0
  140. package/dist/api/routes/uploadFile.js +13 -0
  141. package/dist/api/routes/uploadFile.js.map +1 -0
  142. package/dist/api/routes/uploadFileFromBetterRichTextInput.d.ts +3 -0
  143. package/dist/api/routes/uploadFileFromBetterRichTextInput.js +33 -0
  144. package/dist/api/routes/uploadFileFromBetterRichTextInput.js.map +1 -0
  145. package/dist/api/uploadFile.d.ts +4 -0
  146. package/dist/api/uploadFile.js +209 -0
  147. package/dist/api/uploadFile.js.map +1 -0
  148. package/dist/api/values.d.ts +6 -0
  149. package/dist/api/values.js +88 -0
  150. package/dist/api/values.js.map +1 -0
  151. package/dist/api.d.ts +8 -27
  152. package/dist/api.js +80 -1387
  153. package/dist/api.js.map +1 -1
  154. package/dist/index.d.ts +2 -2
  155. package/dist/index.js +8 -2
  156. package/dist/index.js.map +1 -1
  157. package/dist/tsconfig.strict.tsbuildinfo +1 -0
  158. package/dist/types/config.d.ts +15 -5
  159. package/dist/types/config.js +4 -0
  160. package/dist/types/config.js.map +1 -1
  161. package/dist/types/database.d.ts +1 -0
  162. package/dist/types/database.js +3 -0
  163. package/dist/types/database.js.map +1 -0
  164. package/dist/utils/dbManager.d.ts +32 -7
  165. package/dist/utils/dbManager.js +59 -7
  166. package/dist/utils/dbManager.js.map +1 -1
  167. package/package.json +12 -3
  168. package/src/api/authorization.ts +129 -0
  169. package/src/api/config.ts +152 -0
  170. package/src/api/filterUser.ts +28 -0
  171. package/src/api/importRecords.ts +16 -0
  172. package/src/api/queries.ts +194 -0
  173. package/src/api/request.ts +31 -0
  174. package/src/api/routes/adjacentRecordId.ts +20 -0
  175. package/src/api/routes/config.ts +15 -0
  176. package/src/api/routes/createRecord.ts +131 -0
  177. package/src/api/routes/customAction.ts +60 -0
  178. package/src/api/routes/customComponent.ts +44 -0
  179. package/src/api/routes/customQuery.ts +53 -0
  180. package/src/api/routes/deleteRecord.ts +59 -0
  181. package/src/api/routes/deleteRecordFromPanel.ts +58 -0
  182. package/src/api/routes/deleteRecords.ts +28 -0
  183. package/src/api/routes/duplicateRecord.ts +73 -0
  184. package/src/api/routes/foreignSelectRecords.ts +80 -0
  185. package/src/api/routes/notificationBadges.ts +47 -0
  186. package/src/api/routes/record.ts +48 -0
  187. package/src/api/routes/records.ts +91 -0
  188. package/src/api/routes/recordsExport.ts +92 -0
  189. package/src/api/routes/recordsImport.ts +115 -0
  190. package/src/api/routes/searchCompanies.ts +96 -0
  191. package/src/api/routes/subrecordsImport.ts +114 -0
  192. package/src/api/routes/updateRecord.ts +74 -0
  193. package/src/api/routes/uploadFile.ts +16 -0
  194. package/src/api/routes/uploadFileFromBetterRichTextInput.ts +41 -0
  195. package/src/api/uploadFile.ts +249 -0
  196. package/src/api/values.ts +96 -0
  197. package/src/api.ts +94 -1580
  198. package/src/front/ckeditor5-build/node_modules/semver/bin/semver.js +0 -0
  199. package/src/index.ts +42 -25
  200. package/src/types/config.ts +15 -5
  201. package/src/types/database.ts +2 -0
  202. package/src/utils/dbManager.ts +99 -14
  203. package/tests/api-helpers.test.ts +59 -0
  204. package/tests/dbManager.test.ts +16 -1
  205. package/tests/helpers/fakeDb.ts +9 -7
  206. package/tests/integration/authorizationBoundary.integration.test.ts +57 -0
  207. package/tests/integration/configEnrichment.integration.test.ts +79 -0
  208. package/tests/integration/customComponentBoundary.integration.test.ts +58 -0
  209. package/tests/integration/dbManager.integration.test.ts +105 -3
  210. package/tests/integration/importParserChild.cjs +55 -0
  211. package/tests/integration/indexRuntime.integration.test.ts +22 -0
  212. package/tests/integration/indexRuntimeChild.cjs +110 -0
  213. package/tests/integration/queries.integration.test.ts +88 -0
  214. package/tests/integration/requestConfig.integration.test.ts +118 -0
  215. package/tests/integration/routes.authorization.integration.test.ts +1 -0
  216. package/tests/integration/routes.config.integration.test.ts +39 -1
  217. package/tests/integration/routes.crud.integration.test.ts +233 -1
  218. package/tests/integration/routes.custom.integration.test.ts +119 -0
  219. package/tests/integration/routes.foreign.integration.test.ts +3 -2
  220. package/tests/integration/routes.importExport.integration.test.ts +308 -6
  221. package/tests/integration/routes.integration.test.ts +4 -0
  222. package/tests/integration/routes.records.integration.test.ts +82 -6
  223. package/tests/integration/routes.upload.integration.test.ts +123 -0
  224. package/tests/integration/userFilters.integration.test.ts +225 -0
  225. package/tsconfig.json +1 -1
  226. package/tsconfig.strict.json +45 -0
  227. package/dist/front/ckeditor5-build/src/ckeditor.d.ts +0 -43
  228. package/dist/front/ckeditor5-build/src/ckeditor.js +0 -130
  229. package/dist/front/ckeditor5-build/src/ckeditor.js.map +0 -1
  230. package/dist/tsconfig.tsbuildinfo +0 -1
  231. package/src/front/ckeditor5-build/node_modules/.bin/semver +0 -12
  232. package/src/front/ckeditor5-build/node_modules/.bin/semver.cmd +0 -17
  233. 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
@@ -17,4 +17,4 @@
17
17
  "include": [
18
18
  "src/"
19
19
  ]
20
- }
20
+ }
@@ -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"}