mirror of https://github.com/TriliumNext/Notes
Revert "Merge pull request #1234 from TriliumNext/feature/task_list"
This reverts commitpull/1348/head58a8821c22, reversing changes made to50d491b432.
parent
ee7b97ae56
commit
00e576b052
@ -1,84 +0,0 @@
|
|||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
import type FTask from "../entities/ftask.js";
|
|
||||||
import server from "./server.js";
|
|
||||||
|
|
||||||
interface CreateNewTasksOpts {
|
|
||||||
parentNoteId: string;
|
|
||||||
title: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createNewTask({ parentNoteId, title }: CreateNewTasksOpts) {
|
|
||||||
await server.post(`tasks`, {
|
|
||||||
parentNoteId,
|
|
||||||
title: title.trim()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function toggleTaskDone(taskId: string) {
|
|
||||||
await server.post(`tasks/${taskId}/toggle`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateTask(task: FTask) {
|
|
||||||
if (!task.taskId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await server.patch(`tasks/${task.taskId}/`, {
|
|
||||||
taskId: task.taskId,
|
|
||||||
dueDate: task.dueDate,
|
|
||||||
isDone: task.isDone
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,268 +0,0 @@
|
|||||||
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";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
import calendarTime from "dayjs/plugin/calendar.js";
|
|
||||||
import { t } from "../../services/i18n.js";
|
|
||||||
dayjs.extend(calendarTime);
|
|
||||||
|
|
||||||
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;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-detail-task-list .task-container li:hover {
|
|
||||||
background: var(--input-hover-background);
|
|
||||||
transition: background 250ms ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-detail-task-list .task-container li > header {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-detail-task-list .task-container li .check {
|
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-detail-task-list .task-container li .title {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-detail-task-list .task-container li .due-date {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-detail-task-list .task-container li.overdue .due-date {
|
|
||||||
color: #fd8282;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
function buildTasks(tasks: FTask[]) {
|
|
||||||
let html = "";
|
|
||||||
|
|
||||||
const now = dayjs();
|
|
||||||
const dateFormat = "DD-MM-YYYY";
|
|
||||||
for (const task of tasks) {
|
|
||||||
const classes = ["task"];
|
|
||||||
|
|
||||||
if (task.dueDate && dayjs(task.dueDate).isBefore(now, "days")) {
|
|
||||||
classes.push("overdue");
|
|
||||||
}
|
|
||||||
|
|
||||||
html += `<li class="${classes.join(" ")}" data-task-id="${task.taskId}">`;
|
|
||||||
html += "<header>";
|
|
||||||
html += '<span class="title">';
|
|
||||||
html += `<input type="checkbox" class="check" ${task.isDone ? "checked" : ""} />`;
|
|
||||||
html += `${task.title}</span>`;
|
|
||||||
html += '</span>';
|
|
||||||
if (task.dueDate) {
|
|
||||||
html += `<span class="due-date">`;
|
|
||||||
html += `<span class="bx bx-calendar"></span> `;
|
|
||||||
html += dayjs(task.dueDate).calendar(null, {
|
|
||||||
sameDay: `[${t("tasks.due.today")}]`,
|
|
||||||
nextDay: `[${t("tasks.due.tomorrow")}]`,
|
|
||||||
nextWeek: "dddd",
|
|
||||||
lastDay: `[${t("tasks.due.yesterday")}]`,
|
|
||||||
lastWeek: dateFormat,
|
|
||||||
sameElse: dateFormat
|
|
||||||
});
|
|
||||||
html += "</span>";
|
|
||||||
}
|
|
||||||
html += "</header>";
|
|
||||||
html += `<div class="edit-container"></div>`;
|
|
||||||
html += `</li>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildEditContainer(task: FTask) {
|
|
||||||
return `\
|
|
||||||
<label>Due date:</label>
|
|
||||||
<input type="date" data-tasks-role="due-date" value="${task.dueDate ?? ""}" />
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
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.$addNewTask.val("");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$taskContainer.on("change", "input.check", (e) => {
|
|
||||||
const $target = $(e.target);
|
|
||||||
const taskId = $target.closest("li")[0].dataset.taskId;
|
|
||||||
|
|
||||||
if (!taskId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
taskService.toggleTaskDone(taskId);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$taskContainer.on("click", "li", (e) => {
|
|
||||||
if ((e.target as HTMLElement).tagName === "INPUT") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const $target = $(e.target);
|
|
||||||
|
|
||||||
// Clear existing edit containers.
|
|
||||||
const $existingContainers = this.$taskContainer.find(".edit-container");
|
|
||||||
|
|
||||||
$existingContainers.html("");
|
|
||||||
|
|
||||||
// Add the new edit container.
|
|
||||||
const $editContainer = $target.closest("li").find(".edit-container");
|
|
||||||
const task = this.#getCorrespondingTask($target);
|
|
||||||
if (task) {
|
|
||||||
$editContainer.html(buildEditContainer(task));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$taskContainer.on("change", "input:not(.check)", async (e) => {
|
|
||||||
const $target = $(e.target);
|
|
||||||
const task = this.#getCorrespondingTask($target);
|
|
||||||
if (!task) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const role = $target.data("tasks-role");
|
|
||||||
const value = String($target.val());
|
|
||||||
|
|
||||||
switch (role) {
|
|
||||||
case "due-date":
|
|
||||||
task.dueDate = value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
await taskService.updateTask(task);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#getCorrespondingTask($target: JQuery<HTMLElement>) {
|
|
||||||
const $parentEl = $target.closest("li");
|
|
||||||
if (!$parentEl.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const taskId = $parentEl[0].dataset.taskId;
|
|
||||||
if (!taskId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return froca.getTask(taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async #createNewTask(title: string) {
|
|
||||||
if (!title || !this.noteId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await taskService.createNewTask({
|
|
||||||
title,
|
|
||||||
parentNoteId: this.noteId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async #getTasks() {
|
|
||||||
if (!this.noteId) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return (await froca.getTasks(this.noteId)).toSorted((a, b) => {
|
|
||||||
// Sort by due date, closest date first.
|
|
||||||
if (!a.dueDate) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!b.dueDate) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.dueDate.localeCompare(b.dueDate, "en");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async doRefresh(note: FNote) {
|
|
||||||
this.$widget.show();
|
|
||||||
|
|
||||||
if (!this.note || !this.noteId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tasks = await this.#getTasks();
|
|
||||||
const tasksHtml = buildTasks(tasks);
|
|
||||||
this.$taskContainer.html(tasksHtml);
|
|
||||||
}
|
|
||||||
|
|
||||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
|
||||||
if (this.noteId && loadResults.isTaskListReloaded(this.noteId)) {
|
|
||||||
this.refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
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 updateTask(req: Request) {
|
|
||||||
return tasksService.updateTask(req.params.taskId, req.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toggleTaskDone(req: Request) {
|
|
||||||
const { taskId } = req.params;
|
|
||||||
if (!taskId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tasksService.toggleTaskDone(taskId);
|
|
||||||
}
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
import becca from "../becca/becca.js";
|
|
||||||
import BTask from "../becca/entities/btask.js";
|
|
||||||
import type { TaskRow } from "../becca/entities/rows.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();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateTask(taskId: string, content: TaskRow) {
|
|
||||||
const task = becca.tasks[taskId];
|
|
||||||
task.isDone = !!content.isDone;
|
|
||||||
task.dueDate = content.dueDate;
|
|
||||||
task.save();
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue