1
0

Basic implementation for notes app

This commit is contained in:
2022-08-25 17:14:37 +02:00
parent a0239ecda6
commit 2ab4497bd1
22 changed files with 671 additions and 171 deletions

View File

@@ -21,6 +21,10 @@
<groupId>de.77zzcx7.nbs-cloud</groupId>
<artifactId>web-container-config</artifactId>
</dependency>
<dependency>
<groupId>de.77zzcx7.nbs-cloud</groupId>
<artifactId>files-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>

View File

@@ -0,0 +1,129 @@
package de.nbscloud.notes.controller;
import de.nbscloud.files.api.FilesService;
import de.nbscloud.notes.NotesApp;
import de.nbscloud.webcontainer.MessageHelper;
import de.nbscloud.webcontainer.registry.AppRegistry;
import de.nbscloud.webcontainer.shared.config.WebContainerSharedConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Optional;
@Controller
public class NotesController implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(NotesController.class);
@Autowired
private AppRegistry appRegistry;
@Autowired
private FilesService filesService;
@Autowired
private WebContainerSharedConfig webContainerSharedConfig;
@Autowired
private NotesApp app;
@Autowired
private MessageHelper messageHelper;
public static Optional<Path> notePathToPath(String notePath) {
final Optional<String> optNotePath = Optional.ofNullable(notePath);
return optNotePath.map(p -> Path.of(p));
}
@GetMapping("/notes")
public String start(Model model, @RequestParam(required = false) String notePath) {
final Optional<Path> optNotePath = notePathToPath(notePath);
if(optNotePath.isPresent()) {
final byte[] content = this.filesService.get(app, optNotePath.get());
model.addAttribute("content", new String(content));
}
this.messageHelper.addAndClearAll(model);
model.addAttribute("currentNote", notePath);
model.addAttribute("tree", this.filesService.getTree(app, Optional.empty()));
model.addAttribute("dirs", this.filesService.collectDirs(app, Optional.empty()));
model.addAttribute("apps", this.appRegistry.getAll());
this.webContainerSharedConfig.addDefaults(model);
return "notes/notesIndex";
}
@PostMapping("/notes/save")
public String save(String textContent, String notePath) {
final Optional<Path> optNotePath = notePathToPath(notePath);
if(optNotePath.isPresent()) {
this.filesService.overwriteFile(app, optNotePath.get(), textContent.getBytes(StandardCharsets.UTF_8));
this.messageHelper.addInfo("nbscloud.notes.save.success");
}
return "redirect:?notePath=" + notePath;
}
@PostMapping("/notes/delete")
public String delete(String notePath) {
final Optional<Path> optNotePath = notePathToPath(notePath);
if(optNotePath.isPresent()) {
this.filesService.delete(app, optNotePath.get());
this.messageHelper.addInfo("nbscloud.notes.delete.success");
}
return "redirect:";
}
@PostMapping("/notes/createDir")
public String createDirectory(String dirName, String parentPath, String currentPath) {
final Optional<Path> optParentPath = notePathToPath(parentPath);
if(optParentPath.isPresent()) {
final Path dirPath = optParentPath.get().resolve(dirName);
this.filesService.createDirectory(app, dirPath);
this.messageHelper.addInfo("nbscloud.notes.createDir.success");
}
if(currentPath == null || currentPath.isBlank()) {
return "redirect:";
}
return "redirect:?notePath=" + currentPath;
}
@PostMapping("/notes/createNote")
public String createNote(String noteName, String parentPath, String currentPath) {
final Optional<Path> optParentPath = notePathToPath(parentPath);
if(optParentPath.isPresent()) {
final Path notePath = optParentPath.get().resolve(noteName);
this.filesService.createFile(app, notePath, new byte[] {});
this.messageHelper.addInfo("nbscloud.notes.createNote.success");
return "redirect:?notePath=" + notePath;
}
return "redirect:?notePath=" + currentPath;
}
@Override
public void afterPropertiesSet() throws Exception {
this.filesService.createAppDirectory(this.app);
}
}

View File

@@ -0,0 +1,7 @@
nbscloud.notes.action.save=Save
nbscloud.notes.action.delete=Delete
nbscloud.notes.save.success=Saved
nbscloud.notes.delete.success=Deleted
nbscloud.notes.createDir.success=Directory created
nbscloud.notes.createNote.success=Note created

View File

@@ -0,0 +1,7 @@
nbscloud.notes.action.save=Speichern
nbscloud.notes.action.delete=L\u00F6schen
nbscloud.notes.save.success=Gespeichert
nbscloud.notes.delete.success=Gel\u00F6scht
nbscloud.notes.createDir.success=Verzeichnis erstellt
nbscloud.notes.createNote.success=Notiz erstellt

View File

@@ -0,0 +1,59 @@
#content-container {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
row-gap: 2em;
column-gap: 2em;
}
#nav-tree {
flex-grow: 1;
background-color: var(--background-color-highlight);
overflow-y: scroll;
}
#sub-content {
display: flex;
flex-direction: column;
flex-wrap: nowrap;
row-gap: 2em;
column-gap: 2em;
flex-grow: 1;
flex-basis: 70%;
}
#nav-tree ul {
list-style-type: none;
padding-left: 1.6em;
}
#nav-tree p, a {
margin: 0em;
}
#edit-area {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
}
#edit-area textarea {
flex: 1;
background-color: var(--background-color-highlight);
color: var(--text-color);
resize: none;
}
#actions-sub {
display: flex;
flex-direction: row-reverse;
flex-wrap: nowrap;
row-gap: 2em;
column-gap: 2em;
margin-top: 2em;
}
#dir-container > * {
display: inline;
}

View File

@@ -0,0 +1,12 @@
<div th:fragment="tree-level">
<ul>
<li th:each="tmpSubTree : ${tree}">
<div th:if="${tmpSubTree.directory}" id="dir-container">
<span class="icon">&#xe2c7;</span>
<p th:text="${tmpSubTree.name}" />
</div>
<p th:if="${!tmpSubTree.directory}"><a th:href="@{/notes(notePath=${tmpSubTree.path})}" th:text="${tmpSubTree.name}" /></p>
<th:block th:include="@{notes/fragments/treeLevel} :: tree-level" th:with="tree=${tmpSubTree.subTree}" />
</li>
</ul>
</div>

View File

@@ -0,0 +1,47 @@
<div id="menu-container" th:fragment="menu">
<div class="menu-spacer"></div>
<div id="create-dir-container" class="menu-container">
<details>
<summary>
<span id="create-dir-container-span-input" class="icon menu-icon">&#xe2cc;</span>
</summary>
<div class="menu-modal">
<div class="menu-modal-content">
<form method="POST" action="#" th:action="@{/notes/createDir}" enctype="multipart/form-data">
<input id="create-dir-container-dir-name" type="text" name="dirName" />
<select size="1" id="parentPath" name="parentPath">
<option th:each="dir : ${dirs}"
th:value="${dir}"
th:text="${dir}" />
</select>
<input type="hidden" name="notePath" th:value="${currentNote}" class="display-none" />
<input type="submit" value="Create"/>
</form>
</div>
</div>
</details>
</div>
<div class="menu-spacer"></div>
<div id="create-note-container" class="menu-container">
<details>
<summary>
<span id="create-note-container-span-input" class="icon menu-icon">&#xe89c;</span>
<div class="create-note-container-details-modal-overlay"></div>
</summary>
<div class="menu-modal">
<div class="menu-modal-content">
<form method="POST" action="#" th:action="@{/notes/createNote}" enctype="multipart/form-data">
<input id="create-note-container-dir-name" type="text" name="noteName" />
<select size="1" id="parentPathNote" name="parentPath">
<option th:each="dir : ${dirs}"
th:value="${dir}"
th:text="${dir}" />
</select>
<input type="hidden" name="notePath" th:value="${currentNote}" class="display-none" />
<input type="submit" value="Create"/>
</form>
</div>
</div>
</details>
</div>
</div>

View File

@@ -0,0 +1,40 @@
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="#{nbscloud.notes.index.title} + ${currentNote}"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link th:if="${darkMode}" rel="stylesheet" th:href="@{/css/darkModeColors.css}"/>
<link th:if="${!darkMode}" rel="stylesheet" th:href="@{/css/lightModeColors.css}"/>
<link rel="stylesheet" th:href="@{/css/main.css}"/>
<link rel="stylesheet" th:href="@{/css/notes_main.css}"/>
<link rel="shortcut icon" th:href="@{/favicon.ico}"/>
</head>
<body>
<div id="main-container">
<div th:replace="includes/header :: header"/>
<div th:replace="includes/messages :: messages"/>
<div th:replace="notes/includes/menu :: menu"/>
<div id="content-container">
<div id="nav-tree">
<th:block th:include="@{notes/fragments/treeLevel} :: tree-level" th:with="tree=${tree}" />
</div>
<div id="sub-content">
<form method="POST" action="#" enctype="multipart/form-data">
<div id="edit-area">
<textarea type="text" th:if="${content != null}" th:text="${content}" rows="25" name="textContent"/>
</div>
<div id="actions">
<div th:if="${content != null}" id="actions-sub">
<input type="submit" th:value="#{nbscloud.notes.action.save}" th:formaction="@{/notes/save}" />
<input type="submit" th:value="#{nbscloud.notes.action.delete}" th:formaction="@{/notes/delete}" />
<input type="hidden" name="notePath" th:value="${currentNote}" class="display-none" />
</div>
</div>
</form>
</div>
</div>
<div th:replace="includes/footer :: footer"/>
</div>
</body>
</html>