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,54 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
import * as PageEditor from "../../types/PageEditor";
|
4
|
+
import * as Tags from "../../types/Tags";
|
5
|
+
import { MaybeLocalizedValue } from "../../types";
|
6
|
+
|
7
|
+
import { blockValue, errorsOn } from "./usePage";
|
8
|
+
import LabelledField from "../LabelledField";
|
9
|
+
import { default as TagEditor } from "../TagEditor/Editor";
|
10
|
+
import Block from "./Block";
|
11
|
+
import Dates from "./Dates";
|
12
|
+
|
13
|
+
interface Props {
|
14
|
+
state: PageEditor.State;
|
15
|
+
dispatch: (action: PageEditor.Action) => void;
|
16
|
+
tagsState: Tags.State;
|
17
|
+
tagsDispatch: (action: Tags.Action) => void;
|
18
|
+
}
|
19
|
+
|
20
|
+
export default function Content(props: Props) {
|
21
|
+
const { state, dispatch, tagsState, tagsDispatch } = props;
|
22
|
+
|
23
|
+
const { page, locale, inputDir, templateConfig } = state;
|
24
|
+
|
25
|
+
const handleChange = (attr: string) => (value: MaybeLocalizedValue) => {
|
26
|
+
dispatch({ type: "updateBlocks", payload: { [attr]: value } });
|
27
|
+
};
|
28
|
+
|
29
|
+
return (
|
30
|
+
<React.Fragment>
|
31
|
+
{templateConfig.blocks.map((b) => (
|
32
|
+
<Block
|
33
|
+
key={b.name}
|
34
|
+
block={b}
|
35
|
+
errors={errorsOn(page, b.name)}
|
36
|
+
dir={inputDir}
|
37
|
+
lang={locale}
|
38
|
+
onChange={handleChange(b.name)}
|
39
|
+
value={blockValue(state, b)}
|
40
|
+
/>
|
41
|
+
))}
|
42
|
+
{templateConfig.dates && <Dates state={state} dispatch={dispatch} />}
|
43
|
+
{templateConfig.tags && (
|
44
|
+
<LabelledField label="Tags">
|
45
|
+
<TagEditor
|
46
|
+
name="page[serialized_tags]"
|
47
|
+
state={tagsState}
|
48
|
+
dispatch={tagsDispatch}
|
49
|
+
/>
|
50
|
+
</LabelledField>
|
51
|
+
)}
|
52
|
+
</React.Fragment>
|
53
|
+
);
|
54
|
+
}
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
import * as PageEditor from "../../types/PageEditor";
|
4
|
+
|
5
|
+
import DateRangeSelect from "../DateRangeSelect";
|
6
|
+
|
7
|
+
interface Props {
|
8
|
+
state: PageEditor.State;
|
9
|
+
dispatch: (action: PageEditor.Action) => void;
|
10
|
+
}
|
11
|
+
|
12
|
+
export default function Dates(props: Props) {
|
13
|
+
const { state, dispatch } = props;
|
14
|
+
const { datesEnabled, page } = state;
|
15
|
+
|
16
|
+
const toggleAllDay = () => {
|
17
|
+
dispatch({ type: "update", payload: { all_day: !page.all_day } });
|
18
|
+
};
|
19
|
+
|
20
|
+
const toggleDatesEnabled = () => {
|
21
|
+
dispatch({ type: "setDatesEnabled", payload: !datesEnabled });
|
22
|
+
};
|
23
|
+
|
24
|
+
const setDate = (attr: "starts_at" | "ends_at") => (date: Date) => {
|
25
|
+
dispatch({ type: "update", payload: { [attr]: date } });
|
26
|
+
};
|
27
|
+
|
28
|
+
return (
|
29
|
+
<div className="page-dates field">
|
30
|
+
<input
|
31
|
+
type="hidden"
|
32
|
+
name="page[all_day]"
|
33
|
+
value={datesEnabled && page.all_day ? "1" : "0"}
|
34
|
+
/>
|
35
|
+
<label>Dates</label>
|
36
|
+
<div className="toggles">
|
37
|
+
<label className="has-dates-toggle">
|
38
|
+
<input
|
39
|
+
type="checkbox"
|
40
|
+
checked={datesEnabled}
|
41
|
+
onChange={toggleDatesEnabled}
|
42
|
+
/>
|
43
|
+
Enabled
|
44
|
+
</label>
|
45
|
+
<label className={!datesEnabled && "disabled"}>
|
46
|
+
<input
|
47
|
+
type="checkbox"
|
48
|
+
disabled={!datesEnabled}
|
49
|
+
checked={page.all_day}
|
50
|
+
onChange={toggleAllDay}
|
51
|
+
/>
|
52
|
+
All day event
|
53
|
+
</label>
|
54
|
+
</div>
|
55
|
+
<DateRangeSelect
|
56
|
+
objectName="page"
|
57
|
+
startsAt={page.starts_at}
|
58
|
+
setStartsAt={setDate("starts_at")}
|
59
|
+
endsAt={page.ends_at}
|
60
|
+
setEndsAt={setDate("ends_at")}
|
61
|
+
disabled={!datesEnabled}
|
62
|
+
disableTime={page.all_day}
|
63
|
+
/>
|
64
|
+
</div>
|
65
|
+
);
|
66
|
+
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
import * as Attachments from "../../types/Attachments";
|
4
|
+
import { Locale } from "../../types";
|
5
|
+
|
6
|
+
import List from "../Attachments/List";
|
7
|
+
|
8
|
+
interface Props {
|
9
|
+
locale: string;
|
10
|
+
locales: { [index: string]: Locale };
|
11
|
+
state: Attachments.State;
|
12
|
+
}
|
13
|
+
|
14
|
+
export default function Files(props: Props) {
|
15
|
+
const { locale, locales, state } = props;
|
16
|
+
|
17
|
+
return (
|
18
|
+
<div className="page-files">
|
19
|
+
<List
|
20
|
+
attribute="page[page_files_attributes]"
|
21
|
+
showEmbed={true}
|
22
|
+
locale={locale}
|
23
|
+
locales={locales}
|
24
|
+
state={state}
|
25
|
+
/>
|
26
|
+
</div>
|
27
|
+
);
|
28
|
+
}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
import { csrfToken } from "../../lib/request";
|
4
|
+
import * as PageEditor from "../../types/PageEditor";
|
5
|
+
|
6
|
+
interface FormProps {
|
7
|
+
state: PageEditor.State;
|
8
|
+
children: React.ReactNode;
|
9
|
+
}
|
10
|
+
|
11
|
+
function pageUrl(state: PageEditor.State): string {
|
12
|
+
if (state.page.id) {
|
13
|
+
return `/admin/${state.locale}/pages/${state.page.id}`;
|
14
|
+
} else {
|
15
|
+
return `/admin/${state.locale}/pages`;
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
export default function Form(props: FormProps) {
|
20
|
+
const { state, children } = props;
|
21
|
+
const { page } = state;
|
22
|
+
|
23
|
+
return (
|
24
|
+
<form
|
25
|
+
className="edit-page main-wrapper"
|
26
|
+
method="post"
|
27
|
+
acceptCharset="UTF-8"
|
28
|
+
action={pageUrl(state)}>
|
29
|
+
{page.id && (
|
30
|
+
<input type="hidden" name="_method" value="put" autoComplete="off" />
|
31
|
+
)}
|
32
|
+
<input
|
33
|
+
type="hidden"
|
34
|
+
autoComplete="off"
|
35
|
+
name="authenticity_token"
|
36
|
+
value={csrfToken()}
|
37
|
+
/>
|
38
|
+
{children}
|
39
|
+
</form>
|
40
|
+
);
|
41
|
+
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
import { GridState } from "../../types/Images";
|
4
|
+
import { Locale } from "../../types";
|
5
|
+
|
6
|
+
import Grid from "../ImageGrid/Grid";
|
7
|
+
|
8
|
+
interface Props {
|
9
|
+
locale: string;
|
10
|
+
locales: { [index: string]: Locale };
|
11
|
+
state: GridState;
|
12
|
+
}
|
13
|
+
|
14
|
+
export default function Images(props: Props) {
|
15
|
+
return (
|
16
|
+
<div className="page-images">
|
17
|
+
<Grid
|
18
|
+
attribute="page[page_images_attributes]"
|
19
|
+
primaryAttribute="page[image_id]"
|
20
|
+
enablePrimary={true}
|
21
|
+
showEmbed={true}
|
22
|
+
locale={props.locale}
|
23
|
+
locales={props.locales}
|
24
|
+
state={props.state}
|
25
|
+
/>
|
26
|
+
</div>
|
27
|
+
);
|
28
|
+
}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
import * as PageEditor from "../../types/PageEditor";
|
4
|
+
|
5
|
+
interface Props {
|
6
|
+
state: PageEditor.State;
|
7
|
+
dispatch: (action: PageEditor.Action) => void;
|
8
|
+
}
|
9
|
+
|
10
|
+
export default function LocaleLinks(props: Props) {
|
11
|
+
const { state, dispatch } = props;
|
12
|
+
const { locale, locales } = state;
|
13
|
+
|
14
|
+
const handleClick = (newLocale: string) => (evt: React.MouseEvent) => {
|
15
|
+
evt.preventDefault();
|
16
|
+
dispatch({ type: "setLocale", payload: newLocale });
|
17
|
+
};
|
18
|
+
|
19
|
+
if (!locales) {
|
20
|
+
return "";
|
21
|
+
}
|
22
|
+
|
23
|
+
return (
|
24
|
+
<div className="links">
|
25
|
+
{Object.keys(locales).map((l) => (
|
26
|
+
<a
|
27
|
+
key={l}
|
28
|
+
className={locale == l ? "current" : ""}
|
29
|
+
href="#"
|
30
|
+
onClick={handleClick(l)}>
|
31
|
+
{locales[l].name}
|
32
|
+
</a>
|
33
|
+
))}
|
34
|
+
</div>
|
35
|
+
);
|
36
|
+
}
|
@@ -0,0 +1,67 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
import * as PageEditor from "../../types/PageEditor";
|
4
|
+
import * as Pages from "../../types/Pages";
|
5
|
+
import { MaybeLocalizedValue } from "../../types";
|
6
|
+
|
7
|
+
import { blockValue, errorsOn } from "./usePage";
|
8
|
+
import Block from "./Block";
|
9
|
+
import PathSegment from "./PathSegment";
|
10
|
+
import LabelledField from "../LabelledField";
|
11
|
+
import ImageUploader from "../ImageUploader";
|
12
|
+
|
13
|
+
interface Props {
|
14
|
+
state: PageEditor.State;
|
15
|
+
dispatch: (action: PageEditor.Action) => void;
|
16
|
+
}
|
17
|
+
|
18
|
+
export default function Metadata(props: Props) {
|
19
|
+
const { state, dispatch } = props;
|
20
|
+
|
21
|
+
const { page, locale, locales, inputDir, templateConfig } = state;
|
22
|
+
|
23
|
+
const handleChange = (attr: string) => (value: MaybeLocalizedValue) => {
|
24
|
+
dispatch({ type: "updateBlocks", payload: { [attr]: value } });
|
25
|
+
};
|
26
|
+
|
27
|
+
const handleMetaImage = (value: Pages.MetaImage) => {
|
28
|
+
dispatch({ type: "update", payload: { meta_image: value } });
|
29
|
+
};
|
30
|
+
|
31
|
+
return (
|
32
|
+
<React.Fragment>
|
33
|
+
<PathSegment state={state} dispatch={dispatch} />
|
34
|
+
<LabelledField
|
35
|
+
htmlFor="page_meta_image_id"
|
36
|
+
label="Image"
|
37
|
+
description={
|
38
|
+
"Image displayed when sharing on social media. " +
|
39
|
+
"Will fall back to the primary image if absent. " +
|
40
|
+
"Recommended size is at least 1200x630."
|
41
|
+
}
|
42
|
+
errors={errorsOn(page, "meta_image_id")}>
|
43
|
+
<ImageUploader
|
44
|
+
attr="page[meta_image_id]"
|
45
|
+
locale={locale}
|
46
|
+
locales={locales}
|
47
|
+
image={page.meta_image.image}
|
48
|
+
src={page.meta_image.src}
|
49
|
+
onChange={handleMetaImage}
|
50
|
+
width={250}
|
51
|
+
caption={false}
|
52
|
+
/>
|
53
|
+
</LabelledField>
|
54
|
+
{templateConfig.metadata_blocks.map((b) => (
|
55
|
+
<Block
|
56
|
+
key={b.name}
|
57
|
+
block={b}
|
58
|
+
errors={errorsOn(page, b.name)}
|
59
|
+
dir={inputDir}
|
60
|
+
lang={locale}
|
61
|
+
onChange={handleChange(b.name)}
|
62
|
+
value={blockValue(state, b)}
|
63
|
+
/>
|
64
|
+
))}
|
65
|
+
</React.Fragment>
|
66
|
+
);
|
67
|
+
}
|
@@ -0,0 +1,180 @@
|
|
1
|
+
import React, { useState, ChangeEvent } from "react";
|
2
|
+
|
3
|
+
import * as PageEditor from "../../types/PageEditor";
|
4
|
+
import * as Pages from "../../types/Pages";
|
5
|
+
import LabelledField from "../LabelledField";
|
6
|
+
import DateTimeSelect from "../DateTimeSelect";
|
7
|
+
import { errorsOn } from "./usePage";
|
8
|
+
|
9
|
+
interface OptionsProps {
|
10
|
+
state: PageEditor.State;
|
11
|
+
dispatch: (action: PageEditor.Action) => void;
|
12
|
+
authors: Pages.Author[];
|
13
|
+
statuses: Pages.StatusLabels;
|
14
|
+
}
|
15
|
+
|
16
|
+
export default function Options(props: OptionsProps) {
|
17
|
+
const { state, dispatch, authors, statuses } = props;
|
18
|
+
|
19
|
+
const { page, locale, templates } = state;
|
20
|
+
|
21
|
+
const [showAdvanced, setShowAdvanced] = useState(false);
|
22
|
+
|
23
|
+
const published = page.status == 2;
|
24
|
+
const autopublish = published && page.published_at > new Date();
|
25
|
+
const url = page.urls[locale];
|
26
|
+
|
27
|
+
const handleChange =
|
28
|
+
(attr: string) =>
|
29
|
+
(evt: ChangeEvent<HTMLInputElement> | ChangeEvent<HTMLSelectElement>) => {
|
30
|
+
dispatch({ type: "update", payload: { [attr]: evt.target.value } });
|
31
|
+
};
|
32
|
+
|
33
|
+
const handleChecked =
|
34
|
+
(attr: string) => (evt: ChangeEvent<HTMLInputElement>) => {
|
35
|
+
dispatch({ type: "update", payload: { [attr]: evt.target.checked } });
|
36
|
+
};
|
37
|
+
|
38
|
+
const changePublishedAt = (value: Date) => {
|
39
|
+
dispatch({ type: "update", payload: { published_at: value } });
|
40
|
+
};
|
41
|
+
|
42
|
+
const toggleAdvanced = (evt: React.MouseEvent) => {
|
43
|
+
evt.preventDefault();
|
44
|
+
setShowAdvanced(!showAdvanced);
|
45
|
+
};
|
46
|
+
|
47
|
+
return (
|
48
|
+
<div className="page-options">
|
49
|
+
<LabelledField
|
50
|
+
htmlFor="page_status"
|
51
|
+
label="Status"
|
52
|
+
errors={errorsOn(page, "status")}>
|
53
|
+
<select
|
54
|
+
id="page_status"
|
55
|
+
name="page[status]"
|
56
|
+
onChange={handleChange("status")}
|
57
|
+
value={page.status}>
|
58
|
+
{Object.keys(statuses).map((id) => (
|
59
|
+
<option key={id} value={id}>
|
60
|
+
{statuses[id]}
|
61
|
+
</option>
|
62
|
+
))}
|
63
|
+
</select>
|
64
|
+
</LabelledField>
|
65
|
+
{published && (
|
66
|
+
<div>
|
67
|
+
<LabelledField label="Date" errors={errorsOn(page, "published_at")}>
|
68
|
+
<DateTimeSelect
|
69
|
+
name={"page[published_at]"}
|
70
|
+
onChange={changePublishedAt}
|
71
|
+
value={page.published_at}
|
72
|
+
/>
|
73
|
+
</LabelledField>
|
74
|
+
{autopublish && <p>This page will publish later</p>}
|
75
|
+
</div>
|
76
|
+
)}
|
77
|
+
<LabelledField
|
78
|
+
htmlFor="page_user_id"
|
79
|
+
label="Author"
|
80
|
+
errors={errorsOn(page, "user_id")}>
|
81
|
+
<select
|
82
|
+
id="page_user_id"
|
83
|
+
name="page[user_id]"
|
84
|
+
onChange={handleChange("user_id")}
|
85
|
+
value={page.user_id}>
|
86
|
+
{authors.map((u) => (
|
87
|
+
<option key={u[1]} value={u[1]}>
|
88
|
+
{u[0]}
|
89
|
+
</option>
|
90
|
+
))}
|
91
|
+
</select>
|
92
|
+
</LabelledField>
|
93
|
+
<LabelledField label="Pin to top">
|
94
|
+
<label className="check-box">
|
95
|
+
<input
|
96
|
+
name="page[pinned]"
|
97
|
+
type="checkbox"
|
98
|
+
onChange={handleChecked("pinned")}
|
99
|
+
checked={page.pinned}
|
100
|
+
/>{" "}
|
101
|
+
Make post featured
|
102
|
+
</label>
|
103
|
+
</LabelledField>
|
104
|
+
<LabelledField
|
105
|
+
htmlFor="page_template"
|
106
|
+
label="Template"
|
107
|
+
errors={errorsOn(page, "template")}>
|
108
|
+
<select
|
109
|
+
id="page_template"
|
110
|
+
name="page[template]"
|
111
|
+
onChange={handleChange("template")}
|
112
|
+
value={page.template}>
|
113
|
+
{templates.map((t) => (
|
114
|
+
<option key={t.template_name} value={t.template_name}>
|
115
|
+
{t.name}
|
116
|
+
</option>
|
117
|
+
))}
|
118
|
+
</select>
|
119
|
+
</LabelledField>
|
120
|
+
<p>
|
121
|
+
<a href="#" onClick={toggleAdvanced}>
|
122
|
+
Advanced options
|
123
|
+
</a>
|
124
|
+
</p>
|
125
|
+
{showAdvanced && (
|
126
|
+
<React.Fragment>
|
127
|
+
<LabelledField label="Subpages">
|
128
|
+
<label className="check-box">
|
129
|
+
<input
|
130
|
+
name="page[feed_enabled]"
|
131
|
+
type="checkbox"
|
132
|
+
onChange={handleChecked("feed_enabled")}
|
133
|
+
checked={page.feed_enabled}
|
134
|
+
/>{" "}
|
135
|
+
RSS feed enabled
|
136
|
+
</label>
|
137
|
+
<label className="check-box">
|
138
|
+
<input
|
139
|
+
name="page[news_page]"
|
140
|
+
type="checkbox"
|
141
|
+
onChange={handleChecked("news_page")}
|
142
|
+
checked={page.news_page}
|
143
|
+
/>{" "}
|
144
|
+
Show in news
|
145
|
+
</label>
|
146
|
+
</LabelledField>
|
147
|
+
<LabelledField
|
148
|
+
htmlFor="page_unique_name"
|
149
|
+
label="Unique name"
|
150
|
+
errors={errorsOn(page, "unique_name")}>
|
151
|
+
<input
|
152
|
+
type="text"
|
153
|
+
id="page_unique_name"
|
154
|
+
name="page[unique_name]"
|
155
|
+
value={page.unique_name}
|
156
|
+
onChange={handleChange("unique_name")}
|
157
|
+
/>
|
158
|
+
</LabelledField>
|
159
|
+
<LabelledField
|
160
|
+
htmlFor="page_redirect_to"
|
161
|
+
label="Redirect"
|
162
|
+
errors={errorsOn(page, "redirect_to")}>
|
163
|
+
<input
|
164
|
+
type="text"
|
165
|
+
id="page_redirect_to"
|
166
|
+
name="page[redirect_to]"
|
167
|
+
value={page.redirect_to}
|
168
|
+
onChange={handleChange("redirect_to")}
|
169
|
+
/>
|
170
|
+
</LabelledField>
|
171
|
+
</React.Fragment>
|
172
|
+
)}
|
173
|
+
{url && (
|
174
|
+
<LabelledField label="Page link">
|
175
|
+
<a href={url}>{url}</a>
|
176
|
+
</LabelledField>
|
177
|
+
)}
|
178
|
+
</div>
|
179
|
+
);
|
180
|
+
}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
import * as PageEditor from "../../types/PageEditor";
|
4
|
+
import * as Pages from "../../types/Pages";
|
5
|
+
|
6
|
+
import LocaleLinks from "./LocaleLinks";
|
7
|
+
|
8
|
+
interface PageDescriptionProps {
|
9
|
+
state: PageEditor.State;
|
10
|
+
dispatch: (action: PageEditor.Action) => void;
|
11
|
+
children: React.ReactNode;
|
12
|
+
}
|
13
|
+
|
14
|
+
function editLink(locale: string, page: Pages.Ancestor | Pages.Resource) {
|
15
|
+
return (
|
16
|
+
<a href={`/admin/${locale}/pages/${page.id}/edit`}>
|
17
|
+
{pageName(locale, page)}
|
18
|
+
</a>
|
19
|
+
);
|
20
|
+
}
|
21
|
+
|
22
|
+
function pageName(locale: string, page: Pages.Ancestor | Pages.Resource) {
|
23
|
+
if ("name" in page) {
|
24
|
+
return page.name[locale];
|
25
|
+
}
|
26
|
+
return page.blocks.name[locale] || <i>Untitled</i>;
|
27
|
+
}
|
28
|
+
|
29
|
+
export default function PageDescription(props: PageDescriptionProps) {
|
30
|
+
const { state, dispatch, children } = props;
|
31
|
+
const { locale, page } = state;
|
32
|
+
|
33
|
+
return (
|
34
|
+
<div className="page-description with_content_tabs">
|
35
|
+
<LocaleLinks state={state} dispatch={dispatch} />
|
36
|
+
<h3>
|
37
|
+
{page.ancestors.map((p) => (
|
38
|
+
<React.Fragment key={p.id}>
|
39
|
+
{editLink(locale, p)}
|
40
|
+
{" » "}
|
41
|
+
</React.Fragment>
|
42
|
+
))}
|
43
|
+
{page.id ? editLink(locale, page) : "New Page"}
|
44
|
+
</h3>
|
45
|
+
{children}
|
46
|
+
</div>
|
47
|
+
);
|
48
|
+
}
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import React, { ChangeEvent } from "react";
|
2
|
+
|
3
|
+
import * as PageEditor from "../../types/PageEditor";
|
4
|
+
import * as Pages from "../../types/Pages";
|
5
|
+
import { LocalizedValue } from "../../types";
|
6
|
+
|
7
|
+
import { errorsOn } from "./usePage";
|
8
|
+
import LabelledField from "../LabelledField";
|
9
|
+
|
10
|
+
interface Props {
|
11
|
+
state: PageEditor.State;
|
12
|
+
dispatch: (action: PageEditor.Action) => void;
|
13
|
+
}
|
14
|
+
|
15
|
+
function missingPathSegment(ancestors: Pages.Ancestor[], locale: string) {
|
16
|
+
for (let i = 0; i < ancestors.length; i++) {
|
17
|
+
if (!ancestors[i].path_segment[locale]) {
|
18
|
+
return ancestors[i];
|
19
|
+
}
|
20
|
+
}
|
21
|
+
return null;
|
22
|
+
}
|
23
|
+
|
24
|
+
export default function PathSegment(props: Props) {
|
25
|
+
const { state, dispatch } = props;
|
26
|
+
const { page, locale } = state;
|
27
|
+
|
28
|
+
const value = (page.path_segment as LocalizedValue)[locale];
|
29
|
+
|
30
|
+
const handleChange = (evt: ChangeEvent<HTMLInputElement>) => {
|
31
|
+
dispatch({ type: "update", payload: { path_segment: evt.target.value } });
|
32
|
+
};
|
33
|
+
|
34
|
+
const editAncestor = missingPathSegment(page.ancestors, locale);
|
35
|
+
|
36
|
+
if (editAncestor) {
|
37
|
+
const editUrl = `/admin/${locale}/pages/${editAncestor.id}/edit#metadata`;
|
38
|
+
return (
|
39
|
+
<LabelledField label="Path segment">
|
40
|
+
<p className="description">
|
41
|
+
Unable to add a path segment to this page, please add one to{" "}
|
42
|
+
<a href={editUrl}>this page's ancestor</a> first.
|
43
|
+
</p>
|
44
|
+
</LabelledField>
|
45
|
+
);
|
46
|
+
}
|
47
|
+
|
48
|
+
return (
|
49
|
+
<LabelledField
|
50
|
+
htmlFor="page_path_segment"
|
51
|
+
label="Path segment"
|
52
|
+
description="Only alpanumeric characters and dashes are allowed."
|
53
|
+
errors={errorsOn(page, "path_segment")}>
|
54
|
+
<input
|
55
|
+
type="text"
|
56
|
+
id="page_path_segment"
|
57
|
+
name="page[path_segment]"
|
58
|
+
lang={state.locale}
|
59
|
+
dir="ltr"
|
60
|
+
onChange={handleChange}
|
61
|
+
value={value}
|
62
|
+
/>
|
63
|
+
</LabelledField>
|
64
|
+
);
|
65
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
interface TabPanelProps {
|
4
|
+
active: boolean;
|
5
|
+
children: React.ReactNode;
|
6
|
+
}
|
7
|
+
|
8
|
+
export default function TabPanel(props: TabPanelProps) {
|
9
|
+
const { active, children } = props;
|
10
|
+
|
11
|
+
const classNames = ["content-tab"];
|
12
|
+
if (!active) {
|
13
|
+
classNames.push("hidden");
|
14
|
+
}
|
15
|
+
|
16
|
+
return (
|
17
|
+
<div className={classNames.join(" ")} role="tabpanel">
|
18
|
+
{children}
|
19
|
+
</div>
|
20
|
+
);
|
21
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
import * as PageEditor from "../../types/PageEditor";
|
4
|
+
|
5
|
+
interface Props {
|
6
|
+
tab: string;
|
7
|
+
tabs: PageEditor.Tab[];
|
8
|
+
setTab: (tab: string) => void;
|
9
|
+
}
|
10
|
+
|
11
|
+
export default function Tabs(props: Props) {
|
12
|
+
const { tab, tabs, setTab } = props;
|
13
|
+
|
14
|
+
const handleTabChange = (tab: PageEditor.Tab) => (evt: React.MouseEvent) => {
|
15
|
+
evt.preventDefault();
|
16
|
+
setTab(tab.id);
|
17
|
+
};
|
18
|
+
|
19
|
+
return (
|
20
|
+
<ul className="content-tabs" role="tablist">
|
21
|
+
{tabs.map((t) => (
|
22
|
+
<li key={t.id} className={t.id == tab ? "current" : ""}>
|
23
|
+
{!t.enabled && t.name}
|
24
|
+
{t.enabled && (
|
25
|
+
<a href={`#${t.id}`} onClick={handleTabChange(t)}>
|
26
|
+
{t.name}
|
27
|
+
</a>
|
28
|
+
)}
|
29
|
+
</li>
|
30
|
+
))}
|
31
|
+
</ul>
|
32
|
+
);
|
33
|
+
}
|