Compare commits

5 Commits

Author SHA1 Message Date
Julien Calixte
f89c2fa776 style: just changed the view latest button
All checks were successful
CI / verify (push) Successful in 2m7s
2026-07-12 12:36:54 +02:00
Julien Calixte
c21619c53a feat(notes): drop the redundant Close button from share popup
All checks were successful
CI / verify (push) Successful in 1m6s
The backdrop already dismisses the dialog on outside click.
2026-07-11 12:57:44 +02:00
Julien Calixte
b34b5851cd fix(notes): color the share trigger icon with accent
Scoped styles in HeaderNote don't cross into the ShareNote child
component, so text-base-content painted the icon white/black instead
of matching the other header icons.
2026-07-11 12:57:41 +02:00
Julien Calixte
9581dc88aa fix(notes): don't save empty content over a note that hasn't loaded
All checks were successful
CI / verify (push) Successful in 1m9s
Entering and exiting edit mode while a note was still loading (or after a
failed fetch) triggered a save of the empty placeholder, clobbering the
real file on GitHub with an empty commit. Gate the dirty check, performSave,
and the edit button on a loaded baseline.
2026-07-11 12:51:27 +02:00
Julien Calixte
40b0af6b01 style(welcome): tighten landing copy and reduce em-dash use
All checks were successful
CI / verify (push) Successful in 1m7s
Rework the lede parenthetical and thin the calm/quiet/pool
metaphor where it doubled up on itself, to soften AI-writing tells.
2026-07-11 12:21:15 +02:00
3 changed files with 32 additions and 36 deletions

View File

@@ -55,7 +55,7 @@ const copy = async (url: string, label: string) => {
<template>
<button
class="btn btn-ghost btn-circle text-base-content"
class="btn btn-ghost btn-circle text-accent"
title="Share this view"
aria-label="Share this view"
onclick="share_modal.showModal()"
@@ -148,12 +148,6 @@ const copy = async (url: string, label: string) => {
</span>
</span>
</button>
<div class="modal-action">
<form method="dialog">
<button class="btn btn-ghost">Close</button>
</form>
</div>
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>

View File

@@ -1,11 +1,5 @@
<script lang="ts" setup>
import {
computed,
defineAsyncComponent,
onMounted,
ref,
watch
} from "vue"
import { computed, defineAsyncComponent, onMounted, ref, watch } from "vue"
import { useEditionMode } from "@/hooks/useEditionMode"
import { useFile } from "@/hooks/useFile.hook"
@@ -188,6 +182,19 @@ const loadNote = async () => {
onMounted(loadNote)
// A note only has a trustworthy baseline once it has finished loading. While
// it is still loading (or after a failed load) rawContent holds the empty
// placeholder and initialRawContent is null, so a naive
// `rawContent !== initialRawContent` check reads as a huge edit — and saving
// that would clobber the real file on GitHub with an empty commit. Gate every
// "is this dirty?" decision on a loaded baseline.
const isDirty = computed(
() =>
loadStatus.value === "ready" &&
initialRawContent.value !== null &&
rawContent.value !== initialRawContent.value
)
watch(
path,
(p) => {
@@ -204,8 +211,7 @@ useMarkdownPostRender(content, () => `.note-${sha.value}`, {
macroplan: true,
mermaid: () => rawContent.value.includes("```mermaid"),
shikiji: () => isMarkdown.value && rawContent.value.includes("```"),
images: () =>
/\!\[.*?\]\(.*?\)/.test(rawContent.value) ? props.sha : null,
images: () => (/\!\[.*?\]\(.*?\)/.test(rawContent.value) ? props.sha : null),
triggers: [mode]
})
@@ -215,6 +221,13 @@ const performSave = async (overrideSha?: string) => {
return
}
// Defence in depth: never push content we didn't successfully load, or we'd
// overwrite the file on GitHub with the empty placeholder.
if (loadStatus.value !== "ready" || initialRawContent.value === null) {
console.warn("refusing to save a note that hasn't finished loading")
return
}
const editedSha = overrideSha ?? (await getEditedSha()) ?? sha.value
const { sha: newSha, conflict } = await updateFile({
content: rawContent.value,
@@ -287,8 +300,7 @@ watch(mode, async (newMode) => {
return
}
const hasUserFinishedToEdit =
newMode === "read" && rawContent.value !== initialRawContent.value
const hasUserFinishedToEdit = newMode === "read" && isDirty.value
if (!hasUserFinishedToEdit) {
return
@@ -336,7 +348,7 @@ const onBadgeClick = async () => {
return
}
const hasUnsavedEdits = rawContent.value !== initialRawContent.value
const hasUnsavedEdits = isDirty.value
if (hasUnsavedEdits) {
await handleConflict()
return
@@ -377,7 +389,7 @@ const onBadgeClick = async () => {
class="action"
/>
<button
v-if="isMarkdown && canPush"
v-if="isMarkdown && canPush && loadStatus === 'ready'"
class="action button is-text is-light"
:class="{ 'is-link': mode === 'edit' }"
:style="mode === 'edit' ? 'color: var(--color-primary)' : ''"
@@ -489,7 +501,7 @@ const onBadgeClick = async () => {
<span>You're viewing an older shared version.</span>
<button
type="button"
class="snapshot-banner-action"
class="btn bt-sm btn-soft btn-info"
@click="viewLatest"
>
View latest
@@ -588,16 +600,6 @@ $border-color: rgba(18, 19, 58, 0.2);
font-size: 0.85em;
}
.snapshot-banner-action {
flex-shrink: 0;
padding: 0;
border: 0;
background: transparent;
color: var(--color-primary);
cursor: pointer;
text-decoration: underline;
}
.action {
margin: 0;

View File

@@ -381,10 +381,10 @@ onMounted(() => {
comes to rest.
</h1>
<p class="lede">
Point it at any markdown a GitHub repo of your own, or public
notes from the open ATProto network and Remanso turns it into a
calm, stackable notebook with backlinks where your thinking
finally runs clear.
Point it at any markdown: a GitHub repo of your own, or public
notes from the open ATProto network. Remanso turns it into a
stackable notebook with backlinks where your thinking finally
runs clear.
</p>
<div class="hero-ed-paths">
<!-- CTA 01: GitHub repo -->
@@ -472,7 +472,7 @@ onMounted(() => {
</div>
</div>
<div v-else-if="publicNotesLoadFailed" class="network-empty">
The pool is quiet right now.
Nothing has drifted in yet.
</div>
<router-link
:to="{ name: 'PublicNoteListView' }"