@saltcorn/markup 0.5.6-beta.3 → 0.6.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/builder.js CHANGED
@@ -41,6 +41,14 @@ module.exports = (
41
41
  ? `/static_assets/${version_tag}/fonticonpicker.react.css`
42
42
  : "/fonticonpicker.react.css",
43
43
  }),
44
+ link({
45
+ rel: "stylesheet",
46
+ type: "text/css",
47
+ media: "screen",
48
+ href: version_tag
49
+ ? `/static_assets/${version_tag}/saltcorn-builder.css`
50
+ : "/saltcorn-builder.css",
51
+ }),
44
52
  div({ id: "saltcorn-builder" }),
45
53
  form(
46
54
  { action, method: "post", id: "scbuildform" },
package/form.js CHANGED
@@ -43,13 +43,17 @@ const formRowWrap = (hdr, inner, error = "", fStyle, labelCols) =>
43
43
  }),
44
44
  },
45
45
  hdr.input_type === "section_header"
46
- ? div({ class: `col-sm-12` }, h5(text(hdr.label)), hdr.sublabel && p(i(hdr.sublabel)) )
46
+ ? div(
47
+ { class: `col-sm-12` },
48
+ h5(text(hdr.label)),
49
+ hdr.sublabel && p(i(hdr.sublabel))
50
+ )
47
51
  : [
48
52
  div(
49
53
  { class: isHoriz(fStyle) && `col-sm-${labelCols}` },
50
54
  label(
51
55
  {
52
- for: `input${text_attr(hdr.form_name)}`,
56
+ for: `input${text_attr(hdr.form_name)}`,
53
57
  },
54
58
  text(hdr.label)
55
59
  )
@@ -100,7 +104,7 @@ const innerField = (v, errors, nameAdd = "") => (hdr) => {
100
104
  }"${maybe_disabled} data-fieldname="${text_attr(
101
105
  hdr.form_name
102
106
  )}" name="${text_attr(name)}" id="input${text_attr(name)}">${text(
103
- v[hdr.form_name]
107
+ v[hdr.form_name] || ""
104
108
  )}</textarea>`;
105
109
  case "code":
106
110
  return `<textarea mode="${
@@ -109,9 +113,9 @@ const innerField = (v, errors, nameAdd = "") => (hdr) => {
109
113
  hdr.class || ""
110
114
  }"${maybe_disabled} data-fieldname="${text_attr(
111
115
  hdr.form_name
112
- )}" name="${text_attr(name)}" id="input${text_attr(name)}">${text(
113
- v[hdr.form_name]
114
- )}</textarea>`;
116
+ )}" name="${text_attr(name)}" id="input${text_attr(name)}">${
117
+ v[hdr.form_name] || ""
118
+ }</textarea>`;
115
119
  case "file":
116
120
  if (hdr.attributes && hdr.attributes.select_file_where) {
117
121
  hdr.input_type = "select";
@@ -137,9 +141,9 @@ const innerField = (v, errors, nameAdd = "") => (hdr) => {
137
141
  hdr.class || ""
138
142
  }"${maybe_disabled} data-fieldname="${text_attr(
139
143
  hdr.form_name
140
- )}" name="${name}" id="input${text_attr(name)}" ${
144
+ )}" name="${name}" id="input${text_attr(name)}"${
141
145
  v && isdef(v[hdr.form_name])
142
- ? `value="${text_attr(v[hdr.form_name])}"`
146
+ ? ` value="${text_attr(v[hdr.form_name])}"`
143
147
  : ""
144
148
  }>`;
145
149
  const inner = hdr.postText
@@ -294,7 +298,16 @@ const renderFormLayout = (form) => {
294
298
  action_size,
295
299
  action_icon,
296
300
  configuration,
301
+ action_bgcol,
302
+ action_bordercol,
303
+ action_textcol,
297
304
  }) {
305
+ let style =
306
+ action_style === "btn-custom-color"
307
+ ? `background-color: ${action_bgcol || "#000000"};border-color: ${
308
+ action_bordercol || "#000000"
309
+ }; color: ${action_textcol || "#000000"}`
310
+ : null;
298
311
  if (action_name && action_name.startsWith("Login with ")) {
299
312
  const method_label = action_name.replace("Login with ", "");
300
313
 
@@ -306,6 +319,7 @@ const renderFormLayout = (form) => {
306
319
  action_style !== "btn-link" &&
307
320
  `btn ${action_style || "btn-primary"} ${action_size || ""}`,
308
321
  ],
322
+ style,
309
323
  },
310
324
  action_icon ? i({ class: action_icon }) + "&nbsp;" : false,
311
325
  action_label || action_name
@@ -316,7 +330,9 @@ const renderFormLayout = (form) => {
316
330
  action_style === "btn-link"
317
331
  ? ""
318
332
  : `btn ${action_style || "btn-primary"} ${action_size || ""}`
319
- }">${action_icon ? `<i class="${action_icon}"></i>&nbsp;` : ""}${text(
333
+ }"${style ? ` style="${style}"` : ""}>${
334
+ action_icon ? `<i class="${action_icon}"></i>&nbsp;` : ""
335
+ }${text(
320
336
  action_label || form.submitLabel || action_name || "Save"
321
337
  )}</button>`;
322
338
 
@@ -383,11 +399,11 @@ const renderForm = (form, csrfToken0) => {
383
399
  const mkFormWithLayout = (form, csrfToken) => {
384
400
  const hasFile = form.fields.some((f) => f.input_type === "file");
385
401
  const csrfField = `<input type="hidden" name="_csrf" value="${csrfToken}">`;
386
- const top = `<form action="${form.action}" ${
402
+ const top = `<form action="${form.action}"${
387
403
  form.onChange ? ` onchange="${form.onChange}"` : ""
388
- }class="form-namespace ${form.class || ""}" method="${
404
+ } class="form-namespace ${form.class || ""}" method="${
389
405
  form.methodGET ? "get" : "post"
390
- }" ${hasFile ? 'encType="multipart/form-data"' : ""}>`;
406
+ }"${hasFile ? ' encType="multipart/form-data"' : ""}>`;
391
407
  const blurbp = form.blurb
392
408
  ? Array.isArray(form.blurb)
393
409
  ? form.blurb.join("")
@@ -436,8 +452,8 @@ const mkForm = (form, csrfToken, errors = {}) => {
436
452
  form.onChange ? ` onchange="${form.onChange}"` : ""
437
453
  }class="form-namespace ${form.isStateForm ? "stateForm" : ""} ${
438
454
  form.class || ""
439
- }" method="${form.methodGET ? "get" : "post"}" ${
440
- hasFile ? 'encType="multipart/form-data"' : ""
455
+ }" method="${form.methodGET ? "get" : "post"}"${
456
+ hasFile ? ' encType="multipart/form-data"' : ""
441
457
  }>`;
442
458
  //console.log(form.fields);
443
459
  const flds = form.fields
package/form.test.js CHANGED
@@ -24,10 +24,10 @@ describe("form render", () => {
24
24
  },
25
25
  ],
26
26
  });
27
- const want = `<form action="/" class="form-namespace " method="post" >
27
+ const want = `<form action="/" class="form-namespace " method="post">
28
28
  <input type="hidden" name="_csrf" value=""><div class="form-group">
29
29
  <div><label for="inputname">Name</label></div>
30
- <div><input type="text" class="form-control " data-fieldname="name" name="name" id="inputname" >
30
+ <div><input type="text" class="form-control " data-fieldname="name" name="name" id="inputname">
31
31
  </div></div><div class="form-group row">
32
32
  <div class="col-sm-12">
33
33
  <button type="submit" class="btn btn-primary">Save</button>
@@ -62,10 +62,10 @@ describe("form render", () => {
62
62
  ],
63
63
  },
64
64
  });
65
- const want = `<form action="/" class="form-namespace " method="post" >
65
+ const want = `<form action="/" class="form-namespace " method="post">
66
66
  <input type="hidden" name="_csrf" value="">
67
67
  <h2>
68
- <input type="text" class="form-control " data-fieldname="name" name="name" id="inputname" >
68
+ <input type="text" class="form-control " data-fieldname="name" name="name" id="inputname">
69
69
  </h2><br /></form>`;
70
70
  expect(nolines(renderForm(form, ""))).toBe(nolines(want));
71
71
  });
@@ -83,7 +83,7 @@ describe("form render", () => {
83
83
  },
84
84
  ],
85
85
  });
86
- const want = `<form action="/" class="form-namespace " method="post" >
86
+ const want = `<form action="/" class="form-namespace " method="post">
87
87
  <input type="hidden" name="_csrf" value=""><div class="form-group">
88
88
  <div><label for="inputname">Name</label></div>
89
89
  <div><input type="text" class="form-control is-invalid " data-fieldname="name" name="name" id="inputname" value="Bar"><div>Not a foo</div>
package/helpers.js CHANGED
@@ -31,8 +31,8 @@ const select_options = (v, hdr, force_required, neutral_label = "") => {
31
31
  .map((o) => {
32
32
  const label = typeof o === "string" ? o : o.label;
33
33
  const value = typeof o === "string" ? o : o.value;
34
- return `<option value="${text_attr(value)}" ${
35
- isSelected(value) ? "selected" : ""
34
+ return `<option value="${text_attr(value)}"${
35
+ isSelected(value) ? " selected" : ""
36
36
  }>${text(label)}</option>`;
37
37
  })
38
38
  .join("");
package/index.js CHANGED
@@ -14,6 +14,7 @@ const post_btn = (
14
14
  btnClass = "btn-primary",
15
15
  onClick,
16
16
  small,
17
+ style,
17
18
  ajax,
18
19
  reload_on_done,
19
20
  reload_delay,
@@ -43,9 +44,9 @@ const post_btn = (
43
44
  : confirm
44
45
  ? `onclick="return confirm('${req.__("Are you sure?")}')"`
45
46
  : ""
46
- } class="${klass} btn ${small ? "btn-sm" : ""} ${btnClass}">${
47
- icon ? `<i class="${icon}"></i>&nbsp;` : ""
48
- }${s}</button></form>`;
47
+ } class="${klass} btn ${small ? "btn-sm" : ""} ${btnClass}"${
48
+ style ? ` style="${style}"` : ""
49
+ }>${icon ? `<i class="${icon}"></i>&nbsp;` : ""}${s}</button></form>`;
49
50
  /**
50
51
  * UI Form for Delete Item confirmation
51
52
  * @param href - href
package/layout.js CHANGED
@@ -45,25 +45,26 @@ const makeSegments = (body, alerts) => {
45
45
  return body;
46
46
  } else return { above: [...alertsSegments, body] };
47
47
  };
48
- const applyTextStyle = (textStyle, inner, isBlock) => {
49
- switch (textStyle) {
48
+ const applyTextStyle = (segment, inner) => {
49
+ let style = segment.font ? { fontFamily: segment.font } : {};
50
+ switch (segment.textStyle) {
50
51
  case "h1":
51
- return h1(inner);
52
+ return h1(style, inner);
52
53
  case "h2":
53
- return h2(inner);
54
+ return h2(style, inner);
54
55
  case "h3":
55
- return h3(inner);
56
+ return h3(style, inner);
56
57
  case "h4":
57
- return h4(inner);
58
+ return h4(style, inner);
58
59
  case "h5":
59
- return h5(inner);
60
+ return h5(style, inner);
60
61
  case "h6":
61
- return h6(inner);
62
+ return h6(style, inner);
62
63
  default:
63
- return isBlock
64
- ? div({ class: textStyle || "" }, inner)
65
- : textStyle
66
- ? span({ class: textStyle || "" }, inner)
64
+ return segment.block
65
+ ? div({ class: segment.textStyle || "", style }, inner)
66
+ : segment.textStyle || segment.font
67
+ ? span({ class: segment.textStyle || "", style }, inner)
67
68
  : inner;
68
69
  }
69
70
  };
@@ -158,9 +159,9 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner }) => {
158
159
  ? iconTag +
159
160
  label(
160
161
  { for: `input${text(segment.labelFor)}` },
161
- applyTextStyle(segment.textStyle, inner, segment.block)
162
+ applyTextStyle(segment, inner)
162
163
  )
163
- : iconTag + applyTextStyle(segment.textStyle, inner, segment.block);
164
+ : iconTag + applyTextStyle(segment, inner);
164
165
  }
165
166
  function go(segment, isTop, ix) {
166
167
  if (!segment) return "";
@@ -214,6 +215,14 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner }) => {
214
215
  );
215
216
  }
216
217
  if (segment.type === "link") {
218
+ let style =
219
+ segment.link_style === "btn btn-custom-color"
220
+ ? `background-color: ${
221
+ segment.link_bgcol || "#000000"
222
+ };border-color: ${segment.link_bordercol || "#000000"}; color: ${
223
+ segment.link_textcol || "#000000"
224
+ }`
225
+ : null;
217
226
  return wrap(
218
227
  segment,
219
228
  isTop,
@@ -224,6 +233,7 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner }) => {
224
233
  class: [segment.link_style || "", segment.link_size || ""],
225
234
  target: segment.target_blank ? "_blank" : false,
226
235
  rel: segment.nofollow ? "nofollow" : false,
236
+ style,
227
237
  },
228
238
  segment.link_icon ? i({ class: segment.link_icon }) + "&nbsp;" : "",
229
239
  segment.text
@@ -272,6 +282,7 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner }) => {
272
282
  vAlign,
273
283
  hAlign,
274
284
  block,
285
+ display,
275
286
  imageSize,
276
287
  borderWidth,
277
288
  borderStyle,
@@ -296,6 +307,8 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner }) => {
296
307
  gradDirection,
297
308
  fullPageWidth,
298
309
  overflow,
310
+ rotate,
311
+ style,
299
312
  } = segment;
300
313
  if (hide) return "";
301
314
  if (
@@ -312,7 +325,8 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner }) => {
312
325
  unit || segment[segKey + "Unit"] || "px"
313
326
  };`;
314
327
  const ppCustomCSS = (s) => (s ? s.split("\n").join("") + ";" : "");
315
- const baseDisplayClass = block === false ? "inline-block" : "block";
328
+ const baseDisplayClass =
329
+ block === false ? "inline-block" : display ? display : "block";
316
330
  let displayClass = minScreenWidth
317
331
  ? `d-none d-${minScreenWidth}-${baseDisplayClass}`
318
332
  : baseDisplayClass === "block"
@@ -325,6 +339,10 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner }) => {
325
339
  !segment[what] || allZero(segment[what])
326
340
  ? ""
327
341
  : `${what}: ${segment[what].map((p) => p + "px").join(" ")};`;
342
+ let flexStyles = "";
343
+ Object.keys(style || {}).forEach((k) => {
344
+ flexStyles += `${k}:${style[k]};`;
345
+ });
328
346
  return wrap(
329
347
  segment,
330
348
  isTop,
@@ -346,7 +364,7 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner }) => {
346
364
  ],
347
365
  onclick: segment.url ? `location.href='${segment.url}'` : false,
348
366
 
349
- style: `${ppCustomCSS(customCSS || "")}${sizeProp(
367
+ style: `${flexStyles}${ppCustomCSS(customCSS || "")}${sizeProp(
350
368
  "minHeight",
351
369
  "min-height"
352
370
  )}${sizeProp("height", "height")}${sizeProp(
@@ -378,7 +396,9 @@ const render = ({ blockDispatch, layout, role, alerts, is_owner }) => {
378
396
  gradDirection || 0
379
397
  }deg, ${gradStartColor}, ${gradEndColor});`
380
398
  : ""
381
- } ${setTextColor ? `color: ${textColor};` : ""}`,
399
+ } ${setTextColor ? `color: ${textColor};` : ""}${
400
+ rotate ? `transform: rotate(${rotate}deg);` : ""
401
+ }`,
382
402
  ...(showIfFormulaInputs
383
403
  ? {
384
404
  "data-show-if": `showIfFormulaInputs(e, '${showIfFormulaInputs}')`,
package/markup.test.js CHANGED
@@ -61,7 +61,18 @@ describe("tags", () => {
61
61
  expect(div({ style: { color: "red", border: "1px solid black" } }, 5)).toBe(
62
62
  '<div style="color:red;border:1px solid black">5</div>'
63
63
  );
64
+
65
+ expect(
66
+ div({ style: { marginRight: "1px", border: "1px solid black" } }, 5)
67
+ ).toBe('<div style="margin-right:1px;border:1px solid black">5</div>');
68
+ //border-top-left-radius
69
+ expect(
70
+ div({ style: { marginRight: "1px", borderTopLeftRadius: "3px" } }, 5)
71
+ ).toBe('<div style="margin-right:1px;border-top-left-radius:3px">5</div>');
64
72
  expect(hr({ style: { color: "red" } }, 5)).toBe('<hr style="color:red">');
73
+ expect(hr({ style: {} })).toBe("<hr>");
74
+ expect(hr({ style: null })).toBe("<hr>");
75
+ expect(div({ class: "foo", style: null })).toBe('<div class="foo"></div>');
65
76
  });
66
77
 
67
78
  it("escaping", () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/markup",
3
- "version": "0.5.6-beta.3",
3
+ "version": "0.6.0-beta.0",
4
4
  "description": "Markup for Saltcorn, open-source no-code platform",
5
5
  "homepage": "https://saltcorn.com",
6
6
  "main": "index.js",
package/tags.js CHANGED
@@ -3,6 +3,10 @@ const escape = require("escape-html");
3
3
  const htmlTags = require("html-tags");
4
4
  const voidHtmlTags = new Set(require("html-tags/void"));
5
5
 
6
+ //https://stackoverflow.com/a/54246501
7
+ const camelToCssCase = (str) =>
8
+ str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
9
+
6
10
  const ppClasses = (cs) =>
7
11
  typeof cs === "string" ? cs : !cs ? "" : cs.filter((c) => c).join(" ");
8
12
  const ppClass = (c) => {
@@ -18,7 +22,7 @@ const ppStyles = (cs) =>
18
22
  ? cs.filter((c) => c).join(";")
19
23
  : typeof cs === "object"
20
24
  ? Object.entries(cs)
21
- .map(([k, v]) => `${k}:${v}`)
25
+ .map(([k, v]) => `${camelToCssCase(k)}:${v}`)
22
26
  .join(";")
23
27
  : "";
24
28
  const ppStyle = (c) => {
@@ -50,7 +54,10 @@ const mkTag = (tnm, voidTag) => (...args) => {
50
54
  if (Array.isArray(arg)) {
51
55
  arg.forEach(argIter);
52
56
  } else {
53
- attribs += Object.entries(arg).map(ppAttrib).join(" ");
57
+ attribs += Object.entries(arg)
58
+ .map(ppAttrib)
59
+ .filter((s) => s)
60
+ .join(" ");
54
61
  }
55
62
  } else body += arg;
56
63
  };