spina-pro 0.0.1 → 0.13.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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +83 -0
  3. data/LICENSE +21 -0
  4. data/README.md +66 -0
  5. data/Rakefile +13 -0
  6. data/app/assets/config/spina/pro/manifest.js +2 -0
  7. data/app/assets/javascripts/spina/pro/controllers/form_autosave_controller.js +19 -0
  8. data/app/assets/javascripts/spina/pro/controllers/search_controller.js +64 -0
  9. data/app/assets/javascripts/spina/pro/controllers/seo_controller.js +66 -0
  10. data/app/assets/javascripts/spina/pro/libraries/debounce.js +65 -0
  11. data/app/components/spina/pro/messages/field_component.html.erb +1 -0
  12. data/app/components/spina/pro/messages/field_component.rb +36 -0
  13. data/app/components/spina/pro/messages/message_component.html.erb +31 -0
  14. data/app/components/spina/pro/messages/message_component.rb +30 -0
  15. data/app/components/spina/pro/pages/page_revision_component.html.erb +42 -0
  16. data/app/components/spina/pro/pages/page_revision_component.rb +15 -0
  17. data/app/components/spina/pro/user_interface/hint_component.html.erb +6 -0
  18. data/app/components/spina/pro/user_interface/hint_component.rb +17 -0
  19. data/app/controllers/spina/admin/pro/drafts_controller.rb +41 -0
  20. data/app/controllers/spina/admin/pro/inboxes_controller.rb +50 -0
  21. data/app/controllers/spina/admin/pro/message_archive_controller.rb +15 -0
  22. data/app/controllers/spina/admin/pro/message_spam_controller.rb +15 -0
  23. data/app/controllers/spina/admin/pro/messages_controller.rb +78 -0
  24. data/app/controllers/spina/admin/pro/not_found_errors_controller.rb +35 -0
  25. data/app/controllers/spina/admin/pro/rewrite_rules_controller.rb +53 -0
  26. data/app/controllers/spina/admin/pro/search_controller.rb +41 -0
  27. data/app/controllers/spina/admin/pro/version_history_controller.rb +18 -0
  28. data/app/mailers/spina/pro/message_mailer.rb +15 -0
  29. data/app/models/spina/parts/pro/date.rb +11 -0
  30. data/app/models/spina/parts/pro/date_time.rb +11 -0
  31. data/app/models/spina/pro/inbox.rb +31 -0
  32. data/app/models/spina/pro/message.rb +142 -0
  33. data/app/models/spina/pro/not_found_error.rb +15 -0
  34. data/app/models/spina/pro/page_revision.rb +22 -0
  35. data/app/models/spina/pro/search_document.rb +19 -0
  36. data/app/views/spina/admin/hooks/pro/_primary_navigation.html.erb +40 -0
  37. data/app/views/spina/admin/hooks/pro/_website_secondary_navigation.html.erb +1 -0
  38. data/app/views/spina/admin/pages/_button_metadata.html.erb +4 -0
  39. data/app/views/spina/admin/pages/_button_pro_seo.html.erb +5 -0
  40. data/app/views/spina/admin/pages/_form.html.erb +52 -0
  41. data/app/views/spina/admin/pages/_form_metadata.html.erb +10 -0
  42. data/app/views/spina/admin/pages/_form_page_content.html.erb +15 -0
  43. data/app/views/spina/admin/pages/_form_pro_seo.html.erb +64 -0
  44. data/app/views/spina/admin/parts/pro/date_times/_form.html.erb +6 -0
  45. data/app/views/spina/admin/parts/pro/dates/_form.html.erb +6 -0
  46. data/app/views/spina/admin/pro/drafts/index.html.erb +52 -0
  47. data/app/views/spina/admin/pro/inboxes/_unread_count.html.erb +5 -0
  48. data/app/views/spina/admin/pro/inboxes/_unread_indicator.html.erb +3 -0
  49. data/app/views/spina/admin/pro/inboxes/edit.html.erb +21 -0
  50. data/app/views/spina/admin/pro/inboxes/show.html.erb +50 -0
  51. data/app/views/spina/admin/pro/message_archive/show.html.erb +34 -0
  52. data/app/views/spina/admin/pro/message_spam/show.html.erb +34 -0
  53. data/app/views/spina/admin/pro/messages/_message.html.erb +3 -0
  54. data/app/views/spina/admin/pro/messages/_modal.html.erb +83 -0
  55. data/app/views/spina/admin/pro/messages/edit.html.erb +26 -0
  56. data/app/views/spina/admin/pro/messages/fields/_boolean.html.erb +1 -0
  57. data/app/views/spina/admin/pro/messages/fields/_string.html.erb +1 -0
  58. data/app/views/spina/admin/pro/messages/fields/_text.html.erb +1 -0
  59. data/app/views/spina/admin/pro/messages/show.html.erb +1 -0
  60. data/app/views/spina/admin/pro/messages/update.turbo_stream.erb +2 -0
  61. data/app/views/spina/admin/pro/not_found_errors/_not_found_error.html.erb +36 -0
  62. data/app/views/spina/admin/pro/not_found_errors/ignored.html.erb +10 -0
  63. data/app/views/spina/admin/pro/not_found_errors/index.html.erb +25 -0
  64. data/app/views/spina/admin/pro/rewrite_rules/_form.html.erb +31 -0
  65. data/app/views/spina/admin/pro/rewrite_rules/_rewrite_rule.html.erb +17 -0
  66. data/app/views/spina/admin/pro/rewrite_rules/index.html.erb +31 -0
  67. data/app/views/spina/admin/pro/rewrite_rules/new.html.erb +3 -0
  68. data/app/views/spina/admin/pro/search/_results.html.erb +9 -0
  69. data/app/views/spina/admin/pro/search/new.html.erb +40 -0
  70. data/app/views/spina/admin/pro/search/results/_page.html.erb +25 -0
  71. data/app/views/spina/admin/pro/shared/_redirects_navigation.html.erb +30 -0
  72. data/app/views/spina/admin/pro/version_history/show.html.erb +34 -0
  73. data/app/views/spina/pro/message_mailer/forward.html.erb +70 -0
  74. data/app/views/spina/pro/message_mailer/forward.text.erb +14 -0
  75. data/config/locales/en.yml +96 -0
  76. data/config/locales/nl.yml +96 -0
  77. data/config/routes.rb +44 -0
  78. data/db/migrate/1_create_spina_pro_not_found_errors.rb +14 -0
  79. data/db/migrate/2_add_created_manually_to_spina_rewrite_rules.rb +5 -0
  80. data/db/migrate/3_create_spina_pro_inboxes.rb +11 -0
  81. data/db/migrate/4_create_spina_pro_messages.rb +14 -0
  82. data/db/migrate/5_add_archived_at_to_spina_pro_messages.rb +5 -0
  83. data/db/migrate/6_create_spina_pro_search_documents.rb +9 -0
  84. data/db/migrate/7_create_spina_pro_page_revisions.rb +12 -0
  85. data/db/migrate/8_add_draft_to_spina_pro_page_revisions.rb +5 -0
  86. data/lib/generators/spina/pro/install_generator.rb +13 -0
  87. data/lib/generators/spina/pro/message_generator.rb +14 -0
  88. data/lib/generators/spina/pro/templates/initializer.rb.tt +6 -0
  89. data/lib/generators/spina/pro/templates/message.rb.tt +5 -0
  90. data/lib/spina/pro/engine.rb +57 -0
  91. data/lib/spina/pro/inboxes_helper.rb +17 -0
  92. data/lib/spina/pro/order_by_date.rb +16 -0
  93. data/lib/spina/pro/page_ordering.rb +34 -0
  94. data/lib/spina/pro/page_revisions/load_page_revision.rb +20 -0
  95. data/lib/spina/pro/page_revisions/page_decorator.rb +40 -0
  96. data/lib/spina/pro/page_revisions/pages_controller_decorator.rb +52 -0
  97. data/lib/spina/pro/page_search.rb +20 -0
  98. data/lib/spina/pro/recently_visited.rb +33 -0
  99. data/lib/spina/pro/replace_seo_tab.rb +18 -0
  100. data/lib/spina/pro/search.rb +45 -0
  101. data/lib/spina/pro/track_not_found_errors.rb +23 -0
  102. data/lib/spina/pro/version.rb +5 -0
  103. data/lib/spina/pro.rb +38 -0
  104. metadata +122 -23
@@ -0,0 +1,50 @@
1
+ module Spina::Admin
2
+ module Pro
3
+ class InboxesController < AdminController
4
+ admin_section :inboxes
5
+
6
+ before_action :set_inbox
7
+
8
+ def show
9
+ @messages = scope_search(params[:search], @inbox.messages.not_archived.ham.newest.page(params[:page]).per(25))
10
+ add_breadcrumb @inbox.label
11
+ end
12
+
13
+ def edit
14
+ add_breadcrumb @inbox.label, spina.admin_pro_inbox_path(@inbox), class: 'text-gray-400'
15
+ add_breadcrumb t("spina.pro.inboxes.forward_messages")
16
+ end
17
+
18
+ def update
19
+ if @inbox.update(inbox_params)
20
+ redirect_to spina.admin_pro_inbox_path(@inbox)
21
+ else
22
+ render :edit, status: :unprocessable_entity
23
+ end
24
+ end
25
+
26
+ def mark_all_as_read
27
+ @inbox.messages.unread.update_all(read_at: Time.current)
28
+ @inbox.update_unread_count
29
+ flash[:info] = t('spina.pro.inboxes.marked_all_as_read')
30
+ redirect_to spina.admin_pro_inbox_path(@inbox)
31
+ end
32
+
33
+ private
34
+
35
+ def scope_search(search, records)
36
+ return records if search.blank?
37
+ records.where("email LIKE :search", search: "%#{search}%")
38
+ end
39
+
40
+ def set_inbox
41
+ @inbox = Spina::Pro::Inbox.find(params[:id])
42
+ end
43
+
44
+ def inbox_params
45
+ params.require(:inbox).permit(:label, :forward_email)
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,15 @@
1
+ module Spina
2
+ module Admin
3
+ module Pro
4
+ class MessageArchiveController < AdminController
5
+ admin_section :inboxes
6
+
7
+ def show
8
+ @messages = Spina::Pro::Message.archived.newest.ham.page(params[:page]).per(25)
9
+ add_breadcrumb t("spina.pro.messages.message_archive")
10
+ end
11
+
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Spina
2
+ module Admin
3
+ module Pro
4
+ class MessageSpamController < AdminController
5
+ admin_section :inboxes
6
+
7
+ def show
8
+ @messages = Spina::Pro::Message.spam.newest.page(params[:page]).per(25)
9
+ add_breadcrumb t("spina.pro.messages.spam")
10
+ end
11
+
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,78 @@
1
+ module Spina::Admin
2
+ module Pro
3
+ class MessagesController < AdminController
4
+ admin_section :inboxes
5
+
6
+ before_action :set_inbox
7
+ before_action :set_message
8
+
9
+ def show
10
+ @message.mark_as_read!
11
+ end
12
+
13
+ def edit
14
+ end
15
+
16
+ def update
17
+ if @message.update(message_params)
18
+ render :update
19
+ else
20
+ render :edit, status: :unprocessable_entity
21
+ end
22
+ end
23
+
24
+ def destroy
25
+ @message.destroy
26
+ redirect_to spina.admin_pro_inbox_path(@inbox)
27
+ end
28
+
29
+ def mark_as_unread
30
+ @message.mark_as_unread!
31
+ redirect_to spina.admin_pro_inbox_path(@inbox)
32
+ end
33
+
34
+ def mark_as_spam
35
+ @message.mark_as_spam!
36
+ redirect_to spina.admin_pro_inbox_path(@inbox)
37
+ end
38
+
39
+ def mark_as_ham
40
+ @message.mark_as_ham!
41
+ redirect_to spina.admin_pro_message_spam_path
42
+ end
43
+
44
+ def forward
45
+ Spina::Pro::MessageMailer.forward(@message).deliver_later if @inbox.forwarding?
46
+ flash[:info] = t("spina.pro.messages.forwarded", email: @inbox.forward_email)
47
+ redirect_to spina.admin_pro_inbox_path(@inbox)
48
+ end
49
+
50
+ def archive
51
+ @message.archive!
52
+ flash[:info] = t("spina.pro.messages.archived")
53
+ redirect_to spina.admin_pro_inbox_path(@inbox)
54
+ end
55
+
56
+ def unarchive
57
+ @message.unarchive!
58
+ flash[:info] = t("spina.pro.messages.unarchived")
59
+ redirect_to spina.admin_pro_message_archive_path
60
+ end
61
+
62
+ private
63
+
64
+ def message_params
65
+ params.require(:message).permit!
66
+ end
67
+
68
+ def set_message
69
+ @message = @inbox.messages.find(params[:id])
70
+ end
71
+
72
+ def set_inbox
73
+ @inbox = Spina::Pro::Inbox.find(params[:inbox_id])
74
+ end
75
+
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,35 @@
1
+ module Spina::Admin
2
+ module Pro
3
+ class NotFoundErrorsController < AdminController
4
+
5
+ def index
6
+ add_breadcrumb t("spina.pro.rewrite_rules.title")
7
+ @not_found_errors = Spina::Pro::NotFoundError.not_ignored.order(visits: :desc, last_visited: :desc).page(params[:page]).per(50)
8
+ scope_search(params[:search])
9
+ end
10
+
11
+ def ignored
12
+ @not_found_errors = Spina::Pro::NotFoundError.ignored.order(visits: :desc, last_visited: :desc).page(params[:page]).per(50)
13
+ end
14
+
15
+ def update
16
+ @not_found_error = Spina::Pro::NotFoundError.find(params[:id])
17
+ @not_found_error.update(not_found_error_params)
18
+ render turbo_stream: turbo_stream.update(@not_found_error, partial: "not_found_error", object: @not_found_error)
19
+ end
20
+
21
+ private
22
+
23
+
24
+ def scope_search(search)
25
+ return if search.blank?
26
+ @not_found_errors = @not_found_errors.where("path LIKE :search", search: "%#{search}%")
27
+ end
28
+
29
+ def not_found_error_params
30
+ params.require(:not_found_error).permit(:ignored)
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,53 @@
1
+ module Spina::Admin
2
+ module Pro
3
+ class RewriteRulesController < AdminController
4
+ before_action :set_breadcrumbs
5
+
6
+ def new
7
+ @rewrite_rule = Spina::RewriteRule.new(old_path: params[:old_path])
8
+ end
9
+
10
+ def create
11
+ @rewrite_rule = Spina::RewriteRule.new(rewrite_rule_params)
12
+ if @rewrite_rule.save
13
+ redirect_to spina.admin_pro_rewrite_rules_path
14
+ else
15
+ render turbo_stream: turbo_stream.update(view_context.dom_id(@rewrite_rule, :form), partial: "form")
16
+ end
17
+ end
18
+
19
+ def index
20
+ @rewrite_rules = Spina::RewriteRule.where(created_manually: true).page(params[:page]).per(50)
21
+ scope_search(params[:search])
22
+ end
23
+
24
+ def destroy
25
+ @rewrite_rule = Spina::RewriteRule.find(params[:id])
26
+ @rewrite_rule.destroy
27
+ render turbo_stream: turbo_stream.remove(@rewrite_rule)
28
+ end
29
+
30
+ def auto_generated
31
+ @rewrite_rules = Spina::RewriteRule.where(created_manually: false).page(params[:page]).per(50)
32
+ scope_search(params[:search])
33
+ render :index
34
+ end
35
+
36
+ private
37
+
38
+ def scope_search(search)
39
+ return if search.blank?
40
+ @rewrite_rules = @rewrite_rules.where("old_path LIKE :search OR new_path LIKE :search", search: "%#{search}%")
41
+ end
42
+
43
+ def set_breadcrumbs
44
+ add_breadcrumb t('spina.pro.rewrite_rules.title')
45
+ end
46
+
47
+ def rewrite_rule_params
48
+ params.require(:rewrite_rule).permit(:old_path, :new_path).merge(created_manually: true)
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,41 @@
1
+ module Spina::Admin
2
+ module Pro
3
+ class SearchController < AdminController
4
+
5
+ def create
6
+ if params[:query].present?
7
+ @results = Spina::Pro::SearchDocument.search(params[:query]).limit(8)
8
+ else
9
+ @pages = recently_visited_pages
10
+ end
11
+
12
+ render :new
13
+ end
14
+
15
+ def mac_or_ios?
16
+ @mac_os ||= browser.platform.mac? || browser.platform.ios?
17
+ end
18
+ helper_method :mac_or_ios?
19
+
20
+ def recently_visited_pages
21
+ @pages ||= GlobalID::Locator.locate_many(recently_visited_gids, ignore_missing: true).first(3)
22
+ end
23
+ helper_method :recently_visited_pages
24
+
25
+ private
26
+
27
+ def recently_visited_gids
28
+ begin
29
+ JSON.parse(cookies[:recently_visited])
30
+ rescue
31
+ []
32
+ end
33
+ end
34
+
35
+ def browser
36
+ @browser ||= Browser.new(request.user_agent)
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,18 @@
1
+ module Spina::Admin
2
+ module Pro
3
+ class VersionHistoryController < AdminController
4
+ before_action :set_page
5
+
6
+ def show
7
+ @version_history = @page.version_history.order(updated_at: :desc).page(params[:page]).per(20)
8
+ end
9
+
10
+ private
11
+
12
+ def set_page
13
+ @page = Spina::Page.find(params[:page_id])
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ module Spina
2
+ module Pro
3
+ class MessageMailer < ApplicationMailer
4
+
5
+ def forward(message)
6
+ @message = message
7
+ @inbox = message.inbox
8
+
9
+ mail to: @inbox.forward_email,
10
+ subject: t("spina.pro.message_mailer.forward.subject", type: @message.model_name.human, name: @message.to_name)
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Spina
2
+ module Parts
3
+ module Pro
4
+ class Date < Base
5
+ attr_json :value, :date
6
+
7
+ alias_method :content, :value
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Spina
2
+ module Parts
3
+ module Pro
4
+ class DateTime < Base
5
+ attr_json :value, :datetime
6
+
7
+ alias_method :content, :value
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ module Spina
2
+ module Pro
3
+ class Inbox < ApplicationRecord
4
+ has_many :messages, dependent: :restrict_with_exception
5
+
6
+ validates :name, :label, presence: true
7
+ validates :name, uniqueness: true
8
+
9
+ after_update_commit :broadcast_unread_count, :broadcast_unread_indicator
10
+
11
+ def update_unread_count
12
+ update(unread_count: messages.unread.not_archived.ham.count)
13
+ end
14
+
15
+ def forwarding?
16
+ forward_email.present?
17
+ end
18
+
19
+ private
20
+
21
+ def broadcast_unread_count
22
+ broadcast_update_later_to Spina::Current.account, :messages_unread, target: "messages_unread_count", partial: "spina/admin/pro/inboxes/unread_count"
23
+ end
24
+
25
+ def broadcast_unread_indicator
26
+ broadcast_update_later_to Spina::Current.account, :messages_unread, target: "unread_indicator_inbox_#{id}", partial: "spina/admin/pro/inboxes/unread_indicator"
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,142 @@
1
+ module Spina
2
+ module Pro
3
+ class Message < ApplicationRecord
4
+ include AttrJson::Record
5
+ include Gravatar
6
+
7
+ # Class attributes
8
+ class_attribute :index_attributes, default: []
9
+ class_attribute :inbox_name, default: "messages"
10
+ class_attribute :inbox_label
11
+
12
+ belongs_to :inbox
13
+
14
+ scope :read, -> { where.not(read_at: nil) }
15
+ scope :unread, -> { where(read_at: nil) }
16
+ scope :newest, -> { order(created_at: :desc) }
17
+ scope :not_archived, -> { where(archived_at: nil) }
18
+ scope :archived, -> { where.not(archived_at: nil) }
19
+ scope :ham, -> { where(spam: false) }
20
+ scope :spam, -> { where(spam: true) }
21
+
22
+ # If no inbox is specified, store message in the default "Messages" inbox
23
+ before_validation :store_in_default_inbox, on: :create, if: -> { inbox.nil? }
24
+
25
+ # After commit, update unread count on inbox
26
+ after_commit -> { inbox.update_unread_count }
27
+
28
+ after_create_commit :broadcast_prepend_to_inbox
29
+ after_update_commit :broadcast_replace_to_inbox
30
+
31
+ # Forward message to email if Inbox forwarding is setup
32
+ after_create_commit :forward_message, if: -> { ham? && inbox.forwarding? }
33
+
34
+ def to_name
35
+ if message_attributes.include? :name
36
+ name
37
+ elsif message_attributes.include?(:first_name) && message_attributes.include?(:last_name)
38
+ [first_name, last_name].join(" ")
39
+ else
40
+ email
41
+ end
42
+ end
43
+
44
+ def name_attributes
45
+ [:first_name, :last_name, :name]
46
+ end
47
+
48
+ def ham?
49
+ !spam?
50
+ end
51
+
52
+ def label
53
+ id
54
+ end
55
+
56
+ def unread?
57
+ read_at.nil?
58
+ end
59
+
60
+ def archived?
61
+ archived_at.present?
62
+ end
63
+
64
+ def index_attributes
65
+ self.class.index_attributes
66
+ end
67
+
68
+ def message_attributes
69
+ self.class.attr_json_registry.attribute_names
70
+ end
71
+
72
+ def body_attributes
73
+ message_attributes - name_attributes
74
+ end
75
+
76
+ def field_type(key)
77
+ self.class.attr_json_registry.type_for_attribute(key)&.type
78
+ end
79
+
80
+ def archive!
81
+ update!(archived_at: Time.current) if archived_at.nil?
82
+ end
83
+
84
+ def unarchive!
85
+ update!(archived_at: nil)
86
+ end
87
+
88
+ def mark_as_read!
89
+ update!(read_at: Time.current) if read_at.nil?
90
+ end
91
+
92
+ def mark_as_unread!
93
+ update!(read_at: nil)
94
+ end
95
+
96
+ def mark_as_spam!
97
+ update!(spam: true)
98
+ end
99
+
100
+ def mark_as_ham!
101
+ update!(spam: false)
102
+ end
103
+
104
+ def fallback_url
105
+ "https://eu.ui-avatars.com/api/#{CGI.escape(json_attributes.values.first.to_s)}/128"
106
+ end
107
+
108
+ class << self
109
+
110
+ def field(name, type, **options)
111
+ attr_json(name, type, **options.except(:index))
112
+ self.index_attributes += [name] if options[:index]
113
+ end
114
+
115
+ def inbox(name, label = nil)
116
+ self.inbox_name = name
117
+ self.inbox_label = label
118
+ end
119
+
120
+ end
121
+
122
+ private
123
+
124
+ def store_in_default_inbox
125
+ self.inbox = Inbox.where(name: self.class.inbox_name).first_or_create(label: self.class.inbox_label || self.class.inbox_name.capitalize)
126
+ end
127
+
128
+ def broadcast_prepend_to_inbox
129
+ broadcast_prepend_later_to inbox, target: :messages, partial: "spina/admin/pro/messages/message", locals: {message: self}
130
+ end
131
+
132
+ def broadcast_replace_to_inbox
133
+ broadcast_replace_later_to inbox, partial: "spina/admin/pro/messages/message", locals: {message: self}
134
+ end
135
+
136
+ def forward_message
137
+ MessageMailer.forward(self).deliver_later
138
+ end
139
+
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,15 @@
1
+ module Spina
2
+ module Pro
3
+ class NotFoundError < ApplicationRecord
4
+ scope :ignored, -> { where(ignored: true) }
5
+ scope :not_ignored, -> { where(ignored: false) }
6
+
7
+ validates :path, :last_visited, presence: true
8
+
9
+ def uri
10
+ URI.parse(request_url)
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ module Spina
2
+ module Pro
3
+ class PageRevision < ApplicationRecord
4
+ include AttrJson::Record
5
+ include AttrJson::NestedAttributes
6
+ include TranslatedContent
7
+
8
+ belongs_to :page
9
+
10
+ scope :draft, -> { where(draft: true) }
11
+ scope :published, -> { where(draft: false) }
12
+
13
+ def load
14
+ content.each do |key, value|
15
+ page[key.to_sym] = value
16
+ end
17
+ page
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ module Spina
2
+ module Pro
3
+ class SearchDocument < ApplicationRecord
4
+ include PgSearch::Model
5
+
6
+ belongs_to :searchable, polymorphic: true
7
+
8
+ pg_search_scope :search,
9
+ against: :content,
10
+ using: {
11
+ tsearch: {
12
+ dictionary: Spina::Pro.config.postgresql_dictionary,
13
+ prefix: true
14
+ }
15
+ }
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,40 @@
1
+ <% unless Spina::Pro.config.hide_inboxes %>
2
+ <%= render Spina::MainNavigation::SubNavComponent.new(:inboxes) do |nav| %>
3
+ <% nav.with_icon do %>
4
+ <%= heroicon('inbox', style: :solid, class: 'w-8 h-8 text-white md:mr-3 transform scale-95') %>
5
+
6
+ <%= turbo_stream_from Spina::Current.account, :messages_unread %>
7
+ <div id="messages_unread_count">
8
+ <%= render partial: "spina/admin/pro/inboxes/unread_count" %>
9
+ </div>
10
+
11
+ <div class="text-white font-semibold hidden md:block transform -translate-x-2 ease-in-out duration-300 absolute md:relative opacity-0 transition-all" data-navigation-target="label">
12
+ Inbox
13
+ </div>
14
+ <% end %>
15
+
16
+ <% nav.with_links do %>
17
+ <% inboxes.each do |inbox| %>
18
+
19
+ <li <%= "data-navigation-active" if @inbox == inbox %>>
20
+ <%= link_to spina.admin_pro_inbox_path(inbox), class: "flex items-center p-3 leading-none text-white hover:opacity-100 transition-opacity font-semibold #{'opacity-50' if @inbox != inbox}" do %>
21
+ <%= inbox.label %>
22
+
23
+ <div id="<%= dom_id(inbox, :unread_indicator) %>">
24
+ <%= render "spina/admin/pro/inboxes/unread_indicator", inbox: inbox %>
25
+ </div>
26
+ <% end %>
27
+ </li>
28
+
29
+ <% end %>
30
+
31
+ <%= render Spina::MainNavigation::LinkComponent.new(t("spina.pro.messages.message_archive"), spina.admin_pro_message_archive_path, active: controller_name.in?(["message_archive", "message_spam"])) %>
32
+ <% end %>
33
+ <% end %>
34
+ <% end %>
35
+
36
+ <li class="fixed bottom-20" data-controller="hotkeys" data-hotkeys="cmd+k, ctrl+k">
37
+ <%= button_to spina.admin_pro_search_path, class: "flex w-full items-center p-3 relative opacity-50 hover:opacity-100 transition-opacity", data: {turbo_frame: "modal", hotkeys_target: "button"} do %>
38
+ <%= heroicon('magnifying-glass', style: :mini, class: 'w-8 h-8 text-white md:mr-3') %>
39
+ <% end %>
40
+ </li>
@@ -0,0 +1 @@
1
+ <%= render Spina::MainNavigation::LinkComponent.new t('spina.pro.rewrite_rules.title'), spina.admin_pro_rewrite_rules_path, active: controller_name.in?(%w(rewrite_rules not_found_errors)) %>
@@ -0,0 +1,4 @@
1
+ <%= render Spina::Pages::TabButtonComponent.new(tab_name: 'metadata') do %>
2
+ <%= heroicon('document-search', style: :solid, class: 'w-4 h-4 mr-1 -ml-1 opacity-75') %>
3
+ <span class="">Metadata</span>
4
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <%= render Spina::Pages::TabButtonComponent.new(tab_name: 'pro_seo') do %>
2
+ <%= heroicon('search', style: :solid, class: 'w-4 h-4 mr-1 -ml-1 opacity-75') %>
3
+ <span class="hidden md:inline"><%= t("spina.pages.search_engines") %></span>
4
+ <span class="md:hidden">SEO</span>
5
+ <% end %>