Adding embed editing in issues and patches

This commit is contained in:
Sebastian Martinez
2023-11-28 10:59:55 +01:00
parent 1167dcfab6
commit 95ed1a0353
18 changed files with 246 additions and 123 deletions
+2 -1
View File
@@ -6,7 +6,7 @@ import type {
Tree,
DiffResponse,
} from "./lib/project.js";
import type { Comment } from "./lib/project/comment.js";
import type { Comment, Embed } from "./lib/project/comment.js";
import type {
Commit,
CommitBlob,
@@ -52,6 +52,7 @@ export type {
DiffContent,
DiffFile,
DiffResponse,
Embed,
HunkLine,
Issue,
IssueState,
+1 -1
View File
@@ -1,5 +1,5 @@
import type { Embed } from "@app/lib/file.js";
import type { Commit, Commits } from "./project/commit.js";
import type { Embed } from "./project/comment.js";
import type { Fetcher, RequestOptions } from "./fetcher.js";
import type {
Issue,
+1 -2
View File
@@ -1,5 +1,4 @@
import type { Embed } from "@app/lib/file.js";
import type { Comment } from "./comment.js";
import type { Comment, Embed } from "./comment.js";
import type { ZodSchema } from "zod";
import { array, boolean, literal, object, string, union } from "zod";
+1 -2
View File
@@ -1,5 +1,4 @@
import type { Embed } from "@app/lib/file.js";
import type { Comment } from "./comment.js";
import type { Comment, Embed } from "./comment.js";
import type { ZodSchema, z } from "zod";
import { commentSchema } from "./comment.js";
+4 -2
View File
@@ -1,5 +1,5 @@
<script lang="ts" strictEvents>
import type { Embed } from "@app/lib/file";
import type { Embed } from "@httpd-client";
import type { GroupedReactions } from "@app/lib/reactions";
import { tick } from "svelte";
@@ -21,6 +21,7 @@
export let body: string;
export let enableAttachments: boolean = false;
export let reactions: GroupedReactions | undefined = undefined;
export let embeds: Map<string, Embed> | undefined = undefined;
export let caption = "commented";
export let rawPath: string;
export let timestamp: number;
@@ -145,6 +146,7 @@
<ExtendedTextarea
{rawPath}
{body}
{embeds}
{enableAttachments}
submitInProgress={state === "submit"}
submitCaption="Save"
@@ -152,7 +154,7 @@
on:submit={async ({ detail: { comment, embeds } }) => {
state = "submit";
try {
await editComment_(comment, embeds);
await editComment_(comment, Array.from(embeds.values()));
} finally {
state = "read";
}
+2 -2
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import type { Embed } from "@app/lib/file";
import type { Embed } from "@httpd-client";
import ExtendedTextarea from "@app/components/ExtendedTextarea.svelte";
@@ -44,7 +44,7 @@
on:submit={async ({ detail: { comment, embeds } }) => {
try {
state = "submit";
await submit(comment, embeds);
await submit(comment, Array.from(embeds.values()));
} finally {
state = "collapsed";
}
+11 -19
View File
@@ -1,5 +1,5 @@
<script lang="ts" strictEvents>
import type { Embed, EmbedWithOid } from "@app/lib/file";
import type { Embed } from "@httpd-client";
import { createEventDispatcher } from "svelte";
@@ -23,10 +23,10 @@
export let inline: boolean = false;
export let rawPath: string;
export let body: string = "";
export let embeds: Map<string, Embed> = new Map();
export let submitInProgress: boolean = false;
let preview: boolean = false;
let newEmbeds: EmbedWithOid[] = [];
let selectionStart = 0;
let selectionEnd = 0;
let inputFiles: FileList | undefined = undefined;
@@ -34,15 +34,14 @@
const inputId = `input-label-${crypto.randomUUID()}`;
const dispatch = createEventDispatcher<{
submit: { comment: string; embeds: Embed[] };
submit: { comment: string; embeds: Map<string, Embed> };
close: null;
click: null;
}>();
function submit() {
dispatch("submit", { comment: body, embeds: newEmbeds });
dispatch("submit", { comment: body, embeds });
preview = false;
newEmbeds = [];
}
const MAX_BLOB_SIZE = 4_194_304;
@@ -86,10 +85,10 @@
}
function attachEmbeds(files: FileList) {
const embeds = Array.from(files).map(embed);
void Promise.all(embeds).then(embeds =>
embeds.forEach(embed => {
if (embed.content.length > MAX_BLOB_SIZE) {
const embedPromise = Array.from(files).map(embed);
void Promise.all(embedPromise).then(newEmbeds =>
newEmbeds.forEach(({ oid, name, content }) => {
if (content.length > MAX_BLOB_SIZE) {
modal.show({
component: ErrorModal,
props: {
@@ -103,15 +102,8 @@
});
return;
}
newEmbeds = [
...newEmbeds,
{
oid: embed.oid,
name: embed.name,
content: embed.content,
},
];
const embedText = `![${embed.name}](${embed.oid})\n`;
embeds.set(oid, { name, content });
const embedText = `![${name}](${oid})\n`;
body = body
.slice(0, selectionStart)
.concat(embedText, body.slice(selectionEnd));
@@ -196,7 +188,7 @@
</Radio>
{#if preview}
<div class="preview">
<Markdown {rawPath} content={body} embeds={newEmbeds} />
<Markdown {rawPath} content={body} {embeds} />
</div>
{:else}
<input
+5 -6
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import type { EmbedWithOid } from "@app/lib/file";
import type { Embed } from "@httpd-client";
import dompurify from "dompurify";
import matter from "@radicle/gray-matter";
@@ -28,7 +28,7 @@
export let rawPath: string;
// If present, means we are in a preview context,
// use this for image previews instead of /raw URLs.
export let embeds: EmbedWithOid[] | undefined = undefined;
export let embeds: Map<string, Embed> | undefined = undefined;
let container: HTMLElement;
let frontMatter: [string, any][] | undefined = undefined;
@@ -112,10 +112,9 @@
// If the image is an oid embed
if (imagePath && isCommit(imagePath)) {
const embed = embeds?.find(e => {
return e.oid === imagePath;
});
if (embed) {
const embed = embeds?.get(imagePath);
// If the embed content is the base64 encoded image, use it directly.
if (embed && embed.content.startsWith("data:")) {
i.setAttribute("src", embed.content);
continue;
}
+6 -3
View File
@@ -20,10 +20,11 @@
</script>
<script lang="ts" strictEvents>
import type { Embed } from "@app/lib/file";
import type { Embed } from "@httpd-client";
import * as utils from "@app/lib/utils";
import partial from "lodash/partial";
import { parseEmbedIntoMap } from "@app/lib/file";
import { tick } from "svelte";
import CommentComponent from "@app/components/Comment.svelte";
@@ -88,13 +89,14 @@
<div class="comments">
<div class="top-level-comment" class:has-replies={replies.length > 0}>
<CommentComponent
{enableAttachments}
{rawPath}
id={root.id}
authorId={root.author.id}
authorAlias={root.author.alias}
reactions={root.reactions}
embeds={parseEmbedIntoMap(root.embeds)}
timestamp={root.timestamp}
disableEdit={root.embeds.length > 0}
body={root.body}
editComment={editComment &&
canEditComment(root.author.id) &&
@@ -107,6 +109,7 @@
<div class="replies">
{#each replies as reply}
<CommentComponent
{enableAttachments}
{rawPath}
id={reply.id}
authorId={reply.author.id}
@@ -115,8 +118,8 @@
isReply
isLastReply={replies[replies.length - 1] === reply}
reactions={reply.reactions}
embeds={parseEmbedIntoMap(reply.embeds)}
timestamp={reply.timestamp}
disableEdit={reply.embeds.length > 0}
body={reply.body}
editComment={editComment &&
canEditComment(reply.author.id) &&
+9 -8
View File
@@ -1,10 +1,4 @@
export interface Embed {
name: string;
content: string;
}
export interface EmbedWithOid extends Embed {
oid: string;
}
import type { Embed } from "@httpd-client";
async function parseGitOid(bytes: Uint8Array): Promise<string> {
// Create the header
@@ -38,6 +32,13 @@ function base64String(file: File): Promise<string> {
});
}
export function parseEmbedIntoMap(embeds: Embed[]) {
return embeds.reduce((acc, embed) => {
acc.set(embed.content.substring(4), embed);
return acc;
}, new Map());
}
const mimes: Record<string, string> = {
"3gp": "video/3gpp",
"7z": "application/x-7z-compressed",
@@ -101,7 +102,7 @@ const mimes: Record<string, string> = {
zip: "application/zip",
};
async function embed(file: File): Promise<EmbedWithOid> {
async function embed(file: File) {
const bytes = new Uint8Array(await file.arrayBuffer());
const oid = await parseGitOid(bytes);
const content = await base64String(file);
+1 -1
View File
@@ -1,5 +1,5 @@
<script lang="ts" strictEvents>
import type { Embed } from "@app/lib/file";
import type { Embed } from "@httpd-client";
import Badge from "@app/components/Badge.svelte";
import Clipboard from "@app/components/Clipboard.svelte";
+1 -1
View File
@@ -1,8 +1,8 @@
<script lang="ts">
import type { Embed } from "@httpd-client/lib/project/comment";
import type {
BaseUrl,
DiffResponse,
Embed,
PatchState,
Verdict,
} from "@httpd-client";
+13 -10
View File
@@ -1,6 +1,11 @@
<script lang="ts">
import type { BaseUrl, Issue, IssueState, Project } from "@httpd-client";
import type { Embed } from "@app/lib/file";
import type {
BaseUrl,
Issue,
IssueState,
Embed,
Project,
} from "@httpd-client";
import type { Session } from "@app/lib/httpd";
import isEqual from "lodash/isEqual";
@@ -15,6 +20,7 @@
import { closeFocused } from "@app/components/Popover.svelte";
import { groupReactions } from "@app/lib/reactions";
import { httpdStore } from "@app/lib/httpd";
import { parseEmbedIntoMap } from "@app/lib/file";
import AssigneeInput from "@app/views/projects/Cob/AssigneeInput.svelte";
import Badge from "@app/components/Badge.svelte";
@@ -358,14 +364,10 @@
}
}
$: embeds = issue.discussion.reduce(
(acc, comment) => {
acc[comment.id] = comment.embeds;
return acc;
},
{} as Record<string, Embed[]>,
$: uniqueEmbeds = uniqBy(
issue.discussion.flatMap(comment => comment.embeds),
"content",
);
$: uniqueEmbeds = uniqBy(Object.values(embeds).flat(), "content");
$: selectedItem = issue.state.status === "closed" ? items[0] : items[1];
$: threads = issue.discussion
.filter(
@@ -518,6 +520,7 @@
<ExtendedTextarea
rawPath={rawPath(project.head)}
enableAttachments
embeds={parseEmbedIntoMap(issue.discussion[0].embeds)}
body={issue.discussion[0].body}
submitCaption="Save"
submitInProgress={issueState === "submit"}
@@ -535,7 +538,7 @@
issue.title,
issue.id,
comment,
embeds,
Array.from(embeds.values()),
);
} finally {
issueState = "read";
+3 -7
View File
@@ -1,6 +1,5 @@
<script lang="ts">
import type { BaseUrl, Project } from "@httpd-client";
import type { EmbedWithOid } from "@app/lib/file";
import type { BaseUrl, Embed, Project } from "@httpd-client";
import * as modal from "@app/lib/modal";
import * as router from "@app/lib/router";
@@ -28,7 +27,6 @@
export let rawPath: (commit?: string) => string;
export let tracking: boolean;
let newEmbeds: EmbedWithOid[] = [];
let preview: boolean = false;
let selectionStart = 0;
let selectionEnd = 0;
@@ -41,6 +39,7 @@
let creatingIssue: boolean = false;
const api = new HttpdClient(baseUrl);
const newEmbeds = new Map<string, Embed>();
function handleFileDrop(event: { detail: DragEvent }) {
event.detail.preventDefault();
@@ -48,10 +47,7 @@
const embeds = Array.from(event.detail.dataTransfer.files).map(embed);
void Promise.all(embeds).then(embeds =>
embeds.forEach(({ oid, name, content }) => {
newEmbeds = [
...newEmbeds,
{ oid: oid, name: name, content: content },
];
newEmbeds.set(oid, { name, content });
const embedText = `![${name}](${oid})\n`;
issueText = issueText
.slice(0, selectionStart)
+11 -3
View File
@@ -35,8 +35,7 @@
</script>
<script lang="ts">
import type { BaseUrl, Patch } from "@httpd-client";
import type { Embed } from "@app/lib/file";
import type { BaseUrl, Embed, Patch } from "@httpd-client";
import type { PatchView } from "./router";
import type { Route } from "@app/lib/router";
import type { ComponentProps } from "svelte";
@@ -45,10 +44,11 @@
import * as role from "@app/lib/roles";
import * as router from "@app/lib/router";
import * as utils from "@app/lib/utils";
import { HttpdClient } from "@httpd-client";
import capitalize from "lodash/capitalize";
import isEqual from "lodash/isEqual";
import partial from "lodash/partial";
import uniqBy from "lodash/uniqBy";
import { HttpdClient } from "@httpd-client";
import { httpdStore, type Session } from "@app/lib/httpd";
import Badge from "@app/components/Badge.svelte";
@@ -60,6 +60,7 @@
import CommitTeaser from "@app/views/projects/Commit/CommitTeaser.svelte";
import DropdownList from "@app/components/DropdownList.svelte";
import DropdownListItem from "@app/components/DropdownList/DropdownListItem.svelte";
import Embeds from "@app/views/projects/Cob/Embeds.svelte";
import ErrorModal from "@app/modals/ErrorModal.svelte";
import ExtendedTextarea from "@app/components/ExtendedTextarea.svelte";
import Icon from "@app/components/Icon.svelte";
@@ -468,6 +469,12 @@
revisionId = view.revision;
}
$: uniqueEmbeds = uniqBy(
patch.revisions.flatMap(({ discussions }) =>
discussions.flatMap(comment => comment.embeds),
),
"content",
);
$: description = patch.revisions[0].description;
$: newDescription = description;
$: patchReviews = computeReviews(patch);
@@ -961,6 +968,7 @@
}
}
}} />
<Embeds embeds={uniqueEmbeds} />
</div>
</div>
</Layout>
+80 -1
View File
@@ -1,5 +1,9 @@
import { test, cobUrl, expect } from "@tests/support/fixtures.js";
import { createProject, expectReactionsToWork } from "@tests/support/project";
import {
addEmbed,
createProject,
expectReactionsToWork,
} from "@tests/support/project";
test("navigate single issue", async ({ page }) => {
await page.goto(`${cobUrl}/issues`);
@@ -127,3 +131,78 @@ test("add and remove reactions", async ({ page, authenticatedPeer }) => {
.click();
await expectReactionsToWork(page);
});
test("handling embeds", async ({ page, authenticatedPeer }) => {
const { rid } = await createProject(authenticatedPeer, "embeds");
await page.goto(
`/nodes/${authenticatedPeer.httpdBaseUrl.hostname}:${authenticatedPeer.httpdBaseUrl.port}/${rid}/issues/new`,
);
await page.getByPlaceholder("Title").fill("This is a title");
await page
.getByPlaceholder("Write a description")
.fill("Here is some text\n\n");
await addEmbed(
page,
"./public/images/radicle-228x228.png",
"radicle-228x228.png",
"image/png",
);
await expect(page.getByPlaceholder("Write a description")).toHaveValue(
"Here is some text\n\n![radicle-228x228.png](bae036309c2182c7304c97956969369823b5c6ad)\n",
);
await page.getByRole("button", { name: "Preview" }).click();
await expect(
page.getByRole("img", { name: "radicle-228x228.png" }),
).toBeVisible();
expect(
await page
.getByRole("img", { name: "radicle-228x228.png" })
.getAttribute("src"),
).toBe(
"data:image/png;base64,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",
);
await page.getByRole("button", { name: "Submit" }).click();
await expect(page.getByRole("button", { name: "Submit" })).toBeHidden();
await expect(page.getByText("This is a title")).toBeVisible();
await expect(page.getByText("Here is some text")).toBeVisible();
await expect(
page.getByRole("img", { name: "radicle-228x228.png" }),
).toBeVisible();
const { scheme, hostname, port } = authenticatedPeer.httpdBaseUrl;
expect(
await page
.getByRole("img", { name: "radicle-228x228.png" })
.getAttribute("src"),
).toBe(
`${scheme}://${hostname}:${port}/raw/rad:z2J7s48EbCBckcEmj2dm5eaFVoBsy/blobs/bae036309c2182c7304c97956969369823b5c6ad?mime=image/png`,
);
await expect(
page.locator(".badge").filter({ hasText: "radicle-228x228.png" }),
).toBeVisible();
await page.getByRole("button", { name: "edit issue" }).click();
await page
.getByPlaceholder("Leave a description")
.fill(
"Here is some text\n\n![radicle-228x228.png](bae036309c2182c7304c97956969369823b5c6ad)\n\n",
);
await addEmbed(
page,
"./public/images/apple-touch-icon.png",
"apple-touch-icon.png",
"image/png",
);
await expect(page.getByPlaceholder("Leave a description")).toHaveValue(
"Here is some text\n\n![radicle-228x228.png](bae036309c2182c7304c97956969369823b5c6ad)\n\n![apple-touch-icon.png](c69a66fa4c414c92bf75365fb5c92453fa95dcca)\n",
);
await page.getByRole("button", { name: "Save" }).click();
await expect(page.getByRole("button", { name: "Submit" })).toBeHidden();
await expect(
page.locator(".badge").filter({ hasText: "apple-touch-icon.png" }),
).toBeVisible();
await expect(
page.locator(".badge").filter({ hasText: "radicle-228x228.png" }),
).toBeVisible();
});
+1 -53
View File
@@ -1,5 +1,5 @@
import { test, cobUrl, expect } from "@tests/support/fixtures.js";
import { addEmbed, createProject } from "@tests/support/project";
import { createProject } from "@tests/support/project";
test("navigate issue listing", async ({ page }) => {
await page.goto(cobUrl);
@@ -91,55 +91,3 @@ test("create a new issue", async ({ page, authenticatedPeer }) => {
).toBeVisible();
await expect(page.locator(".badge").filter({ hasText: "bug" })).toBeVisible();
});
test("handling embeds", async ({ page, authenticatedPeer }) => {
const { rid } = await createProject(authenticatedPeer, "embeds");
await page.goto(
`/nodes/${authenticatedPeer.httpdBaseUrl.hostname}:${authenticatedPeer.httpdBaseUrl.port}/${rid}/issues/new`,
);
await page.getByPlaceholder("Title").fill("This is a title");
await page
.getByPlaceholder("Write a description")
.fill("Here is some text\n\n");
await addEmbed(
page,
"./public/images/radicle-228x228.png",
"radicle-228x228.png",
"image/png",
);
await expect(page.getByPlaceholder("Write a description")).toHaveValue(
"Here is some text\n\n![radicle-228x228.png](bae036309c2182c7304c97956969369823b5c6ad)\n",
);
await page.getByRole("button", { name: "Preview" }).click();
await expect(
page.getByRole("img", { name: "radicle-228x228.png" }),
).toBeVisible();
expect(
await page
.getByRole("img", { name: "radicle-228x228.png" })
.getAttribute("src"),
).toBe(
"data:image/png;base64,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",
);
await page.getByRole("button", { name: "Submit" }).click();
await expect(page.getByRole("button", { name: "Submit" })).toBeHidden();
await expect(page.getByText("This is a title")).toBeVisible();
await expect(page.getByText("Here is some text")).toBeVisible();
await expect(
page.getByRole("img", { name: "radicle-228x228.png" }),
).toBeVisible();
const { scheme, hostname, port } = authenticatedPeer.httpdBaseUrl;
expect(
await page
.getByRole("img", { name: "radicle-228x228.png" })
.getAttribute("src"),
).toBe(
`${scheme}://${hostname}:${port}/raw/rad:z2J7s48EbCBckcEmj2dm5eaFVoBsy/blobs/bae036309c2182c7304c97956969369823b5c6ad?mime=image/png`,
);
await expect(
page.locator(".badge").filter({ hasText: "radicle-228x228.png" }),
).toBeVisible();
});
+94 -1
View File
@@ -1,5 +1,6 @@
import { test } from "@tests/support/fixtures.js";
import { test, expect } from "@tests/support/fixtures.js";
import {
addEmbed,
createProject,
expectReactionsToWork,
expectThreadCommentingToWork,
@@ -70,3 +71,95 @@ test("add and remove reactions", async ({ page, authenticatedPeer }) => {
await page.goto(`${authenticatedPeer.uiUrl()}/${rid}/patches/${patchId}`);
await expectReactionsToWork(page);
});
test("handling embeds", async ({ page, authenticatedPeer }) => {
const { rid, projectFolder } = await createProject(
authenticatedPeer,
"embeds",
);
await authenticatedPeer.rad(
[
"issue",
"open",
"--title",
"This is an issue to handle embeds",
"--description",
"We'll give it a few comments and replies with embeds.",
],
{ cwd: projectFolder },
);
await page.goto(`${authenticatedPeer.uiUrl()}/${rid}/issues`);
await page
.getByRole("link", {
name: "This is an issue to handle embeds",
})
.click();
await page.getByRole("button", { name: "Leave your comment" }).click();
await page
.getByPlaceholder("Leave your comment")
.fill("Here is some text\n\n");
await addEmbed(
page,
"./public/images/radicle-228x228.png",
"radicle-228x228.png",
"image/png",
);
await expect(page.getByPlaceholder("Leave your comment")).toHaveValue(
"Here is some text\n\n![radicle-228x228.png](bae036309c2182c7304c97956969369823b5c6ad)\n",
);
await page.getByRole("button", { name: "Preview" }).click();
await expect(
page.getByRole("img", { name: "radicle-228x228.png" }),
).toBeVisible();
expect(
await page
.getByRole("img", { name: "radicle-228x228.png" })
.getAttribute("src"),
).toBe(
"data:image/png;base64,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",
);
await page.getByRole("button", { name: "Comment" }).click();
await expect(page.getByPlaceholder("Leave your comment")).toBeHidden();
await expect(page.getByText("Here is some text")).toBeVisible();
await expect(
page.getByRole("img", { name: "radicle-228x228.png" }),
).toBeVisible();
const { scheme, hostname, port } = authenticatedPeer.httpdBaseUrl;
expect(
await page
.getByRole("img", { name: "radicle-228x228.png" })
.getAttribute("src"),
).toBe(
`${scheme}://${hostname}:${port}/raw/rad:z2J7s48EbCBckcEmj2dm5eaFVoBsy/blobs/bae036309c2182c7304c97956969369823b5c6ad?mime=image/png`,
);
await expect(
page.locator(".badge").filter({ hasText: "radicle-228x228.png" }),
).toBeVisible();
await page.getByRole("button", { name: "edit comment" }).click();
await page
.getByPlaceholder("Leave your comment")
.fill(
"Here is some text\n\n![radicle-228x228.png](bae036309c2182c7304c97956969369823b5c6ad)\n\n",
);
await addEmbed(
page,
"./public/images/apple-touch-icon.png",
"apple-touch-icon.png",
"image/png",
);
await expect(page.getByPlaceholder("Leave your comment")).toHaveValue(
"Here is some text\n\n![radicle-228x228.png](bae036309c2182c7304c97956969369823b5c6ad)\n\n![apple-touch-icon.png](c69a66fa4c414c92bf75365fb5c92453fa95dcca)\n",
);
await page.getByRole("button", { name: "Save" }).click();
await expect(page.getByRole("button", { name: "Save" })).toBeHidden();
await expect(
page.locator(".badge").filter({ hasText: "apple-touch-icon.png" }),
).toBeVisible();
await expect(
page.locator(".badge").filter({ hasText: "radicle-228x228.png" }),
).toBeVisible();
});