@nodegx/mcp 0.2.6 → 0.2.7
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 +10 -7
- package/dist/backend/cli.js +5534 -2481
- package/dist/backend/folder/start.js +89 -1
- package/dist/backend/vm/provision-app.sh +41 -0
- package/dist/cloud-bundle.cjs +4174 -1633
- package/dist/kit-extract.cjs +4530 -2147
- package/dist/kit-extract.inputs.json +30 -1
- package/dist/node-catalog.json +3 -2
- package/dist/noodl-mcp.cjs +10371 -7141
- package/dist/package.json +1 -1
- package/dist/render-harness/data/node-catalog-enriched.json +998 -238
- package/dist/render-harness/data/node-catalog.json +3 -2
- package/dist/render-harness/drive-steps.js +196 -21
- 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": [
|
|
@@ -18526,12 +18526,14 @@
|
|
|
18526
18526
|
"examples": [
|
|
18527
18527
|
"function-compute-on-run",
|
|
18528
18528
|
"community-csv-download",
|
|
18529
|
-
"community-seo-meta-tag-setter"
|
|
18529
|
+
"community-seo-meta-tag-setter",
|
|
18530
|
+
"cloud-members-can-edit-my-records"
|
|
18530
18531
|
],
|
|
18531
18532
|
"patterns": [
|
|
18532
18533
|
"Async action: `run` from a click → `await fetch(...)` → set value outputs → `Outputs.Success()`; wire Success onward for sequencing.",
|
|
18533
18534
|
"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."
|
|
18535
|
+
"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
|
+
"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
18537
|
],
|
|
18536
18538
|
"antiPatterns": [
|
|
18537
18539
|
"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 +21804,7 @@
|
|
|
21802
21804
|
},
|
|
21803
21805
|
"enrichment": {
|
|
21804
21806
|
"typeName": "NewDbModelProperties",
|
|
21805
|
-
"summary": "Create Record: inserts a record into a cloud database class from its property inputs
|
|
21807
|
+
"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
21808
|
"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
21809
|
"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
21810
|
"ports": {
|
|
@@ -21811,18 +21813,20 @@
|
|
|
21811
21813
|
"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
21814
|
"id": "String value; the id of the created record — feed it to a Record node or navigation parameter.",
|
|
21813
21815
|
"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
|
|
21816
|
+
"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
21817
|
},
|
|
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.",
|
|
21818
|
+
"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
21819
|
"examples": [
|
|
21818
21820
|
"cloud-create-record-with-upload",
|
|
21819
21821
|
"cloud-record-crud",
|
|
21820
21822
|
"community-simple-audio-recorde",
|
|
21821
|
-
"community-web-rtc-video-record"
|
|
21823
|
+
"community-web-rtc-video-record",
|
|
21824
|
+
"cloud-members-can-edit-my-records"
|
|
21822
21825
|
],
|
|
21823
21826
|
"patterns": [
|
|
21824
21827
|
"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."
|
|
21828
|
+
"`done` → a navigation or Event Sender, with `id` as payload: jump straight to the new record's detail page.",
|
|
21829
|
+
"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
21830
|
],
|
|
21827
21831
|
"antiPatterns": [
|
|
21828
21832
|
"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 +31606,7 @@
|
|
|
31602
31606
|
},
|
|
31603
31607
|
"enrichment": {
|
|
31604
31608
|
"typeName": "SetDbModelProperties",
|
|
31605
|
-
"summary": "Update Record: writes property values to an existing cloud database record
|
|
31609
|
+
"summary": "Update Record: writes property values to an existing cloud database record on Do; access rules sent with it replace its ACL.",
|
|
31606
31610
|
"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
31611
|
"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
31612
|
"ports": {
|
|
@@ -31613,7 +31617,7 @@
|
|
|
31613
31617
|
"idSource": "Edit-only choice: 'Specify explicitly' uses `modelId`; 'From repeater' targets the record driving the nearest enclosing For Each item.",
|
|
31614
31618
|
"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
31619
|
"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.",
|
|
31620
|
+
"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
31621
|
"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
31622
|
"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
31623
|
},
|
|
@@ -31621,7 +31625,8 @@
|
|
|
31621
31625
|
"examples": [
|
|
31622
31626
|
"cloud-edit-record",
|
|
31623
31627
|
"cloud-record-crud",
|
|
31624
|
-
"cloud-shared-counter"
|
|
31628
|
+
"cloud-shared-counter",
|
|
31629
|
+
"cloud-members-can-edit-my-records"
|
|
31625
31630
|
],
|
|
31626
31631
|
"patterns": [
|
|
31627
31632
|
"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 +37754,8 @@
|
|
|
37749
37754
|
],
|
|
37750
37755
|
"antiPatterns": [
|
|
37751
37756
|
"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."
|
|
37757
|
+
"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
|
+
"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
37759
|
],
|
|
37754
37760
|
"relatedNodes": [
|
|
37755
37761
|
"Open File Picker",
|
|
@@ -56117,11 +56123,12 @@
|
|
|
56117
56123
|
],
|
|
56118
56124
|
"items": [
|
|
56119
56125
|
"nothing handed (undefined): the list it has stays",
|
|
56126
|
+
"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
56127
|
"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
56128
|
],
|
|
56122
56129
|
"value": [
|
|
56123
56130
|
"null: ignored",
|
|
56124
|
-
"the value it already holds, as text: nothing",
|
|
56131
|
+
"the value it already holds, as text: nothing (it is no longer the seed)",
|
|
56125
56132
|
"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
56133
|
],
|
|
56127
56134
|
"mounted": [
|
|
@@ -66997,10 +67004,12 @@
|
|
|
66997
67004
|
"error": "Value; why the last attempt failed."
|
|
66998
67005
|
},
|
|
66999
67006
|
"examples": [
|
|
67000
|
-
"cloud-invite-a-team-member"
|
|
67007
|
+
"cloud-invite-a-team-member",
|
|
67008
|
+
"cloud-members-can-edit-my-records"
|
|
67001
67009
|
],
|
|
67002
67010
|
"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."
|
|
67011
|
+
"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
|
+
"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
67013
|
],
|
|
67005
67014
|
"antiPatterns": [
|
|
67006
67015
|
"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 +69216,8 @@
|
|
|
69207
69216
|
"error": "Value; why the last attempt failed, or (on Unchanged) which of the two non-membership cases it was."
|
|
69208
69217
|
},
|
|
69209
69218
|
"examples": [
|
|
69210
|
-
"cloud-revoke-a-role-and-record-it"
|
|
69219
|
+
"cloud-revoke-a-role-and-record-it",
|
|
69220
|
+
"cloud-members-can-edit-my-records"
|
|
69211
69221
|
],
|
|
69212
69222
|
"patterns": [
|
|
69213
69223
|
"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."
|
|
@@ -69301,7 +69311,7 @@
|
|
|
69301
69311
|
"group": "Request",
|
|
69302
69312
|
"plug": "output",
|
|
69303
69313
|
"type": {
|
|
69304
|
-
"name": "
|
|
69314
|
+
"name": "string"
|
|
69305
69315
|
},
|
|
69306
69316
|
"isSignal": false,
|
|
69307
69317
|
"description": "Id of the user the session token resolved to, and blank for an unauthenticated request"
|
|
@@ -70962,7 +70972,10 @@
|
|
|
70962
70972
|
{
|
|
70963
70973
|
"id": "prompt_body",
|
|
70964
70974
|
"type": "Javascript2",
|
|
70965
|
-
"label": "Build request body"
|
|
70975
|
+
"label": "Build request body",
|
|
70976
|
+
"parameters": {
|
|
70977
|
+
"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};"
|
|
70978
|
+
}
|
|
70966
70979
|
},
|
|
70967
70980
|
{
|
|
70968
70981
|
"id": "chat_stream",
|
|
@@ -70990,7 +71003,18 @@
|
|
|
70990
71003
|
{
|
|
70991
71004
|
"id": "state_label",
|
|
70992
71005
|
"type": "String Mapper",
|
|
70993
|
-
"label": "State to label"
|
|
71006
|
+
"label": "State to label",
|
|
71007
|
+
"parameters": {
|
|
71008
|
+
"input 0": "connecting",
|
|
71009
|
+
"output 0": "Connecting…",
|
|
71010
|
+
"input 1": "open",
|
|
71011
|
+
"output 1": "Answering",
|
|
71012
|
+
"input 2": "reconnecting",
|
|
71013
|
+
"output 2": "Reconnecting…",
|
|
71014
|
+
"input 3": "error",
|
|
71015
|
+
"output 3": "The stream failed",
|
|
71016
|
+
"defaultMapping": ""
|
|
71017
|
+
}
|
|
70994
71018
|
}
|
|
70995
71019
|
],
|
|
70996
71020
|
"connections": [
|
|
@@ -71052,11 +71076,11 @@
|
|
|
71052
71076
|
"fromId": "chat_stream",
|
|
71053
71077
|
"fromProperty": "connectionState",
|
|
71054
71078
|
"toId": "state_label",
|
|
71055
|
-
"toProperty": "
|
|
71079
|
+
"toProperty": "inputString"
|
|
71056
71080
|
},
|
|
71057
71081
|
{
|
|
71058
71082
|
"fromId": "state_label",
|
|
71059
|
-
"fromProperty": "
|
|
71083
|
+
"fromProperty": "mappedString",
|
|
71060
71084
|
"toId": "status_text",
|
|
71061
71085
|
"toProperty": "text"
|
|
71062
71086
|
}
|
|
@@ -71469,7 +71493,7 @@
|
|
|
71469
71493
|
{
|
|
71470
71494
|
"id": "agent-store-shared-state",
|
|
71471
71495
|
"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
|
|
71496
|
+
"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
71497
|
"demonstrates": [
|
|
71474
71498
|
"net.noodl.GlobalStore",
|
|
71475
71499
|
"net.noodl.GlobalStore.Set",
|
|
@@ -71571,7 +71595,10 @@
|
|
|
71571
71595
|
{
|
|
71572
71596
|
"id": "greeting",
|
|
71573
71597
|
"type": "String Format",
|
|
71574
|
-
"label": "Format greeting"
|
|
71598
|
+
"label": "Format greeting",
|
|
71599
|
+
"parameters": {
|
|
71600
|
+
"format": "Signed in as {user}"
|
|
71601
|
+
}
|
|
71575
71602
|
}
|
|
71576
71603
|
],
|
|
71577
71604
|
"connections": [
|
|
@@ -71579,11 +71606,11 @@
|
|
|
71579
71606
|
"fromId": "watch_user",
|
|
71580
71607
|
"fromProperty": "value",
|
|
71581
71608
|
"toId": "greeting",
|
|
71582
|
-
"toProperty": "
|
|
71609
|
+
"toProperty": "user"
|
|
71583
71610
|
},
|
|
71584
71611
|
{
|
|
71585
71612
|
"fromId": "greeting",
|
|
71586
|
-
"fromProperty": "
|
|
71613
|
+
"fromProperty": "formatted",
|
|
71587
71614
|
"toId": "user_text",
|
|
71588
71615
|
"toProperty": "text"
|
|
71589
71616
|
}
|
|
@@ -71952,7 +71979,7 @@
|
|
|
71952
71979
|
"type": "net.noodl.Log",
|
|
71953
71980
|
"label": "The provider round trip did not start",
|
|
71954
71981
|
"parameters": {
|
|
71955
|
-
"level": "
|
|
71982
|
+
"level": "warn"
|
|
71956
71983
|
}
|
|
71957
71984
|
},
|
|
71958
71985
|
{
|
|
@@ -72554,7 +72581,7 @@
|
|
|
72554
72581
|
"type": "net.noodl.Log",
|
|
72555
72582
|
"label": "Unreadable due date",
|
|
72556
72583
|
"parameters": {
|
|
72557
|
-
"level": "
|
|
72584
|
+
"level": "warn",
|
|
72558
72585
|
"message": "Invoice row: the due date could not be read as a date."
|
|
72559
72586
|
}
|
|
72560
72587
|
}
|
|
@@ -72779,7 +72806,7 @@
|
|
|
72779
72806
|
"type": "net.noodl.Log",
|
|
72780
72807
|
"label": "The server did not answer usably",
|
|
72781
72808
|
"parameters": {
|
|
72782
|
-
"level": "
|
|
72809
|
+
"level": "warn"
|
|
72783
72810
|
}
|
|
72784
72811
|
},
|
|
72785
72812
|
{
|
|
@@ -72787,7 +72814,7 @@
|
|
|
72787
72814
|
"type": "net.noodl.Log",
|
|
72788
72815
|
"label": "The answer was not a feed",
|
|
72789
72816
|
"parameters": {
|
|
72790
|
-
"level": "
|
|
72817
|
+
"level": "warn"
|
|
72791
72818
|
}
|
|
72792
72819
|
}
|
|
72793
72820
|
],
|
|
@@ -72958,7 +72985,7 @@
|
|
|
72958
72985
|
"type": "net.noodl.Log",
|
|
72959
72986
|
"label": "The paste would not parse",
|
|
72960
72987
|
"parameters": {
|
|
72961
|
-
"level": "
|
|
72988
|
+
"level": "warn"
|
|
72962
72989
|
}
|
|
72963
72990
|
}
|
|
72964
72991
|
],
|
|
@@ -73057,7 +73084,7 @@
|
|
|
73057
73084
|
"type": "net.noodl.Log",
|
|
73058
73085
|
"label": "The server did not answer usably",
|
|
73059
73086
|
"parameters": {
|
|
73060
|
-
"level": "
|
|
73087
|
+
"level": "warn"
|
|
73061
73088
|
}
|
|
73062
73089
|
},
|
|
73063
73090
|
{
|
|
@@ -73065,7 +73092,7 @@
|
|
|
73065
73092
|
"type": "net.noodl.Log",
|
|
73066
73093
|
"label": "Refused or unreadable — read Error Code",
|
|
73067
73094
|
"parameters": {
|
|
73068
|
-
"level": "
|
|
73095
|
+
"level": "warn"
|
|
73069
73096
|
}
|
|
73070
73097
|
}
|
|
73071
73098
|
],
|
|
@@ -73422,7 +73449,7 @@
|
|
|
73422
73449
|
],
|
|
73423
73450
|
"components": [
|
|
73424
73451
|
{
|
|
73425
|
-
"name": "/Cloud Functions/Check Client Session",
|
|
73452
|
+
"name": "/#__cloud__/Cloud Functions/Check Client Session",
|
|
73426
73453
|
"nodes": [
|
|
73427
73454
|
{
|
|
73428
73455
|
"id": "fn_request",
|
|
@@ -73526,7 +73553,7 @@
|
|
|
73526
73553
|
],
|
|
73527
73554
|
"components": [
|
|
73528
73555
|
{
|
|
73529
|
-
"name": "/Cloud Functions/Verify Inbound",
|
|
73556
|
+
"name": "/#__cloud__/Cloud Functions/Verify Inbound",
|
|
73530
73557
|
"nodes": [
|
|
73531
73558
|
{
|
|
73532
73559
|
"id": "fn_request",
|
|
@@ -73834,7 +73861,7 @@
|
|
|
73834
73861
|
],
|
|
73835
73862
|
"components": [
|
|
73836
73863
|
{
|
|
73837
|
-
"name": "/Cloud Functions/Delete Account",
|
|
73864
|
+
"name": "/#__cloud__/Cloud Functions/Delete Account",
|
|
73838
73865
|
"nodes": [
|
|
73839
73866
|
{
|
|
73840
73867
|
"id": "fn_request",
|
|
@@ -74208,7 +74235,7 @@
|
|
|
74208
74235
|
],
|
|
74209
74236
|
"components": [
|
|
74210
74237
|
{
|
|
74211
|
-
"name": "/Cloud Functions/Invite Team Member",
|
|
74238
|
+
"name": "/#__cloud__/Cloud Functions/Invite Team Member",
|
|
74212
74239
|
"nodes": [
|
|
74213
74240
|
{
|
|
74214
74241
|
"id": "fn_request",
|
|
@@ -74346,6 +74373,853 @@
|
|
|
74346
74373
|
}
|
|
74347
74374
|
]
|
|
74348
74375
|
},
|
|
74376
|
+
{
|
|
74377
|
+
"id": "cloud-members-can-edit-my-records",
|
|
74378
|
+
"title": "Invite people who can change my records too: a role per owner, joined only through an invitation its owner made",
|
|
74379
|
+
"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.",
|
|
74380
|
+
"demonstrates": [
|
|
74381
|
+
"NewDbModelProperties",
|
|
74382
|
+
"SetDbModelProperties",
|
|
74383
|
+
"DbCollection2",
|
|
74384
|
+
"CloudFunction2",
|
|
74385
|
+
"noodl.cloud.request",
|
|
74386
|
+
"net.noodl.user.User",
|
|
74387
|
+
"JavaScriptFunction",
|
|
74388
|
+
"noodl.cloud.addusertorole",
|
|
74389
|
+
"noodl.cloud.removeuserfromrole",
|
|
74390
|
+
"noodl.cloud.response"
|
|
74391
|
+
],
|
|
74392
|
+
"components": [
|
|
74393
|
+
{
|
|
74394
|
+
"name": "/Member Row",
|
|
74395
|
+
"nodes": [
|
|
74396
|
+
{
|
|
74397
|
+
"id": "mem_root",
|
|
74398
|
+
"type": "Group",
|
|
74399
|
+
"label": "One invitation",
|
|
74400
|
+
"parameters": {
|
|
74401
|
+
"flexDirection": "row",
|
|
74402
|
+
"columnGap": "var(--space-2)",
|
|
74403
|
+
"alignItems": "center"
|
|
74404
|
+
},
|
|
74405
|
+
"children": [
|
|
74406
|
+
"mem_address",
|
|
74407
|
+
"mem_remove"
|
|
74408
|
+
]
|
|
74409
|
+
},
|
|
74410
|
+
{
|
|
74411
|
+
"id": "mem_address",
|
|
74412
|
+
"type": "Text",
|
|
74413
|
+
"label": "Who it invites",
|
|
74414
|
+
"parent": "mem_root"
|
|
74415
|
+
},
|
|
74416
|
+
{
|
|
74417
|
+
"id": "mem_remove",
|
|
74418
|
+
"type": "net.noodl.controls.button",
|
|
74419
|
+
"label": "Take it back",
|
|
74420
|
+
"parent": "mem_root",
|
|
74421
|
+
"parameters": {
|
|
74422
|
+
"label": "Remove"
|
|
74423
|
+
}
|
|
74424
|
+
},
|
|
74425
|
+
{
|
|
74426
|
+
"id": "mem_inputs",
|
|
74427
|
+
"type": "Component Inputs",
|
|
74428
|
+
"label": "Invitation in",
|
|
74429
|
+
"ports": [
|
|
74430
|
+
{
|
|
74431
|
+
"name": "withEmail",
|
|
74432
|
+
"plug": "output",
|
|
74433
|
+
"type": "*"
|
|
74434
|
+
}
|
|
74435
|
+
]
|
|
74436
|
+
},
|
|
74437
|
+
{
|
|
74438
|
+
"id": "mem_call",
|
|
74439
|
+
"type": "CloudFunction2",
|
|
74440
|
+
"label": "Take the invitation back, and the access with it",
|
|
74441
|
+
"parameters": {
|
|
74442
|
+
"function": "Cloud Functions/Remove Member"
|
|
74443
|
+
}
|
|
74444
|
+
}
|
|
74445
|
+
],
|
|
74446
|
+
"connections": [
|
|
74447
|
+
{
|
|
74448
|
+
"fromId": "mem_inputs",
|
|
74449
|
+
"fromProperty": "withEmail",
|
|
74450
|
+
"toId": "mem_address",
|
|
74451
|
+
"toProperty": "text"
|
|
74452
|
+
},
|
|
74453
|
+
{
|
|
74454
|
+
"fromId": "mem_inputs",
|
|
74455
|
+
"fromProperty": "withEmail",
|
|
74456
|
+
"toId": "mem_call",
|
|
74457
|
+
"toProperty": "in-withEmail"
|
|
74458
|
+
},
|
|
74459
|
+
{
|
|
74460
|
+
"fromId": "mem_remove",
|
|
74461
|
+
"fromProperty": "onClick",
|
|
74462
|
+
"toId": "mem_call",
|
|
74463
|
+
"toProperty": "call"
|
|
74464
|
+
}
|
|
74465
|
+
]
|
|
74466
|
+
},
|
|
74467
|
+
{
|
|
74468
|
+
"name": "/Invite Someone To Edit",
|
|
74469
|
+
"nodes": [
|
|
74470
|
+
{
|
|
74471
|
+
"id": "inv_root",
|
|
74472
|
+
"type": "Group",
|
|
74473
|
+
"label": "Invite someone to edit my notes",
|
|
74474
|
+
"parameters": {
|
|
74475
|
+
"flexDirection": "column",
|
|
74476
|
+
"rowGap": "var(--space-3)"
|
|
74477
|
+
},
|
|
74478
|
+
"children": [
|
|
74479
|
+
"inv_address",
|
|
74480
|
+
"inv_button",
|
|
74481
|
+
"inv_error",
|
|
74482
|
+
"inv_list"
|
|
74483
|
+
]
|
|
74484
|
+
},
|
|
74485
|
+
{
|
|
74486
|
+
"id": "inv_address",
|
|
74487
|
+
"type": "net.noodl.controls.textinput",
|
|
74488
|
+
"label": "Their email address",
|
|
74489
|
+
"parent": "inv_root",
|
|
74490
|
+
"parameters": {
|
|
74491
|
+
"type": "email",
|
|
74492
|
+
"placeholder": "name@example.com"
|
|
74493
|
+
}
|
|
74494
|
+
},
|
|
74495
|
+
{
|
|
74496
|
+
"id": "inv_button",
|
|
74497
|
+
"type": "net.noodl.controls.button",
|
|
74498
|
+
"label": "Invite them",
|
|
74499
|
+
"parent": "inv_root",
|
|
74500
|
+
"parameters": {
|
|
74501
|
+
"label": "Invite"
|
|
74502
|
+
}
|
|
74503
|
+
},
|
|
74504
|
+
{
|
|
74505
|
+
"id": "inv_error",
|
|
74506
|
+
"type": "Text",
|
|
74507
|
+
"label": "Why it did not save",
|
|
74508
|
+
"parent": "inv_root",
|
|
74509
|
+
"parameters": {
|
|
74510
|
+
"text": ""
|
|
74511
|
+
}
|
|
74512
|
+
},
|
|
74513
|
+
{
|
|
74514
|
+
"id": "inv_list",
|
|
74515
|
+
"type": "For Each",
|
|
74516
|
+
"label": "One row per invitation I made",
|
|
74517
|
+
"parent": "inv_root",
|
|
74518
|
+
"parameters": {
|
|
74519
|
+
"template": "/Member Row"
|
|
74520
|
+
}
|
|
74521
|
+
},
|
|
74522
|
+
{
|
|
74523
|
+
"id": "inv_create",
|
|
74524
|
+
"type": "NewDbModelProperties",
|
|
74525
|
+
"label": "An Invite naming only their address (owner is the backend's)",
|
|
74526
|
+
"parameters": {
|
|
74527
|
+
"collectionName": "Invite"
|
|
74528
|
+
}
|
|
74529
|
+
},
|
|
74530
|
+
{
|
|
74531
|
+
"id": "inv_mine",
|
|
74532
|
+
"type": "DbCollection2",
|
|
74533
|
+
"label": "My invitations (creator-owned: a query returns only mine)",
|
|
74534
|
+
"parameters": {
|
|
74535
|
+
"collectionName": "Invite",
|
|
74536
|
+
"runOnChange-collectionName": false,
|
|
74537
|
+
"runOnChange-querySettings": false
|
|
74538
|
+
}
|
|
74539
|
+
}
|
|
74540
|
+
],
|
|
74541
|
+
"connections": [
|
|
74542
|
+
{
|
|
74543
|
+
"fromId": "inv_address",
|
|
74544
|
+
"fromProperty": "onTextChanged",
|
|
74545
|
+
"toId": "inv_create",
|
|
74546
|
+
"toProperty": "prop-withEmail"
|
|
74547
|
+
},
|
|
74548
|
+
{
|
|
74549
|
+
"fromId": "inv_button",
|
|
74550
|
+
"fromProperty": "onClick",
|
|
74551
|
+
"toId": "inv_create",
|
|
74552
|
+
"toProperty": "store"
|
|
74553
|
+
},
|
|
74554
|
+
{
|
|
74555
|
+
"fromId": "inv_create",
|
|
74556
|
+
"fromProperty": "error",
|
|
74557
|
+
"toId": "inv_error",
|
|
74558
|
+
"toProperty": "text"
|
|
74559
|
+
},
|
|
74560
|
+
{
|
|
74561
|
+
"fromId": "inv_root",
|
|
74562
|
+
"fromProperty": "didMount",
|
|
74563
|
+
"toId": "inv_mine",
|
|
74564
|
+
"toProperty": "storageFetch"
|
|
74565
|
+
},
|
|
74566
|
+
{
|
|
74567
|
+
"fromId": "inv_create",
|
|
74568
|
+
"fromProperty": "done",
|
|
74569
|
+
"toId": "inv_mine",
|
|
74570
|
+
"toProperty": "storageFetch"
|
|
74571
|
+
},
|
|
74572
|
+
{
|
|
74573
|
+
"fromId": "inv_mine",
|
|
74574
|
+
"fromProperty": "items",
|
|
74575
|
+
"toId": "inv_list",
|
|
74576
|
+
"toProperty": "items"
|
|
74577
|
+
}
|
|
74578
|
+
]
|
|
74579
|
+
},
|
|
74580
|
+
{
|
|
74581
|
+
"name": "/Group Note Row",
|
|
74582
|
+
"nodes": [
|
|
74583
|
+
{
|
|
74584
|
+
"id": "gnr_root",
|
|
74585
|
+
"type": "Group",
|
|
74586
|
+
"label": "One note of the group",
|
|
74587
|
+
"parameters": {
|
|
74588
|
+
"flexDirection": "row",
|
|
74589
|
+
"columnGap": "var(--space-2)",
|
|
74590
|
+
"alignItems": "center"
|
|
74591
|
+
},
|
|
74592
|
+
"children": [
|
|
74593
|
+
"gnr_title",
|
|
74594
|
+
"gnr_save"
|
|
74595
|
+
]
|
|
74596
|
+
},
|
|
74597
|
+
{
|
|
74598
|
+
"id": "gnr_title",
|
|
74599
|
+
"type": "net.noodl.controls.textinput",
|
|
74600
|
+
"label": "The note, editable by every member",
|
|
74601
|
+
"parent": "gnr_root"
|
|
74602
|
+
},
|
|
74603
|
+
{
|
|
74604
|
+
"id": "gnr_save",
|
|
74605
|
+
"type": "net.noodl.controls.button",
|
|
74606
|
+
"label": "Save the change",
|
|
74607
|
+
"parent": "gnr_root",
|
|
74608
|
+
"parameters": {
|
|
74609
|
+
"label": "Save"
|
|
74610
|
+
}
|
|
74611
|
+
},
|
|
74612
|
+
{
|
|
74613
|
+
"id": "gnr_inputs",
|
|
74614
|
+
"type": "Component Inputs",
|
|
74615
|
+
"label": "Note in",
|
|
74616
|
+
"ports": [
|
|
74617
|
+
{
|
|
74618
|
+
"name": "title",
|
|
74619
|
+
"plug": "output",
|
|
74620
|
+
"type": "*"
|
|
74621
|
+
}
|
|
74622
|
+
]
|
|
74623
|
+
},
|
|
74624
|
+
{
|
|
74625
|
+
"id": "gnr_update",
|
|
74626
|
+
"type": "SetDbModelProperties",
|
|
74627
|
+
"label": "Change this note (the group's role may write it)",
|
|
74628
|
+
"parameters": {
|
|
74629
|
+
"collectionName": "Note",
|
|
74630
|
+
"idSource": "foreach"
|
|
74631
|
+
}
|
|
74632
|
+
}
|
|
74633
|
+
],
|
|
74634
|
+
"connections": [
|
|
74635
|
+
{
|
|
74636
|
+
"fromId": "gnr_inputs",
|
|
74637
|
+
"fromProperty": "title",
|
|
74638
|
+
"toId": "gnr_title",
|
|
74639
|
+
"toProperty": "startValue"
|
|
74640
|
+
},
|
|
74641
|
+
{
|
|
74642
|
+
"fromId": "gnr_title",
|
|
74643
|
+
"fromProperty": "onTextChanged",
|
|
74644
|
+
"toId": "gnr_update",
|
|
74645
|
+
"toProperty": "prop-title"
|
|
74646
|
+
},
|
|
74647
|
+
{
|
|
74648
|
+
"fromId": "gnr_save",
|
|
74649
|
+
"fromProperty": "onClick",
|
|
74650
|
+
"toId": "gnr_update",
|
|
74651
|
+
"toProperty": "store"
|
|
74652
|
+
}
|
|
74653
|
+
]
|
|
74654
|
+
},
|
|
74655
|
+
{
|
|
74656
|
+
"name": "/Group Notes",
|
|
74657
|
+
"nodes": [
|
|
74658
|
+
{
|
|
74659
|
+
"id": "grp_root",
|
|
74660
|
+
"type": "Group",
|
|
74661
|
+
"label": "The notes of the group I am in",
|
|
74662
|
+
"parameters": {
|
|
74663
|
+
"flexDirection": "column",
|
|
74664
|
+
"rowGap": "var(--space-3)"
|
|
74665
|
+
},
|
|
74666
|
+
"children": [
|
|
74667
|
+
"grp_title",
|
|
74668
|
+
"grp_add",
|
|
74669
|
+
"grp_list"
|
|
74670
|
+
]
|
|
74671
|
+
},
|
|
74672
|
+
{
|
|
74673
|
+
"id": "grp_title",
|
|
74674
|
+
"type": "net.noodl.controls.textinput",
|
|
74675
|
+
"label": "A new note",
|
|
74676
|
+
"parent": "grp_root",
|
|
74677
|
+
"parameters": {
|
|
74678
|
+
"placeholder": "A new note"
|
|
74679
|
+
}
|
|
74680
|
+
},
|
|
74681
|
+
{
|
|
74682
|
+
"id": "grp_add",
|
|
74683
|
+
"type": "net.noodl.controls.button",
|
|
74684
|
+
"label": "Add it to the group",
|
|
74685
|
+
"parent": "grp_root",
|
|
74686
|
+
"parameters": {
|
|
74687
|
+
"label": "Add"
|
|
74688
|
+
}
|
|
74689
|
+
},
|
|
74690
|
+
{
|
|
74691
|
+
"id": "grp_list",
|
|
74692
|
+
"type": "For Each",
|
|
74693
|
+
"label": "One row per note of the group",
|
|
74694
|
+
"parent": "grp_root",
|
|
74695
|
+
"parameters": {
|
|
74696
|
+
"template": "/Group Note Row"
|
|
74697
|
+
}
|
|
74698
|
+
},
|
|
74699
|
+
{
|
|
74700
|
+
"id": "grp_join",
|
|
74701
|
+
"type": "CloudFunction2",
|
|
74702
|
+
"label": "Which group am I in (the server decides)",
|
|
74703
|
+
"parameters": {
|
|
74704
|
+
"function": "Cloud Functions/Join Group"
|
|
74705
|
+
}
|
|
74706
|
+
},
|
|
74707
|
+
{
|
|
74708
|
+
"id": "grp_notes",
|
|
74709
|
+
"type": "DbCollection2",
|
|
74710
|
+
"label": "The group's notes: owned by the group's owner",
|
|
74711
|
+
"parameters": {
|
|
74712
|
+
"collectionName": "Note",
|
|
74713
|
+
"visualFilter": {
|
|
74714
|
+
"combinator": "and",
|
|
74715
|
+
"rules": [
|
|
74716
|
+
{
|
|
74717
|
+
"property": "owner",
|
|
74718
|
+
"operator": "equal to",
|
|
74719
|
+
"input": "group"
|
|
74720
|
+
}
|
|
74721
|
+
]
|
|
74722
|
+
},
|
|
74723
|
+
"visualSort": [
|
|
74724
|
+
{
|
|
74725
|
+
"property": "createdAt",
|
|
74726
|
+
"order": "ascending"
|
|
74727
|
+
}
|
|
74728
|
+
],
|
|
74729
|
+
"runOnChange-collectionName": false,
|
|
74730
|
+
"runOnChange-querySettings": false
|
|
74731
|
+
}
|
|
74732
|
+
},
|
|
74733
|
+
{
|
|
74734
|
+
"id": "grp_add_call",
|
|
74735
|
+
"type": "CloudFunction2",
|
|
74736
|
+
"label": "Add a note to the group (the server checks I am in it)",
|
|
74737
|
+
"parameters": {
|
|
74738
|
+
"function": "Cloud Functions/Add To Group"
|
|
74739
|
+
}
|
|
74740
|
+
}
|
|
74741
|
+
],
|
|
74742
|
+
"connections": [
|
|
74743
|
+
{
|
|
74744
|
+
"fromId": "grp_root",
|
|
74745
|
+
"fromProperty": "didMount",
|
|
74746
|
+
"toId": "grp_join",
|
|
74747
|
+
"toProperty": "call"
|
|
74748
|
+
},
|
|
74749
|
+
{
|
|
74750
|
+
"fromId": "grp_join",
|
|
74751
|
+
"fromProperty": "out-group",
|
|
74752
|
+
"toId": "grp_notes",
|
|
74753
|
+
"toProperty": "qp-group"
|
|
74754
|
+
},
|
|
74755
|
+
{
|
|
74756
|
+
"fromId": "grp_join",
|
|
74757
|
+
"fromProperty": "done",
|
|
74758
|
+
"toId": "grp_notes",
|
|
74759
|
+
"toProperty": "storageFetch"
|
|
74760
|
+
},
|
|
74761
|
+
{
|
|
74762
|
+
"fromId": "grp_notes",
|
|
74763
|
+
"fromProperty": "items",
|
|
74764
|
+
"toId": "grp_list",
|
|
74765
|
+
"toProperty": "items"
|
|
74766
|
+
},
|
|
74767
|
+
{
|
|
74768
|
+
"fromId": "grp_title",
|
|
74769
|
+
"fromProperty": "onTextChanged",
|
|
74770
|
+
"toId": "grp_add_call",
|
|
74771
|
+
"toProperty": "in-title"
|
|
74772
|
+
},
|
|
74773
|
+
{
|
|
74774
|
+
"fromId": "grp_join",
|
|
74775
|
+
"fromProperty": "out-group",
|
|
74776
|
+
"toId": "grp_add_call",
|
|
74777
|
+
"toProperty": "in-group"
|
|
74778
|
+
},
|
|
74779
|
+
{
|
|
74780
|
+
"fromId": "grp_add",
|
|
74781
|
+
"fromProperty": "onClick",
|
|
74782
|
+
"toId": "grp_add_call",
|
|
74783
|
+
"toProperty": "call"
|
|
74784
|
+
},
|
|
74785
|
+
{
|
|
74786
|
+
"fromId": "grp_add_call",
|
|
74787
|
+
"fromProperty": "done",
|
|
74788
|
+
"toId": "grp_notes",
|
|
74789
|
+
"toProperty": "storageFetch"
|
|
74790
|
+
}
|
|
74791
|
+
]
|
|
74792
|
+
},
|
|
74793
|
+
{
|
|
74794
|
+
"name": "/#__cloud__/Cloud Functions/Join Group",
|
|
74795
|
+
"nodes": [
|
|
74796
|
+
{
|
|
74797
|
+
"id": "jg_request",
|
|
74798
|
+
"type": "noodl.cloud.request",
|
|
74799
|
+
"label": "The caller (no parameters: nothing to forge)",
|
|
74800
|
+
"parameters": {
|
|
74801
|
+
"allowNoAuth": false
|
|
74802
|
+
}
|
|
74803
|
+
},
|
|
74804
|
+
{
|
|
74805
|
+
"id": "jg_me",
|
|
74806
|
+
"type": "net.noodl.user.User",
|
|
74807
|
+
"label": "The caller's own session"
|
|
74808
|
+
},
|
|
74809
|
+
{
|
|
74810
|
+
"id": "jg_invites",
|
|
74811
|
+
"type": "DbCollection2",
|
|
74812
|
+
"label": "Every invitation",
|
|
74813
|
+
"parameters": {
|
|
74814
|
+
"collectionName": "Invite",
|
|
74815
|
+
"runOnChange-collectionName": false,
|
|
74816
|
+
"runOnChange-querySettings": false
|
|
74817
|
+
}
|
|
74818
|
+
},
|
|
74819
|
+
{
|
|
74820
|
+
"id": "jg_pick",
|
|
74821
|
+
"type": "JavaScriptFunction",
|
|
74822
|
+
"label": "The group of whoever invited this address: the invitation's owner",
|
|
74823
|
+
"parameters": {
|
|
74824
|
+
"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();"
|
|
74825
|
+
}
|
|
74826
|
+
},
|
|
74827
|
+
{
|
|
74828
|
+
"id": "jg_join",
|
|
74829
|
+
"type": "noodl.cloud.addusertorole",
|
|
74830
|
+
"label": "Put the caller in that group's role",
|
|
74831
|
+
"parameters": {
|
|
74832
|
+
"createRole": true
|
|
74833
|
+
}
|
|
74834
|
+
},
|
|
74835
|
+
{
|
|
74836
|
+
"id": "jg_ok",
|
|
74837
|
+
"type": "noodl.cloud.response",
|
|
74838
|
+
"label": "The group this caller is in",
|
|
74839
|
+
"parameters": {
|
|
74840
|
+
"params": "group"
|
|
74841
|
+
}
|
|
74842
|
+
},
|
|
74843
|
+
{
|
|
74844
|
+
"id": "jg_fail",
|
|
74845
|
+
"type": "noodl.cloud.response",
|
|
74846
|
+
"label": "Tell the caller it went wrong",
|
|
74847
|
+
"parameters": {
|
|
74848
|
+
"status": "failure"
|
|
74849
|
+
}
|
|
74850
|
+
}
|
|
74851
|
+
],
|
|
74852
|
+
"connections": [
|
|
74853
|
+
{
|
|
74854
|
+
"fromId": "jg_request",
|
|
74855
|
+
"fromProperty": "receive",
|
|
74856
|
+
"toId": "jg_me",
|
|
74857
|
+
"toProperty": "fetch"
|
|
74858
|
+
},
|
|
74859
|
+
{
|
|
74860
|
+
"fromId": "jg_request",
|
|
74861
|
+
"fromProperty": "receive",
|
|
74862
|
+
"toId": "jg_invites",
|
|
74863
|
+
"toProperty": "storageFetch"
|
|
74864
|
+
},
|
|
74865
|
+
{
|
|
74866
|
+
"fromId": "jg_me",
|
|
74867
|
+
"fromProperty": "id",
|
|
74868
|
+
"toId": "jg_pick",
|
|
74869
|
+
"toProperty": "in-me"
|
|
74870
|
+
},
|
|
74871
|
+
{
|
|
74872
|
+
"fromId": "jg_me",
|
|
74873
|
+
"fromProperty": "email",
|
|
74874
|
+
"toId": "jg_pick",
|
|
74875
|
+
"toProperty": "in-email"
|
|
74876
|
+
},
|
|
74877
|
+
{
|
|
74878
|
+
"fromId": "jg_invites",
|
|
74879
|
+
"fromProperty": "items",
|
|
74880
|
+
"toId": "jg_pick",
|
|
74881
|
+
"toProperty": "in-invites"
|
|
74882
|
+
},
|
|
74883
|
+
{
|
|
74884
|
+
"fromId": "jg_pick",
|
|
74885
|
+
"fromProperty": "out-role",
|
|
74886
|
+
"toId": "jg_join",
|
|
74887
|
+
"toProperty": "role"
|
|
74888
|
+
},
|
|
74889
|
+
{
|
|
74890
|
+
"fromId": "jg_pick",
|
|
74891
|
+
"fromProperty": "out-userId",
|
|
74892
|
+
"toId": "jg_join",
|
|
74893
|
+
"toProperty": "userId"
|
|
74894
|
+
},
|
|
74895
|
+
{
|
|
74896
|
+
"fromId": "jg_pick",
|
|
74897
|
+
"fromProperty": "out-join",
|
|
74898
|
+
"toId": "jg_join",
|
|
74899
|
+
"toProperty": "add"
|
|
74900
|
+
},
|
|
74901
|
+
{
|
|
74902
|
+
"fromId": "jg_pick",
|
|
74903
|
+
"fromProperty": "out-group",
|
|
74904
|
+
"toId": "jg_ok",
|
|
74905
|
+
"toProperty": "pm-group"
|
|
74906
|
+
},
|
|
74907
|
+
{
|
|
74908
|
+
"fromId": "jg_pick",
|
|
74909
|
+
"fromProperty": "out-none",
|
|
74910
|
+
"toId": "jg_ok",
|
|
74911
|
+
"toProperty": "send"
|
|
74912
|
+
},
|
|
74913
|
+
{
|
|
74914
|
+
"fromId": "jg_join",
|
|
74915
|
+
"fromProperty": "done",
|
|
74916
|
+
"toId": "jg_ok",
|
|
74917
|
+
"toProperty": "send"
|
|
74918
|
+
},
|
|
74919
|
+
{
|
|
74920
|
+
"fromId": "jg_join",
|
|
74921
|
+
"fromProperty": "unchanged",
|
|
74922
|
+
"toId": "jg_ok",
|
|
74923
|
+
"toProperty": "send"
|
|
74924
|
+
},
|
|
74925
|
+
{
|
|
74926
|
+
"fromId": "jg_pick",
|
|
74927
|
+
"fromProperty": "error",
|
|
74928
|
+
"toId": "jg_fail",
|
|
74929
|
+
"toProperty": "errorMessage"
|
|
74930
|
+
},
|
|
74931
|
+
{
|
|
74932
|
+
"fromId": "jg_join",
|
|
74933
|
+
"fromProperty": "error",
|
|
74934
|
+
"toId": "jg_fail",
|
|
74935
|
+
"toProperty": "errorMessage"
|
|
74936
|
+
},
|
|
74937
|
+
{
|
|
74938
|
+
"fromId": "jg_pick",
|
|
74939
|
+
"fromProperty": "failure",
|
|
74940
|
+
"toId": "jg_fail",
|
|
74941
|
+
"toProperty": "send"
|
|
74942
|
+
},
|
|
74943
|
+
{
|
|
74944
|
+
"fromId": "jg_join",
|
|
74945
|
+
"fromProperty": "failure",
|
|
74946
|
+
"toId": "jg_fail",
|
|
74947
|
+
"toProperty": "send"
|
|
74948
|
+
}
|
|
74949
|
+
]
|
|
74950
|
+
},
|
|
74951
|
+
{
|
|
74952
|
+
"name": "/#__cloud__/Cloud Functions/Add To Group",
|
|
74953
|
+
"nodes": [
|
|
74954
|
+
{
|
|
74955
|
+
"id": "ag_request",
|
|
74956
|
+
"type": "noodl.cloud.request",
|
|
74957
|
+
"label": "A note, and the group it is for",
|
|
74958
|
+
"parameters": {
|
|
74959
|
+
"allowNoAuth": false,
|
|
74960
|
+
"params": "title,group"
|
|
74961
|
+
}
|
|
74962
|
+
},
|
|
74963
|
+
{
|
|
74964
|
+
"id": "ag_me",
|
|
74965
|
+
"type": "net.noodl.user.User",
|
|
74966
|
+
"label": "The caller's own session, and the roles it carries"
|
|
74967
|
+
},
|
|
74968
|
+
{
|
|
74969
|
+
"id": "ag_add",
|
|
74970
|
+
"type": "JavaScriptFunction",
|
|
74971
|
+
"label": "Only a member writes, and the note is the group owner's",
|
|
74972
|
+
"parameters": {
|
|
74973
|
+
"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();"
|
|
74974
|
+
}
|
|
74975
|
+
},
|
|
74976
|
+
{
|
|
74977
|
+
"id": "ag_ok",
|
|
74978
|
+
"type": "noodl.cloud.response",
|
|
74979
|
+
"label": "The new note",
|
|
74980
|
+
"parameters": {
|
|
74981
|
+
"params": "id"
|
|
74982
|
+
}
|
|
74983
|
+
},
|
|
74984
|
+
{
|
|
74985
|
+
"id": "ag_fail",
|
|
74986
|
+
"type": "noodl.cloud.response",
|
|
74987
|
+
"label": "Refused, or it went wrong",
|
|
74988
|
+
"parameters": {
|
|
74989
|
+
"status": "failure"
|
|
74990
|
+
}
|
|
74991
|
+
}
|
|
74992
|
+
],
|
|
74993
|
+
"connections": [
|
|
74994
|
+
{
|
|
74995
|
+
"fromId": "ag_request",
|
|
74996
|
+
"fromProperty": "receive",
|
|
74997
|
+
"toId": "ag_me",
|
|
74998
|
+
"toProperty": "fetch"
|
|
74999
|
+
},
|
|
75000
|
+
{
|
|
75001
|
+
"fromId": "ag_me",
|
|
75002
|
+
"fromProperty": "id",
|
|
75003
|
+
"toId": "ag_add",
|
|
75004
|
+
"toProperty": "in-me"
|
|
75005
|
+
},
|
|
75006
|
+
{
|
|
75007
|
+
"fromId": "ag_me",
|
|
75008
|
+
"fromProperty": "roles",
|
|
75009
|
+
"toId": "ag_add",
|
|
75010
|
+
"toProperty": "in-roles"
|
|
75011
|
+
},
|
|
75012
|
+
{
|
|
75013
|
+
"fromId": "ag_request",
|
|
75014
|
+
"fromProperty": "pm-group",
|
|
75015
|
+
"toId": "ag_add",
|
|
75016
|
+
"toProperty": "in-group"
|
|
75017
|
+
},
|
|
75018
|
+
{
|
|
75019
|
+
"fromId": "ag_request",
|
|
75020
|
+
"fromProperty": "pm-title",
|
|
75021
|
+
"toId": "ag_add",
|
|
75022
|
+
"toProperty": "in-title"
|
|
75023
|
+
},
|
|
75024
|
+
{
|
|
75025
|
+
"fromId": "ag_add",
|
|
75026
|
+
"fromProperty": "out-id",
|
|
75027
|
+
"toId": "ag_ok",
|
|
75028
|
+
"toProperty": "pm-id"
|
|
75029
|
+
},
|
|
75030
|
+
{
|
|
75031
|
+
"fromId": "ag_add",
|
|
75032
|
+
"fromProperty": "out-added",
|
|
75033
|
+
"toId": "ag_ok",
|
|
75034
|
+
"toProperty": "send"
|
|
75035
|
+
},
|
|
75036
|
+
{
|
|
75037
|
+
"fromId": "ag_add",
|
|
75038
|
+
"fromProperty": "out-reason",
|
|
75039
|
+
"toId": "ag_fail",
|
|
75040
|
+
"toProperty": "errorMessage"
|
|
75041
|
+
},
|
|
75042
|
+
{
|
|
75043
|
+
"fromId": "ag_add",
|
|
75044
|
+
"fromProperty": "out-refused",
|
|
75045
|
+
"toId": "ag_fail",
|
|
75046
|
+
"toProperty": "send"
|
|
75047
|
+
},
|
|
75048
|
+
{
|
|
75049
|
+
"fromId": "ag_add",
|
|
75050
|
+
"fromProperty": "error",
|
|
75051
|
+
"toId": "ag_fail",
|
|
75052
|
+
"toProperty": "errorMessage"
|
|
75053
|
+
},
|
|
75054
|
+
{
|
|
75055
|
+
"fromId": "ag_add",
|
|
75056
|
+
"fromProperty": "failure",
|
|
75057
|
+
"toId": "ag_fail",
|
|
75058
|
+
"toProperty": "send"
|
|
75059
|
+
}
|
|
75060
|
+
]
|
|
75061
|
+
},
|
|
75062
|
+
{
|
|
75063
|
+
"name": "/#__cloud__/Cloud Functions/Remove Member",
|
|
75064
|
+
"nodes": [
|
|
75065
|
+
{
|
|
75066
|
+
"id": "rm_request",
|
|
75067
|
+
"type": "noodl.cloud.request",
|
|
75068
|
+
"label": "The address whose invitation the caller takes back",
|
|
75069
|
+
"parameters": {
|
|
75070
|
+
"allowNoAuth": false,
|
|
75071
|
+
"params": "withEmail"
|
|
75072
|
+
}
|
|
75073
|
+
},
|
|
75074
|
+
{
|
|
75075
|
+
"id": "rm_me",
|
|
75076
|
+
"type": "net.noodl.user.User",
|
|
75077
|
+
"label": "The caller's own session"
|
|
75078
|
+
},
|
|
75079
|
+
{
|
|
75080
|
+
"id": "rm_invites",
|
|
75081
|
+
"type": "DbCollection2",
|
|
75082
|
+
"label": "Every invitation",
|
|
75083
|
+
"parameters": {
|
|
75084
|
+
"collectionName": "Invite",
|
|
75085
|
+
"runOnChange-collectionName": false,
|
|
75086
|
+
"runOnChange-querySettings": false
|
|
75087
|
+
}
|
|
75088
|
+
},
|
|
75089
|
+
{
|
|
75090
|
+
"id": "rm_pick",
|
|
75091
|
+
"type": "JavaScriptFunction",
|
|
75092
|
+
"label": "The caller's own invitation, and the member who took it up",
|
|
75093
|
+
"parameters": {
|
|
75094
|
+
"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();"
|
|
75095
|
+
}
|
|
75096
|
+
},
|
|
75097
|
+
{
|
|
75098
|
+
"id": "rm_role",
|
|
75099
|
+
"type": "noodl.cloud.removeuserfromrole",
|
|
75100
|
+
"label": "Take them out of the caller's group"
|
|
75101
|
+
},
|
|
75102
|
+
{
|
|
75103
|
+
"id": "rm_ok",
|
|
75104
|
+
"type": "noodl.cloud.response",
|
|
75105
|
+
"label": "Taken back"
|
|
75106
|
+
},
|
|
75107
|
+
{
|
|
75108
|
+
"id": "rm_fail",
|
|
75109
|
+
"type": "noodl.cloud.response",
|
|
75110
|
+
"label": "Refused, or it went wrong",
|
|
75111
|
+
"parameters": {
|
|
75112
|
+
"status": "failure"
|
|
75113
|
+
}
|
|
75114
|
+
}
|
|
75115
|
+
],
|
|
75116
|
+
"connections": [
|
|
75117
|
+
{
|
|
75118
|
+
"fromId": "rm_request",
|
|
75119
|
+
"fromProperty": "receive",
|
|
75120
|
+
"toId": "rm_me",
|
|
75121
|
+
"toProperty": "fetch"
|
|
75122
|
+
},
|
|
75123
|
+
{
|
|
75124
|
+
"fromId": "rm_request",
|
|
75125
|
+
"fromProperty": "receive",
|
|
75126
|
+
"toId": "rm_invites",
|
|
75127
|
+
"toProperty": "storageFetch"
|
|
75128
|
+
},
|
|
75129
|
+
{
|
|
75130
|
+
"fromId": "rm_me",
|
|
75131
|
+
"fromProperty": "id",
|
|
75132
|
+
"toId": "rm_pick",
|
|
75133
|
+
"toProperty": "in-me"
|
|
75134
|
+
},
|
|
75135
|
+
{
|
|
75136
|
+
"fromId": "rm_request",
|
|
75137
|
+
"fromProperty": "pm-withEmail",
|
|
75138
|
+
"toId": "rm_pick",
|
|
75139
|
+
"toProperty": "in-withEmail"
|
|
75140
|
+
},
|
|
75141
|
+
{
|
|
75142
|
+
"fromId": "rm_invites",
|
|
75143
|
+
"fromProperty": "items",
|
|
75144
|
+
"toId": "rm_pick",
|
|
75145
|
+
"toProperty": "in-invites"
|
|
75146
|
+
},
|
|
75147
|
+
{
|
|
75148
|
+
"fromId": "rm_pick",
|
|
75149
|
+
"fromProperty": "out-role",
|
|
75150
|
+
"toId": "rm_role",
|
|
75151
|
+
"toProperty": "role"
|
|
75152
|
+
},
|
|
75153
|
+
{
|
|
75154
|
+
"fromId": "rm_pick",
|
|
75155
|
+
"fromProperty": "out-userId",
|
|
75156
|
+
"toId": "rm_role",
|
|
75157
|
+
"toProperty": "userId"
|
|
75158
|
+
},
|
|
75159
|
+
{
|
|
75160
|
+
"fromId": "rm_pick",
|
|
75161
|
+
"fromProperty": "out-remove",
|
|
75162
|
+
"toId": "rm_role",
|
|
75163
|
+
"toProperty": "remove"
|
|
75164
|
+
},
|
|
75165
|
+
{
|
|
75166
|
+
"fromId": "rm_pick",
|
|
75167
|
+
"fromProperty": "out-removed",
|
|
75168
|
+
"toId": "rm_ok",
|
|
75169
|
+
"toProperty": "send"
|
|
75170
|
+
},
|
|
75171
|
+
{
|
|
75172
|
+
"fromId": "rm_role",
|
|
75173
|
+
"fromProperty": "done",
|
|
75174
|
+
"toId": "rm_ok",
|
|
75175
|
+
"toProperty": "send"
|
|
75176
|
+
},
|
|
75177
|
+
{
|
|
75178
|
+
"fromId": "rm_role",
|
|
75179
|
+
"fromProperty": "unchanged",
|
|
75180
|
+
"toId": "rm_ok",
|
|
75181
|
+
"toProperty": "send"
|
|
75182
|
+
},
|
|
75183
|
+
{
|
|
75184
|
+
"fromId": "rm_pick",
|
|
75185
|
+
"fromProperty": "out-reason",
|
|
75186
|
+
"toId": "rm_fail",
|
|
75187
|
+
"toProperty": "errorMessage"
|
|
75188
|
+
},
|
|
75189
|
+
{
|
|
75190
|
+
"fromId": "rm_pick",
|
|
75191
|
+
"fromProperty": "out-refused",
|
|
75192
|
+
"toId": "rm_fail",
|
|
75193
|
+
"toProperty": "send"
|
|
75194
|
+
},
|
|
75195
|
+
{
|
|
75196
|
+
"fromId": "rm_pick",
|
|
75197
|
+
"fromProperty": "error",
|
|
75198
|
+
"toId": "rm_fail",
|
|
75199
|
+
"toProperty": "errorMessage"
|
|
75200
|
+
},
|
|
75201
|
+
{
|
|
75202
|
+
"fromId": "rm_role",
|
|
75203
|
+
"fromProperty": "error",
|
|
75204
|
+
"toId": "rm_fail",
|
|
75205
|
+
"toProperty": "errorMessage"
|
|
75206
|
+
},
|
|
75207
|
+
{
|
|
75208
|
+
"fromId": "rm_pick",
|
|
75209
|
+
"fromProperty": "failure",
|
|
75210
|
+
"toId": "rm_fail",
|
|
75211
|
+
"toProperty": "send"
|
|
75212
|
+
},
|
|
75213
|
+
{
|
|
75214
|
+
"fromId": "rm_role",
|
|
75215
|
+
"fromProperty": "failure",
|
|
75216
|
+
"toId": "rm_fail",
|
|
75217
|
+
"toProperty": "send"
|
|
75218
|
+
}
|
|
75219
|
+
]
|
|
75220
|
+
}
|
|
75221
|
+
]
|
|
75222
|
+
},
|
|
74349
75223
|
{
|
|
74350
75224
|
"id": "cloud-query-own-rows",
|
|
74351
75225
|
"title": "Only my records: a Query Records filtered on the server by the signed-in user's id",
|
|
@@ -75001,7 +75875,7 @@
|
|
|
75001
75875
|
],
|
|
75002
75876
|
"components": [
|
|
75003
75877
|
{
|
|
75004
|
-
"name": "/Cloud Functions/Revoke Role",
|
|
75878
|
+
"name": "/#__cloud__/Cloud Functions/Revoke Role",
|
|
75005
75879
|
"nodes": [
|
|
75006
75880
|
{
|
|
75007
75881
|
"id": "fn_request",
|
|
@@ -75589,7 +76463,7 @@
|
|
|
75589
76463
|
],
|
|
75590
76464
|
"components": [
|
|
75591
76465
|
{
|
|
75592
|
-
"name": "/Cloud Functions/Issue Partner Token",
|
|
76466
|
+
"name": "/#__cloud__/Cloud Functions/Issue Partner Token",
|
|
75593
76467
|
"nodes": [
|
|
75594
76468
|
{
|
|
75595
76469
|
"id": "fn_request",
|
|
@@ -76217,7 +77091,7 @@
|
|
|
76217
77091
|
],
|
|
76218
77092
|
"components": [
|
|
76219
77093
|
{
|
|
76220
|
-
"name": "/Cloud Functions/List Role Members",
|
|
77094
|
+
"name": "/#__cloud__/Cloud Functions/List Role Members",
|
|
76221
77095
|
"nodes": [
|
|
76222
77096
|
{
|
|
76223
77097
|
"id": "fn_request",
|
|
@@ -76429,7 +77303,7 @@
|
|
|
76429
77303
|
"type": "Javascript2",
|
|
76430
77304
|
"label": "Extract headline",
|
|
76431
77305
|
"parameters": {
|
|
76432
|
-
"code": "
|
|
77306
|
+
"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
77307
|
}
|
|
76434
77308
|
},
|
|
76435
77309
|
{
|
|
@@ -76745,9 +77619,7 @@
|
|
|
76745
77619
|
{
|
|
76746
77620
|
"name": "Clicked",
|
|
76747
77621
|
"plug": "input",
|
|
76748
|
-
"type":
|
|
76749
|
-
"name": "*"
|
|
76750
|
-
},
|
|
77622
|
+
"type": "*",
|
|
76751
77623
|
"index": 0
|
|
76752
77624
|
}
|
|
76753
77625
|
]
|
|
@@ -76759,135 +77631,105 @@
|
|
|
76759
77631
|
{
|
|
76760
77632
|
"name": "text",
|
|
76761
77633
|
"plug": "output",
|
|
76762
|
-
"type":
|
|
76763
|
-
"name": "*"
|
|
76764
|
-
},
|
|
77634
|
+
"type": "*",
|
|
76765
77635
|
"group": "General",
|
|
76766
77636
|
"index": 2
|
|
76767
77637
|
},
|
|
76768
77638
|
{
|
|
76769
77639
|
"name": "css-class",
|
|
76770
77640
|
"plug": "output",
|
|
76771
|
-
"type":
|
|
76772
|
-
"name": "*"
|
|
76773
|
-
},
|
|
77641
|
+
"type": "*",
|
|
76774
77642
|
"group": "General",
|
|
76775
77643
|
"index": 1
|
|
76776
77644
|
},
|
|
76777
77645
|
{
|
|
76778
77646
|
"name": "padding top",
|
|
76779
77647
|
"plug": "output",
|
|
76780
|
-
"type":
|
|
76781
|
-
"name": "*"
|
|
76782
|
-
},
|
|
77648
|
+
"type": "*",
|
|
76783
77649
|
"group": "Margin and padding",
|
|
76784
77650
|
"index": 3
|
|
76785
77651
|
},
|
|
76786
77652
|
{
|
|
76787
77653
|
"name": "padding bottom",
|
|
76788
77654
|
"plug": "output",
|
|
76789
|
-
"type":
|
|
76790
|
-
"name": "*"
|
|
76791
|
-
},
|
|
77655
|
+
"type": "*",
|
|
76792
77656
|
"group": "Margin and padding",
|
|
76793
77657
|
"index": 4
|
|
76794
77658
|
},
|
|
76795
77659
|
{
|
|
76796
77660
|
"name": "padding left",
|
|
76797
77661
|
"plug": "output",
|
|
76798
|
-
"type":
|
|
76799
|
-
"name": "*"
|
|
76800
|
-
},
|
|
77662
|
+
"type": "*",
|
|
76801
77663
|
"group": "Margin and padding",
|
|
76802
77664
|
"index": 5
|
|
76803
77665
|
},
|
|
76804
77666
|
{
|
|
76805
77667
|
"name": "padding right",
|
|
76806
77668
|
"plug": "output",
|
|
76807
|
-
"type":
|
|
76808
|
-
"name": "*"
|
|
76809
|
-
},
|
|
77669
|
+
"type": "*",
|
|
76810
77670
|
"group": "Margin and padding",
|
|
76811
77671
|
"index": 6
|
|
76812
77672
|
},
|
|
76813
77673
|
{
|
|
76814
77674
|
"name": "margin top",
|
|
76815
77675
|
"plug": "output",
|
|
76816
|
-
"type":
|
|
76817
|
-
"name": "*"
|
|
76818
|
-
},
|
|
77676
|
+
"type": "*",
|
|
76819
77677
|
"group": "Margin and padding",
|
|
76820
77678
|
"index": 7
|
|
76821
77679
|
},
|
|
76822
77680
|
{
|
|
76823
77681
|
"name": "margin bottom",
|
|
76824
77682
|
"plug": "output",
|
|
76825
|
-
"type":
|
|
76826
|
-
"name": "*"
|
|
76827
|
-
},
|
|
77683
|
+
"type": "*",
|
|
76828
77684
|
"group": "Margin and padding",
|
|
76829
77685
|
"index": 8
|
|
76830
77686
|
},
|
|
76831
77687
|
{
|
|
76832
77688
|
"name": "margin left",
|
|
76833
77689
|
"plug": "output",
|
|
76834
|
-
"type":
|
|
76835
|
-
"name": "*"
|
|
76836
|
-
},
|
|
77690
|
+
"type": "*",
|
|
76837
77691
|
"group": "Margin and padding",
|
|
76838
77692
|
"index": 9
|
|
76839
77693
|
},
|
|
76840
77694
|
{
|
|
76841
77695
|
"name": "margin right",
|
|
76842
77696
|
"plug": "output",
|
|
76843
|
-
"type":
|
|
76844
|
-
"name": "*"
|
|
76845
|
-
},
|
|
77697
|
+
"type": "*",
|
|
76846
77698
|
"group": "Margin and padding",
|
|
76847
77699
|
"index": 10
|
|
76848
77700
|
},
|
|
76849
77701
|
{
|
|
76850
77702
|
"name": "mounted",
|
|
76851
77703
|
"plug": "output",
|
|
76852
|
-
"type":
|
|
76853
|
-
"name": "*"
|
|
76854
|
-
},
|
|
77704
|
+
"type": "*",
|
|
76855
77705
|
"group": "General",
|
|
76856
77706
|
"index": 0
|
|
76857
77707
|
},
|
|
76858
77708
|
{
|
|
76859
77709
|
"name": "align horizontal",
|
|
76860
77710
|
"plug": "output",
|
|
76861
|
-
"type":
|
|
76862
|
-
"name": "*"
|
|
76863
|
-
},
|
|
77711
|
+
"type": "*",
|
|
76864
77712
|
"group": "Other",
|
|
76865
77713
|
"index": 11
|
|
76866
77714
|
},
|
|
76867
77715
|
{
|
|
76868
77716
|
"name": "align vertical",
|
|
76869
77717
|
"plug": "output",
|
|
76870
|
-
"type":
|
|
76871
|
-
"name": "*"
|
|
76872
|
-
},
|
|
77718
|
+
"type": "*",
|
|
76873
77719
|
"group": "Other",
|
|
76874
77720
|
"index": 12
|
|
76875
77721
|
},
|
|
76876
77722
|
{
|
|
76877
77723
|
"name": "position",
|
|
76878
77724
|
"plug": "output",
|
|
76879
|
-
"type":
|
|
76880
|
-
"name": "*"
|
|
76881
|
-
},
|
|
77725
|
+
"type": "*",
|
|
76882
77726
|
"group": "Other",
|
|
76883
77727
|
"index": 13
|
|
76884
77728
|
},
|
|
76885
77729
|
{
|
|
76886
77730
|
"name": "layout",
|
|
76887
77731
|
"plug": "output",
|
|
76888
|
-
"type":
|
|
76889
|
-
"name": "*"
|
|
76890
|
-
},
|
|
77732
|
+
"type": "*",
|
|
76891
77733
|
"group": "Other",
|
|
76892
77734
|
"index": 14
|
|
76893
77735
|
}
|
|
@@ -77067,17 +77909,13 @@
|
|
|
77067
77909
|
{
|
|
77068
77910
|
"name": "Do",
|
|
77069
77911
|
"plug": "output",
|
|
77070
|
-
"type":
|
|
77071
|
-
"name": "*"
|
|
77072
|
-
},
|
|
77912
|
+
"type": "*",
|
|
77073
77913
|
"index": 0
|
|
77074
77914
|
},
|
|
77075
77915
|
{
|
|
77076
77916
|
"name": "JsonArray",
|
|
77077
77917
|
"plug": "output",
|
|
77078
|
-
"type":
|
|
77079
|
-
"name": "*"
|
|
77080
|
-
},
|
|
77918
|
+
"type": "*",
|
|
77081
77919
|
"index": 1
|
|
77082
77920
|
}
|
|
77083
77921
|
]
|
|
@@ -77139,54 +77977,42 @@
|
|
|
77139
77977
|
{
|
|
77140
77978
|
"name": "File",
|
|
77141
77979
|
"plug": "input",
|
|
77142
|
-
"type":
|
|
77143
|
-
"name": "*"
|
|
77144
|
-
},
|
|
77980
|
+
"type": "*",
|
|
77145
77981
|
"group": "General",
|
|
77146
77982
|
"index": 0
|
|
77147
77983
|
},
|
|
77148
77984
|
{
|
|
77149
77985
|
"name": "Path",
|
|
77150
77986
|
"plug": "input",
|
|
77151
|
-
"type":
|
|
77152
|
-
"name": "*"
|
|
77153
|
-
},
|
|
77987
|
+
"type": "*",
|
|
77154
77988
|
"group": "Metadata",
|
|
77155
77989
|
"index": 1
|
|
77156
77990
|
},
|
|
77157
77991
|
{
|
|
77158
77992
|
"name": "Name",
|
|
77159
77993
|
"plug": "input",
|
|
77160
|
-
"type":
|
|
77161
|
-
"name": "*"
|
|
77162
|
-
},
|
|
77994
|
+
"type": "*",
|
|
77163
77995
|
"group": "Metadata",
|
|
77164
77996
|
"index": 2
|
|
77165
77997
|
},
|
|
77166
77998
|
{
|
|
77167
77999
|
"name": "Size in bytes",
|
|
77168
78000
|
"plug": "input",
|
|
77169
|
-
"type":
|
|
77170
|
-
"name": "*"
|
|
77171
|
-
},
|
|
78001
|
+
"type": "*",
|
|
77172
78002
|
"group": "Metadata",
|
|
77173
78003
|
"index": 3
|
|
77174
78004
|
},
|
|
77175
78005
|
{
|
|
77176
78006
|
"name": "Type",
|
|
77177
78007
|
"plug": "input",
|
|
77178
|
-
"type":
|
|
77179
|
-
"name": "*"
|
|
77180
|
-
},
|
|
78008
|
+
"type": "*",
|
|
77181
78009
|
"group": "Metadata",
|
|
77182
78010
|
"index": 4
|
|
77183
78011
|
},
|
|
77184
78012
|
{
|
|
77185
78013
|
"name": "Success",
|
|
77186
78014
|
"plug": "input",
|
|
77187
|
-
"type":
|
|
77188
|
-
"name": "*"
|
|
77189
|
-
},
|
|
78015
|
+
"type": "*",
|
|
77190
78016
|
"group": "Events",
|
|
77191
78017
|
"index": 5
|
|
77192
78018
|
}
|
|
@@ -77226,18 +78052,14 @@
|
|
|
77226
78052
|
{
|
|
77227
78053
|
"name": "Enable File Picker on click",
|
|
77228
78054
|
"plug": "output",
|
|
77229
|
-
"type":
|
|
77230
|
-
"name": "*"
|
|
77231
|
-
},
|
|
78055
|
+
"type": "*",
|
|
77232
78056
|
"group": "Dropzone",
|
|
77233
78057
|
"index": 0
|
|
77234
78058
|
},
|
|
77235
78059
|
{
|
|
77236
78060
|
"name": "Accepted file types",
|
|
77237
78061
|
"plug": "output",
|
|
77238
|
-
"type":
|
|
77239
|
-
"name": "*"
|
|
77240
|
-
},
|
|
78062
|
+
"type": "*",
|
|
77241
78063
|
"group": "Dropzone",
|
|
77242
78064
|
"index": 1
|
|
77243
78065
|
}
|
|
@@ -77269,54 +78091,42 @@
|
|
|
77269
78091
|
{
|
|
77270
78092
|
"name": "File",
|
|
77271
78093
|
"plug": "input",
|
|
77272
|
-
"type":
|
|
77273
|
-
"name": "*"
|
|
77274
|
-
},
|
|
78094
|
+
"type": "*",
|
|
77275
78095
|
"group": "General",
|
|
77276
78096
|
"index": 0
|
|
77277
78097
|
},
|
|
77278
78098
|
{
|
|
77279
78099
|
"name": "Path",
|
|
77280
78100
|
"plug": "input",
|
|
77281
|
-
"type":
|
|
77282
|
-
"name": "*"
|
|
77283
|
-
},
|
|
78101
|
+
"type": "*",
|
|
77284
78102
|
"group": "Metadata",
|
|
77285
78103
|
"index": 1
|
|
77286
78104
|
},
|
|
77287
78105
|
{
|
|
77288
78106
|
"name": "Name",
|
|
77289
78107
|
"plug": "input",
|
|
77290
|
-
"type":
|
|
77291
|
-
"name": "*"
|
|
77292
|
-
},
|
|
78108
|
+
"type": "*",
|
|
77293
78109
|
"group": "Metadata",
|
|
77294
78110
|
"index": 2
|
|
77295
78111
|
},
|
|
77296
78112
|
{
|
|
77297
78113
|
"name": "Size in bytes",
|
|
77298
78114
|
"plug": "input",
|
|
77299
|
-
"type":
|
|
77300
|
-
"name": "*"
|
|
77301
|
-
},
|
|
78115
|
+
"type": "*",
|
|
77302
78116
|
"group": "Metadata",
|
|
77303
78117
|
"index": 3
|
|
77304
78118
|
},
|
|
77305
78119
|
{
|
|
77306
78120
|
"name": "Type",
|
|
77307
78121
|
"plug": "input",
|
|
77308
|
-
"type":
|
|
77309
|
-
"name": "*"
|
|
77310
|
-
},
|
|
78122
|
+
"type": "*",
|
|
77311
78123
|
"group": "Metadata",
|
|
77312
78124
|
"index": 4
|
|
77313
78125
|
},
|
|
77314
78126
|
{
|
|
77315
78127
|
"name": "Success",
|
|
77316
78128
|
"plug": "input",
|
|
77317
|
-
"type":
|
|
77318
|
-
"name": "*"
|
|
77319
|
-
},
|
|
78129
|
+
"type": "*",
|
|
77320
78130
|
"group": "Events",
|
|
77321
78131
|
"index": 5
|
|
77322
78132
|
}
|
|
@@ -77773,108 +78583,84 @@
|
|
|
77773
78583
|
{
|
|
77774
78584
|
"name": "title",
|
|
77775
78585
|
"plug": "output",
|
|
77776
|
-
"type":
|
|
77777
|
-
"name": "*"
|
|
77778
|
-
},
|
|
78586
|
+
"type": "*",
|
|
77779
78587
|
"group": "General search engine",
|
|
77780
78588
|
"index": 1
|
|
77781
78589
|
},
|
|
77782
78590
|
{
|
|
77783
78591
|
"name": "description",
|
|
77784
78592
|
"plug": "output",
|
|
77785
|
-
"type":
|
|
77786
|
-
"name": "*"
|
|
77787
|
-
},
|
|
78593
|
+
"type": "*",
|
|
77788
78594
|
"group": "General search engine",
|
|
77789
78595
|
"index": 2
|
|
77790
78596
|
},
|
|
77791
78597
|
{
|
|
77792
78598
|
"name": "og:title",
|
|
77793
78599
|
"plug": "output",
|
|
77794
|
-
"type":
|
|
77795
|
-
"name": "*"
|
|
77796
|
-
},
|
|
78600
|
+
"type": "*",
|
|
77797
78601
|
"group": "Open Graph",
|
|
77798
78602
|
"index": 4
|
|
77799
78603
|
},
|
|
77800
78604
|
{
|
|
77801
78605
|
"name": "og:description",
|
|
77802
78606
|
"plug": "output",
|
|
77803
|
-
"type":
|
|
77804
|
-
"name": "*"
|
|
77805
|
-
},
|
|
78607
|
+
"type": "*",
|
|
77806
78608
|
"group": "Open Graph",
|
|
77807
78609
|
"index": 5
|
|
77808
78610
|
},
|
|
77809
78611
|
{
|
|
77810
78612
|
"name": "og:image",
|
|
77811
78613
|
"plug": "output",
|
|
77812
|
-
"type":
|
|
77813
|
-
"name": "*"
|
|
77814
|
-
},
|
|
78614
|
+
"type": "*",
|
|
77815
78615
|
"group": "Open Graph",
|
|
77816
78616
|
"index": 6
|
|
77817
78617
|
},
|
|
77818
78618
|
{
|
|
77819
78619
|
"name": "og:type",
|
|
77820
78620
|
"plug": "output",
|
|
77821
|
-
"type":
|
|
77822
|
-
"name": "*"
|
|
77823
|
-
},
|
|
78621
|
+
"type": "*",
|
|
77824
78622
|
"group": "Open Graph",
|
|
77825
78623
|
"index": 9
|
|
77826
78624
|
},
|
|
77827
78625
|
{
|
|
77828
78626
|
"name": "og:url (override default)",
|
|
77829
78627
|
"plug": "output",
|
|
77830
|
-
"type":
|
|
77831
|
-
"name": "*"
|
|
77832
|
-
},
|
|
78628
|
+
"type": "*",
|
|
77833
78629
|
"group": "Open Graph",
|
|
77834
78630
|
"index": 10
|
|
77835
78631
|
},
|
|
77836
78632
|
{
|
|
77837
78633
|
"name": "robots",
|
|
77838
78634
|
"plug": "output",
|
|
77839
|
-
"type":
|
|
77840
|
-
"name": "*"
|
|
77841
|
-
},
|
|
78635
|
+
"type": "*",
|
|
77842
78636
|
"group": "General search engine",
|
|
77843
78637
|
"index": 3
|
|
77844
78638
|
},
|
|
77845
78639
|
{
|
|
77846
78640
|
"name": "twitter:title",
|
|
77847
78641
|
"plug": "output",
|
|
77848
|
-
"type":
|
|
77849
|
-
"name": "*"
|
|
77850
|
-
},
|
|
78642
|
+
"type": "*",
|
|
77851
78643
|
"group": "Twitter",
|
|
77852
78644
|
"index": 11
|
|
77853
78645
|
},
|
|
77854
78646
|
{
|
|
77855
78647
|
"name": "twitter:description",
|
|
77856
78648
|
"plug": "output",
|
|
77857
|
-
"type":
|
|
77858
|
-
"name": "*"
|
|
77859
|
-
},
|
|
78649
|
+
"type": "*",
|
|
77860
78650
|
"group": "Twitter",
|
|
77861
78651
|
"index": 12
|
|
77862
78652
|
},
|
|
77863
78653
|
{
|
|
77864
78654
|
"name": "twitter:card",
|
|
77865
78655
|
"plug": "output",
|
|
77866
|
-
"type":
|
|
77867
|
-
"name": "*"
|
|
77868
|
-
},
|
|
78656
|
+
"type": "*",
|
|
77869
78657
|
"group": "Twitter",
|
|
77870
78658
|
"index": 13
|
|
77871
78659
|
},
|
|
77872
78660
|
{
|
|
77873
78661
|
"name": "twitter:image",
|
|
77874
78662
|
"plug": "output",
|
|
77875
|
-
"type":
|
|
77876
|
-
"name": "*"
|
|
77877
|
-
},
|
|
78663
|
+
"type": "*",
|
|
77878
78664
|
"group": "Twitter",
|
|
77879
78665
|
"index": 14
|
|
77880
78666
|
}
|
|
@@ -78381,17 +79167,13 @@
|
|
|
78381
79167
|
{
|
|
78382
79168
|
"name": "uploadRecordCreated",
|
|
78383
79169
|
"plug": "input",
|
|
78384
|
-
"type":
|
|
78385
|
-
"name": "*"
|
|
78386
|
-
},
|
|
79170
|
+
"type": "*",
|
|
78387
79171
|
"index": 1
|
|
78388
79172
|
},
|
|
78389
79173
|
{
|
|
78390
79174
|
"name": "absoluteFilePathCreated",
|
|
78391
79175
|
"plug": "input",
|
|
78392
|
-
"type":
|
|
78393
|
-
"name": "*"
|
|
78394
|
-
},
|
|
79176
|
+
"type": "*",
|
|
78395
79177
|
"index": 2
|
|
78396
79178
|
}
|
|
78397
79179
|
]
|
|
@@ -78949,25 +79731,19 @@
|
|
|
78949
79731
|
{
|
|
78950
79732
|
"name": "Html Content",
|
|
78951
79733
|
"plug": "input",
|
|
78952
|
-
"type":
|
|
78953
|
-
"name": "*"
|
|
78954
|
-
},
|
|
79734
|
+
"type": "*",
|
|
78955
79735
|
"index": 1
|
|
78956
79736
|
},
|
|
78957
79737
|
{
|
|
78958
79738
|
"name": "Changed",
|
|
78959
79739
|
"plug": "input",
|
|
78960
|
-
"type":
|
|
78961
|
-
"name": "*"
|
|
78962
|
-
},
|
|
79740
|
+
"type": "*",
|
|
78963
79741
|
"index": 2
|
|
78964
79742
|
},
|
|
78965
79743
|
{
|
|
78966
79744
|
"name": "Saved",
|
|
78967
79745
|
"plug": "input",
|
|
78968
|
-
"type":
|
|
78969
|
-
"name": "*"
|
|
78970
|
-
},
|
|
79746
|
+
"type": "*",
|
|
78971
79747
|
"index": 3
|
|
78972
79748
|
}
|
|
78973
79749
|
]
|
|
@@ -78979,17 +79755,13 @@
|
|
|
78979
79755
|
{
|
|
78980
79756
|
"name": "Initial Content",
|
|
78981
79757
|
"plug": "output",
|
|
78982
|
-
"type":
|
|
78983
|
-
"name": "*"
|
|
78984
|
-
},
|
|
79758
|
+
"type": "*",
|
|
78985
79759
|
"index": 1
|
|
78986
79760
|
},
|
|
78987
79761
|
{
|
|
78988
79762
|
"name": "Update Content",
|
|
78989
79763
|
"plug": "output",
|
|
78990
|
-
"type":
|
|
78991
|
-
"name": "*"
|
|
78992
|
-
},
|
|
79764
|
+
"type": "*",
|
|
78993
79765
|
"index": 2
|
|
78994
79766
|
}
|
|
78995
79767
|
]
|
|
@@ -79093,7 +79865,7 @@
|
|
|
79093
79865
|
{
|
|
79094
79866
|
"id": "community-tiptap-text-editor",
|
|
79095
79867
|
"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.",
|
|
79868
|
+
"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
79869
|
"demonstrates": [
|
|
79098
79870
|
"Boolean",
|
|
79099
79871
|
"CSS Definition",
|
|
@@ -79155,7 +79927,7 @@
|
|
|
79155
79927
|
"type": "For Each",
|
|
79156
79928
|
"parent": "menu",
|
|
79157
79929
|
"parameters": {
|
|
79158
|
-
"template": "
|
|
79930
|
+
"template": "/Tiptap text editor r"
|
|
79159
79931
|
}
|
|
79160
79932
|
},
|
|
79161
79933
|
{
|
|
@@ -79470,7 +80242,7 @@
|
|
|
79470
80242
|
},
|
|
79471
80243
|
{
|
|
79472
80244
|
"fromId": "setmodelproperties",
|
|
79473
|
-
"fromProperty": "
|
|
80245
|
+
"fromProperty": "done",
|
|
79474
80246
|
"toId": "model2",
|
|
79475
80247
|
"toProperty": "fetch"
|
|
79476
80248
|
},
|
|
@@ -79608,17 +80380,13 @@
|
|
|
79608
80380
|
{
|
|
79609
80381
|
"name": "buttonType",
|
|
79610
80382
|
"plug": "output",
|
|
79611
|
-
"type":
|
|
79612
|
-
"name": "*"
|
|
79613
|
-
},
|
|
80383
|
+
"type": "*",
|
|
79614
80384
|
"index": 1
|
|
79615
80385
|
},
|
|
79616
80386
|
{
|
|
79617
80387
|
"name": "editor",
|
|
79618
80388
|
"plug": "output",
|
|
79619
|
-
"type":
|
|
79620
|
-
"name": "*"
|
|
79621
|
-
},
|
|
80389
|
+
"type": "*",
|
|
79622
80390
|
"index": 2
|
|
79623
80391
|
}
|
|
79624
80392
|
]
|
|
@@ -79664,17 +80432,13 @@
|
|
|
79664
80432
|
{
|
|
79665
80433
|
"name": "Success",
|
|
79666
80434
|
"plug": "input",
|
|
79667
|
-
"type":
|
|
79668
|
-
"name": "*"
|
|
79669
|
-
},
|
|
80435
|
+
"type": "*",
|
|
79670
80436
|
"index": 1
|
|
79671
80437
|
},
|
|
79672
80438
|
{
|
|
79673
80439
|
"name": "buttonClicked",
|
|
79674
80440
|
"plug": "input",
|
|
79675
|
-
"type":
|
|
79676
|
-
"name": "*"
|
|
79677
|
-
},
|
|
80441
|
+
"type": "*",
|
|
79678
80442
|
"index": 2
|
|
79679
80443
|
}
|
|
79680
80444
|
]
|
|
@@ -79892,17 +80656,13 @@
|
|
|
79892
80656
|
{
|
|
79893
80657
|
"name": "videoURL",
|
|
79894
80658
|
"plug": "input",
|
|
79895
|
-
"type":
|
|
79896
|
-
"name": "*"
|
|
79897
|
-
},
|
|
80659
|
+
"type": "*",
|
|
79898
80660
|
"index": 1
|
|
79899
80661
|
},
|
|
79900
80662
|
{
|
|
79901
80663
|
"name": "uploadId",
|
|
79902
80664
|
"plug": "input",
|
|
79903
|
-
"type":
|
|
79904
|
-
"name": "*"
|
|
79905
|
-
},
|
|
80665
|
+
"type": "*",
|
|
79906
80666
|
"index": 2
|
|
79907
80667
|
}
|
|
79908
80668
|
]
|
|
@@ -79914,9 +80674,7 @@
|
|
|
79914
80674
|
{
|
|
79915
80675
|
"name": "assignmentId",
|
|
79916
80676
|
"plug": "output",
|
|
79917
|
-
"type":
|
|
79918
|
-
"name": "*"
|
|
79919
|
-
},
|
|
80677
|
+
"type": "*",
|
|
79920
80678
|
"index": 1
|
|
79921
80679
|
}
|
|
79922
80680
|
]
|
|
@@ -80590,7 +81348,7 @@
|
|
|
80590
81348
|
},
|
|
80591
81349
|
{
|
|
80592
81350
|
"fromId": "draft_object",
|
|
80593
|
-
"fromProperty": "title",
|
|
81351
|
+
"fromProperty": "prop-title",
|
|
80594
81352
|
"toId": "draft_title_text",
|
|
80595
81353
|
"toProperty": "text"
|
|
80596
81354
|
}
|
|
@@ -81056,7 +81814,7 @@
|
|
|
81056
81814
|
"parent": "row",
|
|
81057
81815
|
"parameters": {
|
|
81058
81816
|
"useLabel": true,
|
|
81059
|
-
"labelSpacing": "
|
|
81817
|
+
"labelSpacing": "var(--space-2)"
|
|
81060
81818
|
}
|
|
81061
81819
|
},
|
|
81062
81820
|
{
|
|
@@ -82718,7 +83476,7 @@
|
|
|
82718
83476
|
{
|
|
82719
83477
|
"id": "fn-aggregate-stats-function",
|
|
82720
83478
|
"title": "Server-side aggregation exposed as a cloud function",
|
|
82721
|
-
"description": "Aggregate Records runs
|
|
83479
|
+
"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
83480
|
"demonstrates": [
|
|
82723
83481
|
"noodl.cloud.aggregate",
|
|
82724
83482
|
"CloudFunction2",
|
|
@@ -82738,7 +83496,7 @@
|
|
|
82738
83496
|
},
|
|
82739
83497
|
"children": [
|
|
82740
83498
|
"refresh_btn",
|
|
82741
|
-
"
|
|
83499
|
+
"total_text"
|
|
82742
83500
|
]
|
|
82743
83501
|
},
|
|
82744
83502
|
{
|
|
@@ -82751,9 +83509,9 @@
|
|
|
82751
83509
|
}
|
|
82752
83510
|
},
|
|
82753
83511
|
{
|
|
82754
|
-
"id": "
|
|
83512
|
+
"id": "total_text",
|
|
82755
83513
|
"type": "Text",
|
|
82756
|
-
"label": "
|
|
83514
|
+
"label": "Total hours",
|
|
82757
83515
|
"parent": "root"
|
|
82758
83516
|
},
|
|
82759
83517
|
{
|
|
@@ -82774,8 +83532,8 @@
|
|
|
82774
83532
|
},
|
|
82775
83533
|
{
|
|
82776
83534
|
"fromId": "call_stats",
|
|
82777
|
-
"fromProperty": "out-
|
|
82778
|
-
"toId": "
|
|
83535
|
+
"fromProperty": "out-totalHours",
|
|
83536
|
+
"toId": "total_text",
|
|
82779
83537
|
"toProperty": "text"
|
|
82780
83538
|
}
|
|
82781
83539
|
]
|
|
@@ -82789,12 +83547,14 @@
|
|
|
82789
83547
|
"label": "Request"
|
|
82790
83548
|
},
|
|
82791
83549
|
{
|
|
82792
|
-
"id": "
|
|
83550
|
+
"id": "sum_hours",
|
|
82793
83551
|
"type": "noodl.cloud.aggregate",
|
|
82794
|
-
"label": "
|
|
83552
|
+
"label": "Sum task hours",
|
|
82795
83553
|
"parameters": {
|
|
82796
83554
|
"collectionName": "Task",
|
|
82797
|
-
"aggregates": "
|
|
83555
|
+
"aggregates": "totalHours",
|
|
83556
|
+
"aggprop-totalHours": "hours",
|
|
83557
|
+
"aggop-totalHours": "sum"
|
|
82798
83558
|
}
|
|
82799
83559
|
},
|
|
82800
83560
|
{
|
|
@@ -82802,7 +83562,7 @@
|
|
|
82802
83562
|
"type": "noodl.cloud.response",
|
|
82803
83563
|
"label": "Response",
|
|
82804
83564
|
"parameters": {
|
|
82805
|
-
"params": "
|
|
83565
|
+
"params": "totalHours"
|
|
82806
83566
|
}
|
|
82807
83567
|
},
|
|
82808
83568
|
{
|
|
@@ -82818,29 +83578,29 @@
|
|
|
82818
83578
|
{
|
|
82819
83579
|
"fromId": "fn_request",
|
|
82820
83580
|
"fromProperty": "receive",
|
|
82821
|
-
"toId": "
|
|
83581
|
+
"toId": "sum_hours",
|
|
82822
83582
|
"toProperty": "fetch"
|
|
82823
83583
|
},
|
|
82824
83584
|
{
|
|
82825
|
-
"fromId": "
|
|
82826
|
-
"fromProperty": "
|
|
83585
|
+
"fromId": "sum_hours",
|
|
83586
|
+
"fromProperty": "agg-totalHours",
|
|
82827
83587
|
"toId": "fn_response",
|
|
82828
|
-
"toProperty": "pm-
|
|
83588
|
+
"toProperty": "pm-totalHours"
|
|
82829
83589
|
},
|
|
82830
83590
|
{
|
|
82831
|
-
"fromId": "
|
|
83591
|
+
"fromId": "sum_hours",
|
|
82832
83592
|
"fromProperty": "fetched",
|
|
82833
83593
|
"toId": "fn_response",
|
|
82834
83594
|
"toProperty": "send"
|
|
82835
83595
|
},
|
|
82836
83596
|
{
|
|
82837
|
-
"fromId": "
|
|
83597
|
+
"fromId": "sum_hours",
|
|
82838
83598
|
"fromProperty": "error",
|
|
82839
83599
|
"toId": "fn_fail_response",
|
|
82840
83600
|
"toProperty": "errorMessage"
|
|
82841
83601
|
},
|
|
82842
83602
|
{
|
|
82843
|
-
"fromId": "
|
|
83603
|
+
"fromId": "sum_hours",
|
|
82844
83604
|
"fromProperty": "failure",
|
|
82845
83605
|
"toId": "fn_fail_response",
|
|
82846
83606
|
"toProperty": "send"
|
|
@@ -83243,7 +84003,7 @@
|
|
|
83243
84003
|
],
|
|
83244
84004
|
"components": [
|
|
83245
84005
|
{
|
|
83246
|
-
"name": "/Cloud Functions/Notify",
|
|
84006
|
+
"name": "/#__cloud__/Cloud Functions/Notify",
|
|
83247
84007
|
"nodes": [
|
|
83248
84008
|
{
|
|
83249
84009
|
"id": "fn_request",
|
|
@@ -92952,7 +93712,7 @@
|
|
|
92952
93712
|
{
|
|
92953
93713
|
"id": "user-login-flow",
|
|
92954
93714
|
"title": "Log in, show the user, log out",
|
|
92955
|
-
"description": "The authentication triangle: Log In takes `username`/`password` values and a `login` signal, firing `
|
|
93715
|
+
"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
93716
|
"demonstrates": [
|
|
92957
93717
|
"net.noodl.user.LogIn",
|
|
92958
93718
|
"net.noodl.user.LogOut",
|