feat(web): implement complete workspace with themes, tabs, sidebar, and mobile
Transform CalcText from a single-document calculator into a full workspace application with multi-document support, theming, and responsive mobile experience. - Theme system: 5 presets (Light, Dark, Matrix, Midnight, Warm) + accent colors - Document model with localStorage persistence and auto-save - Tab bar with keyboard shortcuts (Ctrl+N/W/Tab/1-9), rename, close - Sidebar with search, recent, favorites, folders, templates, drag-and-drop - 5 templates: Budget, Invoice, Unit Converter, Trip Planner, Loan Calculator - Status bar with cursor position, engine status, dedication to Igor Cassel - Results panel: type-specific colors, click-to-copy, error hints - Format toolbar: H, B, I, //, color labels with live preview toggle - Syntax highlighting using theme CSS variables - Error hover tooltips - Mobile: bottom results tray, sidebar drawer, touch targets, safe areas - Docker multi-stage build (Rust WASM + Vite + Nginx) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,8 @@ import {
|
||||
GutterMarker,
|
||||
gutter,
|
||||
EditorView,
|
||||
hoverTooltip,
|
||||
type Tooltip,
|
||||
} from '@codemirror/view'
|
||||
import { StateField, StateEffect, type Extension, RangeSet } from '@codemirror/state'
|
||||
|
||||
@@ -98,6 +100,48 @@ export const errorLinesField = StateField.define<Set<number>>({
|
||||
},
|
||||
})
|
||||
|
||||
// --- Error Messages (for tooltips) ---
|
||||
|
||||
export const errorMessagesField = StateField.define<Map<number, string>>({
|
||||
create() {
|
||||
return new Map()
|
||||
},
|
||||
update(msgs, tr) {
|
||||
for (const effect of tr.effects) {
|
||||
if (effect.is(setErrorsEffect)) {
|
||||
const newMsgs = new Map<number, string>()
|
||||
for (const error of effect.value) {
|
||||
const lineNumber = tr.state.doc.lineAt(error.from).number
|
||||
newMsgs.set(lineNumber, error.message)
|
||||
}
|
||||
return newMsgs
|
||||
}
|
||||
}
|
||||
return msgs
|
||||
},
|
||||
})
|
||||
|
||||
// --- Error Tooltip (hover) ---
|
||||
|
||||
const errorTooltip = hoverTooltip((view, pos) => {
|
||||
const line = view.state.doc.lineAt(pos)
|
||||
const errorMessages = view.state.field(errorMessagesField)
|
||||
const msg = errorMessages.get(line.number)
|
||||
if (!msg) return null
|
||||
|
||||
return {
|
||||
pos: line.from,
|
||||
end: line.to,
|
||||
above: false,
|
||||
create() {
|
||||
const dom = document.createElement('div')
|
||||
dom.className = 'cm-error-tooltip'
|
||||
dom.textContent = msg
|
||||
return { dom }
|
||||
},
|
||||
} satisfies Tooltip
|
||||
})
|
||||
|
||||
// --- Error Gutter ---
|
||||
|
||||
export const errorGutter = gutter({
|
||||
@@ -118,15 +162,32 @@ export const errorGutter = gutter({
|
||||
|
||||
export const errorBaseTheme = EditorView.baseTheme({
|
||||
'.cm-error-underline': {
|
||||
textDecoration: 'underline wavy red',
|
||||
textDecoration: 'underline wavy var(--error, red)',
|
||||
textDecorationThickness: '1.5px',
|
||||
},
|
||||
'.cm-error-marker': {
|
||||
color: '#e53e3e',
|
||||
color: 'var(--error, #e53e3e)',
|
||||
fontSize: '14px',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
'.cm-error-gutter': {
|
||||
width: '20px',
|
||||
},
|
||||
'.cm-error-tooltip': {
|
||||
backgroundColor: 'var(--bg-secondary, #f8f9fa)',
|
||||
color: 'var(--error, #e53e3e)',
|
||||
border: '1px solid var(--border, #e5e4e7)',
|
||||
borderRadius: '4px',
|
||||
padding: '4px 8px',
|
||||
fontSize: '12px',
|
||||
fontFamily: 'var(--sans, system-ui)',
|
||||
maxWidth: '300px',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
|
||||
},
|
||||
'.cm-tooltip': {
|
||||
border: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -136,7 +197,9 @@ export function errorDisplayExtension(): Extension {
|
||||
return [
|
||||
errorDecorationsField,
|
||||
errorLinesField,
|
||||
errorMessagesField,
|
||||
errorGutter,
|
||||
errorTooltip,
|
||||
errorBaseTheme,
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user