@fieldia/core 0.26.1 → 0.27.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/README.md +307 -3
- package/package.json +1 -1
- package/schema/page.schema.json +227 -23
- package/src/index.d.mts +8 -2
- package/src/index.d.ts +8 -2
- package/src/index.js +22 -1
- package/src/index.js.map +1 -1
- package/src/index.mjs +9 -2
- package/src/index.mjs.map +1 -1
- package/src/lib/expression/app-functions.d.mts +47 -0
- package/src/lib/expression/app-functions.d.ts +47 -0
- package/src/lib/expression/app-functions.js +64 -0
- package/src/lib/expression/app-functions.js.map +1 -0
- package/src/lib/expression/app-functions.mjs +58 -0
- package/src/lib/expression/app-functions.mjs.map +1 -0
- package/src/lib/expression/dates.d.mts +21 -0
- package/src/lib/expression/dates.d.ts +21 -0
- package/src/lib/expression/dates.js +73 -0
- package/src/lib/expression/dates.js.map +1 -0
- package/src/lib/expression/dates.mjs +67 -0
- package/src/lib/expression/dates.mjs.map +1 -0
- package/src/lib/expression/evaluator.js +12 -6
- package/src/lib/expression/evaluator.js.map +1 -1
- package/src/lib/expression/evaluator.mjs +12 -6
- package/src/lib/expression/evaluator.mjs.map +1 -1
- package/src/lib/expression/expression.d.mts +7 -3
- package/src/lib/expression/expression.d.ts +7 -3
- package/src/lib/expression/expression.js +8 -2
- package/src/lib/expression/expression.js.map +1 -1
- package/src/lib/expression/expression.mjs +7 -2
- package/src/lib/expression/expression.mjs.map +1 -1
- package/src/lib/expression/functions-part.d.mts +7 -0
- package/src/lib/expression/functions-part.d.ts +7 -0
- package/src/lib/expression/functions-part.js +13 -0
- package/src/lib/expression/functions-part.js.map +1 -0
- package/src/lib/expression/functions-part.mjs +8 -0
- package/src/lib/expression/functions-part.mjs.map +1 -0
- package/src/lib/expression/functions.d.mts +60 -3
- package/src/lib/expression/functions.d.ts +60 -3
- package/src/lib/expression/functions.js +109 -15
- package/src/lib/expression/functions.js.map +1 -1
- package/src/lib/expression/functions.mjs +103 -16
- package/src/lib/expression/functions.mjs.map +1 -1
- package/src/lib/expression/kinds.d.mts +31 -0
- package/src/lib/expression/kinds.d.ts +31 -0
- package/src/lib/expression/kinds.js +159 -0
- package/src/lib/expression/kinds.js.map +1 -0
- package/src/lib/expression/kinds.mjs +151 -0
- package/src/lib/expression/kinds.mjs.map +1 -0
- package/src/lib/expression/modifier.d.mts +4 -3
- package/src/lib/expression/modifier.d.ts +4 -3
- package/src/lib/expression/modifier.js +4 -3
- package/src/lib/expression/modifier.js.map +1 -1
- package/src/lib/expression/modifier.mjs +4 -3
- package/src/lib/expression/modifier.mjs.map +1 -1
- package/src/lib/expression/more-functions.d.mts +8 -0
- package/src/lib/expression/more-functions.d.ts +8 -0
- package/src/lib/expression/more-functions.js +235 -0
- package/src/lib/expression/more-functions.js.map +1 -0
- package/src/lib/expression/more-functions.mjs +230 -0
- package/src/lib/expression/more-functions.mjs.map +1 -0
- package/src/lib/expression/parser.d.mts +4 -1
- package/src/lib/expression/parser.d.ts +4 -1
- package/src/lib/expression/parser.js +6 -3
- package/src/lib/expression/parser.js.map +1 -1
- package/src/lib/expression/parser.mjs +6 -3
- package/src/lib/expression/parser.mjs.map +1 -1
- package/src/lib/expression/reads.d.mts +2 -2
- package/src/lib/expression/reads.d.ts +2 -2
- package/src/lib/expression/reads.js +6 -4
- package/src/lib/expression/reads.js.map +1 -1
- package/src/lib/expression/reads.mjs +6 -4
- package/src/lib/expression/reads.mjs.map +1 -1
- package/src/lib/format/actions.d.mts +16 -16
- package/src/lib/format/actions.d.ts +16 -16
- package/src/lib/format/check-page.d.mts +7 -2
- package/src/lib/format/check-page.d.ts +7 -2
- package/src/lib/format/check-page.js +10 -4
- package/src/lib/format/check-page.js.map +1 -1
- package/src/lib/format/check-page.mjs +10 -4
- package/src/lib/format/check-page.mjs.map +1 -1
- package/src/lib/format/choice-options.d.mts +34 -0
- package/src/lib/format/choice-options.d.ts +34 -0
- package/src/lib/format/choice-options.js +60 -0
- package/src/lib/format/choice-options.js.map +1 -0
- package/src/lib/format/choice-options.mjs +51 -0
- package/src/lib/format/choice-options.mjs.map +1 -0
- package/src/lib/format/endings-checks.d.mts +8 -0
- package/src/lib/format/endings-checks.d.ts +8 -0
- package/src/lib/format/endings-checks.js +23 -0
- package/src/lib/format/endings-checks.js.map +1 -0
- package/src/lib/format/endings-checks.mjs +20 -0
- package/src/lib/format/endings-checks.mjs.map +1 -0
- package/src/lib/format/endings.d.mts +40 -0
- package/src/lib/format/endings.d.ts +40 -0
- package/src/lib/format/endings.js +15 -0
- package/src/lib/format/endings.js.map +1 -0
- package/src/lib/format/endings.mjs +12 -0
- package/src/lib/format/endings.mjs.map +1 -0
- package/src/lib/format/field.d.mts +151 -16
- package/src/lib/format/field.d.ts +151 -16
- package/src/lib/format/field.js +64 -11
- package/src/lib/format/field.js.map +1 -1
- package/src/lib/format/field.mjs +63 -10
- package/src/lib/format/field.mjs.map +1 -1
- package/src/lib/format/head-checks.d.mts +2 -0
- package/src/lib/format/head-checks.d.ts +2 -0
- package/src/lib/format/head-checks.js +2 -59
- package/src/lib/format/head-checks.js.map +1 -1
- package/src/lib/format/head-checks.mjs +1 -58
- package/src/lib/format/head-checks.mjs.map +1 -1
- package/src/lib/format/later-checks.d.mts +17 -0
- package/src/lib/format/later-checks.d.ts +17 -0
- package/src/lib/format/later-checks.js +20 -0
- package/src/lib/format/later-checks.js.map +1 -0
- package/src/lib/format/later-checks.mjs +17 -0
- package/src/lib/format/later-checks.mjs.map +1 -0
- package/src/lib/format/layout.d.mts +241 -188
- package/src/lib/format/layout.d.ts +241 -188
- package/src/lib/format/layout.js +33 -28
- package/src/lib/format/layout.js.map +1 -1
- package/src/lib/format/layout.mjs +13 -3
- package/src/lib/format/layout.mjs.map +1 -1
- package/src/lib/format/layout.types.d.mts +2 -1
- package/src/lib/format/layout.types.d.ts +2 -1
- package/src/lib/format/limits-checks.d.mts +14 -0
- package/src/lib/format/limits-checks.d.ts +14 -0
- package/src/lib/format/limits-checks.js +36 -0
- package/src/lib/format/limits-checks.js.map +1 -0
- package/src/lib/format/limits-checks.mjs +33 -0
- package/src/lib/format/limits-checks.mjs.map +1 -0
- package/src/lib/format/limits.d.mts +24 -0
- package/src/lib/format/limits.d.ts +24 -0
- package/src/lib/format/limits.js +35 -0
- package/src/lib/format/limits.js.map +1 -0
- package/src/lib/format/limits.mjs +32 -0
- package/src/lib/format/limits.mjs.map +1 -0
- package/src/lib/format/list-checks.d.mts +44 -0
- package/src/lib/format/list-checks.d.ts +44 -0
- package/src/lib/format/list-checks.js +340 -0
- package/src/lib/format/list-checks.js.map +1 -0
- package/src/lib/format/list-checks.mjs +336 -0
- package/src/lib/format/list-checks.mjs.map +1 -0
- package/src/lib/format/map-checks.d.mts +33 -0
- package/src/lib/format/map-checks.d.ts +33 -0
- package/src/lib/format/map-checks.js +180 -0
- package/src/lib/format/map-checks.js.map +1 -0
- package/src/lib/format/map-checks.mjs +175 -0
- package/src/lib/format/map-checks.mjs.map +1 -0
- package/src/lib/format/mapchecks-part.d.mts +5 -0
- package/src/lib/format/mapchecks-part.d.ts +5 -0
- package/src/lib/format/mapchecks-part.js +11 -0
- package/src/lib/format/mapchecks-part.js.map +1 -0
- package/src/lib/format/mapchecks-part.mjs +6 -0
- package/src/lib/format/mapchecks-part.mjs.map +1 -0
- package/src/lib/format/option-checks.d.mts +16 -0
- package/src/lib/format/option-checks.d.ts +16 -0
- package/src/lib/format/option-checks.js +39 -0
- package/src/lib/format/option-checks.js.map +1 -0
- package/src/lib/format/option-checks.mjs +36 -0
- package/src/lib/format/option-checks.mjs.map +1 -0
- package/src/lib/format/options-part.d.mts +6 -0
- package/src/lib/format/options-part.d.ts +6 -0
- package/src/lib/format/options-part.js +11 -0
- package/src/lib/format/options-part.js.map +1 -0
- package/src/lib/format/options-part.mjs +7 -0
- package/src/lib/format/options-part.mjs.map +1 -0
- package/src/lib/format/page.d.mts +147 -71
- package/src/lib/format/page.d.ts +147 -71
- package/src/lib/format/page.js +5 -0
- package/src/lib/format/page.js.map +1 -1
- package/src/lib/format/page.mjs +5 -0
- package/src/lib/format/page.mjs.map +1 -1
- package/src/lib/format/part-checks.d.mts +20 -0
- package/src/lib/format/part-checks.d.ts +20 -0
- package/src/lib/format/part-checks.js +3 -0
- package/src/lib/format/part-checks.js.map +1 -0
- package/src/lib/format/part-checks.mjs +2 -0
- package/src/lib/format/part-checks.mjs.map +1 -0
- package/src/lib/format/references.d.mts +71 -65
- package/src/lib/format/references.d.ts +71 -65
- package/src/lib/format/references.js +194 -631
- package/src/lib/format/references.js.map +1 -1
- package/src/lib/format/references.mjs +194 -631
- package/src/lib/format/references.mjs.map +1 -1
- package/src/lib/format/seats-checks.d.mts +3 -0
- package/src/lib/format/seats-checks.d.ts +3 -0
- package/src/lib/format/seats-checks.js +13 -0
- package/src/lib/format/seats-checks.js.map +1 -0
- package/src/lib/format/seats-checks.mjs +10 -0
- package/src/lib/format/seats-checks.mjs.map +1 -0
- package/src/lib/format/sheet-head-checks.d.mts +4 -0
- package/src/lib/format/sheet-head-checks.d.ts +4 -0
- package/src/lib/format/sheet-head-checks.js +68 -0
- package/src/lib/format/sheet-head-checks.js.map +1 -0
- package/src/lib/format/sheet-head-checks.mjs +65 -0
- package/src/lib/format/sheet-head-checks.mjs.map +1 -0
- package/src/lib/format/signature-checks.d.mts +7 -0
- package/src/lib/format/signature-checks.d.ts +7 -0
- package/src/lib/format/signature-checks.js +32 -0
- package/src/lib/format/signature-checks.js.map +1 -0
- package/src/lib/format/signature-checks.mjs +29 -0
- package/src/lib/format/signature-checks.mjs.map +1 -0
- package/src/lib/format/signature-options.d.mts +24 -0
- package/src/lib/format/signature-options.d.ts +24 -0
- package/src/lib/format/signature-options.js +30 -0
- package/src/lib/format/signature-options.js.map +1 -0
- package/src/lib/format/signature-options.mjs +26 -0
- package/src/lib/format/signature-options.mjs.map +1 -0
- package/src/lib/format/slots-checks.d.mts +4 -0
- package/src/lib/format/slots-checks.d.ts +4 -0
- package/src/lib/format/slots-checks.js +21 -0
- package/src/lib/format/slots-checks.js.map +1 -0
- package/src/lib/format/slots-checks.mjs +18 -0
- package/src/lib/format/slots-checks.mjs.map +1 -0
- package/src/lib/format/statusbar-checks.d.mts +8 -0
- package/src/lib/format/statusbar-checks.d.ts +8 -0
- package/src/lib/format/statusbar-checks.js +35 -0
- package/src/lib/format/statusbar-checks.js.map +1 -0
- package/src/lib/format/statusbar-checks.mjs +32 -0
- package/src/lib/format/statusbar-checks.mjs.map +1 -0
- package/src/lib/format/table-checks.d.mts +21 -0
- package/src/lib/format/table-checks.d.ts +21 -0
- package/src/lib/format/table-checks.js +86 -0
- package/src/lib/format/table-checks.js.map +1 -0
- package/src/lib/format/table-checks.mjs +82 -0
- package/src/lib/format/table-checks.mjs.map +1 -0
- package/src/lib/format/translate.js +2 -2
- package/src/lib/format/translate.js.map +1 -1
- package/src/lib/format/translate.mjs +2 -2
- package/src/lib/format/translate.mjs.map +1 -1
- package/src/lib/format/upgrade.d.mts +6 -3
- package/src/lib/format/upgrade.d.ts +6 -3
- package/src/lib/format/upgrade.js +6 -3
- package/src/lib/format/upgrade.js.map +1 -1
- package/src/lib/format/upgrade.mjs +6 -3
- package/src/lib/format/upgrade.mjs.map +1 -1
- package/src/lib/format/validate.d.mts +18 -2
- package/src/lib/format/validate.d.ts +18 -2
- package/src/lib/format/validate.js +36 -3
- package/src/lib/format/validate.js.map +1 -1
- package/src/lib/format/validate.mjs +35 -3
- package/src/lib/format/validate.mjs.map +1 -1
- package/src/lib/format/version.d.mts +2 -2
- package/src/lib/format/version.d.ts +2 -2
- package/src/lib/format/version.js +2 -2
- package/src/lib/format/version.js.map +1 -1
- package/src/lib/format/version.mjs +2 -2
- package/src/lib/format/version.mjs.map +1 -1
- package/src/lib/format/words.d.mts +6 -2
- package/src/lib/format/words.d.ts +6 -2
- package/src/lib/format/words.js +13 -7
- package/src/lib/format/words.js.map +1 -1
- package/src/lib/format/words.mjs +13 -7
- package/src/lib/format/words.mjs.map +1 -1
- package/src/lib/parts.d.mts +45 -0
- package/src/lib/parts.d.ts +45 -0
- package/src/lib/parts.js +60 -0
- package/src/lib/parts.js.map +1 -0
- package/src/lib/parts.mjs +53 -0
- package/src/lib/parts.mjs.map +1 -0
- package/src/lib/record/cells.d.mts +3 -3
- package/src/lib/record/cells.d.ts +3 -3
- package/src/lib/record/cells.js +16 -15
- package/src/lib/record/cells.js.map +1 -1
- package/src/lib/record/cells.mjs +16 -15
- package/src/lib/record/cells.mjs.map +1 -1
- package/src/lib/record/compute.d.mts +16 -5
- package/src/lib/record/compute.d.ts +16 -5
- package/src/lib/record/compute.js +57 -16
- package/src/lib/record/compute.js.map +1 -1
- package/src/lib/record/compute.mjs +57 -16
- package/src/lib/record/compute.mjs.map +1 -1
- package/src/lib/record/data-source.d.mts +140 -3
- package/src/lib/record/data-source.d.ts +140 -3
- package/src/lib/record/data-source.js.map +1 -1
- package/src/lib/record/data-source.mjs.map +1 -1
- package/src/lib/record/drafts.d.mts +19 -0
- package/src/lib/record/drafts.d.ts +19 -0
- package/src/lib/record/drafts.js +35 -0
- package/src/lib/record/drafts.js.map +1 -0
- package/src/lib/record/drafts.mjs +31 -0
- package/src/lib/record/drafts.mjs.map +1 -0
- package/src/lib/record/env.d.mts +5 -1
- package/src/lib/record/env.d.ts +5 -1
- package/src/lib/record/env.js +9 -1
- package/src/lib/record/env.js.map +1 -1
- package/src/lib/record/env.mjs +8 -1
- package/src/lib/record/env.mjs.map +1 -1
- package/src/lib/record/form-limits.d.mts +81 -0
- package/src/lib/record/form-limits.d.ts +81 -0
- package/src/lib/record/form-limits.js +231 -0
- package/src/lib/record/form-limits.js.map +1 -0
- package/src/lib/record/form-limits.mjs +224 -0
- package/src/lib/record/form-limits.mjs.map +1 -0
- package/src/lib/record/form.d.mts +173 -4
- package/src/lib/record/form.d.ts +173 -4
- package/src/lib/record/form.js +792 -88
- package/src/lib/record/form.js.map +1 -1
- package/src/lib/record/form.mjs +792 -88
- package/src/lib/record/form.mjs.map +1 -1
- package/src/lib/record/limits-part.d.mts +6 -0
- package/src/lib/record/limits-part.d.ts +6 -0
- package/src/lib/record/limits-part.js +12 -0
- package/src/lib/record/limits-part.js.map +1 -0
- package/src/lib/record/limits-part.mjs +7 -0
- package/src/lib/record/limits-part.mjs.map +1 -0
- package/src/lib/record/line-choices.d.mts +30 -0
- package/src/lib/record/line-choices.d.ts +30 -0
- package/src/lib/record/line-choices.js +42 -0
- package/src/lib/record/line-choices.js.map +1 -0
- package/src/lib/record/line-choices.mjs +36 -0
- package/src/lib/record/line-choices.mjs.map +1 -0
- package/src/lib/record/locales/ar.js +10 -0
- package/src/lib/record/locales/ar.js.map +1 -1
- package/src/lib/record/locales/ar.mjs +10 -0
- package/src/lib/record/locales/ar.mjs.map +1 -1
- package/src/lib/record/locales/de.js +10 -0
- package/src/lib/record/locales/de.js.map +1 -1
- package/src/lib/record/locales/de.mjs +10 -0
- package/src/lib/record/locales/de.mjs.map +1 -1
- package/src/lib/record/locales/fr.js +10 -0
- package/src/lib/record/locales/fr.js.map +1 -1
- package/src/lib/record/locales/fr.mjs +10 -0
- package/src/lib/record/locales/fr.mjs.map +1 -1
- package/src/lib/record/memory-data-source.d.mts +44 -3
- package/src/lib/record/memory-data-source.d.ts +44 -3
- package/src/lib/record/memory-data-source.js +102 -40
- package/src/lib/record/memory-data-source.js.map +1 -1
- package/src/lib/record/memory-data-source.mjs +101 -40
- package/src/lib/record/memory-data-source.mjs.map +1 -1
- package/src/lib/record/memory-kept.d.mts +20 -0
- package/src/lib/record/memory-kept.d.ts +20 -0
- package/src/lib/record/memory-kept.js +46 -0
- package/src/lib/record/memory-kept.js.map +1 -0
- package/src/lib/record/memory-kept.mjs +43 -0
- package/src/lib/record/memory-kept.mjs.map +1 -0
- package/src/lib/record/memory-later.d.mts +19 -0
- package/src/lib/record/memory-later.d.ts +19 -0
- package/src/lib/record/memory-later.js +39 -0
- package/src/lib/record/memory-later.js.map +1 -0
- package/src/lib/record/memory-later.mjs +35 -0
- package/src/lib/record/memory-later.mjs.map +1 -0
- package/src/lib/record/memory-part.d.mts +7 -0
- package/src/lib/record/memory-part.d.ts +7 -0
- package/src/lib/record/memory-part.js +13 -0
- package/src/lib/record/memory-part.js.map +1 -0
- package/src/lib/record/memory-part.mjs +8 -0
- package/src/lib/record/memory-part.mjs.map +1 -0
- package/src/lib/record/messages.d.mts +17 -0
- package/src/lib/record/messages.d.ts +17 -0
- package/src/lib/record/messages.js +10 -0
- package/src/lib/record/messages.js.map +1 -1
- package/src/lib/record/messages.mjs +10 -0
- package/src/lib/record/messages.mjs.map +1 -1
- package/src/lib/record/option-rules.d.mts +50 -0
- package/src/lib/record/option-rules.d.ts +50 -0
- package/src/lib/record/option-rules.js +137 -0
- package/src/lib/record/option-rules.js.map +1 -0
- package/src/lib/record/option-rules.mjs +134 -0
- package/src/lib/record/option-rules.mjs.map +1 -0
- package/src/lib/record/rules.d.mts +2 -2
- package/src/lib/record/rules.d.ts +2 -2
- package/src/lib/record/rules.js +3 -3
- package/src/lib/record/rules.js.map +1 -1
- package/src/lib/record/rules.mjs +3 -3
- package/src/lib/record/rules.mjs.map +1 -1
- package/src/lib/record/run.js +9 -7
- package/src/lib/record/run.js.map +1 -1
- package/src/lib/record/run.mjs +9 -7
- package/src/lib/record/run.mjs.map +1 -1
- package/src/lib/record/search.d.mts +12 -2
- package/src/lib/record/search.d.ts +12 -2
- package/src/lib/record/search.js +29 -4
- package/src/lib/record/search.js.map +1 -1
- package/src/lib/record/search.mjs +29 -4
- package/src/lib/record/search.mjs.map +1 -1
- package/src/lib/record/set-when.d.mts +3 -3
- package/src/lib/record/set-when.d.ts +3 -3
- package/src/lib/record/set-when.js +9 -9
- package/src/lib/record/set-when.js.map +1 -1
- package/src/lib/record/set-when.mjs +9 -9
- package/src/lib/record/set-when.mjs.map +1 -1
- package/src/lib/record/signed.d.mts +21 -0
- package/src/lib/record/signed.d.ts +21 -0
- package/src/lib/record/signed.js +62 -0
- package/src/lib/record/signed.js.map +1 -0
- package/src/lib/record/signed.mjs +57 -0
- package/src/lib/record/signed.mjs.map +1 -0
- package/src/lib/record/taking.d.mts +60 -0
- package/src/lib/record/taking.d.ts +60 -0
- package/src/lib/record/taking.js +58 -0
- package/src/lib/record/taking.js.map +1 -0
- package/src/lib/record/taking.mjs +50 -0
- package/src/lib/record/taking.mjs.map +1 -0
- package/src/lib/record/value-map.js +11 -4
- package/src/lib/record/value-map.js.map +1 -1
- package/src/lib/record/value-map.mjs +11 -4
- package/src/lib/record/value-map.mjs.map +1 -1
- package/src/lib/record/values.d.mts +20 -0
- package/src/lib/record/values.d.ts +20 -0
- package/src/lib/record/values.js.map +1 -1
- package/src/lib/record/values.mjs.map +1 -1
- package/src/lib/record/waits.d.mts +6 -1
- package/src/lib/record/waits.d.ts +6 -1
- package/src/lib/record/waits.js +2 -2
- package/src/lib/record/waits.js.map +1 -1
- package/src/lib/record/waits.mjs +2 -2
- package/src/lib/record/waits.mjs.map +1 -1
- package/src/runtime.d.mts +16 -5
- package/src/runtime.d.ts +16 -5
- package/src/runtime.js +41 -3
- package/src/runtime.js.map +1 -1
- package/src/runtime.mjs +13 -0
- package/src/runtime.mjs.map +1 -1
|
@@ -1,34 +1,32 @@
|
|
|
1
|
+
import { changesOptions, optionsList } from './choice-options.mjs';
|
|
1
2
|
import { BUILT_IN_NAMES, USER_PARTS } from '../expression/built-ins.mjs';
|
|
2
3
|
import { compileModifier } from '../expression/modifier.mjs';
|
|
3
4
|
import { compileExpression } from '../expression/expression.mjs';
|
|
5
|
+
import { functionTable } from '../expression/app-functions.mjs';
|
|
4
6
|
import { dependencyOrder } from '../expression/order.mjs';
|
|
5
7
|
import { valuesIn } from './words.mjs';
|
|
6
8
|
import { BROWSER_HOTKEYS, hotkeyWords } from './hotkeys.mjs';
|
|
7
9
|
import { checkButtonParts, checkHead } from './head-checks.mjs';
|
|
8
10
|
import { checkSectionTracks } from './section-checks.mjs';
|
|
9
11
|
import { fieldPattern } from './pattern.mjs';
|
|
10
|
-
import { BULK_KINDS, bulkSettable } from './bulk-edit.mjs';
|
|
11
12
|
import { DUPLICATE_KINDS } from './duplicate-kinds.mjs';
|
|
12
|
-
import { IMPORT_KINDS, importable } from './import-fields.mjs';
|
|
13
13
|
import { isTimeZone } from '../record/zones.mjs';
|
|
14
14
|
import { fill, MESSAGES } from '../record/messages.mjs';
|
|
15
15
|
import { autofillGroupProblem } from './autofill-group.mjs';
|
|
16
|
+
import { checkLimits } from './limits-checks.mjs';
|
|
17
|
+
import { checkSeats } from './seats-checks.mjs';
|
|
18
|
+
import { checkList, checkRelated } from './list-checks.mjs';
|
|
19
|
+
import { checkSlots } from './slots-checks.mjs';
|
|
20
|
+
import { checkSignature } from './signature-checks.mjs';
|
|
21
|
+
import { checkEndings } from './endings-checks.mjs';
|
|
22
|
+
import { checkAddressSearch, checkMap, checkPlace } from './map-checks.mjs';
|
|
23
|
+
import { checkLineKinds, checkTable } from './table-checks.mjs';
|
|
24
|
+
import { checkStatusbar } from './statusbar-checks.mjs';
|
|
25
|
+
import { checkOptionRules } from './option-checks.mjs';
|
|
16
26
|
const has = (record, key) => Object.prototype.hasOwnProperty.call(record, key);
|
|
17
27
|
/** The kinds of field an expression can give a value to: the rest hold records, lines or files. */
|
|
18
28
|
const EXPRESSIBLE = new Set(['char', 'text', 'html', 'integer', 'float', 'monetary', 'boolean', 'date', 'datetime', 'selection', 'json']);
|
|
19
29
|
const EXPRESSIBLE_WORDS = 'text, numbers, yes or no, dates, choices and json can';
|
|
20
|
-
/** The kinds of field a list's filter bar can list the values of. */
|
|
21
|
-
const LISTS_ITS_VALUES = new Set(['selection', 'many2one', 'many2many', 'boolean', 'char', 'integer']);
|
|
22
|
-
/** Kinds of field a list's card shows as its picture: a picture held, or text holding a picture's address. */
|
|
23
|
-
const PICTURES = new Set(['image', 'binary', 'char']);
|
|
24
|
-
/** What a calendar's start and end can be: a day, or a day and a time. */
|
|
25
|
-
const CALENDAR_TIMES = new Set(['date', 'datetime']);
|
|
26
|
-
/** What a record's words on a calendar can be. */
|
|
27
|
-
const CALENDAR_WORDS = new Set(['char', 'text', 'selection', 'many2one', 'integer', 'float', 'monetary', 'date', 'datetime']);
|
|
28
|
-
/** What a calendar's columns come from: a value that repeats. */
|
|
29
|
-
const CALENDAR_COLUMNS = new Set(['many2one', 'selection', 'char', 'integer']);
|
|
30
|
-
/** What a record's colour comes from. */
|
|
31
|
-
const CALENDAR_COLOURS = new Set(['many2one', 'selection', 'char', 'integer', 'boolean']);
|
|
32
30
|
/** Names quoted and joined as a person lists them: "a", "b" and "c". */
|
|
33
31
|
const listed = (names) => {
|
|
34
32
|
const quoted = names.map((name) => `"${name}"`);
|
|
@@ -38,7 +36,12 @@ const listed = (names) => {
|
|
|
38
36
|
export class ReferenceCheck {
|
|
39
37
|
page;
|
|
40
38
|
line;
|
|
39
|
+
options;
|
|
41
40
|
issues = [];
|
|
41
|
+
/** What does not stop the page, said beside it: the kinds an expression hands its functions (`CheckOptions.kinds`). */
|
|
42
|
+
warnings = [];
|
|
43
|
+
/** The parts whose checks are owed, not come yet (the one-tag script's: later-checks.ts): run once they have. */
|
|
44
|
+
later = new Set();
|
|
42
45
|
ids = new Map();
|
|
43
46
|
/** Where the answers of each saved form placed here go, by name: the path of the part that took it first. */
|
|
44
47
|
formNames = new Map();
|
|
@@ -47,11 +50,23 @@ export class ReferenceCheck {
|
|
|
47
50
|
goingTo = [];
|
|
48
51
|
/** The names of the slots the layout places. */
|
|
49
52
|
slots = new Set();
|
|
50
|
-
|
|
53
|
+
/** The app's functions, as the page's expressions call them; or why the names given cannot be. */
|
|
54
|
+
table;
|
|
55
|
+
tableProblem;
|
|
56
|
+
constructor(page, line, options = {}) {
|
|
51
57
|
this.page = page;
|
|
52
58
|
this.line = line;
|
|
59
|
+
this.options = options;
|
|
60
|
+
try {
|
|
61
|
+
this.table = functionTable(options.functions);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
this.tableProblem = error.message;
|
|
65
|
+
}
|
|
53
66
|
}
|
|
54
67
|
run() {
|
|
68
|
+
if (this.tableProblem)
|
|
69
|
+
this.report('(functions)', this.tableProblem);
|
|
55
70
|
if (this.page.timeZone !== undefined)
|
|
56
71
|
this.checkZone(this.page.timeZone, 'timeZone');
|
|
57
72
|
this.checkFields(this.page.fields, 'fields', this.line?.name, this.line?.parent);
|
|
@@ -63,9 +78,10 @@ export class ReferenceCheck {
|
|
|
63
78
|
walkNode: (node, path) => this.walkNode(node, path),
|
|
64
79
|
checkModifiers: (owner, path, keys) => this.checkModifiers(owner, path, keys),
|
|
65
80
|
checkSteps: (steps, path) => this.checkSteps(steps, path),
|
|
66
|
-
checkPress: (button, path) => this.checkPress(button, path),
|
|
81
|
+
checkPress: (button, path) => this.checkPress(button, path, true),
|
|
67
82
|
isPart: (id) => this.ids.has(id),
|
|
68
83
|
isPlace: (id) => this.places.has(id),
|
|
84
|
+
owe: (part) => this.later.add(part),
|
|
69
85
|
});
|
|
70
86
|
if (this.page.on)
|
|
71
87
|
this.checkEvents(this.page.on, 'on');
|
|
@@ -75,6 +91,16 @@ export class ReferenceCheck {
|
|
|
75
91
|
this.checkDuplicates(this.page);
|
|
76
92
|
if (this.page.records !== undefined && !this.line)
|
|
77
93
|
this.checkRecords(this.page.records);
|
|
94
|
+
if (this.page.endings !== undefined && !this.line) {
|
|
95
|
+
if (this.page.data.kind !== 'responses')
|
|
96
|
+
this.report('endings', 'endings are for a page of responses, shown once it is sent');
|
|
97
|
+
// Each ending: in the one-tag script, its checks come with the answers part, which a page with any asks for.
|
|
98
|
+
if (Array.isArray(this.page.endings) && this.page.endings.length)
|
|
99
|
+
checkEndings(this.page, { ...this.fieldTools(), checkModifiers: (owner, path, keys) => this.checkModifiers(owner, path, keys), checkWords: (owner, path, keys, sent) => this.checkWords(owner, path, keys, sent) });
|
|
100
|
+
}
|
|
101
|
+
// A page's limits and Continue later: checked where it has either (in the one-tag script, by the limits part it asks for).
|
|
102
|
+
if (!this.line && (this.page.limits !== undefined || this.page.continueLater !== undefined))
|
|
103
|
+
checkLimits(this.page, { report: (path, message) => this.report(path, message), owe: (part) => this.later.add(part) });
|
|
78
104
|
this.checkGoingTo();
|
|
79
105
|
return this.issues;
|
|
80
106
|
}
|
|
@@ -139,7 +165,7 @@ export class ReferenceCheck {
|
|
|
139
165
|
if (!Array.isArray(children))
|
|
140
166
|
return this.report(`${path}.lineForm`, 'a line’s form holds its parts: { "children": [...] }');
|
|
141
167
|
const page = { ...this.page, fields: def.fields, layout: { type: 'sections', id: `${node.id}#line`, children }, on: undefined };
|
|
142
|
-
const inside = new ReferenceCheck(page, { name: node.field, parent: this.page.fields });
|
|
168
|
+
const inside = new ReferenceCheck(page, { name: node.field, parent: this.page.fields }, this.options);
|
|
143
169
|
inside.walkChildren(children, `${path}.lineForm`);
|
|
144
170
|
inside.checkGoingTo();
|
|
145
171
|
this.issues.push(...inside.issues);
|
|
@@ -161,8 +187,10 @@ export class ReferenceCheck {
|
|
|
161
187
|
this.checkSteps(steps, `${path}.show.${target}`);
|
|
162
188
|
}
|
|
163
189
|
}
|
|
164
|
-
/** A press: steps, an app action, or both — and its steps checked. */
|
|
165
|
-
checkPress(button, path) {
|
|
190
|
+
/** A press: steps, an app action, or both — and its steps checked; on a button drawn on the record (`drawn`, not a list's row's), the `{field}`s its words show. */
|
|
191
|
+
checkPress(button, path, drawn = false) {
|
|
192
|
+
if (drawn && 'type' in button)
|
|
193
|
+
this.checkWords(button, path, ['label', 'pressedLabel']);
|
|
166
194
|
// A button with a menu of its own and nothing else is a menu button.
|
|
167
195
|
const menu = 'menu' in button && !!button.menu?.length;
|
|
168
196
|
if (!button.steps && button.action === undefined && !menu)
|
|
@@ -229,7 +257,7 @@ export class ReferenceCheck {
|
|
|
229
257
|
for (const [name, value] of Object.entries(step.into ?? {})) {
|
|
230
258
|
this.need(name, `${at}.into.${name}`);
|
|
231
259
|
try {
|
|
232
|
-
compileExpression(value);
|
|
260
|
+
compileExpression(value, this.table);
|
|
233
261
|
}
|
|
234
262
|
catch (error) {
|
|
235
263
|
this.report(`${at}.into.${name}`, `cannot read "${value}": ${error.message}`);
|
|
@@ -246,10 +274,22 @@ export class ReferenceCheck {
|
|
|
246
274
|
}
|
|
247
275
|
});
|
|
248
276
|
}
|
|
249
|
-
/**
|
|
250
|
-
|
|
277
|
+
/**
|
|
278
|
+
* Words that show fields' values: each `{name}` a field of this page — in an ending's (`sent`), or `id`, the id the
|
|
279
|
+
* app's source gave the response.
|
|
280
|
+
*/
|
|
281
|
+
checkValuesIn(words, path, sent = false) {
|
|
251
282
|
for (const name of valuesIn(words))
|
|
252
|
-
this.
|
|
283
|
+
if (!(sent && name === 'id' && !has(this.page.fields, 'id')))
|
|
284
|
+
this.need(name, path);
|
|
285
|
+
}
|
|
286
|
+
/** The words of a part that may show the answers given (`keys` of `owner`): each `{name}` a field of the page. */
|
|
287
|
+
checkWords(owner, path, keys, sent = false) {
|
|
288
|
+
for (const key of keys) {
|
|
289
|
+
const words = owner[key];
|
|
290
|
+
if (typeof words === 'string')
|
|
291
|
+
this.checkValuesIn(words, `${path}.${key}`, sent);
|
|
292
|
+
}
|
|
253
293
|
}
|
|
254
294
|
report(path, message) {
|
|
255
295
|
this.issues.push({ path, message });
|
|
@@ -307,13 +347,14 @@ export class ReferenceCheck {
|
|
|
307
347
|
continue;
|
|
308
348
|
let compiled;
|
|
309
349
|
try {
|
|
310
|
-
compiled = compileModifier(value);
|
|
350
|
+
compiled = compileModifier(value, this.table);
|
|
311
351
|
}
|
|
312
352
|
catch (error) {
|
|
313
353
|
this.report(`${path}.${key}`, `cannot read "${value}": ${error.message}`);
|
|
314
354
|
continue;
|
|
315
355
|
}
|
|
316
356
|
this.checkReads(value, compiled, `${path}.${key}`, scope);
|
|
357
|
+
this.checkKinds(value, `${path}.${key}`, scope);
|
|
317
358
|
}
|
|
318
359
|
}
|
|
319
360
|
/**
|
|
@@ -329,7 +370,7 @@ export class ReferenceCheck {
|
|
|
329
370
|
continue;
|
|
330
371
|
let fields;
|
|
331
372
|
try {
|
|
332
|
-
fields = compileModifier(where.condition).fields;
|
|
373
|
+
fields = compileModifier(where.condition, this.table).fields;
|
|
333
374
|
}
|
|
334
375
|
catch (error) {
|
|
335
376
|
this.report(path, `cannot read the condition "${where.condition}": ${error.message}`);
|
|
@@ -360,6 +401,14 @@ export class ReferenceCheck {
|
|
|
360
401
|
this.report(path, `"${source}" reads "${name}", which is not a field ${where}`);
|
|
361
402
|
}
|
|
362
403
|
}
|
|
404
|
+
/** The kinds of value an expression hands its functions, where the check reads them (`CheckOptions.kinds`). */
|
|
405
|
+
checkKinds(source, path, scope) {
|
|
406
|
+
if (!this.options.kinds)
|
|
407
|
+
return;
|
|
408
|
+
const parent = scope.lines === undefined ? undefined : (scope.parent ?? this.page.fields);
|
|
409
|
+
for (const words of this.options.kinds(source, { fields: scope.fields, ...(parent ? { parent } : {}) }))
|
|
410
|
+
this.warnings.push({ path, message: `"${source}": ${words}` });
|
|
411
|
+
}
|
|
363
412
|
/**
|
|
364
413
|
* An expression for a value must read, read only fields where it is worked
|
|
365
414
|
* out, and add up only line fields that exist. Returns the fields it reads,
|
|
@@ -368,13 +417,14 @@ export class ReferenceCheck {
|
|
|
368
417
|
checkExpression(source, path, scope) {
|
|
369
418
|
let compiled;
|
|
370
419
|
try {
|
|
371
|
-
compiled = compileExpression(source);
|
|
420
|
+
compiled = compileExpression(source, this.table);
|
|
372
421
|
}
|
|
373
422
|
catch (error) {
|
|
374
423
|
this.report(path, `cannot read "${source}": ${error.message}`);
|
|
375
424
|
return null;
|
|
376
425
|
}
|
|
377
426
|
this.checkReads(source, compiled, path, scope);
|
|
427
|
+
this.checkKinds(source, path, scope);
|
|
378
428
|
for (const sum of compiled.sums) {
|
|
379
429
|
if (!has(scope.fields, sum.lines))
|
|
380
430
|
continue;
|
|
@@ -418,11 +468,30 @@ export class ReferenceCheck {
|
|
|
418
468
|
this.report(`${base}.${cycle[0]}.compute`, message);
|
|
419
469
|
}
|
|
420
470
|
}
|
|
471
|
+
/**
|
|
472
|
+
* A choice's options, a page's own and a table line's alike: each condition on one reads the record — a line's, its
|
|
473
|
+
* line and `parent.` (`options[].invisible`); its list's `dependsOn` names fields (`checkDependsOn`); an earlier
|
|
474
|
+
* question it carries them from (`optionsFrom.field`) is another choice of the page, never one carrying its own from
|
|
475
|
+
* this one — and never for a line, whose choices are its own or one of the app's lists.
|
|
476
|
+
*/
|
|
477
|
+
checkChoices(name, def, path, fields, scope) {
|
|
478
|
+
optionsList(def.optionsFrom)?.dependsOn?.forEach((other, i) => this.checkDependsOn(name, other, `${path}.optionsFrom.dependsOn[${i}]`, fields, scope.lines, scope.parent));
|
|
479
|
+
// Options by condition, or carried from an earlier question: in the one-tag script, checked by the options part a
|
|
480
|
+
// page with any asks for.
|
|
481
|
+
if (changesOptions(def))
|
|
482
|
+
checkOptionRules(name, def, path, fields, scope, { ...this.fieldTools(), checkModifiers: (owner, at, keys, inside) => this.checkModifiers(owner, at, keys, inside) });
|
|
483
|
+
}
|
|
421
484
|
checkFields(fields, base, lines, parent) {
|
|
422
485
|
const scope = { fields, lines, ...(parent ? { parent } : {}) };
|
|
423
486
|
this.checkWorkedOut(fields, base, scope);
|
|
424
487
|
for (const [name, def] of Object.entries(fields)) {
|
|
425
488
|
const path = `${base}.${name}`;
|
|
489
|
+
// A field's label, its help and its choices' words may show the answers given — the page's own fields', not a line's.
|
|
490
|
+
if (lines === undefined) {
|
|
491
|
+
this.checkWords(def, path, ['label', 'help']);
|
|
492
|
+
if (def.type === 'selection' && Array.isArray(def.options))
|
|
493
|
+
def.options.forEach((option, i) => this.checkWords(option ?? {}, `${path}.options[${i}]`, ['label']));
|
|
494
|
+
}
|
|
426
495
|
if (def.type === 'monetary' && def.currencyField !== undefined)
|
|
427
496
|
this.checkCurrency(def.currencyField, `${path}.currencyField`, fields, lines, parent);
|
|
428
497
|
// What an amount is for, by a condition on the record it is on: "/month" on a rent.
|
|
@@ -462,7 +531,8 @@ export class ReferenceCheck {
|
|
|
462
531
|
this.checkZone(linked.timeZone, `${path}.fields.${column}.timeZone`);
|
|
463
532
|
}
|
|
464
533
|
if (def.type === 'selection')
|
|
465
|
-
|
|
534
|
+
this.checkChoices(name, def, path, fields, scope);
|
|
535
|
+
checkSeats(def, path, lines ?? this.line?.name, (at, message) => this.report(at, message));
|
|
466
536
|
if (def.type === 'properties')
|
|
467
537
|
def.definitionsFrom?.dependsOn?.forEach((other, i) => this.need(other, `${path}.definitionsFrom.dependsOn[${i}]`, fields));
|
|
468
538
|
if (def.type === 'properties' && def.definitions) {
|
|
@@ -480,7 +550,7 @@ export class ReferenceCheck {
|
|
|
480
550
|
// Its lines read the record they are on — on a line, that line — as parent.
|
|
481
551
|
this.checkFields(def.fields, `${path}.fields`, name, fields);
|
|
482
552
|
if ('lineKinds' in def && def.lineKinds)
|
|
483
|
-
|
|
553
|
+
checkLineKinds(name, def.lineKinds, def.fields, `${path}.lineKinds`, this.fieldTools());
|
|
484
554
|
if (def.sequenceField !== undefined) {
|
|
485
555
|
const sequence = def.fields[def.sequenceField];
|
|
486
556
|
if (!sequence)
|
|
@@ -492,6 +562,21 @@ export class ReferenceCheck {
|
|
|
492
562
|
}
|
|
493
563
|
}
|
|
494
564
|
}
|
|
565
|
+
/**
|
|
566
|
+
* A field a choice's list changes with: one beside it, or on a line `parent.` and a field of the record it is on. A
|
|
567
|
+
* line's list never changes with its own column: choosing would ask for its choices again.
|
|
568
|
+
*/
|
|
569
|
+
checkDependsOn(own, name, path, fields, lines, parent = this.page.fields) {
|
|
570
|
+
if (!name.startsWith('parent.')) {
|
|
571
|
+
if (lines !== undefined && name === own)
|
|
572
|
+
return this.report(path, `"${own}" cannot change with itself`);
|
|
573
|
+
return void this.need(name, path, fields);
|
|
574
|
+
}
|
|
575
|
+
if (lines === undefined)
|
|
576
|
+
return this.report(path, `"${name}": only a line’s choices read parent, the record it is on`);
|
|
577
|
+
if (!has(parent, name.slice('parent.'.length)))
|
|
578
|
+
this.report(path, `"${name}": "${name.slice('parent.'.length)}" is not a field of this page`);
|
|
579
|
+
}
|
|
495
580
|
/** A money field's currency: a field beside it, or on a line `parent.` and a field of the record — one that holds a currency. */
|
|
496
581
|
checkCurrency(name, path, fields, lines, parent = this.page.fields) {
|
|
497
582
|
let currency;
|
|
@@ -507,33 +592,55 @@ export class ReferenceCheck {
|
|
|
507
592
|
this.report(path, `"${name}" is a ${currency.type}; a currency field must be a many2one, selection or char`);
|
|
508
593
|
}
|
|
509
594
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
this.
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
this.
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
595
|
+
/** The tools a table of lines' checks are handed (table-checks.ts). */
|
|
596
|
+
tableTools() {
|
|
597
|
+
return {
|
|
598
|
+
...this.fieldTools(),
|
|
599
|
+
claim: (id, path) => this.claim(id, path),
|
|
600
|
+
checkModifiers: (owner, path, keys, scope) => this.checkModifiers(owner, path, keys, scope),
|
|
601
|
+
checkPress: (button, path) => this.checkPress(button, path),
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
/** The tools a part's checks of a field are handed (part-checks.ts). */
|
|
605
|
+
fieldTools() {
|
|
606
|
+
return { page: this.page, need: (name, path, scope) => this.need(name, path, scope), report: (path, message) => this.report(path, message), owe: (part) => this.later.add(part) };
|
|
607
|
+
}
|
|
608
|
+
/** The tools a map's checks are handed (map-checks.ts). */
|
|
609
|
+
mapTools() {
|
|
610
|
+
return {
|
|
611
|
+
page: this.page,
|
|
612
|
+
need: (name, path, scope) => this.need(name, path, scope),
|
|
613
|
+
report: (path, message) => this.report(path, message),
|
|
614
|
+
checkModifiers: (owner, path, keys, scope) => this.checkModifiers(owner, path, keys, scope),
|
|
615
|
+
checkSteps: (steps, path) => this.checkSteps(steps, path),
|
|
616
|
+
here: () => this.here(),
|
|
617
|
+
owe: (part) => this.later.add(part),
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
/** The tools a list page's own checks are handed (list-checks.ts). */
|
|
621
|
+
listTools() {
|
|
622
|
+
return {
|
|
623
|
+
page: this.page,
|
|
624
|
+
need: (name, path, scope) => this.need(name, path, scope),
|
|
625
|
+
report: (path, message) => this.report(path, message),
|
|
626
|
+
claim: (id, path) => this.claim(id, path),
|
|
627
|
+
checkModifiers: (owner, path, keys, scope) => this.checkModifiers(owner, path, keys, scope),
|
|
628
|
+
checkPress: (button, path) => this.checkPress(button, path),
|
|
629
|
+
checkPlace: (at, path, scope, pin, card) => checkPlace(at, path, scope, pin, card, this.mapTools()),
|
|
630
|
+
here: () => this.here(),
|
|
631
|
+
onLine: !!this.line,
|
|
632
|
+
checkCurrency: (name, path, fields, lines, parent) => this.checkCurrency(name, path, fields, lines, parent),
|
|
633
|
+
checkZone: (zone, path) => this.checkZone(zone, path),
|
|
634
|
+
checkFilterItem: (item, at, scope) => this.checkFilterItem(item, at, scope),
|
|
635
|
+
owe: (part) => this.later.add(part),
|
|
636
|
+
};
|
|
530
637
|
}
|
|
531
638
|
/** A page's own buttons at its foot: buttons as any. */
|
|
532
639
|
walkFooter(footer, path) {
|
|
533
640
|
footer?.forEach((button, i) => {
|
|
534
641
|
this.claim(button.id, `${path}.footer[${i}]`);
|
|
535
642
|
this.checkModifiers(button, `${path}.footer[${i}]`);
|
|
536
|
-
this.checkPress(button, `${path}.footer[${i}]
|
|
643
|
+
this.checkPress(button, `${path}.footer[${i}]`, true);
|
|
537
644
|
});
|
|
538
645
|
}
|
|
539
646
|
walkRoot(root, path) {
|
|
@@ -547,405 +654,18 @@ export class ReferenceCheck {
|
|
|
547
654
|
if (root.type === 'sections')
|
|
548
655
|
return this.walkChildren(root.children, path);
|
|
549
656
|
if (root.type === 'list')
|
|
550
|
-
return
|
|
657
|
+
return checkList(root, path, this.listTools());
|
|
658
|
+
// A wizard's own buttons' words, and each step's title and words, may show the answers given: "Your visit to {branch}".
|
|
659
|
+
this.checkWords(root, path, ['nextLabel', 'backLabel', 'finishLabel']);
|
|
551
660
|
root.children.forEach((step, i) => {
|
|
552
661
|
const stepPath = `${path}.children[${i}]`;
|
|
553
662
|
this.places.add(step.id);
|
|
554
663
|
this.claim(step.id, stepPath);
|
|
555
664
|
this.checkModifiers(step, stepPath);
|
|
665
|
+
this.checkWords(step, stepPath, ['label', 'description']);
|
|
556
666
|
this.walkChildren(step.children, stepPath);
|
|
557
667
|
});
|
|
558
668
|
}
|
|
559
|
-
walkList(list, path) {
|
|
560
|
-
if (this.page.data.kind !== 'record')
|
|
561
|
-
this.report('data', 'a list shows records: its data needs kind "record" and a model');
|
|
562
|
-
this.checkListed(list, this.page.fields, path);
|
|
563
|
-
list.groupBy?.forEach((name, i) => this.need(name, `${path}.groupBy[${i}]`));
|
|
564
|
-
// A list's filters are about its own records: their fields are the page's, and there is no record to take a value from.
|
|
565
|
-
const walk = (item, at) => {
|
|
566
|
-
if ('any' in item)
|
|
567
|
-
return item.any.forEach((inner, i) => walk(inner, `${at}.any[${i}]`));
|
|
568
|
-
if ('all' in item)
|
|
569
|
-
return item.all.forEach((inner, i) => walk(inner, `${at}.all[${i}]`));
|
|
570
|
-
this.need(item.field, `${at}.field`);
|
|
571
|
-
if (item.valueFrom !== undefined)
|
|
572
|
-
this.report(`${at}.valueFrom`, "a list's filter compares with values, not with another field");
|
|
573
|
-
};
|
|
574
|
-
const named = new Set();
|
|
575
|
-
list.filters?.forEach((filter, i) => {
|
|
576
|
-
this.claim(filter.id, `${path}.filters[${i}]`);
|
|
577
|
-
named.add(filter.id);
|
|
578
|
-
filter.filter?.forEach((item, j) => walk(item, `${path}.filters[${i}].filter[${j}]`));
|
|
579
|
-
// A period is of a date, or a date and time.
|
|
580
|
-
const dated = filter.period ? this.need(filter.period.field, `${path}.filters[${i}].period.field`) : undefined;
|
|
581
|
-
if (dated && dated.type !== 'date' && dated.type !== 'datetime')
|
|
582
|
-
this.report(`${path}.filters[${i}].period.field`, `"${filter.period?.field}" is a ${dated.type}; a period is of a date, or a date and time`);
|
|
583
|
-
});
|
|
584
|
-
list.defaultFilters?.forEach((id, i) => {
|
|
585
|
-
if (!named.has(id))
|
|
586
|
-
this.report(`${path}.defaultFilters[${i}]`, `no filter "${id}" in this list`);
|
|
587
|
-
});
|
|
588
|
-
list.actions?.forEach((button, i) => {
|
|
589
|
-
this.claim(button.id, `${path}.actions[${i}]`);
|
|
590
|
-
this.checkPress(button, `${path}.actions[${i}]`);
|
|
591
|
-
});
|
|
592
|
-
if (list.map)
|
|
593
|
-
this.walkListMap(list.map, `${path}.map`);
|
|
594
|
-
if (list.calendar)
|
|
595
|
-
this.walkListCalendar(list, `${path}.calendar`);
|
|
596
|
-
this.checkListCells(list, path);
|
|
597
|
-
this.checkListRows(list, path);
|
|
598
|
-
}
|
|
599
|
-
/**
|
|
600
|
-
* A list's cards, gallery, columns people choose and rows' own rules: a
|
|
601
|
-
* card's fields the page's, its picture one that can hold a picture; the
|
|
602
|
-
* optional columns among the columns; a row's tones, bold and buttons
|
|
603
|
-
* reading the list's record (its fields, `id`, `user`); and the list
|
|
604
|
-
* opening on one thing.
|
|
605
|
-
*/
|
|
606
|
-
checkListRows(list, path) {
|
|
607
|
-
if (list.card)
|
|
608
|
-
this.checkListCard(list.card, `${path}.card`, this.page.fields);
|
|
609
|
-
for (const column of Object.keys(list.optionalColumns ?? {})) {
|
|
610
|
-
if (!list.columns.includes(column))
|
|
611
|
-
this.report(`${path}.optionalColumns.${column}`, `"${column}" is no column of this list: a person shows or hides its columns`);
|
|
612
|
-
}
|
|
613
|
-
// A column a person may hide, every one hidden at first: the list would open with no column but its own.
|
|
614
|
-
if (list.optionalColumns && list.columns.every((column) => list.optionalColumns?.[column] === 'hide'))
|
|
615
|
-
this.report(`${path}.optionalColumns`, 'every column starts hidden: show one at least, as the list opens');
|
|
616
|
-
if (list.gallery?.opens === 'gallery') {
|
|
617
|
-
if (list.map?.view && list.map.view !== 'list')
|
|
618
|
-
this.report(`${path}.gallery.opens`, 'the list opens on its map (map.view) and on its gallery: say one');
|
|
619
|
-
if (list.calendar?.opens === 'calendar')
|
|
620
|
-
this.report(`${path}.gallery.opens`, 'the list opens on its calendar and on its gallery: say one');
|
|
621
|
-
}
|
|
622
|
-
list.rowTones?.forEach((tone, i) => this.checkModifiers(tone, `${path}.rowTones[${i}]`, ['when']));
|
|
623
|
-
this.checkModifiers(list, path, ['rowBold']);
|
|
624
|
-
list.rowButtons?.forEach((button, i) => {
|
|
625
|
-
this.claim(button.id, `${path}.rowButtons[${i}]`);
|
|
626
|
-
this.checkPress(button, `${path}.rowButtons[${i}]`);
|
|
627
|
-
// Shown by a condition on each row's record.
|
|
628
|
-
this.checkModifiers(button, `${path}.rowButtons[${i}]`, ['invisible']);
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
/**
|
|
632
|
-
* What a list's card shows — a list page's, or a related list's of its own records' fields: its title, subtitle,
|
|
633
|
-
* status and lines each a field, its picture one that can hold a picture.
|
|
634
|
-
*/
|
|
635
|
-
checkListCard(card, at, fields) {
|
|
636
|
-
for (const key of ['title', 'subtitle', 'status'])
|
|
637
|
-
if (card[key] !== undefined)
|
|
638
|
-
this.need(card[key], `${at}.${key}`, fields);
|
|
639
|
-
if (card.image !== undefined) {
|
|
640
|
-
const def = this.need(card.image, `${at}.image`, fields);
|
|
641
|
-
if (def && !PICTURES.has(def.type))
|
|
642
|
-
this.report(`${at}.image`, `"${card.image}" is a ${def.type}; a card's picture is an image or binary field, or text holding a picture's address`);
|
|
643
|
-
}
|
|
644
|
-
card.fields?.forEach((line, i) => (Array.isArray(line) ? line : [line]).forEach((part, j) => this.need(typeof part === 'string' ? part : part.field, `${at}.fields[${i}]${Array.isArray(line) ? `[${j}]` : ''}${typeof part === 'string' ? '' : '.field'}`, fields)));
|
|
645
|
-
}
|
|
646
|
-
/**
|
|
647
|
-
* A list's calendar: when each record starts (a date, or a date and time)
|
|
648
|
-
* and ends (the same kind), its words, its colour and its columns, each
|
|
649
|
-
* from a field that can; the view it opens on is one it offers, its hours
|
|
650
|
-
* run forward, and the list opens on one thing.
|
|
651
|
-
*/
|
|
652
|
-
walkListCalendar(list, path) {
|
|
653
|
-
const calendar = list.calendar;
|
|
654
|
-
const typed = (name, key, ok, says) => {
|
|
655
|
-
if (name === undefined)
|
|
656
|
-
return undefined;
|
|
657
|
-
const def = this.need(name, `${path}.${key}`);
|
|
658
|
-
if (def && !ok.has(def.type))
|
|
659
|
-
this.report(`${path}.${key}`, `"${name}" is a ${def.type}; ${says}`);
|
|
660
|
-
return def;
|
|
661
|
-
};
|
|
662
|
-
const start = typed(calendar.start, 'start', CALENDAR_TIMES, "a calendar's start is a date, or a date and time");
|
|
663
|
-
const end = typed(calendar.end, 'end', CALENDAR_TIMES, "a calendar's end is a date, or a date and time");
|
|
664
|
-
if (start && end && CALENDAR_TIMES.has(start.type) && CALENDAR_TIMES.has(end.type) && start.type !== end.type) {
|
|
665
|
-
this.report(`${path}.end`, `"${calendar.end}" is a ${end.type}; an end is the same kind of field as its start, a ${start.type}`);
|
|
666
|
-
}
|
|
667
|
-
typed(calendar.title, 'title', CALENDAR_WORDS, "a record's words on a calendar are a field that can be said");
|
|
668
|
-
typed(calendar.colour, 'colour', CALENDAR_COLOURS, 'a colour comes from a link, a choice, text, a whole number or a yes or no');
|
|
669
|
-
typed(calendar.resource, 'resource', CALENDAR_COLUMNS, 'a column for each comes from a link, a choice, text or a whole number');
|
|
670
|
-
const views = calendar.views ?? ['day', 'week', 'month', 'agenda'];
|
|
671
|
-
views.forEach((view, i) => {
|
|
672
|
-
if (views.indexOf(view) !== i)
|
|
673
|
-
this.report(`${path}.views[${i}]`, `"${view}" is offered twice`);
|
|
674
|
-
});
|
|
675
|
-
if (calendar.view && !views.includes(calendar.view))
|
|
676
|
-
this.report(`${path}.view`, `"${calendar.view}" is not one of its views (${views.join(', ')})`);
|
|
677
|
-
if (calendar.hours && calendar.hours.end <= calendar.hours.start)
|
|
678
|
-
this.report(`${path}.hours`, `the hours end at ${calendar.hours.end}, before they start at ${calendar.hours.start}`);
|
|
679
|
-
if (calendar.opens === 'calendar' && list.map?.view && list.map.view !== 'list')
|
|
680
|
-
this.report(`${path}.opens`, 'the list opens on its map (map.view) and on its calendar: say one');
|
|
681
|
-
}
|
|
682
|
-
/**
|
|
683
|
-
* A list drawn as a backlog: each column's cells are a column's, a lead's
|
|
684
|
-
* icon is by a field's values; the filter bar's fields can list their
|
|
685
|
-
* values; rows nest under a link to the list's own records; and buttons for
|
|
686
|
-
* the rows chosen need a box to choose one.
|
|
687
|
-
*/
|
|
688
|
-
checkListCells(list, path) {
|
|
689
|
-
if (list.filterFields && list.search !== 'fields')
|
|
690
|
-
this.report(`${path}.filterFields`, 'the filter fields are for the "fields" search: add "search": "fields"');
|
|
691
|
-
list.filterFields?.forEach((name, i) => {
|
|
692
|
-
const def = this.need(name, `${path}.filterFields[${i}]`);
|
|
693
|
-
if (def && !LISTS_ITS_VALUES.has(def.type))
|
|
694
|
-
this.report(`${path}.filterFields[${i}]`, `"${name}" is a ${def.type}; a filter lists the values of a choice, a link, links, a yes or no, text or a whole number`);
|
|
695
|
-
});
|
|
696
|
-
// A filter's own name is for a filter the bar offers.
|
|
697
|
-
for (const name of Object.keys(list.filterLabels ?? {})) {
|
|
698
|
-
if (!list.filterFields?.includes(name))
|
|
699
|
-
this.report(`${path}.filterLabels.${name}`, `"${name}" is no field of filterFields: its name is for a filter the bar offers`);
|
|
700
|
-
}
|
|
701
|
-
if (list.parentField !== undefined) {
|
|
702
|
-
const def = this.need(list.parentField, `${path}.parentField`);
|
|
703
|
-
const model = this.page.data.kind === 'record' ? this.page.data.model : '';
|
|
704
|
-
if (def && (def.type !== 'many2one' || def.relation !== model)) {
|
|
705
|
-
this.report(`${path}.parentField`, `"${list.parentField}" ${def.type === 'many2one' ? `links to ${def.relation}` : `is a ${def.type}`}; a parent is a many2one to the list’s own model, ${model}`);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
if (list.choose === false && list.actions?.length)
|
|
709
|
-
this.report(`${path}.actions`, 'a list’s buttons act on the rows chosen: with "choose": false there is no way to choose one');
|
|
710
|
-
this.checkBulkEdit(list, path);
|
|
711
|
-
this.checkImport(list, path);
|
|
712
|
-
}
|
|
713
|
-
/**
|
|
714
|
-
* What a list shows of its records — a list page's, or a related list's of its own records' fields: its columns, its
|
|
715
|
-
* order and the fields its search looks in, each a field; each column's cells for a column of it, a sum, an average,
|
|
716
|
-
* the lowest and the highest of a number column (any column's count), a lead's icon by a field's values.
|
|
717
|
-
*/
|
|
718
|
-
checkListed(list, fields, path) {
|
|
719
|
-
list.columns.forEach((name, i) => this.need(name, `${path}.columns[${i}]`, fields));
|
|
720
|
-
list.sort?.forEach((order, i) => this.need(order.field, `${path}.sort[${i}].field`, fields));
|
|
721
|
-
list.searchFields?.forEach((name, i) => this.need(name, `${path}.searchFields[${i}]`, fields));
|
|
722
|
-
for (const [column, cell] of Object.entries(list.cells ?? {})) {
|
|
723
|
-
const at = `${path}.cells.${column}`;
|
|
724
|
-
if (!list.columns.includes(column)) {
|
|
725
|
-
this.report(at, `"${column}" is no column of this list: its cells are for its columns`);
|
|
726
|
-
continue;
|
|
727
|
-
}
|
|
728
|
-
const kind = has(fields, column) ? fields[column].type : undefined;
|
|
729
|
-
if (cell.total && cell.total !== 'count' && kind && kind !== 'integer' && kind !== 'float' && kind !== 'monetary') {
|
|
730
|
-
this.report(`${at}.total`, `"${column}" is a ${kind}; its total "${cell.total}" is of numbers — an integer, a float or an amount — and any column's is "count"`);
|
|
731
|
-
}
|
|
732
|
-
if (!cell.lead)
|
|
733
|
-
continue;
|
|
734
|
-
const def = this.need(cell.lead.field, `${at}.lead.field`, fields);
|
|
735
|
-
if (def?.type !== 'selection')
|
|
736
|
-
continue;
|
|
737
|
-
const values = new Set(def.options.map((option) => String(option.value)));
|
|
738
|
-
for (const value of Object.keys(cell.lead.looks))
|
|
739
|
-
if (!values.has(value))
|
|
740
|
-
this.report(`${at}.lead.looks.${value}`, `"${value}" is no value of "${cell.lead.field}"`);
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
/** A list's Edit: each field it names one value can set in many records, and rows to choose to set it on. */
|
|
744
|
-
checkBulkEdit(list, path) {
|
|
745
|
-
if (!list.bulkEdit)
|
|
746
|
-
return;
|
|
747
|
-
if (list.choose === false)
|
|
748
|
-
this.report(`${path}.bulkEdit`, 'Edit changes the rows chosen: with "choose": false there is no way to choose one');
|
|
749
|
-
if (list.bulkEdit === true) {
|
|
750
|
-
if (!list.columns.some((name) => bulkSettable(this.page.fields[name])))
|
|
751
|
-
this.report(`${path}.bulkEdit`, 'Edit offers the columns one value can set, and none of these can: name the fields it offers');
|
|
752
|
-
return;
|
|
753
|
-
}
|
|
754
|
-
const named = list.bulkEdit;
|
|
755
|
-
named.forEach((name, i) => {
|
|
756
|
-
const at = `${path}.bulkEdit[${i}]`;
|
|
757
|
-
if (named.indexOf(name) !== i)
|
|
758
|
-
this.report(at, `"${name}" is offered twice`);
|
|
759
|
-
const def = this.need(name, at);
|
|
760
|
-
if (!def)
|
|
761
|
-
return;
|
|
762
|
-
if (!BULK_KINDS.has(def.type))
|
|
763
|
-
this.report(at, `"${name}" is a ${def.type}; one value set in many records is words, a number, money, a yes or no, a date, a choice or a link`);
|
|
764
|
-
else if (def.readonly === true || def.compute)
|
|
765
|
-
this.report(at, `"${name}" is ${def.compute ? 'worked out from other fields' : 'read-only'}; Edit sets a field people can change`);
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
/** A list's Import: each field it names one a file's cell can fill, and at least one such field on the page. */
|
|
769
|
-
checkImport(list, path) {
|
|
770
|
-
if (!list.import)
|
|
771
|
-
return;
|
|
772
|
-
if (list.import === true) {
|
|
773
|
-
if (!Object.values(this.page.fields).some((def) => importable(def)))
|
|
774
|
-
this.report(`${path}.import`, 'Import fills the fields a file’s cell can, and this page has none: words, a number, money, yes or no, a date, a choice or a link people can change');
|
|
775
|
-
return;
|
|
776
|
-
}
|
|
777
|
-
const named = list.import;
|
|
778
|
-
named.forEach((name, i) => {
|
|
779
|
-
const at = `${path}.import[${i}]`;
|
|
780
|
-
if (named.indexOf(name) !== i)
|
|
781
|
-
this.report(at, `"${name}" is offered twice`);
|
|
782
|
-
const def = this.need(name, at);
|
|
783
|
-
if (!def)
|
|
784
|
-
return;
|
|
785
|
-
if (!IMPORT_KINDS.has(def.type))
|
|
786
|
-
this.report(at, `"${name}" is a ${def.type}; a file's cell fills words, a number, money, a yes or no, a date, a choice, or a link or links by name`);
|
|
787
|
-
else if (def.readonly === true || def.compute)
|
|
788
|
-
this.report(at, `"${name}" is ${def.compute ? 'worked out from other fields' : 'read-only'}; Import fills a field people can change`);
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
/** A list's map: where each record is, and the fields its pin and its card show, each of a type that can. */
|
|
792
|
-
walkListMap(map, path) {
|
|
793
|
-
const pair = map.latitude !== undefined || map.longitude !== undefined;
|
|
794
|
-
if (map.point !== undefined && pair)
|
|
795
|
-
this.report(path, 'a map takes point, or latitude and longitude, not both');
|
|
796
|
-
else if (map.point === undefined && (map.latitude === undefined || map.longitude === undefined))
|
|
797
|
-
this.report(path, 'a map needs where each record is: point, or latitude and longitude');
|
|
798
|
-
this.checkPlace(map, path, this.here(), map.pin, map.card);
|
|
799
|
-
if (map.regions)
|
|
800
|
-
this.need(map.regions.field, `${path}.regions.field`);
|
|
801
|
-
if (typeof map.heat === 'object' && map.heat.weight !== undefined) {
|
|
802
|
-
const def = this.need(map.heat.weight, `${path}.heat.weight`);
|
|
803
|
-
if (def && def.type !== 'float' && def.type !== 'integer' && def.type !== 'monetary')
|
|
804
|
-
this.report(`${path}.heat.weight`, `"${map.heat.weight}" is a ${def.type}; a weight is a number field`);
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
/**
|
|
808
|
-
* Where a place is kept, and the fields its pin and its card show, each of a
|
|
809
|
-
* type that can: a page's fields, or a line's when the place is a line's.
|
|
810
|
-
*/
|
|
811
|
-
checkPlace(at, path, scope, pin, card) {
|
|
812
|
-
const typed = (name, where, ok, says) => {
|
|
813
|
-
if (name === undefined)
|
|
814
|
-
return;
|
|
815
|
-
const def = this.need(name, where, scope.fields);
|
|
816
|
-
if (def && !ok(def.type))
|
|
817
|
-
this.report(where, `"${name}" is a ${def.type}; ${says}`);
|
|
818
|
-
};
|
|
819
|
-
const number = (type) => type === 'float' || type === 'integer';
|
|
820
|
-
const picture = (type) => type === 'image' || type === 'binary';
|
|
821
|
-
typed(at.point, `${path}.point`, (type) => type === 'json', 'a point is kept in a json field');
|
|
822
|
-
typed(at.latitude, `${path}.latitude`, number, 'a latitude is a number field (float or integer)');
|
|
823
|
-
typed(at.longitude, `${path}.longitude`, number, 'a longitude is a number field (float or integer)');
|
|
824
|
-
if (pin) {
|
|
825
|
-
typed(pin.image, `${path}.pin.image`, picture, "a pin's photo is an image or binary field");
|
|
826
|
-
typed(pin.turn, `${path}.pin.turn`, number, 'a turn is a number field, in degrees');
|
|
827
|
-
if (pin.iconField !== undefined)
|
|
828
|
-
this.need(pin.iconField, `${path}.pin.iconField`, scope.fields);
|
|
829
|
-
if (pin.label !== undefined)
|
|
830
|
-
this.need(pin.label, `${path}.pin.label`, scope.fields);
|
|
831
|
-
pin.tones?.forEach((tone, i) => this.checkModifiers(tone, `${path}.pin.tones[${i}]`, ['when'], scope));
|
|
832
|
-
pin.money?.per?.forEach((item, i) => this.checkModifiers(item, `${path}.pin.money.per[${i}]`, ['when'], scope));
|
|
833
|
-
}
|
|
834
|
-
if (card) {
|
|
835
|
-
if (card.title !== undefined)
|
|
836
|
-
this.need(card.title, `${path}.card.title`, scope.fields);
|
|
837
|
-
typed(card.image, `${path}.card.image`, picture, "a card's picture is an image or binary field");
|
|
838
|
-
// A line is a part, or several on one line; a part a field's name, or a field with its words.
|
|
839
|
-
card.fields?.forEach((line, i) => (Array.isArray(line) ? line : [line]).forEach((part, j) => {
|
|
840
|
-
const at = `${path}.card.fields[${i}]${Array.isArray(line) ? `[${j}]` : ''}${typeof part === 'string' ? '' : '.field'}`;
|
|
841
|
-
this.need(typeof part === 'string' ? part : part.field, at, scope.fields);
|
|
842
|
-
}));
|
|
843
|
-
typed(card.href, `${path}.card.href`, (type) => type === 'char' || type === 'text', "a card's link is a text field holding a web address");
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
/**
|
|
847
|
-
* A form's map: each layer one kind — the record's place, a place for each
|
|
848
|
-
* of its lines, a route between layers, or the reader's own place — its
|
|
849
|
-
* fields where that kind keeps them, and a route's ends layers it can join.
|
|
850
|
-
*/
|
|
851
|
-
walkMap(node, path) {
|
|
852
|
-
const layers = new Map();
|
|
853
|
-
node.layers.forEach((layer, i) => {
|
|
854
|
-
const at = `${path}.layers[${i}]`;
|
|
855
|
-
if (layers.has(layer.id))
|
|
856
|
-
this.report(`${at}.id`, `two layers are called "${layer.id}"`);
|
|
857
|
-
const pair = layer.latitude !== undefined || layer.longitude !== undefined;
|
|
858
|
-
const placed = layer.point !== undefined || pair;
|
|
859
|
-
const kinds = [placed, !!layer.route, !!layer.myLocation, !!layer.regions, !!layer.reachable].filter(Boolean).length;
|
|
860
|
-
if (kinds !== 1)
|
|
861
|
-
this.report(at, 'a layer is one of: a place (point, or latitude and longitude, for the record or for each of its lines), a route, myLocation, regions, or reachable');
|
|
862
|
-
if (layer.regions?.choose !== undefined)
|
|
863
|
-
this.need(layer.regions.choose, `${at}.regions.choose`);
|
|
864
|
-
if (layer.point !== undefined && pair)
|
|
865
|
-
this.report(at, 'a layer takes point, or latitude and longitude, not both');
|
|
866
|
-
else if (layer.point === undefined && pair && (layer.latitude === undefined || layer.longitude === undefined))
|
|
867
|
-
this.report(at, 'a layer needs both latitude and longitude');
|
|
868
|
-
if (layer.lines !== undefined && !placed)
|
|
869
|
-
this.report(`${at}.lines`, "a layer of lines needs where each line is: point, or latitude and longitude, of the line's fields");
|
|
870
|
-
if (layer.live && (!placed || layer.lines !== undefined))
|
|
871
|
-
this.report(`${at}.live`, "live is for a layer of the record's own place: it moves as its fields change");
|
|
872
|
-
this.checkModifiers(layer, at, ['invisible']);
|
|
873
|
-
layers.set(layer.id, { lines: layer.lines !== undefined, place: placed || !!layer.myLocation });
|
|
874
|
-
if (!placed)
|
|
875
|
-
return;
|
|
876
|
-
if (layer.lines === undefined)
|
|
877
|
-
return this.checkPlace(layer, at, this.here(), layer.pin, layer.card);
|
|
878
|
-
const def = this.need(layer.lines, `${at}.lines`);
|
|
879
|
-
if (!def)
|
|
880
|
-
return;
|
|
881
|
-
if (def.type !== 'one2many')
|
|
882
|
-
return this.report(`${at}.lines`, `"${layer.lines}" is a ${def.type}; a layer of lines is a one2many field`);
|
|
883
|
-
this.checkPlace(layer, at, { fields: def.fields, lines: layer.lines, parent: this.page.fields }, layer.pin, layer.card);
|
|
884
|
-
});
|
|
885
|
-
node.layers.forEach((layer, i) => {
|
|
886
|
-
const route = layer.route;
|
|
887
|
-
if (!route)
|
|
888
|
-
return;
|
|
889
|
-
const at = `${path}.layers[${i}].route`;
|
|
890
|
-
const end = (id, key) => {
|
|
891
|
-
if (id === undefined)
|
|
892
|
-
return;
|
|
893
|
-
const found = layers.get(id);
|
|
894
|
-
if (!found)
|
|
895
|
-
this.report(`${at}.${key}`, `no layer "${id}" on this map`);
|
|
896
|
-
else if (!found.place || found.lines)
|
|
897
|
-
this.report(`${at}.${key}`, `a route's ${key} is a layer of one place, or the reader's own; "${id}" is not`);
|
|
898
|
-
};
|
|
899
|
-
end(route.from, 'from');
|
|
900
|
-
end(route.to, 'to');
|
|
901
|
-
if (route.through !== undefined) {
|
|
902
|
-
const found = layers.get(route.through);
|
|
903
|
-
if (!found)
|
|
904
|
-
this.report(`${at}.through`, `no layer "${route.through}" on this map`);
|
|
905
|
-
else if (!found.lines)
|
|
906
|
-
this.report(`${at}.through`, `a route goes through a layer of lines; "${route.through}" is not one`);
|
|
907
|
-
}
|
|
908
|
-
if (route.to === undefined && route.through === undefined)
|
|
909
|
-
this.report(at, 'a route needs where it goes: to, or through');
|
|
910
|
-
const written = (name, key, ok, says) => {
|
|
911
|
-
if (name === undefined)
|
|
912
|
-
return;
|
|
913
|
-
const def = this.need(name, `${at}.write.${key}`);
|
|
914
|
-
if (def && !ok(def.type))
|
|
915
|
-
this.report(`${at}.write.${key}`, `"${name}" is a ${def.type}; ${says}`);
|
|
916
|
-
};
|
|
917
|
-
const number = (type) => type === 'float' || type === 'integer';
|
|
918
|
-
written(route.write?.seconds, 'seconds', number, 'seconds are a number field');
|
|
919
|
-
written(route.write?.metres, 'metres', number, 'metres are a number field');
|
|
920
|
-
written(route.write?.line, 'line', (type) => type === 'json', 'a line is kept in a json field');
|
|
921
|
-
});
|
|
922
|
-
node.layers.forEach((layer, i) => {
|
|
923
|
-
if (!layer.reachable)
|
|
924
|
-
return;
|
|
925
|
-
const found = layers.get(layer.reachable.from);
|
|
926
|
-
const at = `${path}.layers[${i}].reachable.from`;
|
|
927
|
-
if (!found)
|
|
928
|
-
this.report(at, `no layer "${layer.reachable.from}" on this map`);
|
|
929
|
-
else if (!found.place || found.lines)
|
|
930
|
-
this.report(at, `a reachable area is worked out from a layer of one place, or the reader's own; "${layer.reachable.from}" is not`);
|
|
931
|
-
});
|
|
932
|
-
node.fences?.forEach((fence, i) => {
|
|
933
|
-
const at = `${path}.fences[${i}]`;
|
|
934
|
-
const watched = node.layers.find((layer) => layer.id === fence.layer);
|
|
935
|
-
if (!watched)
|
|
936
|
-
this.report(`${at}.layer`, `no layer "${fence.layer}" on this map`);
|
|
937
|
-
else if (!watched.live && !watched.myLocation)
|
|
938
|
-
this.report(`${at}.layer`, `a fence watches a place that moves: a live layer, or the reader's own; "${fence.layer}" is not`);
|
|
939
|
-
if (fence.area !== undefined) {
|
|
940
|
-
const def = this.need(fence.area, `${at}.area`);
|
|
941
|
-
if (def && def.type !== 'json')
|
|
942
|
-
this.report(`${at}.area`, `"${fence.area}" is a ${def.type}; an area is kept in a json field`);
|
|
943
|
-
}
|
|
944
|
-
if (fence.within !== undefined && !node.layers.some((layer) => layer.id === fence.within && layer.reachable))
|
|
945
|
-
this.report(`${at}.within`, `a fence is within a reachable area of this map; "${fence.within}" is not one`);
|
|
946
|
-
this.checkSteps(fence.steps, `${at}.steps`);
|
|
947
|
-
});
|
|
948
|
-
}
|
|
949
669
|
walkChildren(children, path) {
|
|
950
670
|
children.forEach((child, i) => this.walkNode(child, `${path}.children[${i}]`));
|
|
951
671
|
}
|
|
@@ -977,13 +697,13 @@ export class ReferenceCheck {
|
|
|
977
697
|
case 'form':
|
|
978
698
|
return this.checkFormNode(node, path);
|
|
979
699
|
case 'button':
|
|
980
|
-
return this.checkPress(node, path);
|
|
700
|
+
return this.checkPress(node, path, true);
|
|
981
701
|
case 'text':
|
|
982
702
|
return this.checkValuesIn(node.text, `${path}.text`);
|
|
983
703
|
case 'map':
|
|
984
|
-
return
|
|
704
|
+
return checkMap(node, path, this.mapTools());
|
|
985
705
|
case 'related':
|
|
986
|
-
return
|
|
706
|
+
return checkRelated(node, path, this.listTools());
|
|
987
707
|
case 'slot':
|
|
988
708
|
this.slots.add(node.name);
|
|
989
709
|
return;
|
|
@@ -1008,48 +728,6 @@ export class ReferenceCheck {
|
|
|
1008
728
|
else
|
|
1009
729
|
this.formNames.set(node.name, path);
|
|
1010
730
|
}
|
|
1011
|
-
/**
|
|
1012
|
-
* A related list: on a record's page, its columns, order, search, cells, card and rows' rules of its own records'
|
|
1013
|
-
* fields, its link to this record a link of theirs to this model where its fields name it, and its filter's
|
|
1014
|
-
* `valueFrom` this record's.
|
|
1015
|
-
*/
|
|
1016
|
-
checkRelated(node, path) {
|
|
1017
|
-
// The quick check reads no shapes: a part with nothing to go by is said here.
|
|
1018
|
-
if (typeof node.model !== 'string' || typeof node.field !== 'string' || !Array.isArray(node.columns) || !node.fields || typeof node.fields !== 'object') {
|
|
1019
|
-
return this.report(path, 'a related list needs the model of its records, their link to this record (field), their fields and its columns');
|
|
1020
|
-
}
|
|
1021
|
-
const model = this.page.data.kind === 'record' ? this.page.data.model : null;
|
|
1022
|
-
if (!model || this.line)
|
|
1023
|
-
this.report(path, 'a related list shows the records that point at a record: it belongs on a record’s page');
|
|
1024
|
-
const fields = node.fields;
|
|
1025
|
-
this.checkListed(node, fields, path);
|
|
1026
|
-
// On a phone its rows are cards, as a list's: of its own records' fields.
|
|
1027
|
-
if (node.card)
|
|
1028
|
-
this.checkListCard(node.card, `${path}.card`, fields);
|
|
1029
|
-
// Its rows' own rules, as a list's, each read on the row's record: its own fields, its id, the person, the context.
|
|
1030
|
-
const row = { fields };
|
|
1031
|
-
node.rowTones?.forEach((tone, i) => this.checkModifiers(tone, `${path}.rowTones[${i}]`, ['when'], row));
|
|
1032
|
-
this.checkModifiers(node, path, ['rowBold'], row);
|
|
1033
|
-
node.rowButtons?.forEach((button, i) => {
|
|
1034
|
-
this.claim(button.id, `${path}.rowButtons[${i}]`);
|
|
1035
|
-
this.checkPress(button, `${path}.rowButtons[${i}]`);
|
|
1036
|
-
this.checkModifiers(button, `${path}.rowButtons[${i}]`, ['invisible'], row);
|
|
1037
|
-
});
|
|
1038
|
-
const link = has(fields, node.field) ? fields[node.field] : undefined;
|
|
1039
|
-
if (link && model && (link.type !== 'many2one' || link.relation !== model)) {
|
|
1040
|
-
this.report(`${path}.field`, `"${node.field}" ${link.type === 'many2one' ? `links to ${link.relation}` : `is a ${link.type}`}; the records listed point at this one by a many2one to ${model}`);
|
|
1041
|
-
}
|
|
1042
|
-
for (const [name, def] of Object.entries(fields)) {
|
|
1043
|
-
if (def.type === 'monetary' && def.currencyField !== undefined)
|
|
1044
|
-
this.checkCurrency(def.currencyField, `${path}.fields.${name}.currencyField`, fields, undefined, fields);
|
|
1045
|
-
if (def.type === 'datetime' && def.timeZone !== undefined)
|
|
1046
|
-
this.checkZone(def.timeZone, `${path}.fields.${name}.timeZone`);
|
|
1047
|
-
}
|
|
1048
|
-
// Its filter is about its records, by their fields; what it compares with may be read from this record.
|
|
1049
|
-
node.filter?.forEach((item, i) => this.checkFilterItem(item, `${path}.filter[${i}]`, this.here()));
|
|
1050
|
-
if (node.viewAll && node.viewAll.page === this.page.id)
|
|
1051
|
-
this.report(`${path}.viewAll.page`, 'View all opens a list of the records related: this page is the record they point at');
|
|
1052
|
-
}
|
|
1053
731
|
walkTabs(node, path) {
|
|
1054
732
|
node.children.forEach((tab, i) => {
|
|
1055
733
|
const tabPath = `${path}.children[${i}]`;
|
|
@@ -1068,36 +746,13 @@ export class ReferenceCheck {
|
|
|
1068
746
|
this.need(sheet.title.avatarField, `${path}.title.avatarField`);
|
|
1069
747
|
sheet.title.placeholderWhen?.forEach((item, i) => this.checkModifiers(item, `${path}.title.placeholderWhen[${i}]`, ['when']));
|
|
1070
748
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
this.checkModifiers(sheet.statusbar, at);
|
|
1075
|
-
if (durationsField !== undefined) {
|
|
1076
|
-
const times = this.need(durationsField, `${at}.durationsField`);
|
|
1077
|
-
if (times && times.type !== 'json')
|
|
1078
|
-
this.report(`${at}.durationsField`, `"${durationsField}" is a ${times.type}; time per step comes from a json field`);
|
|
1079
|
-
}
|
|
1080
|
-
if (saves && !clickable)
|
|
1081
|
-
this.report(`${at}.saves`, 'a click saves only where a step can be clicked (clickable)');
|
|
1082
|
-
const def = this.need(field, `${path}.statusbar.field`);
|
|
1083
|
-
if (fold && def && def.type !== 'many2one')
|
|
1084
|
-
this.report(`${at}.fold`, 'steps fold by their record (a stage), so only a link’s steps fold; a choice keeps the steps it shows with visibleStates');
|
|
1085
|
-
if (def && def.type !== 'selection' && def.type !== 'many2one') {
|
|
1086
|
-
this.report(`${path}.statusbar.field`, `"${field}" is a ${def.type}; a statusbar needs a selection or many2one`);
|
|
1087
|
-
}
|
|
1088
|
-
if (def?.type === 'selection' && visibleStates) {
|
|
1089
|
-
const values = new Set(def.options.map((o) => o.value));
|
|
1090
|
-
visibleStates.forEach((state, i) => {
|
|
1091
|
-
if (!values.has(state)) {
|
|
1092
|
-
this.report(`${path}.statusbar.visibleStates[${i}]`, `"${state}" is not an option of "${field}"`);
|
|
1093
|
-
}
|
|
1094
|
-
});
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
749
|
+
// A statusbar, drawn by the business part, whose checks come with it in the one-tag script.
|
|
750
|
+
if (sheet.statusbar)
|
|
751
|
+
checkStatusbar(sheet.statusbar, path, { ...this.fieldTools(), checkModifiers: (owner, at, keys) => this.checkModifiers(owner, at, keys) });
|
|
1097
752
|
sheet.buttons?.forEach((button, i) => {
|
|
1098
753
|
this.claim(button.id, `${path}.buttons[${i}]`);
|
|
1099
754
|
this.checkModifiers(button, `${path}.buttons[${i}]`);
|
|
1100
|
-
this.checkPress(button, `${path}.buttons[${i}]
|
|
755
|
+
this.checkPress(button, `${path}.buttons[${i}]`, true);
|
|
1101
756
|
});
|
|
1102
757
|
sheet.statButtons?.forEach((stat, i) => {
|
|
1103
758
|
this.claim(stat.id, `${path}.statButtons[${i}]`);
|
|
@@ -1135,7 +790,7 @@ export class ReferenceCheck {
|
|
|
1135
790
|
alert.buttons?.forEach((button, j) => {
|
|
1136
791
|
this.claim(button.id, `${at}.buttons[${j}]`);
|
|
1137
792
|
this.checkModifiers(button, `${at}.buttons[${j}]`);
|
|
1138
|
-
this.checkPress(button, `${at}.buttons[${j}]
|
|
793
|
+
this.checkPress(button, `${at}.buttons[${j}]`, true);
|
|
1139
794
|
});
|
|
1140
795
|
});
|
|
1141
796
|
// The gear menu: buttons of the record, each doing something — its steps, an action, or a built-in — and named.
|
|
@@ -1163,46 +818,6 @@ export class ReferenceCheck {
|
|
|
1163
818
|
this.checkModifiers(sheet.sidePanel, `${path}.sidePanel`);
|
|
1164
819
|
}
|
|
1165
820
|
}
|
|
1166
|
-
/** An address with suggestions: a char field, its place kept as a place field keeps one, its parts in a json field. */
|
|
1167
|
-
checkAddressSearch(node, def, path) {
|
|
1168
|
-
const options = node.options ?? {};
|
|
1169
|
-
const kind = (key) => {
|
|
1170
|
-
const name = options[key];
|
|
1171
|
-
return typeof name === 'string' && has(this.page.fields, name) ? this.page.fields[name].type : undefined;
|
|
1172
|
-
};
|
|
1173
|
-
if (def && def.type !== 'char')
|
|
1174
|
-
this.report(`${path}.widget`, `address-search is for a char field; "${node.field}" is a ${def.type}`);
|
|
1175
|
-
const place = kind('placeField');
|
|
1176
|
-
if (place && place !== 'json' && place !== 'float')
|
|
1177
|
-
this.report(`${path}.options.placeField`, `"${options['placeField']}" is a ${place}; a place is kept in a json field, or in a float field (its latitude) with longitudeField`);
|
|
1178
|
-
if (place === 'float' && options['longitudeField'] === undefined)
|
|
1179
|
-
this.report(`${path}.options.placeField`, `"${options['placeField']}" keeps a latitude: name the float field for its longitude in longitudeField`);
|
|
1180
|
-
if (options['longitudeField'] !== undefined && place !== 'float')
|
|
1181
|
-
this.report(`${path}.options.longitudeField`, 'longitudeField goes with a placeField that is a float field, its latitude');
|
|
1182
|
-
const longitude = kind('longitudeField');
|
|
1183
|
-
if (longitude && longitude !== 'float')
|
|
1184
|
-
this.report(`${path}.options.longitudeField`, `"${options['longitudeField']}" is a ${longitude}; a longitude is kept in a float field`);
|
|
1185
|
-
const parts = kind('partsField');
|
|
1186
|
-
if (parts && parts !== 'json')
|
|
1187
|
-
this.report(`${path}.options.partsField`, `"${options['partsField']}" is a ${parts}; an address's parts are kept in a json field`);
|
|
1188
|
-
}
|
|
1189
|
-
/** Booking slots: on a date and time, its end written in another, asked again as the fields it depends on change. */
|
|
1190
|
-
checkSlots(node, def, path) {
|
|
1191
|
-
const options = node.options ?? {};
|
|
1192
|
-
if (def && def.type !== 'datetime')
|
|
1193
|
-
this.report(`${path}.widget`, `slots is for a datetime field; "${node.field}" is a ${def.type}`);
|
|
1194
|
-
const end = options['endField'];
|
|
1195
|
-
if (typeof end === 'string' && has(this.page.fields, end)) {
|
|
1196
|
-
const type = this.page.fields[end].type;
|
|
1197
|
-
if (end === node.field)
|
|
1198
|
-
this.report(`${path}.options.endField`, 'a booking ends in another field than the one it starts in');
|
|
1199
|
-
else if (type !== 'datetime')
|
|
1200
|
-
this.report(`${path}.options.endField`, `"${end}" is a ${type}; a booking's end is kept in a datetime field`);
|
|
1201
|
-
}
|
|
1202
|
-
const on = options['dependsOn'];
|
|
1203
|
-
if (Array.isArray(on))
|
|
1204
|
-
on.forEach((name, i) => typeof name === 'string' && this.need(name, `${path}.options.dependsOn[${i}]`));
|
|
1205
|
-
}
|
|
1206
821
|
/** A picker's own settings, on the kind of field each is for: a path on a link, Fieldia's own list on a choice, a button adding tags, a colour for each state. */
|
|
1207
822
|
checkPicker(node, def, path) {
|
|
1208
823
|
const options = node.options ?? {};
|
|
@@ -1224,6 +839,8 @@ export class ReferenceCheck {
|
|
|
1224
839
|
}
|
|
1225
840
|
checkFieldNode(node, path) {
|
|
1226
841
|
const def = this.need(node.field, `${path}.field`);
|
|
842
|
+
// Its own label and help, as the field's: words that may show the answers given.
|
|
843
|
+
this.checkWords(node, path, ['label', 'help']);
|
|
1227
844
|
// Answer rules: each condition and rule across fields reads, each pattern is a regular expression.
|
|
1228
845
|
node.validate?.forEach((rule, i) => {
|
|
1229
846
|
const at = `${path}.validate[${i}]`;
|
|
@@ -1255,39 +872,31 @@ export class ReferenceCheck {
|
|
|
1255
872
|
if (problem)
|
|
1256
873
|
this.report(`${path}.autocomplete`, problem);
|
|
1257
874
|
}
|
|
1258
|
-
if (def && node.totals)
|
|
1259
|
-
|
|
1260
|
-
this.report(`${path}.totals`, `totals only apply to one2many fields; "${node.field}" is a ${def.type}`);
|
|
1261
|
-
}
|
|
1262
|
-
else {
|
|
1263
|
-
node.totals.forEach((column, i) => {
|
|
1264
|
-
const sub = has(def.fields, column) ? def.fields[column] : undefined;
|
|
1265
|
-
if (!sub)
|
|
1266
|
-
this.report(`${path}.totals[${i}]`, `"${column}" is not a field of the lines of "${node.field}"`);
|
|
1267
|
-
else if (!['integer', 'float', 'monetary'].includes(sub.type)) {
|
|
1268
|
-
this.report(`${path}.totals[${i}]`, `"${column}" is a ${sub.type}; only integer, float and monetary columns add up`);
|
|
1269
|
-
}
|
|
1270
|
-
});
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
875
|
+
if (def && node.totals && def.type !== 'one2many')
|
|
876
|
+
this.report(`${path}.totals`, `totals only apply to one2many fields; "${node.field}" is a ${def.type}`);
|
|
1273
877
|
// Options that point at another field: by convention their names end in "Field".
|
|
1274
878
|
for (const [key, value] of Object.entries(node.options ?? {})) {
|
|
1275
879
|
if (key.endsWith('Field') && typeof value === 'string')
|
|
1276
880
|
this.need(value, `${path}.options.${key}`);
|
|
1277
881
|
}
|
|
882
|
+
// An address with suggestions, drawn by the app's maps: its checks come with a map's in the one-tag script.
|
|
1278
883
|
if (node.widget === 'address-search')
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
884
|
+
checkAddressSearch(node, def, path, this.mapTools());
|
|
885
|
+
// Booking slots on a date and time, a signature on a file: drawn by their parts, whose checks of their settings come
|
|
886
|
+
// with them in the one-tag script.
|
|
887
|
+
if (node.widget === 'slots' && def && def.type !== 'datetime')
|
|
888
|
+
this.report(`${path}.widget`, `slots is for a datetime field; "${node.field}" is a ${def.type}`);
|
|
889
|
+
else if (node.widget === 'slots')
|
|
890
|
+
checkSlots(node, path, this.fieldTools());
|
|
891
|
+
if (node.widget === 'signature' && def && def.type !== 'binary')
|
|
892
|
+
this.report(`${path}.widget`, `signature is for a binary field; "${node.field}" is a ${def.type}`);
|
|
893
|
+
else if (node.widget === 'signature')
|
|
894
|
+
checkSignature(node, path, this.fieldTools());
|
|
1282
895
|
if (def)
|
|
1283
896
|
this.checkPicker(node, def, path);
|
|
1284
897
|
// The order lines load in: fields of its lines.
|
|
1285
|
-
if (def && node.order)
|
|
1286
|
-
|
|
1287
|
-
this.report(`${path}.order`, `order only applies to one2many fields; "${node.field}" is a ${def.type}`);
|
|
1288
|
-
else
|
|
1289
|
-
node.order.forEach((item, i) => !has(def.fields, item.field) && this.report(`${path}.order[${i}].field`, `"${item.field}" is not a field of the lines of "${node.field}"`));
|
|
1290
|
-
}
|
|
898
|
+
if (def && node.order && def.type !== 'one2many')
|
|
899
|
+
this.report(`${path}.order`, `order only applies to one2many fields; "${node.field}" is a ${def.type}`);
|
|
1291
900
|
// A line's own form: its parts read the line's fields, the record as parent.
|
|
1292
901
|
if (def && node.lineForm) {
|
|
1293
902
|
if (def.type !== 'one2many')
|
|
@@ -1305,36 +914,8 @@ export class ReferenceCheck {
|
|
|
1305
914
|
// A table's own rules: a cell's read on its line (with parent), a column's `hidden` on the record.
|
|
1306
915
|
if (def && node.lineDelete !== undefined && def.type !== 'one2many')
|
|
1307
916
|
this.report(`${path}.lineDelete`, `lineDelete only applies to one2many fields; "${node.field}" is a ${def.type}`);
|
|
1308
|
-
if (def && (node.cells || node.rowTones || node.rowBold !== undefined || node.rowButtons
|
|
1309
|
-
|
|
1310
|
-
if (node.cells || node.rowTones || node.rowBold !== undefined || node.rowButtons)
|
|
1311
|
-
this.report(path, `cells, rowTones, rowBold and rowButtons only apply to one2many fields; "${node.field}" is a ${def.type}`);
|
|
1312
|
-
}
|
|
1313
|
-
else {
|
|
1314
|
-
const line = { fields: def.fields, lines: node.field };
|
|
1315
|
-
for (const [column, rules] of Object.entries(node.cells ?? {})) {
|
|
1316
|
-
if (!has(def.fields, column)) {
|
|
1317
|
-
this.report(`${path}.cells.${column}`, `"${column}" is not a field of the lines of "${node.field}"`);
|
|
1318
|
-
continue;
|
|
1319
|
-
}
|
|
1320
|
-
this.checkModifiers(rules, `${path}.cells.${column}`, ['invisible', 'readonly', 'required', 'bold'], line);
|
|
1321
|
-
this.checkModifiers(rules, `${path}.cells.${column}`, ['hidden']);
|
|
1322
|
-
rules.tones?.forEach((tone, i) => this.checkModifiers(tone, `${path}.cells.${column}.tones[${i}]`, ['when'], line));
|
|
1323
|
-
// Its widget's options that point at a field: one of the line's.
|
|
1324
|
-
for (const [key, value] of Object.entries(rules.options ?? {})) {
|
|
1325
|
-
if (key.endsWith('Field') && typeof value === 'string')
|
|
1326
|
-
this.need(value, `${path}.cells.${column}.options.${key}`, def.fields);
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
node.rowTones?.forEach((tone, i) => this.checkModifiers(tone, `${path}.rowTones[${i}]`, ['when'], line));
|
|
1330
|
-
this.checkModifiers(node, path, ['rowBold', 'lineDelete'], line);
|
|
1331
|
-
node.rowButtons?.forEach((button, i) => {
|
|
1332
|
-
this.claim(button.id, `${path}.rowButtons[${i}]`);
|
|
1333
|
-
this.checkModifiers(button, `${path}.rowButtons[${i}]`, ['invisible'], line);
|
|
1334
|
-
this.checkPress(button, `${path}.rowButtons[${i}]`);
|
|
1335
|
-
});
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
917
|
+
if (def && def.type !== 'one2many' && (node.cells || node.rowTones || node.rowBold !== undefined || node.rowButtons))
|
|
918
|
+
this.report(path, `cells, rowTones, rowBold and rowButtons only apply to one2many fields; "${node.field}" is a ${def.type}`);
|
|
1338
919
|
// Buttons for the chosen lines and in the control row: buttons of the record, as any.
|
|
1339
920
|
for (const key of ['selectedButtons', 'controlButtons']) {
|
|
1340
921
|
if (!node[key])
|
|
@@ -1347,32 +928,14 @@ export class ReferenceCheck {
|
|
|
1347
928
|
this.checkPress(button, `${path}.${key}[${i}]`);
|
|
1348
929
|
});
|
|
1349
930
|
}
|
|
1350
|
-
if (def && node.optionalColumns)
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
// The columns the table shows: those named, or every line field but the structural ones.
|
|
1356
|
-
const shown = node.columns ?? Object.keys(def.fields).filter((c) => c !== ('lineKinds' in def ? def.lineKinds?.field : undefined) && c !== def.sequenceField);
|
|
1357
|
-
for (const column of Object.keys(node.optionalColumns)) {
|
|
1358
|
-
if (!shown.includes(column))
|
|
1359
|
-
this.report(`${path}.optionalColumns.${column}`, `"${column}" is not a column of "${node.field}"`);
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
931
|
+
if (def && node.optionalColumns && def.type !== 'one2many')
|
|
932
|
+
this.report(`${path}.optionalColumns`, `optional columns only apply to one2many fields; "${node.field}" is a ${def.type}`);
|
|
933
|
+
// A table of lines' own settings, drawn by the lines part: its columns, totals, order, cells and rows' rules.
|
|
934
|
+
if (def?.type === 'one2many')
|
|
935
|
+
return checkTable(node, def, path, this.tableTools());
|
|
1363
936
|
if (!def || !node.columns)
|
|
1364
937
|
return;
|
|
1365
|
-
if (def.type === '
|
|
1366
|
-
node.columns.forEach((column, i) => {
|
|
1367
|
-
if (!has(def.fields, column)) {
|
|
1368
|
-
this.report(`${path}.columns[${i}]`, `"${column}" is not a field of the lines of "${node.field}"`);
|
|
1369
|
-
}
|
|
1370
|
-
else if (def.fields[column].type === 'one2many') {
|
|
1371
|
-
this.report(`${path}.columns[${i}]`, `"${column}" holds lines of its own: they are edited in the line's form (lineForm), not drawn in its row`);
|
|
1372
|
-
}
|
|
1373
|
-
});
|
|
1374
|
-
}
|
|
1375
|
-
else if (def.type === 'many2many') {
|
|
938
|
+
if (def.type === 'many2many') {
|
|
1376
939
|
// A table of the records linked: its columns are fields of those records.
|
|
1377
940
|
node.columns.forEach((column, i) => {
|
|
1378
941
|
if (!def.fields || !has(def.fields, column))
|