generated from nhcarrigan/template
feat: add built-in file editor with syntax highlighting #79
@@ -31,30 +31,101 @@
|
|||||||
<svelte:window onkeydown={handleKeydown} />
|
<svelte:window onkeydown={handleKeydown} />
|
||||||
|
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50" onclick={onCancel}>
|
<div class="dialog-overlay" onclick={onCancel}>
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div
|
<div class="dialog-content" onclick={(e) => e.stopPropagation()}>
|
||||||
class="mx-4 w-full max-w-md rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-xl"
|
<h2 class="dialog-title">{title}</h2>
|
||||||
onclick={(e) => e.stopPropagation()}
|
<p class="dialog-message">{message}</p>
|
||||||
>
|
|
||||||
<h2 class="mb-2 text-lg font-semibold text-gray-100">{title}</h2>
|
|
||||||
<p class="mb-6 text-sm text-gray-300">{message}</p>
|
|
||||||
|
|
||||||
<div class="flex justify-end gap-3">
|
<div class="dialog-actions">
|
||||||
<button
|
<button class="btn-cancel" onclick={onCancel}>
|
||||||
class="rounded-md border border-gray-600 bg-gray-700 px-4 py-2 text-sm text-gray-200 hover:bg-gray-600"
|
|
||||||
onclick={onCancel}
|
|
||||||
>
|
|
||||||
{cancelText}
|
{cancelText}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button class="btn-confirm" class:destructive onclick={onConfirm}>
|
||||||
class="rounded-md px-4 py-2 text-sm text-white {destructive
|
|
||||||
? 'bg-red-600 hover:bg-red-700'
|
|
||||||
: 'bg-pink-600 hover:bg-pink-700'}"
|
|
||||||
onclick={onConfirm}
|
|
||||||
>
|
|
||||||
{confirmText}
|
{confirmText}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.dialog-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 50;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content {
|
||||||
|
margin: 0 1rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 28rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
padding: 1.5rem;
|
||||||
|
box-shadow:
|
||||||
|
0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
||||||
|
0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-title {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-message {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background-color: var(--bg-primary);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel:hover {
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-confirm {
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
border: none;
|
||||||
|
background-color: var(--accent-primary);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-confirm:hover {
|
||||||
|
background-color: var(--accent-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-confirm.destructive {
|
||||||
|
background-color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-confirm.destructive:hover {
|
||||||
|
background-color: #b91c1c;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
|
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div
|
<div
|
||||||
class="fixed inset-0 z-50"
|
class="menu-overlay"
|
||||||
onclick={onClose}
|
onclick={onClose}
|
||||||
oncontextmenu={(e) => {
|
oncontextmenu={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -70,16 +70,9 @@
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div
|
<div class="menu-content" style="left: {x}px; top: {y}px;" onclick={(e) => e.stopPropagation()}>
|
||||||
class="absolute z-50 min-w-[160px] rounded-md border border-gray-700 bg-gray-800 py-1 shadow-lg"
|
<button class="menu-item" onclick={handleNewFile}>
|
||||||
style="left: {x}px; top: {y}px;"
|
<svg class="menu-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
onclick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="flex w-full items-center gap-2 px-3 py-1.5 text-left text-sm text-gray-200 hover:bg-gray-700"
|
|
||||||
onclick={handleNewFile}
|
|
||||||
>
|
|
||||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
@@ -90,11 +83,8 @@
|
|||||||
New File
|
New File
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button class="menu-item" onclick={handleNewFolder}>
|
||||||
class="flex w-full items-center gap-2 px-3 py-1.5 text-left text-sm text-gray-200 hover:bg-gray-700"
|
<svg class="menu-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
onclick={handleNewFolder}
|
|
||||||
>
|
|
||||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
@@ -106,13 +96,10 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{#if targetEntry}
|
{#if targetEntry}
|
||||||
<div class="my-1 border-t border-gray-700"></div>
|
<div class="menu-divider"></div>
|
||||||
|
|
||||||
<button
|
<button class="menu-item" onclick={handleRename}>
|
||||||
class="flex w-full items-center gap-2 px-3 py-1.5 text-left text-sm text-gray-200 hover:bg-gray-700"
|
<svg class="menu-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
onclick={handleRename}
|
|
||||||
>
|
|
||||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
@@ -123,11 +110,8 @@
|
|||||||
Rename
|
Rename
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button class="menu-item destructive" onclick={handleDelete}>
|
||||||
class="flex w-full items-center gap-2 px-3 py-1.5 text-left text-sm text-red-400 hover:bg-gray-700"
|
<svg class="menu-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
onclick={handleDelete}
|
|
||||||
>
|
|
||||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
@@ -140,3 +124,57 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.menu-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-content {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 50;
|
||||||
|
min-width: 160px;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
padding: 0.25rem 0;
|
||||||
|
box-shadow:
|
||||||
|
0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
||||||
|
0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item:hover {
|
||||||
|
background-color: var(--bg-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item.destructive {
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-divider {
|
||||||
|
margin: 0.25rem 0;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-icon {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
onCancel,
|
onCancel,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
// svelte-ignore state_referenced_locally - intentionally capture initial value once
|
|
||||||
let inputValue = $state(initialValue);
|
let inputValue = $state(initialValue);
|
||||||
let inputElement: HTMLInputElement | undefined = $state();
|
let inputElement: HTMLInputElement | undefined = $state();
|
||||||
|
|
||||||
@@ -52,36 +51,123 @@
|
|||||||
<svelte:window onkeydown={handleKeydown} />
|
<svelte:window onkeydown={handleKeydown} />
|
||||||
|
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50" onclick={onCancel}>
|
<div class="dialog-overlay" onclick={onCancel}>
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div
|
<div class="dialog-content" onclick={(e) => e.stopPropagation()}>
|
||||||
class="mx-4 w-full max-w-md rounded-lg border border-gray-700 bg-gray-800 p-6 shadow-xl"
|
<h2 class="dialog-title">{title}</h2>
|
||||||
onclick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<h2 class="mb-4 text-lg font-semibold text-gray-100">{title}</h2>
|
|
||||||
|
|
||||||
<input
|
<input
|
||||||
bind:this={inputElement}
|
bind:this={inputElement}
|
||||||
bind:value={inputValue}
|
bind:value={inputValue}
|
||||||
type="text"
|
type="text"
|
||||||
{placeholder}
|
{placeholder}
|
||||||
class="mb-6 w-full rounded-md border border-gray-600 bg-gray-700 px-3 py-2 text-sm text-gray-100 placeholder-gray-400 focus:border-pink-500 focus:outline-none focus:ring-1 focus:ring-pink-500"
|
class="dialog-input"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="flex justify-end gap-3">
|
<div class="dialog-actions">
|
||||||
<button
|
<button class="btn-cancel" onclick={onCancel}>
|
||||||
class="rounded-md border border-gray-600 bg-gray-700 px-4 py-2 text-sm text-gray-200 hover:bg-gray-600"
|
|
||||||
onclick={onCancel}
|
|
||||||
>
|
|
||||||
{cancelText}
|
{cancelText}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button class="btn-confirm" onclick={handleSubmit} disabled={!inputValue.trim()}>
|
||||||
class="rounded-md bg-pink-600 px-4 py-2 text-sm text-white hover:bg-pink-700 disabled:cursor-not-allowed disabled:opacity-50"
|
|
||||||
onclick={handleSubmit}
|
|
||||||
disabled={!inputValue.trim()}
|
|
||||||
>
|
|
||||||
{confirmText}
|
{confirmText}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.dialog-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 50;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content {
|
||||||
|
margin: 0 1rem;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 28rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
padding: 1.5rem;
|
||||||
|
box-shadow:
|
||||||
|
0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
||||||
|
0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-title {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-input {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background-color: var(--bg-primary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
outline: none;
|
||||||
|
transition:
|
||||||
|
border-color 0.15s ease,
|
||||||
|
box-shadow 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-input::placeholder {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-input:focus {
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
box-shadow: 0 0 0 1px var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
background-color: var(--bg-primary);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel:hover {
|
||||||
|
background-color: var(--bg-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-confirm {
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
border: none;
|
||||||
|
background-color: var(--accent-primary);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-confirm:hover {
|
||||||
|
background-color: var(--accent-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-confirm:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user