jekyll-notion-cms 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
@@ -0,0 +1,101 @@
1
+ name: Notion Sync Workflow
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ notion_event:
7
+ description: 'Type of Notion event'
8
+ required: true
9
+ type: string
10
+ page_id:
11
+ description: 'Notion page ID'
12
+ required: true
13
+ type: string
14
+ database_id:
15
+ description: 'Notion database ID'
16
+ required: true
17
+ type: string
18
+ updated_at:
19
+ description: 'Last update timestamp'
20
+ required: true
21
+ type: string
22
+
23
+ # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
24
+ permissions:
25
+ contents: read
26
+ pages: write
27
+ id-token: write
28
+
29
+ jobs:
30
+ sync:
31
+ runs-on: ubuntu-latest
32
+ steps:
33
+ - name: Checkout code
34
+ uses: actions/checkout@v4
35
+
36
+ - name: Process Notion update
37
+ env:
38
+ NOTION_EVENT: ${{ inputs.notion_event }}
39
+ PAGE_ID: ${{ inputs.page_id }}
40
+ DATABASE_ID: ${{ inputs.database_id }}
41
+ UPDATED_AT: ${{ inputs.updated_at }}
42
+ run: |
43
+ echo "📝 Notion event: $NOTION_EVENT"
44
+ echo "🆔 Page ID: $PAGE_ID"
45
+ echo "🗄️ Database ID: $DATABASE_ID"
46
+ echo "⏰ Updated at: $UPDATED_AT"
47
+
48
+ # Ajoutez votre logique ici
49
+ # Exemples:
50
+ # - Regénérer un site statique
51
+ # - Déployer du contenu
52
+ # - Synchroniser avec un autre service
53
+ # - Envoyer des notifications
54
+
55
+ - name: Your custom action
56
+ run: |
57
+ echo "🚀 Executing your custom logic..."
58
+ # Votre code personnalisé ici
59
+ build:
60
+ runs-on: ubuntu-latest
61
+ steps:
62
+ - name: Checkout
63
+ uses: actions/checkout@v4
64
+ - name: Setup Ruby
65
+ # https://github.com/ruby/setup-ruby/releases/tag/v1.207.0
66
+ uses: ruby/setup-ruby@4a9ddd6f338a97768b8006bf671dfbad383215f4
67
+ with:
68
+ ruby-version: '3.3.5' # Not needed with a .ruby-version file
69
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
70
+ cache-version: 0 # Increment this number if you need to re-download cached gems
71
+ - name: Setup Pages
72
+ id: pages
73
+ uses: actions/configure-pages@v5
74
+ - name: Build with Jekyll
75
+ # Outputs to the './_site' directory by default
76
+ run: bundle exec jekyll build
77
+ env:
78
+ JEKYLL_ENV: production
79
+ NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
80
+ # Add your Notion database secrets below:
81
+ # NOTION_BLOG_DB: ${{ secrets.NOTION_BLOG_DB }}
82
+ # NOTION_PROJECTS_DB: ${{ secrets.NOTION_PROJECTS_DB }}
83
+ # NOTION_EXPERIENCES_DB: ${{ secrets.NOTION_EXPERIENCES_DB }}
84
+ # NOTION_SKILLS_DB: ${{ secrets.NOTION_SKILLS_DB }}
85
+ # NOTION_SERVICES_DB: ${{ secrets.NOTION_SERVICES_DB }}
86
+ # NOTION_TESTIMONIALS_DB: ${{ secrets.NOTION_TESTIMONIALS_DB }}
87
+ - name: Upload artifact
88
+ # Automatically uploads an artifact from the './_site' directory by default
89
+ uses: actions/upload-pages-artifact@v3
90
+
91
+ # Deployment job
92
+ deploy:
93
+ environment:
94
+ name: github-pages
95
+ url: ${{ steps.deployment.outputs.page_url }}
96
+ runs-on: ubuntu-latest
97
+ needs: build
98
+ steps:
99
+ - name: Deploy to GitHub Pages
100
+ id: deployment
101
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,184 @@
1
+ {
2
+ "name": "Notion to GitHub Actions Sync",
3
+ "nodes": [
4
+ {
5
+ "parameters": {
6
+ "resource": "workflow",
7
+ "owner": {
8
+ "mode": "list",
9
+ "value": "YOUR_GITHUB_USERNAME"
10
+ },
11
+ "repository": {
12
+ "__rl": true,
13
+ "value": "your-repository-name",
14
+ "mode": "list",
15
+ "cachedResultName": "your-repository-name",
16
+ "cachedResultUrl": "https://github.com/YOUR_GITHUB_USERNAME/your-repository-name"
17
+ },
18
+ "workflowId": {
19
+ "__rl": true,
20
+ "value": 123456789,
21
+ "mode": "list",
22
+ "cachedResultName": "Notion Sync Workflow"
23
+ },
24
+ "ref": {
25
+ "__rl": true,
26
+ "value": "main",
27
+ "mode": "list",
28
+ "cachedResultName": "main"
29
+ },
30
+ "inputs": "={\n \"notion_event\": \"event\",\n \"page_id\": \"{{ $json.id }}\",\n \"database_id\": \"skills\",\n \"updated_at\": \"=now\"\n}"
31
+ },
32
+ "id": "github-dispatch-1",
33
+ "name": "Trigger GitHub Action",
34
+ "type": "n8n-nodes-base.github",
35
+ "position": [
36
+ 608,
37
+ 304
38
+ ],
39
+ "typeVersion": 1.1,
40
+ "credentials": {
41
+ "githubApi": {
42
+ "id": "YOUR_GITHUB_CREDENTIAL_ID",
43
+ "name": "GitHub account"
44
+ }
45
+ }
46
+ },
47
+ {
48
+ "parameters": {
49
+ "pollTimes": {
50
+ "item": [
51
+ {
52
+ "mode": "everyHour"
53
+ }
54
+ ]
55
+ },
56
+ "event": "pagedUpdatedInDatabase",
57
+ "databaseId": {
58
+ "__rl": true,
59
+ "value": "YOUR_NOTION_DATABASE_ID",
60
+ "mode": "list",
61
+ "cachedResultName": "Your Database Name",
62
+ "cachedResultUrl": "https://www.notion.so/YOUR_NOTION_DATABASE_ID"
63
+ }
64
+ },
65
+ "type": "n8n-nodes-base.notionTrigger",
66
+ "typeVersion": 1,
67
+ "position": [
68
+ 0,
69
+ 96
70
+ ],
71
+ "id": "notion-trigger-1",
72
+ "name": "Notion Trigger",
73
+ "credentials": {
74
+ "notionApi": {
75
+ "id": "YOUR_NOTION_CREDENTIAL_ID",
76
+ "name": "Notion account"
77
+ }
78
+ }
79
+ },
80
+ {
81
+ "parameters": {
82
+ "pollTimes": {
83
+ "item": [
84
+ {
85
+ "mode": "everyHour"
86
+ }
87
+ ]
88
+ },
89
+ "databaseId": {
90
+ "__rl": true,
91
+ "value": "YOUR_NOTION_DATABASE_ID_2",
92
+ "mode": "list",
93
+ "cachedResultName": "Your Second Database",
94
+ "cachedResultUrl": "https://www.notion.so/YOUR_NOTION_DATABASE_ID_2"
95
+ }
96
+ },
97
+ "type": "n8n-nodes-base.notionTrigger",
98
+ "typeVersion": 1,
99
+ "position": [
100
+ 0,
101
+ 304
102
+ ],
103
+ "id": "notion-trigger-2",
104
+ "name": "Notion Trigger 2",
105
+ "credentials": {
106
+ "notionApi": {
107
+ "id": "YOUR_NOTION_CREDENTIAL_ID",
108
+ "name": "Notion account"
109
+ }
110
+ }
111
+ },
112
+ {
113
+ "parameters": {
114
+ "chatId": "YOUR_TELEGRAM_CHAT_ID",
115
+ "text": "=Deployment via GitHub Action successfully triggered!",
116
+ "additionalFields": {
117
+ "appendAttribution": false
118
+ }
119
+ },
120
+ "id": "telegram-notification-1",
121
+ "name": "Send Notification",
122
+ "type": "n8n-nodes-base.telegram",
123
+ "position": [
124
+ 880,
125
+ 304
126
+ ],
127
+ "typeVersion": 1.2,
128
+ "credentials": {
129
+ "telegramApi": {
130
+ "id": "YOUR_TELEGRAM_CREDENTIAL_ID",
131
+ "name": "Telegram Bot"
132
+ }
133
+ }
134
+ }
135
+ ],
136
+ "pinData": {},
137
+ "connections": {
138
+ "Notion Trigger": {
139
+ "main": [
140
+ [
141
+ {
142
+ "node": "Trigger GitHub Action",
143
+ "type": "main",
144
+ "index": 0
145
+ }
146
+ ]
147
+ ]
148
+ },
149
+ "Notion Trigger 2": {
150
+ "main": [
151
+ [
152
+ {
153
+ "node": "Trigger GitHub Action",
154
+ "type": "main",
155
+ "index": 0
156
+ }
157
+ ]
158
+ ]
159
+ },
160
+ "Trigger GitHub Action": {
161
+ "main": [
162
+ [
163
+ {
164
+ "node": "Send Notification",
165
+ "type": "main",
166
+ "index": 0
167
+ }
168
+ ]
169
+ ]
170
+ }
171
+ },
172
+ "active": false,
173
+ "settings": {
174
+ "executionOrder": "v1",
175
+ "saveDataErrorExecution": "all",
176
+ "saveDataSuccessExecution": "all",
177
+ "saveManualExecutions": true,
178
+ "saveExecutionProgress": true
179
+ },
180
+ "meta": {
181
+ "templateCredsSetupCompleted": false
182
+ },
183
+ "tags": []
184
+ }
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jekyll'
4
+ require_relative 'jekyll_notion_cms/version'
5
+ require_relative 'jekyll_notion_cms/notion_client'
6
+ require_relative 'jekyll_notion_cms/property_extractors'
7
+ require_relative 'jekyll_notion_cms/data_organizers'
8
+ require_relative 'jekyll_notion_cms/generator'
9
+
10
+ module JekyllNotionCMS
11
+ class Error < StandardError; end
12
+ class ConfigurationError < Error; end
13
+ class APIError < Error; end
14
+ end
@@ -0,0 +1,229 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JekyllNotionCMS
4
+ # Module for organizing Notion data into different structures
5
+ module DataOrganizers
6
+ module_function
7
+
8
+ # Organize data based on the specified organizer type
9
+ # @param notion_data [Hash] Raw data from Notion API
10
+ # @param config [Hash] Collection configuration
11
+ # @return [Hash, Array] Organized data
12
+ def organize(notion_data, config)
13
+ organizer = config['organizer'] || 'simple_list'
14
+ properties_config = config['properties'] || []
15
+ sort_by = config['sort_by']
16
+ sort_order = config['sort_order'] || 'asc'
17
+
18
+ case organizer
19
+ when 'simple_list'
20
+ organize_simple_list(notion_data, properties_config, sort_by, sort_order)
21
+ when 'items_by_category'
22
+ organize_items_by_category(notion_data, properties_config)
23
+ when 'grouped_by'
24
+ group_field = config['group_by']
25
+ organize_grouped_by(notion_data, properties_config, group_field, sort_by, sort_order)
26
+ when 'nested'
27
+ parent_field = config['parent_field'] || 'parent_id'
28
+ organize_nested(notion_data, properties_config, parent_field, sort_by, sort_order)
29
+ else
30
+ organize_simple_list(notion_data, properties_config, sort_by, sort_order)
31
+ end
32
+ end
33
+
34
+ # Organize as a simple sorted list
35
+ # @param notion_data [Hash] Raw data from Notion API
36
+ # @param properties_config [Array<Hash>] Property configuration
37
+ # @param sort_by [String] Field to sort by
38
+ # @param sort_order [String] Sort order ('asc' or 'desc')
39
+ # @return [Array<Hash>] Sorted list of items
40
+ def organize_simple_list(notion_data, properties_config, sort_by, sort_order)
41
+ items = notion_data['results'].map do |page|
42
+ item = PropertyExtractors.extract_all(page['properties'], properties_config)
43
+ item['id'] = page['id']
44
+ item['created_time'] = page['created_time']
45
+ item['last_edited_time'] = page['last_edited_time']
46
+ item
47
+ end
48
+
49
+ # Filter out items without title
50
+ items = items.select { |item| item['title'] && !item['title'].to_s.empty? }
51
+
52
+ # Sort if sort_by is specified
53
+ sort_items(items, sort_by, sort_order)
54
+ end
55
+
56
+ # Organize items grouped by category
57
+ # Useful for skills, products, team members, or any items with category grouping
58
+ # @param notion_data [Hash] Raw data from Notion API
59
+ # @param properties_config [Array<Hash>] Property configuration
60
+ # @return [Hash] Items grouped by category
61
+ def organize_items_by_category(notion_data, _properties_config)
62
+ items_by_category = {}
63
+
64
+ notion_data['results'].each do |page|
65
+ properties = page['properties']
66
+
67
+ name = PropertyExtractors.extract(properties, 'Name', 'title')
68
+ next if name.nil? || name.empty?
69
+
70
+ level = PropertyExtractors.extract(properties, 'Level', 'number')
71
+ years = PropertyExtractors.extract(properties, 'Years', 'number')
72
+ featured = PropertyExtractors.extract(properties, 'Featured', 'checkbox')
73
+ order = PropertyExtractors.extract(properties, 'Order', 'number')
74
+ category_name = PropertyExtractors.extract(properties, 'Category', 'rollup') || 'Other'
75
+ category_icon = PropertyExtractors.extract(properties, 'Icon', 'rollup')
76
+ category_color = PropertyExtractors.extract(properties, 'Color', 'rollup')
77
+ category_order = PropertyExtractors.extract(properties, 'Category Order', 'rollup')
78
+
79
+ items_by_category[category_name] ||= {
80
+ 'title' => category_name,
81
+ 'category' => category_name,
82
+ 'subcategory' => nil,
83
+ 'icon' => category_icon,
84
+ 'order' => category_order || 999,
85
+ 'items' => []
86
+ }
87
+
88
+ items_by_category[category_name]['items'] << {
89
+ 'name' => name,
90
+ 'level' => level,
91
+ 'years' => years,
92
+ 'description' => nil,
93
+ 'icon' => nil,
94
+ 'color' => category_color,
95
+ 'featured' => featured,
96
+ 'order' => order || 999,
97
+ 'id' => page['id']
98
+ }
99
+ end
100
+
101
+ # Sort categories by order
102
+ items_by_category = items_by_category.sort_by { |_, data| data['order'].to_i }.to_h
103
+
104
+ # Sort items within each category
105
+ items_by_category.each_value do |data|
106
+ data['items'].sort_by! { |item| item['order'].to_i }
107
+ end
108
+
109
+ items_by_category
110
+ end
111
+
112
+ # Organize items grouped by a field
113
+ # @param notion_data [Hash] Raw data from Notion API
114
+ # @param properties_config [Array<Hash>] Property configuration
115
+ # @param group_field [String] Field to group by
116
+ # @param sort_by [String] Field to sort by within groups
117
+ # @param sort_order [String] Sort order
118
+ # @return [Hash] Items grouped by field
119
+ def organize_grouped_by(notion_data, properties_config, group_field, sort_by, sort_order)
120
+ grouped = {}
121
+
122
+ notion_data['results'].each do |page|
123
+ item = PropertyExtractors.extract_all(page['properties'], properties_config)
124
+ item['id'] = page['id']
125
+
126
+ next if item['title'].nil? || item['title'].to_s.empty?
127
+
128
+ group_key = item[group_field]
129
+ group_key = group_key.first if group_key.is_a?(Array)
130
+ group_key ||= 'Other'
131
+
132
+ grouped[group_key] ||= []
133
+ grouped[group_key] << item
134
+ end
135
+
136
+ # Sort within groups
137
+ grouped.each_value do |items|
138
+ sort_items(items, sort_by, sort_order)
139
+ end
140
+
141
+ grouped
142
+ end
143
+
144
+ # Organize items in a nested tree structure
145
+ # @param notion_data [Hash] Raw data from Notion API
146
+ # @param properties_config [Array<Hash>] Property configuration
147
+ # @param parent_field [String] Field containing parent reference
148
+ # @param sort_by [String] Field to sort by
149
+ # @param sort_order [String] Sort order
150
+ # @return [Array<Hash>] Nested tree of items
151
+ def organize_nested(notion_data, properties_config, parent_field, sort_by, sort_order)
152
+ items = {}
153
+ roots = []
154
+
155
+ # First pass: extract all items
156
+ notion_data['results'].each do |page|
157
+ item = PropertyExtractors.extract_all(page['properties'], properties_config)
158
+ item['id'] = page['id']
159
+ item['children'] = []
160
+ items[page['id']] = item
161
+ end
162
+
163
+ # Second pass: build tree structure
164
+ items.each_value do |item|
165
+ parent_ids = item[parent_field]
166
+ parent_id = parent_ids.is_a?(Array) ? parent_ids.first : parent_ids
167
+
168
+ if parent_id && items[parent_id]
169
+ items[parent_id]['children'] << item
170
+ else
171
+ roots << item
172
+ end
173
+ end
174
+
175
+ # Sort at each level
176
+ sort_nested(roots, sort_by, sort_order)
177
+
178
+ roots
179
+ end
180
+
181
+ # Sort items by field
182
+ # @param items [Array<Hash>] Items to sort
183
+ # @param sort_by [String] Field to sort by
184
+ # @param sort_order [String] Sort order ('asc' or 'desc')
185
+ # @return [Array<Hash>] Sorted items
186
+ def sort_items(items, sort_by, sort_order)
187
+ return items unless sort_by && !sort_by.empty?
188
+
189
+ items.sort_by! do |item|
190
+ value = item[sort_by]
191
+ case value
192
+ when nil then sort_order == 'desc' ? -Float::INFINITY : Float::INFINITY
193
+ when Numeric then value
194
+ when String then value.downcase
195
+ when Hash then value['start'] || '' # For date objects
196
+ else value.to_s.downcase
197
+ end
198
+ end
199
+
200
+ items.reverse! if sort_order == 'desc'
201
+ items
202
+ end
203
+
204
+ # Recursively sort nested items
205
+ # @param items [Array<Hash>] Items to sort
206
+ # @param sort_by [String] Field to sort by
207
+ # @param sort_order [String] Sort order
208
+ def sort_nested(items, sort_by, sort_order)
209
+ sort_items(items, sort_by, sort_order)
210
+
211
+ items.each do |item|
212
+ sort_nested(item['children'], sort_by, sort_order) if item['children']&.any?
213
+ end
214
+ end
215
+
216
+ # Check if data is present (non-empty)
217
+ # @param data [Hash, Array] Data to check
218
+ # @return [Boolean] True if data is present
219
+ def data_present?(data)
220
+ return false if data.nil?
221
+
222
+ if data.is_a?(Hash)
223
+ data.size.positive?
224
+ else
225
+ data.length.positive?
226
+ end
227
+ end
228
+ end
229
+ end