pressroom 0.1.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.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +22 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +172 -0
  5. data/Rakefile +8 -0
  6. data/app/assets/javascripts/pressroom/application.js +18 -0
  7. data/app/assets/javascripts/pressroom/controllers/editor_controller.js +146 -0
  8. data/app/assets/javascripts/pressroom/vendor/code.js +181 -0
  9. data/app/assets/javascripts/pressroom/vendor/delimiter.js +98 -0
  10. data/app/assets/javascripts/pressroom/vendor/editorjs.js +11217 -0
  11. data/app/assets/javascripts/pressroom/vendor/header.js +321 -0
  12. data/app/assets/javascripts/pressroom/vendor/image.js +1103 -0
  13. data/app/assets/javascripts/pressroom/vendor/inline-code.js +99 -0
  14. data/app/assets/javascripts/pressroom/vendor/list.js +2091 -0
  15. data/app/assets/javascripts/pressroom/vendor/marker.js +95 -0
  16. data/app/assets/javascripts/pressroom/vendor/quote.js +990 -0
  17. data/app/assets/javascripts/pressroom/vendor/stimulus.js +2563 -0
  18. data/app/assets/javascripts/pressroom/vendor/turbo.js +7256 -0
  19. data/app/assets/stylesheets/pressroom/admin.css +216 -0
  20. data/app/assets/stylesheets/pressroom/content.css +18 -0
  21. data/app/controllers/pressroom/application_controller.rb +41 -0
  22. data/app/controllers/pressroom/categories_controller.rb +50 -0
  23. data/app/controllers/pressroom/posts_controller.rb +98 -0
  24. data/app/controllers/pressroom/tags_controller.rb +39 -0
  25. data/app/controllers/pressroom/uploads_controller.rb +52 -0
  26. data/app/helpers/pressroom/application_helper.rb +4 -0
  27. data/app/helpers/pressroom/blocks_helper.rb +13 -0
  28. data/app/jobs/pressroom/application_job.rb +4 -0
  29. data/app/mailers/pressroom/application_mailer.rb +6 -0
  30. data/app/models/concerns/pressroom/authorable.rb +23 -0
  31. data/app/models/concerns/pressroom/sluggable.rb +66 -0
  32. data/app/models/pressroom/application_record.rb +7 -0
  33. data/app/models/pressroom/category.rb +54 -0
  34. data/app/models/pressroom/post.rb +90 -0
  35. data/app/models/pressroom/tag.rb +30 -0
  36. data/app/models/pressroom/tagging.rb +12 -0
  37. data/app/views/layouts/pressroom/admin.html.erb +34 -0
  38. data/app/views/layouts/pressroom/application.html.erb +17 -0
  39. data/app/views/pressroom/blocks/_code.html.erb +2 -0
  40. data/app/views/pressroom/blocks/_delimiter.html.erb +1 -0
  41. data/app/views/pressroom/blocks/_header.html.erb +5 -0
  42. data/app/views/pressroom/blocks/_image.html.erb +18 -0
  43. data/app/views/pressroom/blocks/_list.html.erb +6 -0
  44. data/app/views/pressroom/blocks/_list_items.html.erb +13 -0
  45. data/app/views/pressroom/blocks/_paragraph.html.erb +1 -0
  46. data/app/views/pressroom/blocks/_quote.html.erb +6 -0
  47. data/app/views/pressroom/categories/_form.html.erb +31 -0
  48. data/app/views/pressroom/categories/edit.html.erb +3 -0
  49. data/app/views/pressroom/categories/index.html.erb +34 -0
  50. data/app/views/pressroom/categories/new.html.erb +3 -0
  51. data/app/views/pressroom/posts/_form.html.erb +49 -0
  52. data/app/views/pressroom/posts/_post_row.html.erb +17 -0
  53. data/app/views/pressroom/posts/edit.html.erb +34 -0
  54. data/app/views/pressroom/posts/index.html.erb +38 -0
  55. data/app/views/pressroom/posts/new.html.erb +3 -0
  56. data/app/views/pressroom/tags/edit.html.erb +24 -0
  57. data/app/views/pressroom/tags/index.html.erb +27 -0
  58. data/config/editorjs_manifest.yml +53 -0
  59. data/config/importmap.rb +18 -0
  60. data/config/locales/pressroom.en.yml +16 -0
  61. data/config/locales/transliteration.ru.yml +70 -0
  62. data/config/locales/transliteration.uk.yml +70 -0
  63. data/config/routes.rb +17 -0
  64. data/db/migrate/20260821000001_create_pressroom_posts.rb +42 -0
  65. data/db/migrate/20260821000002_create_pressroom_taxonomy.rb +33 -0
  66. data/lib/pressroom/blocks/definition.rb +103 -0
  67. data/lib/pressroom/blocks/registry.rb +56 -0
  68. data/lib/pressroom/blocks/renderer.rb +97 -0
  69. data/lib/pressroom/blocks/sanitizer.rb +36 -0
  70. data/lib/pressroom/blocks/schema.rb +247 -0
  71. data/lib/pressroom/blocks/text_extractor.rb +46 -0
  72. data/lib/pressroom/built_in_blocks.rb +97 -0
  73. data/lib/pressroom/configuration.rb +64 -0
  74. data/lib/pressroom/engine.rb +33 -0
  75. data/lib/pressroom/errors.rb +11 -0
  76. data/lib/pressroom/feed.rb +69 -0
  77. data/lib/pressroom/transliteration.rb +213 -0
  78. data/lib/pressroom/version.rb +3 -0
  79. data/lib/pressroom.rb +70 -0
  80. data/lib/tasks/pressroom_tasks.rake +4 -0
  81. data/lib/tasks/pressroom_vendor.rake +31 -0
  82. metadata +171 -0
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pressroom
4
+ # Transliteration rules the gem carries itself.
5
+ #
6
+ # These cannot live in locale files alone. I18n leaves a locale out of
7
+ # available_locales when its data holds nothing but an :i18n key, which is
8
+ # exactly the shape of a transliteration-rules file, and I18n.transliterate
9
+ # then rejects that locale. Padding the files with a filler key would fix
10
+ # the lookup but would also push :ru and :uk into the host application's
11
+ # available_locales, which is not this gem's business.
12
+ #
13
+ # So: when the locale really is one of the host's, its own rules win --
14
+ # including the YAML this gem ships, which the host picks up automatically
15
+ # in that case. Otherwise these tables are used directly.
16
+ module Transliteration
17
+ RULES = {
18
+ ru: {
19
+ "а" => "a",
20
+ "б" => "b",
21
+ "в" => "v",
22
+ "г" => "g",
23
+ "д" => "d",
24
+ "е" => "e",
25
+ "ё" => "e",
26
+ "ж" => "zh",
27
+ "з" => "z",
28
+ "и" => "i",
29
+ "й" => "i",
30
+ "к" => "k",
31
+ "л" => "l",
32
+ "м" => "m",
33
+ "н" => "n",
34
+ "о" => "o",
35
+ "п" => "p",
36
+ "р" => "r",
37
+ "с" => "s",
38
+ "т" => "t",
39
+ "у" => "u",
40
+ "ф" => "f",
41
+ "х" => "h",
42
+ "ц" => "ts",
43
+ "ч" => "ch",
44
+ "ш" => "sh",
45
+ "щ" => "sch",
46
+ "ъ" => "",
47
+ "ы" => "y",
48
+ "ь" => "",
49
+ "э" => "e",
50
+ "ю" => "yu",
51
+ "я" => "ya",
52
+ "А" => "A",
53
+ "Б" => "B",
54
+ "В" => "V",
55
+ "Г" => "G",
56
+ "Д" => "D",
57
+ "Е" => "E",
58
+ "Ё" => "E",
59
+ "Ж" => "Zh",
60
+ "З" => "Z",
61
+ "И" => "I",
62
+ "Й" => "I",
63
+ "К" => "K",
64
+ "Л" => "L",
65
+ "М" => "M",
66
+ "Н" => "N",
67
+ "О" => "O",
68
+ "П" => "P",
69
+ "Р" => "R",
70
+ "С" => "S",
71
+ "Т" => "T",
72
+ "У" => "U",
73
+ "Ф" => "F",
74
+ "Х" => "H",
75
+ "Ц" => "Ts",
76
+ "Ч" => "Ch",
77
+ "Ш" => "Sh",
78
+ "Щ" => "Sch",
79
+ "Ъ" => "",
80
+ "Ы" => "Y",
81
+ "Ь" => "",
82
+ "Э" => "E",
83
+ "Ю" => "Yu",
84
+ "Я" => "Ya",
85
+ }.freeze,
86
+ uk: {
87
+ "а" => "a",
88
+ "б" => "b",
89
+ "в" => "v",
90
+ "г" => "h",
91
+ "ґ" => "g",
92
+ "д" => "d",
93
+ "е" => "e",
94
+ "є" => "ie",
95
+ "ж" => "zh",
96
+ "з" => "z",
97
+ "и" => "y",
98
+ "і" => "i",
99
+ "ї" => "i",
100
+ "й" => "i",
101
+ "к" => "k",
102
+ "л" => "l",
103
+ "м" => "m",
104
+ "н" => "n",
105
+ "о" => "o",
106
+ "п" => "p",
107
+ "р" => "r",
108
+ "с" => "s",
109
+ "т" => "t",
110
+ "у" => "u",
111
+ "ф" => "f",
112
+ "х" => "kh",
113
+ "ц" => "ts",
114
+ "ч" => "ch",
115
+ "ш" => "sh",
116
+ "щ" => "shch",
117
+ "ь" => "",
118
+ "ю" => "iu",
119
+ "я" => "ia",
120
+ "А" => "A",
121
+ "Б" => "B",
122
+ "В" => "V",
123
+ "Г" => "H",
124
+ "Ґ" => "G",
125
+ "Д" => "D",
126
+ "Е" => "E",
127
+ "Є" => "Ie",
128
+ "Ж" => "Zh",
129
+ "З" => "Z",
130
+ "И" => "Y",
131
+ "І" => "I",
132
+ "Ї" => "I",
133
+ "Й" => "I",
134
+ "К" => "K",
135
+ "Л" => "L",
136
+ "М" => "M",
137
+ "Н" => "N",
138
+ "О" => "O",
139
+ "П" => "P",
140
+ "Р" => "R",
141
+ "С" => "S",
142
+ "Т" => "T",
143
+ "У" => "U",
144
+ "Ф" => "F",
145
+ "Х" => "Kh",
146
+ "Ц" => "Ts",
147
+ "Ч" => "Ch",
148
+ "Ш" => "Sh",
149
+ "Щ" => "Shch",
150
+ "Ь" => "",
151
+ "Ю" => "Iu",
152
+ "Я" => "Ia",
153
+ }.freeze
154
+ }.freeze
155
+
156
+ CYRILLIC = /\p{Cyrillic}/
157
+ UKRAINIAN_ONLY = /[іїєґІЇЄҐ]/
158
+
159
+ class << self
160
+ def call(string, locale)
161
+ string = string.to_s
162
+ key = locale.to_s.to_sym
163
+ result = transliterate(string, key)
164
+
165
+ cyrillic = string.scan(CYRILLIC).join
166
+ return result if cyrillic.empty?
167
+
168
+ # Judge the Cyrillic characters on their own. A title like
169
+ # "Как устроен Rails" keeps its Latin word under any locale, so
170
+ # looking at the whole result would wrongly suggest the locale
171
+ # coped.
172
+ return result if usable?(transliterate(cyrillic, key))
173
+
174
+ # The configured locale has no rules for this script -- an
175
+ # English-locale application with a Russian article, which is the
176
+ # common case rather than the exotic one. Falling back to the
177
+ # script's own rules beats an empty slug that numbers every article
178
+ # post-2, post-3, post-4.
179
+ transliterate(string, detect_script(string))
180
+ end
181
+
182
+ private
183
+
184
+ def transliterate(string, key)
185
+ return I18n.transliterate(string, locale: key, replacement: "") if host_locale?(key)
186
+
187
+ rule = RULES[key]
188
+ return transliterator_for(key, rule).transliterate(string, "") if rule
189
+
190
+ I18n.transliterate(string, locale: I18n.default_locale, replacement: "")
191
+ end
192
+
193
+ def usable?(result)
194
+ result.match?(/[a-zA-Z0-9]/)
195
+ end
196
+
197
+ def detect_script(string)
198
+ return nil unless string.match?(CYRILLIC)
199
+
200
+ string.match?(UKRAINIAN_ONLY) ? :uk : :ru
201
+ end
202
+
203
+ def host_locale?(key)
204
+ I18n.available_locales.include?(key)
205
+ end
206
+
207
+ def transliterator_for(key, rule)
208
+ @transliterators ||= {}
209
+ @transliterators[key] ||= I18n::Backend::Transliterator.get(rule)
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,3 @@
1
+ module Pressroom
2
+ VERSION = "0.1.0"
3
+ end
data/lib/pressroom.rb ADDED
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pressroom/version"
4
+ require "pressroom/errors"
5
+ require "pressroom/transliteration"
6
+ require "pressroom/configuration"
7
+ require "pressroom/blocks/schema"
8
+ require "pressroom/blocks/definition"
9
+ require "pressroom/blocks/registry"
10
+ require "pressroom/blocks/sanitizer"
11
+ require "pressroom/blocks/renderer"
12
+ require "pressroom/blocks/text_extractor"
13
+ require "pressroom/built_in_blocks"
14
+ require "pressroom/feed"
15
+ require "pressroom/engine"
16
+
17
+ module Pressroom
18
+ class << self
19
+ def config
20
+ @config ||= Configuration.new
21
+ end
22
+
23
+ def configure
24
+ yield config
25
+ config
26
+ end
27
+
28
+ # Test helper: restores pristine defaults between examples.
29
+ def reset_configuration!
30
+ @config = Configuration.new
31
+ end
32
+
33
+ def registry
34
+ @registry ||= Blocks::Registry.new
35
+ end
36
+
37
+ # Public API: registers a block type.
38
+ #
39
+ # Pressroom.register_block :callout do |b|
40
+ # b.tool "Callout"
41
+ # b.schema { string :title, required: true }
42
+ # b.partial "pressroom/blocks/callout"
43
+ # end
44
+ def register_block(type, &block)
45
+ registry.register(type, &block)
46
+ end
47
+
48
+ # Built per call rather than memoized: configuration may change at
49
+ # runtime in tests, and construction is cheap.
50
+ def renderer
51
+ Blocks::Renderer.new(
52
+ registry: registry,
53
+ sanitizer: Blocks::Sanitizer.new(tags: config.sanitizer_tags,
54
+ attributes: config.sanitizer_attributes),
55
+ strategy: config.resolved_unknown_block_strategy,
56
+ logger: defined?(Rails) ? Rails.logger : nil
57
+ )
58
+ end
59
+
60
+ def text_extractor
61
+ Blocks::TextExtractor.new(registry: registry)
62
+ end
63
+
64
+ # ActiveStorage is optional. When it is absent the image block still
65
+ # renders a picture from a URL; there is simply nowhere to upload to.
66
+ def active_storage?
67
+ defined?(ActiveStorage::Blob).present?
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :pressroom do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "open-uri"
5
+ require "yaml"
6
+
7
+ namespace :pressroom do
8
+ namespace :vendor do
9
+ desc "Download the pinned Editor.js builds into the gem"
10
+ task :editorjs do
11
+ root = File.expand_path("../..", __dir__)
12
+ manifest = YAML.load_file(File.join(root, "config/editorjs_manifest.yml"))
13
+ target = File.join(root, "app/assets/javascripts/pressroom/vendor")
14
+
15
+ FileUtils.mkdir_p(target)
16
+
17
+ manifest.each do |name, entry|
18
+ url = "https://cdn.jsdelivr.net/npm/#{name}@#{entry['version']}/#{entry['remote']}"
19
+ destination = File.join(target, entry["local"])
20
+
21
+ print "#{name}@#{entry['version']} -> #{entry['local']} ... "
22
+ URI.parse(url).open do |remote|
23
+ File.binwrite(destination, remote.read)
24
+ end
25
+ puts "#{File.size(destination)} bytes"
26
+ end
27
+
28
+ puts "Vendored #{manifest.size} packages into #{target}"
29
+ end
30
+ end
31
+ end
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pressroom
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Alex Andreiev
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2026-08-21 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.2'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '8.2'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '7.2'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '8.2'
32
+ - !ruby/object:Gem::Dependency
33
+ name: rails-html-sanitizer
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '1.6'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: '1.6'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rss
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '0.3'
53
+ type: :runtime
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '0.3'
60
+ description: A mountable Rails engine that adds block-editor articles, categories,
61
+ tags and feeds to an existing application.
62
+ executables: []
63
+ extensions: []
64
+ extra_rdoc_files: []
65
+ files:
66
+ - CHANGELOG.md
67
+ - MIT-LICENSE
68
+ - README.md
69
+ - Rakefile
70
+ - app/assets/javascripts/pressroom/application.js
71
+ - app/assets/javascripts/pressroom/controllers/editor_controller.js
72
+ - app/assets/javascripts/pressroom/vendor/code.js
73
+ - app/assets/javascripts/pressroom/vendor/delimiter.js
74
+ - app/assets/javascripts/pressroom/vendor/editorjs.js
75
+ - app/assets/javascripts/pressroom/vendor/header.js
76
+ - app/assets/javascripts/pressroom/vendor/image.js
77
+ - app/assets/javascripts/pressroom/vendor/inline-code.js
78
+ - app/assets/javascripts/pressroom/vendor/list.js
79
+ - app/assets/javascripts/pressroom/vendor/marker.js
80
+ - app/assets/javascripts/pressroom/vendor/quote.js
81
+ - app/assets/javascripts/pressroom/vendor/stimulus.js
82
+ - app/assets/javascripts/pressroom/vendor/turbo.js
83
+ - app/assets/stylesheets/pressroom/admin.css
84
+ - app/assets/stylesheets/pressroom/content.css
85
+ - app/controllers/pressroom/application_controller.rb
86
+ - app/controllers/pressroom/categories_controller.rb
87
+ - app/controllers/pressroom/posts_controller.rb
88
+ - app/controllers/pressroom/tags_controller.rb
89
+ - app/controllers/pressroom/uploads_controller.rb
90
+ - app/helpers/pressroom/application_helper.rb
91
+ - app/helpers/pressroom/blocks_helper.rb
92
+ - app/jobs/pressroom/application_job.rb
93
+ - app/mailers/pressroom/application_mailer.rb
94
+ - app/models/concerns/pressroom/authorable.rb
95
+ - app/models/concerns/pressroom/sluggable.rb
96
+ - app/models/pressroom/application_record.rb
97
+ - app/models/pressroom/category.rb
98
+ - app/models/pressroom/post.rb
99
+ - app/models/pressroom/tag.rb
100
+ - app/models/pressroom/tagging.rb
101
+ - app/views/layouts/pressroom/admin.html.erb
102
+ - app/views/layouts/pressroom/application.html.erb
103
+ - app/views/pressroom/blocks/_code.html.erb
104
+ - app/views/pressroom/blocks/_delimiter.html.erb
105
+ - app/views/pressroom/blocks/_header.html.erb
106
+ - app/views/pressroom/blocks/_image.html.erb
107
+ - app/views/pressroom/blocks/_list.html.erb
108
+ - app/views/pressroom/blocks/_list_items.html.erb
109
+ - app/views/pressroom/blocks/_paragraph.html.erb
110
+ - app/views/pressroom/blocks/_quote.html.erb
111
+ - app/views/pressroom/categories/_form.html.erb
112
+ - app/views/pressroom/categories/edit.html.erb
113
+ - app/views/pressroom/categories/index.html.erb
114
+ - app/views/pressroom/categories/new.html.erb
115
+ - app/views/pressroom/posts/_form.html.erb
116
+ - app/views/pressroom/posts/_post_row.html.erb
117
+ - app/views/pressroom/posts/edit.html.erb
118
+ - app/views/pressroom/posts/index.html.erb
119
+ - app/views/pressroom/posts/new.html.erb
120
+ - app/views/pressroom/tags/edit.html.erb
121
+ - app/views/pressroom/tags/index.html.erb
122
+ - config/editorjs_manifest.yml
123
+ - config/importmap.rb
124
+ - config/locales/pressroom.en.yml
125
+ - config/locales/transliteration.ru.yml
126
+ - config/locales/transliteration.uk.yml
127
+ - config/routes.rb
128
+ - db/migrate/20260821000001_create_pressroom_posts.rb
129
+ - db/migrate/20260821000002_create_pressroom_taxonomy.rb
130
+ - lib/pressroom.rb
131
+ - lib/pressroom/blocks/definition.rb
132
+ - lib/pressroom/blocks/registry.rb
133
+ - lib/pressroom/blocks/renderer.rb
134
+ - lib/pressroom/blocks/sanitizer.rb
135
+ - lib/pressroom/blocks/schema.rb
136
+ - lib/pressroom/blocks/text_extractor.rb
137
+ - lib/pressroom/built_in_blocks.rb
138
+ - lib/pressroom/configuration.rb
139
+ - lib/pressroom/engine.rb
140
+ - lib/pressroom/errors.rb
141
+ - lib/pressroom/feed.rb
142
+ - lib/pressroom/transliteration.rb
143
+ - lib/pressroom/version.rb
144
+ - lib/tasks/pressroom_tasks.rake
145
+ - lib/tasks/pressroom_vendor.rake
146
+ homepage: https://github.com/alex-andreiev/pressroom
147
+ licenses:
148
+ - MIT
149
+ metadata:
150
+ rubygems_mfa_required: 'true'
151
+ source_code_uri: https://github.com/alex-andreiev/pressroom
152
+ changelog_uri: https://github.com/alex-andreiev/pressroom/blob/main/CHANGELOG.md
153
+ bug_tracker_uri: https://github.com/alex-andreiev/pressroom/issues
154
+ rdoc_options: []
155
+ require_paths:
156
+ - lib
157
+ required_ruby_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '3.4'
162
+ required_rubygems_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ requirements: []
168
+ rubygems_version: 3.6.2
169
+ specification_version: 4
170
+ summary: Block-based article publishing engine for Rails
171
+ test_files: []