mirror of https://github.com/TriliumNext/Notes
commit
58a8821c22
@ -0,0 +1,10 @@
|
||||
CREATE TABLE IF NOT EXISTS "tasks"
|
||||
(
|
||||
"taskId" TEXT NOT NULL PRIMARY KEY,
|
||||
"parentNoteId" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL DEFAULT "",
|
||||
"dueDate" INTEGER,
|
||||
"isDone" INTEGER NOT NULL DEFAULT 0,
|
||||
"isDeleted" INTEGER NOT NULL DEFAULT 0,
|
||||
"utcDateModified" TEXT NOT NULL
|
||||
);
|
||||
@ -0,0 +1,84 @@
|
||||
import date_utils from "../../services/date_utils.js";
|
||||
import AbstractBeccaEntity from "./abstract_becca_entity.js";
|
||||
import type BOption from "./boption.js";
|
||||
import type { TaskRow } from "./rows.js";
|
||||
|
||||
export default class BTask extends AbstractBeccaEntity<BOption> {
|
||||
|
||||
static get entityName() {
|
||||
return "tasks";
|
||||
}
|
||||
|
||||
static get primaryKeyName() {
|
||||
return "taskId";
|
||||
}
|
||||
|
||||
static get hashedProperties() {
|
||||
return [ "taskId", "parentNoteId", "title", "dueDate", "isDone", "isDeleted" ];
|
||||
}
|
||||
|
||||
taskId?: string;
|
||||
parentNoteId!: string;
|
||||
title!: string;
|
||||
dueDate?: string;
|
||||
isDone!: boolean;
|
||||
private _isDeleted?: boolean;
|
||||
|
||||
constructor(row?: TaskRow) {
|
||||
super();
|
||||
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateFromRow(row);
|
||||
this.init();
|
||||
}
|
||||
|
||||
get isDeleted() {
|
||||
return !!this._isDeleted;
|
||||
}
|
||||
|
||||
updateFromRow(row: TaskRow) {
|
||||
this.taskId = row.taskId;
|
||||
this.parentNoteId = row.parentNoteId;
|
||||
this.title = row.title;
|
||||
this.dueDate = row.dueDate;
|
||||
this.isDone = !!row.isDone;
|
||||
this._isDeleted = !!row.isDeleted;
|
||||
this.utcDateModified = row.utcDateModified;
|
||||
|
||||
if (this.taskId) {
|
||||
this.becca.tasks[this.taskId] = this;
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.taskId) {
|
||||
this.becca.tasks[this.taskId] = this;
|
||||
}
|
||||
}
|
||||
|
||||
protected beforeSaving(opts?: {}): void {
|
||||
super.beforeSaving();
|
||||
|
||||
this.utcDateModified = date_utils.utcNowDateTime();
|
||||
|
||||
if (this.taskId) {
|
||||
this.becca.tasks[this.taskId] = this;
|
||||
}
|
||||
}
|
||||
|
||||
getPojo() {
|
||||
return {
|
||||
taskId: this.taskId,
|
||||
parentNoteId: this.parentNoteId,
|
||||
title: this.title,
|
||||
dueDate: this.dueDate,
|
||||
isDone: this.isDone,
|
||||
isDeleted: this.isDeleted,
|
||||
utcDateModified: this.utcDateModified
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
import type { Froca } from "../services/froca-interface.js";
|
||||
|
||||
export interface FTaskRow {
|
||||
taskId: string;
|
||||
parentNoteId: string;
|
||||
title: string;
|
||||
dueDate?: string;
|
||||
isDone?: boolean;
|
||||
utcDateModified: string;
|
||||
}
|
||||
|
||||
export default class FTask {
|
||||
private froca: Froca;
|
||||
taskId!: string;
|
||||
parentNoteId!: string;
|
||||
title!: string;
|
||||
dueDate?: string;
|
||||
isDone!: boolean;
|
||||
utcDateModified!: string;
|
||||
|
||||
constructor(froca: Froca, row: FTaskRow) {
|
||||
this.froca = froca;
|
||||
this.update(row);
|
||||
}
|
||||
|
||||
update(row: FTaskRow) {
|
||||
this.taskId = row.taskId;
|
||||
this.parentNoteId = row.parentNoteId;
|
||||
this.title = row.title;
|
||||
this.dueDate = row.dueDate;
|
||||
this.isDone = !!row.isDone;
|
||||
this.utcDateModified = row.utcDateModified;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
import server from "./server.js";
|
||||
|
||||
interface CreateNewTasksOpts {
|
||||
parentNoteId: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export async function createNewTask({ parentNoteId, title }: CreateNewTasksOpts) {
|
||||
await server.post(`tasks`, {
|
||||
parentNoteId,
|
||||
title
|
||||
});
|
||||
}
|
||||
|
||||
export async function toggleTaskDone(taskId: string) {
|
||||
await server.post(`tasks/${taskId}/toggle`);
|
||||
}
|
||||
@ -0,0 +1,129 @@
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type FTask from "../../entities/ftask.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import TypeWidget from "./type_widget.js";
|
||||
import * as taskService from "../../services/tasks.js";
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="note-detail-task-list note-detail-printable">
|
||||
|
||||
<header>
|
||||
<input type="text" placeholder="Add a new task" class="add-new-task" />
|
||||
</header>
|
||||
|
||||
<ol class="task-container">
|
||||
</ol>
|
||||
|
||||
<style>
|
||||
.note-detail-task-list {
|
||||
height: 100%;
|
||||
contain: none;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.note-detail-task-list header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
margin: 0;
|
||||
padding: 0.5em 0;
|
||||
background-color: var(--main-background-color);
|
||||
}
|
||||
|
||||
.note-detail-task-list .add-new-task {
|
||||
width: 100%;
|
||||
padding: 0.25em 0.5em;
|
||||
}
|
||||
|
||||
.note-detail-task-list .task-container {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-radius: var(--bs-border-radius);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.note-detail-task-list .task-container li {
|
||||
background: var(--input-background-color);
|
||||
border-bottom: 1px solid var(--main-background-color);
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
|
||||
.note-detail-task-list .task-container li .check {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
`;
|
||||
|
||||
function buildTask(task: FTask) {
|
||||
return `\
|
||||
<li class="task">
|
||||
<input type="checkbox" class="check" data-task-id="${task.taskId}" ${task.isDone ? "checked" : ""} /> ${task.title}
|
||||
</li>`;
|
||||
}
|
||||
|
||||
export default class TaskListWidget extends TypeWidget {
|
||||
|
||||
private $taskContainer!: JQuery<HTMLElement>;
|
||||
private $addNewTask!: JQuery<HTMLElement>;
|
||||
|
||||
static getType() { return "taskList" }
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$addNewTask = this.$widget.find(".add-new-task");
|
||||
this.$taskContainer = this.$widget.find(".task-container");
|
||||
|
||||
this.$addNewTask.on("keydown", (e) => {
|
||||
if (e.key === "Enter") {
|
||||
this.#createNewTask(String(this.$addNewTask.val()));
|
||||
}
|
||||
});
|
||||
|
||||
this.$taskContainer.on("change", "input", (e) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
const taskId = target.dataset.taskId;
|
||||
|
||||
if (!taskId) {
|
||||
return;
|
||||
}
|
||||
|
||||
taskService.toggleTaskDone(taskId);
|
||||
});
|
||||
}
|
||||
|
||||
async #createNewTask(title: string) {
|
||||
if (!title || !this.noteId) {
|
||||
return;
|
||||
}
|
||||
|
||||
await taskService.createNewTask({
|
||||
title,
|
||||
parentNoteId: this.noteId
|
||||
});
|
||||
}
|
||||
|
||||
async doRefresh(note: FNote) {
|
||||
this.$widget.show();
|
||||
|
||||
if (!this.note || !this.noteId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$taskContainer.html("");
|
||||
|
||||
const tasks = await froca.getTasks(this.noteId);
|
||||
for (const task of tasks) {
|
||||
this.$taskContainer.append($(buildTask(task)));
|
||||
}
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (this.noteId && loadResults.isTaskListReloaded(this.noteId)) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
import type { Request } from "express";
|
||||
import * as tasksService from "../../services/tasks.js";
|
||||
|
||||
export function getTasks(req: Request) {
|
||||
const { parentNoteId } = req.params;
|
||||
return tasksService.getTasks(parentNoteId);
|
||||
}
|
||||
|
||||
export function createNewTask(req: Request) {
|
||||
return tasksService.createNewTask(req.body);
|
||||
}
|
||||
|
||||
export function toggleTaskDone(req: Request) {
|
||||
const { taskId } = req.params;
|
||||
if (!taskId) {
|
||||
return;
|
||||
}
|
||||
|
||||
return tasksService.toggleTaskDone(taskId);
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
import becca from "../becca/becca.js";
|
||||
import BTask from "../becca/entities/btask.js";
|
||||
|
||||
export function getTasks(parentNoteId: string) {
|
||||
return becca.getTasks()
|
||||
.filter((task) => task.parentNoteId === parentNoteId && !task.isDone);
|
||||
}
|
||||
|
||||
interface CreateTaskParams {
|
||||
parentNoteId: string;
|
||||
title: string;
|
||||
dueDate?: string;
|
||||
}
|
||||
|
||||
export function createNewTask(params: CreateTaskParams) {
|
||||
const task = new BTask(params);
|
||||
task.save();
|
||||
|
||||
return {
|
||||
task
|
||||
}
|
||||
}
|
||||
|
||||
export function toggleTaskDone(taskId: string) {
|
||||
const task = becca.tasks[taskId];
|
||||
task.isDone = !task.isDone;
|
||||
task.save();
|
||||
}
|
||||
Loading…
Reference in New Issue