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

Notion - configurable option to close Raycast after Create Database Page #11429

Merged
merged 14 commits into from
Apr 22, 2024
Merged
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
4 changes: 4 additions & 0 deletions extensions/notion/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Notion Changelog

## [Close Raycast after create new database page] - 2024-04-22

- Add settings to close Raycast after create a new database

## [Support inline Markdown for text properties] - 2024-04-18

- Add a preference that enables the use of inline Markdown on text properties when creating a new database page.
Expand Down
12 changes: 11 additions & 1 deletion extensions/notion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,17 @@
"title": "Create Database Page",
"subtitle": "Notion",
"description": "Create a page in a Notion database.",
"mode": "view"
"mode": "view",
"preferences": [
{
"name": "closeAfterCreate",
"type": "checkbox",
"label": "Close Raycast after creating the page",
"required": false,
"default": false,
"description": "This action will be set as the primary action (⌘ + ⏎)."
}
]
},
{
"name": "search-page",
Expand Down
122 changes: 85 additions & 37 deletions extensions/notion/src/components/forms/CreatePageForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
import { ActionPanel, Clipboard, Icon, Form, showToast, useNavigation, Action, Toast } from "@raycast/api";
import {
ActionPanel,
Clipboard,
Icon,
Form,
showToast,
useNavigation,
Action,
Toast,
getPreferenceValues,
closeMainWindow,
PopToRootType,
Keyboard,
} from "@raycast/api";
import { useForm, FormValidation } from "@raycast/utils";
import { useState } from "react";

Expand All @@ -20,18 +33,29 @@ import { createConvertToFieldFunc, FieldProps } from "./PagePropertyField";
export type CreatePageFormValues = {
database: string | undefined;
[K: string]: Form.Value | undefined;
closeAfterSave?: boolean;
content: string;
};

type CreatePageFormProps = {
mutate?: () => Promise<void>;
launchContext?: CreatePageFormValues;
defaults?: CreatePageFormValues;
defaults?: Partial<CreatePageFormValues>;
};

type CreatePageFormPreferences = {
closeAfterCreate: boolean;
};

type Quicklink = Action.CreateQuicklink.Props["quicklink"];

const createPropertyId = (property: DatabaseProperty) => "property::" + property.type + "::" + property.id;

const NON_EDITABLE_PROPETY_TYPES = ["formula"];
const filterNoEditableProperties = (dp: DatabaseProperty) => !NON_EDITABLE_PROPETY_TYPES.includes(dp.type);

export function CreatePageForm({ mutate, launchContext, defaults }: CreatePageFormProps) {
const preferences = getPreferenceValues<CreatePageFormPreferences>();
const defaultValues = launchContext ?? defaults;
const initialDatabaseId = defaultValues?.database;

Expand All @@ -48,56 +72,60 @@ export function CreatePageForm({ mutate, launchContext, defaults }: CreatePageFo
const initialValues: Partial<CreatePageFormValues> = { database: databaseId ?? undefined };
const validation: Parameters<typeof useForm<CreatePageFormValues>>[0]["validation"] = {};
for (const { id, type } of databaseProperties) {
if (type === "formula") continue;
if (NON_EDITABLE_PROPETY_TYPES.includes(type)) continue;
const key = "property::" + type + "::" + id;
if (type == "title") validation[key] = FormValidation.Required;
let value = defaultValues?.[key];
if (type == "date" && value) value = new Date(value as string);
initialValues[key] = value;
}

const { itemProps, values, handleSubmit } = useForm<CreatePageFormValues>({
const { itemProps, values, handleSubmit, reset, focus } = useForm<CreatePageFormValues>({
initialValues,
validation,
async onSubmit(values) {
const { closeAfterSave, ...pageValues } = values;
try {
await showToast({ style: Toast.Style.Animated, title: "Creating page" });

if (initialDatabaseId) {
values.database = initialDatabaseId;
if (closeAfterSave) {
await closeMainWindow({ popToRootType: PopToRootType.Suspended });
}

await showToast({ style: Toast.Style.Animated, title: "Creating page" });

const page = await createDatabasePage({
...initialValues,
...values,
...pageValues,
});

if (page) {
await showToast({
style: Toast.Style.Success,
title: "Created page",
primaryAction: {
title: "Open Page",
shortcut: { modifiers: ["cmd"], key: "o" },
onAction: () => handleOnOpenPage(page, setRecentPage),
},
secondaryAction: page.url
? {
title: "Copy URL",
shortcut: { modifiers: ["cmd", "shift"], key: "c" },
onAction: () => {
Clipboard.copy(page.url as string);
},
}
: undefined,
});
await showToast({
style: Toast.Style.Success,
title: "Page created",
primaryAction: {
title: "Open Page",
shortcut: { modifiers: ["cmd"], key: "o" },
onAction: () => handleOnOpenPage(page, setRecentPage),
},
secondaryAction: page.url
? {
title: "Copy URL",
shortcut: { modifiers: ["cmd", "shift"], key: "c" },
onAction: () => {
Clipboard.copy(page.url as string);
},
}
: undefined,
});

if (mutate) {
mutate();
useNavigation().pop();
}
if (mutate) {
await mutate();
useNavigation().pop();
} else {
reset(initialValues);
const titleProperty = databaseProperties?.find((dp) => dp.type == "title");
titleProperty && focus(createPropertyId(titleProperty));
Comment on lines +123 to +125
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this code doing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reset form.

  • If you open just CreatePageForm then I will clear all fields
  • if you open CreatePageForm with quick link, then we have some default values, so will use these initial values.

Then I will focus on title field, because title of the page is required field in Notion. This make easier flow -> add one page -> save -> browser will focus first field and you are working on thext page

}
} catch {
} catch (error) {
console.error(error);
await showToast({ style: Toast.Style.Failure, title: "Failed to create page" });
}
},
Expand All @@ -121,8 +149,6 @@ export function CreatePageForm({ mutate, launchContext, defaults }: CreatePageFo
return 0;
}

type Quicklink = Action.CreateQuicklink.Props["quicklink"];

function getQuicklink(): Quicklink {
const url = "raycast://extensions/HenriChabrand/notion/create-database-page";
const launchContext = encodeURIComponent(JSON.stringify(values));
Expand All @@ -141,7 +167,7 @@ export function CreatePageForm({ mutate, launchContext, defaults }: CreatePageFo
}

function itemPropsFor<T extends DatabaseProperty["type"]>(property: DatabaseProperty) {
const id = "property::" + property.type + "::" + property.id;
const id = createPropertyId(property);
return {
...(itemProps[id] as FieldProps<T>),
title: property.name,
Expand All @@ -152,14 +178,35 @@ export function CreatePageForm({ mutate, launchContext, defaults }: CreatePageFo

const convertToField = createConvertToFieldFunc(itemPropsFor, relationPages, users);

const renderSubmitAction = (type: "main" | "second") => {
const shortcut: Keyboard.Shortcut | undefined =
type === "second" ? { modifiers: ["cmd", "shift"], key: "enter" } : undefined;

if ((!preferences.closeAfterCreate && type === "main") || (preferences.closeAfterCreate && type === "second")) {
return <Action.SubmitForm title="Create Page" icon={Icon.Plus} onSubmit={handleSubmit} shortcut={shortcut} />;
} else {
return (
<Action.SubmitForm
title="Create Page and Close"
icon={Icon.Plus}
onSubmit={async (values: CreatePageFormValues) => {
handleSubmit({ ...values, closeAfterSave: true });
}}
shortcut={shortcut}
/>
);
}
};

return (
<Form
isLoading={isLoadingDatabases || isLoadingRelationPages}
navigationTitle={initialDatabaseId ? "Create New Page" : "Create Database Page"}
actions={
<ActionPanel>
<ActionPanel.Section>
<Action.SubmitForm title="Create Page" icon={Icon.Plus} onSubmit={handleSubmit} />
{renderSubmitAction("main")}
{renderSubmitAction("second")}
<Action.CreateQuicklink
title="Create Deeplink to Command as Configured"
quicklink={getQuicklink()}
Expand Down Expand Up @@ -239,6 +286,7 @@ export function CreatePageForm({ mutate, launchContext, defaults }: CreatePageFo
{databaseProperties?.filter(filterProperties).sort(sortProperties).map(convertToField)}
<Form.Separator />
<Form.TextArea
{...itemProps["content"]}
id="content"
title="Page Content"
enableMarkdown
Expand Down
3 changes: 2 additions & 1 deletion extensions/notion/src/utils/notion/database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export async function createDatabasePage(values: Form.Values) {

return pageMapper(page);
} catch (err) {
return handleError(err, "Failed to create page", undefined);
throw new Error("Failed to create page", { cause: err });
}
}

Expand All @@ -205,6 +205,7 @@ export async function deleteDatabase(databaseId: string) {
return handleError(err, "Failed to delete database", undefined);
}
}

export interface Database {
id: string;
last_edited_time: number;
Expand Down