Browse Source

implemented adding notes to projects and tasks

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
module/notes
Stephan Richter 3 months ago
parent
commit
65df45482f
  1. 2
      core/src/main/java/de/srsoftware/umbrella/core/model/Note.java
  2. 7
      frontend/src/Components/MarkdownEditor.svelte
  3. 41
      frontend/src/routes/notes/List.svelte
  4. 5
      frontend/src/routes/project/View.svelte
  5. 10
      frontend/src/routes/task/View.svelte
  6. 6
      notes/src/main/java/de/srsoftware/umbrella/notes/NoteModule.java
  7. 2
      translations/src/main/resources/de.json

2
core/src/main/java/de/srsoftware/umbrella/core/model/Note.java

@ -34,7 +34,7 @@ public record Note(long id, String module, long entityId, long authorId, String
USER_ID,authorId, USER_ID,authorId,
TEXT,text, TEXT,text,
RENDERED,markdown(text), RENDERED,markdown(text),
TIMESTAMP,timestamp TIMESTAMP,timestamp.withNano(0)
); );
} }
} }

7
frontend/src/Components/MarkdownEditor.svelte

@ -51,7 +51,12 @@
if (simple) { if (simple) {
value.source = editValue.source; value.source = editValue.source;
value.rendered = editValue.rendered; value.rendered = editValue.rendered;
} else if (ev.keyCode == 13 && ev.ctrlKey) applyEdit(); }
if (ev.keyCode == 13 && ev.ctrlKey){
if (simple){
onSet(editValue.source);
} else applyEdit();
}
if (ev.keyCode == 27) resetEdit(); if (ev.keyCode == 27) resetEdit();
if (timer) clearTimeout(timer); if (timer) clearTimeout(timer);

41
frontend/src/routes/notes/List.svelte

@ -2,16 +2,16 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { api } from '../../urls.svelte.js'; import { api } from '../../urls.svelte.js';
import { t } from '../../translations.svelte.js'; import { t } from '../../translations.svelte.js';
import { user } from '../../user.svelte.js';
import Editor from '../../Components/MarkdownEditor.svelte'; import Editor from '../../Components/MarkdownEditor.svelte';
let error = $state(null); let error = $state(null);
let { module = null, entity_id = null } = $props(); let { module = null, entity_id = null } = $props();
let note = $state({source:null,rendered:null}); let note = $state({source:null,rendered:null});
let notes = $state(null); let notes = $state(null);
let authors = $state(null);
async function onclick(){ async function saveNote(){
alert(note.source);
const url = api(`notes/${module}/${entity_id}`); const url = api(`notes/${module}/${entity_id}`);
const resp = await fetch(url,{ const resp = await fetch(url,{
credentials:'include', credentials:'include',
@ -19,8 +19,15 @@
body:note.source body:note.source
}); });
if (resp.ok){ if (resp.ok){
let newNote = await resp.json();
authors[user.id] = user;
notes[newNote.id] = newNote;
note = {source:'',rendered:''};
error = null;
return true;
} else { } else {
error = await resp.text(); error = await resp.text();
return false;
} }
} }
@ -28,7 +35,9 @@
const url = api(`notes/${module}/${entity_id}`); const url = api(`notes/${module}/${entity_id}`);
const resp = await fetch(url,{credentials:'include'}); const resp = await fetch(url,{credentials:'include'});
if (resp.ok){ if (resp.ok){
notes = await resp.json(); const data = await resp.json();
notes = data.notes;
authors = data.authors;
} else { } else {
error = await resp.text(); error = await resp.text();
} }
@ -37,19 +46,31 @@
onMount(load) onMount(load)
</script> </script>
<h1>{t('notes')}</h1> <style>
fieldset{
position: relative;
}
legend.time{
position: absolute;
top: -19px;
right: 20px;
background: black;
}
</style>
{#if error} {#if error}
<span class="error">{error}</span> <span class="error">{error}</span>
{/if} {/if}
{#if notes} {#if notes}
{#each Object.entries(notes) as [a,b]} {#each Object.entries(notes) as [a,b]}
<fieldset> <fieldset>
<legend>User {b.user_id}{b.timestamp.replace('T',' ')}</legend> <legend class="author">{authors[b.user_id].name}</legend>
<legend class="time">{b.timestamp.replace('T',' ')}</legend>
{@html b.rendered} {@html b.rendered}
</fieldset> </fieldset>
{/each} {/each}
{/if} {/if}
<div class="editor">
<Editor simple={true} bind:value={note} /> <Editor simple={true} bind:value={note} onSet={saveNote} />
<button {onclick}>{t('save_note')}</button> <button onclick={saveNote}>{t('save_note')}</button>
</div>

5
frontend/src/routes/project/View.svelte

@ -8,6 +8,7 @@
import LineEditor from '../../Components/LineEditor.svelte'; import LineEditor from '../../Components/LineEditor.svelte';
import MarkdownEditor from '../../Components/MarkdownEditor.svelte'; import MarkdownEditor from '../../Components/MarkdownEditor.svelte';
import MemberEditor from '../../Components/MemberEditor.svelte'; import MemberEditor from '../../Components/MemberEditor.svelte';
import Notes from '../notes/List.svelte';
import StateSelector from '../../Components/StateSelector.svelte'; import StateSelector from '../../Components/StateSelector.svelte';
import Tags from '../tags/TagList.svelte'; import Tags from '../tags/TagList.svelte';
import TaskList from '../task/TaskList.svelte'; import TaskList from '../task/TaskList.svelte';
@ -211,3 +212,7 @@
</tbody> </tbody>
</table> </table>
{/if} {/if}
<div class="notes">
<h3>{t('notes')}</h3>
<Notes module="project" entity_id={id} />
</div>

10
frontend/src/routes/task/View.svelte

@ -294,12 +294,10 @@
<TagList module="task" {id} user_list={Object.keys(task.members).map(id => +id)} /> <TagList module="task" {id} user_list={Object.keys(task.members).map(id => +id)} />
</td> </td>
</tr> </tr>
<tr>
<th>{t('notes')}</th>
<td>
<Notes module="task" entity_id={id} />
</td>
</tr>
</tbody> </tbody>
</table> </table>
{/if} {/if}
<div class="notes">
<h3>{t('notes')}</h3>
<Notes module="task" entity_id={id} />
</div>

6
notes/src/main/java/de/srsoftware/umbrella/notes/NoteModule.java

@ -19,12 +19,14 @@ import de.srsoftware.umbrella.core.api.UserService;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.Note; import de.srsoftware.umbrella.core.model.Note;
import de.srsoftware.umbrella.core.model.Token; import de.srsoftware.umbrella.core.model.Token;
import de.srsoftware.umbrella.core.model.UmbrellaUser;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
public class NoteModule extends BaseHandler implements NoteService { public class NoteModule extends BaseHandler implements NoteService {
private final NotesDb notesDb; private final NotesDb notesDb;
@ -71,7 +73,9 @@ public class NoteModule extends BaseHandler implements NoteService {
if (module == null) throw unprocessable("Module missing in path."); if (module == null) throw unprocessable("Module missing in path.");
var head = path.pop(); var head = path.pop();
long entityId = Long.parseLong(head); long entityId = Long.parseLong(head);
return sendContent(ex, mapValues(getNotes(module,entityId))); var notes = getNotes(module,entityId);
var authors = notes.values().stream().map(Note::authorId).distinct().map(users::loadUser).collect(Collectors.toMap(UmbrellaUser::id,UmbrellaUser::toMap));
return sendContent(ex, Map.of("notes",mapValues(notes),"authors",authors));
} catch (NumberFormatException e){ } catch (NumberFormatException e){
return sendContent(ex,HTTP_UNPROCESSABLE,"Entity id missing in path."); return sendContent(ex,HTTP_UNPROCESSABLE,"Entity id missing in path.");
} catch (UmbrellaException e){ } catch (UmbrellaException e){

2
translations/src/main/resources/de.json

@ -46,6 +46,7 @@
"data_sent": "Daten übermittelt", "data_sent": "Daten übermittelt",
"date": "Datum", "date": "Datum",
"delete": "löschen", "delete": "löschen",
"delete_task": "Aufgabe löschen ",
"DELETE_USERS": "Nutzer löschen", "DELETE_USERS": "Nutzer löschen",
"delivery_date": "Lieferdatum", "delivery_date": "Lieferdatum",
"description": "Beschreibung", "description": "Beschreibung",
@ -187,6 +188,7 @@
"show_closed": "geschlossene anzeigen", "show_closed": "geschlossene anzeigen",
"show_kanban": "Kanban-Ansicht", "show_kanban": "Kanban-Ansicht",
"start_date": "Startdatum", "start_date": "Startdatum",
"started": "angefangen",
"state": "Status", "state": "Status",
"state_cancelled": "abgebrochen", "state_cancelled": "abgebrochen",
"state_complete": "abgeschlossen", "state_complete": "abgeschlossen",

Loading…
Cancel
Save