fine-tuning notes in preparation for release

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
2025-08-13 01:28:21 +02:00
parent 1df7a2bd3c
commit 85ae67b72f
12 changed files with 246 additions and 79 deletions

View File

@@ -19,7 +19,7 @@
import Login from "./Components/Login.svelte";
import Messages from "./routes/message/Messages.svelte";
import Menu from "./Components/Menu.svelte";
import Notes from "./routes/notes/List.svelte";
import Notes from "./routes/notes/Index.svelte";
import ProjectList from "./routes/project/List.svelte";
import ProjectAdd from "./routes/project/Create.svelte";
import ResetPw from "./routes/user/ResetPw.svelte";

View File

@@ -5,7 +5,7 @@
import Editor from './Editor.svelte';
import LineEditor from '../../Components/LineEditor.svelte';
import Notes from '../notes/List.svelte';
import Notes from '../notes/RelatedNotes.svelte';
let error = $state(null);
let companies = $state(null);

View File

@@ -9,7 +9,7 @@
import LineEditor from '../../Components/LineEditor.svelte';
import MarkdownEditor from '../../Components/MarkdownEditor.svelte';
import MultilineEditor from '../../Components/MultilineEditor.svelte';
import Notes from '../notes/List.svelte';
import Notes from '../notes/RelatedNotes.svelte';
import PositionList from './PositionList.svelte';
import PositionSelector from './PositionSelector.svelte';
import StateSelector from './StateSelector.svelte';

View File

@@ -0,0 +1,83 @@
<script>
import { onMount } from 'svelte';
import { useTinyRouter } from 'svelte-tiny-router';
import { api } from '../../urls.svelte.js';
import { t } from '../../translations.svelte.js';
import { user } from '../../user.svelte.js';
import List from './List.svelte';
let authors = $state({});
let error = $state(null);
let loader = {
offset : 0,
limit : 5,
active : true
}
let note = $state({source:null,rendered:null});
let notes = $state(null);
let {
module = null,
entity_id = null
} = $props();
async function loadNotes(){
const url = api(`notes?offset=${loader.offset}&limit=${loader.limit}`);
const resp = await fetch(url,{credentials:'include'});
if (resp.ok){
const data = await resp.json();
if (!notes) notes = [];
notes.push(...Object.values(data.notes).sort((a, b) => b.id - a.id));
authors = {...authors, ...data.authors};
loader.offset += loader.limit;
loader.active = false;
error = null;
if (Object.keys(data.notes).length) onscroll(null); // when notes were received, check whether they fill up the page
} else {
error = await resp.text();
}
}
async function saveNote(){
const url = api(`notes/${module}/${entity_id}`);
const resp = await fetch(url,{
credentials : 'include',
method : 'POST',
body : note.source
});
if (resp.ok){
let newNote = await resp.json();
authors[user.id] = user;
notes[newNote.id] = newNote;
note = {source:'',rendered:''};
error = null;
return true;
} else {
error = await resp.text();
return false;
}
}
function onscroll(ev){
if (window.innerHeight + window.scrollY >= document.body.offsetHeight && !loader.active) {
loader.active = true;
loadNotes();
}
}
loadNotes(loadNotes)
</script>
<svelte:window {onscroll} />
{#if error}
<span class="error">{error}</span>
{/if}
<List {notes} />

View File

@@ -1,5 +1,4 @@
<script>
import { onMount } from 'svelte';
import { useTinyRouter } from 'svelte-tiny-router';
import { api } from '../../urls.svelte.js';
@@ -8,15 +7,9 @@
import Editor from '../../Components/MarkdownEditor.svelte';
let authors = $state(null);
let error = $state(null);
let note = $state({source:null,rendered:null});
let notes = $state(null);
let { authors, module, notes = $bindable() } = $props();
let error = $state(null);
const router = useTinyRouter();
let {
module = null,
entity_id = null
} = $props();
async function drop(nid){
if (!confirm(t('confirm_delete',{element:t('note')}))) return;
@@ -27,46 +20,22 @@
});
if (resp.ok) {
error = false;
delete notes[nid];
notes = notes.filter((itm,idx) => itm.id != nid);
} else {
error = await resp.text();
}
}
function goToEntity(n){
router.navigate(`/${n.module}/${n.entity_id}/view`);
function goToEntity(note){
router.navigate(`/${note.module}/${note.entity_id}/view`);
}
async function load(){
const url = (module == null && entity_id == null) ? api('notes') : api(`notes/${module}/${entity_id}`);
const resp = await fetch(url,{credentials:'include'});
if (resp.ok){
const data = await resp.json();
notes = data.notes;
authors = data.authors;
} else {
error = await resp.text();
}
}
async function saveNote(){
const url = api(`notes/${module}/${entity_id}`);
const resp = await fetch(url,{
credentials : 'include',
method : 'POST',
body : note.source
});
if (resp.ok){
let newNote = await resp.json();
authors[user.id] = user;
notes[newNote.id] = newNote;
note = {source:'',rendered:''};
error = null;
return true;
} else {
error = await resp.text();
return false;
}
function title(note){
let title = t(note.module);
if (note.module == 'wiki') title += ':';
title += ' ';
title += note.entity_id;
return title;
}
async function update(nid,src){
@@ -81,34 +50,26 @@
return true;
} else {
error = await resp.text();
return false;
}
}
onMount(load)
</script>
{#if error}
<span class="error">{error}</span>
{/if}
{#if notes}
{#each Object.entries(notes) as [nid,note]}
{#each notes as note (note.id)}
<fieldset>
{#if module}
<legend class="author">{authors[note.user_id].name}</legend>
{:else}
<legend class="entity" onclick={() => goToEntity(note)}>{t(note.module)} {note.entity_id}</legend>
<legend class="entity" onclick={() => goToEntity(note)}>{title(note)}</legend>
{/if}
<legend class="time">
{note.timestamp.replace('T',' ')}
{#if user.id == note.user_id}
<button class="symbol" onclick={() => drop(nid)}></button>
<button class="symbol" onclick={() => drop(note.id)}></button>
{/if}
</legend>
<Editor value={note.text} onSet={(newVal) => update(nid,newVal)} editable={user.id == note.user_id} />
<Editor value={note.text} onSet={(newVal) => update(note.id,newVal)} editable={user.id == note.user_id} />
</fieldset>
{/each}
{/if}
<div class="editor">
<Editor simple={true} bind:value={note} onSet={saveNote} />
<button onclick={saveNote}>{t('save_object',{object:t('note')})}</button>
</div>
{/if}

View File

@@ -0,0 +1,99 @@
<script>
import { onMount } from 'svelte';
import { useTinyRouter } from 'svelte-tiny-router';
import { api } from '../../urls.svelte.js';
import { t } from '../../translations.svelte.js';
import { user } from '../../user.svelte.js';
import Editor from '../../Components/MarkdownEditor.svelte';
import List from './List.svelte';
let authors = $state(null);
let error = $state(null);
let note = $state({source:null,rendered:null});
let notes = $state(null);
const router = useTinyRouter();
let {
module = null,
entity_id = null
} = $props();
async function drop(nid){
if (!confirm(t('confirm_delete',{element:t('note')}))) return;
const url = api(`notes/${nid}`);
const resp = await fetch(url,{
credentials : 'include',
method : 'DELETE'
});
if (resp.ok) {
error = false;
delete notes[nid];
} else {
error = await resp.text();
}
}
function goToEntity(n){
router.navigate(`/${n.module}/${n.entity_id}/view`);
}
async function load(){
const url = api(`notes/${module}/${entity_id}`);
const resp = await fetch(url,{credentials:'include'});
if (resp.ok){
const data = await resp.json();
notes = Object.values(data.notes).sort((a, b) => a.id - b.id);
authors = data.authors;
} else {
error = await resp.text();
}
}
async function saveNote(){
const url = api(`notes/${module}/${entity_id}`);
const resp = await fetch(url,{
credentials : 'include',
method : 'POST',
body : note.source
});
if (resp.ok){
let newNote = await resp.json();
authors[user.id] = user;
notes.push(newNote);
note = {source:'',rendered:''};
error = null;
return true;
} else {
error = await resp.text();
return false;
}
}
async function update(nid,src){
const url = api(`notes/${nid}`);
const resp = await fetch(url,{
credentials : 'include',
method : 'PATCH',
body : src
});
if (resp.ok) {
error = false;
return true;
} else {
error = await resp.text();
return true;
}
}
onMount(load);
</script>
{#if error}
<span class="error">{error}</span>
{/if}
<List {authors} {module} {notes} />
<div class="editor">
<Editor simple={true} bind:value={note} onSet={saveNote} />
<button onclick={saveNote}>{t('save_object',{object:t('note')})}</button>
</div>

View File

@@ -8,7 +8,7 @@
import LineEditor from '../../Components/LineEditor.svelte';
import MarkdownEditor from '../../Components/MarkdownEditor.svelte';
import PermissionEditor from '../../Components/PermissionEditor.svelte';
import Notes from '../notes/List.svelte';
import Notes from '../notes/RelatedNotes.svelte';
import StateSelector from '../../Components/StateSelector.svelte';
import Tags from '../tags/TagList.svelte';
import TaskList from '../task/TaskList.svelte';

View File

@@ -8,7 +8,7 @@
import LineEditor from '../../Components/LineEditor.svelte';
import MarkdownEditor from '../../Components/MarkdownEditor.svelte';
import PermissionEditor from '../../Components/PermissionEditor.svelte';
import Notes from '../notes/List.svelte';
import Notes from '../notes/RelatedNotes.svelte';
import StateSelector from '../../Components/StateSelector.svelte';
import TagList from '../tags/TagList.svelte';
import TaskList from './TaskList.svelte';