diff --git a/files-api/src/main/java/de/nbscloud/files/api/FilesService.java b/files-api/src/main/java/de/nbscloud/files/api/FilesService.java index 4e1d8fa..7d2e4c8 100644 --- a/files-api/src/main/java/de/nbscloud/files/api/FilesService.java +++ b/files-api/src/main/java/de/nbscloud/files/api/FilesService.java @@ -65,7 +65,7 @@ public interface FilesService { } } - void createAppDirectory(App app); + void createAppDirectoryIfNotExists(App app); void createDirectory(App app, Path path); @@ -81,7 +81,7 @@ public interface FilesService { * Paths in return list are always relative to the appDir. Non-recursive list. * * @param app to list the files for - * @param path in case of {@link Optional#EMPTY} the appDir is used as start dir. If not empty, path has to be relative + * @param path in case of an empty optional the appDir is used as start dir. If not empty, path has to be relative * to the appDir */ List list(App app, Optional path); diff --git a/files/src/main/java/de/nbscloud/files/FilesServiceImpl.java b/files/src/main/java/de/nbscloud/files/FilesServiceImpl.java index 5635825..c84600c 100644 --- a/files/src/main/java/de/nbscloud/files/FilesServiceImpl.java +++ b/files/src/main/java/de/nbscloud/files/FilesServiceImpl.java @@ -28,7 +28,7 @@ public class FilesServiceImpl implements FilesService { } @Override - public void createAppDirectory(App app) { + public void createAppDirectoryIfNotExists(App app) { try { this.fileSystemService.createDirectory(resolve(app, Path.of(""))); } diff --git a/files/src/main/java/de/nbscloud/files/controller/FilesController.java b/files/src/main/java/de/nbscloud/files/controller/FilesController.java index 92790e8..3aa156a 100644 --- a/files/src/main/java/de/nbscloud/files/controller/FilesController.java +++ b/files/src/main/java/de/nbscloud/files/controller/FilesController.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import de.nbscloud.files.FileSystemService; import de.nbscloud.files.LocationTracker; +import de.nbscloud.files.SessionInfo; import de.nbscloud.files.Share; import de.nbscloud.files.api.FilesService.ContentContainer; import de.nbscloud.files.config.FilesConfig; @@ -12,9 +13,9 @@ import de.nbscloud.files.form.PasswordForm; import de.nbscloud.files.form.RenameForm; import de.nbscloud.files.form.ShareForm; import de.nbscloud.webcontainer.MessageHelper; -import de.nbscloud.files.SessionInfo; import de.nbscloud.webcontainer.registry.AppRegistry; import de.nbscloud.webcontainer.shared.config.WebContainerSharedConfig; +import de.nbscloud.webcontainer.shared.util.ControllerUtils; import org.apache.commons.io.input.ObservableInputStream; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -43,7 +44,6 @@ import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; -import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -248,11 +248,6 @@ public class FilesController implements InitializingBean { final Path filePath = this.locationTracker.getRelativeToBaseDir(this.locationTracker.resolve(filename)); final String shareUuid = UUID.randomUUID().toString(); final Share share = new Share(); - // The format is always "yyyy-MM-dd", see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date - final LocalDate expiryDate = Optional.ofNullable(expiryDateString) - .map(ed -> ed.isBlank() ? null : ed) - .map(ed -> LocalDate.parse(ed, DateTimeFormatter.ofPattern("yyyy-MM-dd"))) - .orElse(null); if(StringUtils.isEmpty(password)) { password = null; // if no real password has been provided assume no password @@ -260,7 +255,7 @@ public class FilesController implements InitializingBean { share.setUuid(shareUuid); share.setPath(filePath.toString()); - share.setExpiryDate(expiryDate); + share.setExpiryDate(ControllerUtils.parseDate(expiryDateString)); share.setOneTime(oneTime); share.setPassword(password); @@ -281,6 +276,10 @@ public class FilesController implements InitializingBean { @GetMapping("/files/shares/files/browse/**") public String sharesStart(Model model, HttpServletRequest httpServletRequest, String sortOrder) { + if(!this.sessionInfo.isRestrictedSession()) { + throw new IllegalStateException(); + } + final String start = start(model, httpServletRequest, sortOrder); model.addAttribute("prefix", "/files/shares"); @@ -301,8 +300,6 @@ public class FilesController implements InitializingBean { final Optional optExpiryDate = Optional.ofNullable(share.getExpiryDate()); final boolean expired = optExpiryDate.map(expiryDate -> LocalDate.now().isAfter(expiryDate)).orElse(false); - this.sessionInfo.setRestrictedSession(true); - if(share.getPassword() != null) { model.addAttribute("form", new PasswordForm(shareUuid, null)); this.webContainerSharedConfig.addDefaults(model); @@ -320,6 +317,8 @@ public class FilesController implements InitializingBean { this.locationTracker.setBaseDirPath(share.getPath()); this.locationTracker.resetCurrentLocation(); + this.sessionInfo.setRestrictedSession(true); + return "redirect:/files/browse/"; } else { @@ -354,6 +353,8 @@ public class FilesController implements InitializingBean { this.locationTracker.setBaseDirPath(share.getPath()); this.locationTracker.resetCurrentLocation(); + this.sessionInfo.setRestrictedSession(true); + return "redirect:/files/browse/"; } else { @@ -408,6 +409,10 @@ public class FilesController implements InitializingBean { @GetMapping("/files/shares/files/preview") public void sharesPreview(HttpServletResponse response, String filename) { + if(!this.sessionInfo.isRestrictedSession()) { + throw new IllegalStateException(); + } + preview(response, filename); } @@ -426,6 +431,10 @@ public class FilesController implements InitializingBean { @GetMapping("/files/shares/files/gallery") public String sharesGallery(Model model) { + if(!this.sessionInfo.isRestrictedSession()) { + throw new IllegalStateException(); + } + final String gallery = gallery(model); model.addAttribute("prefix", "/files/shares"); diff --git a/notes/src/main/java/de/nbscloud/notes/controller/NotesController.java b/notes/src/main/java/de/nbscloud/notes/controller/NotesController.java index c2e6b91..27fb989 100644 --- a/notes/src/main/java/de/nbscloud/notes/controller/NotesController.java +++ b/notes/src/main/java/de/nbscloud/notes/controller/NotesController.java @@ -20,7 +20,7 @@ import java.nio.file.Path; import java.util.Optional; @Controller -public class NotesController implements InitializingBean { +public class NotesController { private static final Logger logger = LoggerFactory.getLogger(NotesController.class); @Autowired @@ -42,6 +42,8 @@ public class NotesController implements InitializingBean { @GetMapping("/notes") public String start(Model model, @RequestParam(required = false) String notePath) { + this.filesService.createAppDirectoryIfNotExists(this.app); + final Optional optNotePath = notePathToPath(notePath); if(optNotePath.isPresent()) { @@ -121,9 +123,4 @@ public class NotesController implements InitializingBean { return "redirect:?notePath=" + currentPath; } - - @Override - public void afterPropertiesSet() throws Exception { - //this.filesService.createAppDirectory(this.app); - } } diff --git a/todo/pom.xml b/todo/pom.xml index c39d75c..a62c112 100644 --- a/todo/pom.xml +++ b/todo/pom.xml @@ -31,5 +31,9 @@ spring-boot-starter-web provided + + de.77zzcx7.nbs-cloud + files-api + \ No newline at end of file diff --git a/todo/src/main/java/de/nbscloud/todo/Category.java b/todo/src/main/java/de/nbscloud/todo/Category.java new file mode 100644 index 0000000..c5cabcf --- /dev/null +++ b/todo/src/main/java/de/nbscloud/todo/Category.java @@ -0,0 +1,5 @@ +package de.nbscloud.todo; + +public enum Category { + A, B, C; +} diff --git a/todo/src/main/java/de/nbscloud/todo/TodoApp.java b/todo/src/main/java/de/nbscloud/todo/TodoApp.java index e4eedfb..c753fbd 100644 --- a/todo/src/main/java/de/nbscloud/todo/TodoApp.java +++ b/todo/src/main/java/de/nbscloud/todo/TodoApp.java @@ -1,19 +1,26 @@ package de.nbscloud.todo; +import de.nbscloud.todo.widget.TodoWidget; import de.nbscloud.webcontainer.registry.App; import de.nbscloud.webcontainer.registry.AppRegistry; +import de.nbscloud.webcontainer.registry.Widget; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import java.util.Collection; +import java.util.List; + @Component public class TodoApp implements App, InitializingBean { + + public static final String ID = "todo"; @Autowired private AppRegistry appRegistry; @Override public String getId() { - return "todo"; + return ID; } @Override @@ -31,6 +38,11 @@ public class TodoApp implements App, InitializingBean { return 40; } + @Override + public Collection getWidgets() { + return List.of(new TodoWidget()); + } + @Override public void afterPropertiesSet() throws Exception { this.appRegistry.registerApp(this); diff --git a/todo/src/main/java/de/nbscloud/todo/TodoItem.java b/todo/src/main/java/de/nbscloud/todo/TodoItem.java new file mode 100644 index 0000000..d6be225 --- /dev/null +++ b/todo/src/main/java/de/nbscloud/todo/TodoItem.java @@ -0,0 +1,74 @@ +package de.nbscloud.todo; + +import java.time.LocalDate; +import java.util.UUID; + +public class TodoItem { + private String id; + private boolean done; + private String text; + private Category category; + private LocalDate added; + private LocalDate due; + + public static final TodoItem create(boolean done, String text, Category category, LocalDate added, LocalDate due) { + final TodoItem item = new TodoItem(); + + item.setDone(done); + item.setText(text); + item.setCategory(category); + item.setAdded(added); + item.setDue(due); + item.setId(UUID.randomUUID().toString()); + + return item; + } + + public boolean isDone() { + return done; + } + + public void setDone(boolean done) { + this.done = done; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public Category getCategory() { + return category; + } + + public void setCategory(Category category) { + this.category = category; + } + + public LocalDate getAdded() { + return added; + } + + public void setAdded(LocalDate added) { + this.added = added; + } + + public LocalDate getDue() { + return due; + } + + public void setDue(LocalDate due) { + this.due = due; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/todo/src/main/java/de/nbscloud/todo/TodoList.java b/todo/src/main/java/de/nbscloud/todo/TodoList.java new file mode 100644 index 0000000..600e650 --- /dev/null +++ b/todo/src/main/java/de/nbscloud/todo/TodoList.java @@ -0,0 +1,16 @@ +package de.nbscloud.todo; + +import java.util.ArrayList; +import java.util.List; + +public class TodoList { + private List todos = new ArrayList<>(); + + public List getTodos() { + return todos; + } + + public void setTodos(List todos) { + this.todos = todos; + } +} diff --git a/todo/src/main/java/de/nbscloud/todo/controller/TodoController.java b/todo/src/main/java/de/nbscloud/todo/controller/TodoController.java new file mode 100644 index 0000000..d5f658c --- /dev/null +++ b/todo/src/main/java/de/nbscloud/todo/controller/TodoController.java @@ -0,0 +1,159 @@ +package de.nbscloud.todo.controller; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.nbscloud.files.api.FilesService; +import de.nbscloud.todo.Category; +import de.nbscloud.todo.TodoApp; +import de.nbscloud.todo.TodoItem; +import de.nbscloud.todo.TodoList; +import de.nbscloud.webcontainer.MessageHelper; +import de.nbscloud.webcontainer.registry.AppRegistry; +import de.nbscloud.webcontainer.shared.config.WebContainerSharedConfig; +import de.nbscloud.webcontainer.shared.util.ControllerUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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 java.nio.charset.StandardCharsets; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.time.LocalDate; +import java.util.*; +import java.util.stream.Collectors; + +@Controller +public class TodoController { + private static final Logger logger = LoggerFactory.getLogger(TodoController.class); + + public static final Comparator TODO_ITEM_COMPARATOR = Comparator.comparing(TodoItem::isDone) + .thenComparing(TodoItem::getDue, + Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparing(TodoItem::getAdded); + static final Path FILENAME = Path.of("todo"); + + @Autowired + private AppRegistry appRegistry; + @Autowired + private FilesService filesService; + @Autowired + private WebContainerSharedConfig webContainerSharedConfig; + @Autowired + private TodoApp app; + @Autowired + private MessageHelper messageHelper; + @Autowired + private ObjectMapper mapper; + + @GetMapping("/todo") + public String start(Model model) { + try { + this.filesService.createAppDirectoryIfNotExists(this.app); + + final String todoListJson = new String(this.filesService.get(app, FILENAME)); + final TodoList todos = mapper.readValue(todoListJson, TodoList.class); + final Map> categoryMap = new TreeMap<>(Comparator.comparing(Category::name)); + + todos.getTodos().stream().collect(Collectors.groupingBy(TodoItem::getCategory, () -> categoryMap, Collectors.toList())); + + if(categoryMap.containsKey(Category.A)) { + Collections.sort(categoryMap.get(Category.A), TODO_ITEM_COMPARATOR); + } + + if(categoryMap.containsKey(Category.B)) { + Collections.sort(categoryMap.get(Category.B), TODO_ITEM_COMPARATOR); + } + + if(categoryMap.containsKey(Category.C)) { + Collections.sort(categoryMap.get(Category.C), TODO_ITEM_COMPARATOR); + } + + model.addAttribute("todosByCategory", categoryMap); + } + catch(JsonProcessingException e) { + logger.error("Error while parsing todo file", e); + + this.messageHelper.addError(e.getMessage()); + } + catch(RuntimeException re) { + if(re.getCause() instanceof NoSuchFileException) { + logger.debug("Todo file does not exist, create it"); + + try { + this.filesService.createFile(app, FILENAME, mapper.writeValueAsString(new TodoList()).getBytes(StandardCharsets.UTF_8)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + else { + logger.error("Error while reading todo file", re); + + this.messageHelper.addError(re.getMessage()); + } + } + + model.addAttribute("categories", Category.values()); + this.messageHelper.addAndClearAll(model); + model.addAttribute("apps", this.appRegistry.getAll()); + this.webContainerSharedConfig.addDefaults(model); + + return "todo/todoIndex"; + } + + @PostMapping("/todo/addItem") + public String addItem(Model model, String text, Category category, String due) { + try { + final String todoListJson = new String(this.filesService.get(app, FILENAME)); + final TodoList todos = mapper.readValue(todoListJson, TodoList.class); + + todos.getTodos().add(TodoItem.create(false, text, category, LocalDate.now(), ControllerUtils.parseDate(due))); + + this.filesService.overwriteFile(app, FILENAME, mapper.writeValueAsBytes(todos)); + + this.messageHelper.addInfo("nbscloud.todo.item.add"); + } catch (JsonProcessingException e) { + logger.error("Error", e); + + this.messageHelper.addError(e.getMessage()); + } + + model.addAttribute("categories", Category.values()); + model.addAttribute("apps", this.appRegistry.getAll()); + this.webContainerSharedConfig.addDefaults(model); + + return "redirect:"; + } + + @PostMapping("/todo/toggle") + public String toggle(Model model, String id, boolean done) { + try { + final String todoListJson = new String(this.filesService.get(app, FILENAME)); + final TodoList todos = mapper.readValue(todoListJson, TodoList.class); + + todos.getTodos().stream().filter(item -> item.getId().equals(id)).findFirst().ifPresent(item -> item.setDone(done)); + + this.filesService.overwriteFile(app, FILENAME, mapper.writeValueAsBytes(todos)); + + if(done) { + this.messageHelper.addInfo("nbscloud.todo.item.toggle.done"); + } + else { + this.messageHelper.addInfo("nbscloud.todo.item.toggle.inprogress"); + } + } catch (JsonProcessingException e) { + logger.error("Error", e); + + this.messageHelper.addError(e.getMessage()); + } + + model.addAttribute("categories", Category.values()); + model.addAttribute("apps", this.appRegistry.getAll()); + this.webContainerSharedConfig.addDefaults(model); + + return "redirect:"; + } +} diff --git a/todo/src/main/java/de/nbscloud/todo/controller/TodoWidgetController.java b/todo/src/main/java/de/nbscloud/todo/controller/TodoWidgetController.java new file mode 100644 index 0000000..a163fb0 --- /dev/null +++ b/todo/src/main/java/de/nbscloud/todo/controller/TodoWidgetController.java @@ -0,0 +1,58 @@ +package de.nbscloud.todo.controller; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.nbscloud.files.api.FilesService; +import de.nbscloud.todo.Category; +import de.nbscloud.todo.TodoApp; +import de.nbscloud.todo.TodoItem; +import de.nbscloud.todo.TodoList; +import de.nbscloud.webcontainer.shared.config.WebContainerSharedConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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 javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.*; +import java.util.stream.Collectors; + +@Controller +public class TodoWidgetController { + private static final Logger logger = LoggerFactory.getLogger(TodoWidgetController.class); + + @Autowired + private WebContainerSharedConfig webContainerSharedConfig; + @Autowired + private FilesService filesService; + @Autowired + private TodoApp app; + @Autowired + private ObjectMapper mapper; + + @GetMapping("todo/widgets/todoOverview") + public String getTodoOverview(HttpServletRequest request, HttpServletResponse response, Model model) { + try { + this.filesService.createAppDirectoryIfNotExists(this.app); + + final String todoListJson = new String(this.filesService.get(app, TodoController.FILENAME)); + final TodoList todos = mapper.readValue(todoListJson, TodoList.class); + final List topFiveTodos = todos.getTodos().stream().filter(item -> !item.isDone()).sorted( + Comparator.comparing(TodoItem::getDue, + Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparing(TodoItem::getCategory) + .thenComparing(TodoItem::getAdded)).limit(5).collect(Collectors.toList()); + + model.addAttribute("todos", topFiveTodos); + } + catch(JsonProcessingException e) { + logger.error("Error while parsing todo file", e); + } + + + return "todo/widgets/todoOverview :: todo-overview"; + } +} diff --git a/todo/src/main/java/de/nbscloud/todo/widget/TodoWidget.java b/todo/src/main/java/de/nbscloud/todo/widget/TodoWidget.java new file mode 100644 index 0000000..6c5c9ab --- /dev/null +++ b/todo/src/main/java/de/nbscloud/todo/widget/TodoWidget.java @@ -0,0 +1,11 @@ +package de.nbscloud.todo.widget; + +import de.nbscloud.todo.TodoApp; +import de.nbscloud.webcontainer.registry.Widget; + +public class TodoWidget implements Widget { + @Override + public String getPath() { + return TodoApp.ID + "/widgets/todoOverview"; + } +} diff --git a/todo/src/main/resources/i18n/todo_messages.properties b/todo/src/main/resources/i18n/todo_messages.properties new file mode 100644 index 0000000..869c917 --- /dev/null +++ b/todo/src/main/resources/i18n/todo_messages.properties @@ -0,0 +1,7 @@ +nbscloud.todo.index.title=nbscloud - todo + +nbscloud.todo.widget.heading=todo overview + +nbscloud.todo.item.add=Todo added +nbscloud.todo.item.toggle.done=Done +nbscloud.todo.item.toggle.inprogress=In progress \ No newline at end of file diff --git a/todo/src/main/resources/i18n/todo_messages_de_DE.properties b/todo/src/main/resources/i18n/todo_messages_de_DE.properties new file mode 100644 index 0000000..1b4f8d7 --- /dev/null +++ b/todo/src/main/resources/i18n/todo_messages_de_DE.properties @@ -0,0 +1,7 @@ +nbscloud.todo.index.title=nbscloud - todo + +nbscloud.todo.widget.heading=todo \u00FCbersicht + +nbscloud.todo.item.add=Todo hinzugef\u00FCgt +nbscloud.todo.item.toggle.done=Fertig +nbscloud.todo.item.toggle.inprogress=Unfertig \ No newline at end of file diff --git a/todo/src/main/resources/static/css/todo_main.css b/todo/src/main/resources/static/css/todo_main.css new file mode 100644 index 0000000..73ba13c --- /dev/null +++ b/todo/src/main/resources/static/css/todo_main.css @@ -0,0 +1,24 @@ +#content-container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + row-gap: 2em; + column-gap: 2em; +} + +.todo-category { + flex-grow: 1; + background-color:var(--background-color-highlight); + padding: 1em; + align-self: stretch; + word-break: break-all; + flex-basis: 100%; +} + +.todo-category-header { + text-decoration-line: underline; +} + +.todo-item > td:nth-child(2) { + padding-inline: 3em; +} \ No newline at end of file diff --git a/todo/src/main/resources/templates/todo/includes/menu.html b/todo/src/main/resources/templates/todo/includes/menu.html new file mode 100644 index 0000000..201bc6f --- /dev/null +++ b/todo/src/main/resources/templates/todo/includes/menu.html @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/todo/src/main/resources/templates/todo/todoIndex.html b/todo/src/main/resources/templates/todo/todoIndex.html new file mode 100644 index 0000000..170239c --- /dev/null +++ b/todo/src/main/resources/templates/todo/todoIndex.html @@ -0,0 +1,39 @@ + + + + + + <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/todo_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="todo/includes/menu :: menu"/> + <div id="content-container"> + <div class="todo-category" th:each="entry : ${todosByCategory}"> + <p th:text="${#strings.prepend(#strings.append(entry.key, #strings.repeat(' ', 10)), ' ')}" + class="todo-category-header"/> + <table id="todo-item-table"> + <tr class="todo-item" th:each="todo : ${entry.value}"> + <td> + <form method="POST" th:action="@{/todo/toggle}" enctype="multipart/form-data"> + <input type="checkbox" th:checked="${todo.done}" name="done" onClick="this.form.submit()" /> <!-- fucking javascript --> + <input type="hidden" name="id" th:value="${todo.id}" class="display-none"/> + </form> + </td> + <td th:text="${todo.text}"/> + <td th:text="${todo.due}"/> + </tr> + </table> + </div> + </div> + <div th:replace="includes/footer :: footer"/> +</div> +</body> +</html> \ No newline at end of file diff --git a/todo/src/main/resources/templates/todo/widgets/todoOverview.html b/todo/src/main/resources/templates/todo/widgets/todoOverview.html new file mode 100644 index 0000000..badf042 --- /dev/null +++ b/todo/src/main/resources/templates/todo/widgets/todoOverview.html @@ -0,0 +1,12 @@ +<div id="todo-widget" class="widget" th:fragment="todo-overview"> + <p class="widget-heading" th:text="#{nbscloud.todo.widget.heading}"/> + <table id="todo-widget-table"> + <tbody> + <tr th:each="todo : ${todos}"> + <td th:text="${todo.category}"/> + <td th:text="${todo.text}"/> + <td th:text="${todo.due}"/> + </tr> + </tbody> + </table> +</div> \ No newline at end of file diff --git a/web-container-config/src/main/java/de/nbscloud/webcontainer/shared/util/ControllerUtils.java b/web-container-config/src/main/java/de/nbscloud/webcontainer/shared/util/ControllerUtils.java new file mode 100644 index 0000000..9109904 --- /dev/null +++ b/web-container-config/src/main/java/de/nbscloud/webcontainer/shared/util/ControllerUtils.java @@ -0,0 +1,16 @@ +package de.nbscloud.webcontainer.shared.util; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Optional; + +public class ControllerUtils { + + public static LocalDate parseDate(String date) { + // The format is always "yyyy-MM-dd", see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date + return Optional.ofNullable(date) + .map(ed -> ed.isBlank() ? null : ed) + .map(ed -> LocalDate.parse(ed, DateTimeFormatter.ofPattern("yyyy-MM-dd"))) + .orElse(null); + } +} diff --git a/web-container/src/main/resources/config/application.properties b/web-container/src/main/resources/config/application.properties index 3461d56..4d2076f 100644 --- a/web-container/src/main/resources/config/application.properties +++ b/web-container/src/main/resources/config/application.properties @@ -9,7 +9,7 @@ info.build.group=@project.groupId@ info.build.artifact=@project.artifactId@ info.build.version=@project.version@ -spring.messages.basename=i18n/container_messages,i18n/files_messages,i18n/dashboard_messages,i18n/notes_messages +spring.messages.basename=i18n/container_messages,i18n/files_messages,i18n/dashboard_messages,i18n/notes_messages,i18n/todo_messages spring.servlet.multipart.max-file-size=-1 spring.servlet.multipart.max-request-size=-1