roam-research-mcp 1.6.0 → 2.4.3
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 +202 -13
- package/build/Roam_Markdown_Cheatsheet.md +116 -269
- package/build/cli/batch/resolver.js +138 -0
- package/build/cli/batch/translator.js +363 -0
- package/build/cli/batch/types.js +4 -0
- package/build/cli/commands/batch.js +345 -0
- package/build/cli/commands/get.js +156 -43
- package/build/cli/commands/refs.js +63 -32
- package/build/cli/commands/rename.js +58 -0
- package/build/cli/commands/save.js +436 -63
- package/build/cli/commands/search.js +152 -31
- package/build/cli/commands/status.js +91 -0
- package/build/cli/commands/update.js +194 -0
- package/build/cli/roam.js +18 -1
- package/build/cli/utils/graph.js +56 -0
- package/build/cli/utils/input.js +10 -0
- package/build/cli/utils/output.js +34 -0
- package/build/config/environment.js +70 -34
- package/build/config/graph-registry.js +221 -0
- package/build/config/graph-registry.test.js +30 -0
- package/build/search/status-search.js +5 -4
- package/build/server/roam-server.js +98 -53
- package/build/shared/validation.js +10 -5
- package/build/tools/helpers/refs.js +50 -31
- package/build/tools/operations/blocks.js +38 -1
- package/build/tools/operations/memory.js +59 -9
- package/build/tools/operations/pages.js +186 -111
- package/build/tools/operations/search/index.js +5 -1
- package/build/tools/operations/todos.js +1 -1
- package/build/tools/schemas.js +123 -42
- package/build/tools/tool-handlers.js +9 -2
- package/build/utils/helpers.js +22 -0
- package/package.json +8 -5
|
@@ -9,40 +9,76 @@ const envPath = join(projectRoot, '.env');
|
|
|
9
9
|
if (existsSync(envPath)) {
|
|
10
10
|
dotenv.config({ path: envPath });
|
|
11
11
|
}
|
|
12
|
-
//
|
|
13
|
-
const
|
|
14
|
-
const GRAPH_NAME = process.env.ROAM_GRAPH_NAME;
|
|
15
|
-
// Validate environment variables
|
|
16
|
-
if (!API_TOKEN || !GRAPH_NAME) {
|
|
17
|
-
const missingVars = [];
|
|
18
|
-
if (!API_TOKEN)
|
|
19
|
-
missingVars.push('ROAM_API_TOKEN');
|
|
20
|
-
if (!GRAPH_NAME)
|
|
21
|
-
missingVars.push('ROAM_GRAPH_NAME');
|
|
22
|
-
throw new Error(`Missing required environment variables: ${missingVars.join(', ')}\n\n` +
|
|
23
|
-
'Please configure these variables either:\n' +
|
|
24
|
-
'1. In your MCP settings file:\n' +
|
|
25
|
-
' - For Cline: ~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json\n' +
|
|
26
|
-
' - For Claude: ~/Library/Application Support/Claude/claude_desktop_config.json\n\n' +
|
|
27
|
-
' Example configuration:\n' +
|
|
28
|
-
' {\n' +
|
|
29
|
-
' "mcpServers": {\n' +
|
|
30
|
-
' "roam-research": {\n' +
|
|
31
|
-
' "command": "node",\n' +
|
|
32
|
-
' "args": ["/path/to/roam-research-mcp/build/index.js"],\n' +
|
|
33
|
-
' "env": {\n' +
|
|
34
|
-
' "ROAM_API_TOKEN": "your-api-token",\n' +
|
|
35
|
-
' "ROAM_GRAPH_NAME": "your-graph-name"\n' +
|
|
36
|
-
' }\n' +
|
|
37
|
-
' }\n' +
|
|
38
|
-
' }\n' +
|
|
39
|
-
' }\n\n' +
|
|
40
|
-
'2. Or in a .env file in the roam-research directory:\n' +
|
|
41
|
-
' ROAM_API_TOKEN=your-api-token\n' +
|
|
42
|
-
' ROAM_GRAPH_NAME=your-graph-name');
|
|
43
|
-
}
|
|
44
|
-
const HTTP_STREAM_PORT = process.env.HTTP_STREAM_PORT || '8088'; // Default to 8088
|
|
12
|
+
// HTTP server configuration
|
|
13
|
+
const HTTP_STREAM_PORT = process.env.HTTP_STREAM_PORT || '8088';
|
|
45
14
|
const CORS_ORIGINS = (process.env.CORS_ORIGIN || 'http://localhost:5678,https://roamresearch.com')
|
|
46
15
|
.split(',')
|
|
47
16
|
.map(origin => origin.trim());
|
|
48
|
-
export
|
|
17
|
+
// Re-export for backwards compatibility with single-graph mode
|
|
18
|
+
// These are still used by CLI commands and for validation
|
|
19
|
+
const API_TOKEN = process.env.ROAM_API_TOKEN;
|
|
20
|
+
const GRAPH_NAME = process.env.ROAM_GRAPH_NAME;
|
|
21
|
+
// Multi-graph mode configuration
|
|
22
|
+
const ROAM_GRAPHS = process.env.ROAM_GRAPHS;
|
|
23
|
+
const ROAM_DEFAULT_GRAPH = process.env.ROAM_DEFAULT_GRAPH;
|
|
24
|
+
/**
|
|
25
|
+
* Check if we're in multi-graph mode
|
|
26
|
+
*/
|
|
27
|
+
export function isMultiGraphMode() {
|
|
28
|
+
return !!ROAM_GRAPHS;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validate that either multi-graph or single-graph configuration is provided
|
|
32
|
+
* Called during server/CLI initialization
|
|
33
|
+
*/
|
|
34
|
+
export function validateEnvironment() {
|
|
35
|
+
if (ROAM_GRAPHS) {
|
|
36
|
+
// Multi-graph mode
|
|
37
|
+
if (!ROAM_DEFAULT_GRAPH) {
|
|
38
|
+
throw new Error('ROAM_DEFAULT_GRAPH is required when using ROAM_GRAPHS.\n' +
|
|
39
|
+
'Set it to the key of the graph to use by default.');
|
|
40
|
+
}
|
|
41
|
+
// Validate JSON
|
|
42
|
+
try {
|
|
43
|
+
JSON.parse(ROAM_GRAPHS);
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
throw new Error(`Invalid JSON in ROAM_GRAPHS: ${e.message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// Single-graph mode - require legacy env vars
|
|
51
|
+
if (!API_TOKEN || !GRAPH_NAME) {
|
|
52
|
+
const missingVars = [];
|
|
53
|
+
if (!API_TOKEN)
|
|
54
|
+
missingVars.push('ROAM_API_TOKEN');
|
|
55
|
+
if (!GRAPH_NAME)
|
|
56
|
+
missingVars.push('ROAM_GRAPH_NAME');
|
|
57
|
+
throw new Error(`Missing required environment variables: ${missingVars.join(', ')}\n\n` +
|
|
58
|
+
'Please configure these variables either:\n' +
|
|
59
|
+
'1. In your MCP settings file:\n' +
|
|
60
|
+
' - For Cline: ~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json\n' +
|
|
61
|
+
' - For Claude: ~/Library/Application Support/Claude/claude_desktop_config.json\n\n' +
|
|
62
|
+
' Example configuration:\n' +
|
|
63
|
+
' {\n' +
|
|
64
|
+
' "mcpServers": {\n' +
|
|
65
|
+
' "roam-research": {\n' +
|
|
66
|
+
' "command": "node",\n' +
|
|
67
|
+
' "args": ["/path/to/roam-research-mcp/build/index.js"],\n' +
|
|
68
|
+
' "env": {\n' +
|
|
69
|
+
' "ROAM_API_TOKEN": "your-api-token",\n' +
|
|
70
|
+
' "ROAM_GRAPH_NAME": "your-graph-name"\n' +
|
|
71
|
+
' }\n' +
|
|
72
|
+
' }\n' +
|
|
73
|
+
' }\n' +
|
|
74
|
+
' }\n\n' +
|
|
75
|
+
'2. Or in a .env file in the roam-research directory:\n' +
|
|
76
|
+
' ROAM_API_TOKEN=your-api-token\n' +
|
|
77
|
+
' ROAM_GRAPH_NAME=your-graph-name\n\n' +
|
|
78
|
+
'3. Or use multi-graph mode:\n' +
|
|
79
|
+
' ROAM_GRAPHS=\'{"personal": {"token": "...", "graph": "..."}}\'\n' +
|
|
80
|
+
' ROAM_DEFAULT_GRAPH=personal');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export { API_TOKEN, GRAPH_NAME, HTTP_STREAM_PORT, CORS_ORIGINS, ROAM_GRAPHS, ROAM_DEFAULT_GRAPH };
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphRegistry - Manages multiple Roam graph connections with safety guardrails
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - Multiple graph configurations via ROAM_GRAPHS env var
|
|
6
|
+
* - Backwards compatibility with single graph via ROAM_API_TOKEN/ROAM_GRAPH_NAME
|
|
7
|
+
* - Write protection for non-default graphs via write_key confirmation
|
|
8
|
+
* - Lazy graph initialization (connects only when first accessed)
|
|
9
|
+
*/
|
|
10
|
+
import { initializeGraph } from '@roam-research/roam-api-sdk';
|
|
11
|
+
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
12
|
+
/** List of tool names that perform write operations */
|
|
13
|
+
export const WRITE_OPERATIONS = [
|
|
14
|
+
'roam_create_page',
|
|
15
|
+
'roam_create_outline',
|
|
16
|
+
'roam_import_markdown',
|
|
17
|
+
'roam_process_batch_actions',
|
|
18
|
+
'roam_add_todo',
|
|
19
|
+
'roam_remember',
|
|
20
|
+
'roam_create_table',
|
|
21
|
+
'roam_move_block',
|
|
22
|
+
'roam_update_page_markdown',
|
|
23
|
+
'roam_rename_page',
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Check if a tool name is a write operation
|
|
27
|
+
*/
|
|
28
|
+
export function isWriteOperation(toolName) {
|
|
29
|
+
return WRITE_OPERATIONS.includes(toolName);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* GraphRegistry - Central manager for multiple Roam graph connections
|
|
33
|
+
*/
|
|
34
|
+
export class GraphRegistry {
|
|
35
|
+
constructor(configs, defaultKey) {
|
|
36
|
+
this.configs = new Map(Object.entries(configs));
|
|
37
|
+
this.initialized = new Map();
|
|
38
|
+
this.defaultKey = defaultKey;
|
|
39
|
+
this.isMultiGraph = this.configs.size > 1;
|
|
40
|
+
// Validate default key exists
|
|
41
|
+
if (!this.configs.has(defaultKey)) {
|
|
42
|
+
throw new Error(`Default graph key "${defaultKey}" not found in configuration`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get configuration for a graph by key
|
|
47
|
+
*/
|
|
48
|
+
getConfig(key) {
|
|
49
|
+
return this.configs.get(key);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get all available graph keys
|
|
53
|
+
*/
|
|
54
|
+
getAvailableGraphs() {
|
|
55
|
+
return Array.from(this.configs.keys());
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get an initialized Graph instance, creating it lazily if needed
|
|
59
|
+
* @param key - Graph key from config. Defaults to defaultKey if not specified.
|
|
60
|
+
*/
|
|
61
|
+
getGraph(key) {
|
|
62
|
+
const resolvedKey = key ?? this.defaultKey;
|
|
63
|
+
// Check if already initialized
|
|
64
|
+
const existing = this.initialized.get(resolvedKey);
|
|
65
|
+
if (existing) {
|
|
66
|
+
return existing;
|
|
67
|
+
}
|
|
68
|
+
// Get config
|
|
69
|
+
const config = this.configs.get(resolvedKey);
|
|
70
|
+
if (!config) {
|
|
71
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown graph: "${resolvedKey}". Available graphs: ${this.getAvailableGraphs().join(', ')}`);
|
|
72
|
+
}
|
|
73
|
+
// Initialize the graph
|
|
74
|
+
const graph = initializeGraph({
|
|
75
|
+
token: config.token,
|
|
76
|
+
graph: config.graph,
|
|
77
|
+
});
|
|
78
|
+
this.initialized.set(resolvedKey, graph);
|
|
79
|
+
return graph;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Check if a write operation is allowed for a given graph
|
|
83
|
+
*
|
|
84
|
+
* Rules:
|
|
85
|
+
* - Writes to default graph are always allowed
|
|
86
|
+
* - Writes to non-default graphs require:
|
|
87
|
+
* - If write_key is configured: must provide matching write_key
|
|
88
|
+
* - If no write_key configured: writes are allowed
|
|
89
|
+
*/
|
|
90
|
+
isWriteAllowed(graphKey, providedWriteKey) {
|
|
91
|
+
const resolvedKey = graphKey ?? this.defaultKey;
|
|
92
|
+
// Writes to default graph are always allowed
|
|
93
|
+
if (resolvedKey === this.defaultKey) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
const config = this.configs.get(resolvedKey);
|
|
97
|
+
if (!config) {
|
|
98
|
+
return false; // Unknown graph
|
|
99
|
+
}
|
|
100
|
+
// If no write_key is configured, allow writes
|
|
101
|
+
if (!config.write_key) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
// Check if provided key matches
|
|
105
|
+
return providedWriteKey === config.write_key;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Validate write access and return an informative error if denied
|
|
109
|
+
*/
|
|
110
|
+
validateWriteAccess(toolName, graphKey, providedWriteKey) {
|
|
111
|
+
if (!isWriteOperation(toolName)) {
|
|
112
|
+
return; // Not a write operation, no validation needed
|
|
113
|
+
}
|
|
114
|
+
const resolvedKey = graphKey ?? this.defaultKey;
|
|
115
|
+
if (!this.isWriteAllowed(resolvedKey, providedWriteKey)) {
|
|
116
|
+
const config = this.configs.get(resolvedKey);
|
|
117
|
+
if (!config) {
|
|
118
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown graph: "${resolvedKey}". Available graphs: ${this.getAvailableGraphs().join(', ')}`);
|
|
119
|
+
}
|
|
120
|
+
// Provide informative error with the required key
|
|
121
|
+
throw new McpError(ErrorCode.InvalidParams, `Write to "${resolvedKey}" graph requires write_key confirmation.\n` +
|
|
122
|
+
`Provide write_key: "${config.write_key}" to proceed.`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Resolve graph key and validate access for a tool call
|
|
127
|
+
* Returns the Graph instance ready to use
|
|
128
|
+
*/
|
|
129
|
+
resolveGraphForTool(toolName, graphKey, writeKey) {
|
|
130
|
+
// Validate write access if this is a write operation
|
|
131
|
+
this.validateWriteAccess(toolName, graphKey, writeKey);
|
|
132
|
+
// Return the graph instance
|
|
133
|
+
return this.getGraph(graphKey);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Generate markdown documentation about available graphs and their configuration
|
|
137
|
+
* Used to inform AI models about graph access requirements
|
|
138
|
+
*/
|
|
139
|
+
getGraphInfoMarkdown() {
|
|
140
|
+
const graphKeys = this.getAvailableGraphs();
|
|
141
|
+
// Single graph mode - minimal info
|
|
142
|
+
if (graphKeys.length === 1 && graphKeys[0] === 'default') {
|
|
143
|
+
return ''; // No need to show graph info in single-graph mode
|
|
144
|
+
}
|
|
145
|
+
const lines = [
|
|
146
|
+
'## Available Graphs',
|
|
147
|
+
'',
|
|
148
|
+
'| Graph | Default | Write Protected |',
|
|
149
|
+
'|-------|---------|-----------------|',
|
|
150
|
+
];
|
|
151
|
+
for (const key of graphKeys) {
|
|
152
|
+
const config = this.configs.get(key);
|
|
153
|
+
const isDefault = key === this.defaultKey;
|
|
154
|
+
const isProtected = !!config.write_key;
|
|
155
|
+
const defaultCol = isDefault ? '✓' : '';
|
|
156
|
+
const protectedCol = isProtected
|
|
157
|
+
? `Yes (requires \`write_key: "${config.write_key}"\`)`
|
|
158
|
+
: 'No';
|
|
159
|
+
lines.push(`| ${key} | ${defaultCol} | ${protectedCol} |`);
|
|
160
|
+
}
|
|
161
|
+
lines.push('');
|
|
162
|
+
lines.push('> **Note:** Write operations to protected graphs require the `write_key` parameter.');
|
|
163
|
+
lines.push('');
|
|
164
|
+
lines.push('---');
|
|
165
|
+
lines.push('');
|
|
166
|
+
return lines.join('\n');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Create a GraphRegistry from environment variables
|
|
171
|
+
*
|
|
172
|
+
* Supports two modes:
|
|
173
|
+
* 1. Multi-graph: ROAM_GRAPHS JSON + ROAM_DEFAULT_GRAPH
|
|
174
|
+
* 2. Single graph (backwards compat): ROAM_API_TOKEN + ROAM_GRAPH_NAME
|
|
175
|
+
*/
|
|
176
|
+
export function createRegistryFromEnv() {
|
|
177
|
+
const roamGraphsJson = process.env.ROAM_GRAPHS;
|
|
178
|
+
if (roamGraphsJson) {
|
|
179
|
+
// Multi-graph mode
|
|
180
|
+
try {
|
|
181
|
+
const configs = JSON.parse(roamGraphsJson);
|
|
182
|
+
const defaultKey = process.env.ROAM_DEFAULT_GRAPH?.trim().replace(/,+$/, '');
|
|
183
|
+
if (!defaultKey) {
|
|
184
|
+
throw new Error('ROAM_DEFAULT_GRAPH is required when using ROAM_GRAPHS');
|
|
185
|
+
}
|
|
186
|
+
return new GraphRegistry(configs, defaultKey);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
if (error instanceof SyntaxError) {
|
|
190
|
+
throw new Error(`Invalid JSON in ROAM_GRAPHS: ${error.message}`);
|
|
191
|
+
}
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Backwards compatibility: single graph mode
|
|
196
|
+
const token = process.env.ROAM_API_TOKEN;
|
|
197
|
+
const graphName = process.env.ROAM_GRAPH_NAME;
|
|
198
|
+
if (!token || !graphName) {
|
|
199
|
+
const missingVars = [];
|
|
200
|
+
if (!token)
|
|
201
|
+
missingVars.push('ROAM_API_TOKEN');
|
|
202
|
+
if (!graphName)
|
|
203
|
+
missingVars.push('ROAM_GRAPH_NAME');
|
|
204
|
+
throw new Error(`Missing required environment variables: ${missingVars.join(', ')}\n\n` +
|
|
205
|
+
'Configure either:\n' +
|
|
206
|
+
'1. Multi-graph mode:\n' +
|
|
207
|
+
' ROAM_GRAPHS=\'{"personal": {"token": "...", "graph": "..."}}\'\n' +
|
|
208
|
+
' ROAM_DEFAULT_GRAPH=personal\n\n' +
|
|
209
|
+
'2. Single graph mode (backwards compatible):\n' +
|
|
210
|
+
' ROAM_API_TOKEN=your-api-token\n' +
|
|
211
|
+
' ROAM_GRAPH_NAME=your-graph-name');
|
|
212
|
+
}
|
|
213
|
+
// Create single-graph registry with "default" as the key
|
|
214
|
+
const configs = {
|
|
215
|
+
default: {
|
|
216
|
+
token,
|
|
217
|
+
graph: graphName,
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
return new GraphRegistry(configs, 'default');
|
|
221
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { GraphRegistry } from './graph-registry.js';
|
|
3
|
+
describe('GraphRegistry', () => {
|
|
4
|
+
describe('getGraphInfoMarkdown', () => {
|
|
5
|
+
it('returns empty string for single-graph mode with default key', () => {
|
|
6
|
+
const registry = new GraphRegistry({ default: { token: 'token', graph: 'graph' } }, 'default');
|
|
7
|
+
expect(registry.getGraphInfoMarkdown()).toBe('');
|
|
8
|
+
});
|
|
9
|
+
it('returns markdown table for multi-graph mode', () => {
|
|
10
|
+
const registry = new GraphRegistry({
|
|
11
|
+
personal: { token: 'token1', graph: 'personal-graph' },
|
|
12
|
+
work: { token: 'token2', graph: 'work-graph', write_key: 'confirm' },
|
|
13
|
+
}, 'personal');
|
|
14
|
+
const markdown = registry.getGraphInfoMarkdown();
|
|
15
|
+
expect(markdown).toContain('## Available Graphs');
|
|
16
|
+
expect(markdown).toContain('| personal | ✓ | No |');
|
|
17
|
+
expect(markdown).toContain('| work | | Yes (requires `write_key: "confirm"`) |');
|
|
18
|
+
expect(markdown).toContain('> **Note:** Write operations to protected graphs');
|
|
19
|
+
});
|
|
20
|
+
it('shows write protection for default graph if configured', () => {
|
|
21
|
+
const registry = new GraphRegistry({
|
|
22
|
+
main: { token: 'token1', graph: 'main-graph', write_key: 'secret' },
|
|
23
|
+
backup: { token: 'token2', graph: 'backup-graph' },
|
|
24
|
+
}, 'main');
|
|
25
|
+
const markdown = registry.getGraphInfoMarkdown();
|
|
26
|
+
expect(markdown).toContain('| main | ✓ | Yes (requires `write_key: "secret"`) |');
|
|
27
|
+
expect(markdown).toContain('| backup | | No |');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -17,14 +17,15 @@ export class StatusSearchHandler extends BaseSearchHandler {
|
|
|
17
17
|
// Build query based on whether we're searching in a specific page
|
|
18
18
|
let queryStr;
|
|
19
19
|
let queryParams;
|
|
20
|
+
// Search for "{{TODO" or "{{DONE" which matches both {{[[TODO]]}} and {{TODO}} formats
|
|
20
21
|
if (targetPageUid) {
|
|
21
22
|
queryStr = `[:find ?block-uid ?block-str
|
|
22
23
|
:in $ ?status ?page-uid
|
|
23
24
|
:where [?p :block/uid ?page-uid]
|
|
24
25
|
[?b :block/page ?p]
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
[?b :block/string ?block-str]
|
|
27
|
+
[?b :block/uid ?block-uid]
|
|
28
|
+
[(clojure.string/includes? ?block-str (str "{{" ?status))]]`;
|
|
28
29
|
queryParams = [status, targetPageUid];
|
|
29
30
|
}
|
|
30
31
|
else {
|
|
@@ -34,7 +35,7 @@ export class StatusSearchHandler extends BaseSearchHandler {
|
|
|
34
35
|
[?b :block/uid ?block-uid]
|
|
35
36
|
[?b :block/page ?p]
|
|
36
37
|
[?p :node/title ?page-title]
|
|
37
|
-
[(clojure.string/includes? ?block-str (str "{{
|
|
38
|
+
[(clojure.string/includes? ?block-str (str "{{" ?status))]]`;
|
|
38
39
|
queryParams = [status];
|
|
39
40
|
}
|
|
40
41
|
const rawResults = await q(this.graph, queryStr, queryParams);
|