Browse Source
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>feature/brute_force_protection
12 changed files with 246 additions and 79 deletions
@ -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} /> |
||||||
@ -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> |
||||||
Loading…
Reference in new issue