implemented sharing of bookmarks
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import { tick } from "svelte";
|
||||
|
||||
let {
|
||||
getCandidates = text => {},
|
||||
getCandidates = async text => { conole.log('no handler for getCandidates('+text+')'); return {};},
|
||||
onSelect = text => []
|
||||
} = $props();
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
let result = {};
|
||||
result[key] = text;
|
||||
options = {};
|
||||
text = '';
|
||||
onSelect(result);
|
||||
}
|
||||
|
||||
@@ -49,10 +50,11 @@
|
||||
min-width: 200px;
|
||||
}
|
||||
</style>
|
||||
<select size={Object.keys(options).length<2?2:Object.keys(options).length+1} {onkeyup} {ondblclick} autofocus width="40">
|
||||
{#if options}
|
||||
<select size={Object.keys(options).length<2?2:Object.keys(options).length+1} {onkeyup} {ondblclick} width="40">
|
||||
<option>{text}</option>
|
||||
{#each Object.entries(options) as [val,caption]}
|
||||
<option value={val}>{caption}</option>
|
||||
{/each}
|
||||
</select>
|
||||
|
||||
{/if}
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
import PermissionSelector from './PermissionSelector.svelte';
|
||||
|
||||
let {
|
||||
members,
|
||||
getCandidates = text => {},
|
||||
updatePermission = (uid,perm) => console.log(`no handler for updatePermission(${uid}, ${perm})`),
|
||||
addMember = (entry) => console.log(`no handler for addMember(${entry})`),
|
||||
dropMember = (member) => console.log(`no handler for dropMember(${member})`),
|
||||
addMember = (entry) => console.log(`no handler for addMember(${entry})`)
|
||||
getCandidates = text => {},
|
||||
members,
|
||||
updatePermission = (uid,perm) => console.log(`no handler for updatePermission(${uid}, ${perm})`)
|
||||
} = $props();
|
||||
|
||||
let error = $state(null);
|
||||
|
||||
50
frontend/src/Components/UserSelector.svelte
Normal file
50
frontend/src/Components/UserSelector.svelte
Normal file
@@ -0,0 +1,50 @@
|
||||
<script>
|
||||
import { api } from '../urls.svelte.js';
|
||||
import { t } from '../translations.svelte.js';
|
||||
import { user } from '../user.svelte.js';
|
||||
|
||||
import Autocomplete from './Autocomplete.svelte';
|
||||
|
||||
let error = $state(null);
|
||||
let {
|
||||
getCandidates = async text => {},
|
||||
heading = t('add_user'),
|
||||
users = $bindable({})
|
||||
} = $props();
|
||||
|
||||
function dropUser(id){
|
||||
delete users[id];
|
||||
}
|
||||
|
||||
function onSelect(entry){
|
||||
for (let [k,v] of Object.entries(entry)){
|
||||
users[k] = {name:v,id:k};
|
||||
}
|
||||
}
|
||||
|
||||
let sortedUsers = $derived.by(() => Object.values(users).sort((a, b) => a.name.localeCompare(b.name)));
|
||||
</script>
|
||||
|
||||
{#if error}
|
||||
<span class="error">{error}</span>
|
||||
{/if}
|
||||
<table>
|
||||
<tbody>
|
||||
{#each sortedUsers as usr,idx}
|
||||
<tr>
|
||||
<td>{usr.name}</td>
|
||||
<td>
|
||||
{#if usr.id != user.id}
|
||||
<button onclick={() => dropUser(usr.id)}>x</button>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
<tr>
|
||||
<td>{heading}</td>
|
||||
<td>
|
||||
<Autocomplete {getCandidates} {onSelect} />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -5,6 +5,7 @@
|
||||
import { t } from '../../translations.svelte.js';
|
||||
|
||||
import Editor from '../../Components/MarkdownEditor.svelte';
|
||||
import Users from '../../Components/UserSelector.svelte';
|
||||
import Tags from '../tags/TagList.svelte';
|
||||
import Template from './Template.svelte';
|
||||
|
||||
@@ -15,20 +16,36 @@
|
||||
rendered:null
|
||||
},
|
||||
tags:[],
|
||||
url:null
|
||||
url:null,
|
||||
users:{}
|
||||
});
|
||||
let error = $state(null);
|
||||
|
||||
async function getCandidates(text){
|
||||
const url = api('user/search');
|
||||
const resp = await fetch(url,{
|
||||
credentials : 'include',
|
||||
method : 'POST',
|
||||
body : text
|
||||
});
|
||||
if (resp.ok){
|
||||
error = null;
|
||||
const input = await resp.json();
|
||||
return Object.fromEntries(
|
||||
Object.entries(input).map(([key, value]) => [key, value.name])
|
||||
);
|
||||
} else {
|
||||
error = await resp.text();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async function loadBookmarks(){
|
||||
const url = api('bookmark/list');
|
||||
const resp = await fetch(url,{credentials:'include'});
|
||||
if (resp.ok){
|
||||
const raw = await resp.json();
|
||||
bookmarks = Object.values(raw)
|
||||
.sort(
|
||||
(a, b) => new Date(b.timestamp) - new Date(a.timestamp)
|
||||
);
|
||||
|
||||
bookmarks = Object.values(raw).sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
||||
error = false;
|
||||
} else {
|
||||
error = await resp.html();
|
||||
@@ -37,12 +54,14 @@
|
||||
|
||||
async function onclick(ev){
|
||||
delete new_bookmark.comment.rendered;
|
||||
const url = api('bookmark/save');
|
||||
const url = api('bookmark');
|
||||
const data = {
|
||||
comment : new_bookmark.comment.source,
|
||||
url : new_bookmark.url,
|
||||
tags : new_bookmark.tags
|
||||
}
|
||||
const share = Object.keys(new_bookmark.users).map(id => +id);
|
||||
if (share.length>0) data.share = share;
|
||||
const resp = await fetch(url,{
|
||||
credentials : 'include',
|
||||
method : 'POST',
|
||||
@@ -66,17 +85,21 @@
|
||||
{/if}
|
||||
<label>
|
||||
{t('URL')}
|
||||
<input bind:value={new_bookmark.url} />
|
||||
<input bind:value={new_bookmark.url} autofocus />
|
||||
</label>
|
||||
<label>
|
||||
{t('Comment')}
|
||||
<Editor simple={true} bind:value={new_bookmark.comment} />
|
||||
</label>
|
||||
<label>
|
||||
{t('share_with')}
|
||||
<Users {getCandidates} users={new_bookmark.users} />
|
||||
</label>
|
||||
<Tags module="bookmark" bind:tags={new_bookmark.tags} />
|
||||
<button {onclick}>{t('save')}</button>
|
||||
{#if bookmarks}
|
||||
{#each bookmarks as bookmark}
|
||||
<Template {bookmark} />
|
||||
<Template {bookmark} />
|
||||
{/each}
|
||||
{/if}
|
||||
</fieldset>
|
||||
@@ -101,7 +101,7 @@
|
||||
|
||||
<div class="taglist">
|
||||
<span class="tag editor">
|
||||
<input type="text" bind:value={newTag} onkeyup={typed} autofocus />
|
||||
<input type="text" bind:value={newTag} onkeyup={typed} />
|
||||
</span>
|
||||
{#each tags as tag,idx}
|
||||
<span class="tag">
|
||||
@@ -109,4 +109,4 @@
|
||||
<button onclick={() => drop(tag)} class="symbol"></button>
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user