@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.
- package/help/JavaScript action code.tmd +1 -0
- package/locales/en.json +18 -1
- package/package.json +8 -8
- package/public/saltcorn-builder.css +37 -2
- package/public/saltcorn.css +4 -0
- package/public/saltcorn.js +13 -9
- package/restart_watcher.js +1 -0
- package/routes/actions.js +24 -3
- package/routes/common_lists.js +305 -136
- package/routes/fields.js +11 -2
- package/routes/files.js +3 -1
- package/routes/list.js +5 -0
- package/routes/page.js +30 -13
- package/routes/page_groupedit.js +104 -83
- package/routes/pageedit.js +20 -5
- package/routes/tables.js +29 -6
- package/routes/tag_entries.js +12 -4
- package/routes/tags.js +61 -12
- package/routes/utils.js +19 -0
- package/routes/view.js +20 -2
- package/routes/viewedit.js +63 -4
- package/tests/page_group.test.js +1 -0
- package/tests/view.test.js +115 -15
- package/tests/viewedit.test.js +0 -21
- package/wrapper.js +2 -2
- package/public/relation_helpers.js +0 -351
package/routes/viewedit.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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: "
|
|
819
|
+
res.json({ error: req.__("Unable to save: No view") });
|
|
761
820
|
}
|
|
762
821
|
})
|
|
763
822
|
);
|
package/tests/page_group.test.js
CHANGED
package/tests/view.test.js
CHANGED
|
@@ -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("
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
});
|
package/tests/viewedit.test.js
CHANGED
|
@@ -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),
|