@opencode-ai/ui 0.0.0-next-15523 → 0.0.0-next-15527
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/dist/context/marked-code-span.d.ts +10 -0
- package/dist/v2/components/icon.d.ts +8 -0
- package/dist/v2/components/line-comment-v2.d.ts +4 -0
- package/dist/v2/components/split-button-v2.d.ts +5 -0
- package/package.json +2 -2
- package/src/components/tabs.css +101 -0
- package/src/context/marked-code-span.ts +17 -0
- package/src/context/marked.tsx +2 -0
- package/src/v2/components/file-tree-v2.css +18 -1
- package/src/v2/components/icon.tsx +8 -0
- package/src/v2/components/line-comment-v2.css +53 -0
- package/src/v2/components/line-comment-v2.tsx +151 -2
- package/src/v2/components/project-avatar-v2.tsx +2 -3
- package/src/v2/components/split-button-v2.css +99 -0
- package/src/v2/components/split-button-v2.tsx +48 -0
|
@@ -88,6 +88,10 @@ declare const icons: {
|
|
|
88
88
|
viewBox: string;
|
|
89
89
|
body: string;
|
|
90
90
|
};
|
|
91
|
+
"outline-xmark": {
|
|
92
|
+
viewBox: string;
|
|
93
|
+
body: string;
|
|
94
|
+
};
|
|
91
95
|
"outline-chevron-down": {
|
|
92
96
|
viewBox: string;
|
|
93
97
|
body: string;
|
|
@@ -128,6 +132,10 @@ declare const icons: {
|
|
|
128
132
|
viewBox: string;
|
|
129
133
|
body: string;
|
|
130
134
|
};
|
|
135
|
+
"outline-share": {
|
|
136
|
+
viewBox: string;
|
|
137
|
+
body: string;
|
|
138
|
+
};
|
|
131
139
|
reset: {
|
|
132
140
|
viewBox: string;
|
|
133
141
|
body: string;
|
|
@@ -11,6 +11,9 @@ export interface LineCommentV2Props extends ComponentProps<"div"> {
|
|
|
11
11
|
actions?: JSX.Element;
|
|
12
12
|
}
|
|
13
13
|
export declare function LineCommentV2(props: LineCommentV2Props): JSX.Element;
|
|
14
|
+
export type LineCommentEditorV2Mention = {
|
|
15
|
+
items: (query: string) => string[] | Promise<string[]>;
|
|
16
|
+
};
|
|
14
17
|
export interface LineCommentEditorV2Props extends Omit<ComponentProps<"div">, "children" | "onInput" | "onSubmit"> {
|
|
15
18
|
/** Visible field label above the textarea (default: “Comment”). */
|
|
16
19
|
heading?: JSX.Element | string;
|
|
@@ -24,5 +27,6 @@ export interface LineCommentEditorV2Props extends Omit<ComponentProps<"div">, "c
|
|
|
24
27
|
cancelLabel?: string;
|
|
25
28
|
submitLabel?: string;
|
|
26
29
|
autofocus?: boolean;
|
|
30
|
+
mention?: LineCommentEditorV2Mention;
|
|
27
31
|
}
|
|
28
32
|
export declare function LineCommentEditorV2(props: LineCommentEditorV2Props): JSX.Element;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type ComponentProps, type ParentProps } from "solid-js";
|
|
2
|
+
import "./split-button-v2.css";
|
|
3
|
+
export declare function SplitButtonV2(props: ParentProps<ComponentProps<"div">>): import("solid-js").JSX.Element;
|
|
4
|
+
export declare function SplitButtonV2Action(props: ComponentProps<"button">): import("solid-js").JSX.Element;
|
|
5
|
+
export declare function SplitButtonV2MenuTrigger(props: ComponentProps<"button">): import("solid-js").JSX.Element;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opencode-ai/ui",
|
|
3
|
-
"version": "0.0.0-next-
|
|
3
|
+
"version": "0.0.0-next-15527",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
"fuzzysort": "3.1.0",
|
|
136
136
|
"katex": "0.16.27",
|
|
137
137
|
"luxon": "3.6.1",
|
|
138
|
-
"marked": "17.0.
|
|
138
|
+
"marked": "17.0.6",
|
|
139
139
|
"marked-katex-extension": "5.1.6",
|
|
140
140
|
"marked-shiki": "1.2.1",
|
|
141
141
|
"morphdom": "2.7.8",
|
package/src/components/tabs.css
CHANGED
|
@@ -698,3 +698,104 @@ body[data-new-layout] #terminal-panel [data-component="tabs"][data-variant="norm
|
|
|
698
698
|
}
|
|
699
699
|
}
|
|
700
700
|
}
|
|
701
|
+
|
|
702
|
+
body[data-new-layout] #review-panel [data-component="tabs"][data-variant="normal"][data-orientation="horizontal"] {
|
|
703
|
+
[data-slot="tabs-list"] {
|
|
704
|
+
height: 52px;
|
|
705
|
+
padding-right: 12px;
|
|
706
|
+
gap: 8px;
|
|
707
|
+
--tabs-review-fade: 8px;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
[data-slot="tabs-trigger-wrapper"] {
|
|
711
|
+
height: 28px;
|
|
712
|
+
padding-inline: 10px;
|
|
713
|
+
border: 0;
|
|
714
|
+
background-color: transparent;
|
|
715
|
+
box-shadow: none;
|
|
716
|
+
|
|
717
|
+
&::after {
|
|
718
|
+
display: none;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
[data-slot="tabs-trigger"] {
|
|
722
|
+
padding: 0 !important;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
&:has([data-slot="tabs-trigger-close-button"]) {
|
|
726
|
+
padding-right: 10px;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
&:has([data-selected]) {
|
|
730
|
+
background-color: var(--v2-background-bg-layer-02);
|
|
731
|
+
box-shadow: none;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
[data-slot="tabs-trigger"] {
|
|
736
|
+
font-family: Inter, sans-serif;
|
|
737
|
+
font-style: normal;
|
|
738
|
+
font-weight: 440;
|
|
739
|
+
font-size: 13px;
|
|
740
|
+
line-height: 16px;
|
|
741
|
+
letter-spacing: -0.04px;
|
|
742
|
+
color: var(--v2-text-text-muted);
|
|
743
|
+
font-variation-settings: "slnt" 0;
|
|
744
|
+
font-variant-numeric: tabular-nums;
|
|
745
|
+
|
|
746
|
+
&[data-selected] {
|
|
747
|
+
color: var(--v2-text-text-base);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
.tab-fileicon-color,
|
|
751
|
+
.tab-fileicon-mono,
|
|
752
|
+
[data-slot="icon-svg"] {
|
|
753
|
+
margin-left: -2px;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/* Preview/temporary tabs: beat trigger + .text-14-medium upright defaults */
|
|
758
|
+
[data-slot="tabs-trigger"] .italic {
|
|
759
|
+
font-style: italic !important;
|
|
760
|
+
font-variation-settings: "slnt" -10 !important;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
body[data-new-layout] #review-panel [data-component="tabs"] .session-review-v2-tabs-bar {
|
|
765
|
+
width: 100%;
|
|
766
|
+
min-width: 0;
|
|
767
|
+
gap: 8px;
|
|
768
|
+
box-sizing: border-box;
|
|
769
|
+
border-bottom: 1px solid var(--border-weaker-base, var(--v2-border-border-weak));
|
|
770
|
+
background-color: var(--v2-background-bg-base);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
body[data-new-layout] #review-panel [data-component="tabs"] .session-review-v2-tabs-bar [data-slot="tabs-list"],
|
|
774
|
+
body[data-new-layout]
|
|
775
|
+
#review-panel
|
|
776
|
+
[data-component="tabs"][data-variant="normal"][data-orientation="horizontal"]
|
|
777
|
+
.session-review-v2-tabs-bar
|
|
778
|
+
[data-slot="tabs-list"] {
|
|
779
|
+
flex: 1 1 0;
|
|
780
|
+
min-width: 0;
|
|
781
|
+
width: auto;
|
|
782
|
+
border-bottom: none;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
body[data-new-layout]
|
|
786
|
+
#review-panel
|
|
787
|
+
[data-component="tabs"]
|
|
788
|
+
.session-review-v2-tabs-bar
|
|
789
|
+
[data-slot="tabs-list"]
|
|
790
|
+
> .sticky {
|
|
791
|
+
padding-right: 0;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
body[data-new-layout] #review-panel [data-component="tabs"] .session-review-v2-open-in-app-slot {
|
|
795
|
+
flex-shrink: 0;
|
|
796
|
+
margin-left: auto;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
body[data-new-layout] #review-panel [data-component="tabs"] .session-review-v2-open-in-app {
|
|
800
|
+
flex-shrink: 0;
|
|
801
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { MarkedExtension } from "marked"
|
|
2
|
+
|
|
3
|
+
// Keep adjacent tilde and backtick runs separate until markedjs/marked#4011 is released.
|
|
4
|
+
export const markedCodeSpanBoundary = {
|
|
5
|
+
tokenizer: {
|
|
6
|
+
inlineText(src) {
|
|
7
|
+
const match = /^(`+(?=~)|~+(?=`))/.exec(src)
|
|
8
|
+
if (!match) return false
|
|
9
|
+
return {
|
|
10
|
+
type: "text",
|
|
11
|
+
raw: match[0],
|
|
12
|
+
text: match[0],
|
|
13
|
+
escaped: this.lexer.state.inRawBlock,
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
} satisfies MarkedExtension
|
package/src/context/marked.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import markedShiki from "marked-shiki"
|
|
|
3
3
|
import katex from "katex"
|
|
4
4
|
import { bundledLanguages, type BundledLanguage } from "shiki"
|
|
5
5
|
import { createSimpleContext } from "./helper"
|
|
6
|
+
import { markedCodeSpanBoundary } from "./marked-code-span"
|
|
6
7
|
import { getSharedHighlighter, registerCustomTheme, ThemeRegistrationResolved } from "@pierre/diffs"
|
|
7
8
|
|
|
8
9
|
export const OpenCodeTheme = {
|
|
@@ -521,6 +522,7 @@ export const { use: useMarked, provider: MarkedProvider } = createSimpleContext(
|
|
|
521
522
|
name: "Marked",
|
|
522
523
|
init: (props: { nativeParser?: NativeMarkdownParser }) => {
|
|
523
524
|
const jsParser = marked.use(
|
|
525
|
+
markedCodeSpanBoundary,
|
|
524
526
|
{
|
|
525
527
|
renderer: {
|
|
526
528
|
link({ href, title, text }) {
|
|
@@ -14,12 +14,14 @@
|
|
|
14
14
|
justify-content: flex-start;
|
|
15
15
|
gap: 6px;
|
|
16
16
|
padding-right: 8px;
|
|
17
|
+
overflow: visible;
|
|
17
18
|
border: none;
|
|
18
19
|
border-radius: 6px;
|
|
19
20
|
background-color: transparent;
|
|
20
21
|
color: var(--v2-text-text-muted);
|
|
21
22
|
text-align: left;
|
|
22
23
|
cursor: pointer;
|
|
24
|
+
scroll-margin-block: 8px;
|
|
23
25
|
transition:
|
|
24
26
|
background-color 120ms ease,
|
|
25
27
|
color 120ms ease;
|
|
@@ -42,6 +44,21 @@
|
|
|
42
44
|
background-color: var(--v2-overlay-simple-overlay-pressed);
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
[data-component="file-tree-v2"] [data-slot="file-tree-v2-guide"] {
|
|
48
|
+
position: absolute;
|
|
49
|
+
top: 0;
|
|
50
|
+
margin-top: -2px;
|
|
51
|
+
height: calc(100% + 2px);
|
|
52
|
+
width: 1px;
|
|
53
|
+
pointer-events: none;
|
|
54
|
+
background-color: var(--v2-border-border-muted);
|
|
55
|
+
opacity: 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
[data-component="file-tree-v2"]:hover [data-slot="file-tree-v2-guide"] {
|
|
59
|
+
opacity: 0.5;
|
|
60
|
+
}
|
|
61
|
+
|
|
45
62
|
[data-component="file-tree-v2"] .filetree-icon--mono {
|
|
46
63
|
color: var(--v2-icon-icon-muted);
|
|
47
64
|
}
|
|
@@ -108,7 +125,7 @@
|
|
|
108
125
|
}
|
|
109
126
|
|
|
110
127
|
[data-component="file-tree-v2"] [data-slot="file-tree-v2-change"][data-change="modified"] {
|
|
111
|
-
|
|
128
|
+
color: var(--v2-state-fg-info);
|
|
112
129
|
}
|
|
113
130
|
|
|
114
131
|
[data-component="file-tree-v2"] [data-slot="file-tree-v2-change"][data-change="added"] {
|
|
@@ -89,6 +89,10 @@ const icons = {
|
|
|
89
89
|
viewBox: "0 0 16 16",
|
|
90
90
|
body: `<path d="M4.25 11.75L11.75 4.25M11.75 11.75L4.25 4.25" stroke="currentColor"/>`,
|
|
91
91
|
},
|
|
92
|
+
"outline-xmark": {
|
|
93
|
+
viewBox: "0 0 16 16",
|
|
94
|
+
body: `<path fill-rule="evenodd" clip-rule="evenodd" d="M4.99487 5.70186L7.29297 7.99995L4.99487 10.2981L5.70198 11.0052L8.00008 8.70706L10.2982 11.0052L11.0053 10.2981L8.70718 7.99995L11.0053 5.70186L10.2982 4.99475L8.00008 7.29285L5.70198 4.99475L4.99487 5.70186Z" fill="currentColor"/>`,
|
|
95
|
+
},
|
|
92
96
|
"outline-chevron-down": {
|
|
93
97
|
viewBox: "0 0 16 16",
|
|
94
98
|
body: `<path d="M5 6.5L8 9.5L11 6.5" stroke="currentColor"/>`,
|
|
@@ -129,6 +133,10 @@ const icons = {
|
|
|
129
133
|
viewBox: "0 0 16 16",
|
|
130
134
|
body: `<path d="M13.5555 6.66656V2.44434H9.33326M13.5555 2.44434L7.99993 7.99989M13.5555 9.33324V13.5555C13.5555 13.5555 12.7599 13.5555 11.7777 13.5555H2.44438C2.44438 13.5555 2.44438 12.7599 2.44438 11.7777V4.22213C2.44438 3.2399 2.44434 2.44435 2.44434 2.44435H6.66661" stroke="currentColor"/>`,
|
|
131
135
|
},
|
|
136
|
+
"outline-share": {
|
|
137
|
+
viewBox: "0 0 16 16",
|
|
138
|
+
body: `<path d="M13.5554 10.4445V13.5556C13.5554 13.5556 12.7599 13.5556 11.7777 13.5556H4.22211C3.23989 13.5556 2.44434 13.5556 2.44434 13.5556V10.4445M4.88878 5.55557L7.99989 2.44446L11.111 5.55557M7.99989 2.44446L7.99989 9.11112" stroke="currentColor"/>`,
|
|
139
|
+
},
|
|
132
140
|
reset: {
|
|
133
141
|
viewBox: "0 0 20 20",
|
|
134
142
|
body: `<path d="M5.83333 4.16406L2.5 7.4974L5.83333 10.8307M3.33333 7.4974H17.9167V15.4141H10" stroke="currentColor" stroke-linecap="square"/>`,
|
|
@@ -203,3 +203,56 @@
|
|
|
203
203
|
gap: 8px;
|
|
204
204
|
flex-shrink: 0;
|
|
205
205
|
}
|
|
206
|
+
|
|
207
|
+
[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-list"] {
|
|
208
|
+
display: flex;
|
|
209
|
+
flex-direction: column;
|
|
210
|
+
gap: 4px;
|
|
211
|
+
max-height: 180px;
|
|
212
|
+
overflow: auto;
|
|
213
|
+
margin-top: 8px;
|
|
214
|
+
padding: 4px;
|
|
215
|
+
border: 0.5px solid var(--v2-border-border-muted);
|
|
216
|
+
border-radius: 6px;
|
|
217
|
+
background: var(--v2-background-bg-layer-02);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-item"] {
|
|
221
|
+
display: flex;
|
|
222
|
+
align-items: center;
|
|
223
|
+
gap: 8px;
|
|
224
|
+
width: 100%;
|
|
225
|
+
min-width: 0;
|
|
226
|
+
padding: 6px 8px;
|
|
227
|
+
border: 0;
|
|
228
|
+
border-radius: 4px;
|
|
229
|
+
background: transparent;
|
|
230
|
+
color: var(--v2-text-text-base);
|
|
231
|
+
text-align: left;
|
|
232
|
+
cursor: pointer;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-item"][data-active] {
|
|
236
|
+
background: var(--v2-background-bg-layer-03);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-path"] {
|
|
240
|
+
display: flex;
|
|
241
|
+
align-items: center;
|
|
242
|
+
min-width: 0;
|
|
243
|
+
font-size: 12px;
|
|
244
|
+
line-height: 16px;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-dir"] {
|
|
248
|
+
min-width: 0;
|
|
249
|
+
color: var(--v2-text-text-muted);
|
|
250
|
+
white-space: nowrap;
|
|
251
|
+
overflow: hidden;
|
|
252
|
+
text-overflow: ellipsis;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
[data-component="line-comment-v2"][data-variant="editor"] [data-slot="line-comment-v2-mention-file"] {
|
|
256
|
+
color: var(--v2-text-text-base);
|
|
257
|
+
white-space: nowrap;
|
|
258
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { For, Show, createSignal, onMount, splitProps, type ComponentProps, type JSX } from "solid-js"
|
|
2
|
+
import { FileIcon } from "../../components/file-icon"
|
|
3
|
+
import { useFilteredList } from "../../hooks"
|
|
2
4
|
import { ButtonV2 } from "./button-v2"
|
|
3
5
|
import "./line-comment-v2.css"
|
|
4
6
|
|
|
@@ -53,6 +55,10 @@ export function LineCommentV2(props: LineCommentV2Props) {
|
|
|
53
55
|
)
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
export type LineCommentEditorV2Mention = {
|
|
59
|
+
items: (query: string) => string[] | Promise<string[]>
|
|
60
|
+
}
|
|
61
|
+
|
|
56
62
|
export interface LineCommentEditorV2Props extends Omit<ComponentProps<"div">, "children" | "onInput" | "onSubmit"> {
|
|
57
63
|
/** Visible field label above the textarea (default: “Comment”). */
|
|
58
64
|
heading?: JSX.Element | string
|
|
@@ -66,10 +72,22 @@ export interface LineCommentEditorV2Props extends Omit<ComponentProps<"div">, "c
|
|
|
66
72
|
cancelLabel?: string
|
|
67
73
|
submitLabel?: string
|
|
68
74
|
autofocus?: boolean
|
|
75
|
+
mention?: LineCommentEditorV2Mention
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function pathFilename(path: string) {
|
|
79
|
+
const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"))
|
|
80
|
+
return index === -1 ? path : path.slice(index + 1)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function pathDirectory(path: string) {
|
|
84
|
+
const index = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"))
|
|
85
|
+
return index === -1 ? "" : path.slice(0, index + 1)
|
|
69
86
|
}
|
|
70
87
|
|
|
71
88
|
export function LineCommentEditorV2(props: LineCommentEditorV2Props) {
|
|
72
89
|
let textareaRef: HTMLTextAreaElement | undefined
|
|
90
|
+
const [mentionOpen, setMentionOpen] = createSignal(false)
|
|
73
91
|
|
|
74
92
|
const [local, rest] = splitProps(props, [
|
|
75
93
|
"heading",
|
|
@@ -83,6 +101,7 @@ export function LineCommentEditorV2(props: LineCommentEditorV2Props) {
|
|
|
83
101
|
"cancelLabel",
|
|
84
102
|
"submitLabel",
|
|
85
103
|
"autofocus",
|
|
104
|
+
"mention",
|
|
86
105
|
"class",
|
|
87
106
|
"classList",
|
|
88
107
|
])
|
|
@@ -90,6 +109,78 @@ export function LineCommentEditorV2(props: LineCommentEditorV2Props) {
|
|
|
90
109
|
const heading = () => local.heading ?? "Comment"
|
|
91
110
|
const canSubmit = () => local.value.trim().length > 0
|
|
92
111
|
|
|
112
|
+
const closeMention = () => {
|
|
113
|
+
setMentionOpen(false)
|
|
114
|
+
mention.clear()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const currentMention = () => {
|
|
118
|
+
const textarea = textareaRef
|
|
119
|
+
if (!textarea) return
|
|
120
|
+
if (!local.mention) return
|
|
121
|
+
if (textarea.selectionStart !== textarea.selectionEnd) return
|
|
122
|
+
|
|
123
|
+
const end = textarea.selectionStart
|
|
124
|
+
const match = textarea.value.slice(0, end).match(/@(\S*)$/)
|
|
125
|
+
if (!match) return
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
query: match[1] ?? "",
|
|
129
|
+
start: end - match[0].length,
|
|
130
|
+
end,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function selectMention(item: { path: string } | undefined) {
|
|
135
|
+
if (!item) return
|
|
136
|
+
|
|
137
|
+
const textarea = textareaRef
|
|
138
|
+
const query = currentMention()
|
|
139
|
+
if (!textarea || !query) return
|
|
140
|
+
|
|
141
|
+
const value = `${textarea.value.slice(0, query.start)}@${item.path} ${textarea.value.slice(query.end)}`
|
|
142
|
+
const cursor = query.start + item.path.length + 2
|
|
143
|
+
|
|
144
|
+
local.onInput(value)
|
|
145
|
+
closeMention()
|
|
146
|
+
|
|
147
|
+
requestAnimationFrame(() => {
|
|
148
|
+
textarea.focus()
|
|
149
|
+
textarea.setSelectionRange(cursor, cursor)
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const mention = useFilteredList<{ path: string }>({
|
|
154
|
+
items: async (query) => {
|
|
155
|
+
if (!local.mention) return []
|
|
156
|
+
if (!query.trim()) return []
|
|
157
|
+
const paths = await local.mention.items(query)
|
|
158
|
+
return paths.map((path) => ({ path }))
|
|
159
|
+
},
|
|
160
|
+
key: (item) => item.path,
|
|
161
|
+
filterKeys: ["path"],
|
|
162
|
+
skipFilter: () => true,
|
|
163
|
+
onSelect: selectMention,
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
const syncMention = () => {
|
|
167
|
+
const item = currentMention()
|
|
168
|
+
if (!item) {
|
|
169
|
+
closeMention()
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
setMentionOpen(true)
|
|
174
|
+
mention.onInput(item.query)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const selectActiveMention = () => {
|
|
178
|
+
const items = mention.flat()
|
|
179
|
+
if (items.length === 0) return
|
|
180
|
+
const active = mention.active()
|
|
181
|
+
selectMention(items.find((item) => item.path === active) ?? items[0])
|
|
182
|
+
}
|
|
183
|
+
|
|
93
184
|
const submit = () => {
|
|
94
185
|
const v = local.value.trim()
|
|
95
186
|
if (!v) return
|
|
@@ -122,9 +213,39 @@ export function LineCommentEditorV2(props: LineCommentEditorV2Props) {
|
|
|
122
213
|
rows={local.rows ?? 3}
|
|
123
214
|
placeholder={local.placeholder ?? "Add context for this change"}
|
|
124
215
|
value={local.value}
|
|
125
|
-
onInput={(e) =>
|
|
216
|
+
onInput={(e) => {
|
|
217
|
+
local.onInput(e.currentTarget.value)
|
|
218
|
+
syncMention()
|
|
219
|
+
}}
|
|
220
|
+
onClick={() => syncMention()}
|
|
221
|
+
onSelect={() => syncMention()}
|
|
126
222
|
onKeyDown={(e) => {
|
|
127
223
|
e.stopPropagation()
|
|
224
|
+
if (e.isComposing || e.keyCode === 229) return
|
|
225
|
+
|
|
226
|
+
if (mentionOpen()) {
|
|
227
|
+
if (e.key === "Escape") {
|
|
228
|
+
e.preventDefault()
|
|
229
|
+
closeMention()
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (e.key === "Tab") {
|
|
234
|
+
if (mention.flat().length === 0) return
|
|
235
|
+
e.preventDefault()
|
|
236
|
+
selectActiveMention()
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const nav = e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "Enter"
|
|
241
|
+
const ctrlNav = e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey && (e.key === "n" || e.key === "p")
|
|
242
|
+
if ((nav || ctrlNav) && mention.flat().length > 0) {
|
|
243
|
+
mention.onKeyDown(e)
|
|
244
|
+
e.preventDefault()
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
128
249
|
if (e.key === "Escape") {
|
|
129
250
|
e.preventDefault()
|
|
130
251
|
e.currentTarget.blur()
|
|
@@ -137,6 +258,34 @@ export function LineCommentEditorV2(props: LineCommentEditorV2Props) {
|
|
|
137
258
|
}
|
|
138
259
|
}}
|
|
139
260
|
/>
|
|
261
|
+
<Show when={mentionOpen() && mention.flat().length > 0}>
|
|
262
|
+
<div data-slot="line-comment-v2-mention-list">
|
|
263
|
+
<For each={mention.flat().slice(0, 10)}>
|
|
264
|
+
{(item) => {
|
|
265
|
+
const directory = item.path.endsWith("/") ? item.path : pathDirectory(item.path)
|
|
266
|
+
const name = item.path.endsWith("/") ? "" : pathFilename(item.path)
|
|
267
|
+
return (
|
|
268
|
+
<button
|
|
269
|
+
type="button"
|
|
270
|
+
data-slot="line-comment-v2-mention-item"
|
|
271
|
+
data-active={mention.active() === item.path ? "" : undefined}
|
|
272
|
+
onMouseDown={(event) => event.preventDefault()}
|
|
273
|
+
onMouseEnter={() => mention.setActive(item.path)}
|
|
274
|
+
onClick={() => selectMention(item)}
|
|
275
|
+
>
|
|
276
|
+
<FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-4" />
|
|
277
|
+
<div data-slot="line-comment-v2-mention-path">
|
|
278
|
+
<span data-slot="line-comment-v2-mention-dir">{directory}</span>
|
|
279
|
+
<Show when={name}>
|
|
280
|
+
<span data-slot="line-comment-v2-mention-file">{name}</span>
|
|
281
|
+
</Show>
|
|
282
|
+
</div>
|
|
283
|
+
</button>
|
|
284
|
+
)
|
|
285
|
+
}}
|
|
286
|
+
</For>
|
|
287
|
+
</div>
|
|
288
|
+
</Show>
|
|
140
289
|
</div>
|
|
141
290
|
<div data-slot="line-comment-v2-footer">
|
|
142
291
|
<div data-slot="line-comment-v2-footer-meta">{local.selection}</div>
|
|
@@ -38,7 +38,6 @@ export interface ProjectAvatarProps extends ComponentProps<"div"> {
|
|
|
38
38
|
|
|
39
39
|
export function ProjectAvatar(props: ProjectAvatarProps) {
|
|
40
40
|
const [split, rest] = splitProps(props, ["fallback", "src", "variant", "unread", "class", "classList", "style"])
|
|
41
|
-
const src = split.src
|
|
42
41
|
return (
|
|
43
42
|
<div
|
|
44
43
|
{...rest}
|
|
@@ -53,9 +52,9 @@ export function ProjectAvatar(props: ProjectAvatarProps) {
|
|
|
53
52
|
<div
|
|
54
53
|
data-slot="project-avatar-surface"
|
|
55
54
|
data-variant={split.variant ?? "gray"}
|
|
56
|
-
data-has-image={src ? "" : undefined}
|
|
55
|
+
data-has-image={split.src ? "" : undefined}
|
|
57
56
|
>
|
|
58
|
-
<Show when={src} fallback={first(split.fallback)}>
|
|
57
|
+
<Show when={split.src} fallback={first(split.fallback)}>
|
|
59
58
|
{(value) => <img src={value()} draggable={false} data-slot="project-avatar-image" />}
|
|
60
59
|
</Show>
|
|
61
60
|
</div>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
[data-component="split-button-v2"] {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
align-items: stretch;
|
|
4
|
+
flex-shrink: 0;
|
|
5
|
+
height: 28px;
|
|
6
|
+
border-radius: 6px;
|
|
7
|
+
background-color: transparent;
|
|
8
|
+
box-shadow: none;
|
|
9
|
+
overflow: hidden;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
[data-component="split-button-v2"]:is(:hover, :has([data-component="split-button-v2-menu-trigger"][data-expanded])) {
|
|
13
|
+
box-shadow: inset 0 0 0 1px var(--v2-border-border-muted);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
[data-component="split-button-v2-action"],
|
|
17
|
+
[data-component="split-button-v2-menu-trigger"] {
|
|
18
|
+
display: inline-flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
height: 28px;
|
|
22
|
+
margin: 0;
|
|
23
|
+
padding: 0;
|
|
24
|
+
border: none;
|
|
25
|
+
background: transparent;
|
|
26
|
+
color: var(--v2-icon-icon-base);
|
|
27
|
+
cursor: default;
|
|
28
|
+
outline: none;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
[data-component="split-button-v2-action"] {
|
|
32
|
+
width: 28px;
|
|
33
|
+
flex-shrink: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
[data-component="split-button-v2-action"]:hover:not(:disabled) {
|
|
37
|
+
background-color: var(--v2-overlay-simple-overlay-hover);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
[data-component="split-button-v2-action"]:is(:active, [data-pressed]):not(:disabled) {
|
|
41
|
+
background-color: var(--v2-overlay-simple-overlay-pressed);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
[data-component="split-button-v2-action"]:is(:focus-visible, [data-focus]) {
|
|
45
|
+
outline: 2px solid var(--v2-border-border-focus);
|
|
46
|
+
outline-offset: -2px;
|
|
47
|
+
z-index: 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
[data-component="split-button-v2-action"]:disabled {
|
|
51
|
+
opacity: 0.5;
|
|
52
|
+
cursor: not-allowed;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
[data-component="split-button-v2-menu-trigger"] {
|
|
56
|
+
width: 20px;
|
|
57
|
+
flex-shrink: 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
[data-component="split-button-v2-menu-trigger"]:is(:hover, [data-expanded]):not(:disabled) {
|
|
61
|
+
background-color: var(--v2-overlay-simple-overlay-hover);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
[data-component="split-button-v2-menu-trigger"]:is(:active, [data-pressed]):not(:disabled) {
|
|
65
|
+
background-color: var(--v2-overlay-simple-overlay-pressed);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
[data-component="split-button-v2-menu-trigger"]:is(:focus-visible, [data-focus]) {
|
|
69
|
+
outline: 2px solid var(--v2-border-border-focus);
|
|
70
|
+
outline-offset: -2px;
|
|
71
|
+
z-index: 1;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
[data-component="split-button-v2-menu-trigger"]:disabled {
|
|
75
|
+
opacity: 0.5;
|
|
76
|
+
cursor: not-allowed;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
[data-component="split-button-v2-action"] [data-component="app-icon"] {
|
|
80
|
+
width: 16px;
|
|
81
|
+
height: 16px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
[data-component="menu-v2-content"].open-in-app-v2-menu [data-component="app-icon"] {
|
|
85
|
+
width: 16px;
|
|
86
|
+
height: 16px;
|
|
87
|
+
flex-shrink: 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
[data-component="menu-v2-content"].open-in-app-v2-menu [data-component="icon"] {
|
|
91
|
+
width: 16px;
|
|
92
|
+
height: 16px;
|
|
93
|
+
flex-shrink: 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
[data-component="menu-v2-content"].open-in-app-v2-menu [data-component="icon"] [data-slot="icon-svg"] {
|
|
97
|
+
width: 16px;
|
|
98
|
+
height: 16px;
|
|
99
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { splitProps, type ComponentProps, type ParentProps } from "solid-js"
|
|
2
|
+
import "./split-button-v2.css"
|
|
3
|
+
|
|
4
|
+
export function SplitButtonV2(props: ParentProps<ComponentProps<"div">>) {
|
|
5
|
+
const [split, rest] = splitProps(props, ["class", "classList", "children"])
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
data-component="split-button-v2"
|
|
9
|
+
classList={{
|
|
10
|
+
...split.classList,
|
|
11
|
+
[split.class ?? ""]: !!split.class,
|
|
12
|
+
}}
|
|
13
|
+
{...rest}
|
|
14
|
+
>
|
|
15
|
+
{split.children}
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function SplitButtonV2Action(props: ComponentProps<"button">) {
|
|
21
|
+
const [split, rest] = splitProps(props, ["class", "classList"])
|
|
22
|
+
return (
|
|
23
|
+
<button
|
|
24
|
+
type="button"
|
|
25
|
+
data-component="split-button-v2-action"
|
|
26
|
+
classList={{
|
|
27
|
+
...split.classList,
|
|
28
|
+
[split.class ?? ""]: !!split.class,
|
|
29
|
+
}}
|
|
30
|
+
{...rest}
|
|
31
|
+
/>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function SplitButtonV2MenuTrigger(props: ComponentProps<"button">) {
|
|
36
|
+
const [split, rest] = splitProps(props, ["class", "classList"])
|
|
37
|
+
return (
|
|
38
|
+
<button
|
|
39
|
+
type="button"
|
|
40
|
+
data-component="split-button-v2-menu-trigger"
|
|
41
|
+
classList={{
|
|
42
|
+
...split.classList,
|
|
43
|
+
[split.class ?? ""]: !!split.class,
|
|
44
|
+
}}
|
|
45
|
+
{...rest}
|
|
46
|
+
/>
|
|
47
|
+
)
|
|
48
|
+
}
|