Skip to content

Commit

Permalink
feat(import)!: ✨ update existing pages if assignment has changed (#385)…
Browse files Browse the repository at this point in the history
… (#387)

* Fix import of assignments without due dates (#251) (#384)

* fix(canvas): 🐛 do not store absence of due date as unix epoch (#251)

* fix(import): 🐛 correctly import assignments without due dates to notion (#251)

* refactor(types): 🏷️ refactor type declaration files

change to regular typescript files

* fix(validator): 🐛 fix timezone validation (#252)

* build(notion): 👽 pin notion api version

* feat(notion): ✨ parse notion page properties

* feat(options): ✨ add `notion.importChanges` options

* fix(interface): 💄 properly hide segmented controls

* refactor(types): 🧑‍💻 strongly type `Storage` key methods

* docs(readme): 📝 update `readme`

* docs(readme): 📝 update advanced options heading size

* feat(import)!: ✨ update existing pages if assignment has changed (#385)

BREAKING CHANGE: This requires a new previously unheld capability on the integration. Existing users must reauthorise with Notion.

* build(actions): 💚 fix gulp build
  • Loading branch information
JamesNZL authored Jul 4, 2024
1 parent 6424e49 commit 0f22184
Show file tree
Hide file tree
Showing 10 changed files with 563 additions and 53 deletions.
52 changes: 37 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,18 @@ A fully-configurable [Chromium](https://chrome.google.com/webstore/detail/elbkjc
- [Setup Instructions](#setup-instructions)
- [Safari Instructions](#safari-instructions)
- [Configurable Options](#configurable-options)
- [`Unlock Date`, `Due Date`, and `Date Span`](#what-is-the-difference-between-unlock-date-due-date-and-date-span)
- [`Course Code Overrides`](#course-code-overrides)
- [`Page Emojis`](#page-emojis)
- [Troubleshooting](#troubleshooting)
- [Wrong Timezone](#wrong-timezone)
- [Release Notes](#release-notes)
- [BREAKING CHANGES](#breaking-changes)
- [`v4.3.0`](#v430)
- [`v5`](#v5)
- [Migration Instructions](#migration-instructions)
- [`v4`](#v4)
- [`v4.3.0`](#v430)
- [Migration Instructions](#migration-instructions-1)
- [Building For Local Development](#building-for-local-development)
- [How It Works](#how-it-works)
- [Assignment Fetching](#assignment-fetching)
Expand All @@ -96,17 +102,17 @@ A fully-configurable [Chromium](https://chrome.google.com/webstore/detail/elbkjc

# Features

- Simple, user-friendly interface
- Provided [Notion database template](https://jamesnzl-sandbox.notion.site/c4d73bebd39c4103b96b2edb8be9e0bd?v=9afaf4b4faee4a5a977c00291be06c9e)
- Configurable options, with input validation
- `Category` database property for seamless integration into an existing tasks database
- No duplication of assignments already in the database
- Fine-tuning of database property names & values
- Overriding of Canvas course names
- Emojis for Notion page icons
- Open source
- Private and secure—all data is stored locally!
> My integration does not save or log any user data.
- 💄 Simple, user-friendly interface
- 📄 Provided [Notion database template](https://jamesnzl-sandbox.notion.site/c4d73bebd39c4103b96b2edb8be9e0bd?v=9afaf4b4faee4a5a977c00291be06c9e)
- 🔧 Configurable options, with input validation
- 🗄️ `Category` database property for seamless integration into an existing tasks database
- 🔍 No duplication of assignments already in the database
- 📝 Updating previously imported assignments with new Canvas changes
- ⚙️ Fine-tuning of database property names & values
- ✏️ Overriding of Canvas course names
- 😀 Emojis for Notion page icons
- 🧑‍💻 Open source
- 🔒 Private and secure—all data is stored locally. I do not log or save any user data.

# Screenshots

Expand Down Expand Up @@ -246,9 +252,14 @@ chmod +x /Users/YOUR_USERNAME/Downloads/Notion\ Canvas\ Assignment\ Import/Conte
| `Due Date Property` | The name of a database Due property, used to set the assignment due date |
| `Date Span Property` | The name of a database Date Span property, used to set the date span of the assignment as `unlock date``due date` |
| `Canvas Category` | The value of a database Category property to categorise all Canvas assignments as |
| `Name Changes` | Whether to `Import` (ie update the existing page) or `Ignore` Canvas changes to previously imported assignment names |
| `Points Changes` | Whether to `Import` (ie update the existing page) or `Ignore` Canvas changes to previously imported assignment points |
| `Unlock Date Changes` | Whether to `Import` (ie update the existing page) or `Ignore` Canvas changes to previously imported assignment unlock dates |
| `Due Date Changes` | Whether to `Import` (ie update the existing page) or `Ignore` Canvas changes to previously imported assignment due dates |
| `Date Span Changes` | Whether to `Import` (ie update the existing page) or `Ignore` Canvas changes to previously imported assignment date spans |
| `Page Emojis` | Any Notion page emojis to apply |

**Advanced Options**
### Advanced Options

| Option | Purpose/Remarks |
| ---------------------------- | ---------------------------------------------------------------------- |
Expand Down Expand Up @@ -319,19 +330,30 @@ If your assignments are being imported into Notion in the wrong timezone, please
## BREAKING CHANGES

> [!CAUTION]
> If you are an existing user, you may be affected by these breaking changes following an update.
### `v5`

1. Support has been added to update existing pages with changed assignment details (eg name, due date, points, etc.). This requires an additional integration capability, so existing users must reauthorise with Notion.

##### Migration Instructions

1. To leverage the support for updating existing pages, you must reauthorise with Notion via the Options page.

### `v4`

1. Support for a configurable Notion `'Status'` property has been removed, as Notion has implemented their own built-in `Status` property. To fix assignments being imported without a `'Status'` value, change your database property to be of type `Status`, and assign a `DEFAULT` value.
> [Read more here](https://www.notion.so/help/guides/status-property-gives-clarity-on-tasks).
## `v4.3.0`
#### `v4.3.0`

1. Notion pages now include the assignment's Canvas description ([#59](https://github.com/JamesNZL/notion-assignment-import/issues/59)).
2. Added support for a new 'Points' `number` database property ([#61](https://github.com/JamesNZL/notion-assignment-import/issues/61)) to tag the assignment's points value.
3. Changed the colour of **Advanced Options** headings to ease identification.
4. Renamed `Available Date` to `Unlock Date`.

### Migration Instructions
##### Migration Instructions

1. To leverage the new support for a `Points` property, you must create a new Notion [`number`](https://www.notion.so/help/database-properties) database property, and configure it on the Options Page.

Expand Down
30 changes: 28 additions & 2 deletions src/apis/notion.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { APIErrorCode, Client, isNotionClientError } from '@notionhq/client';
import { CreatePageParameters, CreatePageResponse, GetDatabaseResponse, GetSelfResponse, QueryDatabaseParameters, QueryDatabaseResponse, SearchParameters, SearchResponse } from '@notionhq/client/build/src/api-endpoints';
import {
CreatePageParameters,
CreatePageResponse,
GetDatabaseResponse,
GetSelfResponse,
QueryDatabaseParameters,
QueryDatabaseResponse,
SearchParameters,
SearchResponse,
UpdatePageParameters,
UpdatePageResponse,
} from '@notionhq/client/build/src/api-endpoints';

import { HandlerClientOptions, PaginatedRequest, PaginatedResponse, RichTextItemResponse } from '../types/notion';
import { ArrayElement } from '../types/utils';
Expand All @@ -25,7 +36,10 @@ export class NotionClient extends Client {
private requestCache = new Map<string, unknown>();

private constructor(options: HandlerClientOptions) {
super(options);
super({
...options,
notionVersion: '2022-06-28',
});
this.auth = options.auth;

NotionClient.rateLimits.set(options.auth, {
Expand Down Expand Up @@ -211,6 +225,18 @@ export class NotionClient extends Client {
);
}

public async updatePageProperties(parameters: UpdatePageParameters): Promise<void | UpdatePageResponse> {
return await this.makeRequest(
this.pages.update,
'pages.update',
parameters,
{
cache: false,
force: true,
}
);
}

public async searchShared({ query, sort, filter }: SearchParameters, { cache, force } = { cache: true, force: false }): Promise<void | SearchResponse> {
return await this.makePaginatedRequest(
this.search,
Expand Down
11 changes: 9 additions & 2 deletions src/apis/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,11 @@ export const Storage = <const>{
return await browser.storage.local.remove(databaseIdKey);
},

async getStorageKey(key: string, defaultValue: unknown) {
async getStorageKey<K extends keyof SavedFields>(key: K, defaultValue: SavedFields[K]) {
return (await browser.storage.local.get({ [key]: defaultValue }))[key];
},

async clearStorageKey(key: string) {
async clearStorageKey(key: keyof SavedFields) {
return await browser.storage.local.remove(key);
},

Expand Down Expand Up @@ -163,6 +163,13 @@ export const Storage = <const>{
propertyValues: {
categoryCanvas: savedFields['notion.propertyValues.categoryCanvas'],
},
importChanges: {
name: savedFields['notion.importChanges.name'],
points: savedFields['notion.importChanges.points'],
available: savedFields['notion.importChanges.available'],
due: savedFields['notion.importChanges.due'],
span: savedFields['notion.importChanges.span'],
},
courseEmojis: JSON.parse(savedFields['notion.courseEmojis']),
},
};
Expand Down
10 changes: 10 additions & 0 deletions src/elements/SegmentedControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,21 @@ export class SegmentedControl extends Element {
}

public override show() {
// Show the extra <div> that wraps the segmented-control-wrapper
const segmentedControlGroup = this.element.parentElement?.parentElement;
if (segmentedControlGroup && segmentedControlGroup.classList.contains('segmented-control-group')) {
segmentedControlGroup.classList.remove('hidden');
}
super.show();
this.dispatchInputEvent();
}

public override hide() {
// Hide the extra <div> that wraps the segmented-control-wrapper
const segmentedControlGroup = this.element.parentElement?.parentElement;
if (segmentedControlGroup && segmentedControlGroup.classList.contains('segmented-control-group')) {
segmentedControlGroup.classList.add('hidden');
}
super.hide();
this.dispatchInputEvent();
}
Expand Down
127 changes: 127 additions & 0 deletions src/options/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,21 @@ interface InputElements {
'notion.propertyNames.due': 'notion-property-due';
'notion.propertyNames.span': 'notion-property-span';
'notion.propertyValues.categoryCanvas': 'notion-category-canvas';
'notion.importChanges.name': 'notion-changes-name';
noChangesName: 'no-changes-name-button';
yesChangesName: 'yes-changes-name-button';
'notion.importChanges.points': 'notion-changes-points';
noChangesPoints: 'no-changes-points-button';
yesChangesPoints: 'yes-changes-points-button';
'notion.importChanges.available': 'notion-changes-available';
noChangesAvailable: 'no-changes-available-button';
yesChangesAvailable: 'yes-changes-available-button';
'notion.importChanges.due': 'notion-changes-due';
noChangesDue: 'no-changes-due-button';
yesChangesDue: 'yes-changes-due-button';
'notion.importChanges.span': 'notion-changes-span';
noChangesSpan: 'no-changes-span-button';
yesChangesSpan: 'yes-changes-span-button';
'notion.courseEmojis': 'course-emojis-group';
}

Expand Down Expand Up @@ -287,6 +302,7 @@ export const CONFIGURATION: {
});
},
validateOn: 'change',
dependents: ['notion-changes-name'],
},
category: {
defaultValue: 'Category',
Expand Down Expand Up @@ -328,6 +344,7 @@ export const CONFIGURATION: {
Validator: StringField,
});
},
dependents: ['notion-changes-points'],
},
available: {
defaultValue: 'Reminder',
Expand All @@ -338,6 +355,7 @@ export const CONFIGURATION: {
Validator: StringField,
});
},
dependents: ['notion-changes-available'],
},
due: {
defaultValue: 'Due',
Expand All @@ -348,6 +366,7 @@ export const CONFIGURATION: {
Validator: StringField,
});
},
dependents: ['notion-changes-due'],
},
span: {
defaultValue: 'Date Span',
Expand All @@ -358,6 +377,7 @@ export const CONFIGURATION: {
Validator: StringField,
});
},
dependents: ['notion-changes-span'],
},
},
propertyValues: {
Expand All @@ -372,6 +392,113 @@ export const CONFIGURATION: {
},
},
},
importChanges: {
'name': {
defaultValue: true,
get input() {
delete (<Partial<typeof this>>this).input;
return this.input = SegmentedControl.getInstance<InputElementId, typeof this.defaultValue>({
id: 'notion-changes-name',
segments: [
{
id: 'no-changes-name-button',
value: false,
},
{
id: 'yes-changes-name-button',
value: true,
default: true,
showDependents: true,
},
],
});
},
},
'points': {
defaultValue: true,
get input() {
delete (<Partial<typeof this>>this).input;
return this.input = SegmentedControl.getInstance<InputElementId, typeof this.defaultValue>({
id: 'notion-changes-points',
segments: [
{
id: 'no-changes-points-button',
value: false,
},
{
id: 'yes-changes-points-button',
value: true,
default: true,
showDependents: true,
},
],
});
},
},
'available': {
defaultValue: true,
get input() {
delete (<Partial<typeof this>>this).input;
return this.input = SegmentedControl.getInstance<InputElementId, typeof this.defaultValue>({
id: 'notion-changes-available',
segments: [
{
id: 'no-changes-available-button',
value: false,
},
{
id: 'yes-changes-available-button',
value: true,
default: true,
showDependents: true,
},
],
});
},
},
'due': {
defaultValue: true,
get input() {
delete (<Partial<typeof this>>this).input;
return this.input = SegmentedControl.getInstance<InputElementId, typeof this.defaultValue>({
id: 'notion-changes-due',
segments: [
{
id: 'no-changes-due-button',
value: false,
},
{
id: 'yes-changes-due-button',
value: true,
default: true,
showDependents: true,
},
],
});
},
},
'span': {
defaultValue: true,
get input() {
delete (<Partial<typeof this>>this).input;
return this.input = SegmentedControl.getInstance<InputElementId, typeof this.defaultValue>({
id: 'notion-changes-span',
segments: [
{
id: 'no-changes-span-button',
value: false,
},
{
id: 'yes-changes-span-button',
value: true,
default: true,
showDependents: true,
},
],
});
},
},
},
courseEmojis: {
defaultValue: '{}',
get input() {
Expand Down
Loading

0 comments on commit 0f22184

Please # to comment.