pages_core 3.15.0 → 3.15.2

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/app/assets/builds/fonts/6569749d.ttf +0 -0
  4. data/app/assets/builds/fonts/7b7db107.woff2 +0 -0
  5. data/app/assets/builds/fonts/921961e9.woff2 +0 -0
  6. data/app/assets/builds/fonts/ee32bc60.ttf +0 -0
  7. data/app/assets/builds/pages_core/admin-dist.js +1 -1
  8. data/app/assets/builds/pages_core/admin-dist.js.map +3 -3
  9. data/app/assets/builds/pages_core/admin.css +26 -14
  10. data/app/assets/builds/pages_core/mailer.css +99 -0
  11. data/app/assets/stylesheets/pages_core/admin/components/forms.css +3 -1
  12. data/app/assets/stylesheets/pages_core/mailer.css +90 -0
  13. data/app/controllers/admin/pages_controller.rb +1 -1
  14. data/app/helpers/pages_core/attachments_helper.rb +1 -1
  15. data/app/javascript/components/PageForm.tsx +6 -0
  16. data/app/javascript/components/drag/useDragCollection.ts +8 -3
  17. data/app/javascript/components/drag/useDragUploader.ts +1 -1
  18. data/app/mailers/admin_mailer.rb +5 -9
  19. data/app/models/autopublisher.rb +1 -1
  20. data/app/models/concerns/pages_core/page_model/attachments.rb +5 -0
  21. data/app/models/concerns/pages_core/page_model/dated_page.rb +3 -3
  22. data/app/models/concerns/pages_core/page_model/images.rb +10 -5
  23. data/app/models/concerns/pages_core/taggable.rb +2 -19
  24. data/app/views/admin_mailer/account_recovery.html.erb +20 -0
  25. data/app/views/admin_mailer/invite.html.erb +11 -0
  26. data/app/views/layouts/pages_core/mailer.html.erb +11 -0
  27. data/lib/pages_core/engine.rb +1 -0
  28. data/lib/pages_core.rb +1 -0
  29. metadata +26 -9
  30. data/app/assets/builds/fonts/2a3059ad.ttf +0 -0
  31. data/app/assets/builds/fonts/47262711.woff2 +0 -0
  32. data/app/assets/builds/fonts/500ddeb0.woff2 +0 -0
  33. data/app/assets/builds/fonts/81221036.ttf +0 -0
  34. data/app/views/admin_mailer/account_recovery.text.erb +0 -10
  35. data/app/views/admin_mailer/invite.text.erb +0 -7
  36. /data/app/assets/stylesheets/pages_core/{admin.postcss.css → admin.css} +0 -0
@@ -543,9 +543,9 @@ template {
543
543
  margin-left: -7px; } }
544
544
 
545
545
  /*!
546
- * Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com
546
+ * Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com
547
547
  * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
548
- * Copyright 2023 Fonticons, Inc.
548
+ * Copyright 2024 Fonticons, Inc.
549
549
  */
550
550
 
551
551
  .fa {
@@ -903,8 +903,8 @@ template {
903
903
  transform: scale(-1, -1); }
904
904
 
905
905
  .fa-rotate-by {
906
- transform: rotate(none);
907
- transform: rotate(var(--fa-rotate-angle, none)); }
906
+ transform: rotate(0);
907
+ transform: rotate(var(--fa-rotate-angle, 0)); }
908
908
 
909
909
  .fa-stack {
910
910
  display: inline-block;
@@ -3210,6 +3210,9 @@ readers do not read off random characters that represent icons */
3210
3210
  .fa-italic::before {
3211
3211
  content: "\f033"; }
3212
3212
 
3213
+ .fa-table-cells-column-lock::before {
3214
+ content: "\e678"; }
3215
+
3213
3216
  .fa-church::before {
3214
3217
  content: "\f51d"; }
3215
3218
 
@@ -5382,6 +5385,9 @@ readers do not read off random characters that represent icons */
5382
5385
  .fa-font::before {
5383
5386
  content: "\f031"; }
5384
5387
 
5388
+ .fa-table-cells-row-lock::before {
5389
+ content: "\e67a"; }
5390
+
5385
5391
  .fa-rupiah-sign::before {
5386
5392
  content: "\e23d"; }
5387
5393
 
@@ -6411,9 +6417,6 @@ readers do not read off random characters that represent icons */
6411
6417
  .fa-share::before {
6412
6418
  content: "\f064"; }
6413
6419
 
6414
- .fa-arrow-turn-right::before {
6415
- content: "\f064"; }
6416
-
6417
6420
  .fa-mail-forward::before {
6418
6421
  content: "\f064"; }
6419
6422
 
@@ -6814,9 +6817,9 @@ readers do not read off random characters that represent icons */
6814
6817
  border-width: 0; }
6815
6818
 
6816
6819
  /*!
6817
- * Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com
6820
+ * Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com
6818
6821
  * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
6819
- * Copyright 2023 Fonticons, Inc.
6822
+ * Copyright 2024 Fonticons, Inc.
6820
6823
  */
6821
6824
 
6822
6825
  :root, :host {
@@ -6827,16 +6830,16 @@ readers do not read off random characters that represent icons */
6827
6830
  font-style: normal;
6828
6831
  font-weight: 900;
6829
6832
  font-display: block;
6830
- src: url("../fonts/47262711.woff2") format("woff2"), url("../fonts/2a3059ad.ttf") format("truetype"); }
6833
+ src: url("../fonts/7b7db107.woff2") format("woff2"), url("../fonts/6569749d.ttf") format("truetype"); }
6831
6834
 
6832
6835
  .fas,
6833
6836
  .fa-solid {
6834
6837
  font-weight: 900; }
6835
6838
 
6836
6839
  /*!
6837
- * Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com
6840
+ * Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com
6838
6841
  * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
6839
- * Copyright 2023 Fonticons, Inc.
6842
+ * Copyright 2024 Fonticons, Inc.
6840
6843
  */
6841
6844
 
6842
6845
  :root, :host {
@@ -6848,7 +6851,7 @@ readers do not read off random characters that represent icons */
6848
6851
  font-style: normal;
6849
6852
  font-weight: 400;
6850
6853
  font-display: block;
6851
- src: url("../fonts/500ddeb0.woff2") format("woff2"), url("../fonts/81221036.ttf") format("truetype"); }
6854
+ src: url("../fonts/921961e9.woff2") format("woff2"), url("../fonts/ee32bc60.ttf") format("truetype"); }
6852
6855
 
6853
6856
  .far,
6854
6857
  .fa-regular {
@@ -7521,7 +7524,7 @@ form .field .date-select {
7521
7524
  }
7522
7525
 
7523
7526
  form .field .date-select .time {
7524
- width: 4rem;
7527
+ width: 5rem;
7525
7528
  min-width: 2rem;
7526
7529
  }
7527
7530
 
@@ -7788,8 +7791,17 @@ form.button-to {
7788
7791
  display: flex;
7789
7792
  flex-wrap: wrap;
7790
7793
  gap: 0.25rem;
7794
+ margin-top: 2rem;
7791
7795
  }
7792
7796
 
7797
+ .buttons:first-child {
7798
+ margin-top: 0px;
7799
+ }
7800
+
7801
+ .buttons:last-child {
7802
+ margin-bottom: 0px;
7803
+ }
7804
+
7793
7805
  .inline-form {
7794
7806
  display: flex;
7795
7807
  }
@@ -0,0 +1,99 @@
1
+ body,
2
+ input,
3
+ textarea,
4
+ a.button,
5
+ a.button:visited,
6
+ button {
7
+ font-family: -apple-system, BlinkMacSystemFont, Calibri, sans-serif;
8
+ color: #111;
9
+ }
10
+
11
+ body {
12
+ font-size: 1rem;
13
+ padding: 2rem;
14
+ }
15
+
16
+ h1,
17
+ h2,
18
+ h3,
19
+ h4,
20
+ h5,
21
+ h6,
22
+ ul,
23
+ ol,
24
+ p {
25
+ max-width: 80ch;
26
+ margin: 0 0 1rem 0;
27
+ }
28
+
29
+ h1:last-child, h2:last-child, h3:last-child, h4:last-child, h5:last-child, h6:last-child, ul:last-child, ol:last-child, p:last-child {
30
+ margin-bottom: 0rem;
31
+ }
32
+
33
+ h1 {
34
+ font-size: 2rem;
35
+ margin-top: 2rem;
36
+ }
37
+
38
+ h1:first-child {
39
+ margin-top: 0rem;
40
+ }
41
+
42
+ a,
43
+ a:visited {
44
+ color: #1d7195;
45
+ }
46
+
47
+ a:hover,
48
+ a:focus {
49
+ color: #15516a;
50
+ }
51
+
52
+ button,
53
+ a.button {
54
+ -webkit-appearance: none;
55
+ -moz-appearance: none;
56
+ appearance: none;
57
+ cursor: inherit;
58
+ font-size: 1rem;
59
+ line-height: 1.5;
60
+ border: 1px solid #aaa;
61
+ border-radius: 5px;
62
+ padding: 0.5em 0.75em;
63
+ background: #e8e8e8;
64
+ box-shadow: inset 0px 0.75em 0.75em rgba(255, 255, 255, 0.25);
65
+ -webkit-text-decoration: none;
66
+ text-decoration: none;
67
+ }
68
+
69
+ button.primary, a.button.primary {
70
+ background: #111;
71
+ border-color: #111;
72
+ color: #fff;
73
+ box-shadow: inset 0px 0.75em 0.75em rgba(255, 255, 255, 0.2);
74
+ }
75
+
76
+ button.primary:hover, a.button.primary:hover {
77
+ box-shadow: inset 0px 0.75em 0.75em rgba(255, 255, 255, 0.1);
78
+ }
79
+
80
+ .buttons,
81
+ table {
82
+ margin: 1.5rem 0;
83
+ }
84
+
85
+ .buttons:last-child, table:last-child {
86
+ margin-bottom: 0px;
87
+ }
88
+
89
+ table {
90
+ border-collapse: collapse;
91
+ border-top: 1px solid #ddd;
92
+ }
93
+
94
+ table th,
95
+ table td {
96
+ text-align: left;
97
+ padding: 0.5rem 1rem 0.5rem 0;
98
+ border-bottom: 1px solid #ddd;
99
+ }
@@ -13,7 +13,7 @@ form {
13
13
  gap: 4px;
14
14
  flex-wrap: wrap;
15
15
  .time {
16
- width: 4rem;
16
+ width: 5rem;
17
17
  min-width: 2rem;
18
18
  }
19
19
  }
@@ -259,6 +259,8 @@ form.button-to {
259
259
  display: flex;
260
260
  flex-wrap: wrap;
261
261
  gap: 0.25rem;
262
+ margin-top: 2rem;
263
+ @mixin child-margins;
262
264
  }
263
265
 
264
266
  .inline-form {
@@ -0,0 +1,90 @@
1
+ body,
2
+ input,
3
+ textarea,
4
+ a.button,
5
+ a.button:visited,
6
+ button {
7
+ font-family: -apple-system, BlinkMacSystemFont, Calibri, sans-serif;
8
+ color: #111;
9
+ }
10
+
11
+ body {
12
+ font-size: 1rem;
13
+ padding: 2rem;
14
+ }
15
+
16
+ h1,
17
+ h2,
18
+ h3,
19
+ h4,
20
+ h5,
21
+ h6,
22
+ ul,
23
+ ol,
24
+ p {
25
+ max-width: 80ch;
26
+ margin: 0 0 1rem 0;
27
+ &:last-child {
28
+ margin-bottom: 0rem;
29
+ }
30
+ }
31
+
32
+ h1 {
33
+ font-size: 2rem;
34
+ margin-top: 2rem;
35
+ &:first-child {
36
+ margin-top: 0rem;
37
+ }
38
+ }
39
+
40
+ a,
41
+ a:visited {
42
+ color: #1d7195;
43
+ }
44
+ a:hover,
45
+ a:focus {
46
+ color: #15516a;
47
+ }
48
+
49
+ button,
50
+ a.button {
51
+ appearance: none;
52
+ cursor: inherit;
53
+ font-size: 1rem;
54
+ line-height: 1.5;
55
+ border: 1px solid #aaa;
56
+ border-radius: 5px;
57
+ padding: 0.5em 0.75em;
58
+ background: #e8e8e8;
59
+ box-shadow: inset 0px 0.75em 0.75em rgba(255, 255, 255, 0.25);
60
+ text-decoration: none;
61
+
62
+ &.primary {
63
+ background: #111;
64
+ border-color: #111;
65
+ color: #fff;
66
+ box-shadow: inset 0px 0.75em 0.75em rgba(255, 255, 255, 0.2);
67
+ &:hover {
68
+ box-shadow: inset 0px 0.75em 0.75em rgba(255, 255, 255, 0.1);
69
+ }
70
+ }
71
+ }
72
+
73
+ .buttons,
74
+ table {
75
+ margin: 1.5rem 0;
76
+ &:last-child {
77
+ margin-bottom: 0px;
78
+ }
79
+ }
80
+
81
+ table {
82
+ border-collapse: collapse;
83
+ border-top: 1px solid #ddd;
84
+ th,
85
+ td {
86
+ text-align: left;
87
+ padding: 0.5rem 1rem 0.5rem 0;
88
+ border-bottom: 1px solid #ddd;
89
+ }
90
+ }
@@ -100,7 +100,7 @@ module Admin
100
100
  render json: ::Admin::PageResource.new(
101
101
  page,
102
102
  params: { user: current_user }
103
- )
103
+ ), status: page.valid? ? :ok : :unprocessable_entity
104
104
  end
105
105
  end
106
106
  end
@@ -17,7 +17,7 @@ module PagesCore
17
17
  page_file = args.detect { |a| a.is_a?(PageFile) }
18
18
  return attachment_path(page_file.attachment) if page_file
19
19
 
20
- super(*args)
20
+ super
21
21
  end
22
22
 
23
23
  private
@@ -82,6 +82,11 @@ export default function PageForm(props: Props) {
82
82
  });
83
83
  };
84
84
 
85
+ const clearDeletedObjects = () => {
86
+ filesState.setDeleted([]);
87
+ imagesState.setDeleted([]);
88
+ };
89
+
85
90
  const handleSubmit = (evt: React.MouseEvent) => {
86
91
  evt.preventDefault();
87
92
  let method = postJson;
@@ -105,6 +110,7 @@ export default function PageForm(props: Props) {
105
110
  if (response.errors && response.errors.length > 0) {
106
111
  errorToast("A validation error prevented the page from being saved.");
107
112
  } else {
113
+ clearDeletedObjects();
108
114
  noticeToast("Your changes were saved");
109
115
  }
110
116
  })
@@ -12,10 +12,15 @@ function getPosition<T>(draggable: Drag.Draggable<T>) {
12
12
  }
13
13
 
14
14
  function hideDraggable<T>(
15
- draggable: Drag.Draggable<T> | null,
16
- callback: () => Drag.Draggable<T>[]
15
+ draggable: Drag.Item<T> | null,
16
+ callback: () => Drag.Item<T>[]
17
17
  ) {
18
- if (draggable && "ref" in draggable && draggable.ref.current) {
18
+ if (
19
+ draggable &&
20
+ draggable !== "Files" &&
21
+ "ref" in draggable &&
22
+ draggable.ref.current
23
+ ) {
19
24
  const prevDisplay = draggable.ref.current.style.display;
20
25
  draggable.ref.current.style.display = "none";
21
26
  const result = callback();
@@ -42,7 +42,7 @@ function mousePosition(evt: AnyTouchEvent): Drag.Position {
42
42
  if ("touches" in evt && evt.type == "touchmove") {
43
43
  x = evt.touches[0].clientX;
44
44
  y = evt.touches[0].clientY;
45
- } else if (evt instanceof MouseEvent) {
45
+ } else if ("clientX" in evt && "clientY" in evt) {
46
46
  x = evt.clientX;
47
47
  y = evt.clientY;
48
48
  }
@@ -2,23 +2,19 @@
2
2
 
3
3
  class AdminMailer < ApplicationMailer
4
4
  default from: proc { "\"Pages\" <no-reply@anyone.no>" }
5
- layout false
5
+ layout "pages_core/mailer"
6
6
 
7
7
  def account_recovery(user, url)
8
8
  @user = user
9
9
  @url = url
10
- mail(
11
- to: @user.email,
12
- subject: "Recover your account on #{PagesCore.config(:site_name)}"
13
- )
10
+ mail(to: @user.email,
11
+ subject: "Recover your account on #{PagesCore.config(:site_name)}")
14
12
  end
15
13
 
16
14
  def invite(invite, url)
17
15
  @invite = invite
18
16
  @url = url
19
- mail(
20
- to: @invite.email,
21
- subject: "#{PagesCore.config(:site_name)} has invited you to Pages"
22
- )
17
+ mail(to: @invite.email,
18
+ subject: "#{PagesCore.config(:site_name)} has invited you to Pages")
23
19
  end
24
20
  end
@@ -30,7 +30,7 @@ class Autopublisher
30
30
  end
31
31
 
32
32
  def due_pages
33
- queued_pages.where("published_at < ?", (Time.now.utc + 2.minutes))
33
+ queued_pages.where(published_at: ...(Time.now.utc + 2.minutes))
34
34
  end
35
35
  end
36
36
  end
@@ -33,6 +33,11 @@ module PagesCore
33
33
  super.in_locale(locale)
34
34
  end
35
35
 
36
+ def page_files_attributes=(attrs)
37
+ ids = page_files.map(&:id)
38
+ super(attrs.reject { |a| a["_destroy"] && ids.exclude?(a["id"]) })
39
+ end
40
+
36
41
  def files
37
42
  page_files
38
43
  end
@@ -10,7 +10,7 @@ module PagesCore
10
10
  before_validation :ensure_ends_at
11
11
 
12
12
  scope :upcoming, -> { where("ends_at > ?", Time.zone.now) }
13
- scope :past, -> { where("ends_at <= ?", Time.zone.now) }
13
+ scope :past, -> { where(ends_at: ..Time.zone.now) }
14
14
  scope :with_dates, -> { where.not(starts_at: nil) }
15
15
  end
16
16
 
@@ -57,13 +57,13 @@ module PagesCore
57
57
  # Finds the page's next sibling by date. Returns nil if there
58
58
  # isn't one.
59
59
  def next_sibling_by_date
60
- siblings_by_date.where("starts_at >= ?", starts_at)&.first
60
+ siblings_by_date.where(starts_at: starts_at..)&.first
61
61
  end
62
62
 
63
63
  # Finds the page's previous sibling by date. Returns nil if
64
64
  # there isn't one.
65
65
  def previous_sibling_by_date
66
- siblings_by_date.where("starts_at < ?", starts_at)&.last
66
+ siblings_by_date.where(starts_at: ...starts_at)&.last
67
67
  end
68
68
 
69
69
  def upcoming?
@@ -19,11 +19,11 @@ module PagesCore
19
19
 
20
20
  after_save :update_primary_image
21
21
 
22
- accepts_nested_attributes_for :page_images,
23
- reject_if: proc { |a|
24
- a["image_id"].blank?
25
- },
26
- allow_destroy: true
22
+ accepts_nested_attributes_for(
23
+ :page_images,
24
+ reject_if: proc { |a| a["image_id"].blank? },
25
+ allow_destroy: true
26
+ )
27
27
  end
28
28
 
29
29
  def image?
@@ -42,6 +42,11 @@ module PagesCore
42
42
  super.in_locale(locale)
43
43
  end
44
44
 
45
+ def page_images_attributes=(attrs)
46
+ ids = page_images.map(&:id)
47
+ super(attrs.reject { |a| a["_destroy"] && ids.exclude?(a["id"]) })
48
+ end
49
+
45
50
  private
46
51
 
47
52
  def update_primary_image
@@ -4,15 +4,12 @@ module PagesCore
4
4
  module Taggable
5
5
  extend ActiveSupport::Concern
6
6
 
7
- attr_accessor :new_tags
8
-
9
7
  included do
10
8
  has_many :taggings,
11
9
  as: :taggable,
12
10
  dependent: :destroy,
13
11
  inverse_of: :taggable
14
12
  has_many :tags, through: :taggings
15
- after_save :update_taggings
16
13
  end
17
14
 
18
15
  module ClassMethods
@@ -32,7 +29,7 @@ module PagesCore
32
29
  end
33
30
 
34
31
  def tag_with(*list)
35
- @new_tags = Tag.parse(list)
32
+ self.tags = Tag.parse(list).map { |n| Tag.find_or_initialize_by(name: n) }
36
33
  end
37
34
 
38
35
  def tag_with!(*list)
@@ -48,21 +45,7 @@ module PagesCore
48
45
  end
49
46
 
50
47
  def tag_names
51
- @new_tags || tags.by_name.map(&:name)
52
- end
53
-
54
- private
55
-
56
- def update_taggings
57
- return unless new_tags
58
-
59
- Tag.transaction do
60
- taggings.includes(:tag).where.not(tag: { name: new_tags }).destroy_all
61
- new_tags.map { |n| Tag.find_or_create_by(name: n) }
62
- .each { |t| taggings.create(tag: t) }
63
- end
64
-
65
- @new_tags = nil
48
+ tags.by_name.map(&:name)
66
49
  end
67
50
  end
68
51
  end
@@ -0,0 +1,20 @@
1
+ <h1>
2
+ Recover your account
3
+ </h1>
4
+ <p>
5
+ Hi, <%= @user.name %>!
6
+ </p>
7
+ <p>
8
+ We've received a request to recover your account on
9
+ <%= PagesCore.config(:site_name) %>.
10
+ </p>
11
+ <p>
12
+ Please click the following link to continue:<br>
13
+ <%= link_to(@url, @url) %>
14
+ </p>
15
+ <p>
16
+ The link will expire in 24 hours.
17
+ </p>
18
+ <p>
19
+ If you do not want to recover your password, please ignore this email.
20
+ </p>
@@ -0,0 +1,11 @@
1
+ <h1>
2
+ Welcome!
3
+ </h1>
4
+ <p>
5
+ <%= @invite.user.name %> has invited you to Pages on
6
+ <%= PagesCore.config(:site_name) %>.<br>
7
+ Click the button below to create your account and get started.
8
+ </p>
9
+ <div class="buttons">
10
+ <%= link_to("Create account", @url, class: "primary button") %>
11
+ </div>
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5
+ <%= stylesheet_link_tag "pages_core/mailer" %>
6
+ </head>
7
+
8
+ <body>
9
+ <%= yield %>
10
+ </body>
11
+ </html>
@@ -29,6 +29,7 @@ module PagesCore
29
29
  Rails.application.config.assets.precompile += %w[
30
30
  pages_core/admin-dist.js
31
31
  pages_core/admin.css
32
+ pages_core/mailer.css
32
33
  pages_core/fonts/*.ttf
33
34
  pages_core/fonts/*.woff2
34
35
  pages/favicon.gif
data/lib/pages_core.rb CHANGED
@@ -29,6 +29,7 @@ require "lograge"
29
29
  require "nokogiri"
30
30
  require "json"
31
31
  require "pg_search"
32
+ require "premailer/rails"
32
33
  require "progress_bar"
33
34
  require "rails_i18n"
34
35
  require "RedCloth"