@nodegx/mcp 0.2.6 → 0.2.8
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 +16 -11
- package/dist/backend/cli.js +5801 -2527
- package/dist/backend/folder/start.js +89 -1
- package/dist/backend/vm/provision-app.sh +41 -0
- package/dist/cloud-bundle.cjs +4265 -1666
- package/dist/kit-extract.cjs +4742 -2156
- package/dist/kit-extract.inputs.json +31 -1
- package/dist/node-catalog.json +10 -9
- package/dist/noodl-mcp.cjs +12019 -7425
- package/dist/package.json +1 -1
- package/dist/render-harness/data/node-catalog-enriched.json +1353 -254
- package/dist/render-harness/data/node-catalog.json +10 -9
- package/dist/render-harness/drive-steps.js +271 -40
- package/dist/render-harness/measure-from-disk.js +174 -22
- package/dist/src/external/viewer/noodl.viewer.js +1 -1
- package/package.json +1 -1
|
@@ -5114,7 +5114,7 @@
|
|
|
5114
5114
|
"whenToUse": "Whenever a file handle needs to become a URL or filename. Note the cloudfile→image typecast lets a cloudfile feed an Image `src` directly; reach for Cloud File when you need the URL as a *string* or the file's name.",
|
|
5115
5115
|
"ports": {
|
|
5116
5116
|
"file": "The cloudfile value to unwrap.",
|
|
5117
|
-
"url": "The file's
|
|
5117
|
+
"url": "The file's URL as a string, for an Image `src` or a link. It's the address the backend answered from, so don't save it into a text property. Save the file itself into a file-typed one.",
|
|
5118
5118
|
"name": "The stored file's name."
|
|
5119
5119
|
},
|
|
5120
5120
|
"examples": [
|
|
@@ -7119,7 +7119,8 @@
|
|
|
7119
7119
|
"isfalse": "Boolean level, inverse of `result`; saves an Inverter when driving 'error shown' style inputs."
|
|
7120
7120
|
},
|
|
7121
7121
|
"examples": [
|
|
7122
|
-
"validate-before-submit"
|
|
7122
|
+
"validate-before-submit",
|
|
7123
|
+
"score-then-move-on"
|
|
7123
7124
|
],
|
|
7124
7125
|
"patterns": [
|
|
7125
7126
|
"Button `onClick` → `eval`, with `ontrue` driving the action: gate an action behind a validity check at click time.",
|
|
@@ -7128,7 +7129,7 @@
|
|
|
7128
7129
|
"antiPatterns": [
|
|
7129
7130
|
"Wiring `ontrue` (signal) to a boolean input like `visible` delivers only a momentary pulse — use `result` for anything that must stay on.",
|
|
7130
7131
|
"Leaving Run On Value Change on when `ontrue`/`onfalse` DOES something (advance a question, navigate, set a Variable, start a Timer): the gate then fires whenever its input settles, including once while the page loads, before anyone has acted. Set `runOnChange-condition: false` and wire `eval` from the signal that should ask — a Repeat's tick, a button's click, a Timer's finish.",
|
|
7131
|
-
"Two gates evaluated by one signal, where one gate's On True changes what the other one reads (advancing a question index that the other's 'is the answer right?' compares against): the
|
|
7132
|
+
"Two gates evaluated by one signal, where one gate's On True changes what the other one reads (advancing a question index that the other's 'is the answer right?' compares against): the reader sees the changed value, whichever order the two are wired in, so wire order is no fix. Chain them instead — the reader's `done` → the writer's `eval` — so the reader decides first (example `score-then-move-on`; the Problems panel warns `condition-reads-a-siblings-change`).",
|
|
7132
7133
|
"Expecting a second Evaluate with an unchanged condition to stay silent: every Evaluate fires On True or On False again, whether or not the value changed.",
|
|
7133
7134
|
"A gate left on Run On Value Change fires the moment its condition is true, even for one step in the middle of a chain of Set Variables. A timeout watching 'time is up AND not answered' fires again while moving on if `answered` is cleared while the countdown still reads 0: the next question starts answered and its first tap is refused (a quiz that scored the right answer after a timeout as wrong). Reset the value that makes it true (the countdown back to 10) before the one that holds it off (`answered` back to false)."
|
|
7134
7135
|
],
|
|
@@ -18526,12 +18527,14 @@
|
|
|
18526
18527
|
"examples": [
|
|
18527
18528
|
"function-compute-on-run",
|
|
18528
18529
|
"community-csv-download",
|
|
18529
|
-
"community-seo-meta-tag-setter"
|
|
18530
|
+
"community-seo-meta-tag-setter",
|
|
18531
|
+
"cloud-members-can-edit-my-records"
|
|
18530
18532
|
],
|
|
18531
18533
|
"patterns": [
|
|
18532
18534
|
"Async action: `run` from a click → `await fetch(...)` → set value outputs → `Outputs.Success()`; wire Success onward for sequencing.",
|
|
18533
18535
|
"Transform between nodes: array in, reshaped array out, feeding a Repeater.",
|
|
18534
|
-
"Shared helper: `exports.canStep = …` in `scripts/engine/rules.js`, `engine/rules.js` under Uses on each Function that needs it, and `Scripts.rules.canStep(tile)` in each body."
|
|
18536
|
+
"Shared helper: `exports.canStep = …` in `scripts/engine/rules.js`, `engine/rules.js` under Uses on each Function that needs it, and `Scripts.rules.canStep(tile)` in each body.",
|
|
18537
|
+
"Records from a script, in a cloud function: `await Noodl.Records.query('Note', { owner: id }, { plain: true })` (rows as stored, ids as strings), `await Noodl.Records.create('Note', { title, owner }, { acl })`, `await Noodl.Records.save(id, { title }, { className: 'Note', acl })`, `await Noodl.Records.delete(id, { className: 'Note' })` and `Noodl.Records.increment`. A cloud function runs as the system: it reads every record, may name `owner`, and creates where the collection's create rule is \"nobody\". `acl` is the record's whole ACL: `{ \"<userId>\": { read: true, write: true }, \"role:<name>\": { … }, \"*\": { … } }` (example `cloud-members-can-edit-my-records`)."
|
|
18535
18538
|
],
|
|
18536
18539
|
"antiPatterns": [
|
|
18537
18540
|
"Wiring to the bare script name — `toProperty: \"amount\"` for a script reading `Inputs.amount`. The port is `in-amount`; the bare name is only its display label. The door refuses it, and drawn by hand it fails on the canvas.",
|
|
@@ -21802,7 +21805,7 @@
|
|
|
21802
21805
|
},
|
|
21803
21806
|
"enrichment": {
|
|
21804
21807
|
"typeName": "NewDbModelProperties",
|
|
21805
|
-
"summary": "Create Record: inserts a record into a cloud database class from its property inputs
|
|
21808
|
+
"summary": "Create Record: inserts a record into a cloud database class from its property inputs on Do, with optional access rules (ACL: users, roles).",
|
|
21806
21809
|
"description": "NewDbModelProperties (Create Record) creates one record in the cloud database class chosen with the `collectionName` (Class) parameter. Each property of the class becomes a value input; when the `store` (Do) signal is triggered, the current input values are sent to the server as a new record. On success the node binds to the new record, outputs its `id`, and fires `done` (Done) followed by `completed`; on failure it fires `failure` with a message on `error`, and `completed` after that too. Optionally, `sourceObjectId` seeds the new record with the data of an existing local object (explicit property inputs win), and Access Control Rules restrict who can read/write the record (defaulting to the creating user when a rule is left unconfigured).",
|
|
21807
21810
|
"whenToUse": "The write half of every 'add item' flow — forms, quick-add buttons, duplicating a record via `sourceObjectId`. Live Query Records nodes pick the new record up automatically, so no manual re-fetch is needed. To modify an existing record use SetDbModelProperties instead; for a non-cloud (BYOB) backend use noodl.byob.CreateRecord.",
|
|
21808
21811
|
"ports": {
|
|
@@ -21811,18 +21814,20 @@
|
|
|
21811
21814
|
"completed": "Signal; fires after every Do whatever the outcome — wire it to carry on regardless. It never replaces `failure`, which still fires and still carries its reason.",
|
|
21812
21815
|
"id": "String value; the id of the created record — feed it to a Record node or navigation parameter.",
|
|
21813
21816
|
"sourceObjectId": "Value input (connections only): id of a local object/record whose data seeds the new record before the property inputs are applied.",
|
|
21814
|
-
"accessControl": "Edit-only list of access rules; each rule expands into its own Target/Read/Write inputs (see runtimeBehavior). With no rules the record
|
|
21817
|
+
"accessControl": "Edit-only list of access rules; each rule expands into its own Target/Read/Write inputs (see runtimeBehavior). With no rules, on the NodeGX backend's default (creator-owned) collections the record is the creator's alone: readable and changeable only by them. Rules you set REPLACE that, so keep one for the creator (target user, the User node's `id` wired into its User Id)."
|
|
21815
21818
|
},
|
|
21816
|
-
"runtimeBehavior": "Ports are runtime-determined: `collectionName` (Class) is registered as an edit-only enum of the project's classes plus _User/_Role, and each schema property of the chosen class becomes a value input `prop-<name>` typed from the schema. Each entry in the `accessControl` list additionally spawns `acl-<ruleId>-target` (user/everyone/role), then `acl-<ruleId>-userid` or `acl-<ruleId>-role`, plus `acl-<ruleId>-read` and `acl-<ruleId>-write` booleans. An authoring tool should set `collectionName` in parameters and drive `prop-<name>` inputs it knows from the class schema.",
|
|
21819
|
+
"runtimeBehavior": "Ports are runtime-determined: `collectionName` (Class) is registered as an edit-only enum of the project's classes plus _User/_Role, and each schema property of the chosen class becomes a value input `prop-<name>` typed from the schema. Each entry in the `accessControl` list additionally spawns `acl-<ruleId>-target` (user/everyone/role), then `acl-<ruleId>-userid` or `acl-<ruleId>-role`, plus `acl-<ruleId>-read` and `acl-<ruleId>-write` booleans. An authoring tool should set `collectionName` in parameters and drive `prop-<name>` inputs it knows from the class schema. What the rules store is the record's ACL, one key per rule: the user's id, `role:<name>` for a role rule (set `acl-<ruleId>-target` to \"role\" as a parameter, then wire or set `acl-<ruleId>-role` to the role's name without the `role:` prefix), or `*` for everyone; each key holds `{\"read\": true, \"write\": true}`. The backend lets a caller read or change the record only through a key it holds, after the collection's own find/get/update rule. Who joins a role is decided only in a cloud function (Add User To Role).",
|
|
21817
21820
|
"examples": [
|
|
21818
21821
|
"cloud-create-record-with-upload",
|
|
21819
21822
|
"cloud-record-crud",
|
|
21820
21823
|
"community-simple-audio-recorde",
|
|
21821
|
-
"community-web-rtc-video-record"
|
|
21824
|
+
"community-web-rtc-video-record",
|
|
21825
|
+
"cloud-members-can-edit-my-records"
|
|
21822
21826
|
],
|
|
21823
21827
|
"patterns": [
|
|
21824
21828
|
"Text Input `text` → `prop-<name>`, Button `onClick` → `store`: the minimal create form.",
|
|
21825
|
-
"`done` → a navigation or Event Sender, with `id` as payload: jump straight to the new record's detail page."
|
|
21829
|
+
"`done` → a navigation or Event Sender, with `id` as payload: jump straight to the new record's detail page.",
|
|
21830
|
+
"Records several people may all change: a rule for the creator and a role rule (`members-of-<ownerId>`), the role joined only in a cloud function that reads an invitation's `owner` (example `cloud-members-can-edit-my-records`). Where only a function should create the records, it creates them as the system and the collection's create rule is \"nobody\". A record it creates while a signed-in person's call runs is that person's (P112-OWN026-OPENSTAT); to share one, the function passes `acl` with `'*'`."
|
|
21826
21831
|
],
|
|
21827
21832
|
"antiPatterns": [
|
|
21828
21833
|
"Re-fetching a Query Records node after `done` — live queries already receive the new record; only re-fetch when the query is not live.",
|
|
@@ -31602,7 +31607,7 @@
|
|
|
31602
31607
|
},
|
|
31603
31608
|
"enrichment": {
|
|
31604
31609
|
"typeName": "SetDbModelProperties",
|
|
31605
|
-
"summary": "Update Record: writes property values to an existing cloud database record
|
|
31610
|
+
"summary": "Update Record: writes property values to an existing cloud database record on Do; access rules sent with it replace its ACL.",
|
|
31606
31611
|
"description": "SetDbModelProperties (Update Record) updates the record identified by `modelId` in the class chosen with the `collectionName` (Class) parameter. Each class property becomes a value input; triggering `store` (Do) applies the connected inputs to the local record and saves them to the server, firing `done` (Done) on confirmation or `failure` with `error` on problems, and `completed` after either. `storeType` (Store to) can switch the node to 'Local only', which updates the in-memory record without any server call — useful for optimistic UI or scratch state. By default only the properties you actually supply are sent (`storeProperties` = 'Only specified'); 'All' sends the record's entire current data. Because the shared local record is updated, every bound Record node and live query in the app sees the change immediately.",
|
|
31607
31612
|
"whenToUse": "The save half of every edit form, and the way to flip a single field ('mark done') from a list row. To create a record use NewDbModelProperties; to remove one use DeleteDbModelProperties; for BYOB backends use noodl.byob.UpdateRecord. For a count, score, balance or progress, set `addTo` and wire the change, not the new total.",
|
|
31608
31613
|
"ports": {
|
|
@@ -31613,7 +31618,7 @@
|
|
|
31613
31618
|
"idSource": "Edit-only choice: 'Specify explicitly' uses `modelId`; 'From repeater' targets the record driving the nearest enclosing For Each item.",
|
|
31614
31619
|
"storeProperties": "Edit-only choice ('Only specified' / 'All'): whether to send just the supplied property inputs or the record's whole data. Shown while `storeType` is cloud.",
|
|
31615
31620
|
"storeType": "Edit-only choice ('Cloud and local' / 'Local only'): whether the save reaches the server or only the in-memory record.",
|
|
31616
|
-
"accessControl": "Edit-only list of access rules applied with the save; each rule expands into its own Target/Read/Write inputs.",
|
|
31621
|
+
"accessControl": "Edit-only list of access rules applied with the save; each rule expands into its own Target/Read/Write inputs. Rules sent with an update replace the record's whole ACL (keys and shape as on Create Record); an update without rules leaves the ACL as it was.",
|
|
31617
31622
|
"onlyIfUnchanged": "Edit-only text (displayed as Only If Unchanged): property names, comma-separated, usually `version`. The update applies only if those properties still hold the values the record had when it was read (taken before this node's inputs are applied), checked by the backend inside the UPDATE itself. If someone else wrote the record in between, nothing is written, `failure` fires, `error` says so, and the local record goes back to what it held (the backend answered 409 with reason `precondition-failed`). To retry, FETCH the record again first: retrying with the same values fails the same way. NodeGX backends only; other backends refuse it. Empty: always write.",
|
|
31618
31623
|
"addTo": "Edit-only text (displayed as Add To): property names, comma-separated. For these, the value wired into `prop-<name>` is an AMOUNT (1, -1, 5) the backend adds to what it holds, in one step, instead of a value that replaces it, so two devices or tabs adding at once both count. The record then shows the backend's total. The other properties are saved first, so `onlyIfUnchanged` still guards them and a refused save adds nothing. A value that is not a number fails the Do and says so. 'Local only' adds the amount to the record held in memory."
|
|
31619
31624
|
},
|
|
@@ -31621,7 +31626,8 @@
|
|
|
31621
31626
|
"examples": [
|
|
31622
31627
|
"cloud-edit-record",
|
|
31623
31628
|
"cloud-record-crud",
|
|
31624
|
-
"cloud-shared-counter"
|
|
31629
|
+
"cloud-shared-counter",
|
|
31630
|
+
"cloud-members-can-edit-my-records"
|
|
31625
31631
|
],
|
|
31626
31632
|
"patterns": [
|
|
31627
31633
|
"A count, score, balance or progress: `addTo` = `points`, `prop-points` = 1 (or the change a Function works out), Button `onClick` → `store`. No script, no read of the old value: the backend adds it, and two devices pressing together both count (example `cloud-shared-counter`).",
|
|
@@ -37749,7 +37755,8 @@
|
|
|
37749
37755
|
],
|
|
37750
37756
|
"antiPatterns": [
|
|
37751
37757
|
"Treating `cloudFile` as set before `done` fired — sequence record writes on the Done signal.",
|
|
37752
|
-
"Setting `private: true` and then binding `cloudFile.url` straight to an Image/link — a private file's plain url 403s for anyone but the uploader; sign it first."
|
|
37758
|
+
"Setting `private: true` and then binding `cloudFile.url` straight to an Image/link — a private file's plain url 403s for anyone but the uploader; sign it first.",
|
|
37759
|
+
"Saving the url of `cloudFile` (or Cloud File's `url`) into a text property. That url names the address the backend was reached at during the upload, which is `127.0.0.1` on your own machine, so it stops working once the app runs on a host. Save the `cloudFile` handle into a file-typed property instead. The NodeGX backend keeps the file's name and fills its address in on every read."
|
|
37753
37760
|
],
|
|
37754
37761
|
"relatedNodes": [
|
|
37755
37762
|
"Open File Picker",
|
|
@@ -51296,7 +51303,7 @@
|
|
|
51296
51303
|
"always: the size it holds now, a bare number read in the unit it held; drawn at the next paint"
|
|
51297
51304
|
],
|
|
51298
51305
|
"visible": [
|
|
51299
|
-
"always: shown or hidden at
|
|
51306
|
+
"always: shown or hidden at once, its space kept; hidden, a Focus does not reach it"
|
|
51300
51307
|
]
|
|
51301
51308
|
},
|
|
51302
51309
|
"warnings": {
|
|
@@ -53514,7 +53521,7 @@
|
|
|
53514
53521
|
"always: the page, or the scrolling Group holding it, scrolls until the box can be seen"
|
|
53515
53522
|
],
|
|
53516
53523
|
"visible": [
|
|
53517
|
-
"always: shown or hidden at
|
|
53524
|
+
"always: shown or hidden at once, its space kept; hidden, a Focus does not reach it"
|
|
53518
53525
|
]
|
|
53519
53526
|
},
|
|
53520
53527
|
"warnings": {
|
|
@@ -56117,11 +56124,12 @@
|
|
|
56117
56124
|
],
|
|
56118
56125
|
"items": [
|
|
56119
56126
|
"nothing handed (undefined): the list it has stays",
|
|
56127
|
+
"the value is still the placed node's seed and the list does not offer it: the list is kept, and the value is cleared to '' and sent",
|
|
56120
56128
|
"otherwise: the list is kept as handed and drawn at the next paint; the value stays, so a list handed again keeps the pick"
|
|
56121
56129
|
],
|
|
56122
56130
|
"value": [
|
|
56123
56131
|
"null: ignored",
|
|
56124
|
-
"the value it already holds, as text: nothing",
|
|
56132
|
+
"the value it already holds, as text: nothing (it is no longer the seed)",
|
|
56125
56133
|
"a change: kept as text, Value follows, and the list selects it at the next paint if it names an option — Changed does not fire (only a person's pick fires it)"
|
|
56126
56134
|
],
|
|
56127
56135
|
"mounted": [
|
|
@@ -56149,7 +56157,7 @@
|
|
|
56149
56157
|
"always: the size it holds now, a bare number read in the unit it held; drawn at the next paint"
|
|
56150
56158
|
],
|
|
56151
56159
|
"visible": [
|
|
56152
|
-
"always: shown or hidden at
|
|
56160
|
+
"always: shown or hidden at once, its space kept; hidden, a Focus does not reach it"
|
|
56153
56161
|
]
|
|
56154
56162
|
},
|
|
56155
56163
|
"warnings": {
|
|
@@ -58353,7 +58361,7 @@
|
|
|
58353
58361
|
"always: the page, or the scrolling Group holding it, scrolls until the button can be seen"
|
|
58354
58362
|
],
|
|
58355
58363
|
"visible": [
|
|
58356
|
-
"always: shown or hidden at
|
|
58364
|
+
"always: shown or hidden at once, its space kept; hidden, it takes no click and a Focus does not reach it"
|
|
58357
58365
|
]
|
|
58358
58366
|
},
|
|
58359
58367
|
"warnings": {
|
|
@@ -60779,7 +60787,7 @@
|
|
|
60779
60787
|
"always: the size it holds now, a bare number read in the unit it held; drawn at the next paint"
|
|
60780
60788
|
],
|
|
60781
60789
|
"visible": [
|
|
60782
|
-
"always: shown or hidden at
|
|
60790
|
+
"always: shown or hidden at once, its space kept; hidden, a Focus does not reach it"
|
|
60783
60791
|
]
|
|
60784
60792
|
},
|
|
60785
60793
|
"warnings": {
|
|
@@ -63672,7 +63680,7 @@
|
|
|
63672
63680
|
"always: the size it holds now, a bare number read in the unit it held; drawn at the next paint"
|
|
63673
63681
|
],
|
|
63674
63682
|
"visible": [
|
|
63675
|
-
"always: shown or hidden at
|
|
63683
|
+
"always: shown or hidden at once, its space kept; hidden, a Focus does not reach it"
|
|
63676
63684
|
],
|
|
63677
63685
|
"opacity": [
|
|
63678
63686
|
"always: how see-through it is; at exactly 0 it takes no pointer, and an act goes to what is behind it"
|
|
@@ -66997,10 +67005,12 @@
|
|
|
66997
67005
|
"error": "Value; why the last attempt failed."
|
|
66998
67006
|
},
|
|
66999
67007
|
"examples": [
|
|
67000
|
-
"cloud-invite-a-team-member"
|
|
67008
|
+
"cloud-invite-a-team-member",
|
|
67009
|
+
"cloud-members-can-edit-my-records"
|
|
67001
67010
|
],
|
|
67002
67011
|
"patterns": [
|
|
67003
|
-
"Wire straight after Create User in a signup function to put every new account into a default role like `member` — `unchanged` and `done` both mean the account is now in that role, so wiring both to the same next step covers a re-run cleanly."
|
|
67012
|
+
"Wire straight after Create User in a signup function to put every new account into a default role like `member` — `unchanged` and `done` both mean the account is now in that role, so wiring both to the same next step covers a re-run cleanly.",
|
|
67013
|
+
"A group per owner, joined by invitation: the role is named in code from the owner's id (`'members-of-' + invitation.owner`), so `createRole` is on (one name per owner, never typed), and the owner is the invitation's `owner` (the backend's word), never a group field the invitation carries: anyone signed in can create an invitation naming any group (example `cloud-members-can-edit-my-records`)."
|
|
67004
67014
|
],
|
|
67005
67015
|
"antiPatterns": [
|
|
67006
67016
|
"Wiring a request parameter straight into `userId` (or `role`) on a function anyone may call. That is 'grant any role to any account' with an HTTP endpoint in front of it — give the function a `call` rule (CWF-017) and check inside the graph that the caller is entitled to make this particular grant.",
|
|
@@ -69207,7 +69217,8 @@
|
|
|
69207
69217
|
"error": "Value; why the last attempt failed, or (on Unchanged) which of the two non-membership cases it was."
|
|
69208
69218
|
},
|
|
69209
69219
|
"examples": [
|
|
69210
|
-
"cloud-revoke-a-role-and-record-it"
|
|
69220
|
+
"cloud-revoke-a-role-and-record-it",
|
|
69221
|
+
"cloud-members-can-edit-my-records"
|
|
69211
69222
|
],
|
|
69212
69223
|
"patterns": [
|
|
69213
69224
|
"Wire a lapsed-subscription webhook straight to this node's `remove`, with `done` and `unchanged` both leading to the same next step — the account ends up without the role either way, which is the only thing that response needs to be true."
|
|
@@ -69247,7 +69258,7 @@
|
|
|
69247
69258
|
},
|
|
69248
69259
|
"isSignal": false,
|
|
69249
69260
|
"default": false,
|
|
69250
|
-
"description": "Whether a request with no valid session token is allowed to run this function at all"
|
|
69261
|
+
"description": "Whether a request with no valid session token is allowed to run this function at all. A run the app starts itself — a schedule, a row change, a run from Studio — is always allowed and sees nobody signed in; a webhook is a request like any other"
|
|
69251
69262
|
},
|
|
69252
69263
|
{
|
|
69253
69264
|
"name": "params",
|
|
@@ -69301,7 +69312,7 @@
|
|
|
69301
69312
|
"group": "Request",
|
|
69302
69313
|
"plug": "output",
|
|
69303
69314
|
"type": {
|
|
69304
|
-
"name": "
|
|
69315
|
+
"name": "string"
|
|
69305
69316
|
},
|
|
69306
69317
|
"isSignal": false,
|
|
69307
69318
|
"description": "Id of the user the session token resolved to, and blank for an unauthenticated request"
|
|
@@ -70962,7 +70973,10 @@
|
|
|
70962
70973
|
{
|
|
70963
70974
|
"id": "prompt_body",
|
|
70964
70975
|
"type": "Javascript2",
|
|
70965
|
-
"label": "Build request body"
|
|
70976
|
+
"label": "Build request body",
|
|
70977
|
+
"parameters": {
|
|
70978
|
+
"code": "Script.Inputs = { prompt: 'string', token: 'string' };\nScript.Outputs = { body: 'object', headers: 'object', send: 'signal' };\n\n// Run builds the request from the prompt as it is now, then asks the stream to connect.\n// `token` is the signed-in person's token for your own endpoint, never a provider key in the page.\nScript.Signals.run = function () {\n Script.setOutputs({\n body: { stream: true, messages: [{ role: 'user', content: Script.Inputs.prompt || '' }] },\n headers: { Authorization: 'Bearer ' + (Script.Inputs.token || '') }\n });\n Script.Outputs.send();\n};"
|
|
70979
|
+
}
|
|
70966
70980
|
},
|
|
70967
70981
|
{
|
|
70968
70982
|
"id": "chat_stream",
|
|
@@ -70990,7 +71004,18 @@
|
|
|
70990
71004
|
{
|
|
70991
71005
|
"id": "state_label",
|
|
70992
71006
|
"type": "String Mapper",
|
|
70993
|
-
"label": "State to label"
|
|
71007
|
+
"label": "State to label",
|
|
71008
|
+
"parameters": {
|
|
71009
|
+
"input 0": "connecting",
|
|
71010
|
+
"output 0": "Connecting…",
|
|
71011
|
+
"input 1": "open",
|
|
71012
|
+
"output 1": "Answering",
|
|
71013
|
+
"input 2": "reconnecting",
|
|
71014
|
+
"output 2": "Reconnecting…",
|
|
71015
|
+
"input 3": "error",
|
|
71016
|
+
"output 3": "The stream failed",
|
|
71017
|
+
"defaultMapping": ""
|
|
71018
|
+
}
|
|
70994
71019
|
}
|
|
70995
71020
|
],
|
|
70996
71021
|
"connections": [
|
|
@@ -71052,11 +71077,11 @@
|
|
|
71052
71077
|
"fromId": "chat_stream",
|
|
71053
71078
|
"fromProperty": "connectionState",
|
|
71054
71079
|
"toId": "state_label",
|
|
71055
|
-
"toProperty": "
|
|
71080
|
+
"toProperty": "inputString"
|
|
71056
71081
|
},
|
|
71057
71082
|
{
|
|
71058
71083
|
"fromId": "state_label",
|
|
71059
|
-
"fromProperty": "
|
|
71084
|
+
"fromProperty": "mappedString",
|
|
71060
71085
|
"toId": "status_text",
|
|
71061
71086
|
"toProperty": "text"
|
|
71062
71087
|
}
|
|
@@ -71469,7 +71494,7 @@
|
|
|
71469
71494
|
{
|
|
71470
71495
|
"id": "agent-store-shared-state",
|
|
71471
71496
|
"title": "Shared state: one store read by two unrelated components",
|
|
71472
|
-
"description": "Two components that are not parent and child, sharing state through a named store instead of prop drilling or a Send/Receive Event pair. Sign In writes `user` with Set Global Store; Header declares the same store by name and
|
|
71497
|
+
"description": "Two components that are not parent and child, sharing state through a named store instead of prop drilling or a Send/Receive Event pair. Sign In writes `user` with Set Global Store; Header declares the same store by name and reads `user` from it — no wiring runs between the two components, because naming the store is what connects them. Header subscribes to just the `user` key, so a write to any other key does not wake it, and puts the value into its greeting through a String Format whose `{user}` tag is the input the wire lands on. The store persists under its storage key, so the greeting survives a reload.",
|
|
71473
71498
|
"demonstrates": [
|
|
71474
71499
|
"net.noodl.GlobalStore",
|
|
71475
71500
|
"net.noodl.GlobalStore.Set",
|
|
@@ -71571,7 +71596,10 @@
|
|
|
71571
71596
|
{
|
|
71572
71597
|
"id": "greeting",
|
|
71573
71598
|
"type": "String Format",
|
|
71574
|
-
"label": "Format greeting"
|
|
71599
|
+
"label": "Format greeting",
|
|
71600
|
+
"parameters": {
|
|
71601
|
+
"format": "Signed in as {user}"
|
|
71602
|
+
}
|
|
71575
71603
|
}
|
|
71576
71604
|
],
|
|
71577
71605
|
"connections": [
|
|
@@ -71579,11 +71607,11 @@
|
|
|
71579
71607
|
"fromId": "watch_user",
|
|
71580
71608
|
"fromProperty": "value",
|
|
71581
71609
|
"toId": "greeting",
|
|
71582
|
-
"toProperty": "
|
|
71610
|
+
"toProperty": "user"
|
|
71583
71611
|
},
|
|
71584
71612
|
{
|
|
71585
71613
|
"fromId": "greeting",
|
|
71586
|
-
"fromProperty": "
|
|
71614
|
+
"fromProperty": "formatted",
|
|
71587
71615
|
"toId": "user_text",
|
|
71588
71616
|
"toProperty": "text"
|
|
71589
71617
|
}
|
|
@@ -71952,7 +71980,7 @@
|
|
|
71952
71980
|
"type": "net.noodl.Log",
|
|
71953
71981
|
"label": "The provider round trip did not start",
|
|
71954
71982
|
"parameters": {
|
|
71955
|
-
"level": "
|
|
71983
|
+
"level": "warn"
|
|
71956
71984
|
}
|
|
71957
71985
|
},
|
|
71958
71986
|
{
|
|
@@ -72554,7 +72582,7 @@
|
|
|
72554
72582
|
"type": "net.noodl.Log",
|
|
72555
72583
|
"label": "Unreadable due date",
|
|
72556
72584
|
"parameters": {
|
|
72557
|
-
"level": "
|
|
72585
|
+
"level": "warn",
|
|
72558
72586
|
"message": "Invoice row: the due date could not be read as a date."
|
|
72559
72587
|
}
|
|
72560
72588
|
}
|
|
@@ -72779,7 +72807,7 @@
|
|
|
72779
72807
|
"type": "net.noodl.Log",
|
|
72780
72808
|
"label": "The server did not answer usably",
|
|
72781
72809
|
"parameters": {
|
|
72782
|
-
"level": "
|
|
72810
|
+
"level": "warn"
|
|
72783
72811
|
}
|
|
72784
72812
|
},
|
|
72785
72813
|
{
|
|
@@ -72787,7 +72815,7 @@
|
|
|
72787
72815
|
"type": "net.noodl.Log",
|
|
72788
72816
|
"label": "The answer was not a feed",
|
|
72789
72817
|
"parameters": {
|
|
72790
|
-
"level": "
|
|
72818
|
+
"level": "warn"
|
|
72791
72819
|
}
|
|
72792
72820
|
}
|
|
72793
72821
|
],
|
|
@@ -72958,7 +72986,7 @@
|
|
|
72958
72986
|
"type": "net.noodl.Log",
|
|
72959
72987
|
"label": "The paste would not parse",
|
|
72960
72988
|
"parameters": {
|
|
72961
|
-
"level": "
|
|
72989
|
+
"level": "warn"
|
|
72962
72990
|
}
|
|
72963
72991
|
}
|
|
72964
72992
|
],
|
|
@@ -73057,7 +73085,7 @@
|
|
|
73057
73085
|
"type": "net.noodl.Log",
|
|
73058
73086
|
"label": "The server did not answer usably",
|
|
73059
73087
|
"parameters": {
|
|
73060
|
-
"level": "
|
|
73088
|
+
"level": "warn"
|
|
73061
73089
|
}
|
|
73062
73090
|
},
|
|
73063
73091
|
{
|
|
@@ -73065,7 +73093,7 @@
|
|
|
73065
73093
|
"type": "net.noodl.Log",
|
|
73066
73094
|
"label": "Refused or unreadable — read Error Code",
|
|
73067
73095
|
"parameters": {
|
|
73068
|
-
"level": "
|
|
73096
|
+
"level": "warn"
|
|
73069
73097
|
}
|
|
73070
73098
|
}
|
|
73071
73099
|
],
|
|
@@ -73422,7 +73450,7 @@
|
|
|
73422
73450
|
],
|
|
73423
73451
|
"components": [
|
|
73424
73452
|
{
|
|
73425
|
-
"name": "/Cloud Functions/Check Client Session",
|
|
73453
|
+
"name": "/#__cloud__/Cloud Functions/Check Client Session",
|
|
73426
73454
|
"nodes": [
|
|
73427
73455
|
{
|
|
73428
73456
|
"id": "fn_request",
|
|
@@ -73526,7 +73554,7 @@
|
|
|
73526
73554
|
],
|
|
73527
73555
|
"components": [
|
|
73528
73556
|
{
|
|
73529
|
-
"name": "/Cloud Functions/Verify Inbound",
|
|
73557
|
+
"name": "/#__cloud__/Cloud Functions/Verify Inbound",
|
|
73530
73558
|
"nodes": [
|
|
73531
73559
|
{
|
|
73532
73560
|
"id": "fn_request",
|
|
@@ -73834,7 +73862,7 @@
|
|
|
73834
73862
|
],
|
|
73835
73863
|
"components": [
|
|
73836
73864
|
{
|
|
73837
|
-
"name": "/Cloud Functions/Delete Account",
|
|
73865
|
+
"name": "/#__cloud__/Cloud Functions/Delete Account",
|
|
73838
73866
|
"nodes": [
|
|
73839
73867
|
{
|
|
73840
73868
|
"id": "fn_request",
|
|
@@ -74208,7 +74236,7 @@
|
|
|
74208
74236
|
],
|
|
74209
74237
|
"components": [
|
|
74210
74238
|
{
|
|
74211
|
-
"name": "/Cloud Functions/Invite Team Member",
|
|
74239
|
+
"name": "/#__cloud__/Cloud Functions/Invite Team Member",
|
|
74212
74240
|
"nodes": [
|
|
74213
74241
|
{
|
|
74214
74242
|
"id": "fn_request",
|
|
@@ -74346,6 +74374,853 @@
|
|
|
74346
74374
|
}
|
|
74347
74375
|
]
|
|
74348
74376
|
},
|
|
74377
|
+
{
|
|
74378
|
+
"id": "cloud-members-can-edit-my-records",
|
|
74379
|
+
"title": "Invite people who can change my records too: a role per owner, joined only through an invitation its owner made",
|
|
74380
|
+
"description": "A second person, or several, who can change everything you can: people who may all read AND change the same records (a partner, a co-organiser, a colleague, anyone you invite to edit with you). The group belongs to one person, its owner, and its role is named from that person's id (\"members-of-<id>\"). The owner's page creates an Invite holding only the invited address; the backend writes who made it into `owner`. Join Group, a cloud function the invited person's page calls, finds the invitations naming the caller's own address (from their session, not the request) and puts them in the role of the invitation's OWNER, never a group id the invitation carries: anyone signed in can create an Invite naming any group, and only `owner` is the server's word, so an invitation can only ever let someone into its creator's own group. Every record of the group is created by Add To Group, which runs as the system: it refuses a caller who is neither the owner nor in the role (the User node's `roles`, read from the caller's session), writes the GROUP owner into `owner` (a page could never name it), and stores access rules for the owner and the role ({\"<ownerId>\": read+write, \"role:members-of-<ownerId>\": read+write}). So members change a record straight from the page with Update Record (the role's write rule lets them), and the group's list is a Query Records filtered on `owner` = the group, which a record somebody else made and opened to the role can never match. Remove Member takes back the caller's own invitation and takes the member out of the caller's own group, whatever the invitation says. `createRole` is on in Join Group because the role name is built in code, one per owner, never typed. Declare Invite (withEmail, memberId) and Note (title) in provision_backend, both creator-owned (the default), and set Note's create rule to \"nobody\" (set_collection_permissions): a cloud function runs as the system and still creates, and a Note posted straight to the collection from a page is refused, so a check in Add To Group is not one a caller can walk around.",
|
|
74381
|
+
"demonstrates": [
|
|
74382
|
+
"NewDbModelProperties",
|
|
74383
|
+
"SetDbModelProperties",
|
|
74384
|
+
"DbCollection2",
|
|
74385
|
+
"CloudFunction2",
|
|
74386
|
+
"noodl.cloud.request",
|
|
74387
|
+
"net.noodl.user.User",
|
|
74388
|
+
"JavaScriptFunction",
|
|
74389
|
+
"noodl.cloud.addusertorole",
|
|
74390
|
+
"noodl.cloud.removeuserfromrole",
|
|
74391
|
+
"noodl.cloud.response"
|
|
74392
|
+
],
|
|
74393
|
+
"components": [
|
|
74394
|
+
{
|
|
74395
|
+
"name": "/Member Row",
|
|
74396
|
+
"nodes": [
|
|
74397
|
+
{
|
|
74398
|
+
"id": "mem_root",
|
|
74399
|
+
"type": "Group",
|
|
74400
|
+
"label": "One invitation",
|
|
74401
|
+
"parameters": {
|
|
74402
|
+
"flexDirection": "row",
|
|
74403
|
+
"columnGap": "var(--space-2)",
|
|
74404
|
+
"alignItems": "center"
|
|
74405
|
+
},
|
|
74406
|
+
"children": [
|
|
74407
|
+
"mem_address",
|
|
74408
|
+
"mem_remove"
|
|
74409
|
+
]
|
|
74410
|
+
},
|
|
74411
|
+
{
|
|
74412
|
+
"id": "mem_address",
|
|
74413
|
+
"type": "Text",
|
|
74414
|
+
"label": "Who it invites",
|
|
74415
|
+
"parent": "mem_root"
|
|
74416
|
+
},
|
|
74417
|
+
{
|
|
74418
|
+
"id": "mem_remove",
|
|
74419
|
+
"type": "net.noodl.controls.button",
|
|
74420
|
+
"label": "Take it back",
|
|
74421
|
+
"parent": "mem_root",
|
|
74422
|
+
"parameters": {
|
|
74423
|
+
"label": "Remove"
|
|
74424
|
+
}
|
|
74425
|
+
},
|
|
74426
|
+
{
|
|
74427
|
+
"id": "mem_inputs",
|
|
74428
|
+
"type": "Component Inputs",
|
|
74429
|
+
"label": "Invitation in",
|
|
74430
|
+
"ports": [
|
|
74431
|
+
{
|
|
74432
|
+
"name": "withEmail",
|
|
74433
|
+
"plug": "output",
|
|
74434
|
+
"type": "*"
|
|
74435
|
+
}
|
|
74436
|
+
]
|
|
74437
|
+
},
|
|
74438
|
+
{
|
|
74439
|
+
"id": "mem_call",
|
|
74440
|
+
"type": "CloudFunction2",
|
|
74441
|
+
"label": "Take the invitation back, and the access with it",
|
|
74442
|
+
"parameters": {
|
|
74443
|
+
"function": "Cloud Functions/Remove Member"
|
|
74444
|
+
}
|
|
74445
|
+
}
|
|
74446
|
+
],
|
|
74447
|
+
"connections": [
|
|
74448
|
+
{
|
|
74449
|
+
"fromId": "mem_inputs",
|
|
74450
|
+
"fromProperty": "withEmail",
|
|
74451
|
+
"toId": "mem_address",
|
|
74452
|
+
"toProperty": "text"
|
|
74453
|
+
},
|
|
74454
|
+
{
|
|
74455
|
+
"fromId": "mem_inputs",
|
|
74456
|
+
"fromProperty": "withEmail",
|
|
74457
|
+
"toId": "mem_call",
|
|
74458
|
+
"toProperty": "in-withEmail"
|
|
74459
|
+
},
|
|
74460
|
+
{
|
|
74461
|
+
"fromId": "mem_remove",
|
|
74462
|
+
"fromProperty": "onClick",
|
|
74463
|
+
"toId": "mem_call",
|
|
74464
|
+
"toProperty": "call"
|
|
74465
|
+
}
|
|
74466
|
+
]
|
|
74467
|
+
},
|
|
74468
|
+
{
|
|
74469
|
+
"name": "/Invite Someone To Edit",
|
|
74470
|
+
"nodes": [
|
|
74471
|
+
{
|
|
74472
|
+
"id": "inv_root",
|
|
74473
|
+
"type": "Group",
|
|
74474
|
+
"label": "Invite someone to edit my notes",
|
|
74475
|
+
"parameters": {
|
|
74476
|
+
"flexDirection": "column",
|
|
74477
|
+
"rowGap": "var(--space-3)"
|
|
74478
|
+
},
|
|
74479
|
+
"children": [
|
|
74480
|
+
"inv_address",
|
|
74481
|
+
"inv_button",
|
|
74482
|
+
"inv_error",
|
|
74483
|
+
"inv_list"
|
|
74484
|
+
]
|
|
74485
|
+
},
|
|
74486
|
+
{
|
|
74487
|
+
"id": "inv_address",
|
|
74488
|
+
"type": "net.noodl.controls.textinput",
|
|
74489
|
+
"label": "Their email address",
|
|
74490
|
+
"parent": "inv_root",
|
|
74491
|
+
"parameters": {
|
|
74492
|
+
"type": "email",
|
|
74493
|
+
"placeholder": "name@example.com"
|
|
74494
|
+
}
|
|
74495
|
+
},
|
|
74496
|
+
{
|
|
74497
|
+
"id": "inv_button",
|
|
74498
|
+
"type": "net.noodl.controls.button",
|
|
74499
|
+
"label": "Invite them",
|
|
74500
|
+
"parent": "inv_root",
|
|
74501
|
+
"parameters": {
|
|
74502
|
+
"label": "Invite"
|
|
74503
|
+
}
|
|
74504
|
+
},
|
|
74505
|
+
{
|
|
74506
|
+
"id": "inv_error",
|
|
74507
|
+
"type": "Text",
|
|
74508
|
+
"label": "Why it did not save",
|
|
74509
|
+
"parent": "inv_root",
|
|
74510
|
+
"parameters": {
|
|
74511
|
+
"text": ""
|
|
74512
|
+
}
|
|
74513
|
+
},
|
|
74514
|
+
{
|
|
74515
|
+
"id": "inv_list",
|
|
74516
|
+
"type": "For Each",
|
|
74517
|
+
"label": "One row per invitation I made",
|
|
74518
|
+
"parent": "inv_root",
|
|
74519
|
+
"parameters": {
|
|
74520
|
+
"template": "/Member Row"
|
|
74521
|
+
}
|
|
74522
|
+
},
|
|
74523
|
+
{
|
|
74524
|
+
"id": "inv_create",
|
|
74525
|
+
"type": "NewDbModelProperties",
|
|
74526
|
+
"label": "An Invite naming only their address (owner is the backend's)",
|
|
74527
|
+
"parameters": {
|
|
74528
|
+
"collectionName": "Invite"
|
|
74529
|
+
}
|
|
74530
|
+
},
|
|
74531
|
+
{
|
|
74532
|
+
"id": "inv_mine",
|
|
74533
|
+
"type": "DbCollection2",
|
|
74534
|
+
"label": "My invitations (creator-owned: a query returns only mine)",
|
|
74535
|
+
"parameters": {
|
|
74536
|
+
"collectionName": "Invite",
|
|
74537
|
+
"runOnChange-collectionName": false,
|
|
74538
|
+
"runOnChange-querySettings": false
|
|
74539
|
+
}
|
|
74540
|
+
}
|
|
74541
|
+
],
|
|
74542
|
+
"connections": [
|
|
74543
|
+
{
|
|
74544
|
+
"fromId": "inv_address",
|
|
74545
|
+
"fromProperty": "onTextChanged",
|
|
74546
|
+
"toId": "inv_create",
|
|
74547
|
+
"toProperty": "prop-withEmail"
|
|
74548
|
+
},
|
|
74549
|
+
{
|
|
74550
|
+
"fromId": "inv_button",
|
|
74551
|
+
"fromProperty": "onClick",
|
|
74552
|
+
"toId": "inv_create",
|
|
74553
|
+
"toProperty": "store"
|
|
74554
|
+
},
|
|
74555
|
+
{
|
|
74556
|
+
"fromId": "inv_create",
|
|
74557
|
+
"fromProperty": "error",
|
|
74558
|
+
"toId": "inv_error",
|
|
74559
|
+
"toProperty": "text"
|
|
74560
|
+
},
|
|
74561
|
+
{
|
|
74562
|
+
"fromId": "inv_root",
|
|
74563
|
+
"fromProperty": "didMount",
|
|
74564
|
+
"toId": "inv_mine",
|
|
74565
|
+
"toProperty": "storageFetch"
|
|
74566
|
+
},
|
|
74567
|
+
{
|
|
74568
|
+
"fromId": "inv_create",
|
|
74569
|
+
"fromProperty": "done",
|
|
74570
|
+
"toId": "inv_mine",
|
|
74571
|
+
"toProperty": "storageFetch"
|
|
74572
|
+
},
|
|
74573
|
+
{
|
|
74574
|
+
"fromId": "inv_mine",
|
|
74575
|
+
"fromProperty": "items",
|
|
74576
|
+
"toId": "inv_list",
|
|
74577
|
+
"toProperty": "items"
|
|
74578
|
+
}
|
|
74579
|
+
]
|
|
74580
|
+
},
|
|
74581
|
+
{
|
|
74582
|
+
"name": "/Group Note Row",
|
|
74583
|
+
"nodes": [
|
|
74584
|
+
{
|
|
74585
|
+
"id": "gnr_root",
|
|
74586
|
+
"type": "Group",
|
|
74587
|
+
"label": "One note of the group",
|
|
74588
|
+
"parameters": {
|
|
74589
|
+
"flexDirection": "row",
|
|
74590
|
+
"columnGap": "var(--space-2)",
|
|
74591
|
+
"alignItems": "center"
|
|
74592
|
+
},
|
|
74593
|
+
"children": [
|
|
74594
|
+
"gnr_title",
|
|
74595
|
+
"gnr_save"
|
|
74596
|
+
]
|
|
74597
|
+
},
|
|
74598
|
+
{
|
|
74599
|
+
"id": "gnr_title",
|
|
74600
|
+
"type": "net.noodl.controls.textinput",
|
|
74601
|
+
"label": "The note, editable by every member",
|
|
74602
|
+
"parent": "gnr_root"
|
|
74603
|
+
},
|
|
74604
|
+
{
|
|
74605
|
+
"id": "gnr_save",
|
|
74606
|
+
"type": "net.noodl.controls.button",
|
|
74607
|
+
"label": "Save the change",
|
|
74608
|
+
"parent": "gnr_root",
|
|
74609
|
+
"parameters": {
|
|
74610
|
+
"label": "Save"
|
|
74611
|
+
}
|
|
74612
|
+
},
|
|
74613
|
+
{
|
|
74614
|
+
"id": "gnr_inputs",
|
|
74615
|
+
"type": "Component Inputs",
|
|
74616
|
+
"label": "Note in",
|
|
74617
|
+
"ports": [
|
|
74618
|
+
{
|
|
74619
|
+
"name": "title",
|
|
74620
|
+
"plug": "output",
|
|
74621
|
+
"type": "*"
|
|
74622
|
+
}
|
|
74623
|
+
]
|
|
74624
|
+
},
|
|
74625
|
+
{
|
|
74626
|
+
"id": "gnr_update",
|
|
74627
|
+
"type": "SetDbModelProperties",
|
|
74628
|
+
"label": "Change this note (the group's role may write it)",
|
|
74629
|
+
"parameters": {
|
|
74630
|
+
"collectionName": "Note",
|
|
74631
|
+
"idSource": "foreach"
|
|
74632
|
+
}
|
|
74633
|
+
}
|
|
74634
|
+
],
|
|
74635
|
+
"connections": [
|
|
74636
|
+
{
|
|
74637
|
+
"fromId": "gnr_inputs",
|
|
74638
|
+
"fromProperty": "title",
|
|
74639
|
+
"toId": "gnr_title",
|
|
74640
|
+
"toProperty": "startValue"
|
|
74641
|
+
},
|
|
74642
|
+
{
|
|
74643
|
+
"fromId": "gnr_title",
|
|
74644
|
+
"fromProperty": "onTextChanged",
|
|
74645
|
+
"toId": "gnr_update",
|
|
74646
|
+
"toProperty": "prop-title"
|
|
74647
|
+
},
|
|
74648
|
+
{
|
|
74649
|
+
"fromId": "gnr_save",
|
|
74650
|
+
"fromProperty": "onClick",
|
|
74651
|
+
"toId": "gnr_update",
|
|
74652
|
+
"toProperty": "store"
|
|
74653
|
+
}
|
|
74654
|
+
]
|
|
74655
|
+
},
|
|
74656
|
+
{
|
|
74657
|
+
"name": "/Group Notes",
|
|
74658
|
+
"nodes": [
|
|
74659
|
+
{
|
|
74660
|
+
"id": "grp_root",
|
|
74661
|
+
"type": "Group",
|
|
74662
|
+
"label": "The notes of the group I am in",
|
|
74663
|
+
"parameters": {
|
|
74664
|
+
"flexDirection": "column",
|
|
74665
|
+
"rowGap": "var(--space-3)"
|
|
74666
|
+
},
|
|
74667
|
+
"children": [
|
|
74668
|
+
"grp_title",
|
|
74669
|
+
"grp_add",
|
|
74670
|
+
"grp_list"
|
|
74671
|
+
]
|
|
74672
|
+
},
|
|
74673
|
+
{
|
|
74674
|
+
"id": "grp_title",
|
|
74675
|
+
"type": "net.noodl.controls.textinput",
|
|
74676
|
+
"label": "A new note",
|
|
74677
|
+
"parent": "grp_root",
|
|
74678
|
+
"parameters": {
|
|
74679
|
+
"placeholder": "A new note"
|
|
74680
|
+
}
|
|
74681
|
+
},
|
|
74682
|
+
{
|
|
74683
|
+
"id": "grp_add",
|
|
74684
|
+
"type": "net.noodl.controls.button",
|
|
74685
|
+
"label": "Add it to the group",
|
|
74686
|
+
"parent": "grp_root",
|
|
74687
|
+
"parameters": {
|
|
74688
|
+
"label": "Add"
|
|
74689
|
+
}
|
|
74690
|
+
},
|
|
74691
|
+
{
|
|
74692
|
+
"id": "grp_list",
|
|
74693
|
+
"type": "For Each",
|
|
74694
|
+
"label": "One row per note of the group",
|
|
74695
|
+
"parent": "grp_root",
|
|
74696
|
+
"parameters": {
|
|
74697
|
+
"template": "/Group Note Row"
|
|
74698
|
+
}
|
|
74699
|
+
},
|
|
74700
|
+
{
|
|
74701
|
+
"id": "grp_join",
|
|
74702
|
+
"type": "CloudFunction2",
|
|
74703
|
+
"label": "Which group am I in (the server decides)",
|
|
74704
|
+
"parameters": {
|
|
74705
|
+
"function": "Cloud Functions/Join Group"
|
|
74706
|
+
}
|
|
74707
|
+
},
|
|
74708
|
+
{
|
|
74709
|
+
"id": "grp_notes",
|
|
74710
|
+
"type": "DbCollection2",
|
|
74711
|
+
"label": "The group's notes: owned by the group's owner",
|
|
74712
|
+
"parameters": {
|
|
74713
|
+
"collectionName": "Note",
|
|
74714
|
+
"visualFilter": {
|
|
74715
|
+
"combinator": "and",
|
|
74716
|
+
"rules": [
|
|
74717
|
+
{
|
|
74718
|
+
"property": "owner",
|
|
74719
|
+
"operator": "equal to",
|
|
74720
|
+
"input": "group"
|
|
74721
|
+
}
|
|
74722
|
+
]
|
|
74723
|
+
},
|
|
74724
|
+
"visualSort": [
|
|
74725
|
+
{
|
|
74726
|
+
"property": "createdAt",
|
|
74727
|
+
"order": "ascending"
|
|
74728
|
+
}
|
|
74729
|
+
],
|
|
74730
|
+
"runOnChange-collectionName": false,
|
|
74731
|
+
"runOnChange-querySettings": false
|
|
74732
|
+
}
|
|
74733
|
+
},
|
|
74734
|
+
{
|
|
74735
|
+
"id": "grp_add_call",
|
|
74736
|
+
"type": "CloudFunction2",
|
|
74737
|
+
"label": "Add a note to the group (the server checks I am in it)",
|
|
74738
|
+
"parameters": {
|
|
74739
|
+
"function": "Cloud Functions/Add To Group"
|
|
74740
|
+
}
|
|
74741
|
+
}
|
|
74742
|
+
],
|
|
74743
|
+
"connections": [
|
|
74744
|
+
{
|
|
74745
|
+
"fromId": "grp_root",
|
|
74746
|
+
"fromProperty": "didMount",
|
|
74747
|
+
"toId": "grp_join",
|
|
74748
|
+
"toProperty": "call"
|
|
74749
|
+
},
|
|
74750
|
+
{
|
|
74751
|
+
"fromId": "grp_join",
|
|
74752
|
+
"fromProperty": "out-group",
|
|
74753
|
+
"toId": "grp_notes",
|
|
74754
|
+
"toProperty": "qp-group"
|
|
74755
|
+
},
|
|
74756
|
+
{
|
|
74757
|
+
"fromId": "grp_join",
|
|
74758
|
+
"fromProperty": "done",
|
|
74759
|
+
"toId": "grp_notes",
|
|
74760
|
+
"toProperty": "storageFetch"
|
|
74761
|
+
},
|
|
74762
|
+
{
|
|
74763
|
+
"fromId": "grp_notes",
|
|
74764
|
+
"fromProperty": "items",
|
|
74765
|
+
"toId": "grp_list",
|
|
74766
|
+
"toProperty": "items"
|
|
74767
|
+
},
|
|
74768
|
+
{
|
|
74769
|
+
"fromId": "grp_title",
|
|
74770
|
+
"fromProperty": "onTextChanged",
|
|
74771
|
+
"toId": "grp_add_call",
|
|
74772
|
+
"toProperty": "in-title"
|
|
74773
|
+
},
|
|
74774
|
+
{
|
|
74775
|
+
"fromId": "grp_join",
|
|
74776
|
+
"fromProperty": "out-group",
|
|
74777
|
+
"toId": "grp_add_call",
|
|
74778
|
+
"toProperty": "in-group"
|
|
74779
|
+
},
|
|
74780
|
+
{
|
|
74781
|
+
"fromId": "grp_add",
|
|
74782
|
+
"fromProperty": "onClick",
|
|
74783
|
+
"toId": "grp_add_call",
|
|
74784
|
+
"toProperty": "call"
|
|
74785
|
+
},
|
|
74786
|
+
{
|
|
74787
|
+
"fromId": "grp_add_call",
|
|
74788
|
+
"fromProperty": "done",
|
|
74789
|
+
"toId": "grp_notes",
|
|
74790
|
+
"toProperty": "storageFetch"
|
|
74791
|
+
}
|
|
74792
|
+
]
|
|
74793
|
+
},
|
|
74794
|
+
{
|
|
74795
|
+
"name": "/#__cloud__/Cloud Functions/Join Group",
|
|
74796
|
+
"nodes": [
|
|
74797
|
+
{
|
|
74798
|
+
"id": "jg_request",
|
|
74799
|
+
"type": "noodl.cloud.request",
|
|
74800
|
+
"label": "The caller (no parameters: nothing to forge)",
|
|
74801
|
+
"parameters": {
|
|
74802
|
+
"allowNoAuth": false
|
|
74803
|
+
}
|
|
74804
|
+
},
|
|
74805
|
+
{
|
|
74806
|
+
"id": "jg_me",
|
|
74807
|
+
"type": "net.noodl.user.User",
|
|
74808
|
+
"label": "The caller's own session"
|
|
74809
|
+
},
|
|
74810
|
+
{
|
|
74811
|
+
"id": "jg_invites",
|
|
74812
|
+
"type": "DbCollection2",
|
|
74813
|
+
"label": "Every invitation",
|
|
74814
|
+
"parameters": {
|
|
74815
|
+
"collectionName": "Invite",
|
|
74816
|
+
"runOnChange-collectionName": false,
|
|
74817
|
+
"runOnChange-querySettings": false
|
|
74818
|
+
}
|
|
74819
|
+
},
|
|
74820
|
+
{
|
|
74821
|
+
"id": "jg_pick",
|
|
74822
|
+
"type": "JavaScriptFunction",
|
|
74823
|
+
"label": "The group of whoever invited this address: the invitation's owner",
|
|
74824
|
+
"parameters": {
|
|
74825
|
+
"functionScript": "if (Inputs.me === undefined || Inputs.email === undefined || Inputs.invites === undefined) return;\nconst me = String(Inputs.me || '');\nconst email = String(Inputs.email || '').trim().toLowerCase();\n// The group an invitation lets you into is the person who MADE it: its owner, which the backend wrote\n// from their session and nobody can change. Never a group id the invitation carries: anyone signed in\n// can create an invitation naming any group. Your own invitations are not a way in to anything.\nconst mine = Inputs.invites\n .filter((i) => email !== '' && String(i.withEmail || '').trim().toLowerCase() === email && String(i.owner) !== me)\n .sort((a, b) => String(a.createdAt || '').localeCompare(String(b.createdAt || '')));\nconst invite = mine[0];\nif (!invite) {\n // Nobody invited this address: the caller's group is their own.\n Outputs.group = me;\n Outputs.none();\n return;\n}\n// Remember who took the invitation up, so its owner can take it back (Remove Member).\nawait Noodl.Records.save(invite.id || invite.objectId, { memberId: me }, { className: 'Invite' });\nOutputs.group = String(invite.owner);\nOutputs.userId = me;\nOutputs.role = 'members-of-' + String(invite.owner);\nOutputs.join();"
|
|
74826
|
+
}
|
|
74827
|
+
},
|
|
74828
|
+
{
|
|
74829
|
+
"id": "jg_join",
|
|
74830
|
+
"type": "noodl.cloud.addusertorole",
|
|
74831
|
+
"label": "Put the caller in that group's role",
|
|
74832
|
+
"parameters": {
|
|
74833
|
+
"createRole": true
|
|
74834
|
+
}
|
|
74835
|
+
},
|
|
74836
|
+
{
|
|
74837
|
+
"id": "jg_ok",
|
|
74838
|
+
"type": "noodl.cloud.response",
|
|
74839
|
+
"label": "The group this caller is in",
|
|
74840
|
+
"parameters": {
|
|
74841
|
+
"params": "group"
|
|
74842
|
+
}
|
|
74843
|
+
},
|
|
74844
|
+
{
|
|
74845
|
+
"id": "jg_fail",
|
|
74846
|
+
"type": "noodl.cloud.response",
|
|
74847
|
+
"label": "Tell the caller it went wrong",
|
|
74848
|
+
"parameters": {
|
|
74849
|
+
"status": "failure"
|
|
74850
|
+
}
|
|
74851
|
+
}
|
|
74852
|
+
],
|
|
74853
|
+
"connections": [
|
|
74854
|
+
{
|
|
74855
|
+
"fromId": "jg_request",
|
|
74856
|
+
"fromProperty": "receive",
|
|
74857
|
+
"toId": "jg_me",
|
|
74858
|
+
"toProperty": "fetch"
|
|
74859
|
+
},
|
|
74860
|
+
{
|
|
74861
|
+
"fromId": "jg_request",
|
|
74862
|
+
"fromProperty": "receive",
|
|
74863
|
+
"toId": "jg_invites",
|
|
74864
|
+
"toProperty": "storageFetch"
|
|
74865
|
+
},
|
|
74866
|
+
{
|
|
74867
|
+
"fromId": "jg_me",
|
|
74868
|
+
"fromProperty": "id",
|
|
74869
|
+
"toId": "jg_pick",
|
|
74870
|
+
"toProperty": "in-me"
|
|
74871
|
+
},
|
|
74872
|
+
{
|
|
74873
|
+
"fromId": "jg_me",
|
|
74874
|
+
"fromProperty": "email",
|
|
74875
|
+
"toId": "jg_pick",
|
|
74876
|
+
"toProperty": "in-email"
|
|
74877
|
+
},
|
|
74878
|
+
{
|
|
74879
|
+
"fromId": "jg_invites",
|
|
74880
|
+
"fromProperty": "items",
|
|
74881
|
+
"toId": "jg_pick",
|
|
74882
|
+
"toProperty": "in-invites"
|
|
74883
|
+
},
|
|
74884
|
+
{
|
|
74885
|
+
"fromId": "jg_pick",
|
|
74886
|
+
"fromProperty": "out-role",
|
|
74887
|
+
"toId": "jg_join",
|
|
74888
|
+
"toProperty": "role"
|
|
74889
|
+
},
|
|
74890
|
+
{
|
|
74891
|
+
"fromId": "jg_pick",
|
|
74892
|
+
"fromProperty": "out-userId",
|
|
74893
|
+
"toId": "jg_join",
|
|
74894
|
+
"toProperty": "userId"
|
|
74895
|
+
},
|
|
74896
|
+
{
|
|
74897
|
+
"fromId": "jg_pick",
|
|
74898
|
+
"fromProperty": "out-join",
|
|
74899
|
+
"toId": "jg_join",
|
|
74900
|
+
"toProperty": "add"
|
|
74901
|
+
},
|
|
74902
|
+
{
|
|
74903
|
+
"fromId": "jg_pick",
|
|
74904
|
+
"fromProperty": "out-group",
|
|
74905
|
+
"toId": "jg_ok",
|
|
74906
|
+
"toProperty": "pm-group"
|
|
74907
|
+
},
|
|
74908
|
+
{
|
|
74909
|
+
"fromId": "jg_pick",
|
|
74910
|
+
"fromProperty": "out-none",
|
|
74911
|
+
"toId": "jg_ok",
|
|
74912
|
+
"toProperty": "send"
|
|
74913
|
+
},
|
|
74914
|
+
{
|
|
74915
|
+
"fromId": "jg_join",
|
|
74916
|
+
"fromProperty": "done",
|
|
74917
|
+
"toId": "jg_ok",
|
|
74918
|
+
"toProperty": "send"
|
|
74919
|
+
},
|
|
74920
|
+
{
|
|
74921
|
+
"fromId": "jg_join",
|
|
74922
|
+
"fromProperty": "unchanged",
|
|
74923
|
+
"toId": "jg_ok",
|
|
74924
|
+
"toProperty": "send"
|
|
74925
|
+
},
|
|
74926
|
+
{
|
|
74927
|
+
"fromId": "jg_pick",
|
|
74928
|
+
"fromProperty": "error",
|
|
74929
|
+
"toId": "jg_fail",
|
|
74930
|
+
"toProperty": "errorMessage"
|
|
74931
|
+
},
|
|
74932
|
+
{
|
|
74933
|
+
"fromId": "jg_join",
|
|
74934
|
+
"fromProperty": "error",
|
|
74935
|
+
"toId": "jg_fail",
|
|
74936
|
+
"toProperty": "errorMessage"
|
|
74937
|
+
},
|
|
74938
|
+
{
|
|
74939
|
+
"fromId": "jg_pick",
|
|
74940
|
+
"fromProperty": "failure",
|
|
74941
|
+
"toId": "jg_fail",
|
|
74942
|
+
"toProperty": "send"
|
|
74943
|
+
},
|
|
74944
|
+
{
|
|
74945
|
+
"fromId": "jg_join",
|
|
74946
|
+
"fromProperty": "failure",
|
|
74947
|
+
"toId": "jg_fail",
|
|
74948
|
+
"toProperty": "send"
|
|
74949
|
+
}
|
|
74950
|
+
]
|
|
74951
|
+
},
|
|
74952
|
+
{
|
|
74953
|
+
"name": "/#__cloud__/Cloud Functions/Add To Group",
|
|
74954
|
+
"nodes": [
|
|
74955
|
+
{
|
|
74956
|
+
"id": "ag_request",
|
|
74957
|
+
"type": "noodl.cloud.request",
|
|
74958
|
+
"label": "A note, and the group it is for",
|
|
74959
|
+
"parameters": {
|
|
74960
|
+
"allowNoAuth": false,
|
|
74961
|
+
"params": "title,group"
|
|
74962
|
+
}
|
|
74963
|
+
},
|
|
74964
|
+
{
|
|
74965
|
+
"id": "ag_me",
|
|
74966
|
+
"type": "net.noodl.user.User",
|
|
74967
|
+
"label": "The caller's own session, and the roles it carries"
|
|
74968
|
+
},
|
|
74969
|
+
{
|
|
74970
|
+
"id": "ag_add",
|
|
74971
|
+
"type": "JavaScriptFunction",
|
|
74972
|
+
"label": "Only a member writes, and the note is the group owner's",
|
|
74973
|
+
"parameters": {
|
|
74974
|
+
"functionScript": "if (Inputs.me === undefined || Inputs.roles === undefined || Inputs.group === undefined) return;\nconst me = String(Inputs.me || '');\nconst group = String(Inputs.group || '');\nconst roles = Array.isArray(Inputs.roles) ? Inputs.roles : [];\n// The group a caller may add to: their own, or one whose role their own session carries. The role is\n// only ever granted by Join Group, from an invitation's owner, so a group id sent here proves nothing.\nif (group === '' || (group !== me && !roles.includes('members-of-' + group))) {\n Outputs.reason = 'You are not a member of this group.';\n Outputs.refused();\n return;\n}\n// Created as the system, so the record's owner is the GROUP's owner (a page could never name it), and\n// its access rules are the owner and the group's role: every member reads and changes it.\nconst acl = {};\nacl[group] = { read: true, write: true };\nacl['role:members-of-' + group] = { read: true, write: true };\nconst note = await Noodl.Records.create('Note', { title: String(Inputs.title || ''), owner: group }, { acl });\nOutputs.id = note.getId();\nOutputs.added();"
|
|
74975
|
+
}
|
|
74976
|
+
},
|
|
74977
|
+
{
|
|
74978
|
+
"id": "ag_ok",
|
|
74979
|
+
"type": "noodl.cloud.response",
|
|
74980
|
+
"label": "The new note",
|
|
74981
|
+
"parameters": {
|
|
74982
|
+
"params": "id"
|
|
74983
|
+
}
|
|
74984
|
+
},
|
|
74985
|
+
{
|
|
74986
|
+
"id": "ag_fail",
|
|
74987
|
+
"type": "noodl.cloud.response",
|
|
74988
|
+
"label": "Refused, or it went wrong",
|
|
74989
|
+
"parameters": {
|
|
74990
|
+
"status": "failure"
|
|
74991
|
+
}
|
|
74992
|
+
}
|
|
74993
|
+
],
|
|
74994
|
+
"connections": [
|
|
74995
|
+
{
|
|
74996
|
+
"fromId": "ag_request",
|
|
74997
|
+
"fromProperty": "receive",
|
|
74998
|
+
"toId": "ag_me",
|
|
74999
|
+
"toProperty": "fetch"
|
|
75000
|
+
},
|
|
75001
|
+
{
|
|
75002
|
+
"fromId": "ag_me",
|
|
75003
|
+
"fromProperty": "id",
|
|
75004
|
+
"toId": "ag_add",
|
|
75005
|
+
"toProperty": "in-me"
|
|
75006
|
+
},
|
|
75007
|
+
{
|
|
75008
|
+
"fromId": "ag_me",
|
|
75009
|
+
"fromProperty": "roles",
|
|
75010
|
+
"toId": "ag_add",
|
|
75011
|
+
"toProperty": "in-roles"
|
|
75012
|
+
},
|
|
75013
|
+
{
|
|
75014
|
+
"fromId": "ag_request",
|
|
75015
|
+
"fromProperty": "pm-group",
|
|
75016
|
+
"toId": "ag_add",
|
|
75017
|
+
"toProperty": "in-group"
|
|
75018
|
+
},
|
|
75019
|
+
{
|
|
75020
|
+
"fromId": "ag_request",
|
|
75021
|
+
"fromProperty": "pm-title",
|
|
75022
|
+
"toId": "ag_add",
|
|
75023
|
+
"toProperty": "in-title"
|
|
75024
|
+
},
|
|
75025
|
+
{
|
|
75026
|
+
"fromId": "ag_add",
|
|
75027
|
+
"fromProperty": "out-id",
|
|
75028
|
+
"toId": "ag_ok",
|
|
75029
|
+
"toProperty": "pm-id"
|
|
75030
|
+
},
|
|
75031
|
+
{
|
|
75032
|
+
"fromId": "ag_add",
|
|
75033
|
+
"fromProperty": "out-added",
|
|
75034
|
+
"toId": "ag_ok",
|
|
75035
|
+
"toProperty": "send"
|
|
75036
|
+
},
|
|
75037
|
+
{
|
|
75038
|
+
"fromId": "ag_add",
|
|
75039
|
+
"fromProperty": "out-reason",
|
|
75040
|
+
"toId": "ag_fail",
|
|
75041
|
+
"toProperty": "errorMessage"
|
|
75042
|
+
},
|
|
75043
|
+
{
|
|
75044
|
+
"fromId": "ag_add",
|
|
75045
|
+
"fromProperty": "out-refused",
|
|
75046
|
+
"toId": "ag_fail",
|
|
75047
|
+
"toProperty": "send"
|
|
75048
|
+
},
|
|
75049
|
+
{
|
|
75050
|
+
"fromId": "ag_add",
|
|
75051
|
+
"fromProperty": "error",
|
|
75052
|
+
"toId": "ag_fail",
|
|
75053
|
+
"toProperty": "errorMessage"
|
|
75054
|
+
},
|
|
75055
|
+
{
|
|
75056
|
+
"fromId": "ag_add",
|
|
75057
|
+
"fromProperty": "failure",
|
|
75058
|
+
"toId": "ag_fail",
|
|
75059
|
+
"toProperty": "send"
|
|
75060
|
+
}
|
|
75061
|
+
]
|
|
75062
|
+
},
|
|
75063
|
+
{
|
|
75064
|
+
"name": "/#__cloud__/Cloud Functions/Remove Member",
|
|
75065
|
+
"nodes": [
|
|
75066
|
+
{
|
|
75067
|
+
"id": "rm_request",
|
|
75068
|
+
"type": "noodl.cloud.request",
|
|
75069
|
+
"label": "The address whose invitation the caller takes back",
|
|
75070
|
+
"parameters": {
|
|
75071
|
+
"allowNoAuth": false,
|
|
75072
|
+
"params": "withEmail"
|
|
75073
|
+
}
|
|
75074
|
+
},
|
|
75075
|
+
{
|
|
75076
|
+
"id": "rm_me",
|
|
75077
|
+
"type": "net.noodl.user.User",
|
|
75078
|
+
"label": "The caller's own session"
|
|
75079
|
+
},
|
|
75080
|
+
{
|
|
75081
|
+
"id": "rm_invites",
|
|
75082
|
+
"type": "DbCollection2",
|
|
75083
|
+
"label": "Every invitation",
|
|
75084
|
+
"parameters": {
|
|
75085
|
+
"collectionName": "Invite",
|
|
75086
|
+
"runOnChange-collectionName": false,
|
|
75087
|
+
"runOnChange-querySettings": false
|
|
75088
|
+
}
|
|
75089
|
+
},
|
|
75090
|
+
{
|
|
75091
|
+
"id": "rm_pick",
|
|
75092
|
+
"type": "JavaScriptFunction",
|
|
75093
|
+
"label": "The caller's own invitation, and the member who took it up",
|
|
75094
|
+
"parameters": {
|
|
75095
|
+
"functionScript": "if (Inputs.me === undefined || Inputs.withEmail === undefined || Inputs.invites === undefined) return;\nconst me = String(Inputs.me || '');\nconst email = String(Inputs.withEmail || '').trim().toLowerCase();\n// Only the caller's own invitations, and only the caller's own group: whatever an invitation says, the\n// role taken away is the caller's.\nconst mine = Inputs.invites.filter(\n (i) => String(i.owner) === me && String(i.withEmail || '').trim().toLowerCase() === email\n);\nif (mine.length === 0) {\n Outputs.reason = 'You have not invited that address.';\n Outputs.refused();\n return;\n}\nconst member = mine.map((i) => String(i.memberId || '')).find((m) => m !== '') || '';\nfor (const i of mine) await Noodl.Records.delete(i.id || i.objectId, { className: 'Invite' });\nif (member === '') {\n Outputs.removed();\n return;\n}\nOutputs.userId = member;\nOutputs.role = 'members-of-' + me;\nOutputs.remove();"
|
|
75096
|
+
}
|
|
75097
|
+
},
|
|
75098
|
+
{
|
|
75099
|
+
"id": "rm_role",
|
|
75100
|
+
"type": "noodl.cloud.removeuserfromrole",
|
|
75101
|
+
"label": "Take them out of the caller's group"
|
|
75102
|
+
},
|
|
75103
|
+
{
|
|
75104
|
+
"id": "rm_ok",
|
|
75105
|
+
"type": "noodl.cloud.response",
|
|
75106
|
+
"label": "Taken back"
|
|
75107
|
+
},
|
|
75108
|
+
{
|
|
75109
|
+
"id": "rm_fail",
|
|
75110
|
+
"type": "noodl.cloud.response",
|
|
75111
|
+
"label": "Refused, or it went wrong",
|
|
75112
|
+
"parameters": {
|
|
75113
|
+
"status": "failure"
|
|
75114
|
+
}
|
|
75115
|
+
}
|
|
75116
|
+
],
|
|
75117
|
+
"connections": [
|
|
75118
|
+
{
|
|
75119
|
+
"fromId": "rm_request",
|
|
75120
|
+
"fromProperty": "receive",
|
|
75121
|
+
"toId": "rm_me",
|
|
75122
|
+
"toProperty": "fetch"
|
|
75123
|
+
},
|
|
75124
|
+
{
|
|
75125
|
+
"fromId": "rm_request",
|
|
75126
|
+
"fromProperty": "receive",
|
|
75127
|
+
"toId": "rm_invites",
|
|
75128
|
+
"toProperty": "storageFetch"
|
|
75129
|
+
},
|
|
75130
|
+
{
|
|
75131
|
+
"fromId": "rm_me",
|
|
75132
|
+
"fromProperty": "id",
|
|
75133
|
+
"toId": "rm_pick",
|
|
75134
|
+
"toProperty": "in-me"
|
|
75135
|
+
},
|
|
75136
|
+
{
|
|
75137
|
+
"fromId": "rm_request",
|
|
75138
|
+
"fromProperty": "pm-withEmail",
|
|
75139
|
+
"toId": "rm_pick",
|
|
75140
|
+
"toProperty": "in-withEmail"
|
|
75141
|
+
},
|
|
75142
|
+
{
|
|
75143
|
+
"fromId": "rm_invites",
|
|
75144
|
+
"fromProperty": "items",
|
|
75145
|
+
"toId": "rm_pick",
|
|
75146
|
+
"toProperty": "in-invites"
|
|
75147
|
+
},
|
|
75148
|
+
{
|
|
75149
|
+
"fromId": "rm_pick",
|
|
75150
|
+
"fromProperty": "out-role",
|
|
75151
|
+
"toId": "rm_role",
|
|
75152
|
+
"toProperty": "role"
|
|
75153
|
+
},
|
|
75154
|
+
{
|
|
75155
|
+
"fromId": "rm_pick",
|
|
75156
|
+
"fromProperty": "out-userId",
|
|
75157
|
+
"toId": "rm_role",
|
|
75158
|
+
"toProperty": "userId"
|
|
75159
|
+
},
|
|
75160
|
+
{
|
|
75161
|
+
"fromId": "rm_pick",
|
|
75162
|
+
"fromProperty": "out-remove",
|
|
75163
|
+
"toId": "rm_role",
|
|
75164
|
+
"toProperty": "remove"
|
|
75165
|
+
},
|
|
75166
|
+
{
|
|
75167
|
+
"fromId": "rm_pick",
|
|
75168
|
+
"fromProperty": "out-removed",
|
|
75169
|
+
"toId": "rm_ok",
|
|
75170
|
+
"toProperty": "send"
|
|
75171
|
+
},
|
|
75172
|
+
{
|
|
75173
|
+
"fromId": "rm_role",
|
|
75174
|
+
"fromProperty": "done",
|
|
75175
|
+
"toId": "rm_ok",
|
|
75176
|
+
"toProperty": "send"
|
|
75177
|
+
},
|
|
75178
|
+
{
|
|
75179
|
+
"fromId": "rm_role",
|
|
75180
|
+
"fromProperty": "unchanged",
|
|
75181
|
+
"toId": "rm_ok",
|
|
75182
|
+
"toProperty": "send"
|
|
75183
|
+
},
|
|
75184
|
+
{
|
|
75185
|
+
"fromId": "rm_pick",
|
|
75186
|
+
"fromProperty": "out-reason",
|
|
75187
|
+
"toId": "rm_fail",
|
|
75188
|
+
"toProperty": "errorMessage"
|
|
75189
|
+
},
|
|
75190
|
+
{
|
|
75191
|
+
"fromId": "rm_pick",
|
|
75192
|
+
"fromProperty": "out-refused",
|
|
75193
|
+
"toId": "rm_fail",
|
|
75194
|
+
"toProperty": "send"
|
|
75195
|
+
},
|
|
75196
|
+
{
|
|
75197
|
+
"fromId": "rm_pick",
|
|
75198
|
+
"fromProperty": "error",
|
|
75199
|
+
"toId": "rm_fail",
|
|
75200
|
+
"toProperty": "errorMessage"
|
|
75201
|
+
},
|
|
75202
|
+
{
|
|
75203
|
+
"fromId": "rm_role",
|
|
75204
|
+
"fromProperty": "error",
|
|
75205
|
+
"toId": "rm_fail",
|
|
75206
|
+
"toProperty": "errorMessage"
|
|
75207
|
+
},
|
|
75208
|
+
{
|
|
75209
|
+
"fromId": "rm_pick",
|
|
75210
|
+
"fromProperty": "failure",
|
|
75211
|
+
"toId": "rm_fail",
|
|
75212
|
+
"toProperty": "send"
|
|
75213
|
+
},
|
|
75214
|
+
{
|
|
75215
|
+
"fromId": "rm_role",
|
|
75216
|
+
"fromProperty": "failure",
|
|
75217
|
+
"toId": "rm_fail",
|
|
75218
|
+
"toProperty": "send"
|
|
75219
|
+
}
|
|
75220
|
+
]
|
|
75221
|
+
}
|
|
75222
|
+
]
|
|
75223
|
+
},
|
|
74349
75224
|
{
|
|
74350
75225
|
"id": "cloud-query-own-rows",
|
|
74351
75226
|
"title": "Only my records: a Query Records filtered on the server by the signed-in user's id",
|
|
@@ -74541,7 +75416,7 @@
|
|
|
74541
75416
|
"label": "Update note",
|
|
74542
75417
|
"parameters": {
|
|
74543
75418
|
"collectionName": "Note",
|
|
74544
|
-
"idSource": "
|
|
75419
|
+
"idSource": "explicit"
|
|
74545
75420
|
}
|
|
74546
75421
|
},
|
|
74547
75422
|
{
|
|
@@ -74550,7 +75425,7 @@
|
|
|
74550
75425
|
"label": "Delete note",
|
|
74551
75426
|
"parameters": {
|
|
74552
75427
|
"collectionName": "Note",
|
|
74553
|
-
"idSource": "
|
|
75428
|
+
"idSource": "explicit"
|
|
74554
75429
|
}
|
|
74555
75430
|
}
|
|
74556
75431
|
],
|
|
@@ -75001,7 +75876,7 @@
|
|
|
75001
75876
|
],
|
|
75002
75877
|
"components": [
|
|
75003
75878
|
{
|
|
75004
|
-
"name": "/Cloud Functions/Revoke Role",
|
|
75879
|
+
"name": "/#__cloud__/Cloud Functions/Revoke Role",
|
|
75005
75880
|
"nodes": [
|
|
75006
75881
|
{
|
|
75007
75882
|
"id": "fn_request",
|
|
@@ -75589,7 +76464,7 @@
|
|
|
75589
76464
|
],
|
|
75590
76465
|
"components": [
|
|
75591
76466
|
{
|
|
75592
|
-
"name": "/Cloud Functions/Issue Partner Token",
|
|
76467
|
+
"name": "/#__cloud__/Cloud Functions/Issue Partner Token",
|
|
75593
76468
|
"nodes": [
|
|
75594
76469
|
{
|
|
75595
76470
|
"id": "fn_request",
|
|
@@ -76217,7 +77092,7 @@
|
|
|
76217
77092
|
],
|
|
76218
77093
|
"components": [
|
|
76219
77094
|
{
|
|
76220
|
-
"name": "/Cloud Functions/List Role Members",
|
|
77095
|
+
"name": "/#__cloud__/Cloud Functions/List Role Members",
|
|
76221
77096
|
"nodes": [
|
|
76222
77097
|
{
|
|
76223
77098
|
"id": "fn_request",
|
|
@@ -76429,7 +77304,7 @@
|
|
|
76429
77304
|
"type": "Javascript2",
|
|
76430
77305
|
"label": "Extract headline",
|
|
76431
77306
|
"parameters": {
|
|
76432
|
-
"code": "
|
|
77307
|
+
"code": "Script.Inputs = { response: 'object' };\nScript.Outputs = { headline: 'string' };\n\n// Each response sets the headline: the first item's title, or nothing when the answer has no items.\nScript.Setters.response = function (response) {\n Script.setOutputs({ headline: response && response.items && response.items.length ? response.items[0].title : '' });\n};"
|
|
76433
77308
|
}
|
|
76434
77309
|
},
|
|
76435
77310
|
{
|
|
@@ -76745,9 +77620,7 @@
|
|
|
76745
77620
|
{
|
|
76746
77621
|
"name": "Clicked",
|
|
76747
77622
|
"plug": "input",
|
|
76748
|
-
"type":
|
|
76749
|
-
"name": "*"
|
|
76750
|
-
},
|
|
77623
|
+
"type": "*",
|
|
76751
77624
|
"index": 0
|
|
76752
77625
|
}
|
|
76753
77626
|
]
|
|
@@ -76759,135 +77632,105 @@
|
|
|
76759
77632
|
{
|
|
76760
77633
|
"name": "text",
|
|
76761
77634
|
"plug": "output",
|
|
76762
|
-
"type":
|
|
76763
|
-
"name": "*"
|
|
76764
|
-
},
|
|
77635
|
+
"type": "*",
|
|
76765
77636
|
"group": "General",
|
|
76766
77637
|
"index": 2
|
|
76767
77638
|
},
|
|
76768
77639
|
{
|
|
76769
77640
|
"name": "css-class",
|
|
76770
77641
|
"plug": "output",
|
|
76771
|
-
"type":
|
|
76772
|
-
"name": "*"
|
|
76773
|
-
},
|
|
77642
|
+
"type": "*",
|
|
76774
77643
|
"group": "General",
|
|
76775
77644
|
"index": 1
|
|
76776
77645
|
},
|
|
76777
77646
|
{
|
|
76778
77647
|
"name": "padding top",
|
|
76779
77648
|
"plug": "output",
|
|
76780
|
-
"type":
|
|
76781
|
-
"name": "*"
|
|
76782
|
-
},
|
|
77649
|
+
"type": "*",
|
|
76783
77650
|
"group": "Margin and padding",
|
|
76784
77651
|
"index": 3
|
|
76785
77652
|
},
|
|
76786
77653
|
{
|
|
76787
77654
|
"name": "padding bottom",
|
|
76788
77655
|
"plug": "output",
|
|
76789
|
-
"type":
|
|
76790
|
-
"name": "*"
|
|
76791
|
-
},
|
|
77656
|
+
"type": "*",
|
|
76792
77657
|
"group": "Margin and padding",
|
|
76793
77658
|
"index": 4
|
|
76794
77659
|
},
|
|
76795
77660
|
{
|
|
76796
77661
|
"name": "padding left",
|
|
76797
77662
|
"plug": "output",
|
|
76798
|
-
"type":
|
|
76799
|
-
"name": "*"
|
|
76800
|
-
},
|
|
77663
|
+
"type": "*",
|
|
76801
77664
|
"group": "Margin and padding",
|
|
76802
77665
|
"index": 5
|
|
76803
77666
|
},
|
|
76804
77667
|
{
|
|
76805
77668
|
"name": "padding right",
|
|
76806
77669
|
"plug": "output",
|
|
76807
|
-
"type":
|
|
76808
|
-
"name": "*"
|
|
76809
|
-
},
|
|
77670
|
+
"type": "*",
|
|
76810
77671
|
"group": "Margin and padding",
|
|
76811
77672
|
"index": 6
|
|
76812
77673
|
},
|
|
76813
77674
|
{
|
|
76814
77675
|
"name": "margin top",
|
|
76815
77676
|
"plug": "output",
|
|
76816
|
-
"type":
|
|
76817
|
-
"name": "*"
|
|
76818
|
-
},
|
|
77677
|
+
"type": "*",
|
|
76819
77678
|
"group": "Margin and padding",
|
|
76820
77679
|
"index": 7
|
|
76821
77680
|
},
|
|
76822
77681
|
{
|
|
76823
77682
|
"name": "margin bottom",
|
|
76824
77683
|
"plug": "output",
|
|
76825
|
-
"type":
|
|
76826
|
-
"name": "*"
|
|
76827
|
-
},
|
|
77684
|
+
"type": "*",
|
|
76828
77685
|
"group": "Margin and padding",
|
|
76829
77686
|
"index": 8
|
|
76830
77687
|
},
|
|
76831
77688
|
{
|
|
76832
77689
|
"name": "margin left",
|
|
76833
77690
|
"plug": "output",
|
|
76834
|
-
"type":
|
|
76835
|
-
"name": "*"
|
|
76836
|
-
},
|
|
77691
|
+
"type": "*",
|
|
76837
77692
|
"group": "Margin and padding",
|
|
76838
77693
|
"index": 9
|
|
76839
77694
|
},
|
|
76840
77695
|
{
|
|
76841
77696
|
"name": "margin right",
|
|
76842
77697
|
"plug": "output",
|
|
76843
|
-
"type":
|
|
76844
|
-
"name": "*"
|
|
76845
|
-
},
|
|
77698
|
+
"type": "*",
|
|
76846
77699
|
"group": "Margin and padding",
|
|
76847
77700
|
"index": 10
|
|
76848
77701
|
},
|
|
76849
77702
|
{
|
|
76850
77703
|
"name": "mounted",
|
|
76851
77704
|
"plug": "output",
|
|
76852
|
-
"type":
|
|
76853
|
-
"name": "*"
|
|
76854
|
-
},
|
|
77705
|
+
"type": "*",
|
|
76855
77706
|
"group": "General",
|
|
76856
77707
|
"index": 0
|
|
76857
77708
|
},
|
|
76858
77709
|
{
|
|
76859
77710
|
"name": "align horizontal",
|
|
76860
77711
|
"plug": "output",
|
|
76861
|
-
"type":
|
|
76862
|
-
"name": "*"
|
|
76863
|
-
},
|
|
77712
|
+
"type": "*",
|
|
76864
77713
|
"group": "Other",
|
|
76865
77714
|
"index": 11
|
|
76866
77715
|
},
|
|
76867
77716
|
{
|
|
76868
77717
|
"name": "align vertical",
|
|
76869
77718
|
"plug": "output",
|
|
76870
|
-
"type":
|
|
76871
|
-
"name": "*"
|
|
76872
|
-
},
|
|
77719
|
+
"type": "*",
|
|
76873
77720
|
"group": "Other",
|
|
76874
77721
|
"index": 12
|
|
76875
77722
|
},
|
|
76876
77723
|
{
|
|
76877
77724
|
"name": "position",
|
|
76878
77725
|
"plug": "output",
|
|
76879
|
-
"type":
|
|
76880
|
-
"name": "*"
|
|
76881
|
-
},
|
|
77726
|
+
"type": "*",
|
|
76882
77727
|
"group": "Other",
|
|
76883
77728
|
"index": 13
|
|
76884
77729
|
},
|
|
76885
77730
|
{
|
|
76886
77731
|
"name": "layout",
|
|
76887
77732
|
"plug": "output",
|
|
76888
|
-
"type":
|
|
76889
|
-
"name": "*"
|
|
76890
|
-
},
|
|
77733
|
+
"type": "*",
|
|
76891
77734
|
"group": "Other",
|
|
76892
77735
|
"index": 14
|
|
76893
77736
|
}
|
|
@@ -77067,17 +77910,13 @@
|
|
|
77067
77910
|
{
|
|
77068
77911
|
"name": "Do",
|
|
77069
77912
|
"plug": "output",
|
|
77070
|
-
"type":
|
|
77071
|
-
"name": "*"
|
|
77072
|
-
},
|
|
77913
|
+
"type": "*",
|
|
77073
77914
|
"index": 0
|
|
77074
77915
|
},
|
|
77075
77916
|
{
|
|
77076
77917
|
"name": "JsonArray",
|
|
77077
77918
|
"plug": "output",
|
|
77078
|
-
"type":
|
|
77079
|
-
"name": "*"
|
|
77080
|
-
},
|
|
77919
|
+
"type": "*",
|
|
77081
77920
|
"index": 1
|
|
77082
77921
|
}
|
|
77083
77922
|
]
|
|
@@ -77139,54 +77978,42 @@
|
|
|
77139
77978
|
{
|
|
77140
77979
|
"name": "File",
|
|
77141
77980
|
"plug": "input",
|
|
77142
|
-
"type":
|
|
77143
|
-
"name": "*"
|
|
77144
|
-
},
|
|
77981
|
+
"type": "*",
|
|
77145
77982
|
"group": "General",
|
|
77146
77983
|
"index": 0
|
|
77147
77984
|
},
|
|
77148
77985
|
{
|
|
77149
77986
|
"name": "Path",
|
|
77150
77987
|
"plug": "input",
|
|
77151
|
-
"type":
|
|
77152
|
-
"name": "*"
|
|
77153
|
-
},
|
|
77988
|
+
"type": "*",
|
|
77154
77989
|
"group": "Metadata",
|
|
77155
77990
|
"index": 1
|
|
77156
77991
|
},
|
|
77157
77992
|
{
|
|
77158
77993
|
"name": "Name",
|
|
77159
77994
|
"plug": "input",
|
|
77160
|
-
"type":
|
|
77161
|
-
"name": "*"
|
|
77162
|
-
},
|
|
77995
|
+
"type": "*",
|
|
77163
77996
|
"group": "Metadata",
|
|
77164
77997
|
"index": 2
|
|
77165
77998
|
},
|
|
77166
77999
|
{
|
|
77167
78000
|
"name": "Size in bytes",
|
|
77168
78001
|
"plug": "input",
|
|
77169
|
-
"type":
|
|
77170
|
-
"name": "*"
|
|
77171
|
-
},
|
|
78002
|
+
"type": "*",
|
|
77172
78003
|
"group": "Metadata",
|
|
77173
78004
|
"index": 3
|
|
77174
78005
|
},
|
|
77175
78006
|
{
|
|
77176
78007
|
"name": "Type",
|
|
77177
78008
|
"plug": "input",
|
|
77178
|
-
"type":
|
|
77179
|
-
"name": "*"
|
|
77180
|
-
},
|
|
78009
|
+
"type": "*",
|
|
77181
78010
|
"group": "Metadata",
|
|
77182
78011
|
"index": 4
|
|
77183
78012
|
},
|
|
77184
78013
|
{
|
|
77185
78014
|
"name": "Success",
|
|
77186
78015
|
"plug": "input",
|
|
77187
|
-
"type":
|
|
77188
|
-
"name": "*"
|
|
77189
|
-
},
|
|
78016
|
+
"type": "*",
|
|
77190
78017
|
"group": "Events",
|
|
77191
78018
|
"index": 5
|
|
77192
78019
|
}
|
|
@@ -77226,18 +78053,14 @@
|
|
|
77226
78053
|
{
|
|
77227
78054
|
"name": "Enable File Picker on click",
|
|
77228
78055
|
"plug": "output",
|
|
77229
|
-
"type":
|
|
77230
|
-
"name": "*"
|
|
77231
|
-
},
|
|
78056
|
+
"type": "*",
|
|
77232
78057
|
"group": "Dropzone",
|
|
77233
78058
|
"index": 0
|
|
77234
78059
|
},
|
|
77235
78060
|
{
|
|
77236
78061
|
"name": "Accepted file types",
|
|
77237
78062
|
"plug": "output",
|
|
77238
|
-
"type":
|
|
77239
|
-
"name": "*"
|
|
77240
|
-
},
|
|
78063
|
+
"type": "*",
|
|
77241
78064
|
"group": "Dropzone",
|
|
77242
78065
|
"index": 1
|
|
77243
78066
|
}
|
|
@@ -77269,54 +78092,42 @@
|
|
|
77269
78092
|
{
|
|
77270
78093
|
"name": "File",
|
|
77271
78094
|
"plug": "input",
|
|
77272
|
-
"type":
|
|
77273
|
-
"name": "*"
|
|
77274
|
-
},
|
|
78095
|
+
"type": "*",
|
|
77275
78096
|
"group": "General",
|
|
77276
78097
|
"index": 0
|
|
77277
78098
|
},
|
|
77278
78099
|
{
|
|
77279
78100
|
"name": "Path",
|
|
77280
78101
|
"plug": "input",
|
|
77281
|
-
"type":
|
|
77282
|
-
"name": "*"
|
|
77283
|
-
},
|
|
78102
|
+
"type": "*",
|
|
77284
78103
|
"group": "Metadata",
|
|
77285
78104
|
"index": 1
|
|
77286
78105
|
},
|
|
77287
78106
|
{
|
|
77288
78107
|
"name": "Name",
|
|
77289
78108
|
"plug": "input",
|
|
77290
|
-
"type":
|
|
77291
|
-
"name": "*"
|
|
77292
|
-
},
|
|
78109
|
+
"type": "*",
|
|
77293
78110
|
"group": "Metadata",
|
|
77294
78111
|
"index": 2
|
|
77295
78112
|
},
|
|
77296
78113
|
{
|
|
77297
78114
|
"name": "Size in bytes",
|
|
77298
78115
|
"plug": "input",
|
|
77299
|
-
"type":
|
|
77300
|
-
"name": "*"
|
|
77301
|
-
},
|
|
78116
|
+
"type": "*",
|
|
77302
78117
|
"group": "Metadata",
|
|
77303
78118
|
"index": 3
|
|
77304
78119
|
},
|
|
77305
78120
|
{
|
|
77306
78121
|
"name": "Type",
|
|
77307
78122
|
"plug": "input",
|
|
77308
|
-
"type":
|
|
77309
|
-
"name": "*"
|
|
77310
|
-
},
|
|
78123
|
+
"type": "*",
|
|
77311
78124
|
"group": "Metadata",
|
|
77312
78125
|
"index": 4
|
|
77313
78126
|
},
|
|
77314
78127
|
{
|
|
77315
78128
|
"name": "Success",
|
|
77316
78129
|
"plug": "input",
|
|
77317
|
-
"type":
|
|
77318
|
-
"name": "*"
|
|
77319
|
-
},
|
|
78130
|
+
"type": "*",
|
|
77320
78131
|
"group": "Events",
|
|
77321
78132
|
"index": 5
|
|
77322
78133
|
}
|
|
@@ -77773,108 +78584,84 @@
|
|
|
77773
78584
|
{
|
|
77774
78585
|
"name": "title",
|
|
77775
78586
|
"plug": "output",
|
|
77776
|
-
"type":
|
|
77777
|
-
"name": "*"
|
|
77778
|
-
},
|
|
78587
|
+
"type": "*",
|
|
77779
78588
|
"group": "General search engine",
|
|
77780
78589
|
"index": 1
|
|
77781
78590
|
},
|
|
77782
78591
|
{
|
|
77783
78592
|
"name": "description",
|
|
77784
78593
|
"plug": "output",
|
|
77785
|
-
"type":
|
|
77786
|
-
"name": "*"
|
|
77787
|
-
},
|
|
78594
|
+
"type": "*",
|
|
77788
78595
|
"group": "General search engine",
|
|
77789
78596
|
"index": 2
|
|
77790
78597
|
},
|
|
77791
78598
|
{
|
|
77792
78599
|
"name": "og:title",
|
|
77793
78600
|
"plug": "output",
|
|
77794
|
-
"type":
|
|
77795
|
-
"name": "*"
|
|
77796
|
-
},
|
|
78601
|
+
"type": "*",
|
|
77797
78602
|
"group": "Open Graph",
|
|
77798
78603
|
"index": 4
|
|
77799
78604
|
},
|
|
77800
78605
|
{
|
|
77801
78606
|
"name": "og:description",
|
|
77802
78607
|
"plug": "output",
|
|
77803
|
-
"type":
|
|
77804
|
-
"name": "*"
|
|
77805
|
-
},
|
|
78608
|
+
"type": "*",
|
|
77806
78609
|
"group": "Open Graph",
|
|
77807
78610
|
"index": 5
|
|
77808
78611
|
},
|
|
77809
78612
|
{
|
|
77810
78613
|
"name": "og:image",
|
|
77811
78614
|
"plug": "output",
|
|
77812
|
-
"type":
|
|
77813
|
-
"name": "*"
|
|
77814
|
-
},
|
|
78615
|
+
"type": "*",
|
|
77815
78616
|
"group": "Open Graph",
|
|
77816
78617
|
"index": 6
|
|
77817
78618
|
},
|
|
77818
78619
|
{
|
|
77819
78620
|
"name": "og:type",
|
|
77820
78621
|
"plug": "output",
|
|
77821
|
-
"type":
|
|
77822
|
-
"name": "*"
|
|
77823
|
-
},
|
|
78622
|
+
"type": "*",
|
|
77824
78623
|
"group": "Open Graph",
|
|
77825
78624
|
"index": 9
|
|
77826
78625
|
},
|
|
77827
78626
|
{
|
|
77828
78627
|
"name": "og:url (override default)",
|
|
77829
78628
|
"plug": "output",
|
|
77830
|
-
"type":
|
|
77831
|
-
"name": "*"
|
|
77832
|
-
},
|
|
78629
|
+
"type": "*",
|
|
77833
78630
|
"group": "Open Graph",
|
|
77834
78631
|
"index": 10
|
|
77835
78632
|
},
|
|
77836
78633
|
{
|
|
77837
78634
|
"name": "robots",
|
|
77838
78635
|
"plug": "output",
|
|
77839
|
-
"type":
|
|
77840
|
-
"name": "*"
|
|
77841
|
-
},
|
|
78636
|
+
"type": "*",
|
|
77842
78637
|
"group": "General search engine",
|
|
77843
78638
|
"index": 3
|
|
77844
78639
|
},
|
|
77845
78640
|
{
|
|
77846
78641
|
"name": "twitter:title",
|
|
77847
78642
|
"plug": "output",
|
|
77848
|
-
"type":
|
|
77849
|
-
"name": "*"
|
|
77850
|
-
},
|
|
78643
|
+
"type": "*",
|
|
77851
78644
|
"group": "Twitter",
|
|
77852
78645
|
"index": 11
|
|
77853
78646
|
},
|
|
77854
78647
|
{
|
|
77855
78648
|
"name": "twitter:description",
|
|
77856
78649
|
"plug": "output",
|
|
77857
|
-
"type":
|
|
77858
|
-
"name": "*"
|
|
77859
|
-
},
|
|
78650
|
+
"type": "*",
|
|
77860
78651
|
"group": "Twitter",
|
|
77861
78652
|
"index": 12
|
|
77862
78653
|
},
|
|
77863
78654
|
{
|
|
77864
78655
|
"name": "twitter:card",
|
|
77865
78656
|
"plug": "output",
|
|
77866
|
-
"type":
|
|
77867
|
-
"name": "*"
|
|
77868
|
-
},
|
|
78657
|
+
"type": "*",
|
|
77869
78658
|
"group": "Twitter",
|
|
77870
78659
|
"index": 13
|
|
77871
78660
|
},
|
|
77872
78661
|
{
|
|
77873
78662
|
"name": "twitter:image",
|
|
77874
78663
|
"plug": "output",
|
|
77875
|
-
"type":
|
|
77876
|
-
"name": "*"
|
|
77877
|
-
},
|
|
78664
|
+
"type": "*",
|
|
77878
78665
|
"group": "Twitter",
|
|
77879
78666
|
"index": 14
|
|
77880
78667
|
}
|
|
@@ -78381,17 +79168,13 @@
|
|
|
78381
79168
|
{
|
|
78382
79169
|
"name": "uploadRecordCreated",
|
|
78383
79170
|
"plug": "input",
|
|
78384
|
-
"type":
|
|
78385
|
-
"name": "*"
|
|
78386
|
-
},
|
|
79171
|
+
"type": "*",
|
|
78387
79172
|
"index": 1
|
|
78388
79173
|
},
|
|
78389
79174
|
{
|
|
78390
79175
|
"name": "absoluteFilePathCreated",
|
|
78391
79176
|
"plug": "input",
|
|
78392
|
-
"type":
|
|
78393
|
-
"name": "*"
|
|
78394
|
-
},
|
|
79177
|
+
"type": "*",
|
|
78395
79178
|
"index": 2
|
|
78396
79179
|
}
|
|
78397
79180
|
]
|
|
@@ -78949,25 +79732,19 @@
|
|
|
78949
79732
|
{
|
|
78950
79733
|
"name": "Html Content",
|
|
78951
79734
|
"plug": "input",
|
|
78952
|
-
"type":
|
|
78953
|
-
"name": "*"
|
|
78954
|
-
},
|
|
79735
|
+
"type": "*",
|
|
78955
79736
|
"index": 1
|
|
78956
79737
|
},
|
|
78957
79738
|
{
|
|
78958
79739
|
"name": "Changed",
|
|
78959
79740
|
"plug": "input",
|
|
78960
|
-
"type":
|
|
78961
|
-
"name": "*"
|
|
78962
|
-
},
|
|
79741
|
+
"type": "*",
|
|
78963
79742
|
"index": 2
|
|
78964
79743
|
},
|
|
78965
79744
|
{
|
|
78966
79745
|
"name": "Saved",
|
|
78967
79746
|
"plug": "input",
|
|
78968
|
-
"type":
|
|
78969
|
-
"name": "*"
|
|
78970
|
-
},
|
|
79747
|
+
"type": "*",
|
|
78971
79748
|
"index": 3
|
|
78972
79749
|
}
|
|
78973
79750
|
]
|
|
@@ -78979,17 +79756,13 @@
|
|
|
78979
79756
|
{
|
|
78980
79757
|
"name": "Initial Content",
|
|
78981
79758
|
"plug": "output",
|
|
78982
|
-
"type":
|
|
78983
|
-
"name": "*"
|
|
78984
|
-
},
|
|
79759
|
+
"type": "*",
|
|
78985
79760
|
"index": 1
|
|
78986
79761
|
},
|
|
78987
79762
|
{
|
|
78988
79763
|
"name": "Update Content",
|
|
78989
79764
|
"plug": "output",
|
|
78990
|
-
"type":
|
|
78991
|
-
"name": "*"
|
|
78992
|
-
},
|
|
79765
|
+
"type": "*",
|
|
78993
79766
|
"index": 2
|
|
78994
79767
|
}
|
|
78995
79768
|
]
|
|
@@ -79093,7 +79866,7 @@
|
|
|
79093
79866
|
{
|
|
79094
79867
|
"id": "community-tiptap-text-editor",
|
|
79095
79868
|
"title": "A rich-text editor with a toolbar and local drafts",
|
|
79096
|
-
"description": "The larger companion to the single-button component: a `Static Data` node holds the list of toolbar commands, a `For Each` draws one button per entry, and the editor itself is mounted by a `JavaScriptFunction` onto a `Group`'s element. Two details are worth more than the editor. The draft is saved to `localStorage` on a `Timer` and read back on mount, which is the whole of 'don't lose my work' and costs two function nodes. And `Model2` plus `SetModelProperties` keep the document in the project's own data model rather than only inside the third-party editor, so something other than the editor can read what was typed. ⚠️ The toolbar is driven by data, so adding a command is a row in the `Static Data` node — not a new button.",
|
|
79869
|
+
"description": "The larger companion to the single-button component: a `Static Data` node holds the list of toolbar commands, a `For Each` draws one button per entry, and the editor itself is mounted by a `JavaScriptFunction` onto a `Group`'s element. Two details are worth more than the editor. The draft is saved to `localStorage` on a `Timer` and read back on mount, which is the whole of 'don't lose my work' and costs two function nodes. And `Model2` plus `SetModelProperties` keep the document in the project's own data model rather than only inside the third-party editor, so something other than the editor can read what was typed. ⚠️ The toolbar is driven by data, so adding a command is a row in the `Static Data` node — not a new button. Each row is the companion example's button component, `/Tiptap text editor r` (`community-tiptap-text-editor-r`): write that one first.",
|
|
79097
79870
|
"demonstrates": [
|
|
79098
79871
|
"Boolean",
|
|
79099
79872
|
"CSS Definition",
|
|
@@ -79155,7 +79928,7 @@
|
|
|
79155
79928
|
"type": "For Each",
|
|
79156
79929
|
"parent": "menu",
|
|
79157
79930
|
"parameters": {
|
|
79158
|
-
"template": "
|
|
79931
|
+
"template": "/Tiptap text editor r"
|
|
79159
79932
|
}
|
|
79160
79933
|
},
|
|
79161
79934
|
{
|
|
@@ -79470,7 +80243,7 @@
|
|
|
79470
80243
|
},
|
|
79471
80244
|
{
|
|
79472
80245
|
"fromId": "setmodelproperties",
|
|
79473
|
-
"fromProperty": "
|
|
80246
|
+
"fromProperty": "done",
|
|
79474
80247
|
"toId": "model2",
|
|
79475
80248
|
"toProperty": "fetch"
|
|
79476
80249
|
},
|
|
@@ -79608,17 +80381,13 @@
|
|
|
79608
80381
|
{
|
|
79609
80382
|
"name": "buttonType",
|
|
79610
80383
|
"plug": "output",
|
|
79611
|
-
"type":
|
|
79612
|
-
"name": "*"
|
|
79613
|
-
},
|
|
80384
|
+
"type": "*",
|
|
79614
80385
|
"index": 1
|
|
79615
80386
|
},
|
|
79616
80387
|
{
|
|
79617
80388
|
"name": "editor",
|
|
79618
80389
|
"plug": "output",
|
|
79619
|
-
"type":
|
|
79620
|
-
"name": "*"
|
|
79621
|
-
},
|
|
80390
|
+
"type": "*",
|
|
79622
80391
|
"index": 2
|
|
79623
80392
|
}
|
|
79624
80393
|
]
|
|
@@ -79664,17 +80433,13 @@
|
|
|
79664
80433
|
{
|
|
79665
80434
|
"name": "Success",
|
|
79666
80435
|
"plug": "input",
|
|
79667
|
-
"type":
|
|
79668
|
-
"name": "*"
|
|
79669
|
-
},
|
|
80436
|
+
"type": "*",
|
|
79670
80437
|
"index": 1
|
|
79671
80438
|
},
|
|
79672
80439
|
{
|
|
79673
80440
|
"name": "buttonClicked",
|
|
79674
80441
|
"plug": "input",
|
|
79675
|
-
"type":
|
|
79676
|
-
"name": "*"
|
|
79677
|
-
},
|
|
80442
|
+
"type": "*",
|
|
79678
80443
|
"index": 2
|
|
79679
80444
|
}
|
|
79680
80445
|
]
|
|
@@ -79892,17 +80657,13 @@
|
|
|
79892
80657
|
{
|
|
79893
80658
|
"name": "videoURL",
|
|
79894
80659
|
"plug": "input",
|
|
79895
|
-
"type":
|
|
79896
|
-
"name": "*"
|
|
79897
|
-
},
|
|
80660
|
+
"type": "*",
|
|
79898
80661
|
"index": 1
|
|
79899
80662
|
},
|
|
79900
80663
|
{
|
|
79901
80664
|
"name": "uploadId",
|
|
79902
80665
|
"plug": "input",
|
|
79903
|
-
"type":
|
|
79904
|
-
"name": "*"
|
|
79905
|
-
},
|
|
80666
|
+
"type": "*",
|
|
79906
80667
|
"index": 2
|
|
79907
80668
|
}
|
|
79908
80669
|
]
|
|
@@ -79914,9 +80675,7 @@
|
|
|
79914
80675
|
{
|
|
79915
80676
|
"name": "assignmentId",
|
|
79916
80677
|
"plug": "output",
|
|
79917
|
-
"type":
|
|
79918
|
-
"name": "*"
|
|
79919
|
-
},
|
|
80678
|
+
"type": "*",
|
|
79920
80679
|
"index": 1
|
|
79921
80680
|
}
|
|
79922
80681
|
]
|
|
@@ -80590,7 +81349,7 @@
|
|
|
80590
81349
|
},
|
|
80591
81350
|
{
|
|
80592
81351
|
"fromId": "draft_object",
|
|
80593
|
-
"fromProperty": "title",
|
|
81352
|
+
"fromProperty": "prop-title",
|
|
80594
81353
|
"toId": "draft_title_text",
|
|
80595
81354
|
"toProperty": "text"
|
|
80596
81355
|
}
|
|
@@ -81056,7 +81815,7 @@
|
|
|
81056
81815
|
"parent": "row",
|
|
81057
81816
|
"parameters": {
|
|
81058
81817
|
"useLabel": true,
|
|
81059
|
-
"labelSpacing": "
|
|
81818
|
+
"labelSpacing": "var(--space-2)"
|
|
81060
81819
|
}
|
|
81061
81820
|
},
|
|
81062
81821
|
{
|
|
@@ -82718,7 +83477,7 @@
|
|
|
82718
83477
|
{
|
|
82719
83478
|
"id": "fn-aggregate-stats-function",
|
|
82720
83479
|
"title": "Server-side aggregation exposed as a cloud function",
|
|
82721
|
-
"description": "Aggregate Records runs
|
|
83480
|
+
"description": "Aggregate Records runs a sum, min, max or average over a number column (or the distinct values of a text column) of a database class without transferring the records — it exists only in the cloud runtime, so the idiomatic shape is a small cloud function around it: Request triggers the aggregate's fetch, `fetched` sends the Response, and each name in `aggregates` gets its own Property and Operation inputs (`aggprop-<name>`, `aggop-<name>`) and its result on `agg-<name>`, returned as a response param. Here that is `totalHours`, the sum of every task's `hours`. There is no count operation: for a count, use Query Records with Fetch total count on. The browser calls the function like any cloud function and renders the statistic.",
|
|
82722
83481
|
"demonstrates": [
|
|
82723
83482
|
"noodl.cloud.aggregate",
|
|
82724
83483
|
"CloudFunction2",
|
|
@@ -82738,7 +83497,7 @@
|
|
|
82738
83497
|
},
|
|
82739
83498
|
"children": [
|
|
82740
83499
|
"refresh_btn",
|
|
82741
|
-
"
|
|
83500
|
+
"total_text"
|
|
82742
83501
|
]
|
|
82743
83502
|
},
|
|
82744
83503
|
{
|
|
@@ -82751,9 +83510,9 @@
|
|
|
82751
83510
|
}
|
|
82752
83511
|
},
|
|
82753
83512
|
{
|
|
82754
|
-
"id": "
|
|
83513
|
+
"id": "total_text",
|
|
82755
83514
|
"type": "Text",
|
|
82756
|
-
"label": "
|
|
83515
|
+
"label": "Total hours",
|
|
82757
83516
|
"parent": "root"
|
|
82758
83517
|
},
|
|
82759
83518
|
{
|
|
@@ -82774,8 +83533,8 @@
|
|
|
82774
83533
|
},
|
|
82775
83534
|
{
|
|
82776
83535
|
"fromId": "call_stats",
|
|
82777
|
-
"fromProperty": "out-
|
|
82778
|
-
"toId": "
|
|
83536
|
+
"fromProperty": "out-totalHours",
|
|
83537
|
+
"toId": "total_text",
|
|
82779
83538
|
"toProperty": "text"
|
|
82780
83539
|
}
|
|
82781
83540
|
]
|
|
@@ -82789,12 +83548,14 @@
|
|
|
82789
83548
|
"label": "Request"
|
|
82790
83549
|
},
|
|
82791
83550
|
{
|
|
82792
|
-
"id": "
|
|
83551
|
+
"id": "sum_hours",
|
|
82793
83552
|
"type": "noodl.cloud.aggregate",
|
|
82794
|
-
"label": "
|
|
83553
|
+
"label": "Sum task hours",
|
|
82795
83554
|
"parameters": {
|
|
82796
83555
|
"collectionName": "Task",
|
|
82797
|
-
"aggregates": "
|
|
83556
|
+
"aggregates": "totalHours",
|
|
83557
|
+
"aggprop-totalHours": "hours",
|
|
83558
|
+
"aggop-totalHours": "sum"
|
|
82798
83559
|
}
|
|
82799
83560
|
},
|
|
82800
83561
|
{
|
|
@@ -82802,7 +83563,7 @@
|
|
|
82802
83563
|
"type": "noodl.cloud.response",
|
|
82803
83564
|
"label": "Response",
|
|
82804
83565
|
"parameters": {
|
|
82805
|
-
"params": "
|
|
83566
|
+
"params": "totalHours"
|
|
82806
83567
|
}
|
|
82807
83568
|
},
|
|
82808
83569
|
{
|
|
@@ -82818,29 +83579,29 @@
|
|
|
82818
83579
|
{
|
|
82819
83580
|
"fromId": "fn_request",
|
|
82820
83581
|
"fromProperty": "receive",
|
|
82821
|
-
"toId": "
|
|
83582
|
+
"toId": "sum_hours",
|
|
82822
83583
|
"toProperty": "fetch"
|
|
82823
83584
|
},
|
|
82824
83585
|
{
|
|
82825
|
-
"fromId": "
|
|
82826
|
-
"fromProperty": "
|
|
83586
|
+
"fromId": "sum_hours",
|
|
83587
|
+
"fromProperty": "agg-totalHours",
|
|
82827
83588
|
"toId": "fn_response",
|
|
82828
|
-
"toProperty": "pm-
|
|
83589
|
+
"toProperty": "pm-totalHours"
|
|
82829
83590
|
},
|
|
82830
83591
|
{
|
|
82831
|
-
"fromId": "
|
|
83592
|
+
"fromId": "sum_hours",
|
|
82832
83593
|
"fromProperty": "fetched",
|
|
82833
83594
|
"toId": "fn_response",
|
|
82834
83595
|
"toProperty": "send"
|
|
82835
83596
|
},
|
|
82836
83597
|
{
|
|
82837
|
-
"fromId": "
|
|
83598
|
+
"fromId": "sum_hours",
|
|
82838
83599
|
"fromProperty": "error",
|
|
82839
83600
|
"toId": "fn_fail_response",
|
|
82840
83601
|
"toProperty": "errorMessage"
|
|
82841
83602
|
},
|
|
82842
83603
|
{
|
|
82843
|
-
"fromId": "
|
|
83604
|
+
"fromId": "sum_hours",
|
|
82844
83605
|
"fromProperty": "failure",
|
|
82845
83606
|
"toId": "fn_fail_response",
|
|
82846
83607
|
"toProperty": "send"
|
|
@@ -83243,7 +84004,7 @@
|
|
|
83243
84004
|
],
|
|
83244
84005
|
"components": [
|
|
83245
84006
|
{
|
|
83246
|
-
"name": "/Cloud Functions/Notify",
|
|
84007
|
+
"name": "/#__cloud__/Cloud Functions/Notify",
|
|
83247
84008
|
"nodes": [
|
|
83248
84009
|
{
|
|
83249
84010
|
"id": "fn_request",
|
|
@@ -85420,6 +86181,176 @@
|
|
|
85420
86181
|
}
|
|
85421
86182
|
]
|
|
85422
86183
|
},
|
|
86184
|
+
{
|
|
86185
|
+
"id": "option-set",
|
|
86186
|
+
"title": "A fixed list as an option set: the languages, read by a dropdown, the choice stored by a Set Variable",
|
|
86187
|
+
"description": "A fixed list (a language, a status, a priority) lives in ONE place: a logic component under `Option sets/<Name>` holding one `Static Data` node with the list as a JSON array, published through `Component Outputs` as `items` and `count`. NodeGX Studio lists every component under `Option sets/` in Data → Option sets, where the person adds or renames an option themselves, and the NodeGX editor shows it as the Static Data node it is. A page places an instance and wires its `items` wherever the list is needed (a Dropdown here; a `For Each` of buttons works the same). The step that stores the person's choice takes the chosen row's value from the control, so no literal like \"fr\" or \"en\" is typed into a Set Variable anywhere: two steps typing two literals into one variable are a fixed list the project does not have, and the next language means editing every one of them. Each row carries `Label` (what the person reads) and `Value` (what the app stores), the two fields a Dropdown reads off each item.",
|
|
86188
|
+
"demonstrates": [
|
|
86189
|
+
"Static Data",
|
|
86190
|
+
"Component Outputs",
|
|
86191
|
+
"net.noodl.controls.options",
|
|
86192
|
+
"Set Variable",
|
|
86193
|
+
"Variable2"
|
|
86194
|
+
],
|
|
86195
|
+
"components": [
|
|
86196
|
+
{
|
|
86197
|
+
"name": "/Option sets/Languages",
|
|
86198
|
+
"nodes": [
|
|
86199
|
+
{
|
|
86200
|
+
"id": "options",
|
|
86201
|
+
"type": "Static Data",
|
|
86202
|
+
"label": "Languages · the options",
|
|
86203
|
+
"parameters": {
|
|
86204
|
+
"type": "json",
|
|
86205
|
+
"json": "[{\"Label\":\"English\",\"Value\":\"en\"},{\"Label\":\"Français\",\"Value\":\"fr\"}]"
|
|
86206
|
+
}
|
|
86207
|
+
},
|
|
86208
|
+
{
|
|
86209
|
+
"id": "out",
|
|
86210
|
+
"type": "Component Outputs",
|
|
86211
|
+
"label": "What the page reads",
|
|
86212
|
+
"ports": [
|
|
86213
|
+
{
|
|
86214
|
+
"name": "items",
|
|
86215
|
+
"plug": "input",
|
|
86216
|
+
"type": "array"
|
|
86217
|
+
},
|
|
86218
|
+
{
|
|
86219
|
+
"name": "count",
|
|
86220
|
+
"plug": "input",
|
|
86221
|
+
"type": "number"
|
|
86222
|
+
}
|
|
86223
|
+
]
|
|
86224
|
+
}
|
|
86225
|
+
],
|
|
86226
|
+
"connections": [
|
|
86227
|
+
{
|
|
86228
|
+
"fromId": "options",
|
|
86229
|
+
"fromProperty": "items",
|
|
86230
|
+
"toId": "out",
|
|
86231
|
+
"toProperty": "items"
|
|
86232
|
+
},
|
|
86233
|
+
{
|
|
86234
|
+
"fromId": "options",
|
|
86235
|
+
"fromProperty": "count",
|
|
86236
|
+
"toId": "out",
|
|
86237
|
+
"toProperty": "count"
|
|
86238
|
+
}
|
|
86239
|
+
]
|
|
86240
|
+
},
|
|
86241
|
+
{
|
|
86242
|
+
"name": "/Pages/Language",
|
|
86243
|
+
"nodes": [
|
|
86244
|
+
{
|
|
86245
|
+
"id": "page",
|
|
86246
|
+
"type": "Page",
|
|
86247
|
+
"label": "Page",
|
|
86248
|
+
"parameters": {
|
|
86249
|
+
"title": "Language",
|
|
86250
|
+
"urlPath": "language"
|
|
86251
|
+
},
|
|
86252
|
+
"children": [
|
|
86253
|
+
"body"
|
|
86254
|
+
]
|
|
86255
|
+
},
|
|
86256
|
+
{
|
|
86257
|
+
"id": "body",
|
|
86258
|
+
"type": "Group",
|
|
86259
|
+
"label": "Body",
|
|
86260
|
+
"parent": "page",
|
|
86261
|
+
"parameters": {
|
|
86262
|
+
"width": {
|
|
86263
|
+
"value": 100,
|
|
86264
|
+
"unit": "%"
|
|
86265
|
+
},
|
|
86266
|
+
"sizeMode": "contentHeight",
|
|
86267
|
+
"flexDirection": "column",
|
|
86268
|
+
"rowGap": "var(--space-3)",
|
|
86269
|
+
"paddingLeft": "var(--space-4)",
|
|
86270
|
+
"paddingRight": "var(--space-4)",
|
|
86271
|
+
"paddingTop": "var(--space-4)",
|
|
86272
|
+
"paddingBottom": "var(--space-4)"
|
|
86273
|
+
},
|
|
86274
|
+
"children": [
|
|
86275
|
+
"prompt",
|
|
86276
|
+
"picker"
|
|
86277
|
+
]
|
|
86278
|
+
},
|
|
86279
|
+
{
|
|
86280
|
+
"id": "prompt",
|
|
86281
|
+
"type": "Text",
|
|
86282
|
+
"label": "Prompt",
|
|
86283
|
+
"parent": "body",
|
|
86284
|
+
"parameters": {
|
|
86285
|
+
"text": "Language"
|
|
86286
|
+
}
|
|
86287
|
+
},
|
|
86288
|
+
{
|
|
86289
|
+
"id": "picker",
|
|
86290
|
+
"type": "net.noodl.controls.options",
|
|
86291
|
+
"label": "Language picker",
|
|
86292
|
+
"parent": "body",
|
|
86293
|
+
"parameters": {
|
|
86294
|
+
"placeholder": "Choose a language",
|
|
86295
|
+
"sizeMode": "contentHeight",
|
|
86296
|
+
"width": {
|
|
86297
|
+
"value": 100,
|
|
86298
|
+
"unit": "%"
|
|
86299
|
+
}
|
|
86300
|
+
}
|
|
86301
|
+
},
|
|
86302
|
+
{
|
|
86303
|
+
"id": "languages",
|
|
86304
|
+
"type": "/Option sets/Languages",
|
|
86305
|
+
"label": "Languages"
|
|
86306
|
+
},
|
|
86307
|
+
{
|
|
86308
|
+
"id": "store_language",
|
|
86309
|
+
"type": "Set Variable",
|
|
86310
|
+
"label": "Remember the language",
|
|
86311
|
+
"parameters": {
|
|
86312
|
+
"name": "language",
|
|
86313
|
+
"setWith": "string"
|
|
86314
|
+
}
|
|
86315
|
+
},
|
|
86316
|
+
{
|
|
86317
|
+
"id": "read_language",
|
|
86318
|
+
"type": "Variable2",
|
|
86319
|
+
"label": "Current language",
|
|
86320
|
+
"parameters": {
|
|
86321
|
+
"name": "language"
|
|
86322
|
+
}
|
|
86323
|
+
}
|
|
86324
|
+
],
|
|
86325
|
+
"connections": [
|
|
86326
|
+
{
|
|
86327
|
+
"fromId": "languages",
|
|
86328
|
+
"fromProperty": "items",
|
|
86329
|
+
"toId": "picker",
|
|
86330
|
+
"toProperty": "items"
|
|
86331
|
+
},
|
|
86332
|
+
{
|
|
86333
|
+
"fromId": "picker",
|
|
86334
|
+
"fromProperty": "value",
|
|
86335
|
+
"toId": "store_language",
|
|
86336
|
+
"toProperty": "value"
|
|
86337
|
+
},
|
|
86338
|
+
{
|
|
86339
|
+
"fromId": "picker",
|
|
86340
|
+
"fromProperty": "onChange",
|
|
86341
|
+
"toId": "store_language",
|
|
86342
|
+
"toProperty": "do"
|
|
86343
|
+
},
|
|
86344
|
+
{
|
|
86345
|
+
"fromId": "read_language",
|
|
86346
|
+
"fromProperty": "value",
|
|
86347
|
+
"toId": "picker",
|
|
86348
|
+
"toProperty": "value"
|
|
86349
|
+
}
|
|
86350
|
+
]
|
|
86351
|
+
}
|
|
86352
|
+
]
|
|
86353
|
+
},
|
|
85423
86354
|
{
|
|
85424
86355
|
"id": "repeater-query-records",
|
|
85425
86356
|
"title": "List page: Repeater fed by Query Records",
|
|
@@ -85534,6 +86465,158 @@
|
|
|
85534
86465
|
}
|
|
85535
86466
|
]
|
|
85536
86467
|
},
|
|
86468
|
+
{
|
|
86469
|
+
"id": "score-then-move-on",
|
|
86470
|
+
"title": "Score an answer, then move on to the next question",
|
|
86471
|
+
"description": "Two gates that act on one press, where the second changes what the first reads: moving on changes the question index, and the 'is the answer right?' check compares against that index. Wire both Evaluates from the press and the scoring gate reads the NEXT question, whichever order the wires are in, so every answer is scored against the wrong question. Chain them instead: the press evaluates the scoring gate, and the scoring gate's Done evaluates the move-on gate, so the score is decided before the index moves. Both gates have Run On Value Change unticked, so only the press asks them.",
|
|
86472
|
+
"demonstrates": [
|
|
86473
|
+
"Condition",
|
|
86474
|
+
"Counter",
|
|
86475
|
+
"Expression",
|
|
86476
|
+
"net.noodl.controls.button",
|
|
86477
|
+
"net.noodl.controls.textinput",
|
|
86478
|
+
"Group"
|
|
86479
|
+
],
|
|
86480
|
+
"components": [
|
|
86481
|
+
{
|
|
86482
|
+
"name": "/Quiz Step",
|
|
86483
|
+
"nodes": [
|
|
86484
|
+
{
|
|
86485
|
+
"id": "root",
|
|
86486
|
+
"type": "Group",
|
|
86487
|
+
"label": "Question layout",
|
|
86488
|
+
"parameters": {
|
|
86489
|
+
"flexDirection": "column",
|
|
86490
|
+
"paddingLeft": "var(--space-4)",
|
|
86491
|
+
"paddingRight": "var(--space-4)",
|
|
86492
|
+
"paddingTop": "var(--space-4)",
|
|
86493
|
+
"paddingBottom": "var(--space-4)"
|
|
86494
|
+
},
|
|
86495
|
+
"children": [
|
|
86496
|
+
"answer_input",
|
|
86497
|
+
"next_button"
|
|
86498
|
+
]
|
|
86499
|
+
},
|
|
86500
|
+
{
|
|
86501
|
+
"id": "answer_input",
|
|
86502
|
+
"type": "net.noodl.controls.textinput",
|
|
86503
|
+
"label": "Answer field",
|
|
86504
|
+
"parent": "root",
|
|
86505
|
+
"parameters": {
|
|
86506
|
+
"placeholder": "Your answer"
|
|
86507
|
+
}
|
|
86508
|
+
},
|
|
86509
|
+
{
|
|
86510
|
+
"id": "next_button",
|
|
86511
|
+
"type": "net.noodl.controls.button",
|
|
86512
|
+
"label": "Next",
|
|
86513
|
+
"parent": "root",
|
|
86514
|
+
"parameters": {
|
|
86515
|
+
"label": "Next"
|
|
86516
|
+
}
|
|
86517
|
+
},
|
|
86518
|
+
{
|
|
86519
|
+
"id": "question_index",
|
|
86520
|
+
"type": "Counter",
|
|
86521
|
+
"label": "Question index"
|
|
86522
|
+
},
|
|
86523
|
+
{
|
|
86524
|
+
"id": "score",
|
|
86525
|
+
"type": "Counter",
|
|
86526
|
+
"label": "Score"
|
|
86527
|
+
},
|
|
86528
|
+
{
|
|
86529
|
+
"id": "is_right",
|
|
86530
|
+
"type": "Expression",
|
|
86531
|
+
"label": "Answer is right?",
|
|
86532
|
+
"parameters": {
|
|
86533
|
+
"expression": "answer == ['Paris', 'Lisbon', 'Rome', 'Madrid', 'Berlin'][index]"
|
|
86534
|
+
}
|
|
86535
|
+
},
|
|
86536
|
+
{
|
|
86537
|
+
"id": "is_not_last",
|
|
86538
|
+
"type": "Expression",
|
|
86539
|
+
"label": "Not the last question?",
|
|
86540
|
+
"parameters": {
|
|
86541
|
+
"expression": "index < 4"
|
|
86542
|
+
}
|
|
86543
|
+
},
|
|
86544
|
+
{
|
|
86545
|
+
"id": "score_gate",
|
|
86546
|
+
"type": "Condition",
|
|
86547
|
+
"label": "Score if right",
|
|
86548
|
+
"parameters": {
|
|
86549
|
+
"runOnChange-condition": false
|
|
86550
|
+
}
|
|
86551
|
+
},
|
|
86552
|
+
{
|
|
86553
|
+
"id": "advance_gate",
|
|
86554
|
+
"type": "Condition",
|
|
86555
|
+
"label": "Move on if not last",
|
|
86556
|
+
"parameters": {
|
|
86557
|
+
"runOnChange-condition": false
|
|
86558
|
+
}
|
|
86559
|
+
}
|
|
86560
|
+
],
|
|
86561
|
+
"connections": [
|
|
86562
|
+
{
|
|
86563
|
+
"fromId": "answer_input",
|
|
86564
|
+
"fromProperty": "onTextChanged",
|
|
86565
|
+
"toId": "is_right",
|
|
86566
|
+
"toProperty": "answer"
|
|
86567
|
+
},
|
|
86568
|
+
{
|
|
86569
|
+
"fromId": "question_index",
|
|
86570
|
+
"fromProperty": "currentCount",
|
|
86571
|
+
"toId": "is_right",
|
|
86572
|
+
"toProperty": "index"
|
|
86573
|
+
},
|
|
86574
|
+
{
|
|
86575
|
+
"fromId": "question_index",
|
|
86576
|
+
"fromProperty": "currentCount",
|
|
86577
|
+
"toId": "is_not_last",
|
|
86578
|
+
"toProperty": "index"
|
|
86579
|
+
},
|
|
86580
|
+
{
|
|
86581
|
+
"fromId": "is_right",
|
|
86582
|
+
"fromProperty": "result",
|
|
86583
|
+
"toId": "score_gate",
|
|
86584
|
+
"toProperty": "condition"
|
|
86585
|
+
},
|
|
86586
|
+
{
|
|
86587
|
+
"fromId": "is_not_last",
|
|
86588
|
+
"fromProperty": "result",
|
|
86589
|
+
"toId": "advance_gate",
|
|
86590
|
+
"toProperty": "condition"
|
|
86591
|
+
},
|
|
86592
|
+
{
|
|
86593
|
+
"fromId": "next_button",
|
|
86594
|
+
"fromProperty": "onClick",
|
|
86595
|
+
"toId": "score_gate",
|
|
86596
|
+
"toProperty": "eval"
|
|
86597
|
+
},
|
|
86598
|
+
{
|
|
86599
|
+
"fromId": "score_gate",
|
|
86600
|
+
"fromProperty": "ontrue",
|
|
86601
|
+
"toId": "score",
|
|
86602
|
+
"toProperty": "increase"
|
|
86603
|
+
},
|
|
86604
|
+
{
|
|
86605
|
+
"fromId": "score_gate",
|
|
86606
|
+
"fromProperty": "done",
|
|
86607
|
+
"toId": "advance_gate",
|
|
86608
|
+
"toProperty": "eval"
|
|
86609
|
+
},
|
|
86610
|
+
{
|
|
86611
|
+
"fromId": "advance_gate",
|
|
86612
|
+
"fromProperty": "ontrue",
|
|
86613
|
+
"toId": "question_index",
|
|
86614
|
+
"toProperty": "increase"
|
|
86615
|
+
}
|
|
86616
|
+
]
|
|
86617
|
+
}
|
|
86618
|
+
]
|
|
86619
|
+
},
|
|
85537
86620
|
{
|
|
85538
86621
|
"id": "send-event-across-components",
|
|
85539
86622
|
"title": "Decoupled communication with Send Event / Receive Event",
|
|
@@ -86740,7 +87823,7 @@
|
|
|
86740
87823
|
{
|
|
86741
87824
|
"id": "ui-empty-state",
|
|
86742
87825
|
"title": "Empty state: what a list shows when it has no rows",
|
|
86743
|
-
"description": "A list with nothing in it should say what it is and what to do, not render nothing. The designed version is small and centred inside a dashed card: an icon in a muted disc, one heading, one line of explanation capped at ~380px, and exactly one action. The switch is one wire: the query's `isEmpty` boolean into the empty state
|
|
87826
|
+
"description": "A list with nothing in it should say what it is and what to do, not render nothing. The designed version is small and centred inside a dashed card: an icon in a muted disc, one heading, one line of explanation capped at ~380px, and exactly one action. The switch is one wire: the query's `isEmpty` boolean into the `mounted` input of a Group that holds the empty state. A placed component has no `mounted` or `visible` of its own, so the wire goes to the Group around it. Use `mounted`, not `visible` — `mounted` takes the element out of the layout, while `visible` only hides it and keeps the space it occupies, which leaves a page-height hole above your empty state. The list needs no gate at all: a `For Each` over an empty array renders no rows and occupies no height, so nothing has to be inverted and no logic node is involved. Skipping the empty state is the difference between an app that looks unfinished on first run and one that does not, and first run is when it is always seen.",
|
|
86744
87827
|
"demonstrates": [
|
|
86745
87828
|
"Group",
|
|
86746
87829
|
"net.noodl.visual.icon",
|
|
@@ -86807,7 +87890,7 @@
|
|
|
86807
87890
|
"children": [
|
|
86808
87891
|
"heading",
|
|
86809
87892
|
"list",
|
|
86810
|
-
"
|
|
87893
|
+
"emptyWrap"
|
|
86811
87894
|
]
|
|
86812
87895
|
},
|
|
86813
87896
|
{
|
|
@@ -86831,11 +87914,27 @@
|
|
|
86831
87914
|
},
|
|
86832
87915
|
"parent": "shell"
|
|
86833
87916
|
},
|
|
87917
|
+
{
|
|
87918
|
+
"id": "emptyWrap",
|
|
87919
|
+
"type": "Group",
|
|
87920
|
+
"label": "Shown only when the query is empty",
|
|
87921
|
+
"parameters": {
|
|
87922
|
+
"width": {
|
|
87923
|
+
"value": 100,
|
|
87924
|
+
"unit": "%"
|
|
87925
|
+
},
|
|
87926
|
+
"flexDirection": "column"
|
|
87927
|
+
},
|
|
87928
|
+
"parent": "shell",
|
|
87929
|
+
"children": [
|
|
87930
|
+
"empty"
|
|
87931
|
+
]
|
|
87932
|
+
},
|
|
86834
87933
|
{
|
|
86835
87934
|
"id": "empty",
|
|
86836
87935
|
"type": "/Components/EmptyState",
|
|
86837
|
-
"label": "
|
|
86838
|
-
"parent": "
|
|
87936
|
+
"label": "The empty state",
|
|
87937
|
+
"parent": "emptyWrap"
|
|
86839
87938
|
},
|
|
86840
87939
|
{
|
|
86841
87940
|
"id": "query",
|
|
@@ -86856,7 +87955,7 @@
|
|
|
86856
87955
|
{
|
|
86857
87956
|
"fromId": "query",
|
|
86858
87957
|
"fromProperty": "isEmpty",
|
|
86859
|
-
"toId": "
|
|
87958
|
+
"toId": "emptyWrap",
|
|
86860
87959
|
"toProperty": "mounted"
|
|
86861
87960
|
}
|
|
86862
87961
|
]
|
|
@@ -92952,7 +94051,7 @@
|
|
|
92952
94051
|
{
|
|
92953
94052
|
"id": "user-login-flow",
|
|
92954
94053
|
"title": "Log in, show the user, log out",
|
|
92955
|
-
"description": "The authentication triangle: Log In takes `username`/`password` values and a `login` signal, firing `
|
|
94054
|
+
"description": "The authentication triangle: Log In takes `username`/`password` values and a `login` signal, firing `done` or `failure` (+`error`). The User node is the session's single source of truth — its `authenticated` boolean and profile outputs update on login/logout, so UI binds to it rather than to the action nodes. Log Out ends the session from anywhere. Gate visible areas on `authenticated`, not on the login node's `done` pulse.",
|
|
92956
94055
|
"demonstrates": [
|
|
92957
94056
|
"net.noodl.user.LogIn",
|
|
92958
94057
|
"net.noodl.user.LogOut",
|