pages_core 3.14.0 → 3.15.1
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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/app/assets/builds/fonts/6569749d.ttf +0 -0
- data/app/assets/builds/fonts/7b7db107.woff2 +0 -0
- data/app/assets/builds/fonts/921961e9.woff2 +0 -0
- data/app/assets/builds/fonts/ee32bc60.ttf +0 -0
- data/app/assets/builds/pages_core/admin-dist.js +19 -8
- data/app/assets/builds/pages_core/admin-dist.js.map +4 -4
- data/app/assets/builds/pages_core/admin.css +699 -394
- data/app/assets/builds/pages_core/mailer.css +99 -0
- data/app/assets/fonts/Inter-Black.woff2 +0 -0
- data/app/assets/fonts/Inter-BlackItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-Bold.woff2 +0 -0
- data/app/assets/fonts/Inter-BoldItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-ExtraBold.woff2 +0 -0
- data/app/assets/fonts/Inter-ExtraBoldItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-ExtraLight.woff2 +0 -0
- data/app/assets/fonts/Inter-ExtraLightItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-Italic.woff2 +0 -0
- data/app/assets/fonts/Inter-Light.woff2 +0 -0
- data/app/assets/fonts/Inter-LightItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-Medium.woff2 +0 -0
- data/app/assets/fonts/Inter-MediumItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-Regular.woff2 +0 -0
- data/app/assets/fonts/Inter-SemiBold.woff2 +0 -0
- data/app/assets/fonts/Inter-SemiBoldItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-Thin.woff2 +0 -0
- data/app/assets/fonts/Inter-ThinItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Black.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-BlackItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Bold.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-BoldItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-ExtraBold.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-ExtraBoldItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-ExtraLight.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-ExtraLightItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Italic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Light.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-LightItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Medium.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-MediumItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Regular.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-SemiBold.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-SemiBoldItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Thin.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-ThinItalic.woff2 +0 -0
- data/app/assets/fonts/InterVariable-Italic.woff2 +0 -0
- data/app/assets/fonts/InterVariable.woff2 +0 -0
- data/app/assets/stylesheets/pages_core/admin/components/archive.css +1 -1
- data/app/assets/stylesheets/pages_core/admin/components/attachments.css +22 -34
- data/app/assets/stylesheets/pages_core/admin/components/base.css +1 -68
- data/app/assets/stylesheets/pages_core/admin/components/forms.css +109 -48
- data/app/assets/stylesheets/pages_core/admin/components/header.css +56 -58
- data/app/assets/stylesheets/pages_core/admin/components/image_editor.css +35 -24
- data/app/assets/stylesheets/pages_core/admin/components/image_grid.css +28 -27
- data/app/assets/stylesheets/pages_core/admin/components/image_uploader.css +5 -5
- data/app/assets/stylesheets/pages_core/admin/components/layout.css +7 -1
- data/app/assets/stylesheets/pages_core/admin/components/list_table.css +24 -15
- data/app/assets/stylesheets/pages_core/admin/components/page_tree.css +63 -104
- data/app/assets/stylesheets/pages_core/admin/components/pagination.css +12 -13
- data/app/assets/stylesheets/pages_core/admin/components/search.css +1 -16
- data/app/assets/stylesheets/pages_core/admin/components/sidebar.css +5 -11
- data/app/assets/stylesheets/pages_core/admin/components/tag_editor.css +22 -36
- data/app/assets/stylesheets/pages_core/admin/components/toast.css +1 -2
- data/app/assets/stylesheets/pages_core/admin/components/toolbar.css +10 -10
- data/app/assets/stylesheets/pages_core/admin/components/totp.css +1 -1
- data/app/assets/stylesheets/pages_core/admin/controllers/pages.css +37 -51
- data/app/assets/stylesheets/pages_core/admin/global/fonts.css +271 -0
- data/app/assets/stylesheets/pages_core/admin/global/typography.css +109 -0
- data/app/assets/stylesheets/pages_core/admin/vars.css +1 -3
- data/app/assets/stylesheets/pages_core/{admin.postcss.css → admin.css} +1 -0
- data/app/assets/stylesheets/pages_core/mailer.css +90 -0
- data/app/controllers/admin/account_recoveries_controller.rb +2 -2
- data/app/controllers/admin/pages_controller.rb +22 -42
- data/app/controllers/concerns/pages_core/error_reporting.rb +1 -1
- data/app/controllers/concerns/pages_core/page_parameters.rb +29 -0
- data/app/controllers/concerns/pages_core/policies_helper.rb +1 -1
- data/app/controllers/concerns/pages_core/preview_pages_controller.rb +20 -20
- data/app/controllers/pages_core/admin_controller.rb +0 -2
- data/app/controllers/pages_core/frontend/pages_controller.rb +2 -6
- data/app/formatters/pages_core/html_formatter.rb +2 -4
- data/app/helpers/admin/menu_helper.rb +5 -4
- data/app/helpers/admin/pages_helper.rb +1 -21
- data/app/helpers/pages_core/admin/admin_helper.rb +2 -3
- data/app/helpers/pages_core/admin/content_tabs_helper.rb +1 -2
- data/app/helpers/pages_core/admin/labelled_field_helper.rb +1 -1
- data/app/helpers/pages_core/attachments_helper.rb +1 -1
- data/app/helpers/pages_core/frontend_helper.rb +1 -1
- data/app/helpers/pages_core/images_helper.rb +10 -8
- data/app/helpers/pages_core/labelled_form_builder.rb +2 -7
- data/app/helpers/pages_core/page_path_helper.rb +1 -1
- data/app/javascript/components/Attachments/Attachment.tsx +20 -18
- data/app/javascript/components/Attachments/AttachmentEditor.tsx +11 -9
- data/app/javascript/components/{Attachments.jsx → Attachments/List.tsx} +58 -63
- data/app/javascript/components/Attachments/useAttachments.ts +15 -0
- data/app/javascript/components/Attachments.tsx +14 -0
- data/app/javascript/components/DateRangeSelect.tsx +105 -0
- data/app/javascript/components/DateTimeSelect.tsx +136 -0
- data/app/javascript/components/EditableImage.tsx +11 -9
- data/app/javascript/components/FileUploadButton.tsx +7 -7
- data/app/javascript/components/ImageCropper/FocalPoint.tsx +9 -12
- data/app/javascript/components/ImageCropper/Image.tsx +10 -8
- data/app/javascript/components/ImageCropper/Toolbar.tsx +11 -12
- data/app/javascript/components/ImageCropper/useCrop.ts +24 -53
- data/app/javascript/components/ImageCropper.tsx +10 -15
- data/app/javascript/components/ImageEditor/Form.tsx +12 -8
- data/app/javascript/components/ImageEditor.tsx +12 -7
- data/app/javascript/components/ImageGrid/DragElement.tsx +9 -12
- data/app/javascript/components/{ImageGrid.jsx → ImageGrid/Grid.tsx} +62 -71
- data/app/javascript/components/ImageGrid/GridImage.tsx +22 -23
- data/app/javascript/components/ImageGrid/Placeholder.tsx +2 -2
- data/app/javascript/components/ImageGrid/useImageGrid.ts +26 -0
- data/app/javascript/components/ImageGrid.tsx +15 -0
- data/app/javascript/components/ImageUploader.tsx +35 -22
- data/app/javascript/components/LabelledField.tsx +34 -0
- data/app/javascript/components/Modal.tsx +2 -2
- data/app/javascript/components/PageForm/Block.tsx +81 -0
- data/app/javascript/components/PageForm/Content.tsx +54 -0
- data/app/javascript/components/PageForm/Dates.tsx +66 -0
- data/app/javascript/components/PageForm/Files.tsx +28 -0
- data/app/javascript/components/PageForm/Form.tsx +41 -0
- data/app/javascript/components/PageForm/Images.tsx +28 -0
- data/app/javascript/components/PageForm/LocaleLinks.tsx +36 -0
- data/app/javascript/components/PageForm/Metadata.tsx +67 -0
- data/app/javascript/components/PageForm/Options.tsx +180 -0
- data/app/javascript/components/PageForm/PageDescription.tsx +48 -0
- data/app/javascript/components/PageForm/PathSegment.tsx +65 -0
- data/app/javascript/components/PageForm/TabPanel.tsx +21 -0
- data/app/javascript/components/PageForm/Tabs.tsx +33 -0
- data/app/javascript/components/PageForm/UnconfiguredContent.tsx +42 -0
- data/app/javascript/components/PageForm/pageParams.ts +95 -0
- data/app/javascript/components/PageForm/preview.ts +23 -0
- data/app/javascript/components/PageForm/usePage.ts +169 -0
- data/app/javascript/components/PageForm/useTabs.ts +46 -0
- data/app/javascript/components/PageForm.tsx +169 -0
- data/app/javascript/components/PageImages.tsx +7 -9
- data/app/javascript/components/PageTree/Draggable.tsx +40 -39
- data/app/javascript/components/PageTree/Node.tsx +62 -56
- data/app/javascript/components/PageTree/PageName.tsx +28 -0
- data/app/javascript/components/PageTree.tsx +65 -53
- data/app/javascript/components/{RichTextArea.jsx → RichTextArea.tsx} +98 -79
- data/app/javascript/components/RichTextToolbarButton.tsx +4 -6
- data/app/javascript/components/TagEditor/AddTagForm.tsx +19 -12
- data/app/javascript/components/TagEditor/Editor.tsx +32 -0
- data/app/javascript/components/TagEditor/Tag.tsx +6 -4
- data/app/javascript/components/TagEditor/useTags.ts +58 -0
- data/app/javascript/components/TagEditor.tsx +8 -58
- data/app/javascript/components/Toast.tsx +3 -3
- data/app/javascript/components/drag/draggedOrder.ts +22 -14
- data/app/javascript/components/drag/useDragCollection.ts +35 -30
- data/app/javascript/components/drag/useDragUploader.ts +32 -21
- data/app/javascript/components/drag/useDraggable.ts +7 -6
- data/app/javascript/components/drag.ts +0 -1
- data/app/javascript/components.ts +1 -3
- data/app/javascript/features/RichText.tsx +2 -3
- data/app/javascript/features/contentTabs.ts +79 -0
- data/app/javascript/index.ts +5 -12
- data/app/javascript/lib/Tree.ts +31 -45
- data/app/javascript/lib/request.ts +11 -11
- data/app/javascript/stores/useToastStore.ts +1 -1
- data/app/javascript/types/Attachments.ts +29 -0
- data/app/javascript/types/Crop.ts +36 -0
- data/app/javascript/types/Drag.ts +34 -0
- data/app/javascript/types/Images.ts +47 -0
- data/app/javascript/types/PageEditor.ts +26 -0
- data/app/javascript/types/Pages.ts +75 -0
- data/app/javascript/types/Tags.ts +9 -0
- data/app/javascript/types/Template.ts +24 -0
- data/app/javascript/types/Trees.ts +19 -0
- data/app/javascript/types.ts +2 -25
- data/app/mailers/admin_mailer.rb +5 -9
- data/app/models/attachment.rb +1 -1
- data/app/models/autopublisher.rb +1 -1
- data/app/models/concerns/pages_core/authenticable_user.rb +63 -0
- data/app/models/concerns/pages_core/emailable.rb +16 -0
- data/app/models/concerns/pages_core/page_model/dated_page.rb +3 -3
- data/app/models/concerns/pages_core/page_model/templateable.rb +2 -16
- data/app/models/concerns/pages_core/taggable.rb +2 -19
- data/app/models/invite.rb +2 -6
- data/app/models/otp_secret.rb +4 -4
- data/app/models/page.rb +0 -3
- data/app/models/user.rb +2 -46
- data/app/policies/page_policy.rb +6 -2
- data/app/resources/admin/page_resource.rb +95 -0
- data/app/resources/admin/page_tree_resource.rb +27 -0
- data/app/resources/admin/template_configuration_resource.rb +50 -0
- data/app/views/admin/news/_sidebar.html.erb +2 -4
- data/app/views/admin/news/index.html.erb +0 -1
- data/app/views/admin/pages/_form.html.erb +10 -30
- data/app/views/admin/pages/_search_bar.html.erb +1 -1
- data/app/views/admin/pages/edit.html.erb +1 -57
- data/app/views/admin/pages/index.html.erb +1 -1
- data/app/views/admin/pages/new.html.erb +1 -44
- data/app/views/admin/sessions/new.html.erb +9 -11
- data/app/views/admin/users/_access_control.html.erb +5 -1
- data/app/views/admin/users/_list.html.erb +12 -7
- data/app/views/admin_mailer/account_recovery.html.erb +20 -0
- data/app/views/admin_mailer/invite.html.erb +11 -0
- data/app/views/layouts/admin/_header.html.erb +2 -4
- data/app/views/layouts/admin/_page_header.html.erb +1 -2
- data/app/views/layouts/admin.html.erb +1 -1
- data/app/views/layouts/pages_core/mailer.html.erb +11 -0
- data/config/locales/en.yml +0 -4
- data/config/routes.rb +3 -7
- data/db/migrate/20240126160700_add_2fa_fields.rb +5 -1
- data/db/migrate/20240131140700_change_email_to_citext.rb +18 -0
- data/db/migrate/20240201160700_remove_persistent_data.rb +7 -0
- data/db/migrate/20240508145300_remove_categories.rb +21 -0
- data/lib/pages_core/configuration/base.rb +2 -2
- data/lib/pages_core/engine.rb +1 -0
- data/lib/pages_core/templates/configuration.rb +1 -1
- data/lib/pages_core/templates/configuration_proxy.rb +2 -2
- data/lib/pages_core/templates/template_configuration.rb +11 -1
- data/lib/pages_core/templates.rb +6 -4
- data/lib/pages_core/version.rb +1 -1
- data/lib/pages_core.rb +1 -0
- data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/gridOverlay.ts +6 -7
- data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/responsiveEmbeds.ts +17 -12
- data/lib/rails/generators/pages_core/rspec/rspec_generator.rb +0 -2
- data/lib/rails/generators/pages_core/rspec/templates/rails_helper.rb +3 -4
- metadata +119 -36
- data/app/assets/builds/fonts/2a3059ad.ttf +0 -0
- data/app/assets/builds/fonts/47262711.woff2 +0 -0
- data/app/assets/builds/fonts/500ddeb0.woff2 +0 -0
- data/app/assets/builds/fonts/81221036.ttf +0 -0
- data/app/assets/stylesheets/pages_core/admin/components/login.css +0 -27
- data/app/controllers/admin/categories_controller.rb +0 -56
- data/app/controllers/concerns/pages_core/admin/persistent_params.rb +0 -75
- data/app/helpers/pages_core/admin/page_blocks_helper.rb +0 -66
- data/app/helpers/pages_core/admin/page_json_helper.rb +0 -23
- data/app/javascript/components/DateRangeSelect.jsx +0 -225
- data/app/javascript/components/PageDates.jsx +0 -73
- data/app/javascript/components/PageFiles.jsx +0 -25
- data/app/javascript/components/PageTree/types.ts +0 -15
- data/app/javascript/components/drag/types.ts +0 -28
- data/app/javascript/controllers/EditPageController.ts +0 -22
- data/app/javascript/controllers/MainController.ts +0 -74
- data/app/javascript/controllers/PageOptionsController.js +0 -67
- data/app/models/category.rb +0 -22
- data/app/models/concerns/pages_core/has_otp.rb +0 -27
- data/app/models/page_category.rb +0 -6
- data/app/views/admin/pages/_edit_content.html.erb +0 -19
- data/app/views/admin/pages/_edit_files.html.erb +0 -4
- data/app/views/admin/pages/_edit_images.html.erb +0 -4
- data/app/views/admin/pages/_edit_metadata.html.erb +0 -35
- data/app/views/admin/pages/_edit_options.html.erb +0 -91
- data/app/views/admin_mailer/account_recovery.text.erb +0 -10
- data/app/views/admin_mailer/invite.text.erb +0 -7
- data/lib/rails/generators/pages_core/rspec/templates/mailer_macros.rb +0 -11
@@ -0,0 +1,58 @@
|
|
1
|
+
import { useReducer } from "react";
|
2
|
+
|
3
|
+
import * as Tags from "../../types/Tags";
|
4
|
+
|
5
|
+
function onlyUnique(value: string, index: number, self: string[]) {
|
6
|
+
return self.indexOf(value) === index;
|
7
|
+
}
|
8
|
+
|
9
|
+
export function allTags(state: Tags.State) {
|
10
|
+
return [...state.tags, ...state.enabled].filter(onlyUnique);
|
11
|
+
}
|
12
|
+
|
13
|
+
function normalize(tag: string, state: Tags.State) {
|
14
|
+
return (
|
15
|
+
allTags(state).filter((t) => t.toLowerCase() == tag.toLowerCase())[0] || tag
|
16
|
+
);
|
17
|
+
}
|
18
|
+
|
19
|
+
export function isEnabled(tag: string, state: Tags.State) {
|
20
|
+
return (
|
21
|
+
state.enabled.map((t) => t.toLowerCase()).indexOf(tag.toLowerCase()) !== -1
|
22
|
+
);
|
23
|
+
}
|
24
|
+
|
25
|
+
function toggle(tag: string, state: Tags.State) {
|
26
|
+
if (isEnabled(tag, state)) {
|
27
|
+
return { ...state, enabled: state.enabled.filter((t) => t !== tag) };
|
28
|
+
} else {
|
29
|
+
return { ...state, enabled: [...state.enabled, tag] };
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
function reducer(state: Tags.State, action: Tags.Action) {
|
34
|
+
const { type, payload } = action;
|
35
|
+
const normalized = normalize(payload, state);
|
36
|
+
switch (type) {
|
37
|
+
case "addTag":
|
38
|
+
return {
|
39
|
+
tags: [...state.tags, normalized].filter(onlyUnique),
|
40
|
+
enabled: [...state.enabled, normalized].filter(onlyUnique)
|
41
|
+
};
|
42
|
+
case "toggleTag":
|
43
|
+
return toggle(normalized, state);
|
44
|
+
default:
|
45
|
+
return state;
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
export default function useTags(
|
50
|
+
initTags: string[],
|
51
|
+
initEnabled: string[]
|
52
|
+
): [Tags.State, (action: Tags.Action) => void] {
|
53
|
+
const [state, dispatch] = useReducer(reducer, {
|
54
|
+
tags: initTags,
|
55
|
+
enabled: initEnabled
|
56
|
+
});
|
57
|
+
return [state, dispatch];
|
58
|
+
}
|
@@ -1,65 +1,15 @@
|
|
1
|
-
import React
|
1
|
+
import React from "react";
|
2
2
|
|
3
|
-
import
|
4
|
-
import
|
3
|
+
import useTags from "./TagEditor/useTags";
|
4
|
+
import Editor from "./TagEditor/Editor";
|
5
|
+
import * as Tags from "../types/Tags";
|
5
6
|
|
6
|
-
interface
|
7
|
+
interface Props extends Tags.State {
|
7
8
|
name: string;
|
8
|
-
enabled: string[];
|
9
|
-
tags: string[];
|
10
9
|
}
|
11
10
|
|
12
|
-
function
|
13
|
-
|
14
|
-
}
|
15
|
-
|
16
|
-
export default function TagEditor(props: TagEditorProps) {
|
17
|
-
const [tags, setTags] = useState(props.tags);
|
18
|
-
const [enabled, setEnabled] = useState(props.enabled);
|
19
|
-
|
20
|
-
const tagList = [...tags, ...enabled].filter(onlyUnique);
|
21
|
-
|
22
|
-
const normalize = (tag: string): string => {
|
23
|
-
return (
|
24
|
-
tagList.filter((t) => t.toLowerCase() == tag.toLowerCase())[0] || tag
|
25
|
-
);
|
26
|
-
};
|
27
|
-
|
28
|
-
const tagEnabled = (tag: string): boolean => {
|
29
|
-
return (
|
30
|
-
enabled.map((t) => t.toLowerCase()).indexOf(tag.toLowerCase()) !== -1
|
31
|
-
);
|
32
|
-
};
|
33
|
-
|
34
|
-
const toggleEnabled = (tag: string) => {
|
35
|
-
const normalized = normalize(tag);
|
36
|
-
|
37
|
-
if (tagEnabled(normalized)) {
|
38
|
-
setEnabled(enabled.filter((t) => t !== normalized));
|
39
|
-
} else {
|
40
|
-
setEnabled([...enabled, normalized]);
|
41
|
-
}
|
42
|
-
};
|
43
|
-
|
44
|
-
const addTag = (tag: string) => {
|
45
|
-
const normalized = normalize(tag);
|
46
|
-
|
47
|
-
setTags([...tags, normalized].filter(onlyUnique));
|
48
|
-
setEnabled([...enabled, normalized].filter(onlyUnique));
|
49
|
-
};
|
11
|
+
export default function TagEditor(props: Props) {
|
12
|
+
const [state, dispatch] = useTags(props.tags, props.enabled);
|
50
13
|
|
51
|
-
return
|
52
|
-
<div className="tag-editor clearfix">
|
53
|
-
<input type="hidden" name={props.name} value={JSON.stringify(enabled)} />
|
54
|
-
{tagList.map((t) => (
|
55
|
-
<Tag
|
56
|
-
key={t}
|
57
|
-
tag={t}
|
58
|
-
enabled={tagEnabled(t)}
|
59
|
-
toggleEnabled={toggleEnabled}
|
60
|
-
/>
|
61
|
-
))}
|
62
|
-
<AddTagForm addTag={addTag} />
|
63
|
-
</div>
|
64
|
-
);
|
14
|
+
return <Editor name={props.name} state={state} dispatch={dispatch} />;
|
65
15
|
}
|
@@ -2,15 +2,15 @@ import React, { useEffect, useRef, useState } from "react";
|
|
2
2
|
|
3
3
|
import useToastStore from "../stores/useToastStore";
|
4
4
|
|
5
|
-
interface
|
5
|
+
interface Props {
|
6
6
|
error: string;
|
7
7
|
notice: string;
|
8
8
|
}
|
9
9
|
|
10
|
-
export default function Toast(props:
|
10
|
+
export default function Toast(props: Props) {
|
11
11
|
const [fadeout, setFadeout] = useState(false);
|
12
12
|
const { toasts, error, notice, next } = useToastStore((state) => state);
|
13
|
-
const timerRef = useRef<
|
13
|
+
const timerRef = useRef<ReturnType<typeof setTimeout>>(null);
|
14
14
|
|
15
15
|
const toast = toasts[0];
|
16
16
|
|
@@ -1,11 +1,14 @@
|
|
1
|
-
import
|
1
|
+
import * as Drag from "../../types/Drag";
|
2
2
|
|
3
|
-
function hovering(
|
4
|
-
dragState:
|
5
|
-
target:
|
6
|
-
)
|
3
|
+
function hovering<T>(
|
4
|
+
dragState: Drag.State<T>,
|
5
|
+
target: Drag.Item<T> | React.MutableRefObject<HTMLDivElement>
|
6
|
+
) {
|
7
7
|
const { x, y } = dragState;
|
8
8
|
let rect: DOMRect;
|
9
|
+
if (typeof target === "string") {
|
10
|
+
return false;
|
11
|
+
}
|
9
12
|
if ("rect" in target) {
|
10
13
|
rect = target.rect;
|
11
14
|
} else if ("current" in target && target.current) {
|
@@ -16,10 +19,10 @@ function hovering(
|
|
16
19
|
return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
|
17
20
|
}
|
18
21
|
|
19
|
-
export function collectionOrder(
|
20
|
-
collection:
|
21
|
-
dragState:
|
22
|
-
):
|
22
|
+
export function collectionOrder<T>(
|
23
|
+
collection: Drag.Collection<T>,
|
24
|
+
dragState: Drag.State<T>
|
25
|
+
): Array<Drag.Item<T>> {
|
23
26
|
const { draggables, ref } = collection;
|
24
27
|
const { dragging } = dragState;
|
25
28
|
|
@@ -27,7 +30,12 @@ export function collectionOrder(
|
|
27
30
|
return draggables;
|
28
31
|
}
|
29
32
|
|
30
|
-
let ordered = draggables
|
33
|
+
let ordered: Drag.Item<T>[] = draggables;
|
34
|
+
if (typeof dragging !== "string") {
|
35
|
+
ordered = draggables
|
36
|
+
.filter((d) => typeof d !== "string")
|
37
|
+
.filter((d: Drag.Draggable<T>) => d.handle !== dragging.handle);
|
38
|
+
}
|
31
39
|
if (hovering(dragState, ref)) {
|
32
40
|
const hovered = ordered.filter((d) => hovering(dragState, d))[0];
|
33
41
|
if (hovered) {
|
@@ -41,10 +49,10 @@ export function collectionOrder(
|
|
41
49
|
return ordered;
|
42
50
|
}
|
43
51
|
|
44
|
-
export default function draggedOrder(
|
45
|
-
collection:
|
46
|
-
dragState:
|
47
|
-
)
|
52
|
+
export default function draggedOrder<T>(
|
53
|
+
collection: Drag.Collection<T>,
|
54
|
+
dragState: Drag.State<T>
|
55
|
+
) {
|
48
56
|
let ordered = collectionOrder(collection, dragState);
|
49
57
|
|
50
58
|
if (dragState.dragging && ordered.indexOf(dragState.dragging) === -1) {
|
@@ -1,25 +1,21 @@
|
|
1
1
|
import { createRef, useEffect, useReducer, useRef } from "react";
|
2
2
|
import { uniqueId } from "lodash";
|
3
3
|
|
4
|
-
import
|
5
|
-
Draggable,
|
6
|
-
DraggableRecord,
|
7
|
-
DragCollectionAction,
|
8
|
-
DragCollection
|
9
|
-
} from "./types";
|
4
|
+
import * as Drag from "../../types/Drag";
|
10
5
|
|
11
|
-
|
12
|
-
|
13
|
-
function getPosition(draggable: Draggable) {
|
14
|
-
if (draggable && draggable.ref && draggable.ref.current) {
|
6
|
+
function getPosition<T>(draggable: Drag.Draggable<T>) {
|
7
|
+
if (draggable && "ref" in draggable && draggable.ref.current) {
|
15
8
|
return draggable.ref.current.getBoundingClientRect();
|
16
9
|
} else {
|
17
10
|
return null;
|
18
11
|
}
|
19
12
|
}
|
20
13
|
|
21
|
-
function hideDraggable(
|
22
|
-
|
14
|
+
function hideDraggable<T>(
|
15
|
+
draggable: Drag.Draggable<T> | null,
|
16
|
+
callback: () => Drag.Draggable<T>[]
|
17
|
+
) {
|
18
|
+
if (draggable && "ref" in draggable && draggable.ref.current) {
|
23
19
|
const prevDisplay = draggable.ref.current.style.display;
|
24
20
|
draggable.ref.current.style.display = "none";
|
25
21
|
const result = callback();
|
@@ -30,7 +26,10 @@ function hideDraggable(draggable: Draggable | null, callback: () => void) {
|
|
30
26
|
}
|
31
27
|
}
|
32
28
|
|
33
|
-
function insertFiles(
|
29
|
+
function insertFiles<T>(
|
30
|
+
state: Drag.Item<T>[],
|
31
|
+
files: Drag.Item<T>[]
|
32
|
+
): Drag.Item<T>[] {
|
34
33
|
const index = state.indexOf("Files");
|
35
34
|
if (index === -1 || !files) {
|
36
35
|
return state;
|
@@ -39,29 +38,33 @@ function insertFiles(state: Draggable[], files: Draggable[]): Draggable[] {
|
|
39
38
|
}
|
40
39
|
}
|
41
40
|
|
42
|
-
function dragCollectionReducer(
|
43
|
-
state:
|
44
|
-
action:
|
45
|
-
):
|
41
|
+
function dragCollectionReducer<T = Drag.DraggableRecord>(
|
42
|
+
state: Drag.Item<T>[],
|
43
|
+
action: Drag.CollectionAction<T>
|
44
|
+
): Drag.Item<T>[] {
|
46
45
|
switch (action.type) {
|
47
46
|
case "append":
|
48
|
-
return [...state, ...
|
47
|
+
return [...state, ...action.payload];
|
49
48
|
case "prepend":
|
50
|
-
return [...
|
49
|
+
return [...action.payload, ...state];
|
51
50
|
case "insertFiles":
|
52
51
|
return insertFiles(state, action.payload);
|
53
52
|
case "update":
|
54
|
-
return state.map((d) => {
|
55
|
-
return d.handle === action.payload
|
53
|
+
return state.map((d: Drag.Draggable<T>) => {
|
54
|
+
return d.handle === (action.payload as Drag.Draggable<T>).handle
|
55
|
+
? action.payload
|
56
|
+
: d;
|
56
57
|
});
|
57
58
|
case "updatePositions":
|
58
59
|
return hideDraggable(action.payload, () => {
|
59
|
-
return state.map((d) => {
|
60
|
+
return state.map((d: Drag.Draggable<T>) => {
|
60
61
|
return { ...d, rect: getPosition(d) };
|
61
62
|
});
|
62
63
|
});
|
63
64
|
case "remove":
|
64
|
-
return state.filter(
|
65
|
+
return state.filter(
|
66
|
+
(d: Drag.Draggable<T>) => d.handle !== action.payload.handle
|
67
|
+
);
|
65
68
|
case "replace":
|
66
69
|
return action.payload;
|
67
70
|
case "reorder":
|
@@ -71,7 +74,9 @@ function dragCollectionReducer(
|
|
71
74
|
}
|
72
75
|
}
|
73
76
|
|
74
|
-
export function createDraggable
|
77
|
+
export function createDraggable<T = Drag.DraggableRecord>(
|
78
|
+
record: T
|
79
|
+
): Drag.Draggable<T> {
|
75
80
|
return {
|
76
81
|
record: record,
|
77
82
|
rect: null,
|
@@ -80,13 +85,13 @@ export function createDraggable(record: Record<string, unknown>): Draggable {
|
|
80
85
|
};
|
81
86
|
}
|
82
87
|
|
83
|
-
export default function useDragCollection(
|
84
|
-
records:
|
85
|
-
):
|
86
|
-
const containerRef = useRef<
|
87
|
-
const [draggables, dispatch] = useReducer(dragCollectionReducer
|
88
|
+
export default function useDragCollection<T = Drag.DraggableRecord>(
|
89
|
+
records: Array<T>
|
90
|
+
): Drag.Collection<T> {
|
91
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
92
|
+
const [draggables, dispatch] = useReducer(dragCollectionReducer<T>, [], () =>
|
88
93
|
records.map((r) => createDraggable(r))
|
89
|
-
)
|
94
|
+
);
|
90
95
|
|
91
96
|
useEffect(() => {
|
92
97
|
dispatch({ type: "updatePositions" });
|
@@ -1,16 +1,20 @@
|
|
1
1
|
import { useEffect, useState } from "react";
|
2
2
|
|
3
|
-
import
|
3
|
+
import * as Drag from "../../types/Drag";
|
4
4
|
|
5
|
-
|
5
|
+
type AnyTouchEvent =
|
6
|
+
| MouseEvent
|
7
|
+
| TouchEvent
|
8
|
+
| React.MouseEvent
|
9
|
+
| React.TouchEvent;
|
10
|
+
|
11
|
+
function containsFiles(evt: AnyTouchEvent) {
|
6
12
|
if ("dataTransfer" in evt) {
|
7
|
-
const dataTransfer
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
return true;
|
13
|
-
}
|
13
|
+
const dataTransfer = evt.dataTransfer as DataTransfer;
|
14
|
+
const types = dataTransfer.types;
|
15
|
+
for (let i = 0; i < types.length; i++) {
|
16
|
+
if (types[i] === "Files" || types[i] === "application/x-moz-file") {
|
17
|
+
return true;
|
14
18
|
}
|
15
19
|
}
|
16
20
|
}
|
@@ -33,7 +37,7 @@ function getFiles(dt: DataTransfer): File[] {
|
|
33
37
|
return files;
|
34
38
|
}
|
35
39
|
|
36
|
-
function mousePosition(evt:
|
40
|
+
function mousePosition(evt: AnyTouchEvent): Drag.Position {
|
37
41
|
let x: number | null, y: number | null;
|
38
42
|
if ("touches" in evt && evt.type == "touchmove") {
|
39
43
|
x = evt.touches[0].clientX;
|
@@ -45,11 +49,18 @@ function mousePosition(evt: TouchEvent | MouseEvent): Position {
|
|
45
49
|
return { x: x, y: y };
|
46
50
|
}
|
47
51
|
|
48
|
-
export default function useDragUploader(
|
49
|
-
collections:
|
50
|
-
onDragEnd: (dragState:
|
51
|
-
)
|
52
|
-
|
52
|
+
export default function useDragUploader<T>(
|
53
|
+
collections: Drag.Collection<T>[],
|
54
|
+
onDragEnd: (dragState: Drag.State<T>, files: File[]) => void
|
55
|
+
): [
|
56
|
+
Drag.State<T>,
|
57
|
+
(evt: AnyTouchEvent, draggable: Drag.Item<T>) => void,
|
58
|
+
{
|
59
|
+
onDragOver: (evt: AnyTouchEvent) => void;
|
60
|
+
onDrop: (evt: AnyTouchEvent) => void;
|
61
|
+
}
|
62
|
+
] {
|
63
|
+
const initialState: Drag.State<T> = {
|
53
64
|
dragging: false,
|
54
65
|
x: null,
|
55
66
|
y: null
|
@@ -57,18 +68,18 @@ export default function useDragUploader(
|
|
57
68
|
|
58
69
|
const [dragState, setDragState] = useState(initialState);
|
59
70
|
|
60
|
-
const updatePositions = (dragging
|
71
|
+
const updatePositions = (dragging?: Drag.Draggable<T> | string) => {
|
61
72
|
collections.forEach((c) => {
|
62
73
|
c.dispatch({ type: "updatePositions", payload: dragging });
|
63
74
|
});
|
64
75
|
};
|
65
76
|
|
66
|
-
const startDrag = (evt:
|
77
|
+
const startDrag = (evt: AnyTouchEvent, draggable: Drag.Item<T>) => {
|
67
78
|
updatePositions(draggable);
|
68
79
|
setDragState({ dragging: draggable, ...mousePosition(evt) });
|
69
80
|
};
|
70
81
|
|
71
|
-
const drag = (evt:
|
82
|
+
const drag = (evt: AnyTouchEvent) => {
|
72
83
|
if (dragState.dragging) {
|
73
84
|
evt.stopPropagation();
|
74
85
|
evt.preventDefault();
|
@@ -80,14 +91,14 @@ export default function useDragUploader(
|
|
80
91
|
}
|
81
92
|
};
|
82
93
|
|
83
|
-
const dragEnd = (evt:
|
94
|
+
const dragEnd = (evt: AnyTouchEvent) => {
|
84
95
|
if (dragState.dragging) {
|
85
96
|
const prevDragState = dragState;
|
86
97
|
let files: File[] = [];
|
87
98
|
evt.stopPropagation();
|
88
99
|
evt.preventDefault();
|
89
100
|
if ("dataTransfer" in evt && dragState.dragging == "Files") {
|
90
|
-
files = getFiles(evt.dataTransfer);
|
101
|
+
files = getFiles(evt.dataTransfer as DataTransfer);
|
91
102
|
}
|
92
103
|
setDragState({ dragging: false, x: null, y: null });
|
93
104
|
onDragEnd(prevDragState, files);
|
@@ -95,7 +106,7 @@ export default function useDragUploader(
|
|
95
106
|
}
|
96
107
|
};
|
97
108
|
|
98
|
-
const dragLeave = (evt:
|
109
|
+
const dragLeave = (evt: AnyTouchEvent) => {
|
99
110
|
if (dragState.dragging === "Files") {
|
100
111
|
evt.preventDefault();
|
101
112
|
evt.stopPropagation();
|
@@ -1,13 +1,14 @@
|
|
1
|
-
import React, { useEffect, useRef } from "react";
|
2
|
-
import { Draggable } from "./types";
|
1
|
+
import React, { DragEvent, useEffect, useRef } from "react";
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
import * as Drag from "../../types/Drag";
|
4
|
+
|
5
|
+
export default function useDraggable<T = Drag.DraggableRecord>(
|
6
|
+
draggable: Drag.Draggable<T>,
|
7
|
+
startDrag: (evt: React.MouseEvent, draggable: Drag.Draggable<T>) => void
|
7
8
|
) {
|
8
9
|
const ref = useRef<HTMLDivElement>(null);
|
9
10
|
|
10
|
-
const handleDrag = (evt:
|
11
|
+
const handleDrag = (evt: DragEvent) => {
|
11
12
|
evt.preventDefault();
|
12
13
|
evt.stopPropagation();
|
13
14
|
startDrag(evt, draggable);
|
@@ -6,9 +6,7 @@ export { default as ImageCropper } from "./components/ImageCropper";
|
|
6
6
|
export { default as ImageGrid } from "./components/ImageGrid";
|
7
7
|
export { default as ImageUploader } from "./components/ImageUploader";
|
8
8
|
export { default as Modal } from "./components/Modal";
|
9
|
-
export { default as
|
10
|
-
export { default as PageFiles } from "./components/PageFiles";
|
11
|
-
export { default as PageImages } from "./components/PageImages";
|
9
|
+
export { default as PageForm } from "./components/PageForm";
|
12
10
|
export { default as PageTree } from "./components/PageTree";
|
13
11
|
export { default as RichTextArea } from "./components/RichTextArea";
|
14
12
|
export { default as TagEditor } from "./components/TagEditor";
|
@@ -6,7 +6,7 @@ import readyHandler from "../lib/readyHandler";
|
|
6
6
|
class RichText {
|
7
7
|
apply() {
|
8
8
|
const elems = document.querySelectorAll("textarea.rich");
|
9
|
-
elems.forEach((elem) => {
|
9
|
+
elems.forEach((elem: HTMLTextAreaElement) => {
|
10
10
|
this.enhance(elem);
|
11
11
|
});
|
12
12
|
}
|
@@ -20,8 +20,7 @@ class RichText {
|
|
20
20
|
name={elem.name}
|
21
21
|
rows={elem.rows}
|
22
22
|
id={elem.id}
|
23
|
-
|
24
|
-
container
|
23
|
+
/>
|
25
24
|
);
|
26
25
|
elem.parentNode.removeChild(elem);
|
27
26
|
}
|
@@ -0,0 +1,79 @@
|
|
1
|
+
import readyHandler from "../lib/readyHandler";
|
2
|
+
|
3
|
+
interface WindowState {
|
4
|
+
tabId: string;
|
5
|
+
}
|
6
|
+
|
7
|
+
function applyTabs() {
|
8
|
+
const tabLinks: HTMLAnchorElement[] = Array.from(
|
9
|
+
document.querySelectorAll(".content-tabs li a")
|
10
|
+
);
|
11
|
+
const tabs: HTMLDivElement[] = Array.from(
|
12
|
+
document.querySelectorAll(".content-tab")
|
13
|
+
);
|
14
|
+
|
15
|
+
const showTab = (tab: string | null) => {
|
16
|
+
tabLinks.forEach((l) => {
|
17
|
+
const parent = l.parentNode as HTMLUListElement | null;
|
18
|
+
if (parent) {
|
19
|
+
if (l.dataset.tab == tab) {
|
20
|
+
parent.classList.add("current");
|
21
|
+
} else {
|
22
|
+
parent.classList.remove("current");
|
23
|
+
}
|
24
|
+
}
|
25
|
+
});
|
26
|
+
|
27
|
+
tabs.forEach((t) => {
|
28
|
+
if (t.dataset.tab == tab) {
|
29
|
+
t.classList.remove("hidden");
|
30
|
+
} else {
|
31
|
+
t.classList.add("hidden");
|
32
|
+
}
|
33
|
+
});
|
34
|
+
};
|
35
|
+
|
36
|
+
const changeTab = (evt: MouseEvent) => {
|
37
|
+
evt.preventDefault();
|
38
|
+
const link = evt.target as HTMLAnchorElement;
|
39
|
+
if ("tab" in link.dataset) {
|
40
|
+
const tab = link.dataset.tab;
|
41
|
+
showTab(tab);
|
42
|
+
history.pushState(
|
43
|
+
{ tabId: tab },
|
44
|
+
"",
|
45
|
+
`${window.location.pathname}#${tab}`
|
46
|
+
);
|
47
|
+
}
|
48
|
+
};
|
49
|
+
|
50
|
+
const stateHandler = (evt: PopStateEvent) => {
|
51
|
+
if (evt.state && "tabId" in evt.state) {
|
52
|
+
const { tabId } = evt.state as WindowState;
|
53
|
+
showTab(tabId);
|
54
|
+
}
|
55
|
+
};
|
56
|
+
|
57
|
+
if (tabLinks.length > 0 && tabs.length > 0) {
|
58
|
+
const tabNames = tabs.map((t) => t.dataset.tab);
|
59
|
+
|
60
|
+
tabLinks.forEach((l) => l.addEventListener("click", changeTab));
|
61
|
+
|
62
|
+
let initTab: string = null;
|
63
|
+
const tabExpression = /#(.*)$/;
|
64
|
+
|
65
|
+
if (document.location.toString().match(tabExpression)) {
|
66
|
+
const id = document.location.toString().match(tabExpression)[1];
|
67
|
+
if (tabNames.indexOf(id) !== -1) {
|
68
|
+
initTab = id;
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
showTab(initTab || tabs[0].dataset.tab);
|
73
|
+
window.addEventListener("popstate", stateHandler);
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
export default function contentTabs() {
|
78
|
+
readyHandler.ready(applyTabs);
|
79
|
+
}
|
data/app/javascript/index.ts
CHANGED
@@ -1,31 +1,24 @@
|
|
1
|
-
import
|
2
|
-
import { Application } from "@hotwired/stimulus";
|
1
|
+
import Rails from "@rails/ujs";
|
3
2
|
import "react_ujs";
|
4
3
|
import { FC } from "react";
|
5
4
|
|
6
5
|
import * as Components from "./components";
|
7
6
|
|
8
|
-
import EditPageController from "./controllers/EditPageController";
|
9
|
-
import MainController from "./controllers/MainController";
|
10
|
-
import PageOptionsController from "./controllers/PageOptionsController";
|
11
|
-
|
12
7
|
import RichText from "./features/RichText";
|
8
|
+
import contentTabs from "./features/contentTabs";
|
13
9
|
|
14
10
|
export function registerComponent(name: string, component: FC) {
|
15
11
|
window[name] = component;
|
16
12
|
}
|
17
13
|
|
18
14
|
export default function startPages() {
|
19
|
-
|
15
|
+
Rails.start();
|
20
16
|
for (const name in Components) {
|
21
17
|
registerComponent(name, Components[name] as FC);
|
22
18
|
}
|
23
|
-
RichText.start();
|
24
19
|
|
25
|
-
|
26
|
-
|
27
|
-
application.register("main", MainController);
|
28
|
-
application.register("page-options", PageOptionsController);
|
20
|
+
RichText.start();
|
21
|
+
contentTabs();
|
29
22
|
}
|
30
23
|
|
31
24
|
export * from "./components";
|