plum-cms 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 (178) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +20 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +252 -0
  5. data/app/assets/builds/tailwind.css +2 -0
  6. data/app/assets/images/plum-mark.svg +7 -0
  7. data/app/assets/images/table-needs-logo.svg +83 -0
  8. data/app/assets/javascripts/plum/application.js +15 -0
  9. data/app/assets/stylesheets/application.css +10 -0
  10. data/app/assets/stylesheets/plum/control_panel.css +2 -0
  11. data/app/assets/tailwind/application.css +47 -0
  12. data/app/controllers/plum/application_controller.rb +34 -0
  13. data/app/controllers/plum/cp/assets_controller.rb +84 -0
  14. data/app/controllers/plum/cp/base_controller.rb +45 -0
  15. data/app/controllers/plum/cp/content_types_controller.rb +59 -0
  16. data/app/controllers/plum/cp/dashboard_controller.rb +10 -0
  17. data/app/controllers/plum/cp/entries_controller.rb +240 -0
  18. data/app/controllers/plum/cp/form_definitions_controller.rb +105 -0
  19. data/app/controllers/plum/cp/form_submissions_controller.rb +26 -0
  20. data/app/controllers/plum/cp/globals_controller.rb +84 -0
  21. data/app/controllers/plum/cp/nav_items_controller.rb +68 -0
  22. data/app/controllers/plum/cp/nav_menus_controller.rb +56 -0
  23. data/app/controllers/plum/cp/site_settings_controller.rb +87 -0
  24. data/app/controllers/plum/cp/taxonomies_controller.rb +56 -0
  25. data/app/controllers/plum/cp/terms_controller.rb +55 -0
  26. data/app/controllers/plum/cp/theme_previews_controller.rb +27 -0
  27. data/app/controllers/plum/cp/themes_controller.rb +98 -0
  28. data/app/controllers/plum/form_submissions_controller.rb +44 -0
  29. data/app/controllers/plum/pages_controller.rb +161 -0
  30. data/app/controllers/plum/sessions_controller.rb +25 -0
  31. data/app/controllers/plum/theme_assets_controller.rb +14 -0
  32. data/app/helpers/application_helper.rb +2 -0
  33. data/app/helpers/markdown_helper.rb +21 -0
  34. data/app/helpers/plum/cp/assets_helper.rb +21 -0
  35. data/app/helpers/plum/cp/theme_settings_helper.rb +26 -0
  36. data/app/javascript/application.js +18 -0
  37. data/app/javascript/controllers/application.js +9 -0
  38. data/app/javascript/controllers/index.js +4 -0
  39. data/app/javascript/controllers/plum/blocks_editor_controller.js +275 -0
  40. data/app/javascript/controllers/plum/blueprint_controller.js +120 -0
  41. data/app/javascript/controllers/plum/form_fields_controller.js +84 -0
  42. data/app/javascript/controllers/plum/image_picker_controller.js +143 -0
  43. data/app/javascript/controllers/plum/theme_settings_controller.js +17 -0
  44. data/app/models/plum/application_record.rb +5 -0
  45. data/app/models/plum/asset.rb +69 -0
  46. data/app/models/plum/content_type.rb +35 -0
  47. data/app/models/plum/entry.rb +67 -0
  48. data/app/models/plum/entry_term.rb +8 -0
  49. data/app/models/plum/form_definition.rb +68 -0
  50. data/app/models/plum/form_submission.rb +76 -0
  51. data/app/models/plum/global.rb +24 -0
  52. data/app/models/plum/nav_item.rb +58 -0
  53. data/app/models/plum/nav_menu.rb +24 -0
  54. data/app/models/plum/site.rb +112 -0
  55. data/app/models/plum/site_scoped.rb +17 -0
  56. data/app/models/plum/site_setting.rb +35 -0
  57. data/app/models/plum/taxonomy.rb +24 -0
  58. data/app/models/plum/term.rb +23 -0
  59. data/app/models/plum/user.rb +16 -0
  60. data/app/services/plum/base_blocks.rb +161 -0
  61. data/app/services/plum/block_editor_config.rb +65 -0
  62. data/app/services/plum/block_library.rb +31 -0
  63. data/app/services/plum/builder_renderer.rb +63 -0
  64. data/app/services/plum/field_expander.rb +78 -0
  65. data/app/services/plum/form_renderer.rb +110 -0
  66. data/app/services/plum/liquid_context.rb +335 -0
  67. data/app/services/plum/liquid_filters.rb +58 -0
  68. data/app/services/plum/liquid_renderer.rb +49 -0
  69. data/app/services/plum/theme.rb +160 -0
  70. data/app/services/plum/theme_asset_path.rb +31 -0
  71. data/app/services/plum/theme_package_installer.rb +287 -0
  72. data/app/services/plum/theme_registry.rb +63 -0
  73. data/app/services/plum/theme_resolver.rb +55 -0
  74. data/app/services/plum/theme_settings_params.rb +54 -0
  75. data/app/themes/bagel-boy/assets/screenshot.svg +92 -0
  76. data/app/themes/bagel-boy/assets/theme.css +184 -0
  77. data/app/themes/bagel-boy/blocks/hours.liquid +4 -0
  78. data/app/themes/bagel-boy/blocks/menu_item.liquid +7 -0
  79. data/app/themes/bagel-boy/layouts/base.liquid +64 -0
  80. data/app/themes/bagel-boy/templates/collections/posts.liquid +32 -0
  81. data/app/themes/bagel-boy/templates/entries/_default.liquid +13 -0
  82. data/app/themes/bagel-boy/templates/entries/landing.liquid +4 -0
  83. data/app/themes/bagel-boy/templates/entries/pages.liquid +18 -0
  84. data/app/themes/bagel-boy/templates/entries/posts.liquid +8 -0
  85. data/app/themes/bagel-boy/templates/index.liquid +10 -0
  86. data/app/themes/bagel-boy/templates/search.liquid +28 -0
  87. data/app/themes/bagel-boy/theme.yml +47 -0
  88. data/app/themes/bagel-shop/assets/screenshot.svg +28 -0
  89. data/app/themes/bagel-shop/assets/theme.css +7 -0
  90. data/app/themes/bagel-shop/layouts/base.liquid +507 -0
  91. data/app/themes/bagel-shop/templates/entries/_default.liquid +15 -0
  92. data/app/themes/bagel-shop/templates/entries/pages.liquid +12 -0
  93. data/app/themes/bagel-shop/templates/entries/posts.liquid +19 -0
  94. data/app/themes/bagel-shop/templates/index.liquid +55 -0
  95. data/app/themes/bagel-shop/theme.yml +30 -0
  96. data/app/themes/default/assets/screenshot.svg +24 -0
  97. data/app/themes/default/assets/theme.css +89 -0
  98. data/app/themes/default/layouts/base.liquid +117 -0
  99. data/app/themes/default/templates/collections/_default.liquid +16 -0
  100. data/app/themes/default/templates/collections/posts.liquid +27 -0
  101. data/app/themes/default/templates/entries/_default.liquid +26 -0
  102. data/app/themes/default/templates/entries/landing.liquid +5 -0
  103. data/app/themes/default/templates/entries/pages.liquid +17 -0
  104. data/app/themes/default/templates/entries/posts.liquid +20 -0
  105. data/app/themes/default/templates/index.liquid +22 -0
  106. data/app/themes/default/templates/search.liquid +28 -0
  107. data/app/themes/default/templates/taxonomies/_default.liquid +25 -0
  108. data/app/themes/default/templates/taxonomies/_default_index.liquid +14 -0
  109. data/app/themes/default/theme.yml +23 -0
  110. data/app/views/layouts/plum/cp.html.erb +138 -0
  111. data/app/views/layouts/plum/session.html.erb +43 -0
  112. data/app/views/plum/cp/assets/_form.html.erb +44 -0
  113. data/app/views/plum/cp/assets/edit.html.erb +14 -0
  114. data/app/views/plum/cp/assets/index.html.erb +52 -0
  115. data/app/views/plum/cp/assets/new.html.erb +9 -0
  116. data/app/views/plum/cp/content_types/_form.html.erb +113 -0
  117. data/app/views/plum/cp/content_types/edit.html.erb +8 -0
  118. data/app/views/plum/cp/content_types/index.html.erb +48 -0
  119. data/app/views/plum/cp/content_types/new.html.erb +5 -0
  120. data/app/views/plum/cp/content_types/show.html.erb +59 -0
  121. data/app/views/plum/cp/dashboard/show.html.erb +50 -0
  122. data/app/views/plum/cp/entries/_form.html.erb +196 -0
  123. data/app/views/plum/cp/entries/edit.html.erb +9 -0
  124. data/app/views/plum/cp/entries/index.html.erb +51 -0
  125. data/app/views/plum/cp/entries/new.html.erb +5 -0
  126. data/app/views/plum/cp/form_definitions/_form.html.erb +90 -0
  127. data/app/views/plum/cp/form_definitions/edit.html.erb +11 -0
  128. data/app/views/plum/cp/form_definitions/index.html.erb +43 -0
  129. data/app/views/plum/cp/form_definitions/new.html.erb +5 -0
  130. data/app/views/plum/cp/form_definitions/show.html.erb +70 -0
  131. data/app/views/plum/cp/form_submissions/show.html.erb +20 -0
  132. data/app/views/plum/cp/globals/_form.html.erb +44 -0
  133. data/app/views/plum/cp/globals/edit.html.erb +11 -0
  134. data/app/views/plum/cp/globals/index.html.erb +41 -0
  135. data/app/views/plum/cp/globals/new.html.erb +5 -0
  136. data/app/views/plum/cp/globals/show.html.erb +14 -0
  137. data/app/views/plum/cp/nav_items/_form.html.erb +56 -0
  138. data/app/views/plum/cp/nav_items/edit.html.erb +11 -0
  139. data/app/views/plum/cp/nav_items/new.html.erb +6 -0
  140. data/app/views/plum/cp/nav_menus/_form.html.erb +33 -0
  141. data/app/views/plum/cp/nav_menus/_nav_item.html.erb +24 -0
  142. data/app/views/plum/cp/nav_menus/edit.html.erb +11 -0
  143. data/app/views/plum/cp/nav_menus/index.html.erb +41 -0
  144. data/app/views/plum/cp/nav_menus/new.html.erb +5 -0
  145. data/app/views/plum/cp/nav_menus/show.html.erb +24 -0
  146. data/app/views/plum/cp/shared/_icon.html.erb +39 -0
  147. data/app/views/plum/cp/shared/_image_picker.html.erb +38 -0
  148. data/app/views/plum/cp/site_settings/edit.html.erb +145 -0
  149. data/app/views/plum/cp/site_settings/show.html.erb +62 -0
  150. data/app/views/plum/cp/taxonomies/_form.html.erb +38 -0
  151. data/app/views/plum/cp/taxonomies/edit.html.erb +2 -0
  152. data/app/views/plum/cp/taxonomies/index.html.erb +38 -0
  153. data/app/views/plum/cp/taxonomies/new.html.erb +2 -0
  154. data/app/views/plum/cp/taxonomies/show.html.erb +46 -0
  155. data/app/views/plum/cp/terms/_form.html.erb +30 -0
  156. data/app/views/plum/cp/terms/edit.html.erb +2 -0
  157. data/app/views/plum/cp/terms/new.html.erb +2 -0
  158. data/app/views/plum/cp/themes/index.html.erb +160 -0
  159. data/app/views/plum/form_mailer/submission_notification.html.erb +13 -0
  160. data/app/views/plum/form_mailer/submission_notification.text.erb +7 -0
  161. data/app/views/plum/sessions/new.html.erb +20 -0
  162. data/compat/plum-cms.rb +1 -0
  163. data/config/locales/en.yml +31 -0
  164. data/config/plum_importmap.rb +4 -0
  165. data/config/plum_routes.rb +34 -0
  166. data/db/engine_migrate/20260101000001_create_plum_tables.rb +154 -0
  167. data/lib/generators/plum/install/install_generator.rb +54 -0
  168. data/lib/generators/plum/install/templates/plum_initializer.rb +37 -0
  169. data/lib/plum/configuration.rb +88 -0
  170. data/lib/plum/content_source.rb +45 -0
  171. data/lib/plum/content_source_context.rb +30 -0
  172. data/lib/plum/content_source_registry.rb +120 -0
  173. data/lib/plum/engine.rb +30 -0
  174. data/lib/plum/liquid_tags/form_tag.rb +17 -0
  175. data/lib/plum/version.rb +7 -0
  176. data/lib/plum.rb +11 -0
  177. data/vendor/javascript/lexxy.js +11859 -0
  178. metadata +405 -0
@@ -0,0 +1,30 @@
1
+ module Plum
2
+ class ContentSourceContext
3
+ attr_reader :controller, :site
4
+
5
+ def initialize(controller:, site:)
6
+ @controller = controller
7
+ @site = site
8
+ end
9
+
10
+ def request
11
+ controller.request
12
+ end
13
+
14
+ def params
15
+ controller.params
16
+ end
17
+
18
+ def session
19
+ controller.session
20
+ end
21
+
22
+ def current_user
23
+ controller.send(:current_user) if controller.respond_to?(:current_user, true)
24
+ end
25
+
26
+ def owner
27
+ site.owner if site.respond_to?(:owner)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,120 @@
1
+ require "date"
2
+ require_relative "content_source"
3
+ require_relative "content_source_context"
4
+
5
+ module Plum
6
+ class ContentSourceRegistry
7
+ RESERVED_HANDLES = %w[entry entries forms globals nav site].freeze
8
+ HANDLE_PATTERN = /\A[a-z][a-z0-9_]*\z/
9
+
10
+ def initialize
11
+ @sources = {}
12
+ end
13
+
14
+ def register(handle, source = nil, &block)
15
+ raise ArgumentError, "Provide a source or a block, not both" if source && block
16
+ raise ArgumentError, "Content source is required" unless source || block
17
+
18
+ normalized_handle = normalize_handle(handle)
19
+ validate_handle!(normalized_handle)
20
+
21
+ @sources[normalized_handle] = source || block
22
+ end
23
+
24
+ def handles
25
+ @sources.keys
26
+ end
27
+
28
+ def clear
29
+ @sources.clear
30
+ end
31
+
32
+ def to_liquid_context(controller:, site:)
33
+ context = ContentSourceContext.new(controller: controller, site: site)
34
+
35
+ @sources.each_with_object({}) do |(handle, source), hash|
36
+ hash[handle] = resolve_source(handle, source, context)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def normalize_handle(handle)
43
+ handle.to_s
44
+ end
45
+
46
+ def validate_handle!(handle)
47
+ raise ArgumentError, "Content source handle cannot be blank" if handle.blank?
48
+ unless handle.match?(HANDLE_PATTERN)
49
+ raise ArgumentError, "Content source handle must use lowercase letters, numbers, and underscores"
50
+ end
51
+
52
+ if RESERVED_HANDLES.include?(handle)
53
+ raise ArgumentError, "`#{handle}` is reserved by Plum's Liquid context"
54
+ end
55
+ end
56
+
57
+ def resolve_source(handle, source, context)
58
+ source = constantize_source(source)
59
+ value =
60
+ if source.respond_to?(:call)
61
+ source.call(context)
62
+ elsif source.respond_to?(:to_liquid)
63
+ source.to_liquid
64
+ elsif source.respond_to?(:new)
65
+ adapter = source.new(context)
66
+ unless adapter.respond_to?(:to_liquid)
67
+ raise ContentSourceError, "adapter #{source.name || source.inspect} must implement #to_liquid"
68
+ end
69
+
70
+ adapter.to_liquid
71
+ else
72
+ source
73
+ end
74
+
75
+ normalize_for_liquid(value)
76
+ rescue ContentSourceError
77
+ raise
78
+ rescue StandardError => e
79
+ raise ContentSourceError, "Content source `#{handle}` failed: #{e.message}"
80
+ end
81
+
82
+ def constantize_source(source)
83
+ return source unless source.is_a?(String)
84
+
85
+ source.safe_constantize ||
86
+ raise(ContentSourceError, "Content source adapter `#{source}` could not be found")
87
+ end
88
+
89
+ def normalize_for_liquid(value)
90
+ case value
91
+ when nil, String, Numeric, TrueClass, FalseClass, Date, Time
92
+ value
93
+ when Hash
94
+ value.each_with_object({}) do |(key, child), hash|
95
+ hash[key.to_s] = normalize_for_liquid(child)
96
+ end
97
+ when Array
98
+ value.map { |child| normalize_for_liquid(child) }
99
+ else
100
+ normalize_object_for_liquid(value)
101
+ end
102
+ end
103
+
104
+ def normalize_object_for_liquid(value)
105
+ if value.respond_to?(:to_liquid)
106
+ liquid_value = value.to_liquid
107
+ return liquid_value if liquid_value.equal?(value)
108
+
109
+ return normalize_for_liquid(liquid_value)
110
+ end
111
+
112
+ if value.respond_to?(:to_a) && !value.is_a?(String)
113
+ return value.to_a.map { |child| normalize_for_liquid(child) }
114
+ end
115
+
116
+ raise ContentSourceError,
117
+ "#{value.class.name} is not Liquid-safe. Return a Hash, Array, scalar, or object with #to_liquid."
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,30 @@
1
+ require_relative "configuration"
2
+
3
+ module Plum
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace Plum
6
+ config.paths["config/routes.rb"] = "config/plum_routes.rb"
7
+ config.paths["db/migrate"] = [ "db/engine_migrate" ]
8
+
9
+ initializer "plum.assets" do |app|
10
+ app.config.assets.paths << root.join("app/assets/javascripts")
11
+ app.config.assets.paths << root.join("app/javascript/controllers/plum")
12
+ app.config.assets.paths << root.join("vendor/javascript")
13
+
14
+ lexxy_spec = Gem.loaded_specs["lexxy"]
15
+ if lexxy_spec
16
+ lexxy_root = Pathname.new(lexxy_spec.gem_dir)
17
+ %w[app/assets/stylesheets app/javascript].each do |subpath|
18
+ path = lexxy_root.join(subpath)
19
+ app.config.assets.paths << path.to_s if path.exist?
20
+ end
21
+ end
22
+ end
23
+
24
+ initializer "plum.importmap", before: "importmap" do |app|
25
+ if app.config.respond_to?(:importmap)
26
+ app.config.importmap.paths << root.join("config/plum_importmap.rb")
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ module Plum
2
+ module LiquidTags
3
+ class FormTag < Liquid::Tag
4
+ def initialize(tag_name, markup, options)
5
+ super
6
+ @handle_expression = Liquid::Expression.parse(markup.strip)
7
+ end
8
+
9
+ def render(context)
10
+ handle = context.evaluate(@handle_expression).to_s
11
+ renderer = context.registers[:form_renderer]
12
+
13
+ renderer ? renderer.call(handle) : ""
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ module Plum
2
+ module Version
3
+ STRING = "0.1.0"
4
+ end
5
+
6
+ VERSION = Version::STRING
7
+ end
data/lib/plum.rb ADDED
@@ -0,0 +1,11 @@
1
+ require_relative "plum/version"
2
+ require_relative "plum/content_source"
3
+ require_relative "plum/configuration"
4
+
5
+ module Plum
6
+ def self.table_name_prefix
7
+ "plum_"
8
+ end
9
+ end
10
+
11
+ require_relative "plum/engine" if defined?(Rails::Engine)