Skip to content

Commit e84cc2d

Browse files
committed
Merge main into migration-block-init-staging-branch
2 parents c0083ee + 6c66b05 commit e84cc2d

64 files changed

Lines changed: 2137 additions & 1371 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/roam-main.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ jobs:
2323
steps:
2424
- name: Checkout Code
2525
uses: actions/checkout@v6
26+
with:
27+
fetch-depth: 0
2628

2729
- name: Install pnpm
2830
uses: pnpm/action-setup@v4
@@ -53,3 +55,15 @@ jobs:
5355

5456
- name: Deploy
5557
run: npx turbo run deploy --filter=roam --ui stream -- --no-compile
58+
59+
- name: Get version
60+
id: version
61+
run: echo "version=$(node -p "require('./apps/roam/package.json').version")" >> $GITHUB_OUTPUT
62+
63+
- name: Sync Linear release
64+
uses: linear/linear-release-action@v0
65+
with:
66+
access_key: ${{ secrets.LINEAR_RELEASES_ACCESS_KEY }}
67+
name: Roam ${{ steps.version.outputs.version }}
68+
version: ${{ steps.version.outputs.version }}
69+
include_paths: "apps/roam/**,packages/tailwind-config/**,packages/utils/**,packages/database/**,packages/ui/**"

.github/workflows/roam-release.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ jobs:
1919
steps:
2020
- name: Checkout Code
2121
uses: actions/checkout@v6
22+
with:
23+
fetch-depth: 0
2224

2325
- name: Install pnpm
2426
uses: pnpm/action-setup@v4
@@ -42,6 +44,14 @@ jobs:
4244
id: version
4345
run: echo "version=$(node -p "require('./apps/roam/package.json').version")" >> $GITHUB_OUTPUT
4446

47+
- name: Sync Linear release
48+
uses: linear/linear-release-action@v0
49+
with:
50+
access_key: ${{ secrets.LINEAR_RELEASES_ACCESS_KEY }}
51+
name: Roam ${{ steps.version.outputs.version }}
52+
version: ${{ steps.version.outputs.version }}
53+
include_paths: "apps/roam/**,packages/tailwind-config/**,packages/utils/**,packages/database/**,packages/ui/**"
54+
4555
- name: Inject & upload source maps to PostHog
4656
uses: PostHog/upload-source-maps@v0.5.7.0
4757
with:
@@ -53,3 +63,11 @@ jobs:
5363

5464
- name: Update Roam Depot Extension
5565
run: npx turbo run publish --filter=roam --ui stream -- --no-compile
66+
67+
- name: Mark Linear release sent to Roam review
68+
uses: linear/linear-release-action@v0
69+
with:
70+
access_key: ${{ secrets.LINEAR_RELEASES_ACCESS_KEY }}
71+
command: update
72+
version: ${{ steps.version.outputs.version }}
73+
stage: Sent to Roam for Review

.github/workflows/test-database.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- name: Setup node
2222
uses: actions/setup-node@v4
2323
with:
24-
node-version: "20"
24+
node-version: "22"
2525
cache: "pnpm"
2626
- name: Install Dependencies
2727
run: pnpm install --frozen-lockfile

apps/obsidian/src/components/AdminPanelSettings.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import { useState, useCallback } from "react";
22
import { usePlugin } from "./PluginContext";
33
import { Notice } from "obsidian";
4+
import { updateUsername } from "~/utils/supabaseContext";
45
import { initializeSupabaseSync } from "~/utils/syncDgNodesToSupabase";
56

67
export const AdminPanelSettings = () => {
78
const plugin = usePlugin();
89
const [syncModeEnabled, setSyncModeEnabled] = useState<boolean>(
910
plugin.settings.syncModeEnabled ?? false,
1011
);
12+
const [username, setUsername] = useState<string>(
13+
plugin.settings.username || "",
14+
);
1115

1216
const handleSyncModeToggle = useCallback(
1317
async (newValue: boolean) => {
@@ -30,6 +34,13 @@ export const AdminPanelSettings = () => {
3034
[plugin],
3135
);
3236

37+
const handleSetUsername = async (newValue: string) => {
38+
setUsername(newValue);
39+
plugin.settings.username = newValue;
40+
await plugin.saveSettings();
41+
await updateUsername(plugin, newValue);
42+
};
43+
3344
return (
3445
<div className="general-settings">
3546
<div className="setting-item">
@@ -48,6 +59,27 @@ export const AdminPanelSettings = () => {
4859
</div>
4960
</div>
5061
</div>
62+
<div
63+
className={
64+
"setting-item " + (plugin.settings.syncModeEnabled ? "" : "hidden")
65+
}
66+
>
67+
<div className="setting-item-info">
68+
<div className="setting-item-name">Username</div>
69+
<div className="setting-item-description">
70+
A username that will be associated with your vault if you share
71+
data.
72+
</div>
73+
</div>
74+
<div className="setting-item-control">
75+
<input
76+
type="text"
77+
value={username}
78+
onChange={(e) => setUsername(e.target.value)}
79+
onBlur={(e) => void handleSetUsername(e.target.value)}
80+
/>
81+
</div>
82+
</div>
5183
</div>
5284
);
5385
};

apps/obsidian/src/components/DiscourseContextView.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ import { getDiscourseNodeFormatExpression } from "~/utils/getDiscourseNodeFormat
1313
import { RelationshipSection } from "~/components/RelationshipSection";
1414
import { VIEW_TYPE_DISCOURSE_CONTEXT } from "~/types";
1515
import { PluginProvider, usePlugin } from "~/components/PluginContext";
16-
import { getNodeTypeById, getAndFormatImportSource } from "~/utils/typeUtils";
16+
import {
17+
getNodeTypeById,
18+
getAndFormatImportSource,
19+
getUserNameById,
20+
} from "~/utils/typeUtils";
1721
import { refreshImportedFile } from "~/utils/importNodes";
1822
import { publishNode } from "~/utils/publishNode";
1923
import { createBaseForNodeType } from "~/utils/baseForNodeType";
@@ -161,6 +165,13 @@ const DiscourseContext = ({ activeFile }: DiscourseContextProps) => {
161165
!isImported &&
162166
!!frontmatter.nodeTypeId;
163167

168+
const formattedVaultName = isImported
169+
? getAndFormatImportSource(
170+
frontmatter.importedFromRid as string,
171+
plugin.settings.spaceNames,
172+
)
173+
: "";
174+
164175
return (
165176
<>
166177
<div className="mb-6">
@@ -232,7 +243,7 @@ const DiscourseContext = ({ activeFile }: DiscourseContextProps) => {
232243
View only
233244
</span>
234245
<InfoTooltip
235-
content={`Imported from ${getAndFormatImportSource(frontmatter.importedFromRid as string, plugin.settings.spaceNames)}. Direct edits will be overwritten when refreshed.`}
246+
content={`Imported from ${formattedVaultName}. Direct edits will be overwritten when refreshed.`}
236247
/>
237248
</div>
238249
)}
@@ -244,6 +255,15 @@ const DiscourseContext = ({ activeFile }: DiscourseContextProps) => {
244255
</div>
245256
)}
246257

258+
{isImported &&
259+
frontmatter.authorId &&
260+
typeof frontmatter.authorId === "number" && (
261+
<div className="text-modifier-text mt-2 text-xs">
262+
<div>
263+
Author: {getUserNameById(plugin, frontmatter.authorId)}
264+
</div>
265+
</div>
266+
)}
247267
{isImported && sourceDates && (
248268
<div className="text-modifier-text mt-2 text-xs">
249269
<div>Created in source: {sourceDates.createdAt}</div>

apps/obsidian/src/components/ImportNodesModal.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { createRoot, Root } from "react-dom/client";
33
import { StrictMode, useState, useEffect, useCallback } from "react";
44
import type DiscourseGraphPlugin from "../index";
55
import type { ImportableNode, GroupWithNodes } from "~/types";
6+
import { getUserNameById } from "~/utils/typeUtils";
67
import {
8+
fetchUserNames,
79
getAvailableGroupIds,
810
getPublishedNodesForGroups,
911
getLocalNodeInstanceIds,
@@ -61,6 +63,8 @@ const ImportNodesContent = ({ plugin, onClose }: ImportNodesModalProps) => {
6163
return;
6264
}
6365

66+
await fetchUserNames(plugin, client);
67+
6468
const publishedNodes = await getPublishedNodesForGroups({
6569
client,
6670
groupIds,
@@ -82,7 +86,7 @@ const ImportNodesContent = ({ plugin, onClose }: ImportNodesModalProps) => {
8286
getSpaceUris(client, uniqueSpaceIds),
8387
]);
8488

85-
// Populate plugin settings with current space names so they stay up to date
89+
// Keep spaceNames in settings up to date for UI display (formatImportSource reads it)
8690
if (uniqueSpaceIds.length > 0) {
8791
if (!plugin.settings.spaceNames) plugin.settings.spaceNames = {};
8892

@@ -106,21 +110,26 @@ const ImportNodesContent = ({ plugin, onClose }: ImportNodesModalProps) => {
106110
groupName:
107111
spaceNames.get(node.space_id) ?? `Space ${node.space_id}`,
108112
nodes: [],
113+
authorIds: new Set(),
109114
});
110115
}
111116

112117
const group = grouped.get(groupId)!;
118+
const spaceName =
119+
spaceNames.get(node.space_id) ?? `Space ${node.space_id}`;
113120
group.nodes.push({
114121
nodeInstanceId: node.source_local_id,
115122
title: node.text,
116123
spaceId: node.space_id,
117-
spaceName: spaceNames.get(node.space_id) ?? `Space ${node.space_id}`,
124+
spaceName,
118125
groupId,
119126
selected: false,
120127
createdAt: node.createdAt,
121128
modifiedAt: node.modifiedAt,
122129
filePath: node.filePath,
130+
authorId: node.authorId,
123131
});
132+
if (node.authorId) group.authorIds.add(node.authorId);
124133
}
125134

126135
setGroupsWithNodes(Array.from(grouped.values()));
@@ -258,6 +267,7 @@ const ImportNodesContent = ({ plugin, onClose }: ImportNodesModalProps) => {
258267
number,
259268
{
260269
spaceName: string;
270+
authorIds: Set<number>;
261271
nodes: Array<{
262272
node: ImportableNode;
263273
groupId: string;
@@ -271,6 +281,7 @@ const ImportNodesContent = ({ plugin, onClose }: ImportNodesModalProps) => {
271281
if (!nodesBySpace.has(node.spaceId)) {
272282
nodesBySpace.set(node.spaceId, {
273283
spaceName: node.spaceName,
284+
authorIds: group.authorIds,
274285
nodes: [],
275286
});
276287
}
@@ -322,14 +333,19 @@ const ImportNodesContent = ({ plugin, onClose }: ImportNodesModalProps) => {
322333

323334
<div className="max-h-96 overflow-y-auto rounded border">
324335
{Array.from(nodesBySpace.entries()).map(
325-
([spaceId, { spaceName, nodes }]) => {
336+
([spaceId, { spaceName, nodes, authorIds }]) => {
326337
return (
327338
<div key={spaceId} className="border-b">
328339
<div className="bg-muted/10 flex items-center px-3 py-2">
329340
<span className="mr-2">📂</span>
330341
<span className="text-accent-foreground line-clamp-1 font-medium italic">
331342
{spaceName}
332343
</span>
344+
{authorIds.size === 1 && (
345+
<span>
346+
&nbsp;({getUserNameById(plugin, [...authorIds][0]!)})
347+
</span>
348+
)}
333349
<span className="text-muted ml-2 text-sm">
334350
({nodes.length} node{nodes.length !== 1 ? "s" : ""})
335351
</span>
@@ -349,6 +365,11 @@ const ImportNodesContent = ({ plugin, onClose }: ImportNodesModalProps) => {
349365
<div className="min-w-0 flex-1">
350366
<div className="line-clamp-3 font-medium">
351367
{node.title}
368+
{node.authorId && authorIds.size > 1 && (
369+
<span className="font-light">
370+
&nbsp;({getUserNameById(plugin, node.authorId)})
371+
</span>
372+
)}
352373
</div>
353374
</div>
354375
</div>

apps/obsidian/src/components/InlineNodeTypePicker.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { Editor } from "obsidian";
1+
import { Editor, MarkdownView } from "obsidian";
22
import { DiscourseNode } from "~/types";
3-
import { createDiscourseNode } from "~/utils/createNode";
43
import type DiscourseGraphPlugin from "~/index";
4+
import { createModifyNodeModalSubmitHandler } from "~/utils/registerCommands";
5+
import ModifyNodeModal from "./ModifyNodeModal";
56

67
/**
78
* A popover that shows all node types inline near the cursor/selection.
@@ -98,7 +99,7 @@ export class InlineNodeTypePicker {
9899
itemEl.addEventListener("mousedown", (e) => {
99100
e.preventDefault();
100101
e.stopPropagation();
101-
void this.selectItem(item);
102+
this.selectItem(item);
102103
});
103104

104105
itemEl.addEventListener("mouseenter", () => {
@@ -138,14 +139,22 @@ export class InlineNodeTypePicker {
138139
}
139140
}
140141

141-
private async selectItem(item: DiscourseNode) {
142+
private selectItem(item: DiscourseNode) {
142143
this.close();
143-
await createDiscourseNode({
144+
const currentFile =
145+
this.options.plugin.app.workspace.getActiveViewOfType(MarkdownView)
146+
?.file || undefined;
147+
new ModifyNodeModal(this.options.plugin.app, {
148+
nodeTypes: this.options.plugin.settings.nodeTypes,
144149
plugin: this.options.plugin,
145-
nodeType: item,
146-
text: this.options.selectedText,
147-
editor: this.options.editor,
148-
});
150+
initialTitle: this.options.selectedText,
151+
initialNodeType: item,
152+
currentFile,
153+
onSubmit: createModifyNodeModalSubmitHandler(
154+
this.options.plugin,
155+
this.options.editor,
156+
),
157+
}).open();
149158
}
150159

151160
private setupEventHandlers() {
@@ -173,7 +182,7 @@ export class InlineNodeTypePicker {
173182
e.stopPropagation();
174183
const selectedItem = this.items[this.selectedIndex];
175184
if (selectedItem) {
176-
void this.selectItem(selectedItem);
185+
this.selectItem(selectedItem);
177186
}
178187
} else if (e.key === "Escape") {
179188
e.preventDefault();

0 commit comments

Comments
 (0)