fix padding
This commit is contained in:
parent
9c022b9cb3
commit
53e2ab8b1f
1 changed files with 241 additions and 217 deletions
|
|
@ -73,7 +73,10 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||||
if (input.mode === "last-turn") {
|
if (input.mode === "last-turn") {
|
||||||
const sessionID = input.sessionID
|
const sessionID = input.sessionID
|
||||||
if (!sessionID) return []
|
if (!sessionID) return []
|
||||||
const result = await props.api.client.session.diff({ sessionID, messageID: input.messageID }, { throwOnError: true })
|
const result = await props.api.client.session.diff(
|
||||||
|
{ sessionID, messageID: input.messageID },
|
||||||
|
{ throwOnError: true },
|
||||||
|
)
|
||||||
return normalizeDiffs(result.data ?? [])
|
return normalizeDiffs(result.data ?? [])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,7 +124,10 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||||
const highlighted = highlightedFileNode()
|
const highlighted = highlightedFileNode()
|
||||||
if (highlighted !== undefined && fileRows().some((row) => row.id === highlighted)) return
|
if (highlighted !== undefined && fileRows().some((row) => row.id === highlighted)) return
|
||||||
const lastHighlighted = lastHighlightedFileNode()
|
const lastHighlighted = lastHighlightedFileNode()
|
||||||
const next = lastHighlighted !== undefined && fileRows().some((row) => row.id === lastHighlighted) ? lastHighlighted : fileRows()[0]?.id
|
const next =
|
||||||
|
lastHighlighted !== undefined && fileRows().some((row) => row.id === lastHighlighted)
|
||||||
|
? lastHighlighted
|
||||||
|
: fileRows()[0]?.id
|
||||||
setHighlightedFileNode(next)
|
setHighlightedFileNode(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,7 +136,8 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||||
if (node !== undefined) setLastHighlightedFileNode(node)
|
if (node !== undefined) setLastHighlightedFileNode(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
const moveFileSelection = (offset: number) => setHighlighted(moveFileTreeSelection(fileRows(), highlightedFileNode(), offset))
|
const moveFileSelection = (offset: number) =>
|
||||||
|
setHighlighted(moveFileTreeSelection(fileRows(), highlightedFileNode(), offset))
|
||||||
|
|
||||||
const clearFileTreePatchState = () => {
|
const clearFileTreePatchState = () => {
|
||||||
setHighlightedFileNode(undefined)
|
setHighlightedFileNode(undefined)
|
||||||
|
|
@ -182,14 +189,17 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||||
|
|
||||||
const jumpRelativePatchFile = (offset: number) => {
|
const jumpRelativePatchFile = (offset: number) => {
|
||||||
const current = focus() === "files" ? highlightedFileNode() : undefined
|
const current = focus() === "files" ? highlightedFileNode() : undefined
|
||||||
const nextFromSelection = current === undefined ? undefined : moveFileTreeSelectionToFile(fileRows(), current, offset)
|
const nextFromSelection =
|
||||||
|
current === undefined ? undefined : moveFileTreeSelectionToFile(fileRows(), current, offset)
|
||||||
if (nextFromSelection !== undefined) {
|
if (nextFromSelection !== undefined) {
|
||||||
jumpToFileIndex(fileRows().find((row) => row.id === nextFromSelection)?.fileIndex)
|
jumpToFileIndex(fileRows().find((row) => row.id === nextFromSelection)?.fileIndex)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const currentFileIndex = activePatchFileIndex() ?? currentPatchFileIndex()
|
const currentFileIndex = activePatchFileIndex() ?? currentPatchFileIndex()
|
||||||
const currentRow = fileRows().find((row) => row.fileIndex === currentFileIndex)
|
const currentRow = fileRows().find((row) => row.fileIndex === currentFileIndex)
|
||||||
scrollToFileIndex(fileRows().find((row) => row.id === moveFileTreeSelectionToFile(fileRows(), currentRow?.id, offset))?.fileIndex)
|
scrollToFileIndex(
|
||||||
|
fileRows().find((row) => row.id === moveFileTreeSelectionToFile(fileRows(), currentRow?.id, offset))?.fileIndex,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const highlightedPatchFileIndex = () => fileRows().find((row) => row.id === highlightedFileNode())?.fileIndex
|
const highlightedPatchFileIndex = () => fileRows().find((row) => row.id === highlightedFileNode())?.fileIndex
|
||||||
|
|
@ -243,166 +253,170 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||||
props.api.route.navigate("home")
|
props.api.route.navigate("home")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "diff.down",
|
name: "diff.down",
|
||||||
title: "Move diff viewer down",
|
title: "Move diff viewer down",
|
||||||
category: "VCS",
|
category: "VCS",
|
||||||
run: focusRunner({
|
run: focusRunner({
|
||||||
files() {
|
files() {
|
||||||
moveFileSelection(1)
|
moveFileSelection(1)
|
||||||
},
|
|
||||||
patches() {
|
|
||||||
clearFileTreePatchState()
|
|
||||||
scroll?.scrollBy(1)
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "diff.up",
|
|
||||||
title: "Move diff viewer up",
|
|
||||||
category: "VCS",
|
|
||||||
run: focusRunner({
|
|
||||||
files() {
|
|
||||||
moveFileSelection(-1)
|
|
||||||
},
|
|
||||||
patches() {
|
|
||||||
clearFileTreePatchState()
|
|
||||||
scroll?.scrollBy(-1)
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "diff.page.down",
|
|
||||||
title: "Page diff viewer down",
|
|
||||||
category: "VCS",
|
|
||||||
run: focusRunner({
|
|
||||||
files() {
|
|
||||||
moveFileSelection(8)
|
|
||||||
},
|
|
||||||
patches() {
|
|
||||||
clearFileTreePatchState()
|
|
||||||
if (scroll) scroll.scrollBy(scroll.height)
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "diff.page.up",
|
|
||||||
title: "Page diff viewer up",
|
|
||||||
category: "VCS",
|
|
||||||
run: focusRunner({
|
|
||||||
files() {
|
|
||||||
moveFileSelection(-8)
|
|
||||||
},
|
|
||||||
patches() {
|
|
||||||
clearFileTreePatchState()
|
|
||||||
if (scroll) scroll.scrollBy(-scroll.height)
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "diff.toggle",
|
|
||||||
title: "Toggle diff viewer item",
|
|
||||||
category: "VCS",
|
|
||||||
run: focusRunner({
|
|
||||||
files() {
|
|
||||||
toggleSelectedFileTreeRow()
|
|
||||||
},
|
|
||||||
patches() {},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "diff.expand",
|
|
||||||
title: "Expand diff viewer item",
|
|
||||||
category: "VCS",
|
|
||||||
run: focusRunner({
|
|
||||||
files() {
|
|
||||||
setExpandedFileNodes((expanded) => setFileTreeDirectoryExpanded(fileTree(), expanded, highlightedFileNode(), true))
|
|
||||||
},
|
|
||||||
patches() {},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "diff.collapse",
|
|
||||||
title: "Collapse diff viewer item",
|
|
||||||
category: "VCS",
|
|
||||||
run: focusRunner({
|
|
||||||
files() {
|
|
||||||
setExpandedFileNodes((expanded) => setFileTreeDirectoryExpanded(fileTree(), expanded, highlightedFileNode(), false))
|
|
||||||
},
|
|
||||||
patches() {},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "diff.next_file",
|
|
||||||
title: "Jump to next diff file",
|
|
||||||
category: "VCS",
|
|
||||||
run() {
|
|
||||||
jumpRelativePatchFile(1)
|
|
||||||
},
|
},
|
||||||
},
|
patches() {
|
||||||
{
|
clearFileTreePatchState()
|
||||||
name: "diff.previous_file",
|
scroll?.scrollBy(1)
|
||||||
title: "Jump to previous diff file",
|
|
||||||
category: "VCS",
|
|
||||||
run() {
|
|
||||||
jumpRelativePatchFile(-1)
|
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
{
|
},
|
||||||
name: "diff.switch_focus",
|
{
|
||||||
title: "Switch diff viewer focus",
|
name: "diff.up",
|
||||||
category: "VCS",
|
title: "Move diff viewer up",
|
||||||
run() {
|
category: "VCS",
|
||||||
if (!showFileTree()) return
|
run: focusRunner({
|
||||||
setFocus((current) => {
|
files() {
|
||||||
if (current === "files") return "patches"
|
moveFileSelection(-1)
|
||||||
ensureHighlightedFileNode()
|
|
||||||
return "files"
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
},
|
patches() {
|
||||||
{
|
clearFileTreePatchState()
|
||||||
name: "diff.toggle_file_tree",
|
scroll?.scrollBy(-1)
|
||||||
title: "Toggle diff viewer file tree",
|
|
||||||
category: "VCS",
|
|
||||||
run() {
|
|
||||||
setShowFileTree((value) => {
|
|
||||||
if (value) setFocus("patches")
|
|
||||||
return !value
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
{
|
},
|
||||||
name: "diff.single_patch",
|
{
|
||||||
title: "Toggle single patch view",
|
name: "diff.page.down",
|
||||||
category: "VCS",
|
title: "Page diff viewer down",
|
||||||
run() {
|
category: "VCS",
|
||||||
setSinglePatch((value) => {
|
run: focusRunner({
|
||||||
const next = !value
|
files() {
|
||||||
if (next) ensureHighlightedPatchFile()
|
moveFileSelection(8)
|
||||||
else scrollToHighlightedPatchFile()
|
|
||||||
return next
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
},
|
patches() {
|
||||||
{
|
clearFileTreePatchState()
|
||||||
name: "diff.switch_diff",
|
if (scroll) scroll.scrollBy(scroll.height)
|
||||||
title: "Switch diff viewer source",
|
|
||||||
category: "VCS",
|
|
||||||
run() {
|
|
||||||
openSwitchDiffDialog()
|
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
{
|
},
|
||||||
name: "diff.toggle_view",
|
{
|
||||||
title: "Toggle diff viewer split or unified view",
|
name: "diff.page.up",
|
||||||
category: "VCS",
|
title: "Page diff viewer up",
|
||||||
run() {
|
category: "VCS",
|
||||||
if (!splitAvailable()) return
|
run: focusRunner({
|
||||||
setViewOverride(view() === "split" ? "unified" : "split")
|
files() {
|
||||||
|
moveFileSelection(-8)
|
||||||
},
|
},
|
||||||
|
patches() {
|
||||||
|
clearFileTreePatchState()
|
||||||
|
if (scroll) scroll.scrollBy(-scroll.height)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff.toggle",
|
||||||
|
title: "Toggle diff viewer item",
|
||||||
|
category: "VCS",
|
||||||
|
run: focusRunner({
|
||||||
|
files() {
|
||||||
|
toggleSelectedFileTreeRow()
|
||||||
|
},
|
||||||
|
patches() {},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff.expand",
|
||||||
|
title: "Expand diff viewer item",
|
||||||
|
category: "VCS",
|
||||||
|
run: focusRunner({
|
||||||
|
files() {
|
||||||
|
setExpandedFileNodes((expanded) =>
|
||||||
|
setFileTreeDirectoryExpanded(fileTree(), expanded, highlightedFileNode(), true),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
patches() {},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff.collapse",
|
||||||
|
title: "Collapse diff viewer item",
|
||||||
|
category: "VCS",
|
||||||
|
run: focusRunner({
|
||||||
|
files() {
|
||||||
|
setExpandedFileNodes((expanded) =>
|
||||||
|
setFileTreeDirectoryExpanded(fileTree(), expanded, highlightedFileNode(), false),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
patches() {},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff.next_file",
|
||||||
|
title: "Jump to next diff file",
|
||||||
|
category: "VCS",
|
||||||
|
run() {
|
||||||
|
jumpRelativePatchFile(1)
|
||||||
},
|
},
|
||||||
]
|
},
|
||||||
|
{
|
||||||
|
name: "diff.previous_file",
|
||||||
|
title: "Jump to previous diff file",
|
||||||
|
category: "VCS",
|
||||||
|
run() {
|
||||||
|
jumpRelativePatchFile(-1)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff.switch_focus",
|
||||||
|
title: "Switch diff viewer focus",
|
||||||
|
category: "VCS",
|
||||||
|
run() {
|
||||||
|
if (!showFileTree()) return
|
||||||
|
setFocus((current) => {
|
||||||
|
if (current === "files") return "patches"
|
||||||
|
ensureHighlightedFileNode()
|
||||||
|
return "files"
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff.toggle_file_tree",
|
||||||
|
title: "Toggle diff viewer file tree",
|
||||||
|
category: "VCS",
|
||||||
|
run() {
|
||||||
|
setShowFileTree((value) => {
|
||||||
|
if (value) setFocus("patches")
|
||||||
|
return !value
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff.single_patch",
|
||||||
|
title: "Toggle single patch view",
|
||||||
|
category: "VCS",
|
||||||
|
run() {
|
||||||
|
setSinglePatch((value) => {
|
||||||
|
const next = !value
|
||||||
|
if (next) ensureHighlightedPatchFile()
|
||||||
|
else scrollToHighlightedPatchFile()
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff.switch_diff",
|
||||||
|
title: "Switch diff viewer source",
|
||||||
|
category: "VCS",
|
||||||
|
run() {
|
||||||
|
openSwitchDiffDialog()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "diff.toggle_view",
|
||||||
|
title: "Toggle diff viewer split or unified view",
|
||||||
|
category: "VCS",
|
||||||
|
run() {
|
||||||
|
if (!splitAvailable()) return
|
||||||
|
setViewOverride(view() === "split" ? "unified" : "split")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
const switchDiffOptions = createMemo(() => [
|
const switchDiffOptions = createMemo(() => [
|
||||||
{
|
{
|
||||||
|
|
@ -505,67 +519,74 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||||
>
|
>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={diff.error}>
|
<Match when={diff.error}>
|
||||||
<text fg={theme().error}>Failed to load diff</text>
|
<box paddingTop={1}>
|
||||||
|
<text fg={theme().error}>Failed to load diff</text>
|
||||||
|
</box>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={files().length === 0}>
|
<Match when={files().length === 0}>
|
||||||
<text fg={theme().textMuted}>No diff to show</text>
|
<box paddingTop={1}>
|
||||||
|
<text fg={theme().textMuted}>No diff to show</text>
|
||||||
|
</box>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={files().length > 0}>
|
<Match when={files().length > 0}>
|
||||||
<scrollbox
|
<scrollbox
|
||||||
ref={(element: ScrollBoxRenderable) => (scroll = element)}
|
ref={(element: ScrollBoxRenderable) => (scroll = element)}
|
||||||
flexGrow={1}
|
flexGrow={1}
|
||||||
minHeight={0}
|
minHeight={0}
|
||||||
verticalScrollbarOptions={{ visible: false }}
|
verticalScrollbarOptions={{ visible: false }}
|
||||||
horizontalScrollbarOptions={{ visible: false }}
|
horizontalScrollbarOptions={{ visible: false }}
|
||||||
>
|
>
|
||||||
<For each={visiblePatchFiles()}>
|
<For each={visiblePatchFiles()}>
|
||||||
{(entry) => (
|
{(entry) => (
|
||||||
<box
|
<box
|
||||||
ref={(element: BoxRenderable) => registerPatchNode(entry.fileIndex, element)}
|
ref={(element: BoxRenderable) => registerPatchNode(entry.fileIndex, element)}
|
||||||
marginBottom={1}
|
marginBottom={1}
|
||||||
backgroundColor={theme().backgroundPanel}
|
backgroundColor={theme().backgroundPanel}
|
||||||
>
|
>
|
||||||
<box
|
<box
|
||||||
flexDirection="row"
|
flexDirection="row"
|
||||||
gap={2}
|
gap={2}
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
paddingTop={1}
|
paddingTop={1}
|
||||||
paddingBottom={1}
|
paddingBottom={1}
|
||||||
paddingLeft={2}
|
paddingLeft={2}
|
||||||
paddingRight={1}
|
paddingRight={1}
|
||||||
backgroundColor={theme().backgroundPanel}
|
backgroundColor={theme().backgroundPanel}
|
||||||
>
|
>
|
||||||
<text fg={theme().text}>{entry.file.file}</text>
|
<text fg={theme().text}>{entry.file.file}</text>
|
||||||
<text fg={theme().diffAdded}>+{entry.file.additions}</text>
|
<text fg={theme().diffAdded}>+{entry.file.additions}</text>
|
||||||
<text fg={theme().diffRemoved}>-{entry.file.deletions}</text>
|
<text fg={theme().diffRemoved}>-{entry.file.deletions}</text>
|
||||||
</box>
|
</box>
|
||||||
<Show when={entry.file.patch} fallback={<text fg={theme().textMuted}>No patch available for this file.</text>}>
|
<Show
|
||||||
{(patch) => (
|
when={entry.file.patch}
|
||||||
<diff
|
fallback={<text fg={theme().textMuted}>No patch available for this file.</text>}
|
||||||
diff={patch()}
|
>
|
||||||
view={view()}
|
{(patch) => (
|
||||||
filetype={filetype(entry.file.file)}
|
<diff
|
||||||
syntaxStyle={themeState.syntax()}
|
diff={patch()}
|
||||||
showLineNumbers={true}
|
view={view()}
|
||||||
width="100%"
|
filetype={filetype(entry.file.file)}
|
||||||
wrapMode="word"
|
syntaxStyle={themeState.syntax()}
|
||||||
fg={theme().text}
|
showLineNumbers={true}
|
||||||
addedBg={theme().diffAddedBg}
|
width="100%"
|
||||||
removedBg={theme().diffRemovedBg}
|
wrapMode="word"
|
||||||
contextBg={theme().diffContextBg}
|
fg={theme().text}
|
||||||
addedSignColor={theme().diffHighlightAdded}
|
addedBg={theme().diffAddedBg}
|
||||||
removedSignColor={theme().diffHighlightRemoved}
|
removedBg={theme().diffRemovedBg}
|
||||||
lineNumberFg={theme().diffLineNumber}
|
contextBg={theme().diffContextBg}
|
||||||
lineNumberBg={theme().diffContextBg}
|
addedSignColor={theme().diffHighlightAdded}
|
||||||
addedLineNumberBg={theme().diffAddedLineNumberBg}
|
removedSignColor={theme().diffHighlightRemoved}
|
||||||
removedLineNumberBg={theme().diffRemovedLineNumberBg}
|
lineNumberFg={theme().diffLineNumber}
|
||||||
/>
|
lineNumberBg={theme().diffContextBg}
|
||||||
)}
|
addedLineNumberBg={theme().diffAddedLineNumberBg}
|
||||||
</Show>
|
removedLineNumberBg={theme().diffRemovedLineNumberBg}
|
||||||
</box>
|
/>
|
||||||
)}
|
)}
|
||||||
</For>
|
</Show>
|
||||||
</scrollbox>
|
</box>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</scrollbox>
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
</box>
|
</box>
|
||||||
|
|
@ -598,14 +619,16 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||||
<Show when={toggleFileTreeShortcut()}>
|
<Show when={toggleFileTreeShortcut()}>
|
||||||
{(shortcut) => (
|
{(shortcut) => (
|
||||||
<text fg={theme().text}>
|
<text fg={theme().text}>
|
||||||
{shortcut()} <span style={{ fg: theme().textMuted }}>{showFileTree() ? "hide file tree" : "show file tree"}</span>
|
{shortcut()}{" "}
|
||||||
|
<span style={{ fg: theme().textMuted }}>{showFileTree() ? "hide file tree" : "show file tree"}</span>
|
||||||
</text>
|
</text>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={singlePatchShortcut()}>
|
<Show when={singlePatchShortcut()}>
|
||||||
{(shortcut) => (
|
{(shortcut) => (
|
||||||
<text fg={theme().text}>
|
<text fg={theme().text}>
|
||||||
{shortcut()} <span style={{ fg: theme().textMuted }}>{singlePatch() ? "all patches" : "single patch"}</span>
|
{shortcut()}{" "}
|
||||||
|
<span style={{ fg: theme().textMuted }}>{singlePatch() ? "all patches" : "single patch"}</span>
|
||||||
</text>
|
</text>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
|
|
@ -619,7 +642,8 @@ function DiffViewer(props: { api: TuiPluginApi }) {
|
||||||
<Show when={toggleViewShortcut()}>
|
<Show when={toggleViewShortcut()}>
|
||||||
{(shortcut) => (
|
{(shortcut) => (
|
||||||
<text fg={theme().text}>
|
<text fg={theme().text}>
|
||||||
{shortcut()} <span style={{ fg: theme().textMuted }}>{view() === "split" ? "unified view" : "split view"}</span>
|
{shortcut()}{" "}
|
||||||
|
<span style={{ fg: theme().textMuted }}>{view() === "split" ? "unified view" : "split view"}</span>
|
||||||
</text>
|
</text>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue