Skip to content
65 changes: 65 additions & 0 deletions components/workflowy/actions/create-node/create-node.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import workflowy from "../../workflowy.app.mjs";
import { POSITIONS } from "../../common/constants.mjs";

export default {
key: "workflowy-create-node",
name: "Create Node",
description: "Creates a new bullet node in WorkFlowy via the beta API (POST /api/v1/nodes). Use this to add a top-level node or a child under an existing parent. To create a child node, first run **Search Nodes** to obtain a valid parent node ID and pass it as `parentNodeId`. Returns the newly created node's ID. [See the documentation](https://beta.workflowy.com/api-reference/#nodes-create).",
version: "0.0.1",
type: "action",
annotations: {
readOnlyHint: false,
destructiveHint: false,
openWorldHint: true,
},
props: {
workflowy,
name: {
propDefinition: [
workflowy,
"name",
],
},
note: {
propDefinition: [
workflowy,
"note",
],
},
parentNodeId: {
type: "string",
label: "Parent Node ID",
description: "Free-form parent node ID under which to create this node. Leave blank to create a top-level node. To find a valid ID, first run **Search Nodes** and copy the `id` of the desired parent. Also accepts special values: `None` (root), `inbox`, `calendar`, `today`, `tomorrow`, `next_week`, or a date like `YYYY-MM-DD`.",
optional: true,
},
layoutMode: {
propDefinition: [
workflowy,
"layoutMode",
],
},
position: {
type: "string",
label: "Position",
description: "Where to place the node among its siblings. One of `top` (default) or `bottom`.",
options: POSITIONS,
optional: true,
default: "top",
},
},
async run({ $ }) {
const response = await this.workflowy.createNode({
$,
data: {
name: this.name,
note: this.note,
parent_id: this.parentNodeId,
layoutMode: this.layoutMode,
position: this.position,
},
});
const nodeId = response?.item_id ?? "unknown";
$.export("$summary", `Created node "${this.name}" with ID ${nodeId}`);
return response;
},
};
52 changes: 52 additions & 0 deletions components/workflowy/actions/search-nodes/search-nodes.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import workflowy from "../../workflowy.app.mjs";

export default {
key: "workflowy-search-nodes",
name: "Search Nodes",
description: "Searches all WorkFlowy nodes by keyword. WorkFlowy has no dedicated search endpoint, so this exports all nodes (GET /api/v1/nodes-export) and filters client-side by matching the query against each node's name and note. Use this to discover node IDs before running **Create Node** (as a parent) or **Update Node**. Note: the export endpoint is rate limited to 1 request per minute. Returns matching nodes with their IDs and content. [See the documentation](https://beta.workflowy.com/api-reference/#nodes-export).",
version: "0.0.1",
type: "action",
annotations: {
readOnlyHint: true,
destructiveHint: false,
openWorldHint: true,
},
props: {
workflowy,
query: {
type: "string",
label: "Query",
description: "Keyword to match (case-insensitive) against each node's name and note.",
},
maxResults: {
type: "integer",
label: "Max Results",
description: "Maximum number of matching nodes to return. Min 1, max 1000. Defaults to 100.",
min: 1,
max: 1000,
default: 100,
optional: true,
},
},
async run({ $ }) {
const response = await this.workflowy.exportNodes({
$,
});
const nodes = response?.nodes ?? [];
const lowerQuery = String(this.query).toLowerCase();
const maxResults = this.maxResults ?? 100;

const matches = nodes
.filter((node) => {
const name = (node.name ?? "").toLowerCase();
const note = (node.note ?? "").toLowerCase();
return name.includes(lowerQuery) || note.includes(lowerQuery);
})
.slice(0, maxResults);

$.export("$summary", `Found ${matches.length} node${matches.length === 1
? ""
: "s"} matching "${this.query}"`);
return matches;
},
};
63 changes: 63 additions & 0 deletions components/workflowy/actions/update-node/update-node.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { ConfigurationError } from "@pipedream/platform";
import workflowy from "../../workflowy.app.mjs";

export default {
key: "workflowy-update-node",
name: "Update Node",
description: "Updates an existing WorkFlowy node's name, note, and/or layout mode (POST /api/v1/nodes/:id). Run **Search Nodes** first to obtain the target node ID. At least one of name, note, or layout mode must be provided. Note that the update endpoint only returns a status, not the full updated node. [See the documentation](https://beta.workflowy.com/api-reference/#nodes-update).",
version: "0.0.1",
type: "action",
annotations: {
readOnlyHint: false,
destructiveHint: false,
openWorldHint: true,
},
props: {
workflowy,
nodeId: {
type: "string",
label: "Node ID",
description: "The ID of the node to update. Run **Search Nodes** to find a valid node ID.",
},
name: {
propDefinition: [
workflowy,
"name",
],
description: "New main text for the node. Provide at least one of name, note, or layout mode.",
optional: true,
},
note: {
propDefinition: [
workflowy,
"note",
],
description: "New note (secondary text) for the node. Provide at least one of name, note, or layout mode.",
optional: true,
},
layoutMode: {
propDefinition: [
workflowy,
"layoutMode",
],
},
},
async run({ $ }) {
if (this.name === undefined && this.note === undefined && this.layoutMode === undefined) {
throw new ConfigurationError("At least one of Name, Note, or Layout Mode must be provided.");
}

const updatedNode = await this.workflowy.updateNode({
$,
nodeId: this.nodeId,
data: {
name: this.name,
note: this.note,
layoutMode: this.layoutMode,
},
});

$.export("$summary", `Updated node ${this.nodeId}`);
return updatedNode;
},
};
15 changes: 15 additions & 0 deletions components/workflowy/common/constants.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const BASE_URL = "https://beta.workflowy.com";
export const VERSION_PATH = "/api/v1";
export const LAYOUT_MODES = [
"bullets",
"todo",
"h1",
"h2",
"h3",
"code-block",
"quote-block",
];
export const POSITIONS = [
"top",
"bottom",
];
7 changes: 5 additions & 2 deletions components/workflowy/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pipedream/workflowy",
"version": "0.0.1",
"version": "0.1.0",
"description": "Pipedream WorkFlowy Components",
"main": "workflowy.app.mjs",
"keywords": [
Expand All @@ -11,5 +11,8 @@
"author": "Pipedream <support@pipedream.com> (https://pipedream.com/)",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@pipedream/platform": "^3.0.3"
}
}
}
71 changes: 67 additions & 4 deletions components/workflowy/workflowy.app.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,74 @@
import { axios } from "@pipedream/platform";
import {
BASE_URL,
LAYOUT_MODES,
VERSION_PATH,
} from "./common/constants.mjs";

export default {
type: "app",
app: "workflowy",
propDefinitions: {},
propDefinitions: {
name: {
type: "string",
label: "Name",
description: "The main text of the node.",
},
note: {
type: "string",
label: "Note",
description: "Optional note (secondary text) for the node.",
optional: true,
},
layoutMode: {
type: "string",
label: "Layout Mode",
description: "Optional display mode for the node. One of: `bullets`, `todo`, `h1`, `h2`, `h3`, `code-block`, `quote-block`.",
options: LAYOUT_MODES,
optional: true,
},
},
methods: {
// this.$auth contains connected account data
authKeys() {
console.log(Object.keys(this.$auth));
_makeRequest({
$ = this, method = "GET", path, params, data,
}) {
return axios($, {
method,
url: `${BASE_URL}${VERSION_PATH}${path}`,
headers: {
"Authorization": `Bearer ${this.$auth.api_key}`,
"Content-Type": "application/json",
},
params,
data,
});
},
createNode({
$, data,
}) {
return this._makeRequest({
$,
method: "POST",
path: "/nodes",
data,
});
},
updateNode({
$, nodeId, data,
}) {
return this._makeRequest({
$,
method: "POST",
path: `/nodes/${nodeId}`,
data,
});
},
exportNodes({ $ }) {
return this._makeRequest({
$,
method: "GET",
path: "/nodes-export",
});
},
},
};
6 changes: 5 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading