Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optionally show notebook tag and title on each card #40

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/board.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ export default class Board {
name: this.boardName,
messages: [],
hiddenTags: [],
displayConfig: this.parsedConfig?.display ?? {},
};

if (this.isValid) {
Expand Down
33 changes: 30 additions & 3 deletions src/gui/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import React from "react";
import React, { useContext } from "react";
import styled from "styled-components";
import { IoMdPricetag } from "react-icons/io";
import { IoMdFolder, IoMdPricetag } from "react-icons/io";
import { IoCalendarOutline } from "react-icons/io5";
import moment from "moment";

import type { NoteData } from "../types";
import { BoardContext } from "./index";

const dateFmt = document.getElementById('date-fmt')?.innerHTML || ""

export default React.forwardRef<HTMLDivElement, { note: NoteData }>(
({ note }, ref) => {
const { title, tags, due } = note;

const board = useContext(BoardContext);

const { title, tags, due, notebookData: notebook } = note;

const renderExtra = (
key: number,
Expand All @@ -23,10 +27,28 @@ export default React.forwardRef<HTMLDivElement, { note: NoteData }>(
{text}
</ExtraItem>
);

const extras: [React.ReactNode, string, string?][] = tags.map((tag) => [
<IoMdPricetag size="1rem" />,
tag,
]);

if (board.displayConfig.showNotebookTag) {
const { icon } = notebook;
extras.unshift([
icon ? (
icon.dataUrl ? (
<DataIcon alt={notebook.title} src={icon.dataUrl} />
) : (
<span>{icon.emoji}</span>
)
) : (
<IoMdFolder size="1rem" />
),
notebook.title,
]);
}

if (due > 0) {
const dueDate = new Date(due);
const dateStr = moment(dueDate).format(dateFmt);
Expand Down Expand Up @@ -83,3 +105,8 @@ const ExtraItem = styled.div<{ color: string }>(({ color }) => ({
const IconCont = styled.span({
marginRight: "4px",
});

const DataIcon = styled.img({
maxHeight: "1rem",
maxWidth: "1rem",
});
97 changes: 54 additions & 43 deletions src/gui/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import React from "react";
import { render } from "react-dom";
import styled from "styled-components";
import { IoMdSettings, IoMdAdd } from "react-icons/io";
import { IoMdSettings, IoMdAdd, IoMdEye } from "react-icons/io";

import { capitalize } from "../utils";
import { DispatchFn, useRemoteBoard } from "./hooks";
import { DragDropContext } from "./DragDrop";
import Column from "./Column";
import type { Message } from "../types";
import type { BoardState, Message } from "../types";

export const DispatchContext = React.createContext<DispatchFn>(async () => {});
export const IsWaitingContext = React.createContext<boolean>(false);
export const BoardContext = React.createContext<BoardState>({} as BoardState);

function MessageBox({
message,
Expand Down Expand Up @@ -57,49 +58,59 @@ function App() {
}));

const cont = board ? (
<Container>
<Header>
{board.name}
<IconCont
onClick={() =>
dispatch({ type: "settings", payload: { target: "filters" } })
}
>
<IoMdSettings size="25px" />
</IconCont>

<IconCont
onClick={() =>
dispatch({ type: "settings", payload: { target: "columnnew" } })
}
>
<IoMdAdd size="25px" />
</IconCont>
</Header>

<MessagesCont>
{board.messages.map((msg, idx) => (
<MessageBox
key={idx}
message={msg}
onMsgAction={(action) =>
dispatch({
type: "messageAction",
payload: { actionName: action, messageId: msg.id },
})
<BoardContext.Provider value={board}>
<Container>
<Header>
{board.name}
<IconCont
onClick={() =>
dispatch({ type: "settings", payload: { target: "filters" } })
}
/>
))}
</MessagesCont>

{board.columns && (
<ColumnsCont>
{notesToShow?.map(({ name, notes }) => (
<Column key={name} name={name} notes={notes} />
>
<IoMdSettings size="25px" />
</IconCont>

<IconCont
onClick={() =>
dispatch({ type: "settings", payload: { target: "display" } })
}
>
<IoMdEye size="25px" />
</IconCont>

<IconCont
onClick={() =>
dispatch({ type: "settings", payload: { target: "columnnew" } })
}
>
<IoMdAdd size="25px" />
</IconCont>
</Header>

<MessagesCont>
{board.messages.map((msg, idx) => (
<MessageBox
key={idx}
message={msg}
onMsgAction={(action) =>
dispatch({
type: "messageAction",
payload: { actionName: action, messageId: msg.id },
})
}
/>
))}
</ColumnsCont>
)}
</Container>
</MessagesCont>

{board.columns && (
<ColumnsCont>
{notesToShow?.map(({ name, notes }) => (
<Column key={name} name={name} notes={notes} />
))}
</ColumnsCont>
)}
</Container>
</BoardContext.Provider>
) : (
<h1>Loading...</h1>
);
Expand Down
36 changes: 24 additions & 12 deletions src/noteData.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import joplin from "api";
import { getUpdatedConfigNote } from "./parser";
import { NoteData, ConfigNote, UpdateQuery } from "./types";
import { ConfigNote, Folder, NoteData, UpdateQuery } from "./types";

/**
* Execute the given search query and return all matching notes in the kanban format.
Expand Down Expand Up @@ -35,6 +35,11 @@ async function search(query: string): Promise<NoteData[]> {
has_more: boolean;
};

const allNotebooks = await getAllNotebooks();
const notebookIndex = Object.fromEntries(
allNotebooks.map((folder) => [folder.id, folder])
);

let allNotes: any[] = [];
let page = 1;
while (true) {
Expand Down Expand Up @@ -71,6 +76,7 @@ async function search(query: string): Promise<NoteData[]> {
isTodo: !!note.is_todo,
isCompleted: !!note.todo_completed,
notebookId: note.parent_id,
notebookData: notebookIndex[note.parent_id],
due: note.todo_due,
order: note.order === 0 ? note.created_time : note.order,
createdTime: note.created_time,
Expand Down Expand Up @@ -272,28 +278,34 @@ export async function findAllChildrenNotebook(
return children;
}

type Folder = {
id: string;
title: string;
parent_id: string;
};

/**
* Get a list of all notebooks, with id, title, and parent_id.
*/
export async function getAllNotebooks(): Promise<Folder[]> {
const fields = ["id", "title", "parent_id", "icon"];

let folders: Folder[] = [];
let page = 1;

while (true) {
console.log("data call get", ["folders"], { page });

const {
items: newFolders,
items,
has_more: hasMore,
}: { items: Folder[]; has_more: boolean } = await joplin.data.get(
["folders"],
{ page }
);
}: { items: (Folder & { icon: string })[]; has_more: boolean } =
await joplin.data.get(["folders"], { page, fields });
const newFolders = items.map((folder) => {
let icon = folder.icon || null;
if (icon)
try {
// Is it always JSON? I'm not sure.
icon = JSON.parse(icon);
} catch (e) {
console.error(e);
}
return { ...folder, icon };
});
folders = [...folders, ...newFolders];

if (!hasMore) break;
Expand Down
4 changes: 4 additions & 0 deletions src/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,14 @@ const editorTypes = {
completed: "checkbox",
backlog: "checkbox",
},
display: {
showNotebookTag: "checkbox",
},
};

export const getRuleEditorTypes = (targetPath: string) => {
if (targetPath === "filters") return editorTypes.filters;
if (targetPath === "display") return editorTypes.display;
if (targetPath.startsWith("column")) return editorTypes.columns;
throw new Error(`Unkown target path ${targetPath}`);
};
Expand Down
15 changes: 14 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ export interface Config {
backlog?: boolean;
}[];
display: {
markdown: string;
markdown?: string;
showNotebookTag?: boolean;
};
}

Expand Down Expand Up @@ -79,6 +80,7 @@ export interface BoardState {
}[];
hiddenTags: string[];
messages: Message[];
displayConfig: Config["display"];
}

// Joplin API related types
Expand All @@ -96,11 +98,22 @@ export interface ConfigNote {
body: string;
}

export interface Folder {
id: string;
title: string;
parent_id: string;
icon: null | {
emoji: string;
dataUrl?: string;
};
}

export interface NoteData {
id: string;
title: string;
tags: string[];
notebookId: string;
notebookData: Folder;
isTodo: boolean;
isCompleted: boolean;
due: number;
Expand Down
7 changes: 7 additions & 0 deletions tests/board.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ const note = (args: Partial<NoteData> = {}): NoteData => ({
id: "id",
title: "title",
notebookId: parentNb,
notebookData: {
id: parentNb,
title: "notebook",
icon: { emoji: "☢️" },
parent_id: ""
},
tags: ["task"],
createdTime: mockTime,
due: 0,
Expand All @@ -90,6 +96,7 @@ const state = (
{ name: "Working", notes: working },
{ name: "Done", notes: done },
],
displayConfig: {},
});

describe("Board", () => {
Expand Down