@saltcorn/server 0.9.4-beta.1 → 0.9.4-beta.11

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.
@@ -29,6 +29,9 @@ const Workflow = require("@saltcorn/data/models/workflow");
29
29
  const User = require("@saltcorn/data/models/user");
30
30
  const Page = require("@saltcorn/data/models/page");
31
31
  const File = require("@saltcorn/data/models/file");
32
+ const Tag = require("@saltcorn/data/models/tag");
33
+ const TagEntry = require("@saltcorn/data/models/tag_entry");
34
+
32
35
  const db = require("@saltcorn/data/db");
33
36
  const { sleep } = require("@saltcorn/data/utils");
34
37
 
@@ -56,7 +59,18 @@ router.get(
56
59
  error_catcher(async (req, res) => {
57
60
  let orderBy = "name";
58
61
  if (req.query._sortby === "viewtemplate") orderBy = "viewtemplate";
59
- const views = await View.find({}, { orderBy, nocase: true });
62
+ const viewq = {};
63
+ let filterOnTag;
64
+ if (req.query._tag) {
65
+ const tagEntries = await TagEntry.find({
66
+ tag_id: +req.query._tag,
67
+ not: { view_id: null },
68
+ });
69
+ viewq.id = { in: tagEntries.map((te) => te.view_id).filter(Boolean) };
70
+ filterOnTag = await Tag.findOne({ id: +req.query._tag });
71
+ }
72
+
73
+ const views = await View.find(viewq, { orderBy, nocase: true });
60
74
  await setTableRefs(views);
61
75
 
62
76
  if (req.query._sortby === "table")
@@ -64,7 +78,7 @@ router.get(
64
78
  a.table.toLowerCase() > b.table.toLowerCase() ? 1 : -1
65
79
  );
66
80
 
67
- const viewMarkup = await viewsList(views, req);
81
+ const viewMarkup = await viewsList(views, req, { filterOnTag });
68
82
  const tables = await Table.find();
69
83
 
70
84
  res.sendWrap(req.__(`Views`), {
@@ -182,6 +196,26 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
182
196
  required: true,
183
197
  options: roles.map((r) => ({ value: r.id, label: r.role })),
184
198
  }),
199
+ new Field({
200
+ name: "page_title",
201
+ label: req.__("Page title"),
202
+ type: "String",
203
+ parent_field: "attributes",
204
+ tab: "View settings",
205
+ sublabel: req.__(
206
+ "Some view patterns accept interpolations. Ex: <code>{{ name }}</code> or <code>{{ row ? `Edit ${row.name}` : `New person` }}</code>"
207
+ ),
208
+ }),
209
+ new Field({
210
+ name: "page_description",
211
+ label: req.__("Page description"),
212
+ type: "String",
213
+ parent_field: "attributes",
214
+ tab: "View settings",
215
+ sublabel: req.__(
216
+ "For search engines. Some view patterns accept interpolations."
217
+ ),
218
+ }),
185
219
  new Field({
186
220
  name: "default_render_page",
187
221
  label: req.__("Show on page"),
@@ -215,6 +249,8 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
215
249
  type: "String",
216
250
  parent_field: "attributes",
217
251
  tab: "Popup settings",
252
+ sublabel:
253
+ "Some view patterns accept interpolations. Ex: <code>{{ name }}</code> or <code>{{ row ? `Edit ${row.name}` : `New person` }}</code>",
218
254
  }),
219
255
  {
220
256
  name: "popup_width",
@@ -236,6 +272,26 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
236
272
  options: ["px", "%", "vw", "em", "rem"],
237
273
  },
238
274
  },
275
+ {
276
+ name: "popup_minwidth",
277
+ label: req.__("Popup min width"),
278
+ type: "Integer",
279
+ tab: "Popup settings",
280
+ parent_field: "attributes",
281
+ attributes: { asideNext: true },
282
+ },
283
+ {
284
+ name: "popup_minwidth_units",
285
+ label: req.__("Units"),
286
+ type: "String",
287
+ tab: "Popup settings",
288
+ fieldview: "radio_group",
289
+ parent_field: "attributes",
290
+ attributes: {
291
+ inline: true,
292
+ options: ["px", "%", "vw", "em", "rem"],
293
+ },
294
+ },
239
295
  {
240
296
  name: "popup_save_indicator",
241
297
  label: req.__("Save indicator"),
@@ -569,7 +625,10 @@ const respondWorkflow = (view, wf, wfres, req, res, table) => {
569
625
  else if (wfres.renderBuilder) {
570
626
  wfres.renderBuilder.options.view_id = view.id;
571
627
  res.sendWrap(
572
- req.__(`%s configuration`, view.name),
628
+ {
629
+ title: req.__(`%s configuration`, view.name),
630
+ requestFluidLayout: true,
631
+ },
573
632
  wrap(renderBuilder(wfres.renderBuilder, req.csrfToken()), true)
574
633
  );
575
634
  } else res.redirect(wfres.redirect);
@@ -757,7 +816,7 @@ router.post(
757
816
  await View.update({ configuration: newcfg }, +id);
758
817
  res.json({ success: "ok" });
759
818
  } else {
760
- res.json({ error: "no view" });
819
+ res.json({ error: req.__("Unable to save: No view") });
761
820
  }
762
821
  })
763
822
  );
@@ -73,6 +73,7 @@ describe("edit Page groups", () => {
73
73
  name: nameAfterUpdate,
74
74
  description: null,
75
75
  min_role: 100,
76
+ random_allocation: false,
76
77
  });
77
78
  });
78
79
 
@@ -419,6 +419,10 @@ describe("update matching rows", () => {
419
419
  await field.update({ is_unique: false });
420
420
  });
421
421
 
422
+ afterAll(async () => {
423
+ await resetToFixtures();
424
+ });
425
+
422
426
  it("update matching books normal", async () => {
423
427
  const table = Table.findOne({ name: "books" });
424
428
  await updateMatchingRows({
@@ -755,7 +759,7 @@ describe("relation path to query and state", () => {
755
759
  .expect(toNotInclude("album B"));
756
760
  });
757
761
 
758
- it("OneToOneSHow", async () => {
762
+ it("OneToOneShow", async () => {
759
763
  const app = await getApp({ disableCsrf: true });
760
764
  const loginCookie = await getAdminLoginCookie();
761
765
  await request(app)
@@ -805,7 +809,7 @@ describe("relation path to query and state", () => {
805
809
  .expect(toInclude(`value="artist B"`));
806
810
  });
807
811
 
808
- it("Parent", async () => {
812
+ it("Parent one layer", async () => {
809
813
  const app = await getApp({ disableCsrf: true });
810
814
  const loginCookie = await getAdminLoginCookie();
811
815
  await request(app)
@@ -833,6 +837,28 @@ describe("relation path to query and state", () => {
833
837
  .expect(toInclude("No row selected"));
834
838
  });
835
839
 
840
+ it("Parent two layers", async () => {
841
+ const app = await getApp({ disableCsrf: true });
842
+ const loginCookie = await getAdminLoginCookie();
843
+ await request(app)
844
+ .get(`/view/show_patient_with_publisher?id=2`)
845
+ .set("Cookie", loginCookie)
846
+ // view link
847
+ .expect(toInclude("/view/show_publisher?.patients.favbook.publisher=2"))
848
+ // embedded show
849
+ .expect(toInclude("Michael Douglas"))
850
+ .expect(toInclude("AK Press"));
851
+
852
+ await request(app)
853
+ .get(`/view/show_patient_with_publisher?id=1`)
854
+ .set("Cookie", loginCookie)
855
+ // view link
856
+ .expect(toInclude("/view/show_publisher?.patients.favbook.publisher=1"))
857
+ // embedded show
858
+ .expect(toInclude("Kirk Douglas"))
859
+ .expect(toInclude("No row selected"));
860
+ });
861
+
836
862
  it("RelationPath", async () => {
837
863
  const app = await getApp({ disableCsrf: true });
838
864
  const loginCookie = await getAdminLoginCookie();
@@ -873,11 +899,23 @@ describe("edit-in-edit with relation path and legacy", () => {
873
899
  const app = await getApp({ disableCsrf: true });
874
900
  const loginCookie = await getAdminLoginCookie();
875
901
  await request(app)
876
- .get("/view/edit_department_with_edit_in_edit_legacy?id=1")
902
+ .get("/view/edit_department_with_edit_in_edit_relation_path?id=1")
877
903
  .set("Cookie", loginCookie)
878
904
  .expect(toInclude("add_repeater"));
879
-
880
- // TODO post
905
+ await request(app)
906
+ .post("/view/edit_department_with_edit_in_edit_relation_path?id=1")
907
+ .set("Cookie", loginCookie)
908
+ .send({
909
+ department_0: "1",
910
+ department_1: "1",
911
+ id: "1",
912
+ id_0: "1",
913
+ id_1: "2",
914
+ name: "my_department",
915
+ name_0: "manager",
916
+ name_1: "my_employee",
917
+ })
918
+ .expect(toRedirect("/"));
881
919
  });
882
920
 
883
921
  it("edit-in-edit with relation path two layer", async () => {
@@ -887,10 +925,21 @@ describe("edit-in-edit with relation path and legacy", () => {
887
925
  .get("/view/edit_cover_with_edit_artist_on_album_rel_path?id=1")
888
926
  .set("Cookie", loginCookie)
889
927
  .expect(toInclude("add_repeater"));
890
-
891
- // TODO post
928
+ await request(app)
929
+ .post("/view/edit_cover_with_edit_artist_on_album_rel_path?id=1")
930
+ .set("Cookie", loginCookie)
931
+ .send({
932
+ album_0: "1",
933
+ album_1: "1",
934
+ artist_0: "1",
935
+ artist_1: "2",
936
+ id: "1",
937
+ id_0: "1",
938
+ id_1: "3",
939
+ name: "green cover",
940
+ })
941
+ .expect(toRedirect("/"));
892
942
  });
893
-
894
943
  it("edit-in-edit legacy one layer", async () => {
895
944
  const app = await getApp({ disableCsrf: true });
896
945
  const loginCookie = await getAdminLoginCookie();
@@ -898,8 +947,20 @@ describe("edit-in-edit with relation path and legacy", () => {
898
947
  .get("/view/edit_department_with_edit_in_edit_legacy?id=1")
899
948
  .set("Cookie", loginCookie)
900
949
  .expect(toInclude("add_repeater"));
901
-
902
- // TODO post
950
+ await request(app)
951
+ .post("/view/edit_department_with_edit_in_edit_legacy?id=1")
952
+ .set("Cookie", loginCookie)
953
+ .send({
954
+ department_0: "1",
955
+ department_1: "1",
956
+ id: "1",
957
+ id_0: "1",
958
+ id_1: "2",
959
+ name: "my_department",
960
+ name_0: "manager",
961
+ name_1: "my_employee",
962
+ })
963
+ .expect(toRedirect("/"));
903
964
  });
904
965
 
905
966
  it("edit-in-edit with relation path two layer", async () => {
@@ -909,8 +970,20 @@ describe("edit-in-edit with relation path and legacy", () => {
909
970
  .get("/view/edit_cover_with_edit_artist_on_album_rel_path?id=1")
910
971
  .set("Cookie", loginCookie)
911
972
  .expect(toInclude("add_repeater"));
912
-
913
- // TODO post
973
+ await request(app)
974
+ .post("/view/edit_cover_with_edit_artist_on_album_rel_path?id=1")
975
+ .set("Cookie", loginCookie)
976
+ .send({
977
+ album_0: "1",
978
+ album_1: "1",
979
+ artist_0: "1",
980
+ artist_1: "2",
981
+ id: "1",
982
+ id_0: "1",
983
+ id_1: "3",
984
+ name: "green cover",
985
+ })
986
+ .expect(toRedirect("/"));
914
987
  });
915
988
 
916
989
  it("edit-in-edit legacy two layer", async () => {
@@ -920,8 +993,20 @@ describe("edit-in-edit with relation path and legacy", () => {
920
993
  .get("/view/edit_cover_with_edit_artist_on_album_legacy?id=1")
921
994
  .set("Cookie", loginCookie)
922
995
  .expect(toInclude("add_repeater"));
923
-
924
- // TODO post
996
+ await request(app)
997
+ .post("/view/edit_cover_with_edit_artist_on_album_legacy?id=1")
998
+ .set("Cookie", loginCookie)
999
+ .send({
1000
+ album_0: "1",
1001
+ album_1: "1",
1002
+ artist_0: "1",
1003
+ artist_1: "2",
1004
+ id: "1",
1005
+ id_0: "1",
1006
+ id_1: "3",
1007
+ name: "green cover",
1008
+ })
1009
+ .expect(toRedirect("/"));
925
1010
  });
926
1011
  });
927
1012
 
@@ -982,6 +1067,21 @@ describe("legacy relations with relation path", () => {
982
1067
  await request(app)
983
1068
  .get("/view/authoredit_with_show?id=1")
984
1069
  .set("Cookie", loginCookie)
985
- .expect(toInclude(["Herman Melville", "agi"]));
1070
+ .expect(toInclude("Herman Melville"));
1071
+ });
1072
+
1073
+ it("edit-view with independent list", async () => {
1074
+ const app = await getApp({ disableCsrf: true });
1075
+ const loginCookie = await getAdminLoginCookie();
1076
+ await request(app)
1077
+ .get("/view/authoredit_with_independent_list")
1078
+ .set("Cookie", loginCookie)
1079
+ .expect(toInclude("Herman Melville"))
1080
+ .expect(toInclude("Delete"));
1081
+ await request(app)
1082
+ .get("/view/authoredit_with_independent_list?id=1")
1083
+ .set("Cookie", loginCookie)
1084
+ .expect(toInclude("Herman Melville"))
1085
+ .expect(toInclude("Delete"));
986
1086
  });
987
1087
  });
@@ -360,27 +360,6 @@ describe("viewedit new Show", () => {
360
360
  .send("columns=" + encodeURIComponent(JSON.stringify(columns)))
361
361
  .send("layout=" + encodeURIComponent(JSON.stringify(layout)))
362
362
  .set("Cookie", loginCookie)
363
- .expect(toInclude("Set page title"));
364
- });
365
- it("save new view page title", async () => {
366
- const loginCookie = await getAdminLoginCookie();
367
- const table = Table.findOne({ name: "books" });
368
-
369
- const ctx = encodeURIComponent(
370
- JSON.stringify({
371
- table_id: table.id,
372
- viewname: "mybook",
373
- layout,
374
- columns,
375
- })
376
- );
377
-
378
- const app = await getApp({ disableCsrf: true });
379
- await request(app)
380
- .post("/viewedit/config/mybook")
381
- .send("contextEnc=" + ctx)
382
- .send("stepName=Set+page+title")
383
- .set("Cookie", loginCookie)
384
363
  .expect(toRedirect("/viewedit"));
385
364
  });
386
365
  it("should show new view", async () => {
package/wrapper.js CHANGED
@@ -195,7 +195,6 @@ const get_headers = (req, version_tag, description, extras = []) => {
195
195
  { script: `/static_assets/${version_tag}/saltcorn-common.js` },
196
196
  { script: `/static_assets/${version_tag}/saltcorn.js` },
197
197
  { script: `/static_assets/${version_tag}/dayjs.min.js` },
198
- { script: `/static_assets/${version_tag}/relation_helpers.js` },
199
198
  ];
200
199
  let from_cfg = [];
201
200
  if (state.getConfig("page_custom_css", ""))
@@ -350,7 +349,8 @@ module.exports = (version_tag) =>
350
349
  menu: no_menu ? undefined : get_menu(req),
351
350
  currentUrl,
352
351
  originalUrl: req.originalUrl,
353
-
352
+ requestFluidLayout:
353
+ typeof opts === "string" ? false : opts.requestFluidLayout,
354
354
  alerts,
355
355
  body: html.length === 1 ? html[0] : html.join(""),
356
356
  headers: get_headers(req, version_tag, opts.description, pageHeaders),