pages_core 3.12.1 → 3.12.2

Sign up to get free protection for your applications and to get access to all the features.
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
- };