Add Rails backend: accounts via magic link, server documents, sharing
The app becomes a Rails 8 application; the Vue frontend moves to app/frontend and is served through vite_rails. - Without an account documents stay in the browser (IndexedDB). Sign-in is by magic link or the 6-digit code from the same mail; afterwards local documents can be uploaded into the account. - Server documents (/d/<id>) with versioned saves and a single-editor lock per browser tab; others see them read-only and follow by polling. - Sharing via link (works without an account) and via e-mail invitation, roles viewer/editor; the owner manages links and people. - Store gains a read-only mode that blocks all content changes. - Solid Queue, Cache and Cable share the one PostgreSQL database. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* Shown once after signing in when this browser still has local documents:
|
||||
* which of them should move into the account (server)? Uploaded ones are
|
||||
* removed from the browser. "Später" leaves everything as is -- uploading
|
||||
* stays possible from the document list.
|
||||
*/
|
||||
import { ref, watch } from 'vue';
|
||||
import { useLibrary } from '../store/library';
|
||||
import BaseModal from './BaseModal.vue';
|
||||
|
||||
const props = defineProps<{ open: boolean }>();
|
||||
const emit = defineEmits<{ (e: 'close'): void }>();
|
||||
|
||||
const library = useLibrary();
|
||||
const selected = ref<Set<string>>(new Set());
|
||||
const busy = ref(false);
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
(o) => {
|
||||
if (o) selected.value = new Set(library.state.docs.map((d) => d.id));
|
||||
},
|
||||
);
|
||||
|
||||
function toggle(id: string) {
|
||||
const s = new Set(selected.value);
|
||||
if (s.has(id)) s.delete(id);
|
||||
else s.add(id);
|
||||
selected.value = s;
|
||||
}
|
||||
|
||||
async function run() {
|
||||
busy.value = true;
|
||||
try {
|
||||
await library.upload([...selected.value]);
|
||||
emit('close');
|
||||
} finally {
|
||||
busy.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseModal :open="open" title="Dokumente in dein Konto übernehmen?" @close="emit('close')">
|
||||
<p class="lead">
|
||||
In diesem Browser liegen noch Dokumente. Ausgewählte werden in dein Konto hochgeladen – dann sind sie
|
||||
auf allen Geräten da und lassen sich teilen – und hier aus dem Browser entfernt.
|
||||
</p>
|
||||
<ul class="list">
|
||||
<li v-for="d in library.state.docs" :key="d.id">
|
||||
<label>
|
||||
<input type="checkbox" :checked="selected.has(d.id)" @change="toggle(d.id)" />
|
||||
{{ d.title }}
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<template #footer>
|
||||
<button @click="emit('close')">Später</button>
|
||||
<button class="primary" :disabled="busy || !selected.size" @click="run">
|
||||
{{ busy ? 'Lade hoch …' : `${selected.size} hochladen` }}
|
||||
</button>
|
||||
</template>
|
||||
</BaseModal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.lead {
|
||||
margin: 0 0 12px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-height: 40vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.list li + li {
|
||||
margin-top: 4px;
|
||||
}
|
||||
.list label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 8px;
|
||||
border-radius: 6px;
|
||||
background: var(--surface-2);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user