Skip to content
This repository has been archived by the owner on Nov 10, 2023. It is now read-only.

Feature/page links #17

Merged
merged 4 commits into from
Jun 5, 2020
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
1 change: 1 addition & 0 deletions docs/blogs-using-notoma.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ published: true
layout: default
nav_order: 110
title: Blogs using Notoma
published_at: 2020-05-28 04:14:00
---
<!--
THIS FILE IS GENERATED BY NOTOMA AUTOMATICALLY, DON'T EDIT IT!
Expand Down
38 changes: 38 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
has_children: false
published: true
layout: default
nav_order: 102
title: Configuration
published_at: 2020-06-05 00:47:00
---
<!--
THIS FILE IS GENERATED BY NOTOMA AUTOMATICALLY, DON'T EDIT IT!
Notion link for this article: https://www.notion.so/a5fc5c39d10f4d038425c0ef8bfaa760
-->

Notoma loads configuration via environment variables, or an `.env` file. The easiest way to configure Notoma is to take a look at the [`.env.sample`](https://github.com/xnutsive/notoma/blob/master/.env.sample) [file in the repository,](https://github.com/xnutsive/notoma/blob/master/.env.sample) and tweak it to your liking.

```bash
NOTOMA_NOTION_TOKEN_V2 = your_secret_notion_cookie_token_v2
NOTOMA_NOTION_BLOG_URL = https://www.notion.so/respawn/7b46cea379bd4d45b68860c2fa35a2d4?v=b4609f6aae0d4fc1adc65a73f72d0e21

# If your blog database doesn't have the layout property, Notoma will use the default layout.
NOTOMA_DEFAULT_LAYOUT = default

# Permalink patttern to use for @links in your Notion pages.
NOTOMA_PERMALINK_PATTERN = https://$baseurl/$title/

# Base URL to use when assembling @links
NOTOMA_BASE_URL = xnutsive.notoma.io
```

#### Supported permalink substitutions

*Note: the permalink format* __*must match*__ *whatever format you're using in your blog engine. It's on you to set and make sure the format matches. If they don't match, the @links won't work, and you'll likely see some 404 pages.*

When Notoma processes @links, it'll try to build a link by taking `NOTOMA_PERMALINK_PATTERN` from the config, and substituting it's parts:
- Notoma will use all the keys from the front matter that are available. That means you can use any Notion property you add in your URLS, as long as it's a string.
- Notoma will automatically slugify title for you the same way it does for file names.
- Notoma will automatically add `year`, `month` and `day` to the substitutions mapping, although they're numeric and the date formatting in permalinks is not currently supported.

4 changes: 3 additions & 1 deletion docs/supported-markdown-tags.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ published: true
layout: default
nav_order: 101
title: Supported Markdown Tags
published_at: 2020-06-05 00:22:00
---
<!--
THIS FILE IS GENERATED BY NOTOMA AUTOMATICALLY, DON'T EDIT IT!
Expand All @@ -16,8 +17,9 @@ This page shows off what tags are supported — it's generated with Notoma itsel

__Some bold text__ and *some italic text,* also some ~~strikethrough~~.

Link to another page in the Notion blog (@-style link): ‣.
Link to another page in the Notion blog (@-style link): ‣

_For these links to work, you need to specify the permalink pattern for your blog in the _
## Subhedings and typography

### Subsubheading for list items
Expand Down
12 changes: 8 additions & 4 deletions notoma/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ def convert(
if verbose:
click.echo(f"Processing articles from Notion: {blog.parent.title}")

__convert_pages(published_pages(blog), dest, verbose)
__convert_pages(published_pages(blog), dest, config, verbose)

if drafts:
__convert_pages(draft_pages(blog), Path(drafts).absolute(), verbose)
__convert_pages(draft_pages(blog), Path(drafts).absolute(), config, verbose)
draft_pages(blog)


Expand All @@ -96,15 +96,19 @@ def new() -> None:
raise NotImplementedError("Creating a new blog is not implemented yet.")


def __convert_pages(pages: list, dest_dir: Path, verbose: bool = False) -> None:
def __convert_pages(
pages: list, dest_dir: Path, config: Config, verbose: bool = False
) -> None:
"Convert a bunch of pages with a nice progress bar."

if verbose:
click.echo(f"{len(pages)} pages to process.")

with click.progressbar(pages) as bar:
for page in bar:
page_path(page, dest_dir=dest_dir).write_text(page_to_markdown(page))
page_path(page, dest_dir=dest_dir).write_text(
page_to_markdown(page, config=config)
)

if verbose:
click.echo(f"Processed {len(pages)} pages.")
Expand Down
9 changes: 5 additions & 4 deletions notoma/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
token_v2="NOTOMA_NOTION_TOKEN_V2",
blog_url="NOTOMA_NOTION_BLOG_URL",
default_layout="NOTOMA_DEFAULT_LAYOUT",
permalink_pattern="NOTOMA_PERMALINK_PATTERN",
baseurl="NOTOMA_BASE_URL",
)


Expand Down Expand Up @@ -43,12 +45,11 @@ def token_v2(self) -> str:
def blog_url(self) -> str:
return self.__config["blog_url"]

@property
def default_layout(self) -> str:
return self.__config["default_layout"]

def __getitem__(self, key):
return self.__config[key]

def __iter__(self):
return iter(self.__config)

def __repr__(self):
return "\n".join(f"{k}: {v}" for k, v in self.__config.items())
38 changes: 6 additions & 32 deletions notoma/core.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import yaml
from pathlib import Path
from typing import Union, List

Expand All @@ -7,7 +6,8 @@
from notion.collection import Collection, NotionDate

from .config import Config
from .templates import template
from .templates import load_template
from .page import page_path, front_matter


def notion_client(token_v2: str) -> NotionClient:
Expand Down Expand Up @@ -43,34 +43,8 @@ def draft_pages(blog: Collection) -> List[PageBlock]:
]


def page_to_markdown(page: PageBlock) -> str:
"Translates a Notion Page (`PageBlock`) into a Markdown string."
return template("post", debug=True).render(
page=page, front_matter=front_matter(page)
def page_to_markdown(page: PageBlock, config: Config) -> str:
"Translates a Notion Page (`PageBlock`) into a Markdown string and returns it."
return load_template("post", debug=True, config=config).render(
page=page, front_matter=front_matter(page, config)
)


def page_path(page: PageBlock, dest_dir: Path = Path(".")) -> Path:
"Build a .md file path in `dest_dir` based on a Notion page metadata."
fname = "-".join(page.title.lower().replace(".", "").split(" ")) + ".md"
return dest_dir / fname


def front_matter(page: PageBlock, config: Config = Config()) -> str:
"Builds and returns a page front matter in a yaml-like format."
all_props = page.get_all_properties()
if "layout" not in all_props:
all_props["layout"] = config.default_layout
renderables = {k: v for k, v in all_props.items() if v != ""}
return __sanitize_front_matter(renderables)


def __sanitize_front_matter(items: dict) -> dict:
"Sanitizes and returns front matter items."
for k, v in items.items():
if type(v) not in [str, list]:
if isinstance(v, NotionDate):
items[k] = v.start
if isinstance(v, bool):
items[k] = str(v).lower()
return items
86 changes: 86 additions & 0 deletions notoma/page.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from pathlib import Path
from string import Template
from datetime import datetime

import re

from notion.collection import NotionDate, CollectionRowBlock
from notion.block import PageBlock

from .config import Config

"""
Functions that operate on Notion's `PageBlock`.
"""


def page_path(page: PageBlock, dest_dir: Path = Path(".")) -> Path:
"Build a .md file path in `dest_dir` based on a Notion page metadata."
fname = __title_to_slug(page.title) + ".md"
return dest_dir / fname


def __title_to_slug(title: str) -> str:
return re.sub(r"[^\w\-]", "", re.sub(r"\s+", "-", title)).lower()


def front_matter(page: CollectionRowBlock, config: Config) -> str:
"Builds and returns a page front matter in a yaml-like format."
# Start with all the properties from the page, including formulas and rollups.
all_props = page.get_all_properties()

# Add the default layout from the config
# if there's no layout property on the page itself.
if "layout" not in all_props:
all_props["layout"] = config.default_layout

# Add default published_at if there's no specific property for it.
if "published_at" not in all_props:
last_edited_time = datetime.utcfromtimestamp(
int(page.get("last_edited_time")) / 1000
)
all_props["published_at"] = last_edited_time

# Select only properties that are not empty
renderables = {k: v for k, v in all_props.items() if v != ""}
return __sanitize_front_matter(renderables)


def __sanitize_front_matter(items: dict) -> dict:
"Sanitizes and returns front matter items as a dictionary."
for k, v in items.items():
if type(v) not in [str, list]:
if isinstance(v, NotionDate):
items[k] = v.start
if isinstance(v, bool):
items[k] = str(v).lower()
return items


def page_url_substitutions(page: PageBlock, config: Config) -> dict:
"Builds and returns a `dict` of substitutions to build URL to this page in the Blog."
# Start with a dict from `front_m subs = front_matter(page, config)
subs = front_matter(page, config)

# Grab the config baseurl
subs["baseurl"] = config["baseurl"]

# FIXME Clean this up into a mapping of callables?
#
if "title" in subs:
subs["title"] = __title_to_slug(subs["title"])

if "categories" in subs:
subs["categories"] = "/".join(subs["categories"])
else:
subs["categories"] = ""

if "published_at" in subs:
subs["year"], subs["month"], subs["day"], *_ = subs["published_at"].timetuple()

return subs


def build_page_url(page: PageBlock, pattern: Template, config: Config) -> str:
"Build the URL string for a given URL pattern and returns it as `str`."
return pattern.substitute(page_url_substitutions(page, config))
Loading