feat(components): make CommentEditor configurable and use it for description
Replace the plain textarea in CreateReviewDialog with CommentEditor bound to the description. The dialog now passes language, disabled, rows, ariaLabel, previewLabel and placeholder so the description field gains markdown preview and consistent accessible labels/placeholders. Make CommentEditor props optional and configurable: - onSend is now (() => void | Promise<void>) | undefined; Enter/Cmd+Enter and the send button are guarded/hidden when onSend is not provided. - Add placeholder, ariaLabel, previewLabel and rows (default 5) to allow parent components to control appearance and accessibility. Also add a small documentation tweak in commit_ai's cloud template: remind authors to keep the title plain text and expand guidance on Markdown formatting.
This commit is contained in:
@@ -8,7 +8,11 @@
|
||||
export let language: "de" | "en" = "en";
|
||||
export let disabled = false;
|
||||
export let busy = false;
|
||||
export let onSend: () => void | Promise<void>;
|
||||
export let onSend: (() => void | Promise<void>) | undefined = undefined;
|
||||
export let placeholder: string | undefined = undefined;
|
||||
export let ariaLabel: string | undefined = undefined;
|
||||
export let previewLabel: string | undefined = undefined;
|
||||
export let rows = 5;
|
||||
let textarea: HTMLTextAreaElement;
|
||||
let preview = false;
|
||||
let monospace = false;
|
||||
@@ -59,7 +63,7 @@
|
||||
if (!(event.ctrlKey || event.metaKey)) return;
|
||||
const key = event.key.toLowerCase();
|
||||
if (key === "b" || key === "i" || key === "k") { event.preventDefault(); void format(key === "b" ? "**" : key === "i" ? "_" : "[",key === "b" ? "**" : key === "i" ? "_" : "](https://example.com)"); }
|
||||
if (key === "enter") { event.preventDefault(); if (value.trim() && !busy && !disabled) void onSend(); }
|
||||
if (key === "enter" && onSend) { event.preventDefault(); if (value.trim() && !busy && !disabled) void onSend(); }
|
||||
}
|
||||
function previewLinks(node: HTMLElement) {
|
||||
node.addEventListener("click", previewClick);
|
||||
@@ -87,14 +91,14 @@
|
||||
</div>
|
||||
{#if preview}
|
||||
<!-- Rendered Markdown is restricted to safe content tags and sanitized before insertion. -->
|
||||
<div class="md-preview" role="region" aria-label={de ? "Kommentarvorschau" : "Comment preview"} use:previewLinks>
|
||||
<div class="md-preview" role="region" aria-label={previewLabel ?? (de ? "Kommentarvorschau" : "Comment preview")} use:previewLinks>
|
||||
{#if value.trim()}{@html rendered}{:else}<span>{de ? "Noch nichts zum Anzeigen." : "Nothing to preview yet."}</span>{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<textarea bind:this={textarea} bind:value class:monospace wrap={wrap ? "soft" : "off"} maxlength="100000" rows="5" disabled={disabled || busy} aria-label={de ? "Kommentar schreiben" : "Write comment"} placeholder={de ? "Kommentar hinzufügen …" : "Leave a comment …"} onkeydown={keyboard}></textarea>
|
||||
<textarea bind:this={textarea} bind:value class:monospace wrap={wrap ? "soft" : "off"} maxlength="100000" {rows} disabled={disabled || busy} aria-label={ariaLabel ?? (de ? "Kommentar schreiben" : "Write comment")} placeholder={placeholder ?? (de ? "Kommentar hinzufügen …" : "Leave a comment …")} onkeydown={keyboard}></textarea>
|
||||
{/if}
|
||||
{#if linkError}<small role="alert">{linkError}</small>{/if}
|
||||
<div class="md-footer"><small>Markdown <span>· Ctrl/⌘ + Enter</span></small><button class="md-send" type="button" disabled={!value.trim() || disabled || busy} onclick={() => onSend()}><Send size={13} />{busy ? (de ? "Wird gesendet …" : "Sending …") : (de ? "Kommentar senden" : "Post comment")}</button></div>
|
||||
<div class="md-footer"><small>Markdown {#if onSend}<span>· Ctrl/⌘ + Enter</span>{/if}</small>{#if onSend}<button class="md-send" type="button" disabled={!value.trim() || disabled || busy} onclick={() => onSend?.()}><Send size={13} />{busy ? (de ? "Wird gesendet …" : "Sending …") : (de ? "Kommentar senden" : "Post comment")}</button>{/if}</div>
|
||||
</div>
|
||||
<style>
|
||||
.md-editor{width:100%;min-width:0;color:var(--color-ink);font:400 12px/1.5 var(--font-sans)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import SelectMenu from "./SelectMenu.svelte";
|
||||
import CommentEditor from "./CommentEditor.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import { GitBranch, GitPullRequest, LockKeyhole, X, LoaderCircle, ArrowRight, Sparkles } from "@lucide/svelte";
|
||||
import { credLoad, pullRequestAiGenerate, createIntegrationReviewRequest, listIntegrationRepositories, listIntegrationRepositoryBranches, listRemotes, listBranches } from "../git";
|
||||
@@ -169,7 +170,13 @@
|
||||
<p class="hint">{de ? "Beide Branches müssen bereits in diesem Repository gepusht sein." : "Both branches must already be pushed to this repository."}</p>
|
||||
<div class="ai-draft-action"><button type="button" disabled={generating || busy || branchesLoading || !sourceBranch || !targetBranch || sameBranch} onclick={generateDraft}>{#if generating}<LoaderCircle class="spin" size={15}/>{:else}<Sparkles size={15}/>{/if}{generating ? (de ? "Wird generiert …" : "Generating…") : (de ? "Mit KI erstellen" : "Generate with AI")}</button><small>{de ? "Erstellt Titel und Beschreibung aus dem lokalen Stand der Remote-Branches. Vorher Fetch ausführen." : "Creates a title and description from locally fetched remote branches. Fetch first."}</small></div>
|
||||
<label>{de ? "Titel" : "Title"}<input bind:this={titleInput} bind:value={title} disabled={generating || busy} placeholder={de ? "Was ändert sich?" : "What is changing?"} required /></label>
|
||||
<label>{de ? "Beschreibung" : "Description"}<textarea bind:value={description} disabled={generating || busy} rows="7" placeholder={de ? "Beschreibe deine Änderungen … (Markdown unterstützt)" : "Describe your changes… (Markdown supported)"}></textarea></label>
|
||||
<div class="repository-field">
|
||||
<span>{de ? "Beschreibung" : "Description"}</span>
|
||||
<CommentEditor bind:value={description} language={de ? "de" : "en"} disabled={generating || busy} rows={7}
|
||||
ariaLabel={de ? "Beschreibung" : "Description"}
|
||||
previewLabel={de ? "Beschreibungsvorschau" : "Description preview"}
|
||||
placeholder={de ? "Beschreibe deine Änderungen …" : "Describe your changes…"} />
|
||||
</div>
|
||||
</div>
|
||||
<footer><button type="button" disabled={generating || busy} onclick={onClose}>{de ? "Abbrechen" : "Cancel"}</button><button class="primary" type="submit" disabled={generating || busy || loading || !valid}>{#if busy}<LoaderCircle class="spin" size={15}/>{:else}<GitPullRequest size={15}/>{/if}{busy ? (de ? "Wird erstellt …" : "Creating…") : heading}</button></footer>
|
||||
</form>
|
||||
@@ -178,5 +185,5 @@
|
||||
<style>
|
||||
.ai-draft-action{display:flex;align-items:center;gap:12px}.ai-draft-action small{color:var(--color-ink-muted);line-height:1.5}.ai-draft-action button{flex-shrink:0}
|
||||
.repository-field{min-width:0;display:grid;gap:9px}.field-heading{display:flex;align-items:center;justify-content:space-between;font-weight:600}.field-heading small{font-size:10px;font-weight:400;color:var(--color-ink-faint)}
|
||||
dialog{margin:auto;width:min(640px,calc(100vw - 40px));max-height:calc(100vh - 48px);padding:0;border:1px solid var(--color-border-subtle);border-radius:14px;background:var(--app-dialog-bg);color:var(--color-ink);box-shadow:var(--app-dialog-shadow);font-family:inherit;font-size:12px;overflow:auto}dialog::backdrop{background:color-mix(in srgb, var(--app-dialog-backdrop) 92%, transparent);backdrop-filter:blur(3px)}header{display:flex;align-items:center;gap:12px;padding:22px 26px;background:var(--app-dialog-chrome);border-bottom:1px solid var(--color-border-subtle)}h2{margin:0;font-size:16px;font-weight:650}p{margin:5px 0 0;color:var(--color-ink-muted)}.heading-icon{display:grid;place-items:center;width:38px;height:38px;border-radius:10px;background:color-mix(in srgb,var(--color-accent) 12%,transparent);color:var(--color-accent)}button,input,textarea{font:inherit}button{display:inline-flex;justify-content:center;align-items:center;gap:8px;border:1px solid var(--color-border-subtle);border-radius:7px;padding:9px 13px;background:var(--color-surface);color:var(--color-ink);cursor:pointer}button:disabled{opacity:.5;cursor:default}.close{margin-left:auto;border:0;padding:6px}.body{display:grid;gap:20px;padding:24px 26px}label{display:grid;gap:8px;font-weight:600;min-width:0}input,textarea{box-sizing:border-box;width:100%;padding:10px 11px;border:1px solid var(--color-border-subtle);border-radius:7px;background:var(--app-bg);color:var(--color-ink);font-weight:400}input:focus,textarea:focus{outline:2px solid var(--color-accent);outline-offset:1px}textarea{resize:vertical;min-height:110px;line-height:1.6}.branches{padding:16px;background:color-mix(in srgb,var(--color-accent) 3%,var(--app-bg));border:1px solid var(--color-border-subtle);display:grid;grid-template-columns:minmax(0,1fr) 16px minmax(0,1fr);gap:12px;align-items:end}.branches>:global(svg){margin-bottom:12px;color:var(--color-accent)}.hint{margin-top:-8px;font-size:11px;line-height:1.5}.error,.validation{color:var(--color-danger);line-height:1.5}.error{padding:12px;border-radius:7px;background:color-mix(in srgb,var(--color-danger) 9%,transparent);overflow-wrap:anywhere}.error button{margin-left:8px}footer{display:flex;justify-content:flex-end;gap:9px;padding:16px 26px;background:var(--app-dialog-chrome);border-top:1px solid var(--color-border-subtle)}.primary{background:var(--color-accent-solid);border-color:var(--color-accent-solid);color:var(--color-on-accent)}.primary:enabled:hover{filter:brightness(1.08)}:global(.spin){animation:rotate 1s linear infinite}@keyframes rotate{to{transform:rotate(360deg)}}
|
||||
dialog{margin:auto;width:min(640px,calc(100vw - 40px));max-height:calc(100vh - 48px);padding:0;border:1px solid var(--color-border-subtle);border-radius:14px;background:var(--app-dialog-bg);color:var(--color-ink);box-shadow:var(--app-dialog-shadow);font-family:inherit;font-size:12px;overflow:auto}dialog::backdrop{background:color-mix(in srgb, var(--app-dialog-backdrop) 92%, transparent);backdrop-filter:blur(3px)}header{display:flex;align-items:center;gap:12px;padding:22px 26px;background:var(--app-dialog-chrome);border-bottom:1px solid var(--color-border-subtle)}h2{margin:0;font-size:16px;font-weight:650}p{margin:5px 0 0;color:var(--color-ink-muted)}.heading-icon{display:grid;place-items:center;width:38px;height:38px;border-radius:10px;background:color-mix(in srgb,var(--color-accent) 12%,transparent);color:var(--color-accent)}button,input{font:inherit}button{display:inline-flex;justify-content:center;align-items:center;gap:8px;border:1px solid var(--color-border-subtle);border-radius:7px;padding:9px 13px;background:var(--color-surface);color:var(--color-ink);cursor:pointer}button:disabled{opacity:.5;cursor:default}.close{margin-left:auto;border:0;padding:6px}.body{display:grid;gap:20px;padding:24px 26px}label{display:grid;gap:8px;font-weight:600;min-width:0}input{box-sizing:border-box;width:100%;padding:10px 11px;border:1px solid var(--color-border-subtle);border-radius:7px;background:var(--app-bg);color:var(--color-ink);font-weight:400}input:focus{outline:2px solid var(--color-accent);outline-offset:1px}.branches{padding:16px;background:color-mix(in srgb,var(--color-accent) 3%,var(--app-bg));border:1px solid var(--color-border-subtle);display:grid;grid-template-columns:minmax(0,1fr) 16px minmax(0,1fr);gap:12px;align-items:end}.branches>:global(svg){margin-bottom:12px;color:var(--color-accent)}.hint{margin-top:-8px;font-size:11px;line-height:1.5}.error,.validation{color:var(--color-danger);line-height:1.5}.error{padding:12px;border-radius:7px;background:color-mix(in srgb,var(--color-danger) 9%,transparent);overflow-wrap:anywhere}.error button{margin-left:8px}footer{display:flex;justify-content:flex-end;gap:9px;padding:16px 26px;background:var(--app-dialog-chrome);border-top:1px solid var(--color-border-subtle)}.primary{background:var(--color-accent-solid);border-color:var(--color-accent-solid);color:var(--color-on-accent)}.primary:enabled:hover{filter:brightness(1.08)}:global(.spin){animation:rotate 1s linear infinite}@keyframes rotate{to{transform:rotate(360deg)}}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user