Skip to content

Commit

Permalink
improvement(highlights): add markdown support
Browse files Browse the repository at this point in the history
Added support for highlights widget.
Reused 'comments' editor known from job discussions.
Thus implementing all that is supported by this component:
titles, links, multi-line content, code blocks etc.

One caveat: mentioning does not trigger notifications yet. This is going
to be done in followup task.

closes: #510
  • Loading branch information
soyacz committed Dec 30, 2024
1 parent 2c3cea5 commit 1f0e940
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 100 deletions.
11 changes: 8 additions & 3 deletions frontend/Discussion/CommentEditor.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script>
import { createEventDispatcher } from "svelte";
import { createEventDispatcher, onMount } from "svelte";
import { parse as markdownParse } from "marked";
import Fa from "svelte-fa";
import {
Expand Down Expand Up @@ -33,6 +33,11 @@
test_run_id: "",
posted_at: new Date(),
};
export let entryType = "comment";
onMount(() => {
textArea?.focus();
});
const randomTip = function () {
const editorTips = [
Expand Down Expand Up @@ -332,7 +337,7 @@
<span class="fw-bold"><Fa icon={faLightbulb} /> Tip</span>: {randomTip()}
</div>
<div class="ms-auto text-end">
{#if mode == "edit"}
{#if mode == "edit" || entryType != "comment"}
<button
class="btn btn-danger"
on:click={() => {
Expand All @@ -341,7 +346,7 @@
>
{/if}
<button class="btn btn-success" on:click={handleSubmitComment}
>{mode == "post" ? "Submit" : "Update"} comment</button
>{mode == "post" ? "Submit" : "Update"} {entryType}</button
>
</div>
</div>
Expand Down
128 changes: 76 additions & 52 deletions frontend/Views/Widgets/ViewHighlights/ActionItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
import {createEventDispatcher} from 'svelte';
import {getPicture} from "../../../Common/UserUtils";
import AssigneeSelector from "../../../Common/AssigneeSelector.svelte";
import CommentEditor from "../../../Discussion/CommentEditor.svelte";
import * as marked from "marked";
import {MarkdownUserMention} from "../../../Discussion/MarkedMentionExtension";
import {markdownRendererOptions} from "../../../markdownOptions";
export let action;
export let currentUserId;
Expand All @@ -16,17 +20,31 @@
let assignee_id = action.assignee_id;
let isArchived = action.isArchived;
const dispatch = createEventDispatcher();
let commentBody = {
id: '',
message: action.content,
release: '',
reactions: {},
mentions: [],
user_id: '',
release_id: '',
test_run_id: '',
posted_at: new Date(),
};
marked.use({
extensions: [MarkdownUserMention]
});
const toggleArchive = () => dispatch('toggleArchive', {action});
const toggleComments = () => {
action.showComments = !action.showComments;
if (action.showComments && action.comments.length === 0) {
dispatch('loadComments', {action});
}
};
const updateContent = () => {
dispatch('updateContent', {action, newContent: action.content});
const updateContent = (e) => {
commentBody.message = e.detail.message;
isEditing = false;
dispatch('updateContent', {action, newContent: commentBody.message});
};
const addComment = () => {
if (!newComment.trim()) return;
Expand All @@ -42,57 +60,63 @@

<li class="list-group-item" class:bg-light={isArchived}>
<div class="d-flex justify-content-between align-items-center">
<div class="img-profile me-2" style="background-image: url('{getPicture(creator?.picture_id)}');" data-bs-toggle="tooltip"
title="{creator?.username}"/>
<div class="d-flex align-items-center flex-grow-1">
<input id="checkbox-{action.id}" class="form-check-input me-2" type="checkbox" disabled={isArchived}
bind:checked={action.isCompleted} on:change={toggleComplete}>
{#if isEditing}
<input type="text" class="form-control me-2" bind:value={action.content}
on:keydown={(e) => { if(e.key === 'Enter') updateContent(); }}>
<button class="btn btn-sm btn-primary me-2" on:click={updateContent}>Save</button>
<button class="btn btn-sm btn-secondary me-2" on:click={() => isEditing = false}>Cancel</button>
{:else}
<label class="form-check-label" for="checkbox-{action.id}">{action.content}</label>
{/if}
</div>
<div class="d-flex align-items-center">
<small class="text-muted me-2">{action.createdAt.toLocaleDateString("en-CA")}</small>
<div class="select-width ">
<AssigneeSelector {assignee_id} on:select={handleAssignee} disabled={isArchived} on:clear={handleAssignee}/>
{#if !isEditing}
<div class="img-profile me-2" style="background-image: url('{getPicture(creator?.picture_id)}');" data-bs-toggle="tooltip"
title="{creator?.username}"/>
<div class="d-flex align-items-center flex-grow-1">
<input id="checkbox-{action.id}" class="form-check-input me-2 mt-0" type="checkbox" disabled={isArchived}
bind:checked={action.isCompleted} on:change={toggleComplete}>
<label class="form-check-label no-bottom-margin"
for="checkbox-{action.id}">{@html marked.parse(action.content, markdownRendererOptions)}
</label>
</div>
<button class="btn btn-sm btn-outline-secondary mx-1" on:click={toggleComments}>
<Fa icon={faComment}/>
<span class="ms-1">{action.comments_count}</span>
</button>
{#if !action.isArchived}
{#if action.creator_id === currentUserId}
<button class="btn btn-sm btn-outline-secondary me-1" on:click={() => isEditing = !isEditing}>
<Fa icon={faEdit}/>
</button>
{/if}
{/if}
<button class="btn btn-sm btn-outline-secondary" on:click={toggleArchive}>
<Fa icon={action.isArchived ? faUndo : faArchive}/>
</button>
</div>
</div>
{#if action.showComments}
<div class="mt-2 col-md-10">
<ul class="list-group list-group-flush">
{#each action.comments as comment (comment.id)}
<Comment {comment} {currentUserId} {action} on:deleteComment on:updateCommentContent/>
{/each}
</ul>
{#if !action.isArchived}
<div class="input-group mt-2">
<input type="text" class="form-control" placeholder="Add a comment" bind:value={newComment}
on:keydown={handleCommentKeydown}>
<button class="btn btn-outline-primary" on:click={addComment}>Add</button>
<div class="d-flex align-items-center">
<small class="text-muted me-2">{action.createdAt.toLocaleDateString("en-CA")}</small>
<div class="select-width ">
<AssigneeSelector {assignee_id} on:select={handleAssignee} disabled={isArchived} on:clear={handleAssignee}/>
</div>
{/if}
</div>
{/if}
<button class="btn btn-sm btn-outline-secondary mx-1" on:click={toggleComments}>
<Fa icon={faComment}/>
<span class="ms-1">{action.comments_count}</span>
</button>
{#if !action.isArchived}
{#if action.creator_id === currentUserId}
<button class="btn btn-sm btn-outline-secondary me-1" on:click={() => isEditing = !isEditing}>
<Fa icon={faEdit}/>
</button>
{/if}
{/if}
<button class="btn btn-sm btn-outline-secondary" on:click={toggleArchive}>
<Fa icon={action.isArchived ? faUndo : faArchive}/>
</button>
</div>
{:else}
<div class="flex-grow-1">
<CommentEditor
commentBody={Object.assign({}, commentBody)}
mode="edit"
entryType="action item"
on:submitComment={updateContent}
on:cancelEditing={() => (isEditing = false)}
/>
</div>
{/if}
{#if action.showComments}
<div class="mt-2 col-md-10">
<ul class="list-group list-group-flush">
{#each action.comments as comment (comment.id)}
<Comment {comment} {currentUserId} {action} on:deleteComment on:updateCommentContent/>
{/each}
</ul>
{#if !action.isArchived}
<div class="input-group mt-2">
<input type="text" class="form-control" placeholder="Add a comment" bind:value={newComment}
on:keydown={handleCommentKeydown}>
<button class="btn btn-outline-primary" on:click={addComment}>Add</button>
</div>
{/if}
</div>
{/if}
</li>

<style>
Expand Down
91 changes: 58 additions & 33 deletions frontend/Views/Widgets/ViewHighlights/HighlightItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
import {createEventDispatcher} from 'svelte';
import UserProfile from "../../../Discussion/UserProfile.svelte";
import {getPicture} from "../../../Common/UserUtils";
import CommentEditor from "../../../Discussion/CommentEditor.svelte";
import * as marked from "marked";
import {markdownRendererOptions} from "../../../markdownOptions";
import {MarkdownUserMention} from "../../../Discussion/MarkedMentionExtension";
export let highlight;
export let currentUserId;
Expand All @@ -13,6 +17,20 @@
let isEditing = false;
let newComment = '';
let creationTimeStr = highlight.createdAt.toLocaleDateString("en-CA");
let commentBody = {
id: '',
message: highlight.content,
release: '',
reactions: {},
mentions: [],
user_id: '',
release_id: '',
test_run_id: '',
posted_at: new Date(),
};
marked.use({
extensions: [MarkdownUserMention]
});
const dispatch = createEventDispatcher();
const toggleArchive = () => dispatch('toggleArchive', {highlight});
Expand All @@ -22,10 +40,7 @@
dispatch('loadComments', {highlight});
}
};
const updateContent = () => {
dispatch('updateContent', {highlight, newContent: highlight.content});
isEditing = false;
};
const addComment = () => {
if (!newComment.trim()) return;
dispatch('addComment', {highlight, content: newComment});
Expand All @@ -34,39 +49,49 @@
const handleCommentKeydown = (event: KeyboardEvent) => {
if (event.key === 'Enter') addComment();
};
const handleCommentUpdate = function (e) {
commentBody.message = e.detail.message;
isEditing = false;
dispatch('updateContent', {highlight, newContent: commentBody.message});
};
</script>

<li class="list-group-item" class:bg-light={highlight.isArchived}>
<div class="d-flex justify-content-between align-items-center">
<div class="img-profile me-2" style="background-image: url('{getPicture(creator?.picture_id)}');" data-bs-toggle="tooltip" title="{creator?.username}" />
<div class="d-flex align-items-center flex-grow-1">
{#if isEditing}
<input type="text" class="form-control me-2" bind:value={highlight.content}
on:keydown={(e) => { if(e.key === 'Enter') updateContent(); }}>
<button class="btn btn-sm btn-primary me-2" on:click={updateContent}>Save</button>
<button class="btn btn-sm btn-secondary me-2" on:click={() => isEditing = false}>Cancel</button>
{:else}
<span>{highlight.content}</span>
{/if}
</div>
<div class="d-flex align-items-center">
<small class="text-muted me-2">{creationTimeStr}</small>
<button class="btn btn-sm btn-outline-secondary me-1" on:click={toggleComments}>
<Fa icon={faComment}/>
<span class="ms-1">{highlight.comments_count}</span>
</button>
{#if !highlight.isArchived}
{#if highlight.creator_id === currentUserId}
<button class="btn btn-sm btn-outline-secondary me-1" on:click={() => isEditing = !isEditing}>
<Fa icon={faEdit}/>
</button>
<div class="d-flex justify-content-between align-items-center">
{#if !isEditing}
<div class="img-profile me-2" style="background-image: url('{getPicture(creator?.picture_id)}');" data-bs-toggle="tooltip"
title="{creator?.username}"/>
<div class="d-flex align-items-center flex-grow-1 no-bottom-margin-p">
<span class="no-bottom-margin">{@html marked.parse(highlight.content, markdownRendererOptions)}</span>
</div>
<div class="d-flex align-items-center">
<small class="text-muted me-2">{creationTimeStr}</small>
<button class="btn btn-sm btn-outline-secondary me-1" on:click={toggleComments}>
<Fa icon={faComment}/>
<span class="ms-1">{highlight.comments_count}</span>
</button>
{#if !highlight.isArchived}
{#if highlight.creator_id === currentUserId}
<button class="btn btn-sm btn-outline-secondary me-1" on:click={() => isEditing = !isEditing}>
<Fa icon={faEdit}/>
</button>
{/if}
{/if}
{/if}
<button class="btn btn-sm btn-outline-secondary" on:click={toggleArchive}>
<Fa icon={highlight.isArchived ? faUndo : faArchive}/>
</button>
</div>
<button class="btn btn-sm btn-outline-secondary" on:click={toggleArchive}>
<Fa icon={highlight.isArchived ? faUndo : faArchive}/>
</button>
</div>
{:else}
<div class="flex-grow-1">
<CommentEditor
commentBody={Object.assign({}, commentBody)}
mode="edit"
entryType="highlight"
on:submitComment={handleCommentUpdate}
on:cancelEditing={() => (isEditing = false)}
/>
</div>
{/if}
</div>
{#if highlight.showComments}
<div class="mt-2 col-md-10">
Expand Down
41 changes: 29 additions & 12 deletions frontend/Views/Widgets/ViewHighlights/ViewHighlights.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import {userList} from "../../../Stores/UserlistSubscriber";
import HighlightItem from "./HighlightItem.svelte";
import ActionItem from "./ActionItem.svelte";
import CommentEditor from "../../../Discussion/CommentEditor.svelte";
export let dashboardObject;
export let settings;
Expand Down Expand Up @@ -90,6 +91,7 @@
};
}
const addEntry = async (type: "highlight" | "action", content: string) => {
if (!content.trim()) return;
const response = await fetch("/api/v1/views/widgets/highlights/create", {
Expand All @@ -116,6 +118,12 @@
}
}
};
const handleHightlightCreate = function (e) {
addEntry("highlight", e.detail.message);
};
const handleActionItemCreate = function (e) {
addEntry("action", e.detail.message);
};
const handleToggleArchive = async (event) => {
const entry = event.detail.highlight || event.detail.action;
Expand Down Expand Up @@ -314,12 +322,13 @@
{/each}
<li class="list-group-item">
{#if showNewHighlight}
<div class="input-group">
<input type="text" class="form-control" placeholder="New highlight" bind:value={newHighlight}
on:keydown={(e) => e.key === "Enter" && addEntry("highlight", newHighlight)}
bind:this={newHighlightInput}>
<button class="btn btn-primary" on:click={() => addEntry("highlight", newHighlight)}>Add</button>
<button class="btn btn-outline-secondary" on:click={() => showNewHighlight = false}>Cancel</button>
<div class="flex-grow-1">
<CommentEditor
mode="post"
entryType="highlight"
on:submitComment={handleHightlightCreate}
on:cancelEditing={() => (showNewHighlight = false)}
/>
</div>
{:else}
<button class="btn w-100 text-start text-muted" on:click={() => showNewHighlight = true}>
Expand Down Expand Up @@ -375,12 +384,13 @@
{/each}
<li class="list-group-item">
{#if showNewActionItem}
<div class="input-group">
<input type="text" class="form-control" placeholder="New action item" bind:value={newActionItem}
on:keydown={(e) => e.key === "Enter" && addEntry("action", newActionItem)}
bind:this={newActionInput}>
<button class="btn btn-primary" on:click={() => addEntry("action", newActionItem)}>Add</button>
<button class="btn btn-outline-secondary" on:click={() => showNewActionItem = false}>Cancel</button>
<div class="flex-grow-1">
<CommentEditor
mode="post"
entryType="action item"
on:submitComment={handleActionItemCreate}
on:cancelEditing={() => (showNewActionItem = false)}
/>
</div>
{:else}
<button class="btn w-100 text-start text-muted" on:click={() => showNewActionItem = true}>
Expand Down Expand Up @@ -418,3 +428,10 @@
</div>
{/key}
</div>

<style>
:global(.no-bottom-margin *:last-child) {
margin-bottom: 0;
}
</style>

0 comments on commit 1f0e940

Please sign in to comment.