file viewer: edit Markdown with optimistic-lock conflict detection
Nightly Build / build (push) Successful in 6m47s
Nightly Build / build (push) Successful in 6m47s
- GET /api/file returns ETag (mtime+size) + X-Writable on disk files; PUT /api/file accepts optional if_match -> 409 Conflict on stale version (last-write-wins preserved when omitted), echoes the new ETag - FileViewerBase: View | Edit tabs for .md when the caller can write; source textarea with Save/Cancel, live preview while editing - Watcher no longer clobbers the buffer mid-edit: while editing with unsaved changes it probes the server ETag and only raises a conflict when the remote actually moved on (own-save echo is ignored) - Conflict banner: Reload remote | Copy mine, then reload | Overwrite - i18n (en/it/fr) + CSS; docs/projects.md updated
This commit is contained in:
@@ -53,6 +53,124 @@
|
||||
|
||||
/* ── Markdown preview ────────────────────────────────────────────────────────── */
|
||||
|
||||
.fv-md-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
/* View | Edit tab bar — shown only when the caller can write the file. */
|
||||
.fv-md-tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.15rem;
|
||||
padding: 0.4rem 1rem 0;
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.fv-md-tab {
|
||||
appearance: none;
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
padding: 0.35rem 0.85rem;
|
||||
margin-bottom: -1px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--bs-secondary-color);
|
||||
cursor: pointer;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
.fv-md-tab:hover { color: var(--bs-body-color); }
|
||||
.fv-md-tab.active {
|
||||
color: var(--bs-body-color);
|
||||
border-bottom-color: var(--accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
.fv-md-dirty-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
margin-left: 0.35rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── Source editor (Edit tab) ───────────────────────────────────────────────── */
|
||||
|
||||
.fv-edit {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
padding: 0.75rem 1rem 1rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.fv-edit-textarea {
|
||||
flex: 1 1 auto;
|
||||
min-height: 50vh;
|
||||
width: 100%;
|
||||
resize: vertical;
|
||||
padding: 0.85rem 1rem;
|
||||
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.55;
|
||||
tab-size: 4;
|
||||
color: var(--bs-body-color);
|
||||
background: var(--bs-secondary-bg);
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 6px;
|
||||
white-space: pre;
|
||||
overflow-wrap: normal;
|
||||
overflow: auto;
|
||||
}
|
||||
.fv-edit-textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 30%, transparent);
|
||||
}
|
||||
.fv-edit-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.fv-edit-hint {
|
||||
font-size: 0.78rem;
|
||||
color: var(--bs-secondary-color);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* ── Conflict banner (remote changed while editing) ─────────────────────────── */
|
||||
|
||||
.fv-conflict-banner {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
padding: 0.6rem 0.85rem;
|
||||
background: var(--bs-warning-bg-subtle, #fff3cd);
|
||||
border: 1px solid var(--bs-warning-border-subtle, #ffe69c);
|
||||
color: var(--bs-emphasis-color, inherit);
|
||||
border-radius: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.fv-conflict-banner > .bi {
|
||||
color: var(--bs-warning-text-emphasis, #664d03);
|
||||
font-size: 1.05rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.fv-conflict-text {
|
||||
flex: 1 1 200px;
|
||||
font-size: 0.82rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.fv-conflict-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.fv-md {
|
||||
max-width: 100%;
|
||||
padding: 1.5rem 2rem;
|
||||
|
||||
Reference in New Issue
Block a user