pages_core 3.12.1 → 3.12.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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/app/assets/builds/pages_core/admin-dist.js +59 -14
  4. data/app/assets/builds/pages_core/admin-dist.js.map +7 -0
  5. data/app/assets/builds/pages_core/admin.css +39 -0
  6. data/app/assets/stylesheets/pages_core/admin/components/search.css +27 -0
  7. data/app/assets/stylesheets/pages_core/admin/controllers/pages.css +6 -0
  8. data/app/controllers/admin/pages_controller.rb +12 -11
  9. data/app/controllers/concerns/pages_core/rss_controller.rb +17 -1
  10. data/app/controllers/pages_core/admin_controller.rb +6 -0
  11. data/app/controllers/pages_core/frontend/pages_controller.rb +9 -5
  12. data/app/controllers/pages_core/sitemaps_controller.rb +3 -5
  13. data/app/helpers/admin/pages_helper.rb +32 -0
  14. data/app/javascript/admin-dist.ts +2 -0
  15. data/app/javascript/components/Attachments/{Attachment.jsx → Attachment.tsx} +42 -33
  16. data/app/javascript/components/Attachments/{AttachmentEditor.jsx → AttachmentEditor.tsx} +23 -23
  17. data/app/javascript/components/{EditableImage.jsx → EditableImage.tsx} +27 -24
  18. data/app/javascript/components/{FileUploadButton.jsx → FileUploadButton.tsx} +15 -16
  19. data/app/javascript/components/ImageCropper/FocalPoint.tsx +94 -0
  20. data/app/javascript/components/ImageCropper/{Image.jsx → Image.tsx} +13 -14
  21. data/app/javascript/components/ImageCropper/{Toolbar.jsx → Toolbar.tsx} +16 -12
  22. data/app/javascript/components/ImageCropper/{useCrop.js → useCrop.ts} +80 -37
  23. data/app/javascript/components/{ImageCropper.jsx → ImageCropper.tsx} +17 -15
  24. data/app/javascript/components/ImageEditor/{Form.jsx → Form.tsx} +24 -23
  25. data/app/javascript/components/{ImageEditor.jsx → ImageEditor.tsx} +17 -15
  26. data/app/javascript/components/ImageGrid/{DragElement.jsx → DragElement.tsx} +12 -10
  27. data/app/javascript/components/ImageGrid/{GridImage.jsx → GridImage.tsx} +40 -30
  28. data/app/javascript/components/ImageGrid/{Placeholder.jsx → Placeholder.tsx} +5 -6
  29. data/app/javascript/components/ImageGrid.jsx +3 -4
  30. data/app/javascript/components/{ImageUploader.jsx → ImageUploader.tsx} +46 -41
  31. data/app/javascript/components/Modal.tsx +48 -0
  32. data/app/javascript/components/PageImages.tsx +28 -0
  33. data/app/javascript/components/{PageTreeDraggable.jsx → PageTree/Draggable.tsx} +79 -57
  34. data/app/javascript/components/{PageTreeNode.jsx → PageTree/Node.tsx} +79 -70
  35. data/app/javascript/components/PageTree/types.ts +15 -0
  36. data/app/javascript/components/PageTree.tsx +206 -0
  37. data/app/javascript/components/RichTextToolbarButton.tsx +17 -0
  38. data/app/javascript/components/TagEditor/{AddTagForm.jsx → AddTagForm.tsx} +9 -10
  39. data/app/javascript/components/TagEditor/{Tag.jsx → Tag.tsx} +8 -9
  40. data/app/javascript/components/{TagEditor.jsx → TagEditor.tsx} +12 -13
  41. data/app/javascript/components/Toast.tsx +61 -0
  42. data/app/javascript/components/drag/{draggedOrder.js → draggedOrder.ts} +22 -12
  43. data/app/javascript/components/drag/types.ts +28 -0
  44. data/app/javascript/components/drag/{useDragCollection.js → useDragCollection.ts} +40 -22
  45. data/app/javascript/components/drag/{useDragUploader.js → useDragUploader.ts} +34 -25
  46. data/app/javascript/components/drag/useDraggable.ts +21 -0
  47. data/app/javascript/components/{drag.js → drag.ts} +1 -0
  48. data/app/javascript/controllers/{EditPageController.js → EditPageController.ts} +3 -1
  49. data/app/javascript/controllers/{LoginController.js → LoginController.ts} +7 -3
  50. data/app/javascript/controllers/{MainController.js → MainController.ts} +19 -14
  51. data/app/javascript/{index.js → index.ts} +8 -7
  52. data/app/javascript/lib/{Tree.js → Tree.ts} +106 -85
  53. data/app/javascript/lib/{copyToClipboard.js → copyToClipboard.ts} +1 -1
  54. data/app/javascript/lib/{readyHandler.js → readyHandler.ts} +4 -2
  55. data/app/javascript/lib/{request.js → request.ts} +11 -5
  56. data/app/javascript/stores/useModalStore.ts +15 -0
  57. data/app/javascript/stores/useToastStore.ts +26 -0
  58. data/app/javascript/stores.ts +2 -0
  59. data/app/javascript/types.ts +30 -0
  60. data/app/policies/page_policy.rb +4 -0
  61. data/app/views/admin/calendars/_sidebar.html.erb +3 -0
  62. data/app/views/admin/news/_sidebar.html.erb +3 -0
  63. data/app/views/admin/pages/_list_item.html.erb +4 -22
  64. data/app/views/admin/pages/_search_bar.html.erb +12 -0
  65. data/app/views/admin/pages/index.html.erb +3 -0
  66. data/app/views/admin/pages/search.html.erb +54 -0
  67. data/app/views/feeds/pages.rss.builder +3 -9
  68. data/config/routes.rb +1 -0
  69. data/lib/pages_core/configuration/pages.rb +0 -1
  70. data/lib/rails/generators/pages_core/frontend/frontend_generator.rb +33 -17
  71. data/lib/rails/generators/pages_core/frontend/templates/application.html.erb +0 -1
  72. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/gridOverlay.ts +40 -0
  73. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/responsiveEmbeds.ts +68 -0
  74. data/lib/rails/generators/pages_core/frontend/templates/postcss.config.js +17 -0
  75. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/application.postcss.css +4 -0
  76. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/base.css +24 -0
  77. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/layout.css +21 -0
  78. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/config.css +5 -0
  79. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/animation.css +5 -0
  80. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/colors.css +18 -0
  81. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/fonts.css +6 -0
  82. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/grid.css +65 -0
  83. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/typography.css +131 -0
  84. data/lib/rails/generators/pages_core/install/templates/pages_initializer.rb +0 -3
  85. metadata +68 -62
  86. data/app/javascript/admin-dist.js +0 -2
  87. data/app/javascript/components/ImageCropper/FocalPoint.jsx +0 -93
  88. data/app/javascript/components/Modal.jsx +0 -59
  89. data/app/javascript/components/PageImages.jsx +0 -25
  90. data/app/javascript/components/PageTree.jsx +0 -196
  91. data/app/javascript/components/RichTextToolbarButton.jsx +0 -20
  92. data/app/javascript/components/Toast.jsx +0 -72
  93. data/app/javascript/components/drag/useDraggable.js +0 -17
  94. data/app/javascript/stores/ModalStore.jsx +0 -12
  95. data/app/javascript/stores/ToastStore.jsx +0 -14
  96. data/app/javascript/stores.js +0 -2
  97. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/GridOverlay.js +0 -66
  98. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/ResponsiveEmbeds.js +0 -72
  99. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/application.sass.scss +0 -15
  100. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/base.scss +0 -12
  101. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/config.scss +0 -26
  102. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/breakpoints.scss +0 -42
  103. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/clearfix.scss +0 -7
  104. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/fonts.scss +0 -32
  105. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/grid.scss +0 -168
  106. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/grid_overlay.scss +0 -44
  107. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/colors.scss +0 -8
  108. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/typography.scss +0 -90
  109. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/vendor/normalize.css +0 -349
  110. /data/app/javascript/components/Attachments/{Placeholder.jsx → Placeholder.tsx} +0 -0
  111. /data/app/javascript/components/ImageGrid/{FilePlaceholder.jsx → FilePlaceholder.tsx} +0 -0
  112. /data/app/javascript/{components.js → components.ts} +0 -0
  113. /data/app/javascript/{hooks.js → hooks.ts} +0 -0
@@ -8690,6 +8690,38 @@ body.modal > .wrapper {
8690
8690
  margin: 20px -2px;
8691
8691
  }
8692
8692
 
8693
+ .search-bar {
8694
+ display: flex;
8695
+ }
8696
+
8697
+ .search-bar input {
8698
+ flex: 1 1 auto;
8699
+ border-right: none;
8700
+ max-width: 600px;
8701
+ min-width: 50px;
8702
+ }
8703
+
8704
+ .search-bar input:focus {
8705
+ z-index: 20;
8706
+ }
8707
+
8708
+ .search-bar button {
8709
+ border-top-left-radius: 0px;
8710
+ border-bottom-left-radius: 0px;
8711
+ white-space: nowrap;
8712
+ }
8713
+
8714
+ main .search-bar {
8715
+ background: #f8f8f8;
8716
+ background: var(--background-disabled-color);
8717
+ padding: 16px 32px;
8718
+ margin: 0px 0px 16px 0px;
8719
+ }
8720
+
8721
+ .sidebar .search-bar {
8722
+ margin-top: 16px;
8723
+ }
8724
+
8693
8725
  .sidebar {
8694
8726
  background: #f8f8f8;
8695
8727
  background: var(--background-disabled-color);
@@ -9131,6 +9163,13 @@ td.drag-handle {
9131
9163
  font-weight: normal;
9132
9164
  }
9133
9165
 
9166
+ .news-item-list .autopublish-notice, .calendar-item-list .autopublish-notice {
9167
+ font-size: 0.75rem;
9168
+ font-weight: normal;
9169
+ color: #666;
9170
+ color: var(--text-light-color);
9171
+ }
9172
+
9134
9173
  .news-item-list .draft,
9135
9174
  .calendar-item-list .draft,
9136
9175
  .news-item-list .hidden,
@@ -0,0 +1,27 @@
1
+ .search-bar {
2
+ display: flex;
3
+ & input {
4
+ flex: 1 1 auto;
5
+ border-right: none;
6
+ max-width: 600px;
7
+ min-width: 50px;
8
+ &:focus {
9
+ z-index: 20;
10
+ }
11
+ }
12
+ & button {
13
+ border-top-left-radius: 0px;
14
+ border-bottom-left-radius: 0px;
15
+ white-space: nowrap;
16
+ }
17
+ }
18
+
19
+ main .search-bar {
20
+ background: var(--background-disabled-color);
21
+ padding: 16px 32px;
22
+ margin: 0px 0px 16px 0px;
23
+ }
24
+
25
+ .sidebar .search-bar {
26
+ margin-top: 16px;
27
+ }
@@ -122,6 +122,12 @@ td.drag-handle {
122
122
  }
123
123
  }
124
124
 
125
+ & .autopublish-notice {
126
+ font-size: 0.75rem;
127
+ font-weight: normal;
128
+ color: var(--text-light-color);
129
+ }
130
+
125
131
  & .draft,
126
132
  & .hidden,
127
133
  & .autopublish,
@@ -9,8 +9,6 @@ module Admin
9
9
 
10
10
  require_authorization
11
11
 
12
- helper_method :page_json
13
-
14
12
  def index
15
13
  @pages = Page.admin_list(@locale)
16
14
  end
@@ -19,6 +17,15 @@ module Admin
19
17
  @pages = Page.deleted.by_updated_at.in_locale(@locale)
20
18
  end
21
19
 
20
+ def search
21
+ return if search_query.blank?
22
+
23
+ @search_documents =
24
+ SearchDocument.where(searchable_type: "Page")
25
+ .search(search_query, locale: @locale)
26
+ .paginate(per_page: 50, page: params[:page])
27
+ end
28
+
22
29
  def show
23
30
  redirect_to edit_admin_page_url(@locale, @page)
24
31
  end
@@ -26,19 +33,14 @@ module Admin
26
33
  def new
27
34
  build_params = params[:page] ? page_params : nil
28
35
  @page = build_page(@locale, build_params)
29
- @page.parent = if params[:parent]
30
- Page.find(params[:parent])
31
- elsif @news_pages
32
- @news_pages.first
33
- end
36
+ @page.parent = Page.find_by(id: params[:parent])
34
37
  end
35
38
 
36
39
  def edit; end
37
40
 
38
41
  def create
39
- @page = build_page(@locale, page_params, param_categories)
42
+ @page = build_page(@locale, page_params, param_categories).tap(&:save)
40
43
  if @page.valid?
41
- @page.save
42
44
  respond_with_page(@page) do
43
45
  redirect_to(edit_admin_page_url(@locale, @page))
44
46
  end
@@ -103,8 +105,7 @@ module Admin
103
105
  def param_categories
104
106
  return [] unless params[:category]
105
107
 
106
- params.permit(category: {})[:category]
107
- .to_hash
108
+ params.permit(category: {})[:category].to_hash
108
109
  .map { |id, _| Category.find(id) }
109
110
  end
110
111
 
@@ -11,10 +11,26 @@ module PagesCore
11
11
  Page.where(parent_page_id: feeds)
12
12
  .order("published_at DESC")
13
13
  .published
14
- .limit(20)
15
14
  .localized(locale)
16
15
  end
17
16
 
17
+ def per_page_rss_param(default = 20, max = 1000)
18
+ return default unless params[:per_page].is_a?(String)
19
+
20
+ params[:per_page].to_i.clamp(1, max)
21
+ end
22
+
23
+ def render_page_rss(page, pagination_page = 1)
24
+ if page.feed_enabled?
25
+ render_rss(page.pages.paginate(per_page: per_page_rss_param,
26
+ page: pagination_page)
27
+ .includes(:image, :author),
28
+ title: page.name)
29
+ else
30
+ render_error 404
31
+ end
32
+ end
33
+
18
34
  def render_rss(items, title: nil)
19
35
  @title = PagesCore.config.site_name
20
36
  @title += ": #{title}" if title
@@ -13,6 +13,8 @@ module PagesCore
13
13
 
14
14
  layout "admin"
15
15
 
16
+ helper_method :search_query
17
+
16
18
  class << self
17
19
  # Get name of class with in lowercase, with underscores.
18
20
  def underscore
@@ -30,6 +32,10 @@ module PagesCore
30
32
 
31
33
  protected
32
34
 
35
+ def search_query
36
+ params[:q] || ""
37
+ end
38
+
33
39
  def set_i18n_locale
34
40
  I18n.locale = :en
35
41
  end
@@ -20,7 +20,10 @@ module PagesCore
20
20
  def index
21
21
  respond_to do |format|
22
22
  format.html { render_published_page(root_pages.try(&:first)) }
23
- format.rss { render_rss(all_feed_items) }
23
+ format.rss do
24
+ render_rss(all_feed_items.paginate(per_page: per_page_rss_param,
25
+ page: page_param))
26
+ end
24
27
  end
25
28
  end
26
29
 
@@ -28,10 +31,7 @@ module PagesCore
28
31
  respond_to do |format|
29
32
  format.html { render_published_page(@page) }
30
33
  format.json { render json: PageResource.new(@page) }
31
- format.rss do
32
- render_rss(@page.pages.limit(20).includes(:image, :author),
33
- title: @page.name)
34
- end
34
+ format.rss { render_page_rss(@page, page_param) }
35
35
  end
36
36
  end
37
37
 
@@ -64,6 +64,10 @@ module PagesCore
64
64
  super
65
65
  end
66
66
 
67
+ def page_param
68
+ params[:page].is_a?(String) ? params[:page] : 1
69
+ end
70
+
67
71
  def page_template(page)
68
72
  if PagesCore::Templates.names.include?(page.template)
69
73
  page.template
@@ -20,10 +20,8 @@ module PagesCore
20
20
  end
21
21
 
22
22
  def format_record(record)
23
- {
24
- loc: record_url(record),
25
- lastmod: format_time(record.updated_at)
26
- }
23
+ { loc: record_url(record),
24
+ lastmod: format_time(record.updated_at) }
27
25
  end
28
26
 
29
27
  def formatted_entries
@@ -46,7 +44,7 @@ module PagesCore
46
44
  ([Page.root.try(:localize, I18n.default_locale)] +
47
45
  locales.flat_map do |locale|
48
46
  Page.published.localized(locale).includes(:parent)
49
- end).compact.uniq
47
+ end).compact
50
48
  end
51
49
 
52
50
  def page_record_url(record)
@@ -4,6 +4,15 @@ module Admin
4
4
  module PagesHelper
5
5
  include PagesCore::Admin::PageBlocksHelper
6
6
 
7
+ def autopublish_notice(page)
8
+ return unless page.autopublish?
9
+
10
+ tag.div(class: "autopublish-notice") do
11
+ safe_join(["This page will be published",
12
+ tag.b(publish_time(page.published_at))], " ")
13
+ end
14
+ end
15
+
7
16
  def available_templates_for_select
8
17
  PagesCore::Templates.names.collect do |template|
9
18
  if template == "index"
@@ -30,6 +39,14 @@ module Admin
30
39
  ([page.author] + User.activated).uniq
31
40
  end
32
41
 
42
+ def page_list_row(page, &block)
43
+ classes = [page.status_label.downcase]
44
+ classes << "autopublish" if page.autopublish?
45
+ classes << "pinned" if page.pinned?
46
+
47
+ tag.tr(capture(&block), class: classes.join(" "))
48
+ end
49
+
33
50
  def page_name(page, options = {})
34
51
  page_names = if options[:include_parents]
35
52
  page.self_and_ancestors.reverse
@@ -42,6 +59,21 @@ module Admin
42
59
  )
43
60
  end
44
61
 
62
+ def page_published_status(page)
63
+ return page_published_date(page) if page.published?
64
+ return tag.em("Not published") if page.status_label == "Published"
65
+
66
+ tag.em(page.status_label)
67
+ end
68
+
69
+ def page_published_date(page)
70
+ if page.published_at.year == Time.zone.now.year
71
+ l(page.published_at, format: :pages_date)
72
+ else
73
+ l(page.published_at, format: :pages_full)
74
+ end
75
+ end
76
+
45
77
  def publish_time(time)
46
78
  if time.year != Time.zone.now.year
47
79
  time.strftime("on %b %d %Y at %H:%M")
@@ -0,0 +1,2 @@
1
+ import startPages from "./index";
2
+ startPages();
@@ -1,28 +1,48 @@
1
1
  import React from "react";
2
- import PropTypes from "prop-types";
3
2
  import copyToClipboard from "../../lib/copyToClipboard";
4
3
  import AttachmentEditor from "./AttachmentEditor";
5
- import ModalStore from "../../stores/ModalStore";
6
- import ToastStore from "../../stores/ToastStore";
4
+ import useModalStore from "../../stores/useModalStore";
5
+ import useToastStore from "../../stores/useToastStore";
6
+ import { AttachmentResource, Locale } from "../../types";
7
7
 
8
- import { useDraggable } from "../drag";
8
+ import { useDraggable, Draggable } from "../drag";
9
9
 
10
- export default function Attachment(props) {
10
+ interface Record {
11
+ id: number | null,
12
+ attachment: AttachmentResource,
13
+ uploading: boolean
14
+ }
15
+
16
+ interface AttachmentProps {
17
+ attributeName: string,
18
+ placeholder: boolean,
19
+ draggable: { record: Record },
20
+ locale: string,
21
+ locales: { [index: string]: Locale },
22
+ deleteRecord: () => void,
23
+ showEmbed: boolean,
24
+ position: number,
25
+ onUpdate: (localizations: Record<string, Record<string, string>>) => void,
26
+ startDrag: (evt: Event, draggable: Draggable) => void
27
+ }
28
+
29
+ export default function Attachment(props: AttachmentProps) {
11
30
  const { attributeName, draggable, locales, locale } = props;
12
31
  const { record } = draggable;
13
32
  const { attachment, uploading } = record;
14
33
 
34
+ const openModal = useModalStore((state) => state.open);
35
+ const notice = useToastStore((state) => state.notice);
36
+
15
37
  const listeners = useDraggable(draggable, props.startDrag);
16
38
 
17
- const copyEmbed = (evt) => {
39
+ const copyEmbed = (evt: Event) => {
18
40
  evt.preventDefault();
19
41
  copyToClipboard(`[attachment:${attachment.id}]`);
20
- ToastStore.dispatch({
21
- type: "NOTICE", message: "Embed code copied to clipboard"
22
- });
42
+ notice("Embed code copied to clipboard");
23
43
  };
24
44
 
25
- const deleteRecord = (evt) => {
45
+ const deleteRecord = (evt: Event) => {
26
46
  evt.preventDefault();
27
47
  if (props.deleteRecord) {
28
48
  props.deleteRecord();
@@ -43,15 +63,15 @@ export default function Attachment(props) {
43
63
  return null;
44
64
  };
45
65
 
46
- const editAttachment = (evt) => {
66
+ const editAttachment = (evt: Event) => {
47
67
  evt.preventDefault();
48
- ModalStore.dispatch({
49
- type: "OPEN",
50
- payload: <AttachmentEditor attachment={attachment}
51
- locale={locale}
52
- locales={locales}
53
- onUpdate={props.onUpdate} />
54
- });
68
+ openModal(
69
+ <AttachmentEditor
70
+ attachment={attachment}
71
+ locale={locale}
72
+ locales={locales}
73
+ onUpdate={props.onUpdate} />
74
+ );
55
75
  };
56
76
 
57
77
  const classes = ["attachment"];
@@ -64,7 +84,10 @@ export default function Attachment(props) {
64
84
 
65
85
  const icon = uploading ? "cloud-arrow-up" : "paperclip";
66
86
 
67
- const localeDir = (locales && locales[locale] && locales[locale].dir) || "ltr";
87
+ let localeDir = "ltr";
88
+ if (locale in locales && locales[locale].dir) {
89
+ localeDir = locales[locale].dir;
90
+ }
68
91
 
69
92
  return (
70
93
  <div className={classes.join(" ")}
@@ -108,17 +131,3 @@ export default function Attachment(props) {
108
131
  </div>
109
132
  );
110
133
  }
111
-
112
- Attachment.propTypes = {
113
- locale: PropTypes.string,
114
- locales: PropTypes.object,
115
- draggable: PropTypes.object,
116
- deleteRecord: PropTypes.func,
117
- startDrag: PropTypes.func,
118
- showEmbed: PropTypes.bool,
119
- onUpdate: PropTypes.func,
120
- attributeName: PropTypes.string,
121
- placeholder: PropTypes.bool,
122
- position: PropTypes.number,
123
- ref: PropTypes.object
124
- };
@@ -1,11 +1,18 @@
1
- import React, { useState } from "react";
2
- import PropTypes from "prop-types";
1
+ import React, { ChangeEvent, useState } from "react";
3
2
  import copyToClipboard, { copySupported } from "../../lib/copyToClipboard";
4
- import ModalStore from "../../stores/ModalStore";
5
- import ToastStore from "../../stores/ToastStore";
3
+ import useModalStore from "../../stores/useModalStore";
4
+ import useToastStore from "../../stores/useToastStore";
5
+ import { AttachmentResource, Locale } from "../../types";
6
6
  import { putJson } from "../../lib/request";
7
7
 
8
- export default function AttachmentEditor(props) {
8
+ interface AttachmentEditorProps {
9
+ attachment: AttachmentResource,
10
+ locale: string,
11
+ locales: { [index: string]: Locale },
12
+ onUpdate: (localizations: Record<string, Record<string, string>>) => void
13
+ }
14
+
15
+ export default function AttachmentEditor(props: AttachmentEditorProps) {
9
16
  const { attachment, locales } = props;
10
17
 
11
18
  const [locale, setLocale] = useState(props.locale);
@@ -14,7 +21,10 @@ export default function AttachmentEditor(props) {
14
21
  description: attachment.description || {},
15
22
  });
16
23
 
17
- const updateLocalization = (name) => (evt) => {
24
+ const notice = useToastStore((state) => state.notice);
25
+ const closeModal = useModalStore((state) => state.close);
26
+
27
+ const updateLocalization = (name: "name" | "description") => (evt: ChangeEvent<HTMLInputElement>) => {
18
28
  setLocalizations({
19
29
  ...localizations,
20
30
  [name]: { ...localizations[name],
@@ -22,27 +32,24 @@ export default function AttachmentEditor(props) {
22
32
  });
23
33
  };
24
34
 
25
- const copyEmbedCode = (evt) => {
35
+ const copyEmbedCode = (evt: Event) => {
26
36
  evt.preventDefault();
27
37
  copyToClipboard(`[attachment:${attachment.id}]`);
28
- ToastStore.dispatch({
29
- type: "NOTICE", message: "Embed code copied to clipboard"
30
- });
38
+ notice("Embed code copied to clipboard");
31
39
  };
32
40
 
33
- const save = (evt) => {
41
+ const save = (evt: Event) => {
34
42
  evt.preventDefault();
35
43
  evt.stopPropagation();
36
44
 
37
- let data = { ...localizations };
45
+ const data = { ...localizations };
38
46
 
39
- putJson(`/admin/attachments/${attachment.id}`,
40
- { attachment: data });
47
+ void putJson(`/admin/attachments/${attachment.id}`, { attachment: data });
41
48
 
42
49
  if (props.onUpdate) {
43
50
  props.onUpdate(data);
44
51
  }
45
- ModalStore.dispatch({ type: "CLOSE" });
52
+ closeModal();
46
53
  };
47
54
 
48
55
  const inputDir = (locales && locales[locale] && locales[locale].dir) || "ltr";
@@ -106,7 +113,7 @@ export default function AttachmentEditor(props) {
106
113
  <button onClick={save}>
107
114
  Save
108
115
  </button>
109
- <button onClick={() => ModalStore.dispatch({ type: "CLOSE" })}>
116
+ <button onClick={closeModal}>
110
117
  Cancel
111
118
  </button>
112
119
  </div>
@@ -114,10 +121,3 @@ export default function AttachmentEditor(props) {
114
121
  </div>
115
122
  );
116
123
  }
117
-
118
- AttachmentEditor.propTypes = {
119
- attachment: PropTypes.object,
120
- locale: PropTypes.string,
121
- locales: PropTypes.object,
122
- onUpdate: PropTypes.func
123
- };
@@ -1,20 +1,33 @@
1
1
  import React, { useState } from "react";
2
- import PropTypes from "prop-types";
3
2
  import ImageEditor from "./ImageEditor";
4
- import ModalStore from "../stores/ModalStore";
3
+ import useModalStore from "../stores/useModalStore";
5
4
 
6
- export default function EditableImage(props) {
5
+ import { Locale, ImageResource } from "../types";
6
+
7
+ interface EditableImageProps {
8
+ image: ImageResource,
9
+ src: string,
10
+ caption: boolean,
11
+ locale: string,
12
+ locales: Record<string, Locale>,
13
+ width: number,
14
+ onUpdate?: (newImage: ImageResource, src: string) => void
15
+ }
16
+
17
+ export default function EditableImage(props: EditableImageProps) {
7
18
  const [image, setImage] = useState(props.image);
8
19
  const [src, setSrc] = useState(props.src);
9
20
 
21
+ const openModal = useModalStore((state) => state.open);
22
+
10
23
  const height = () => {
11
24
  const width = image.crop_width || image.real_width;
12
25
  const height = image.crop_height || image.real_height;
13
26
  return Math.round((height / width) * props.width);
14
27
  };
15
28
 
16
- const updateImage = (updatedImage, src) => {
17
- let newImage = { ...image, ...updatedImage };
29
+ const updateImage = (updatedImage: ImageResource, src: string) => {
30
+ const newImage = { ...image, ...updatedImage };
18
31
  setSrc(src);
19
32
  setImage(newImage);
20
33
  if (props.onUpdate) {
@@ -22,16 +35,16 @@ export default function EditableImage(props) {
22
35
  }
23
36
  };
24
37
 
25
- const handleClick = (evt) => {
38
+ const handleClick = (evt: Event) => {
26
39
  evt.preventDefault();
27
- ModalStore.dispatch({
28
- type: "OPEN",
29
- payload: <ImageEditor image={image}
30
- caption={props.caption}
31
- locale={props.locale}
32
- locales={props.locales}
33
- onUpdate={updateImage} />
34
- });
40
+ openModal(
41
+ <ImageEditor
42
+ image={image}
43
+ caption={props.caption}
44
+ locale={props.locale}
45
+ locales={props.locales}
46
+ onUpdate={updateImage} />
47
+ );
35
48
  };
36
49
 
37
50
  const altWarning = !image.alternative[props.locale];
@@ -49,13 +62,3 @@ export default function EditableImage(props) {
49
62
  </div>
50
63
  );
51
64
  }
52
-
53
- EditableImage.propTypes = {
54
- image: PropTypes.object,
55
- src: PropTypes.string,
56
- caption: PropTypes.bool,
57
- locale: PropTypes.string,
58
- locales: PropTypes.object,
59
- width: PropTypes.number,
60
- onUpdate: PropTypes.func
61
- };
@@ -1,13 +1,19 @@
1
- import React, { useRef } from "react";
2
- import PropTypes from "prop-types";
1
+ import React, { ChangeEvent, useRef } from "react";
3
2
 
4
- export default function FileUploadButton(props) {
5
- const inputRef = useRef();
3
+ interface FileUploadButtonProps {
4
+ callback: (files: File[]) => void,
5
+ type: string,
6
+ multiple: boolean,
7
+ multiline: boolean
8
+ }
9
+
10
+ export default function FileUploadButton(props: FileUploadButtonProps) {
11
+ const inputRef = useRef<HTMLInputElement>();
6
12
 
7
- const handleChange = (evt) => {
8
- let fileList = evt.target.files;
9
- let files = [];
10
- for (var i = 0; i < fileList.length; i++) {
13
+ const handleChange = (evt: ChangeEvent<HTMLInputElement>) => {
14
+ const fileList = evt.target.files;
15
+ const files: File[] = [];
16
+ for (let i = 0; i < fileList.length; i++) {
11
17
  files.push(fileList[i]);
12
18
  }
13
19
  if (files.length > 0) {
@@ -15,7 +21,7 @@ export default function FileUploadButton(props) {
15
21
  }
16
22
  };
17
23
 
18
- const triggerDialog = (evt) => {
24
+ const triggerDialog = (evt: Event) => {
19
25
  evt.preventDefault();
20
26
  inputRef.current.click();
21
27
  };
@@ -38,10 +44,3 @@ export default function FileUploadButton(props) {
38
44
  </div>
39
45
  );
40
46
  }
41
-
42
- FileUploadButton.propTypes = {
43
- callback: PropTypes.func,
44
- type: PropTypes.string,
45
- multiple: PropTypes.bool,
46
- multiline: PropTypes.bool
47
- };