Initial release: Ironpad v0.1.0 - Local-first, file-based project and knowledge management system. Rust backend, Vue 3 frontend, Milkdown editor, Git integration, cross-platform builds. Built with AI using Open Method.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
skepsismusic
2026-02-06 00:13:31 +01:00
commit ebe3e2aa8f
97 changed files with 25033 additions and 0 deletions

View File

@@ -0,0 +1,322 @@
<script setup lang="ts">
import { ref } from 'vue'
const emit = defineEmits<{
'format': [type: string, extra?: string]
'insert-image': [file: File]
'insert-link': []
}>()
const fileInput = ref<HTMLInputElement | null>(null)
// Formatting actions
function bold() { emit('format', 'bold') }
function italic() { emit('format', 'italic') }
function strikethrough() { emit('format', 'strikethrough') }
function heading(level: number) { emit('format', 'heading', String(level)) }
function link() { emit('insert-link') }
function code() { emit('format', 'code') }
function codeBlock() { emit('format', 'codeblock') }
function quote() { emit('format', 'quote') }
function bulletList() { emit('format', 'bullet') }
function numberedList() { emit('format', 'numbered') }
function taskList() { emit('format', 'task') }
function horizontalRule() { emit('format', 'hr') }
// Image handling
function triggerImageUpload() {
fileInput.value?.click()
}
function handleFileSelect(event: Event) {
const input = event.target as HTMLInputElement
const file = input.files?.[0]
if (file) {
emit('insert-image', file)
// Reset input so same file can be selected again
input.value = ''
}
}
// Heading dropdown
const showHeadingDropdown = ref(false)
function toggleHeadingDropdown() {
showHeadingDropdown.value = !showHeadingDropdown.value
}
function selectHeading(level: number) {
heading(level)
showHeadingDropdown.value = false
}
// Close dropdown when clicking outside
function closeDropdowns() {
showHeadingDropdown.value = false
}
</script>
<template>
<div class="editor-toolbar" @click.stop>
<!-- Hidden file input for image upload -->
<input
ref="fileInput"
type="file"
accept="image/*"
style="display: none"
@change="handleFileSelect"
/>
<!-- Text formatting group -->
<div class="toolbar-group">
<button
class="toolbar-btn"
@click="bold"
title="Bold (Ctrl+B)"
>
<span class="icon">B</span>
</button>
<button
class="toolbar-btn"
@click="italic"
title="Italic (Ctrl+I)"
>
<span class="icon italic">I</span>
</button>
<button
class="toolbar-btn"
@click="strikethrough"
title="Strikethrough"
>
<span class="icon strikethrough">S</span>
</button>
<button
class="toolbar-btn"
@click="code"
title="Inline code (Ctrl+`)"
>
<span class="icon mono">&lt;/&gt;</span>
</button>
</div>
<div class="toolbar-divider"></div>
<!-- Heading dropdown -->
<div class="toolbar-group">
<div class="dropdown-container">
<button
class="toolbar-btn"
@click="toggleHeadingDropdown"
title="Heading"
>
<span class="icon">H</span>
<span class="dropdown-arrow"></span>
</button>
<div v-if="showHeadingDropdown" class="dropdown-menu" @click.stop>
<button @click="selectHeading(1)">Heading 1</button>
<button @click="selectHeading(2)">Heading 2</button>
<button @click="selectHeading(3)">Heading 3</button>
<button @click="selectHeading(4)">Heading 4</button>
</div>
</div>
</div>
<div class="toolbar-divider"></div>
<!-- Insert group -->
<div class="toolbar-group">
<button
class="toolbar-btn"
@click="link"
title="Insert link"
>
<span class="icon">🔗</span>
</button>
<button
class="toolbar-btn"
@click="triggerImageUpload"
title="Insert image"
>
<span class="icon">🖼</span>
</button>
<button
class="toolbar-btn"
@click="codeBlock"
title="Code block"
>
<span class="icon mono">{}</span>
</button>
</div>
<div class="toolbar-divider"></div>
<!-- List group -->
<div class="toolbar-group">
<button
class="toolbar-btn"
@click="bulletList"
title="Bullet list"
>
<span class="icon"></span>
</button>
<button
class="toolbar-btn"
@click="numberedList"
title="Numbered list"
>
<span class="icon">1.</span>
</button>
<button
class="toolbar-btn"
@click="taskList"
title="Task list"
>
<span class="icon"></span>
</button>
</div>
<div class="toolbar-divider"></div>
<!-- Block group -->
<div class="toolbar-group">
<button
class="toolbar-btn"
@click="quote"
title="Quote"
>
<span class="icon">"</span>
</button>
<button
class="toolbar-btn"
@click="horizontalRule"
title="Horizontal rule"
>
<span class="icon">—</span>
</button>
</div>
<!-- Click outside to close dropdowns -->
<div
v-if="showHeadingDropdown"
class="dropdown-overlay"
@click="closeDropdowns"
></div>
</div>
</template>
<style scoped>
.editor-toolbar {
display: flex;
align-items: center;
gap: 4px;
padding: 8px 12px;
background: var(--color-bg-secondary);
border-bottom: 1px solid var(--color-border);
flex-wrap: wrap;
}
.toolbar-group {
display: flex;
align-items: center;
gap: 2px;
}
.toolbar-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 28px;
padding: 0;
border: none;
border-radius: 4px;
background: transparent;
color: var(--color-text);
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.toolbar-btn:hover {
background: var(--color-bg-hover);
}
.toolbar-btn:active {
background: var(--color-border);
}
.toolbar-btn .icon {
font-size: 14px;
font-weight: 600;
}
.toolbar-btn .icon.italic {
font-style: italic;
}
.toolbar-btn .icon.strikethrough {
text-decoration: line-through;
}
.toolbar-btn .icon.mono {
font-family: var(--font-mono);
font-size: 11px;
font-weight: 500;
}
.toolbar-btn .dropdown-arrow {
font-size: 8px;
margin-left: 2px;
color: var(--color-text-secondary);
}
.toolbar-divider {
width: 1px;
height: 20px;
background: var(--color-border);
margin: 0 6px;
}
/* Dropdown */
.dropdown-container {
position: relative;
}
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
margin-top: 4px;
background: var(--color-bg);
border: 1px solid var(--color-border);
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 100;
min-width: 120px;
overflow: hidden;
}
.dropdown-menu button {
display: block;
width: 100%;
padding: 8px 12px;
border: none;
background: transparent;
color: var(--color-text);
text-align: left;
cursor: pointer;
font-size: 13px;
}
.dropdown-menu button:hover {
background: var(--color-bg-hover);
}
.dropdown-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 99;
}
</style>