pages_core 3.7.0 → 3.8.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 (164) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -2
  3. data/app/assets/builds/pages_core/admin-dist.js +55 -0
  4. data/app/assets/stylesheets/pages/admin/components/image_editor.scss +1 -0
  5. data/app/assets/stylesheets/pages/admin/components/image_grid.scss +33 -5
  6. data/app/assets/stylesheets/pages/admin/components/layout.scss +2 -1
  7. data/app/assets/stylesheets/pages/admin/components/login.scss +6 -0
  8. data/app/assets/stylesheets/pages/admin/components/tabs.scss +5 -0
  9. data/app/assets/stylesheets/pages/admin/components/tag_editor.scss +13 -7
  10. data/app/assets/stylesheets/pages/admin/controllers/pages.scss +13 -5
  11. data/app/assets/stylesheets/pages/admin.scss +0 -1
  12. data/app/controller_dummies/admin/admin_controller.rb +1 -1
  13. data/app/controller_dummies/application_controller.rb +1 -1
  14. data/app/controller_dummies/attachments_controller.rb +1 -1
  15. data/app/controller_dummies/frontend_controller.rb +1 -1
  16. data/app/controller_dummies/images_controller.rb +1 -1
  17. data/app/controller_dummies/page_files_controller.rb +1 -1
  18. data/app/controller_dummies/pages_controller.rb +1 -1
  19. data/app/controller_dummies/sitemaps_controller.rb +1 -1
  20. data/app/controllers/admin/attachments_controller.rb +1 -1
  21. data/app/controllers/admin/images_controller.rb +10 -7
  22. data/app/controllers/concerns/pages_core/authentication.rb +9 -4
  23. data/app/controllers/concerns/pages_core/preview_pages_controller.rb +5 -0
  24. data/app/controllers/pages_core/frontend/pages_controller.rb +5 -2
  25. data/app/controllers/sessions_controller.rb +1 -1
  26. data/app/formatters/pages_core/link_renderer.rb +2 -2
  27. data/app/helpers/application_helper.rb +1 -1
  28. data/app/helpers/frontend_helper.rb +1 -1
  29. data/app/helpers/pages_core/admin/content_tabs_helper.rb +5 -2
  30. data/app/helpers/pages_core/admin/image_uploads_helper.rb +2 -3
  31. data/app/helpers/pages_core/admin/tag_editor_helper.rb +9 -39
  32. data/app/helpers/pages_core/head_tags_helper.rb +11 -20
  33. data/app/helpers/pages_core/open_graph_tags_helper.rb +1 -1
  34. data/app/javascript/admin-dist.js +2 -0
  35. data/app/javascript/components/Attachments/Attachment.jsx +121 -0
  36. data/app/javascript/components/Attachments/AttachmentEditor.jsx +116 -0
  37. data/app/javascript/components/Attachments/Placeholder.jsx +10 -0
  38. data/app/javascript/components/Attachments.jsx +165 -0
  39. data/app/{assets/javascripts/pages/admin/components/date_range_select.jsx → javascript/components/DateRangeSelect.jsx} +16 -5
  40. data/app/javascript/components/EditableImage.jsx +61 -0
  41. data/app/{assets/javascripts/pages/admin/components/file_upload_button.jsx → javascript/components/FileUploadButton.jsx} +11 -1
  42. data/app/{assets/javascripts/pages/admin/components/focal_point.jsx → javascript/components/ImageCropper/FocalPoint.jsx} +12 -1
  43. data/app/javascript/components/ImageCropper/Image.jsx +65 -0
  44. data/app/javascript/components/ImageCropper/Toolbar.jsx +73 -0
  45. data/app/javascript/components/ImageCropper/useCrop.js +199 -0
  46. data/app/javascript/components/ImageCropper.jsx +90 -0
  47. data/app/javascript/components/ImageEditor/Form.jsx +98 -0
  48. data/app/javascript/components/ImageEditor.jsx +62 -0
  49. data/app/javascript/components/ImageGrid/DragElement.jsx +30 -0
  50. data/app/javascript/components/ImageGrid/FilePlaceholder.jsx +9 -0
  51. data/app/javascript/components/ImageGrid/GridImage.jsx +103 -0
  52. data/app/javascript/components/ImageGrid/Placeholder.jsx +23 -0
  53. data/app/javascript/components/ImageGrid.jsx +257 -0
  54. data/app/javascript/components/ImageUploader.jsx +171 -0
  55. data/app/{assets/javascripts/pages/admin/components/modal.jsx → javascript/components/Modal.jsx} +13 -2
  56. data/app/javascript/components/ModalStore.jsx +12 -0
  57. data/app/{assets/javascripts/pages/admin/components/page_dates.jsx → javascript/components/PageDates.jsx} +11 -1
  58. data/app/{assets/javascripts/pages/admin/components/page_files.jsx → javascript/components/PageFiles.jsx} +11 -2
  59. data/app/{assets/javascripts/pages/admin/components/page_images.jsx → javascript/components/PageImages.jsx} +11 -2
  60. data/app/{assets/javascripts/pages/admin/components/page_tree_store.jsx → javascript/components/PageTree.jsx} +127 -137
  61. data/app/{assets/javascripts/pages/admin/components/page_tree.jsx → javascript/components/PageTreeDraggable.jsx} +35 -29
  62. data/app/{assets/javascripts/pages/admin/components/page_tree_node.jsx → javascript/components/PageTreeNode.jsx} +35 -20
  63. data/app/javascript/components/RichTextArea.jsx +213 -0
  64. data/app/javascript/components/RichTextToolbarButton.jsx +20 -0
  65. data/app/javascript/components/TagEditor/AddTagForm.jsx +42 -0
  66. data/app/javascript/components/TagEditor/Tag.jsx +32 -0
  67. data/app/javascript/components/TagEditor.jsx +61 -0
  68. data/app/javascript/components/Toast.jsx +72 -0
  69. data/app/javascript/components/ToastStore.jsx +14 -0
  70. data/app/javascript/components/drag/draggedOrder.js +51 -0
  71. data/app/javascript/components/drag/useDragCollection.js +84 -0
  72. data/app/javascript/components/drag/useDragUploader.js +112 -0
  73. data/app/javascript/components/drag/useDraggable.js +17 -0
  74. data/app/javascript/components/drag.js +6 -0
  75. data/app/javascript/components.js +14 -0
  76. data/app/javascript/controllers/EditPageController.js +20 -0
  77. data/app/javascript/controllers/LoginController.js +29 -0
  78. data/app/javascript/controllers/MainController.js +65 -0
  79. data/app/javascript/controllers/PageOptionsController.js +62 -0
  80. data/app/javascript/features/RichText.jsx +34 -0
  81. data/app/javascript/hooks.js +2 -0
  82. data/app/javascript/index.js +33 -0
  83. data/app/{assets/javascripts/pages/admin/lib/tree.jsx → javascript/lib/Tree.js} +55 -54
  84. data/app/javascript/lib/copyToClipboard.js +13 -0
  85. data/app/javascript/lib/readyHandler.js +22 -0
  86. data/app/javascript/lib/request.js +36 -0
  87. data/app/models/concerns/pages_core/page_model/images.rb +3 -1
  88. data/app/models/concerns/pages_core/page_model/searchable.rb +19 -0
  89. data/app/models/concerns/pages_core/searchable_document.rb +71 -0
  90. data/app/models/concerns/pages_core/taggable.rb +27 -12
  91. data/app/models/page.rb +2 -0
  92. data/app/models/page_exporter.rb +2 -2
  93. data/app/models/page_image.rb +0 -2
  94. data/app/models/role.rb +1 -1
  95. data/app/models/search_document.rb +72 -0
  96. data/app/models/tag.rb +1 -0
  97. data/app/models/user.rb +1 -1
  98. data/app/{serializers/admin/attachment_serializer.rb → resources/admin/attachment_resource.rb} +6 -5
  99. data/app/{serializers/admin/image_serializer.rb → resources/admin/image_resource.rb} +9 -9
  100. data/app/resources/admin/page_file_resource.rb +10 -0
  101. data/app/{serializers/admin/page_image_serializer.rb → resources/admin/page_image_resource.rb} +4 -2
  102. data/app/resources/export/attachment_resource.rb +10 -0
  103. data/app/resources/export/page_image_resource.rb +45 -0
  104. data/app/resources/export/page_resource.rb +42 -0
  105. data/app/{serializers/page_image_serializer.rb → resources/page_image_resource.rb} +8 -16
  106. data/app/resources/page_resource.rb +33 -0
  107. data/app/services/pages_core/destroy_invite_service.rb +2 -2
  108. data/app/services/pages_core/invite_service.rb +2 -2
  109. data/app/views/admin/pages/_edit_content.html.erb +1 -1
  110. data/app/views/admin/pages/_edit_files.html.erb +1 -5
  111. data/app/views/admin/pages/_edit_images.html.erb +1 -5
  112. data/app/views/admin/pages/_edit_options.html.erb +74 -55
  113. data/app/views/admin/pages/_form.html.erb +19 -0
  114. data/app/views/admin/pages/edit.html.erb +35 -61
  115. data/app/views/admin/pages/index.html.erb +0 -1
  116. data/app/views/admin/pages/new.html.erb +32 -32
  117. data/app/views/admin/users/_access_control.html.erb +5 -1
  118. data/app/views/admin/users/login.html.erb +12 -4
  119. data/app/views/feeds/pages.rss.builder +1 -2
  120. data/app/views/layouts/admin/_header.html.erb +1 -1
  121. data/app/views/layouts/admin/_page_header.html.erb +33 -0
  122. data/app/views/layouts/admin.html.erb +23 -42
  123. data/app/views/pages_core/_google_analytics.html.erb +8 -0
  124. data/db/migrate/20180625154059_enable_search_extensions.rb +10 -0
  125. data/db/migrate/20210209151400_create_search_configurations.rb +35 -0
  126. data/db/migrate/20210210235200_create_search_documents.rb +74 -0
  127. data/lib/pages_core/engine.rb +1 -5
  128. data/lib/pages_core/templates/block_configuration.rb +1 -1
  129. data/lib/pages_core/templates/configuration_handler.rb +1 -1
  130. data/lib/pages_core/version.rb +1 -1
  131. data/lib/pages_core.rb +3 -5
  132. data/lib/rails/generators/pages_core/frontend/frontend_generator.rb +0 -7
  133. data/lib/rails/generators/pages_core/install/templates/page_templates_initializer.rb +2 -2
  134. metadata +101 -115
  135. data/app/assets/javascripts/pages/admin/components/attachment.jsx +0 -130
  136. data/app/assets/javascripts/pages/admin/components/attachment_editor.jsx +0 -131
  137. data/app/assets/javascripts/pages/admin/components/attachments.jsx +0 -211
  138. data/app/assets/javascripts/pages/admin/components/drag_uploader.jsx +0 -174
  139. data/app/assets/javascripts/pages/admin/components/editable_image.jsx +0 -57
  140. data/app/assets/javascripts/pages/admin/components/grid_image.jsx +0 -124
  141. data/app/assets/javascripts/pages/admin/components/image_editor.jsx +0 -496
  142. data/app/assets/javascripts/pages/admin/components/image_grid.jsx +0 -306
  143. data/app/assets/javascripts/pages/admin/components/image_uploader.jsx +0 -176
  144. data/app/assets/javascripts/pages/admin/components/modal_store.jsx +0 -20
  145. data/app/assets/javascripts/pages/admin/components/rich_text_area.jsx +0 -64
  146. data/app/assets/javascripts/pages/admin/components/rich_text_toolbar.jsx +0 -91
  147. data/app/assets/javascripts/pages/admin/components/toast.jsx +0 -34
  148. data/app/assets/javascripts/pages/admin/components/toast_store.jsx +0 -52
  149. data/app/assets/javascripts/pages/admin/components.jsx +0 -2
  150. data/app/assets/javascripts/pages/admin/features/content_tabs.jsx +0 -72
  151. data/app/assets/javascripts/pages/admin/features/edit_page.jsx +0 -97
  152. data/app/assets/javascripts/pages/admin/features/rich_text.jsx +0 -14
  153. data/app/assets/javascripts/pages/admin/features/tag_editor.jsx +0 -160
  154. data/app/assets/javascripts/pages/admin.jsx +0 -17
  155. data/app/assets/javascripts/pages/login_form.jsx +0 -21
  156. data/app/serializers/admin/page_file_serializer.rb +0 -8
  157. data/app/serializers/page_export_serializer.rb +0 -32
  158. data/app/serializers/page_file_export_serializer.rb +0 -6
  159. data/app/serializers/page_image_export_serializer.rb +0 -42
  160. data/app/serializers/page_serializer.rb +0 -23
  161. data/app/views/layouts/admin/_analytics.html.erb +0 -16
  162. data/lib/rails/generators/pages_core/frontend/templates/application.js.erb +0 -15
  163. data/vendor/assets/javascripts/ReactCrop.min.js +0 -1
  164. data/vendor/assets/javascripts/reflux.min.js +0 -1
@@ -0,0 +1,17 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ export default function useDraggable(draggable, startDrag) {
4
+ const ref = useRef();
5
+
6
+ const handleDrag = (evt) => {
7
+ evt.preventDefault();
8
+ evt.stopPropagation();
9
+ startDrag(evt, draggable);
10
+ };
11
+
12
+ useEffect(() => {
13
+ draggable.ref.current = ref.current;
14
+ }, []);
15
+
16
+ return { ref: ref, onDragStart: handleDrag, draggable: true };
17
+ }
@@ -0,0 +1,6 @@
1
+ export { default as useDragCollection,
2
+ createDraggable } from "./drag/useDragCollection";
3
+ export { default as useDragUploader } from "./drag/useDragUploader";
4
+ export { default as useDraggable } from "./drag/useDraggable";
5
+ export { default as draggedOrder,
6
+ collectionOrder } from "./drag/draggedOrder";
@@ -0,0 +1,14 @@
1
+ export { default as Attachments } from "./components/Attachments";
2
+ export { default as DateRangeSelect } from "./components/DateRangeSelect";
3
+ export { default as EditableImage } from "./components/EditableImage";
4
+ export { default as ImageCropper } from "./components/ImageCropper";
5
+ export { default as ImageGrid } from "./components/ImageGrid";
6
+ export { default as ImageUploader } from "./components/ImageUploader";
7
+ export { default as Modal } from "./components/Modal";
8
+ export { default as PageDates } from "./components/PageDates";
9
+ export { default as PageFiles } from "./components/PageFiles";
10
+ export { default as PageImages } from "./components/PageImages";
11
+ export { default as PageTree } from "./components/PageTree";
12
+ export { default as RichTextArea } from "./components/RichTextArea";
13
+ export { default as TagEditor } from "./components/TagEditor";
14
+ export { default as Toast } from "./components/Toast";
@@ -0,0 +1,20 @@
1
+ import { Controller } from "stimulus";
2
+
3
+ export default class EditPageController extends Controller {
4
+ static get targets() {
5
+ return ["form"];
6
+ }
7
+
8
+ preview() {
9
+ const form = this.formTarget;
10
+ const prevAction = form.action;
11
+ const prevTarget = form.target;
12
+
13
+ form.target = "_blank";
14
+ form.action = form.dataset.previewUrl;
15
+ form.submit();
16
+
17
+ form.action = prevAction;
18
+ form.target = prevTarget;
19
+ }
20
+ }
@@ -0,0 +1,29 @@
1
+ import { Controller } from "stimulus";
2
+
3
+ export default class LoginController extends Controller {
4
+ static get targets() {
5
+ return ["tab"];
6
+ }
7
+
8
+ connect() {
9
+ if (this.tabTargets.length > 0) {
10
+ this.showTab(this.tabTargets[0].dataset.tab);
11
+ }
12
+ }
13
+
14
+
15
+ changeTab(evt) {
16
+ evt.preventDefault();
17
+ this.showTab(evt.target.dataset.tab);
18
+ }
19
+
20
+ showTab(tab) {
21
+ this.tabTargets.forEach((t) => {
22
+ if (t.dataset.tab == tab) {
23
+ t.classList.remove("hidden");
24
+ } else {
25
+ t.classList.add("hidden");
26
+ }
27
+ });
28
+ }
29
+ }
@@ -0,0 +1,65 @@
1
+ import { Controller } from "stimulus";
2
+
3
+ export default class MainController extends Controller {
4
+ static get targets() {
5
+ return ["tab", "link"];
6
+ }
7
+
8
+ connect() {
9
+ let tabs = this.tabNames();
10
+ if (tabs.length > 0) {
11
+ let initTab = null;
12
+ const tabExpression = /#(.*)$/;
13
+
14
+ if (document.location.toString().match(tabExpression)) {
15
+ let id = document.location.toString().match(tabExpression)[1];
16
+ if (tabs.indexOf(id) !== -1) {
17
+ initTab = id;
18
+ }
19
+ }
20
+
21
+ this.showTab(initTab || tabs[0]);
22
+ }
23
+
24
+ window.addEventListener("popstate", this.stateHandler.bind(this));
25
+ }
26
+
27
+ disconnect() {
28
+ window.removeEventListener("popstate", this.stateHandler.bind(this));
29
+ }
30
+
31
+ stateHandler(evt) {
32
+ if (evt.state && evt.state.tabId) {
33
+ this.showTab(evt.state.tabId);
34
+ }
35
+ }
36
+
37
+ changeTab(evt) {
38
+ evt.preventDefault();
39
+ const tab = evt.target.dataset.tab;
40
+ this.showTab(tab);
41
+ history.pushState({ tabId: tab }, "", `${window.location.pathname}#${tab}`);
42
+ }
43
+
44
+ showTab(tab) {
45
+ this.linkTargets.forEach((l) => {
46
+ if (l.dataset.tab == tab) {
47
+ l.classList.add("current");
48
+ } else {
49
+ l.classList.remove("current");
50
+ }
51
+ });
52
+
53
+ this.tabTargets.forEach((t) => {
54
+ if (t.dataset.tab == tab) {
55
+ t.classList.remove("hidden");
56
+ } else {
57
+ t.classList.add("hidden");
58
+ }
59
+ });
60
+ }
61
+
62
+ tabNames () {
63
+ return this.linkTargets.map((l) => l.dataset.tab);
64
+ }
65
+ }
@@ -0,0 +1,62 @@
1
+ import { Controller } from "stimulus";
2
+
3
+ export default class PageOptionsController extends Controller {
4
+ static get targets() {
5
+ return ["advancedOptions", "autoPublishNotice", "published", "publishedDate"];
6
+ }
7
+
8
+ connect() {
9
+ this.checkAutoPublish();
10
+ this.checkPublishedDate();
11
+ }
12
+
13
+ checkAutoPublish() {
14
+ if (this.publishedDate() > new Date()) {
15
+ this.show(this.autoPublishNoticeTarget);
16
+ } else {
17
+ this.hide(this.autoPublishNoticeTarget);
18
+ }
19
+ }
20
+
21
+ checkPublishedDate() {
22
+ if (this.publishedTarget.value == 2) {
23
+ this.show(this.publishedDateTarget);
24
+ } else {
25
+ this.hide(this.publishedDateTarget);
26
+ }
27
+ }
28
+
29
+ publishedDate() {
30
+ const lookup = (name) => {
31
+ return this.publishedDateTarget.getElementsByClassName(name)[0].value;
32
+ };
33
+ return new Date(
34
+ lookup("year"),
35
+ (lookup("month") - 1),
36
+ lookup("day"),
37
+ lookup("hour"),
38
+ lookup("minute")
39
+ );
40
+ }
41
+
42
+ show(elem) {
43
+ elem.classList.add("show");
44
+ }
45
+
46
+ hide(elem) {
47
+ elem.classList.remove("show");
48
+ }
49
+
50
+ toggle(elem) {
51
+ if (elem.classList.contains("show")) {
52
+ this.hide(elem);
53
+ } else {
54
+ this.show(elem);
55
+ }
56
+ }
57
+
58
+ toggleAdvancedOptions(evt) {
59
+ evt.preventDefault();
60
+ this.toggle(this.advancedOptionsTarget);
61
+ }
62
+ }
@@ -0,0 +1,34 @@
1
+ import React from "react";
2
+ import ReactDOM from "react-dom";
3
+ import RichTextArea from "../components/RichTextArea";
4
+ import readyHandler from "../lib/readyHandler";
5
+
6
+ class RichText {
7
+ apply () {
8
+ const elems = document.querySelectorAll("textarea.rich");
9
+ elems.forEach((elem) => {
10
+ this.enhance(elem);
11
+ });
12
+ }
13
+
14
+ enhance (elem) {
15
+ const container = document.createElement("div");
16
+ elem.parentNode.appendChild(container);
17
+ ReactDOM.render(
18
+ <RichTextArea value={elem.value}
19
+ name={elem.name}
20
+ rows={elem.rows}
21
+ id={elem.id} />,
22
+ container
23
+ );
24
+ elem.parentNode.removeChild(elem);
25
+ }
26
+
27
+ start () {
28
+ readyHandler.ready(() => {
29
+ this.apply();
30
+ });
31
+ }
32
+ }
33
+
34
+ export default new RichText();
@@ -0,0 +1,2 @@
1
+ export { useCrop, cropParams } from "./components/ImageCropper";
2
+ export * from "./components/drag";
@@ -0,0 +1,33 @@
1
+ import Rails from "@rails/ujs";
2
+ import { Application } from "stimulus";
3
+ require("react_ujs");
4
+
5
+ import * as Components from "./components";
6
+
7
+ import EditPageController from "./controllers/EditPageController";
8
+ import MainController from "./controllers/MainController";
9
+ import LoginController from "./controllers/LoginController";
10
+ import PageOptionsController from "./controllers/PageOptionsController";
11
+
12
+ import RichText from "./features/RichText";
13
+
14
+ export function registerComponent(name, component) {
15
+ window[name] = component;
16
+ }
17
+
18
+ export default function startPages () {
19
+ Rails.start();
20
+ for (var name in Components) {
21
+ registerComponent(name, Components[name]);
22
+ }
23
+ RichText.start();
24
+
25
+ const application = Application.start();
26
+ application.register("edit-page", EditPageController);
27
+ application.register("main", MainController);
28
+ application.register("login", LoginController);
29
+ application.register("page-options", PageOptionsController);
30
+ }
31
+
32
+ export * from "./components";
33
+ export * from "./hooks";
@@ -1,55 +1,56 @@
1
1
  /*
2
- Based on
3
-
4
- js-tree by Wang Zuo
5
- https://github.com/wangzuo/js-tree
6
-
7
- react-ui-tree
8
- https://github.com/pqx/react-ui-tree
9
-
10
- The MIT License (MIT)
11
-
12
- Copyright (c) 2015 Wang Zuo
13
-
14
- Permission is hereby granted, free of charge, to any person obtaining a copy
15
- of this software and associated documentation files (the "Software"), to deal
16
- in the Software without restriction, including without limitation the rights
17
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
- copies of the Software, and to permit persons to whom the Software is
19
- furnished to do so, subject to the following conditions:
20
-
21
- The above copyright notice and this permission notice shall be included in
22
- all copies or substantial portions of the Software.
23
-
24
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
- SOFTWARE.
31
-
32
- Copyright (c) 2015 pqx Limited
33
-
34
- Permission is hereby granted, free of charge, to any person obtaining a copy
35
- of this software and associated documentation files (the "Software"), to deal
36
- in the Software without restriction, including without limitation the rights
37
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
38
- copies of the Software, and to permit persons to whom the Software is
39
- furnished to do so, subject to the following conditions:
40
-
41
- The above copyright notice and this permission notice shall be included in
42
- all copies or substantial portions of the Software.
43
-
44
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
45
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
46
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
47
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
48
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
49
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
50
- SOFTWARE.
51
- */
52
- class Tree {
2
+ Based on
3
+
4
+ js-tree by Wang Zuo
5
+ https://github.com/wangzuo/js-tree
6
+
7
+ react-ui-tree
8
+ https://github.com/pqx/react-ui-tree
9
+
10
+ The MIT License (MIT)
11
+
12
+ Copyright (c) 2015 Wang Zuo
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in
22
+ all copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+
32
+ Copyright (c) 2015 pqx Limited
33
+
34
+ Permission is hereby granted, free of charge, to any person obtaining a copy
35
+ of this software and associated documentation files (the "Software"), to deal
36
+ in the Software without restriction, including without limitation the rights
37
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
38
+ copies of the Software, and to permit persons to whom the Software is
39
+ furnished to do so, subject to the following conditions:
40
+
41
+ The above copyright notice and this permission notice shall be included in
42
+ all copies or substantial portions of the Software.
43
+
44
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
45
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
46
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
47
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
48
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
49
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
50
+ SOFTWARE.
51
+ */
52
+
53
+ export default class Tree {
53
54
  constructor(obj) {
54
55
  this.cnt = 1;
55
56
  this.obj = obj || { children: [] };
@@ -72,7 +73,7 @@ class Tree {
72
73
 
73
74
  function walk(objs, parent) {
74
75
  var children = [];
75
- objs.forEach(function(obj, i) {
76
+ objs.forEach(function(obj) {
76
77
  var index = {};
77
78
  index.id = self.cnt;
78
79
  index.node = obj;
@@ -276,7 +277,7 @@ class Tree {
276
277
 
277
278
  getParent(id) {
278
279
  var indexes = this.indexes;
279
- if (indexes.hasOwnProperty(id)) {
280
+ if (Object.prototype.hasOwnProperty.call(indexes, id)) {
280
281
  return this.getIndex(indexes[id].parent);
281
282
  }
282
283
  }
@@ -284,7 +285,7 @@ class Tree {
284
285
  getNodeByTop(top) {
285
286
  var indexes = this.indexes;
286
287
  for(var id in indexes) {
287
- if (indexes.hasOwnProperty(id)) {
288
+ if (Object.prototype.hasOwnProperty.call(indexes, id)) {
288
289
  if(indexes[id].top === top) {
289
290
  return indexes[id];
290
291
  }
@@ -0,0 +1,13 @@
1
+ export function copySupported () {
2
+ return document.queryCommandSupported &&
3
+ document.queryCommandSupported("copy");
4
+ }
5
+
6
+ export default function copyToClipboard (str) {
7
+ const el = document.createElement("textarea");
8
+ el.value = str;
9
+ document.body.appendChild(el);
10
+ el.select();
11
+ document.execCommand("copy");
12
+ document.body.removeChild(el);
13
+ }
@@ -0,0 +1,22 @@
1
+ let readyHandlers = [];
2
+
3
+ const handleState = () => {
4
+ if (["interactive", "complete"].indexOf(document.readyState) > -1) {
5
+ while(readyHandlers.length > 0) {
6
+ (readyHandlers.shift())();
7
+ }
8
+ }
9
+ };
10
+
11
+ class ReadyHandler {
12
+ constructor () {
13
+ document.onreadystatechange = handleState;
14
+ }
15
+
16
+ ready (handler) {
17
+ readyHandlers.push(handler);
18
+ handleState();
19
+ }
20
+ }
21
+
22
+ export default new ReadyHandler();
@@ -0,0 +1,36 @@
1
+ export function csrfToken() {
2
+ return document.querySelector("[name=csrf-token]").content;
3
+ }
4
+
5
+ function jsonFetchOptions() {
6
+ return({ method: "POST",
7
+ headers: { "Content-Type": "application/json; charset=utf-8",
8
+ "X-CSRF-Token": csrfToken() } });
9
+ }
10
+
11
+ export async function postJson(url, data) {
12
+ const options = { ...jsonFetchOptions(), method: "POST" };
13
+ if (data) {
14
+ options.body = JSON.stringify(data);
15
+ }
16
+ const response = await fetch(url, options);
17
+ return response.json();
18
+ }
19
+
20
+ export async function putJson(url, data) {
21
+ const options = { ...jsonFetchOptions(), method: "PUT" };
22
+ if (data) {
23
+ options.body = JSON.stringify(data);
24
+ }
25
+ const response = await fetch(url, options);
26
+ return response.json();
27
+ }
28
+
29
+ export async function post(url, data) {
30
+ const response = await fetch(url, {
31
+ method: "POST",
32
+ body: data,
33
+ headers: { "X-CSRF-Token": csrfToken() }
34
+ });
35
+ return response.json();
36
+ }
@@ -8,7 +8,9 @@ module PagesCore
8
8
  included do
9
9
  belongs_to_image :image, optional: true
10
10
  belongs_to_image :meta_image, class_name: "Image", optional: true
11
- has_many :page_images, -> { order("position") }, inverse_of: :page
11
+ has_many :page_images, -> { order("position") },
12
+ inverse_of: :page,
13
+ dependent: :destroy
12
14
  has_many(
13
15
  :images,
14
16
  -> { where(page_images: { primary: false }).order("position") },
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PagesCore
4
+ module PageModel
5
+ module Searchable
6
+ extend ActiveSupport::Concern
7
+
8
+ def search_document_attributes
9
+ super.merge(
10
+ published: published?,
11
+ name: name,
12
+ description: try(&:meta_description?) ? meta_description : excerpt,
13
+ # content: "",
14
+ tags: tag_names.join(" ")
15
+ )
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PagesCore
4
+ module SearchableDocument
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ has_many :search_documents, as: :searchable, dependent: :destroy
9
+ after_save :update_search_documents!
10
+ end
11
+
12
+ class Indexer
13
+ attr_reader :record
14
+
15
+ def initialize(record)
16
+ @record = record
17
+ end
18
+
19
+ class << self
20
+ def index_all!(scope)
21
+ scope.all.find_each { |r| new(r).index! }
22
+ end
23
+ end
24
+
25
+ def index!
26
+ SearchDocument.transaction do
27
+ record.search_documents.where.not(locale: locales).destroy_all
28
+ locales.each do |locale|
29
+ update_index(
30
+ locale,
31
+ localized_record(locale).search_document_attributes
32
+ )
33
+ end
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def locales
40
+ if record.respond_to?(:locales)
41
+ record.locales
42
+ elsif PagesCore.config.locales
43
+ PagesCore.config.locales.keys
44
+ else
45
+ [I18n.default_locale]
46
+ end
47
+ end
48
+
49
+ def localized_record(locale)
50
+ return record unless record.respond_to?(:localize)
51
+
52
+ record.localize(locale)
53
+ end
54
+
55
+ def update_index(locale, attrs)
56
+ record.search_documents.create_or_find_by!(locale: locale).update(attrs)
57
+ end
58
+ end
59
+
60
+ def search_document_attributes
61
+ return {} unless respond_to?(:localized_attributes)
62
+
63
+ content = localized_attributes.keys.map { |a| localizer.get(a) }.join(" ")
64
+ { content: content }
65
+ end
66
+
67
+ def update_search_documents!
68
+ PagesCore::SearchableDocument::Indexer.new(self).index!
69
+ end
70
+ end
71
+ end
@@ -4,25 +4,27 @@ module PagesCore
4
4
  module Taggable
5
5
  extend ActiveSupport::Concern
6
6
 
7
+ attr_accessor :new_tags
8
+
7
9
  included do
8
10
  has_many :taggings,
9
11
  as: :taggable,
10
12
  dependent: :destroy,
11
13
  inverse_of: :taggable
12
14
  has_many :tags, through: :taggings
15
+ after_save :update_taggings
13
16
  end
14
17
 
15
18
  module ClassMethods
16
19
  def tagged_with(*tags)
17
- all
18
- .includes(:tags)
19
- .where(tags: { name: Tag.parse(tags) })
20
- .references(:tags)
20
+ all.includes(:tags)
21
+ .where(tags: { name: Tag.parse(tags) })
22
+ .references(:tags)
21
23
  end
22
24
  end
23
25
 
24
26
  def serialized_tags
25
- tags.order("name ASC").map(&:name).to_json
27
+ tag_names.to_json
26
28
  end
27
29
 
28
30
  def serialized_tags=(json)
@@ -30,12 +32,11 @@ module PagesCore
30
32
  end
31
33
 
32
34
  def tag_with(*list)
33
- Tag.transaction do
34
- taggings.destroy_all
35
- Tag.parse(list).each do |name|
36
- Tag.find_or_create_by(name: name).on(self)
37
- end
38
- end
35
+ @new_tags = Tag.parse(list)
36
+ end
37
+
38
+ def tag_with!(*list)
39
+ update(tag_list: list)
39
40
  end
40
41
 
41
42
  def tag_list=(tag_list)
@@ -47,7 +48,21 @@ module PagesCore
47
48
  end
48
49
 
49
50
  def tag_names
50
- tags.map(&:name).sort
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
51
66
  end
52
67
  end
53
68
  end