Compare commits
7 Commits
b720759c89
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f89c2fa776 | ||
|
|
c21619c53a | ||
|
|
b34b5851cd | ||
|
|
9581dc88aa | ||
|
|
40b0af6b01 | ||
|
|
2592b19c8f | ||
|
|
99edff828a |
@@ -38,6 +38,10 @@ Tailwind v4 uses the modern CSS-based config in `src/styles/app.css`:
|
||||
- DaisyUI configured via `@plugin 'daisyui' { themes: retro --default, coffee --prefersdark; }`
|
||||
- `@tailwindcss/typography` for prose styling
|
||||
|
||||
### Icons
|
||||
|
||||
Use [Tabler icons](https://tabler.io/icons) for every icon. Paste the outline SVG inline and keep `stroke="currentColor"` so it inherits color from its parent (icons in the note header render in `--color-accent`).
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```sh
|
||||
|
||||
@@ -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()"
|
||||
@@ -90,7 +90,25 @@ const copy = async (url: string, label: string) => {
|
||||
class="share-option"
|
||||
@click="copy(snapshotUrl(), 'Snapshot')"
|
||||
>
|
||||
<span class="share-option-icon">📌</span>
|
||||
<span class="share-option-icon">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="icon icon-tabler icon-tabler-pin"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M15 4.5l-4 4l-4 1.5l-1.5 1.5l7 7l1.5 -1.5l1.5 -4l4 -4" />
|
||||
<path d="M9 15l-4.5 4.5" />
|
||||
<path d="M14.5 4l5.5 5.5" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="share-option-text">
|
||||
<span class="share-option-title">This exact version</span>
|
||||
<span class="share-option-desc">
|
||||
@@ -105,20 +123,31 @@ const copy = async (url: string, label: string) => {
|
||||
class="share-option"
|
||||
@click="copy(livingUrl(), 'Always-latest')"
|
||||
>
|
||||
<span class="share-option-icon">🔄</span>
|
||||
<span class="share-option-icon">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="icon icon-tabler icon-tabler-repeat"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M4 12v-3a3 3 0 0 1 3 -3h13m-3 -3l3 3l-3 3" />
|
||||
<path d="M20 12v3a3 3 0 0 1 -3 3h-13m3 3l-3 -3l3 -3" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class="share-option-text">
|
||||
<span class="share-option-title">Always latest</span>
|
||||
<span class="share-option-desc">
|
||||
Follows these notes — the reader gets the current version.
|
||||
Follows these notes, so the reader always sees the current version.
|
||||
</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>
|
||||
@@ -147,8 +176,8 @@ const copy = async (url: string, label: string) => {
|
||||
}
|
||||
|
||||
.share-option-icon {
|
||||
font-size: 1.4rem;
|
||||
line-height: 1.6rem;
|
||||
flex-shrink: 0;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.share-option-text {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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' }"
|
||||
|
||||
Reference in New Issue
Block a user