Skip to content

Commit

Permalink
Todoist Integration (#8)
Browse files Browse the repository at this point in the history
* First steps to a todoist integration

* Enable adding todos

* Complete the todoist integration
  • Loading branch information
Dlurak authored Jan 14, 2025
1 parent 04c7e16 commit 8ee92ec
Show file tree
Hide file tree
Showing 16 changed files with 564 additions and 4 deletions.
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use flake
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ node_modules
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
.direnv/
27 changes: 27 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
};

outputs = {self, nixpkgs}: let
supportedSystems = ["x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin"];
forEachSupportedSystem = f:
nixpkgs.lib.genAttrs supportedSystems (system:
f {
pkgs = import nixpkgs {
inherit system;
};
});
in {
devShells = forEachSupportedSystem ({pkgs}: {
default = pkgs.mkShell {
packages = with pkgs; [
nodejs
pnpm
nodePackages.typescript-language-server
prettierd
];
shellHook = ''
if [ ! -d node_modules ]; then
echo "Use pnpm to install dependencies"
fi
'';
};
});
};
}
2 changes: 1 addition & 1 deletion src/lib/components/settings/BoolSetting.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
{/if}
</span>

<Switch bind:checked={value} />
<Switch bind:checked={value} on:activate on:inactivate on:toggle />
</div>
{#if description && showDescription}
<div transition:slide={{ duration: $animationLength }} class="text-gray-500 dark:text-gray-400">
Expand Down
120 changes: 120 additions & 0 deletions src/lib/components/settings/todo/todoist/Modal.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<script context="module" lang="ts">
const todoitToken = svocal('settings.todo.todoist.code');
type Color = keyof typeof TODOIST_COLORS;
async function createProject(name: string, color: Color) {
const res = await fetch('https://api.todoist.com/rest/v2/projects', {
method: Method.POST,
headers: { Authorization: `Bearer ${get(todoitToken)}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
name: name.trim() || 'Dlool',
color,
is_favourite: true,
view_style: 'board'
})
}).then((r) => r.json());
const scheme = z.object({
id: z.string(),
name: z.string(),
comment_count: z.number(),
color: z.union([
z.literal('berry_red'),
z.literal('red'),
z.literal('orange'),
z.literal('yellow'),
z.literal('olive_green'),
z.literal('lime_green'),
z.literal('green'),
z.literal('mint_green'),
z.literal('teal'),
z.literal('sky_blue'),
z.literal('light_blue'),
z.literal('blue'),
z.literal('grape'),
z.literal('violet'),
z.literal('lavender'),
z.literal('magenta'),
z.literal('salmon'),
z.literal('charcoal'),
z.literal('grey'),
z.literal('taupe')
]),
is_shared: z.boolean(),
order: z.number(),
is_favorite: z.boolean(),
is_inbox_project: z.boolean(),
is_team_inbox: z.boolean(),
view_style: z.union([z.literal('list'), z.literal('board')]),
url: z.string().url(),
parent_id: z.nullable(z.unknown())
});
return scheme.parse(res);
}
</script>

<script lang="ts">
import PrimaryButton from '$lib/components/buttons/PrimaryButton.svelte';
import TextInput from '$lib/components/input/Text.svelte';
import { sendDefaultErrorToast, sendToast } from '$lib/components/layout/toasts';
import Info from '$lib/components/utils/Info.svelte';
import { TODOIST_COLORS } from '$lib/constants/todoistColors';
import { i } from '$lib/i18n/store';
import { Method } from '$lib/utils/api';
import { objectEntries } from '$lib/utils/objects/entries';
import { svocal } from '$lib/utils/store/svocal';
import { createEventDispatcher } from 'svelte';
import { get, readable } from 'svelte/store';
import { z } from 'zod';
let selectedColor: Color = 'green';
let disabled = false;
let name = 'Dlool';
const dispatch = createEventDispatcher<{ finish: string }>();
</script>

<div class="flex max-w-[26rem] flex-col gap-4">
<Info>
Dlool erstellt ein neues Todoist projekt, die Erinerrungen in diesem Projekt werden automatisch
aktualisiert. Bitte ändere die Beschreibung deswegen nicht
</Info>

<h3>Neues Projekt</h3>
<TextInput placeholder={readable('hallo')} bind:value={name} />

<h4>Farbe</h4>
<div class="flex flex-wrap gap-1">
{#each objectEntries(TODOIST_COLORS) as [key, hex] (key)}
<button
on:click={() => {
selectedColor = key;
}}
class="h-7 w-7 rounded-full border-solid border-black bg-[--bg] touch:h-10 touch:w-10"
class:border-2={selectedColor === key}
style:--bg={hex}
/>
{/each}
</div>

<PrimaryButton
on:click={async () => {
disabled = true;
try {
const { id } = await createProject(name, selectedColor);
sendToast({
type: 'success',
content: i('todoist.listCreated'),
timeout: 5_000
});
dispatch('finish', id);
} catch {
disabled = false;
sendDefaultErrorToast();
}
}}
{disabled}
>
Erstellen!
</PrimaryButton>
</div>
12 changes: 10 additions & 2 deletions src/lib/constants/settings.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { get, type Readable } from 'svelte/store';
import { get, writable, type Readable } from 'svelte/store';
import {
DeviceTablet,
Calendar,
Cog,
PaintBrush,
User,
BookOpen,
type IconSource
type IconSource,
Check
} from 'svelte-hero-icons';
import { i } from '$lib/i18n/store';
import { isApple } from '$lib/stores';
Expand Down Expand Up @@ -58,6 +59,13 @@ export const settings: Setting[] = [
label: i('settings.assignments'),
icon: BookOpen
},
{ type: "hr" },
{
type: 'link',
uri: '/todo',
label: writable("Todo-Integration"), // TODO: i18n
icon: Check
},
{
type: 'hr',
show: (browser) => {
Expand Down
22 changes: 22 additions & 0 deletions src/lib/constants/todoistColors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export const TODOIST_COLORS = {
berry_red: '#b8256f',
red: '#db4035',
orange: '#ff9933',
yellow: '#fad000',
olive_green: '#afb83b',
lime_green: '#7ecc49',
green: '#299438',
mint_green: '#6accbc',
teal: '#158fad',
sky_blue: '#14aaf5',
light_blue: '#96c3eb',
blue: '#4073ff',
grape: '#884dff',
violet: '#af38eb',
lavender: '#eb96eb',
magenta: '#e05194',
salmon: '#ff8d85',
charcoal: '#808080',
grey: '#b8b8b8',
taupe: '#ccac93'
};
14 changes: 14 additions & 0 deletions src/lib/locales/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ const de = {
'title.settings': 'Einstellungen',
'title.settings.profile': 'Profil Einstellungen',
'title.settings.general': 'Allgemeine Einstellungen',
'title.settings.todo': 'ToDo Synchronisation',
'title.holiday': 'Ferien',

'home.subtitle': 'Das Hausaufgabenheft der nächsten Generation für Deine ganze Klasse',
Expand Down Expand Up @@ -717,6 +718,19 @@ Deine bisherigen Einstellungen sind leider nicht mit der neuen Version kompatibe
'profile.links.reqs': 'Deine Beitritts-Anfragen',
'profile.links.settings': 'Deine Profil Einstellungen',

'todoist.enable': "Aktiviere todoist Synchronisation",
'todoist.createListButton': "Erstelle eine neue Todoist Liste",
'todoist.createList': "Erstelle eine Todoist Liste",
'todoist.listCreated': "Todist Liste erstellt",
'todoist.tasksCreated': {
counts: {
default: "Aufgaben zum Todoist Projekt hinzugefügt",
1: "Eine Aufgabe zum Todoist Projekt hinzugefügt",
2: "Zwei Aufgabe zum Todoist Projekt hinzugefügt",
25: "25 Aufgabe zum Todoist Projekt hinzugefügt",
}
},

literal: '$literal'
} as const satisfies I18nDict;

Expand Down
7 changes: 7 additions & 0 deletions src/lib/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ const en = {
'title.settings': 'Settings',
'title.settings.profile': 'Profile settings',
'title.settings.general': 'General settings',
'title.settings.todo': 'ToDo Syncing',
'title.holiday': 'Holiday',

'home.subtitle': 'Next generation homework for your entire class',
Expand Down Expand Up @@ -705,6 +706,12 @@ Your current settings sadly won't be compatible withthe new version. But you can
'profile.links.reqs': 'Your Join-Requests',
'profile.links.settings': 'Your Profile Settings',

'todoist.enable': "Activate todoist syncing",
'todoist.createListButton': "Create a new list",
'todoist.createList': "Create a Todoist list",
'todoist.listCreated': "Todist list created",
'todoist.tasksCreated': "Added tasks to the Todoist project",

literal: '$literal'
} as const satisfies I18nDict;

Expand Down
11 changes: 11 additions & 0 deletions src/lib/utils/store/svocal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ const sv = {
'settings.homework.smart-subjects': ['settings.homework.smart-subjects', () => true],
'settings.reduceMotion': ['settings.reduceMotion', () => false],
'settings.tagsInOverview': ['settings.tagsInOverview', () => true],
'settings.todo.todoist.code': ['settings.todo.todoist.code', () => null as string | null],
'settings.todo.todoist.enabled': ['settings.todo.todoist.enabled', () => false],
'settings.todo.todoist.listId': ['settings.todo.todoist.listId', () => null as string | null],
'settings.todo.todoist.projectIds': [
'settings.todo.todoist.projectIds',
() => ({}) as Record<string, string>
],
'settings.todo.todoist.taskIds': [
'settings.todo.todoist.taskIds',
() => ({}) as Record<string, string>
],
'dlool-version': ['dlool-version', () => '2'],
'assignments.order.key': ['assignments.order.key', () => 'due' satisfies OrderKey as OrderKey],
'assignments.order.direction': [
Expand Down
33 changes: 33 additions & 0 deletions src/lib/utils/todoist/createNewSection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Method } from '$lib/utils/api';
import { svocal } from '$lib/utils/store/svocal';
import { internalSubjectRepresentation } from '$lib/utils/subjects/internal';
import { get } from 'svelte/store';
import { z } from 'zod';

const todoistToken = svocal('settings.todo.todoist.code');
const todoistProjectId = svocal('settings.todo.todoist.listId');
const todoistSections = svocal('settings.todo.todoist.projectIds');

const scheme = z.object({ id: z.string() });

export async function createSection(name: string) {
const subject = internalSubjectRepresentation(name);

if (get(todoistSections)[subject]) {
return { id: get(todoistSections)[subject] };
}

const res = await fetch('https://api.todoist.com/rest/v2/sections', {
method: Method.POST,
headers: { Authorization: `Bearer ${get(todoistToken)}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ name, project_id: get(todoistProjectId) })
}).then((r) => r.json());
const parsed = scheme.parse(res);

todoistSections.update((prev) => {
prev[subject] = parsed.id;
return prev;
});

return parsed;
}
Loading

0 comments on commit 8ee92ec

Please sign in to comment.