jekyll-fetch-notion 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 42f67d3357969f45294781b7901ad390ce89839a322db0c7138026c3e63a4d75
4
+ data.tar.gz: 43bece5765cdb7419279f4f517cd4546052e0112e4a5bb326eb060691523dd3a
5
+ SHA512:
6
+ metadata.gz: 5aa2e9d66557492d0f88d2543309e881279fce8567fc01d0bee5de2fd429c22edbc87039a4d43ec8fb2a1cd1debebf4a91b428d33adc8b087bd68669bb0ca3af
7
+ data.tar.gz: c692a7f3a600909f4b85cd73a505a525491f31c059e03b6f190f9c45978992e6898ce77fa0fc35fabee5985d2b4a6a8413ec917fed3987c40f3b92d9ba26de8c
data/README.md ADDED
@@ -0,0 +1,198 @@
1
+ # jekyll-fetch-notion
2
+
3
+ The Jekyll plugin introduces the `fetch_notion` command. This command fetches all Notion
4
+ content as specified by the `_config.yml` file and places it directly into your git
5
+ repository. This project is a fork of [jekyll-notion][1], created due to incompatible
6
+ synchronization methods. You can find more details in [this pull request][10].
7
+
8
+ *Note: Data and page fetching are planned for future releases but are not yet available.*
9
+
10
+ The original project was designed with the idea that Notion content should be fetched
11
+ during site building and not tracked by git. This project, however, takes a different
12
+ approach. It arranges your workflow so that your Notion content is git-tracked and
13
+ fetched before site building.
14
+
15
+ I believe this method aligns better with Jekyll's "ideology" and also offers several
16
+ advantages:
17
+
18
+ - Your Notion content is git-tracked and preserved. This ensures a smooth transition from
19
+ Notion back to plain `.md` editing or to another synchronization tool. As Jekyll is a
20
+ static site generator, everything remains static.
21
+ - Separating Notion synchronization from site building simplifies the process and
22
+ provides you more precise control. For example, you again can git-clone and build a
23
+ site as-is without Notion token.
24
+
25
+ However, every implementation has its own pros and cons. Choose the one that best suits
26
+ your needs.
27
+
28
+ For more insights into my experience with `jekyll-fetch-notion`, check out these posts:
29
+
30
+ - [Yet another way to establish Notion + Jekyll synchronization][12]
31
+ - [Notion + Jekyll images synchronization][11]
32
+
33
+ ## Installation
34
+
35
+ Use `gem` to install:
36
+
37
+ ```bash
38
+ gem install 'jekyll-fetch-notion'
39
+ ```
40
+
41
+ Or add it to the `Gemfile`:
42
+
43
+ ```ruby
44
+ gem 'jekyll-fetch-notion'
45
+ ```
46
+
47
+ And then update your jekyll plugins property in `_config.yml`:
48
+
49
+ ```yml
50
+ plugins:
51
+ - jekyll-fetch-notion
52
+ ```
53
+
54
+ ## Usage
55
+
56
+ Before using the gem, create an integration and generate a secret token. For more in-depth
57
+ instructions refer to the Notion "Getting Started" [guide][2].
58
+
59
+ Once you have your secret token, make sure to export it into an environment variable named
60
+ `NOTION_TOKEN`:
61
+
62
+ ```bash
63
+ export NOTION_TOKEN=<secret_...>
64
+ ```
65
+
66
+ Then you can run command to fetch all the Notion content and place it inside your's site
67
+ repository:
68
+
69
+ ```bash
70
+ jekyll fetch_notion
71
+ ```
72
+
73
+ Then you probably need to stage, commit and push all the fetched files to trigger your
74
+ default CI build flow and make your updated site deployed.
75
+
76
+ ### Databases
77
+
78
+ Once the [notion database][3] has been shared, specify the database `id` in the
79
+ `_config.yml` file as follows:
80
+
81
+ ```yml
82
+ notion:
83
+ databases:
84
+ - id: 5cfed4de3bdc4f43ae8ba653a7a2219b
85
+ ```
86
+
87
+ By default, the notion pages in the database will be loaded into the `posts` collection.
88
+
89
+ We can also define multiple databases as follows:
90
+
91
+ ```yml
92
+ collections:
93
+ - recipes
94
+ - films
95
+
96
+ notion:
97
+ databases:
98
+ - id: b0e688e199af4295ae80b67eb52f2e2f
99
+ - id: 2190450d4cb34739a5c8340c4110fe21
100
+ collection: recipes
101
+ - id: e42383cd49754897b967ce453760499f
102
+ collection: films
103
+ ```
104
+
105
+ After running `jekyll fetch_notion` command, the `posts`, `recipes` and `films`
106
+ collections will be fetched with pages from the notion databases.
107
+
108
+ #### Database options
109
+
110
+ Each dabatase support the following options.
111
+
112
+ - `id`: the notion database unique identifier
113
+ - `collection`: the collection each page belongs to (posts by default)
114
+ - `filter`: the database [filter property][4]
115
+ - `sorts`: the database [sorts criteria][5]
116
+
117
+ ```yml
118
+ notion:
119
+ databases:
120
+ - id: e42383cd49754897b967ce453760499f
121
+ collection: posts
122
+ filter: { "property": "Published", "checkbox": { "equals": true } }
123
+ sorts: [{ "timestamp": "created_time", "direction": "ascending" }]
124
+ ```
125
+
126
+ #### Posts date
127
+
128
+ The `created_time` property of a notion page is used to set the date in the post filename.
129
+ This is the date used for the `date` variable of the [predefined variables for posts][6].
130
+
131
+ It's important to note that the `created_time` cannot be modifed. However, if you wish to
132
+ change the date of a post, you can create a new page property named "date" (or "Date").
133
+ This way, the posts collection will use the `date` property for the post date variable
134
+ instead of the `created_time`.
135
+
136
+ ### Notion properties
137
+
138
+ Notion page properties are set for each document in the front matter.
139
+
140
+ Please, refer to the [notion_to_md][8] gem to learn more.
141
+
142
+ ### Page filename
143
+
144
+ There are two kinds of documents in Jekyll: posts and others.
145
+
146
+ When the document is a post, the filename format contains the `created_time` property
147
+ plus the page title as specified in [jekyll docs][9].
148
+
149
+ ```
150
+ YEAR-MONTH-DAY-title.MARKUP
151
+ ```
152
+
153
+ The filename for any other document is the page title.
154
+
155
+ ## Alternatives
156
+
157
+ Actually, there are a lot of alternatives available, but, however, mostly all of them are
158
+ not so mature and hard to extend. This one (like `jekyll-notion`) is tested by time and
159
+ easy to extend because of `notion_to_md` usage.
160
+
161
+ ## License
162
+
163
+ ```
164
+ MIT License
165
+
166
+ Copyright (c) 2024 Enrique Arias, Daniil Sivak
167
+
168
+ Permission is hereby granted, free of charge, to any person obtaining a copy
169
+ of this software and associated documentation files (the "Software"), to deal
170
+ in the Software without restriction, including without limitation the rights
171
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
172
+ copies of the Software, and to permit persons to whom the Software is
173
+ furnished to do so, subject to the following conditions:
174
+
175
+ The above copyright notice and this permission notice shall be included in all
176
+ copies or substantial portions of the Software.
177
+
178
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
179
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
180
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
181
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
182
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
183
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
184
+ SOFTWARE.
185
+ ```
186
+
187
+ [1]: https://github.com/emoriarty/jekyll-notion
188
+ [2]: https://developers.notion.com/docs/getting-started
189
+ [3]: https://developers.notion.com/docs/working-with-databases
190
+ [4]: https://developers.notion.com/reference/post-database-query-filter
191
+ [5]: https://developers.notion.com/reference/post-database-query-sort
192
+ [6]: https://jekyllrb.com/docs/front-matter/#predefined-variables-for-posts
193
+ [7]: https://jekyllrb.com/docs/permalinks/#front-matter
194
+ [8]: https://github.com/emoriarty/notion_to_md/
195
+ [9]: https://jekyllrb.com/docs/posts/#creating-posts
196
+ [10]: https://github.com/emoriarty/jekyll-notion/pull/68
197
+ [11]: https://seroperson.me/2023/11/16/notion-jekyll-images-synchronization/
198
+ [12]: https://seroperson.me/2023/08/26/yet-another-way-to-establish-notion-jekyll-synchronization/
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JekyllFetchNotion
4
+ class AbstractNotionResource
5
+ def initialize(config:)
6
+ @notion = Notion::Client.new
7
+ @config = config
8
+ end
9
+
10
+ def config
11
+ @config || {}
12
+ end
13
+
14
+ def id
15
+ config["id"]
16
+ end
17
+
18
+ def fetch
19
+ raise "Do not use the AbstractNotionResource class. Implement the fetch method in a subclass."
20
+ end
21
+
22
+ def collection_name
23
+ raise "Do not use the AbstractGenerator class. Implement the collection_name method in a subclass."
24
+ end
25
+
26
+ def data_name
27
+ raise "Do not use the AbstractGenerator class. Implement the data_name method in a subclass."
28
+ end
29
+
30
+ protected
31
+
32
+ def id?
33
+ if id.nil? || id.empty?
34
+ Jekyll.logger.warn("Jekyll Notion:",
35
+ "Database or page id is not provided. Cannot read from Notion.")
36
+ return false
37
+ end
38
+ true
39
+ end
40
+
41
+ def build_blocks(block_id)
42
+ NotionToMd::Blocks.build(block_id: block_id) do |nested_id|
43
+ @notion.block_children({block_id: nested_id})
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,45 @@
1
+ module JekyllFetchNotion
2
+ class FetchCommand < Jekyll::Command
3
+ def self.init_with_program(p)
4
+ p.command(:fetch_notion) do |c|
5
+ c.syntax "fetch_notion [options]"
6
+ c.description "Fetches Notion content and places it in collection's directory"
7
+
8
+ c.action do |args, options|
9
+ process(args, options)
10
+ end
11
+ end
12
+ end
13
+
14
+ def self.process(args = [], options = {})
15
+ @config = configuration_from_options(options)
16
+
17
+ return unless notion_token?
18
+
19
+ # requires plugins (and _plugins/ directory) to be able to
20
+ # define custom notion_to_md blocks via monkey-patching
21
+ Jekyll::Site.new(@config)
22
+
23
+ config_databases.each do |db_config|
24
+ db = NotionDatabase.new(config: db_config)
25
+ CollectionGenerator.new(
26
+ notion_resource: db,
27
+ plugin: nil,
28
+ config: @config
29
+ ).generate
30
+ end
31
+ end
32
+
33
+ def self.notion_token?
34
+ if ENV["NOTION_TOKEN"].nil? || ENV["NOTION_TOKEN"].empty?
35
+ Jekyll.logger.warn("Jekyll Notion:", "NOTION_TOKEN not provided. Cannot read from Notion.")
36
+ return false
37
+ end
38
+ true
39
+ end
40
+
41
+ def self.config_databases
42
+ @config["notion"]["databases"] || []
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JekyllFetchNotion
4
+ class DatabaseFactory
5
+ def self.for(notion_resource:, site:, plugin:)
6
+ if notion_resource.data_name.nil?
7
+ CollectionGenerator.new(
8
+ notion_resource: notion_resource,
9
+ site: site,
10
+ plugin: plugin,
11
+ config: site.config
12
+ )
13
+ else
14
+ DataGenerator.new(
15
+ notion_resource: notion_resource,
16
+ site: site,
17
+ plugin: plugin
18
+ )
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,29 @@
1
+ module JekyllFetchNotion
2
+ class FileCreator
3
+ attr_reader :path, :content
4
+ def initialize(path, content)
5
+ @path = path
6
+ @content = content
7
+ end
8
+
9
+ def create!
10
+ ensure_directory_exists
11
+ write_file
12
+ end
13
+
14
+ private
15
+
16
+ def ensure_directory_exists
17
+ dir = File.dirname @path
18
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
19
+ end
20
+
21
+ def write_file
22
+ File.open(@path, "w") do |f|
23
+ f.puts(@content)
24
+ end
25
+
26
+ Jekyll.logger.info "File #{@path} #{"OK".green}"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JekyllFetchNotion
4
+ class AbstractGenerator
5
+ def initialize(notion_resource:, plugin:)
6
+ @notion_resource = notion_resource
7
+ @plugin = plugin
8
+ end
9
+
10
+ def generate
11
+ raise "Do not use the AbstractGenerator class. Implement the generate method in a subclass."
12
+ end
13
+
14
+ def resource_id
15
+ @notion_resource.id
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JekyllFetchNotion
4
+ class CollectionGenerator < AbstractGenerator
5
+ attr_reader :config
6
+
7
+ def initialize(notion_resource:, plugin:, config:)
8
+ super(
9
+ notion_resource: notion_resource,
10
+ plugin: plugin
11
+ )
12
+ @config = config
13
+ end
14
+
15
+ def generate
16
+ @notion_resource.fetch.each do |page|
17
+ make_doc(page)
18
+ log_new_page(page)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def make_doc(page)
25
+ FileCreator.new(make_path(page), make_md(page)).create!
26
+ end
27
+
28
+ def make_path(page)
29
+ "#{@config["source"]}/_#{@notion_resource.collection_name}/#{make_filename(page)}"
30
+ end
31
+
32
+ def make_filename(page)
33
+ if @notion_resource.collection_name == "posts"
34
+ "#{date_for(page).strftime("%Y-%m-%d")}-#{Jekyll::Utils.slugify(page.title, mode: "latin")}.md"
35
+ else
36
+ "#{Jekyll::Utils.slugify(page.title, mode: "latin")}.md"
37
+ end
38
+ end
39
+
40
+ def make_md(page)
41
+ NotionToMd::Converter
42
+ .new(page_id: page.id)
43
+ .convert(frontmatter: true)
44
+ end
45
+
46
+ def log_new_page(page)
47
+ Jekyll.logger.info("Jekyll Notion:", "Page => #{page.title}")
48
+ end
49
+
50
+ def date_for(page)
51
+ # The "date" property overwrites the Jekyll::Document#data["date"] key
52
+ # which is the date used by Jekyll to set the post date.
53
+ Time.parse(page.props["date"]).to_date
54
+ rescue TypeError, NoMethodError
55
+ # Because the "date" property is not required,
56
+ # it fallbacks to the created_time which is always present.
57
+ Time.parse(page.created_time).to_date
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JekyllFetchNotion
4
+ class NotionDatabase < AbstractNotionResource
5
+ # Returns an empty array or a NotionToMd:Page array
6
+ def fetch
7
+ return [] unless id?
8
+
9
+ @fetch ||= @notion.database_query(query)[:results].map do |page|
10
+ NotionToMd::Page.new(
11
+ page: page,
12
+ blocks: build_blocks(page.id)
13
+ )
14
+ end
15
+ end
16
+
17
+ def filter
18
+ config["filter"]
19
+ end
20
+
21
+ def sorts
22
+ config["sorts"]
23
+ end
24
+
25
+ def collection_name
26
+ config["collection"] || "posts"
27
+ end
28
+
29
+ def data_name
30
+ config["data"]
31
+ end
32
+
33
+ private
34
+
35
+ def query
36
+ {
37
+ database_id: id,
38
+ filter: filter,
39
+ sorts: sorts
40
+ }.compact
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JekyllFetchNotion
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jekyll"
4
+ require "notion"
5
+ require "notion_to_md"
6
+ require "logger"
7
+ require "jekyll-fetch-notion/commands/fetch_notion"
8
+
9
+ NotionToMd::Logger.level = Logger::ERROR
10
+
11
+ Notion.configure do |config|
12
+ config.token = ENV["NOTION_TOKEN"]
13
+ end
14
+
15
+ module JekyllFetchNotion
16
+ autoload :DatabaseFactory, "jekyll-fetch-notion/factories/database_factory"
17
+ autoload :AbstractGenerator, "jekyll-fetch-notion/generators/abstract_generator"
18
+ autoload :CollectionGenerator, "jekyll-fetch-notion/generators/collection_generator"
19
+ autoload :AbstractNotionResource, "jekyll-fetch-notion/abstract_notion_resource"
20
+ autoload :NotionDatabase, "jekyll-fetch-notion/notion_database"
21
+ autoload :FileCreator, "jekyll-fetch-notion/file_creator"
22
+ end
metadata ADDED
@@ -0,0 +1,174 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jekyll-fetch-notion
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Enrique Arias
8
+ - Daniil Sivak
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2024-01-24 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: jekyll
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '3.7'
21
+ - - "<"
22
+ - !ruby/object:Gem::Version
23
+ version: '5.0'
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ version: '3.7'
31
+ - - "<"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ - !ruby/object:Gem::Dependency
35
+ name: notion-ruby-client
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ type: :runtime
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ - !ruby/object:Gem::Dependency
49
+ name: notion_to_md
50
+ requirement: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.2'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.2'
62
+ - !ruby/object:Gem::Dependency
63
+ name: bundler
64
+ requirement: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2'
69
+ type: :development
70
+ prerelease: false
71
+ version_requirements: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2'
76
+ - !ruby/object:Gem::Dependency
77
+ name: rspec
78
+ requirement: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ - !ruby/object:Gem::Dependency
91
+ name: vcr
92
+ requirement: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '6.2'
97
+ type: :development
98
+ prerelease: false
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '6.2'
104
+ - !ruby/object:Gem::Dependency
105
+ name: rubocop-jekyll
106
+ requirement: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.12'
111
+ type: :development
112
+ prerelease: false
113
+ version_requirements: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.12'
118
+ - !ruby/object:Gem::Dependency
119
+ name: simplecov
120
+ requirement: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.21'
125
+ type: :development
126
+ prerelease: false
127
+ version_requirements: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.21'
132
+ description:
133
+ email:
134
+ - emoriarty81@gmail.com
135
+ - seroperson@gmail.com
136
+ executables: []
137
+ extensions: []
138
+ extra_rdoc_files:
139
+ - README.md
140
+ files:
141
+ - README.md
142
+ - lib/jekyll-fetch-notion.rb
143
+ - lib/jekyll-fetch-notion/abstract_notion_resource.rb
144
+ - lib/jekyll-fetch-notion/commands/fetch_notion.rb
145
+ - lib/jekyll-fetch-notion/factories/database_factory.rb
146
+ - lib/jekyll-fetch-notion/file_creator.rb
147
+ - lib/jekyll-fetch-notion/generators/abstract_generator.rb
148
+ - lib/jekyll-fetch-notion/generators/collection_generator.rb
149
+ - lib/jekyll-fetch-notion/notion_database.rb
150
+ - lib/jekyll-fetch-notion/version.rb
151
+ homepage: https://github.com/seroperson/jekyll-fetch-notion
152
+ licenses:
153
+ - MIT
154
+ metadata: {}
155
+ post_install_message:
156
+ rdoc_options: []
157
+ require_paths:
158
+ - lib
159
+ required_ruby_version: !ruby/object:Gem::Requirement
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: 2.5.0
164
+ required_rubygems_version: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ version: '0'
169
+ requirements: []
170
+ rubygems_version: 3.4.22
171
+ signing_key:
172
+ specification_version: 4
173
+ summary: A Jekyll plugin to generate pages from Notion
174
+ test_files: []