Compare commits
24 Commits
dbbb2f7009
...
v0.1.5
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ae9b67b9c | |||
| 3b706c0092 | |||
| baa3061685 | |||
| fd61895bbd | |||
| cdcb09e24b | |||
| 341b0d6e9d | |||
| 58d1dc0045 | |||
| 5e0df227f3 | |||
| 2d0b176ab8 | |||
| bfb5ae3a70 | |||
| 9d99770b38 | |||
| e383255e73 | |||
| 684e2fa1e9 | |||
| 8e26f19f66 | |||
| 40becf1135 | |||
| 764bc6aeea | |||
| 12048ffdd3 | |||
| c7f6cca663 | |||
| 2b9a87511b | |||
| 688c4e36b3 | |||
| 5bda835566 | |||
| 6f6e0d7b76 | |||
| c1094239a3 | |||
| f9958701e6 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lewebsimple/wp-headless",
|
"name": "lewebsimple/wp-headless",
|
||||||
"description": "WordPress project",
|
"description": "WP Headless",
|
||||||
"version": "0.4.25",
|
"version": "0.4.25",
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
12
composer.lock
generated
12
composer.lock
generated
@@ -205,15 +205,15 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "wpackagist-plugin/acf-extended",
|
"name": "wpackagist-plugin/acf-extended",
|
||||||
"version": "0.9.2.2",
|
"version": "0.9.2.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "svn",
|
"type": "svn",
|
||||||
"url": "https://plugins.svn.wordpress.org/acf-extended/",
|
"url": "https://plugins.svn.wordpress.org/acf-extended/",
|
||||||
"reference": "tags/0.9.2.2"
|
"reference": "tags/0.9.2.3"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://downloads.wordpress.org/plugin/acf-extended.0.9.2.2.zip"
|
"url": "https://downloads.wordpress.org/plugin/acf-extended.0.9.2.3.zip"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"composer/installers": "^1.0 || ^2.0"
|
"composer/installers": "^1.0 || ^2.0"
|
||||||
@@ -241,15 +241,15 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "wpackagist-plugin/disable-comments",
|
"name": "wpackagist-plugin/disable-comments",
|
||||||
"version": "2.6.1",
|
"version": "2.6.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "svn",
|
"type": "svn",
|
||||||
"url": "https://plugins.svn.wordpress.org/disable-comments/",
|
"url": "https://plugins.svn.wordpress.org/disable-comments/",
|
||||||
"reference": "tags/2.6.1"
|
"reference": "tags/2.6.2"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://downloads.wordpress.org/plugin/disable-comments.2.6.1.zip"
|
"url": "https://downloads.wordpress.org/plugin/disable-comments.2.6.2.zip"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"composer/installers": "^1.0 || ^2.0"
|
"composer/installers": "^1.0 || ^2.0"
|
||||||
|
|||||||
@@ -1,5 +1,80 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v0.1.5
|
||||||
|
|
||||||
|
[compare changes](https://gitea.websimple.com/templates/wp-headless/compare/v0.1.4...v0.1.5)
|
||||||
|
|
||||||
|
### 🩹 Fixes
|
||||||
|
|
||||||
|
- Auth server utils upgrade to latest nuxt-graphql (fd61895)
|
||||||
|
- Immutable extractNodes (baa3061)
|
||||||
|
- Type issue with NodePage (3b706c0)
|
||||||
|
|
||||||
|
## v0.1.4
|
||||||
|
|
||||||
|
[compare changes](https://gitea.websimple.com/templates/wp-headless/compare/v0.1.2...v0.1.4)
|
||||||
|
|
||||||
|
### 🚀 Enhancements
|
||||||
|
|
||||||
|
- Initial NodeByUri logic and frontend (688c4e3)
|
||||||
|
- BuilderSections component (2b9a875)
|
||||||
|
- LaoutContained (c7f6cca)
|
||||||
|
- LayoutContained section wrapper (12048ff)
|
||||||
|
- Initial typography / prose styles (764bc6a)
|
||||||
|
- UiProse prose component with link highjacking (40becf1)
|
||||||
|
- TinyMCE WYSIWYG editor styles (8e26f19)
|
||||||
|
- Login / logout toast (2d0b176)
|
||||||
|
- Hide title on front page (5e0df22)
|
||||||
|
|
||||||
|
### 🩹 Fixes
|
||||||
|
|
||||||
|
- Fatal 404 (bfb5ae3)
|
||||||
|
|
||||||
|
### 💅 Refactors
|
||||||
|
|
||||||
|
- Update to nuxt-graphql 0.5.x (e383255)
|
||||||
|
- /api/login route (9d99770)
|
||||||
|
|
||||||
|
## v0.1.3
|
||||||
|
|
||||||
|
[compare changes](https://gitea.websimple.com/templates/wp-headless/compare/v0.1.2...v0.1.3)
|
||||||
|
|
||||||
|
### 🚀 Enhancements
|
||||||
|
|
||||||
|
- Initial NodeByUri logic and frontend (688c4e3)
|
||||||
|
- BuilderSections component (2b9a875)
|
||||||
|
- LaoutContained (c7f6cca)
|
||||||
|
- LayoutContained section wrapper (12048ff)
|
||||||
|
- Initial typography / prose styles (764bc6a)
|
||||||
|
- UiProse prose component with link highjacking (40becf1)
|
||||||
|
- TinyMCE WYSIWYG editor styles (8e26f19)
|
||||||
|
- Login / logout toast (2d0b176)
|
||||||
|
- Hide title on front page (5e0df22)
|
||||||
|
|
||||||
|
### 🩹 Fixes
|
||||||
|
|
||||||
|
- Fatal 404 (bfb5ae3)
|
||||||
|
|
||||||
|
### 💅 Refactors
|
||||||
|
|
||||||
|
- Update to nuxt-graphql 0.5.x (e383255)
|
||||||
|
- /api/login route (9d99770)
|
||||||
|
|
||||||
|
## v0.1.2
|
||||||
|
|
||||||
|
[compare changes](https://gitea.websimple.com/templates/wp-headless/compare/v0.1.1...v0.1.2)
|
||||||
|
|
||||||
|
### 🚀 Enhancements
|
||||||
|
|
||||||
|
- Initial Nuxt UI configuration (ca2e660)
|
||||||
|
- Initial layout with SiteHeader / SiteFooter (3d7a2b2)
|
||||||
|
- Update .gitignore and add Copilot instructions (f520db7)
|
||||||
|
- Optional SSL for dev server (9b6a86f)
|
||||||
|
- Typecheck npm script (33589d4)
|
||||||
|
- Initial theme setup (theme features, locale, main menu) (a286047)
|
||||||
|
- Initial GraphQL setup with remote WP schema (d0244eb)
|
||||||
|
- Initial authentication logic and UX (c109423)
|
||||||
|
|
||||||
## v0.1.1
|
## v0.1.1
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
156
wp-content/themes/moonshine/acf-json/group_abstract_builder.json
Normal file
156
wp-content/themes/moonshine/acf-json/group_abstract_builder.json
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
{
|
||||||
|
"key": "group_abstract_builder",
|
||||||
|
"title": "Abstract - Builder",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"key": "field_builder_sections",
|
||||||
|
"label": "Section(s)",
|
||||||
|
"name": "sections",
|
||||||
|
"aria-label": "",
|
||||||
|
"type": "flexible_content",
|
||||||
|
"instructions": "",
|
||||||
|
"required": 0,
|
||||||
|
"conditional_logic": 0,
|
||||||
|
"wrapper": {
|
||||||
|
"width": "",
|
||||||
|
"class": "",
|
||||||
|
"id": ""
|
||||||
|
},
|
||||||
|
"acfe_flexible_advanced": 1,
|
||||||
|
"acfe_flexible_stylised_button": 0,
|
||||||
|
"acfe_flexible_hide_empty_message": 0,
|
||||||
|
"acfe_flexible_empty_message": "",
|
||||||
|
"acfe_flexible_layouts_templates": 0,
|
||||||
|
"acfe_flexible_layouts_placeholder": 0,
|
||||||
|
"acfe_flexible_layouts_thumbnails": 0,
|
||||||
|
"acfe_flexible_async": [],
|
||||||
|
"acfe_flexible_add_actions": [
|
||||||
|
"copy",
|
||||||
|
"title",
|
||||||
|
"toggle"
|
||||||
|
],
|
||||||
|
"acfe_flexible_remove_button": [],
|
||||||
|
"acfe_flexible_remove_top_actions": [],
|
||||||
|
"acfe_flexible_modal_edit": {
|
||||||
|
"acfe_flexible_modal_edit_enabled": "1",
|
||||||
|
"acfe_flexible_modal_edit_size": "xlarge"
|
||||||
|
},
|
||||||
|
"acfe_flexible_modal": {
|
||||||
|
"acfe_flexible_modal_enabled": "0",
|
||||||
|
"acfe_flexible_modal_title": false,
|
||||||
|
"acfe_flexible_modal_size": "xlarge",
|
||||||
|
"acfe_flexible_modal_col": "4",
|
||||||
|
"acfe_flexible_modal_categories": false
|
||||||
|
},
|
||||||
|
"acfe_flexible_modal_settings": {
|
||||||
|
"acfe_flexible_modal_settings_enabled": "1",
|
||||||
|
"acfe_flexible_modal_settings_size": "large",
|
||||||
|
"acfe_flexible_modal_settings_close": "1",
|
||||||
|
"acfe_flexible_modal_settings_close_label": ""
|
||||||
|
},
|
||||||
|
"layouts": {
|
||||||
|
"layout_6852f761e95b0": {
|
||||||
|
"key": "layout_6852f761e95b0",
|
||||||
|
"name": "text_block",
|
||||||
|
"label": "Bloc de texte",
|
||||||
|
"display": "block",
|
||||||
|
"sub_fields": [
|
||||||
|
{
|
||||||
|
"key": "field_68eeceb62b8a6",
|
||||||
|
"label": "Contenu",
|
||||||
|
"name": "content",
|
||||||
|
"aria-label": "",
|
||||||
|
"type": "wysiwyg",
|
||||||
|
"instructions": "",
|
||||||
|
"required": 1,
|
||||||
|
"conditional_logic": 0,
|
||||||
|
"wrapper": {
|
||||||
|
"width": "",
|
||||||
|
"class": "",
|
||||||
|
"id": ""
|
||||||
|
},
|
||||||
|
"default_value": "",
|
||||||
|
"acfe_wysiwyg_height": 300,
|
||||||
|
"acfe_wysiwyg_max_height": "",
|
||||||
|
"acfe_wysiwyg_valid_elements": "",
|
||||||
|
"acfe_wysiwyg_custom_style": "",
|
||||||
|
"acfe_wysiwyg_disable_wp_style": 0,
|
||||||
|
"acfe_wysiwyg_autoresize": 0,
|
||||||
|
"acfe_wysiwyg_disable_resize": 0,
|
||||||
|
"acfe_wysiwyg_remove_path": 0,
|
||||||
|
"acfe_wysiwyg_menubar": 0,
|
||||||
|
"acfe_wysiwyg_transparent": 0,
|
||||||
|
"acfe_wysiwyg_merge_toolbar": 0,
|
||||||
|
"acfe_wysiwyg_custom_toolbar": 0,
|
||||||
|
"required_message": "",
|
||||||
|
"allow_in_bindings": 0,
|
||||||
|
"tabs": "all",
|
||||||
|
"toolbar": "full",
|
||||||
|
"media_upload": 1,
|
||||||
|
"delay": 0,
|
||||||
|
"show_in_graphql": 1,
|
||||||
|
"graphql_description": "",
|
||||||
|
"graphql_field_name": "content",
|
||||||
|
"graphql_non_null": 1,
|
||||||
|
"acfe_wysiwyg_auto_init": 0,
|
||||||
|
"acfe_wysiwyg_min_height": 300,
|
||||||
|
"acfe_wysiwyg_toolbar_buttons": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"min": "",
|
||||||
|
"max": "",
|
||||||
|
"acfe_flexible_modal_edit_size": "",
|
||||||
|
"acfe_flexible_settings": [
|
||||||
|
"group_layout_contained"
|
||||||
|
],
|
||||||
|
"acfe_flexible_settings_size": "large",
|
||||||
|
"acfe_flexible_render_template": false,
|
||||||
|
"acfe_flexible_render_style": false,
|
||||||
|
"acfe_flexible_render_script": false,
|
||||||
|
"acfe_flexible_thumbnail": false,
|
||||||
|
"acfe_flexible_category": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"min": "",
|
||||||
|
"max": "",
|
||||||
|
"button_label": "Ajouter un élément",
|
||||||
|
"show_in_graphql": 1,
|
||||||
|
"graphql_description": "",
|
||||||
|
"graphql_field_name": "sections",
|
||||||
|
"graphql_non_null": 0,
|
||||||
|
"acfe_flexible_layouts_previews": false,
|
||||||
|
"acfe_flexible_close_button_label": "",
|
||||||
|
"acfe_flexible_layouts_state": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"location": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"param": "abstract"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"menu_order": 0,
|
||||||
|
"position": "acf_after_title",
|
||||||
|
"style": "seamless",
|
||||||
|
"label_placement": "top",
|
||||||
|
"instruction_placement": "label",
|
||||||
|
"hide_on_screen": [
|
||||||
|
"the_content"
|
||||||
|
],
|
||||||
|
"active": true,
|
||||||
|
"description": "",
|
||||||
|
"show_in_rest": 0,
|
||||||
|
"display_title": "",
|
||||||
|
"acfe_autosync": [
|
||||||
|
"json"
|
||||||
|
],
|
||||||
|
"acfe_form": 0,
|
||||||
|
"show_in_graphql": 1,
|
||||||
|
"graphql_field_name": "GroupAbstractBuilder",
|
||||||
|
"map_graphql_types_from_location_rules": 0,
|
||||||
|
"graphql_types": "",
|
||||||
|
"acfe_meta": "",
|
||||||
|
"acfe_note": "",
|
||||||
|
"modified": 1768358815
|
||||||
|
}
|
||||||
149
wp-content/themes/moonshine/acf-json/group_layout_contained.json
Normal file
149
wp-content/themes/moonshine/acf-json/group_layout_contained.json
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
{
|
||||||
|
"key": "group_layout_contained",
|
||||||
|
"title": "Layout - Contained",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"key": "field_68dc29d78941c",
|
||||||
|
"label": "Conteneur",
|
||||||
|
"name": "container",
|
||||||
|
"aria-label": "",
|
||||||
|
"type": "select",
|
||||||
|
"instructions": "",
|
||||||
|
"required": 1,
|
||||||
|
"conditional_logic": 0,
|
||||||
|
"wrapper": {
|
||||||
|
"width": "",
|
||||||
|
"class": "",
|
||||||
|
"id": ""
|
||||||
|
},
|
||||||
|
"choices": {
|
||||||
|
"default": "1536px",
|
||||||
|
"xl": "1280px",
|
||||||
|
"lg": "1024px",
|
||||||
|
"fluid": "Largeur fluide",
|
||||||
|
"none": "Pleine largeur"
|
||||||
|
},
|
||||||
|
"default_value": "default",
|
||||||
|
"return_format": "value",
|
||||||
|
"multiple": 0,
|
||||||
|
"max": "",
|
||||||
|
"prepend": "",
|
||||||
|
"append": "",
|
||||||
|
"required_message": "",
|
||||||
|
"allow_null": 0,
|
||||||
|
"allow_in_bindings": 0,
|
||||||
|
"ui": 0,
|
||||||
|
"show_in_graphql": 1,
|
||||||
|
"graphql_description": "",
|
||||||
|
"graphql_field_name": "container",
|
||||||
|
"graphql_non_null": 1,
|
||||||
|
"ajax": 0,
|
||||||
|
"placeholder": "",
|
||||||
|
"create_options": 0,
|
||||||
|
"save_options": 0,
|
||||||
|
"allow_custom": 0,
|
||||||
|
"search_placeholder": "",
|
||||||
|
"min": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "field_693c8c3b5ce50",
|
||||||
|
"label": "Espacement vertical",
|
||||||
|
"name": "vertical_padding",
|
||||||
|
"aria-label": "",
|
||||||
|
"type": "select",
|
||||||
|
"instructions": "",
|
||||||
|
"required": 1,
|
||||||
|
"conditional_logic": 0,
|
||||||
|
"wrapper": {
|
||||||
|
"width": "",
|
||||||
|
"class": "",
|
||||||
|
"id": ""
|
||||||
|
},
|
||||||
|
"choices": {
|
||||||
|
"sm": "Petit (12px)",
|
||||||
|
"md": "Medium (24px)",
|
||||||
|
"lg": "Grand (48px)"
|
||||||
|
},
|
||||||
|
"default_value": "md",
|
||||||
|
"return_format": "value",
|
||||||
|
"multiple": 0,
|
||||||
|
"allow_null": 0,
|
||||||
|
"allow_in_bindings": 0,
|
||||||
|
"ui": 0,
|
||||||
|
"show_in_graphql": 1,
|
||||||
|
"graphql_description": "",
|
||||||
|
"graphql_field_name": "verticalPadding",
|
||||||
|
"graphql_non_null": 1,
|
||||||
|
"ajax": 0,
|
||||||
|
"placeholder": "",
|
||||||
|
"create_options": 0,
|
||||||
|
"save_options": 0,
|
||||||
|
"allow_custom": 0,
|
||||||
|
"search_placeholder": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "field_693c8c945ce51",
|
||||||
|
"label": "Couleur d'arrière-plan",
|
||||||
|
"name": "bg_color",
|
||||||
|
"aria-label": "",
|
||||||
|
"type": "select",
|
||||||
|
"instructions": "",
|
||||||
|
"required": 1,
|
||||||
|
"conditional_logic": 0,
|
||||||
|
"wrapper": {
|
||||||
|
"width": "",
|
||||||
|
"class": "",
|
||||||
|
"id": ""
|
||||||
|
},
|
||||||
|
"choices": {
|
||||||
|
"default": "Par défaut",
|
||||||
|
"muted": "Atténué",
|
||||||
|
"inverted": "Inversé"
|
||||||
|
},
|
||||||
|
"default_value": "default",
|
||||||
|
"return_format": "value",
|
||||||
|
"multiple": 0,
|
||||||
|
"allow_null": 0,
|
||||||
|
"allow_in_bindings": 0,
|
||||||
|
"ui": 0,
|
||||||
|
"show_in_graphql": 1,
|
||||||
|
"graphql_description": "",
|
||||||
|
"graphql_field_name": "bgColor",
|
||||||
|
"graphql_non_null": 1,
|
||||||
|
"ajax": 0,
|
||||||
|
"placeholder": "",
|
||||||
|
"create_options": 0,
|
||||||
|
"save_options": 0,
|
||||||
|
"allow_custom": 0,
|
||||||
|
"search_placeholder": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"location": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"param": "abstract"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"menu_order": 0,
|
||||||
|
"position": "normal",
|
||||||
|
"style": "default",
|
||||||
|
"label_placement": "top",
|
||||||
|
"instruction_placement": "label",
|
||||||
|
"hide_on_screen": "",
|
||||||
|
"active": true,
|
||||||
|
"description": "",
|
||||||
|
"show_in_rest": 0,
|
||||||
|
"display_title": "",
|
||||||
|
"acfe_autosync": [
|
||||||
|
"json"
|
||||||
|
],
|
||||||
|
"acfe_form": 0,
|
||||||
|
"show_in_graphql": 1,
|
||||||
|
"graphql_field_name": "GroupLayoutContained",
|
||||||
|
"map_graphql_types_from_location_rules": 0,
|
||||||
|
"graphql_types": "",
|
||||||
|
"acfe_meta": "",
|
||||||
|
"acfe_note": "",
|
||||||
|
"modified": 1768358794
|
||||||
|
}
|
||||||
66
wp-content/themes/moonshine/acf-json/group_post_page.json
Normal file
66
wp-content/themes/moonshine/acf-json/group_post_page.json
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"key": "group_post_page",
|
||||||
|
"title": "Post - Page",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"key": "field_690cbda0abcbb",
|
||||||
|
"label": "Constructeur de page",
|
||||||
|
"name": "builder",
|
||||||
|
"aria-label": "",
|
||||||
|
"type": "clone",
|
||||||
|
"instructions": "",
|
||||||
|
"required": 0,
|
||||||
|
"conditional_logic": 0,
|
||||||
|
"wrapper": {
|
||||||
|
"width": "",
|
||||||
|
"class": "",
|
||||||
|
"id": ""
|
||||||
|
},
|
||||||
|
"graphql_field_name": "builder",
|
||||||
|
"clone": [
|
||||||
|
"group_abstract_builder"
|
||||||
|
],
|
||||||
|
"display": "seamless",
|
||||||
|
"layout": "block",
|
||||||
|
"prefix_label": 0,
|
||||||
|
"prefix_name": 0,
|
||||||
|
"acfe_seamless_style": 0,
|
||||||
|
"acfe_clone_modal": 0,
|
||||||
|
"acfe_clone_modal_close": 0,
|
||||||
|
"acfe_clone_modal_button": "",
|
||||||
|
"acfe_clone_modal_size": "large"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"location": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"param": "post_type",
|
||||||
|
"operator": "==",
|
||||||
|
"value": "page"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"menu_order": 0,
|
||||||
|
"position": "normal",
|
||||||
|
"style": "seamless",
|
||||||
|
"label_placement": "top",
|
||||||
|
"instruction_placement": "label",
|
||||||
|
"hide_on_screen": [
|
||||||
|
"the_content"
|
||||||
|
],
|
||||||
|
"active": true,
|
||||||
|
"description": "",
|
||||||
|
"show_in_rest": 0,
|
||||||
|
"display_title": "",
|
||||||
|
"acfe_autosync": [
|
||||||
|
"json"
|
||||||
|
],
|
||||||
|
"acfe_form": 0,
|
||||||
|
"show_in_graphql": 1,
|
||||||
|
"graphql_field_name": "GroupPostPage",
|
||||||
|
"map_graphql_types_from_location_rules": 0,
|
||||||
|
"graphql_types": "",
|
||||||
|
"acfe_meta": "",
|
||||||
|
"acfe_note": "",
|
||||||
|
"modified": 1768336934
|
||||||
|
}
|
||||||
13
wp-content/themes/moonshine/app/app.config.ts
Normal file
13
wp-content/themes/moonshine/app/app.config.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export default defineAppConfig({
|
||||||
|
ui: {
|
||||||
|
colors: {
|
||||||
|
primary: "indigo",
|
||||||
|
neutral: "neutral",
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
slots: {
|
||||||
|
base: "cursor-pointer",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,2 +1,10 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss" theme(static) source("../../..");
|
||||||
@import "@nuxt/ui";
|
@import "@nuxt/ui";
|
||||||
|
|
||||||
|
@import "./a11y.css";
|
||||||
|
@import "./containers.css";
|
||||||
|
@import "./links.css";
|
||||||
|
@import "./prose.css";
|
||||||
|
@import "./typography.css";
|
||||||
|
|
||||||
|
@import "./vendors/tinymce.css";
|
||||||
|
|||||||
7
wp-content/themes/moonshine/app/assets/css/a11y.css
Normal file
7
wp-content/themes/moonshine/app/assets/css/a11y.css
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
@utility disabled-default {
|
||||||
|
@apply disabled:cursor-not-allowed aria-disabled:cursor-not-allowed disabled:opacity-75 aria-disabled:opacity-75;
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility focus-default {
|
||||||
|
@apply focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2;
|
||||||
|
}
|
||||||
47
wp-content/themes/moonshine/app/assets/css/containers.css
Normal file
47
wp-content/themes/moonshine/app/assets/css/containers.css
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
:root {
|
||||||
|
--ui-container: var(--breakpoint-2xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container padding */
|
||||||
|
@utility px-container {
|
||||||
|
@apply px-4 sm:px-6 lg:px-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container sizes */
|
||||||
|
@utility container { @apply mx-auto px-container max-w-(--breakpoint-2xl); }
|
||||||
|
@utility container-xl { @apply container max-w-(--breakpoint-xl); }
|
||||||
|
@utility container-lg { @apply container max-w-(--breakpoint-lg); }
|
||||||
|
@utility container-md { @apply container max-w-(--breakpoint-md); }
|
||||||
|
@utility container-sm { @apply container max-w-(--breakpoint-sm); }
|
||||||
|
@utility container-fluid { @apply container max-w-screen; }
|
||||||
|
@utility container-none { @apply w-full max-w-screen; }
|
||||||
|
|
||||||
|
/* Split containers */
|
||||||
|
:root {
|
||||||
|
--container-outside-margin: 0;
|
||||||
|
--container-width: 100vw;
|
||||||
|
@variant sm {
|
||||||
|
--container-outside-margin: calc(50vw - theme("screens.sm") / 2);
|
||||||
|
--container-width: theme("screens.sm");
|
||||||
|
}
|
||||||
|
@variant md {
|
||||||
|
--container-outside-margin: calc(50vw - theme("screens.md") / 2);
|
||||||
|
--container-width: theme("screens.md");
|
||||||
|
}
|
||||||
|
@variant lg {
|
||||||
|
--container-outside-margin: calc(50vw - theme("screens.lg") / 2);
|
||||||
|
--container-width: theme("screens.lg");
|
||||||
|
}
|
||||||
|
@variant xl {
|
||||||
|
--container-outside-margin: calc(50vw - theme("screens.xl") / 2);
|
||||||
|
--container-width: theme("screens.xl");
|
||||||
|
}
|
||||||
|
@variant 2xl {
|
||||||
|
--container-outside-margin: calc(50vw - theme("screens.2xl") / 2);
|
||||||
|
--container-width: theme("screens.2xl");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility container-left { @apply ml-(--container-outside-margin) px-container;}
|
||||||
|
@utility container-right { @apply mr-(--container-outside-margin) px-container;}
|
||||||
|
@utility container-half { width: calc(var(--container-width) / 2);}
|
||||||
7
wp-content/themes/moonshine/app/assets/css/links.css
Normal file
7
wp-content/themes/moonshine/app/assets/css/links.css
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/* Variant to target all children links without specific link or button classes */
|
||||||
|
@custom-variant links (& a:not([class*='link-']):not([class*='button-']));
|
||||||
|
|
||||||
|
/* Link styles */
|
||||||
|
@utility link-base { @apply cursor-pointer disabled-default transition; }
|
||||||
|
@utility link-underline { @apply link-base underline hover:decoration-primary; }
|
||||||
|
@utility link-opacity { @apply link-base hover:opacity-80; }
|
||||||
16
wp-content/themes/moonshine/app/assets/css/prose.css
Normal file
16
wp-content/themes/moonshine/app/assets/css/prose.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
@utility prose {
|
||||||
|
/* Headings (allow class overrides) */
|
||||||
|
h1:not([class*="heading-"]) { @apply heading-1; }
|
||||||
|
h2:not([class*="heading-"]) { @apply heading-2; }
|
||||||
|
h3:not([class*="heading-"]) { @apply heading-3; }
|
||||||
|
h4:not([class*="heading-"]) { @apply heading-4; }
|
||||||
|
|
||||||
|
/* Links */
|
||||||
|
@apply links:link-underline;
|
||||||
|
|
||||||
|
/* Paragraphs */
|
||||||
|
p:not([class*="paragraph-"]) { @apply paragraph-base; }
|
||||||
|
|
||||||
|
/* Spacing */
|
||||||
|
@apply space-y-2;
|
||||||
|
}
|
||||||
10
wp-content/themes/moonshine/app/assets/css/typography.css
Normal file
10
wp-content/themes/moonshine/app/assets/css/typography.css
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/* Heading styles */
|
||||||
|
@utility heading-base { @apply font-bold tracking-tight };
|
||||||
|
@utility heading-1 { @apply heading-base text-4xl; }
|
||||||
|
@utility heading-2 { @apply heading-base text-3xl; }
|
||||||
|
@utility heading-3 { @apply heading-base text-2xl; }
|
||||||
|
@utility heading-4 { @apply heading-base text-xl; }
|
||||||
|
|
||||||
|
/* Paragraph styles */
|
||||||
|
@utility paragraph-base { @apply font-sans; }
|
||||||
|
@utility paragraph-lead { @apply paragraph-base text-2xl; }
|
||||||
3
wp-content/themes/moonshine/app/assets/css/vendors/tinymce.css
vendored
Normal file
3
wp-content/themes/moonshine/app/assets/css/vendors/tinymce.css
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
body#tinymce {
|
||||||
|
@apply prose;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const { isLoggedIn } = useAuth();
|
||||||
|
const attrs = computed(() => {
|
||||||
|
return isLoggedIn.value
|
||||||
|
? {
|
||||||
|
label: "Déconnexion",
|
||||||
|
icon: "i-lucide-log-out",
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
label: "Connexion",
|
||||||
|
icon: "i-lucide-log-in",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<AuthState>
|
||||||
|
<UButton to="/connexion" v-bind="attrs" color="neutral" />
|
||||||
|
</AuthState>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const { login } = useAuthConnexion();
|
||||||
|
const fields = [
|
||||||
|
{
|
||||||
|
name: "username",
|
||||||
|
type: "text" as const,
|
||||||
|
label: "Courriel",
|
||||||
|
placeholder: "Entrez votre courriel",
|
||||||
|
required: true,
|
||||||
|
}, {
|
||||||
|
name: "password",
|
||||||
|
label: "Mot de passe",
|
||||||
|
type: "password" as const,
|
||||||
|
placeholder: "Entrez votre mot de passe",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UAuthForm
|
||||||
|
:schema="authLoginFormSchema"
|
||||||
|
:fields="fields"
|
||||||
|
title="Connexion"
|
||||||
|
description="Veuillez vous identifier."
|
||||||
|
loading-auto
|
||||||
|
@submit="login"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const { logout } = useAuthConnexion();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full space-y-6">
|
||||||
|
<div class="flex flex-col text-center">
|
||||||
|
<div class="text-xl text-pretty font-semibold text-highlighted">
|
||||||
|
Déconnexion
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-base text-pretty text-muted">
|
||||||
|
Veuillez confirmer la déconnexion.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<UButton
|
||||||
|
icon="i-lucide-log-out"
|
||||||
|
block
|
||||||
|
loading-auto
|
||||||
|
to="#"
|
||||||
|
label="Déconnexion"
|
||||||
|
@click="logout()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-full space-y-6">
|
||||||
|
<div class="flex flex-col text-center">
|
||||||
|
<div class="text-xl text-pretty font-semibold text-highlighted">
|
||||||
|
Redirection en cours
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-base text-pretty text-muted">
|
||||||
|
Veuillez patienter...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fragment BuilderSections on GroupAbstractBuilder_Fields {
|
||||||
|
sections {
|
||||||
|
__typename
|
||||||
|
... on GroupAbstractBuilderSectionsTextBlockLayout {
|
||||||
|
... SectionTextBlock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { BuilderSectionsFragment } from "#graphql/operations";
|
||||||
|
|
||||||
|
const props = defineProps<BuilderSectionsFragment>();
|
||||||
|
const sections = computed(() => {
|
||||||
|
return (props.sections || [])
|
||||||
|
.filter((section) => !!section)
|
||||||
|
.map(({ __typename, ...attrs }) => ({
|
||||||
|
componentName: __typename.replace(/^GroupAbstractBuilderSections(.+?)Layout$/, "Section$1"),
|
||||||
|
attrs,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div id="builder-sections">
|
||||||
|
<Component
|
||||||
|
:is="componentName"
|
||||||
|
v-for="({ componentName, attrs }, index) in sections"
|
||||||
|
:key="index"
|
||||||
|
v-bind="attrs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
fragment LayoutContained on GroupLayoutContained_Fields {
|
||||||
|
container
|
||||||
|
verticalPadding
|
||||||
|
bgColor
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { LayoutContainedFragment } from "#graphql/operations";
|
||||||
|
import { tv, type VariantProps } from "tailwind-variants";
|
||||||
|
|
||||||
|
const props = defineProps<LayoutContainedFragment>();
|
||||||
|
|
||||||
|
const layoutWrapperVariants = tv({
|
||||||
|
slots: {
|
||||||
|
base: "",
|
||||||
|
inner: "",
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
container: {
|
||||||
|
default: { inner: "container" },
|
||||||
|
lg: { inner: "container-lg" },
|
||||||
|
xl: { inner: "container-xl" },
|
||||||
|
fluid: { inner: "container-fluid" },
|
||||||
|
none: { inner: "container-none" },
|
||||||
|
},
|
||||||
|
verticalPadding: {
|
||||||
|
sm: { base: "py-3" },
|
||||||
|
md: { base: "py-6" },
|
||||||
|
lg: { base: "py-12" },
|
||||||
|
},
|
||||||
|
bgColor: {
|
||||||
|
default: { base: "bg-default" },
|
||||||
|
muted: { base: "bg-muted" },
|
||||||
|
inverted: { base: "bg-inverted text-inverted" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
container: "default",
|
||||||
|
verticalPadding: "md",
|
||||||
|
bgColor: "default",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { base, inner } = layoutWrapperVariants({
|
||||||
|
container: props.container[0],
|
||||||
|
verticalPadding: props.verticalPadding[0],
|
||||||
|
bgColor: props.bgColor[0],
|
||||||
|
} as VariantProps<typeof layoutWrapperVariants>);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section :class="base()">
|
||||||
|
<div :class="inner()">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fragment NodePage on Page {
|
||||||
|
title
|
||||||
|
isFrontPage
|
||||||
|
groupPostPage {
|
||||||
|
... BuilderSections
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { NodePageFragment } from "#graphql/operations";
|
||||||
|
|
||||||
|
defineProps<NodePageFragment>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div id="node-page">
|
||||||
|
<h1 v-if="!isFrontPage" class="font-bold text-4xl">
|
||||||
|
{{ title }}
|
||||||
|
</h1>
|
||||||
|
<BuilderSections :sections="groupPostPage?.sections || []" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const { isLoggedIn } = useAuth();
|
||||||
|
const { isRedirecting } = useAuthConnexion();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section data-section-name="auth-connexion" class="py-12">
|
||||||
|
<div class="container-sm">
|
||||||
|
<AuthState>
|
||||||
|
<AuthRedirecting v-if="isRedirecting" />
|
||||||
|
<template v-else>
|
||||||
|
<AuthLogoutForm v-if="isLoggedIn" />
|
||||||
|
<AuthLoginForm v-else />
|
||||||
|
</template>
|
||||||
|
</AuthState>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
fragment SectionTextBlock on GroupAbstractBuilderSectionsTextBlockLayout {
|
||||||
|
content
|
||||||
|
layoutSettings {
|
||||||
|
...LayoutContained
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SectionTextBlockFragment } from "#graphql/operations";
|
||||||
|
|
||||||
|
defineProps<SectionTextBlockFragment>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<LayoutContained data-section-type="text-block" v-bind="layoutSettings!">
|
||||||
|
<UiProse :content="content" />
|
||||||
|
</LayoutContained>
|
||||||
|
</template>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { data } = await useGraphQLQuery("GeneralSettings", undefined, { cache: { ttl: 0 } });
|
const { data } = await useAsyncGraphQLQuery("GeneralSettings", undefined, { cache: { ttl: 0 } });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -3,5 +3,9 @@ const title = "Moonshine";
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UHeader :title="title" />
|
<UHeader :title="title">
|
||||||
|
<template #right>
|
||||||
|
<AuthConnexionButton />
|
||||||
|
</template>
|
||||||
|
</UHeader>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{ content: string }>();
|
||||||
|
const refContent = useTemplateRef("refContent");
|
||||||
|
useProseLinks(refContent);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="refContent" class="prose" v-html="content" />
|
||||||
|
</template>
|
||||||
6
wp-content/themes/moonshine/app/composables/useAuth.ts
Normal file
6
wp-content/themes/moonshine/app/composables/useAuth.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export function useAuth() {
|
||||||
|
const { loggedIn: isLoggedIn, session } = useUserSession();
|
||||||
|
const hasRole = (role: string) => session.value?.user?.roles?.includes(role) || false;
|
||||||
|
const isAdmin = computed(() => hasRole("administrator"));
|
||||||
|
return { isLoggedIn, hasRole, isAdmin };
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import type { FormSubmitEvent } from "@nuxt/ui";
|
||||||
|
|
||||||
|
const isRedirecting = ref(false);
|
||||||
|
|
||||||
|
export function useAuthConnexion() {
|
||||||
|
const toast = useToast();
|
||||||
|
const { fetch: refreshUserSession } = useUserSession();
|
||||||
|
const routeRedirect = useRoute().query.redirect as string || undefined;
|
||||||
|
|
||||||
|
// Helper: Redirect after login / logout
|
||||||
|
async function redirectTo(to: string | undefined) {
|
||||||
|
isRedirecting.value = true;
|
||||||
|
await delay(1000);
|
||||||
|
await refreshUserSession();
|
||||||
|
await navigateTo(to || routeRedirect || "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login
|
||||||
|
async function login({ data: body }: FormSubmitEvent<AuthLoginForm>, redirect?: string) {
|
||||||
|
try {
|
||||||
|
const { success, message } = await $fetch("/api/login", { method: "POST", body });
|
||||||
|
if (!success) {
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
toast.add({
|
||||||
|
title: "Connexion réussie",
|
||||||
|
color: "success",
|
||||||
|
description: message,
|
||||||
|
duration: 3000,
|
||||||
|
});
|
||||||
|
await redirectTo(redirect);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
toast.add({
|
||||||
|
title: "Erreur de connexion",
|
||||||
|
color: "error",
|
||||||
|
description: error instanceof Error ? error.message : "Une erreur est survenue lors de la connexion.",
|
||||||
|
duration: 5000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logout
|
||||||
|
async function logout(redirect?: string) {
|
||||||
|
try {
|
||||||
|
const result = await $fetch("/api/logout", { method: "POST" });
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error("Échec de la déconnexion.");
|
||||||
|
}
|
||||||
|
toast.add({
|
||||||
|
title: "Déconnexion réussie",
|
||||||
|
color: "success",
|
||||||
|
description: "Vous avez été déconnecté avec succès.",
|
||||||
|
duration: 3000,
|
||||||
|
});
|
||||||
|
await redirectTo(redirect);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
toast.add({
|
||||||
|
title: "Erreur de déconnexion",
|
||||||
|
color: "error",
|
||||||
|
description: error instanceof Error ? error.message : "Une erreur est survenue lors de la déconnexion.",
|
||||||
|
duration: 5000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isRedirecting, login, logout };
|
||||||
|
}
|
||||||
65
wp-content/themes/moonshine/app/composables/useProseLinks.ts
Normal file
65
wp-content/themes/moonshine/app/composables/useProseLinks.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
export function useProseLinks(refContent: Ref<HTMLElement | null>) {
|
||||||
|
const router = useRouter();
|
||||||
|
const { url } = useSiteConfig();
|
||||||
|
const siteUrl = new URL(url);
|
||||||
|
|
||||||
|
// Determine if the href is internal
|
||||||
|
const isInternal = (href: string) => {
|
||||||
|
if (!href) return false;
|
||||||
|
if (href.startsWith("/")) return true;
|
||||||
|
if (href.startsWith("#")) return false;
|
||||||
|
try {
|
||||||
|
const hrefUrl = new URL(href);
|
||||||
|
return hrefUrl.hostname === siteUrl.hostname;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert href to relative path
|
||||||
|
const convertToRelative = (href: string) => {
|
||||||
|
if (href.startsWith("/")) return href;
|
||||||
|
try {
|
||||||
|
const hrefUrl = new URL(href);
|
||||||
|
if (hrefUrl.hostname === siteUrl.hostname) {
|
||||||
|
return hrefUrl.pathname + hrefUrl.search + hrefUrl.hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// Invalid URL
|
||||||
|
}
|
||||||
|
return href;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Highjack click events to use router for internal links
|
||||||
|
const handleClick = (e: MouseEvent) => {
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
const link = target.closest("a");
|
||||||
|
if (!link) return;
|
||||||
|
const href = link.getAttribute("href");
|
||||||
|
if (!href) return;
|
||||||
|
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || link.target === "_blank" || link.hasAttribute("download")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isInternal(href)) {
|
||||||
|
e.preventDefault();
|
||||||
|
const path = convertToRelative(href);
|
||||||
|
router.push(path);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attach and detach event listeners
|
||||||
|
onMounted(() => {
|
||||||
|
const element = unref(refContent);
|
||||||
|
if (element) {
|
||||||
|
element.addEventListener("click", handleClick);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
const element = unref(refContent);
|
||||||
|
if (element) {
|
||||||
|
element.removeEventListener("click", handleClick);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
28
wp-content/themes/moonshine/app/error.vue
Normal file
28
wp-content/themes/moonshine/app/error.vue
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { fr } from "@nuxt/ui/locale";
|
||||||
|
import type { NuxtError } from "#app";
|
||||||
|
|
||||||
|
const props = defineProps<{ error: NuxtError }>();
|
||||||
|
const formattedError = computed(() => {
|
||||||
|
const error = {
|
||||||
|
statusCode: props.error.statusCode,
|
||||||
|
statusMessage: props.error.statusMessage,
|
||||||
|
message: props.error.message,
|
||||||
|
};
|
||||||
|
switch (error.statusCode) {
|
||||||
|
case 404:
|
||||||
|
error.statusMessage = "Page non trouvée";
|
||||||
|
break;
|
||||||
|
case 500:
|
||||||
|
error.message = "Erreur interne du serveur.";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UApp :locale="fr">
|
||||||
|
<UError :error="formattedError" />
|
||||||
|
</UApp>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
fragment AuthUser on User {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
roles {
|
||||||
|
nodes {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation AuthLogin($username: String!, $password: String!) {
|
||||||
|
login( input: { provider: PASSWORD, credentials: { username: $username, password: $password }}) {
|
||||||
|
authToken
|
||||||
|
refreshToken
|
||||||
|
user {
|
||||||
|
...AuthUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
mutation AuthRefreshToken($refreshToken: String!) {
|
||||||
|
refreshToken( input: { refreshToken: $refreshToken }) {
|
||||||
|
authToken
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
query NodeByUri($uri: String!) {
|
||||||
|
nodeByUri(uri: $uri) {
|
||||||
|
__typename
|
||||||
|
... on Page {
|
||||||
|
... NodePage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,19 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
const { path: uri } = useRoute();
|
||||||
|
const { data } = await useAsyncGraphQLQuery("NodeByUri", { uri });
|
||||||
|
|
||||||
|
// Resolve and validate Node component
|
||||||
|
if (!data.value.nodeByUri) {
|
||||||
|
throw createError({ statusCode: 404, message: `La page demandée est introuvable: ${uri}`, fatal: true });
|
||||||
|
}
|
||||||
|
const componentName = `Node${data.value.nodeByUri.__typename}`;
|
||||||
|
if (!useNuxtApp().vueApp.component(componentName)) {
|
||||||
|
throw createError({ statusCode: 404, message: `La page demandée ne peut pas être affichée correctement: ${componentName}`, fatal: true });
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div id="page-node-from-uri" />
|
<div v-if="data.nodeByUri" id="page-node-from-uri">
|
||||||
|
<Component :is="componentName" v-bind="data.nodeByUri" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
12
wp-content/themes/moonshine/app/pages/connexion.vue
Normal file
12
wp-content/themes/moonshine/app/pages/connexion.vue
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const { isRedirecting } = useAuthConnexion();
|
||||||
|
onBeforeMount(() => {
|
||||||
|
isRedirecting.value = false;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div id="page-connexion">
|
||||||
|
<SectionAuthConnexion />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
2
wp-content/themes/moonshine/editor-style.css
Normal file
2
wp-content/themes/moonshine/editor-style.css
Normal file
File diff suppressed because one or more lines are too long
@@ -2,3 +2,6 @@
|
|||||||
|
|
||||||
// Core
|
// Core
|
||||||
require_once __DIR__ . '/includes/core/theme-setup.php';
|
require_once __DIR__ . '/includes/core/theme-setup.php';
|
||||||
|
|
||||||
|
// Vendors
|
||||||
|
require_once __DIR__ . '/includes/vendors/tinymce.php';
|
||||||
|
|||||||
129
wp-content/themes/moonshine/includes/vendors/tinymce.php
vendored
Normal file
129
wp-content/themes/moonshine/includes/vendors/tinymce.php
vendored
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Enable formats (styleselect) in TinyMCE
|
||||||
|
add_filter( 'mce_buttons', 'moonshine_tinymce_styleselect' );
|
||||||
|
function moonshine_tinymce_styleselect( $buttons ) {
|
||||||
|
array_unshift( $buttons, 'styleselect' );
|
||||||
|
return $buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure TinyMCE
|
||||||
|
add_filter( 'tiny_mce_before_init', 'moonshine_tiny_mce_before_init' );
|
||||||
|
function moonshine_tiny_mce_before_init( $settings ) {
|
||||||
|
// Reset TinyMCE editor CSS
|
||||||
|
if ( isset( $settings['content_css'] ) ) {
|
||||||
|
$content_css = explode( ',', $settings['content_css'] );
|
||||||
|
unset( $content_css[1] ); // wp-content.min.css
|
||||||
|
$settings['content_css'] = implode( ',', $content_css );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format styles
|
||||||
|
$settings['style_formats'] = wp_json_encode(
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
'title' => __( "Link styles", 'moonshine' ),
|
||||||
|
'items' => array(// Link styles
|
||||||
|
array(
|
||||||
|
'title' => "Lien (opacité)",
|
||||||
|
'selector' => 'a',
|
||||||
|
'classes' => 'link-opacity',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => __( "Inline styles", 'moonshine' ),
|
||||||
|
'items' => array(// Inline styles
|
||||||
|
array(
|
||||||
|
'title' => __( "Semi-bold", 'moonshine' ),
|
||||||
|
'inline' => 'span',
|
||||||
|
'classes' => 'font-semibold',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => __( "Paragraph styles", 'moonshine' ),
|
||||||
|
'items' => array(// Paragraph styles
|
||||||
|
array(
|
||||||
|
'title' => "Paragraphe vedette",
|
||||||
|
'block' => 'p',
|
||||||
|
'classes' => 'paragraph-lead',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => __( "Heading styles", 'moonshine' ),
|
||||||
|
'items' => array(// Heading styles
|
||||||
|
array(
|
||||||
|
'title' => "Titre 1",
|
||||||
|
'selector' => 'h1,h2,h3,h4',
|
||||||
|
'classes' => 'heading-1',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => "Titre 2",
|
||||||
|
'selector' => 'h1,h2,h3,h4',
|
||||||
|
'classes' => 'heading-2',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => "Titre 3",
|
||||||
|
'selector' => 'h1,h2,h3,h4',
|
||||||
|
'classes' => 'heading-3',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'title' => "Titre 4",
|
||||||
|
'selector' => 'h1,h2,h3,h4',
|
||||||
|
'classes' => 'heading-4',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Block styles
|
||||||
|
$settings['block_formats'] = implode(
|
||||||
|
';',
|
||||||
|
array(
|
||||||
|
'Paragraph=p',
|
||||||
|
'Heading 1=h1',
|
||||||
|
'Heading 2=h2',
|
||||||
|
'Heading 3=h3',
|
||||||
|
'Heading 4=h4',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override TinyMCE editor styles
|
||||||
|
add_filter( 'mce_css', 'moonshine_override_editor_styles' );
|
||||||
|
function moonshine_override_editor_styles() {
|
||||||
|
return get_stylesheet_directory_uri() . '/editor-style.css';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove default TinyMCE styles for all editors (WordPress & ACF)
|
||||||
|
add_action( 'admin_print_footer_scripts', 'moonshine_remove_tinymce_default_styles', 99 );
|
||||||
|
function moonshine_remove_tinymce_default_styles() {
|
||||||
|
?>
|
||||||
|
<script>
|
||||||
|
(function($) {
|
||||||
|
if (typeof tinymce !== 'undefined') {
|
||||||
|
tinymce.on('AddEditor', function({editor}) {
|
||||||
|
editor.on('init', function() {
|
||||||
|
$(editor.iframeElement).contents().find("link[href*='content.min.css']").remove();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})(jQuery);
|
||||||
|
</script>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
// Convert absolute URLs to relative in link query
|
||||||
|
add_filter( 'wp_link_query', 'moonshine_tinymce_relative_urls' );
|
||||||
|
function moonshine_tinymce_relative_urls( $results ) {
|
||||||
|
foreach ( $results as &$result ) {
|
||||||
|
if ( empty( $result['permalink'] ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$result['permalink'] = str_replace( get_home_url(), '', $result['permalink'] );
|
||||||
|
}
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ export default defineNuxtConfig({
|
|||||||
"@lewebsimple/nuxt-graphql",
|
"@lewebsimple/nuxt-graphql",
|
||||||
"@nuxt/eslint",
|
"@nuxt/eslint",
|
||||||
"@nuxt/ui",
|
"@nuxt/ui",
|
||||||
|
"@nuxtjs/seo",
|
||||||
|
"nuxt-auth-utils",
|
||||||
],
|
],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
@@ -17,6 +19,12 @@ export default defineNuxtConfig({
|
|||||||
|
|
||||||
css: ["~/assets/css/_main.css"],
|
css: ["~/assets/css/_main.css"],
|
||||||
|
|
||||||
|
site: {
|
||||||
|
url: "https://wp-headless.ledevsimple.ca",
|
||||||
|
name: "WP Headless",
|
||||||
|
defaultLocale: "fr",
|
||||||
|
},
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
colorMode: false,
|
colorMode: false,
|
||||||
},
|
},
|
||||||
@@ -36,12 +44,20 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
graphql: {
|
graphql: {
|
||||||
schemas: {
|
yoga: {
|
||||||
wp: {
|
context: ["~~/server/graphql/context"],
|
||||||
type: "remote",
|
schemas: {
|
||||||
url: `${process.env.NUXT_WP_URL || "https://wp-headless.ledevsimple.ca"}/graphql`,
|
wp: {
|
||||||
|
type: "remote",
|
||||||
|
url: `${process.env.NUXT_WP_URL || "https://wp-headless.ledevsimple.ca"}/graphql`,
|
||||||
|
hooks: ["~~/server/graphql/wp-hooks"],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
saveSdl: "server/graphql/schema.graphql",
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sitemap: {
|
||||||
|
zeroRuntime: true,
|
||||||
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,27 +1,32 @@
|
|||||||
{
|
{
|
||||||
"name": "@lewebsimple/moonshine",
|
"name": "@lewebsimple/moonshine",
|
||||||
"description": "Headless WordPress theme based on Nuxt.",
|
"description": "Headless WordPress theme based on Nuxt.",
|
||||||
"version": "0.1.1",
|
"version": "0.1.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nuxt build",
|
"build": "nuxt build",
|
||||||
"dev": "nuxt dev --host 0.0.0.0",
|
"editor-style": "pnpx @tailwindcss/cli -i ./app/assets/css/_main.css -o ./editor-style.css --minify",
|
||||||
|
"dev": "nuxt dev",
|
||||||
"lint": "eslint --fix .",
|
"lint": "eslint --fix .",
|
||||||
"postinstall": "pnpm --sequential /postinstall:.*/",
|
"postinstall": "pnpm --sequential /postinstall:.*/",
|
||||||
"postinstall:nuxt": "nuxt prepare",
|
"postinstall:nuxt": "nuxt prepare",
|
||||||
"preview": "nuxt preview",
|
"preview": "nuxt preview",
|
||||||
"release": "pnpm lint && pnpm typecheck && changelogen --noAuthors --release --push",
|
"release": "pnpm lint && changelogen --noAuthors --release --push",
|
||||||
"typecheck": "nuxt typecheck"
|
"typecheck": "nuxt typecheck"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify-json/lucide": "^1.2.84",
|
"@iconify-json/lucide": "^1.2.86",
|
||||||
"@lewebsimple/nuxt-graphql": "^0.3.5",
|
"@lewebsimple/nuxt-graphql": "^0.5.10",
|
||||||
"@nuxt/ui": "4.3.0",
|
"@nuxt/ui": "4.3.0",
|
||||||
|
"@nuxtjs/seo": "^3.3.0",
|
||||||
|
"jwt-decode": "^4.0.0",
|
||||||
"nuxt": "^4.2.2",
|
"nuxt": "^4.2.2",
|
||||||
|
"nuxt-auth-utils": "^0.5.27",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"vue": "^3.5.26",
|
"vue": "^3.5.27",
|
||||||
"vue-router": "^4.6.4"
|
"vue-router": "^4.6.4",
|
||||||
|
"zod": "^4.3.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxt/eslint": "^1.12.1",
|
"@nuxt/eslint": "^1.12.1",
|
||||||
@@ -30,6 +35,12 @@
|
|||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vue-tsc": "^3.2.2"
|
"vue-tsc": "^3.2.2"
|
||||||
},
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {
|
||||||
|
"@tiptap/core": "3.14.0",
|
||||||
|
"@tiptap/pm": "3.14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"changelog": {
|
"changelog": {
|
||||||
"types": {
|
"types": {
|
||||||
"chore": false
|
"chore": false
|
||||||
|
|||||||
4071
wp-content/themes/moonshine/pnpm-lock.yaml
generated
4071
wp-content/themes/moonshine/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
5
wp-content/themes/moonshine/pnpm-workspace.yaml
Normal file
5
wp-content/themes/moonshine/pnpm-workspace.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
onlyBuiltDependencies:
|
||||||
|
- '@parcel/watcher'
|
||||||
|
- esbuild
|
||||||
|
- unrs-resolver
|
||||||
|
- vue-demi
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
User-Agent: *
|
|
||||||
Disallow:
|
|
||||||
21
wp-content/themes/moonshine/server/api/login.post.ts
Normal file
21
wp-content/themes/moonshine/server/api/login.post.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
try {
|
||||||
|
const variables = await readBody<AuthLoginForm>(event);
|
||||||
|
const { data } = await useServerGraphQLMutation(event, "AuthLogin", variables);
|
||||||
|
if (!data?.login) {
|
||||||
|
throw new Error("INVALID_LOGIN");
|
||||||
|
}
|
||||||
|
if (!await handleLogin(event, data)) {
|
||||||
|
throw new Error("LOGIN_FAILED");
|
||||||
|
}
|
||||||
|
return { success: true, message: "Connexion réussie" };
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
const messages = {
|
||||||
|
INVALID_LOGIN: "Identifiants invalides. Veuillez réessayer.",
|
||||||
|
LOGIN_FAILED: "Une erreur est survenue lors de la connexion. Veuillez réessayer plus tard.",
|
||||||
|
};
|
||||||
|
const message = (error instanceof Error && error.message in messages) ? error.message : "LOGIN_FAILED";
|
||||||
|
return { success: false, message: messages[message as keyof typeof messages] };
|
||||||
|
}
|
||||||
|
});
|
||||||
12
wp-content/themes/moonshine/server/api/logout.post.ts
Normal file
12
wp-content/themes/moonshine/server/api/logout.post.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { defineEventHandler } from "h3";
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
try {
|
||||||
|
await handleLogout(event);
|
||||||
|
return { success: true, message: "Déconnexion réussie" };
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : "Une erreur est survenue lors de la déconnexion.";
|
||||||
|
return { success: false, message };
|
||||||
|
}
|
||||||
|
});
|
||||||
6
wp-content/themes/moonshine/server/graphql/context.ts
Normal file
6
wp-content/themes/moonshine/server/graphql/context.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export default defineGraphQLContext(async (event) => {
|
||||||
|
const authToken = await getAuthToken(event);
|
||||||
|
return {
|
||||||
|
authToken,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -39,6 +39,18 @@ type ACFE_AdvancedLink_Url implements ACFE_AdvancedLink {
|
|||||||
url: String
|
url: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"""A Field Group managed by ACF"""
|
||||||
|
interface AcfFieldGroup {
|
||||||
|
"""The name of the field group"""
|
||||||
|
fieldGroupName: String @deprecated(reason: "Use __typename instead")
|
||||||
|
}
|
||||||
|
|
||||||
|
"""Fields associated with an ACF Field Group"""
|
||||||
|
interface AcfFieldGroupFields {
|
||||||
|
"""The name of the field group"""
|
||||||
|
fieldGroupName: String @deprecated(reason: "Use __typename instead")
|
||||||
|
}
|
||||||
|
|
||||||
"""The Headless Login authentication data."""
|
"""The Headless Login authentication data."""
|
||||||
type AuthenticationData {
|
type AuthenticationData {
|
||||||
"""A new authentication token to use in future requests."""
|
"""A new authentication token to use in future requests."""
|
||||||
@@ -3295,6 +3307,179 @@ enum GoogleProviderPromptTypeEnum {
|
|||||||
SELECT_ACCOUNT
|
SELECT_ACCOUNT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
The "GroupAbstractBuilder" Field Group. Added to the Schema by "WPGraphQL for ACF".
|
||||||
|
"""
|
||||||
|
type GroupAbstractBuilder implements AcfFieldGroup & AcfFieldGroupFields & GroupAbstractBuilder_Fields {
|
||||||
|
"""The name of the field group"""
|
||||||
|
fieldGroupName: String @deprecated(reason: "Use __typename instead")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Field of the "flexible_content" Field Type added to the schema as part of the "GroupAbstractBuilder" Field Group
|
||||||
|
"""
|
||||||
|
sections: [GroupAbstractBuilderSections_Layout]
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
The "GroupAbstractBuilderSectionsLayoutSettings" Field Group. Added to the Schema by "WPGraphQL for ACF".
|
||||||
|
"""
|
||||||
|
type GroupAbstractBuilderSectionsLayoutSettings implements AcfFieldGroup & AcfFieldGroupFields & GroupAbstractBuilderSectionsLayoutSettings_Fields & GroupLayoutContained_Fields {
|
||||||
|
"""
|
||||||
|
Field of the "select" Field Type added to the schema as part of the "GroupLayoutContained" Field Group
|
||||||
|
"""
|
||||||
|
bgColor: [String]!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Field of the "select" Field Type added to the schema as part of the "GroupLayoutContained" Field Group
|
||||||
|
"""
|
||||||
|
container: [String]!
|
||||||
|
|
||||||
|
"""The name of the field group"""
|
||||||
|
fieldGroupName: String @deprecated(reason: "Use __typename instead")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Field of the "select" Field Type added to the schema as part of the "GroupLayoutContained" Field Group
|
||||||
|
"""
|
||||||
|
verticalPadding: [String]!
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Interface representing fields of the ACF "GroupAbstractBuilderSectionsLayoutSettings" Field Group
|
||||||
|
"""
|
||||||
|
interface GroupAbstractBuilderSectionsLayoutSettings_Fields implements AcfFieldGroup & AcfFieldGroupFields {
|
||||||
|
"""The name of the field group"""
|
||||||
|
fieldGroupName: String @deprecated(reason: "Use __typename instead")
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
The "GroupAbstractBuilderSectionsTextBlockLayout" Field Group. Added to the Schema by "WPGraphQL for ACF".
|
||||||
|
"""
|
||||||
|
type GroupAbstractBuilderSectionsTextBlockLayout implements AcfFieldGroup & AcfFieldGroupFields & GroupAbstractBuilderSectionsTextBlockLayout_Fields & GroupAbstractBuilderSections_Layout {
|
||||||
|
"""
|
||||||
|
Field of the "wysiwyg" Field Type added to the schema as part of the "GroupAbstractBuilderSectionsTextBlockLayout" Field Group
|
||||||
|
"""
|
||||||
|
content: String!
|
||||||
|
|
||||||
|
"""The name of the field group"""
|
||||||
|
fieldGroupName: String @deprecated(reason: "Use __typename instead")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Field of the "clone" Field Type added to the schema as part of the "GroupAbstractBuilderSectionsTextBlockLayout" Field Group
|
||||||
|
"""
|
||||||
|
layoutSettings: GroupAbstractBuilderSectionsLayoutSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Interface representing fields of the ACF "GroupAbstractBuilderSectionsTextBlockLayout" Field Group
|
||||||
|
"""
|
||||||
|
interface GroupAbstractBuilderSectionsTextBlockLayout_Fields implements AcfFieldGroup & AcfFieldGroupFields & GroupAbstractBuilderSections_Layout {
|
||||||
|
"""
|
||||||
|
Field of the "wysiwyg" Field Type added to the schema as part of the "GroupAbstractBuilderSectionsTextBlockLayout" Field Group
|
||||||
|
"""
|
||||||
|
content: String!
|
||||||
|
|
||||||
|
"""The name of the field group"""
|
||||||
|
fieldGroupName: String @deprecated(reason: "Use __typename instead")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Field of the "clone" Field Type added to the schema as part of the "GroupAbstractBuilderSectionsTextBlockLayout" Field Group
|
||||||
|
"""
|
||||||
|
layoutSettings: GroupAbstractBuilderSectionsLayoutSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Layout of the "sections" Field of the "GroupAbstractBuilder" Field Group Field
|
||||||
|
"""
|
||||||
|
interface GroupAbstractBuilderSections_Layout {
|
||||||
|
"""The name of the ACF Flex Field Layout"""
|
||||||
|
fieldGroupName: String
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Interface representing fields of the ACF "GroupAbstractBuilder" Field Group
|
||||||
|
"""
|
||||||
|
interface GroupAbstractBuilder_Fields implements AcfFieldGroup & AcfFieldGroupFields {
|
||||||
|
"""The name of the field group"""
|
||||||
|
fieldGroupName: String @deprecated(reason: "Use __typename instead")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Field of the "flexible_content" Field Type added to the schema as part of the "GroupAbstractBuilder" Field Group
|
||||||
|
"""
|
||||||
|
sections: [GroupAbstractBuilderSections_Layout]
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
The "GroupLayoutContained" Field Group. Added to the Schema by "WPGraphQL for ACF".
|
||||||
|
"""
|
||||||
|
type GroupLayoutContained implements AcfFieldGroup & AcfFieldGroupFields & GroupLayoutContained_Fields {
|
||||||
|
"""
|
||||||
|
Field of the "select" Field Type added to the schema as part of the "GroupLayoutContained" Field Group
|
||||||
|
"""
|
||||||
|
bgColor: [String]!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Field of the "select" Field Type added to the schema as part of the "GroupLayoutContained" Field Group
|
||||||
|
"""
|
||||||
|
container: [String]!
|
||||||
|
|
||||||
|
"""The name of the field group"""
|
||||||
|
fieldGroupName: String @deprecated(reason: "Use __typename instead")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Field of the "select" Field Type added to the schema as part of the "GroupLayoutContained" Field Group
|
||||||
|
"""
|
||||||
|
verticalPadding: [String]!
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Interface representing fields of the ACF "GroupLayoutContained" Field Group
|
||||||
|
"""
|
||||||
|
interface GroupLayoutContained_Fields implements AcfFieldGroup & AcfFieldGroupFields {
|
||||||
|
"""
|
||||||
|
Field of the "select" Field Type added to the schema as part of the "GroupLayoutContained" Field Group
|
||||||
|
"""
|
||||||
|
bgColor: [String]!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Field of the "select" Field Type added to the schema as part of the "GroupLayoutContained" Field Group
|
||||||
|
"""
|
||||||
|
container: [String]!
|
||||||
|
|
||||||
|
"""The name of the field group"""
|
||||||
|
fieldGroupName: String @deprecated(reason: "Use __typename instead")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Field of the "select" Field Type added to the schema as part of the "GroupLayoutContained" Field Group
|
||||||
|
"""
|
||||||
|
verticalPadding: [String]!
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
The "GroupPostPage" Field Group. Added to the Schema by "WPGraphQL for ACF".
|
||||||
|
"""
|
||||||
|
type GroupPostPage implements AcfFieldGroup & AcfFieldGroupFields & GroupAbstractBuilder_Fields & GroupPostPage_Fields {
|
||||||
|
"""The name of the field group"""
|
||||||
|
fieldGroupName: String @deprecated(reason: "Use __typename instead")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Field of the "flexible_content" Field Type added to the schema as part of the "GroupAbstractBuilder" Field Group
|
||||||
|
"""
|
||||||
|
sections: [GroupAbstractBuilderSections_Layout]
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Interface representing fields of the ACF "GroupPostPage" Field Group
|
||||||
|
"""
|
||||||
|
interface GroupPostPage_Fields implements AcfFieldGroup & AcfFieldGroupFields & GroupAbstractBuilder_Fields {
|
||||||
|
"""The name of the field group"""
|
||||||
|
fieldGroupName: String @deprecated(reason: "Use __typename instead")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Field of the "flexible_content" Field Type added to the schema as part of the "GroupAbstractBuilder" Field Group
|
||||||
|
"""
|
||||||
|
sections: [GroupAbstractBuilderSections_Layout]
|
||||||
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Content that can be organized in a parent-child structure. Provides fields for navigating up and down the hierarchy and maintaining structured relationships.
|
Content that can be organized in a parent-child structure. Provides fields for navigating up and down the hierarchy and maintaining structured relationships.
|
||||||
"""
|
"""
|
||||||
@@ -5759,7 +5944,7 @@ enum OrderEnum {
|
|||||||
"""
|
"""
|
||||||
A standalone content entry generally used for static, non-chronological content such as "About Us" or "Contact" pages.
|
A standalone content entry generally used for static, non-chronological content such as "About Us" or "Contact" pages.
|
||||||
"""
|
"""
|
||||||
type Page implements ContentNode & DatabaseIdentifier & HierarchicalContentNode & HierarchicalNode & MenuItemLinkable & Node & NodeWithAuthor & NodeWithContentEditor & NodeWithFeaturedImage & NodeWithPageAttributes & NodeWithRevisions & NodeWithTemplate & NodeWithTitle & Previewable & UniformResourceIdentifiable {
|
type Page implements ContentNode & DatabaseIdentifier & HierarchicalContentNode & HierarchicalNode & MenuItemLinkable & Node & NodeWithAuthor & NodeWithContentEditor & NodeWithFeaturedImage & NodeWithPageAttributes & NodeWithRevisions & NodeWithTemplate & NodeWithTitle & Previewable & UniformResourceIdentifiable & WithAcfGroupPostPage {
|
||||||
"""
|
"""
|
||||||
Returns ancestors of the node. Default ordered as lowest (closest to the child) to highest (closest to the root).
|
Returns ancestors of the node. Default ordered as lowest (closest to the child) to highest (closest to the root).
|
||||||
"""
|
"""
|
||||||
@@ -5902,6 +6087,9 @@ type Page implements ContentNode & DatabaseIdentifier & HierarchicalContentNode
|
|||||||
"""Globally unique ID of the featured image assigned to the node"""
|
"""Globally unique ID of the featured image assigned to the node"""
|
||||||
featuredImageId: ID
|
featuredImageId: ID
|
||||||
|
|
||||||
|
"""Fields of the GroupPostPage ACF Field Group"""
|
||||||
|
groupPostPage: GroupPostPage
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The global unique identifier for this post. This currently matches the value stored in WP_Post->guid and the guid column in the "post_objects" database table.
|
The global unique identifier for this post. This currently matches the value stored in WP_Post->guid and the guid column in the "post_objects" database table.
|
||||||
"""
|
"""
|
||||||
@@ -13884,6 +14072,14 @@ interface WPPageInfo implements PageInfo {
|
|||||||
startCursor: String
|
startCursor: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Provides access to fields of the "GroupPostPage" ACF Field Group via the "groupPostPage" field
|
||||||
|
"""
|
||||||
|
interface WithAcfGroupPostPage {
|
||||||
|
"""Fields of the GroupPostPage ACF Field Group"""
|
||||||
|
groupPostPage: GroupPostPage
|
||||||
|
}
|
||||||
|
|
||||||
"""The writing setting type"""
|
"""The writing setting type"""
|
||||||
type WritingSettings {
|
type WritingSettings {
|
||||||
"""Catégorie d’article par défaut."""
|
"""Catégorie d’article par défaut."""
|
||||||
|
|||||||
11
wp-content/themes/moonshine/server/graphql/wp-hooks.ts
Normal file
11
wp-content/themes/moonshine/server/graphql/wp-hooks.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export default defineRemoteExecutorHooks({
|
||||||
|
onRequest(request) {
|
||||||
|
if (request.context.authToken) {
|
||||||
|
request.extensions ??= {};
|
||||||
|
request.extensions.headers = {
|
||||||
|
...request.extensions.headers,
|
||||||
|
Authorization: `Bearer ${request.context.authToken}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
78
wp-content/themes/moonshine/server/utils/auth.ts
Normal file
78
wp-content/themes/moonshine/server/utils/auth.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import type { H3Event } from "h3";
|
||||||
|
import { GraphQLClient } from "graphql-request";
|
||||||
|
import { jwtDecode } from "jwt-decode";
|
||||||
|
import type { User } from "#auth-utils";
|
||||||
|
import type { AuthUserFragment, AuthLoginMutationResult } from "#graphql/operations";
|
||||||
|
import { AuthRefreshTokenDocument } from "#graphql/operations";
|
||||||
|
|
||||||
|
// Handle login result and store user session
|
||||||
|
export async function handleLogin(event: H3Event, loginResult: AuthLoginMutationResult) {
|
||||||
|
if (!loginResult?.login) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const { user, authToken, refreshToken } = loginResult.login;
|
||||||
|
if (!user || !authToken || !refreshToken) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
await setUserSession(event, {
|
||||||
|
user: getAuthUser(user),
|
||||||
|
secure: {
|
||||||
|
authToken,
|
||||||
|
refreshToken,
|
||||||
|
},
|
||||||
|
loggedInAt: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle user logout by clearing session
|
||||||
|
export async function handleLogout(event: H3Event) {
|
||||||
|
await clearUserSession(event);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert AuthUserFragment to nuxt-auth-utils User
|
||||||
|
function getAuthUser(user: AuthUserFragment): User {
|
||||||
|
return {
|
||||||
|
id: Number(user.id),
|
||||||
|
email: user.email!,
|
||||||
|
roles: extractNodes(user.roles).map(({ name }) => name!) || [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh auth token by calling remote GraphQL endpoint directly
|
||||||
|
export async function refreshAuthToken(refreshToken: string): Promise<string | undefined> {
|
||||||
|
const client = new GraphQLClient(`${process.env.NUXT_WP_URL || "https://wp-headless.ledevsimple.ca"}/graphql`);
|
||||||
|
const data = await client.request(AuthRefreshTokenDocument, { refreshToken });
|
||||||
|
return data.refreshToken?.authToken || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get auth token from user session (refresh if needed)
|
||||||
|
export async function getAuthToken(event: H3Event): Promise<string | undefined> {
|
||||||
|
// Retrieve user session, return if none
|
||||||
|
const session = await getUserSession(event);
|
||||||
|
if (!session.secure) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract tokens and check expiration
|
||||||
|
const { authToken, refreshToken } = session.secure;
|
||||||
|
const decoded = jwtDecode<{ exp: number }>(authToken);
|
||||||
|
const isExpired = decoded.exp * 1000 < Date.now();
|
||||||
|
if (isExpired) {
|
||||||
|
try {
|
||||||
|
const newAuthToken = await refreshAuthToken(refreshToken);
|
||||||
|
if (!newAuthToken) {
|
||||||
|
throw new Error("Impossible de rafraîchir le jeton d'authentification.");
|
||||||
|
}
|
||||||
|
session.secure.authToken = newAuthToken;
|
||||||
|
await setUserSession(event, session);
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
await clearUserSession(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return session.secure.authToken;
|
||||||
|
}
|
||||||
19
wp-content/themes/moonshine/shared/types/auth.d.ts
vendored
Normal file
19
wp-content/themes/moonshine/shared/types/auth.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// auth.d.ts
|
||||||
|
declare module "#auth-utils" {
|
||||||
|
interface User {
|
||||||
|
id: number;
|
||||||
|
email: string;
|
||||||
|
roles: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserSession {
|
||||||
|
loggedInAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SecureSessionData {
|
||||||
|
authToken: string;
|
||||||
|
refreshToken: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { };
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
export const authLoginFormSchema = z.object({
|
||||||
|
username: z.email("Courriel invalide"),
|
||||||
|
password: z.string("Veuillez saisir votre mot de passe"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AuthLoginForm = z.infer<typeof authLoginFormSchema>;
|
||||||
3
wp-content/themes/moonshine/shared/utils/delay.ts
Normal file
3
wp-content/themes/moonshine/shared/utils/delay.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export async function delay(ms: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
4
wp-content/themes/moonshine/shared/utils/graphql.ts
Normal file
4
wp-content/themes/moonshine/shared/utils/graphql.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// Helper: Extracts nodes from a GraphQL connection object, returning an empty array if nodes are absent.
|
||||||
|
export function extractNodes<T>(connection: { nodes?: readonly T[] } | null | undefined): readonly T[] {
|
||||||
|
return connection?.nodes ?? [];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user