Compare commits
5 Commits
2592b19c8f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f89c2fa776 | ||
|
|
c21619c53a | ||
|
|
b34b5851cd | ||
|
|
9581dc88aa | ||
|
|
40b0af6b01 |
@@ -55,7 +55,7 @@ const copy = async (url: string, label: string) => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
class="btn btn-ghost btn-circle text-base-content"
|
class="btn btn-ghost btn-circle text-accent"
|
||||||
title="Share this view"
|
title="Share this view"
|
||||||
aria-label="Share this view"
|
aria-label="Share this view"
|
||||||
onclick="share_modal.showModal()"
|
onclick="share_modal.showModal()"
|
||||||
@@ -148,12 +148,6 @@ const copy = async (url: string, label: string) => {
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="modal-action">
|
|
||||||
<form method="dialog">
|
|
||||||
<button class="btn btn-ghost">Close</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<form method="dialog" class="modal-backdrop">
|
<form method="dialog" class="modal-backdrop">
|
||||||
<button>close</button>
|
<button>close</button>
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import { computed, defineAsyncComponent, onMounted, ref, watch } from "vue"
|
||||||
computed,
|
|
||||||
defineAsyncComponent,
|
|
||||||
onMounted,
|
|
||||||
ref,
|
|
||||||
watch
|
|
||||||
} from "vue"
|
|
||||||
|
|
||||||
import { useEditionMode } from "@/hooks/useEditionMode"
|
import { useEditionMode } from "@/hooks/useEditionMode"
|
||||||
import { useFile } from "@/hooks/useFile.hook"
|
import { useFile } from "@/hooks/useFile.hook"
|
||||||
@@ -188,6 +182,19 @@ const loadNote = async () => {
|
|||||||
|
|
||||||
onMounted(loadNote)
|
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(
|
watch(
|
||||||
path,
|
path,
|
||||||
(p) => {
|
(p) => {
|
||||||
@@ -204,8 +211,7 @@ useMarkdownPostRender(content, () => `.note-${sha.value}`, {
|
|||||||
macroplan: true,
|
macroplan: true,
|
||||||
mermaid: () => rawContent.value.includes("```mermaid"),
|
mermaid: () => rawContent.value.includes("```mermaid"),
|
||||||
shikiji: () => isMarkdown.value && rawContent.value.includes("```"),
|
shikiji: () => isMarkdown.value && rawContent.value.includes("```"),
|
||||||
images: () =>
|
images: () => (/\!\[.*?\]\(.*?\)/.test(rawContent.value) ? props.sha : null),
|
||||||
/\!\[.*?\]\(.*?\)/.test(rawContent.value) ? props.sha : null,
|
|
||||||
triggers: [mode]
|
triggers: [mode]
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -215,6 +221,13 @@ const performSave = async (overrideSha?: string) => {
|
|||||||
return
|
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 editedSha = overrideSha ?? (await getEditedSha()) ?? sha.value
|
||||||
const { sha: newSha, conflict } = await updateFile({
|
const { sha: newSha, conflict } = await updateFile({
|
||||||
content: rawContent.value,
|
content: rawContent.value,
|
||||||
@@ -287,8 +300,7 @@ watch(mode, async (newMode) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasUserFinishedToEdit =
|
const hasUserFinishedToEdit = newMode === "read" && isDirty.value
|
||||||
newMode === "read" && rawContent.value !== initialRawContent.value
|
|
||||||
|
|
||||||
if (!hasUserFinishedToEdit) {
|
if (!hasUserFinishedToEdit) {
|
||||||
return
|
return
|
||||||
@@ -336,7 +348,7 @@ const onBadgeClick = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasUnsavedEdits = rawContent.value !== initialRawContent.value
|
const hasUnsavedEdits = isDirty.value
|
||||||
if (hasUnsavedEdits) {
|
if (hasUnsavedEdits) {
|
||||||
await handleConflict()
|
await handleConflict()
|
||||||
return
|
return
|
||||||
@@ -377,7 +389,7 @@ const onBadgeClick = async () => {
|
|||||||
class="action"
|
class="action"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
v-if="isMarkdown && canPush"
|
v-if="isMarkdown && canPush && loadStatus === 'ready'"
|
||||||
class="action button is-text is-light"
|
class="action button is-text is-light"
|
||||||
:class="{ 'is-link': mode === 'edit' }"
|
:class="{ 'is-link': mode === 'edit' }"
|
||||||
:style="mode === 'edit' ? 'color: var(--color-primary)' : ''"
|
:style="mode === 'edit' ? 'color: var(--color-primary)' : ''"
|
||||||
@@ -489,7 +501,7 @@ const onBadgeClick = async () => {
|
|||||||
<span>You're viewing an older shared version.</span>
|
<span>You're viewing an older shared version.</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="snapshot-banner-action"
|
class="btn bt-sm btn-soft btn-info"
|
||||||
@click="viewLatest"
|
@click="viewLatest"
|
||||||
>
|
>
|
||||||
View latest
|
View latest
|
||||||
@@ -588,16 +600,6 @@ $border-color: rgba(18, 19, 58, 0.2);
|
|||||||
font-size: 0.85em;
|
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 {
|
.action {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
|
|||||||
@@ -381,10 +381,10 @@ onMounted(() => {
|
|||||||
comes to rest.
|
comes to rest.
|
||||||
</h1>
|
</h1>
|
||||||
<p class="lede">
|
<p class="lede">
|
||||||
Point it at any markdown — a GitHub repo of your own, or public
|
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
|
notes from the open ATProto network. Remanso turns it into a
|
||||||
calm, stackable notebook with backlinks where your thinking
|
stackable notebook with backlinks where your thinking finally
|
||||||
finally runs clear.
|
runs clear.
|
||||||
</p>
|
</p>
|
||||||
<div class="hero-ed-paths">
|
<div class="hero-ed-paths">
|
||||||
<!-- CTA 01: GitHub repo -->
|
<!-- CTA 01: GitHub repo -->
|
||||||
@@ -472,7 +472,7 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="publicNotesLoadFailed" class="network-empty">
|
<div v-else-if="publicNotesLoadFailed" class="network-empty">
|
||||||
The pool is quiet right now.
|
Nothing has drifted in yet.
|
||||||
</div>
|
</div>
|
||||||
<router-link
|
<router-link
|
||||||
:to="{ name: 'PublicNoteListView' }"
|
:to="{ name: 'PublicNoteListView' }"
|
||||||
|
|||||||
Reference in New Issue
Block a user