diff --git a/addons/website/static/src/client_actions/website_preview/website_preview.js b/addons/website/static/src/client_actions/website_preview/website_preview.js index 490c0b88a05..5d3ab2b4815 100644 --- a/addons/website/static/src/client_actions/website_preview/website_preview.js +++ b/addons/website/static/src/client_actions/website_preview/website_preview.js @@ -4,7 +4,6 @@ import { registry } from '@web/core/registry'; import { useService } from '@web/core/utils/hooks'; import core from 'web.core'; import { AceEditorAdapterComponent } from '../../components/ace_editor/ace_editor'; -import { PagePropertiesDialogWrapper } from '../../components/dialog/page_properties'; import { WebsiteEditorComponent } from '../../components/editor/editor'; import { WebsiteTranslator } from '../../components/translator/translator'; import {OptimizeSEODialog} from '@website/components/dialog/seo'; @@ -307,7 +306,6 @@ WebsitePreview.components = { BlockIframe, WebsiteTranslator, AceEditorAdapterComponent, - PagePropertiesDialogWrapper, }; registry.category('actions').add('website_preview', WebsitePreview); diff --git a/addons/website/static/src/client_actions/website_preview/website_preview.xml b/addons/website/static/src/client_actions/website_preview/website_preview.xml index 7e6cbb5d1de..691395be0fa 100644 --- a/addons/website/static/src/client_actions/website_preview/website_preview.xml +++ b/addons/website/static/src/client_actions/website_preview/website_preview.xml @@ -34,7 +34,6 @@ reloadIframe.bind="this.reloadIframe" removeWelcomeMessage.bind="this.removeWelcomeMessage"/> - diff --git a/addons/website/static/src/components/dialog/page_properties.js b/addons/website/static/src/components/dialog/page_properties.js index ad89d53c87f..0068d852310 100644 --- a/addons/website/static/src/components/dialog/page_properties.js +++ b/addons/website/static/src/components/dialog/page_properties.js @@ -4,11 +4,10 @@ import {CheckBox} from '@web/core/checkbox/checkbox'; import {useService, useAutofocus} from "@web/core/utils/hooks"; import {useWowlService} from '@web/legacy/utils'; import {WebsiteDialog} from './dialog'; -import {FormViewDialog} from 'web.view_dialogs'; -import {standaloneAdapter} from 'web.OwlCompatibility'; -import {qweb} from 'web.core'; +import {FormViewDialog} from "@web/views/view_dialogs/form_view_dialog"; +import {qweb, _t} from 'web.core'; -const {Component, onWillStart, useState, xml, useRef, markup, onRendered} = owl; +const {Component, onWillStart, onRendered, useState, xml, useRef, markup} = owl; export class PageDependencies extends Component { setup() { @@ -55,7 +54,7 @@ export class PageDependencies extends Component { showDependencies() { $(this.action.el).popover({ - title: this.env._t('Dependencies'), + title: this.env._t("Dependencies"), boundary: 'viewport', placement: 'right', trigger: 'focus', @@ -101,9 +100,9 @@ export class DeletePageDialog extends Component { await this.orm.unlink("website.page", [ this.props.pageId, ]); - this.website.goToWebsite(); + this.website.goToWebsite({path: '/'}); this.props.close(); - this.props.onClose(); + this.props.onDelete(); } } DeletePageDialog.components = { @@ -114,7 +113,7 @@ DeletePageDialog.components = { DeletePageDialog.template = 'website.DeletePageDialog'; DeletePageDialog.props = { pageId: Number, - onClose: Function, + onDelete: {type: Function, optional: true}, close: Function, }; @@ -138,7 +137,7 @@ export class DuplicatePageDialog extends Component { ); this.website.goToWebsite({path: res, edition: true}); } - this.props.onClose(); + this.props.onDuplicate(); } } DuplicatePageDialog.components = {WebsiteDialog}; @@ -155,108 +154,108 @@ DuplicatePageDialog.template = xml` `; DuplicatePageDialog.props = { - onClose: Function, + onDuplicate: {type: Function, optional: true}, close: Function, pageId: Number, }; -export class PagePropertiesDialogWrapper extends Component { +export class PagePropertiesDialog extends FormViewDialog { setup() { - try { - this.websiteService = useService('website'); - this.dialogService = useService('dialog'); - this.orm = useService('orm'); - } catch { - // Use services in legacy environment. - this.websiteService = useWowlService('website'); - this.dialogService = useWowlService('dialog'); - this.orm = useWowlService('orm'); - } + super.setup(); + this.dialog = useService('dialog'); + this.website = useService('website'); + this.viewProps.resId = this.resId; + } + + get resId() { + return this.props.resId || (this.website.currentWebsite && this.website.currentWebsite.metadata.mainObject.id); + } + + clonePage() { + this.dialog.add(DuplicatePageDialog, { + pageId: this.resId, + onDuplicate: () => { + this.props.close(); + this.props.onClose(); + }, + }); + } + + deletePage() { + this.dialog.add(DeletePageDialog, { + pageId: this.resId, + onDelete: () => { + this.props.close(); + this.props.onClose(); + }, + }); + } +} +PagePropertiesDialog.template = 'website.PagePropertiesDialog'; +PagePropertiesDialog.props = { + ...FormViewDialog.props, + onClose: {type: Function, optional: true}, + resModel: {type: String, optional: true}, +}; + +PagePropertiesDialog.defaultProps = { + ...FormViewDialog.defaultProps, + resModel: 'website.page', + title: _t("Page Properties"), + context: { + form_view_ref: 'website.website_page_properties_view_form', + }, + onClose: () => {}, +}; + +// TODO remove this once the 'website.page' listView is adapted to OWL. +export class PagePropertiesDialogManager extends Component { + setup() { + this.dialog = useService('dialog'); onRendered(this.createDialog); } createDialog() { - if (this.props.mode === 'clone') { - this.dialogService.add(DuplicatePageDialog, { - pageId: this.pageId, - onClose: this.props.onClose, - }); - } else if (this.props.mode === 'delete') { - this.dialogService.add(DeletePageDialog, { - pageId: this.pageId, - onClose: this.props.onClose, - }); - } else { - const parent = standaloneAdapter({Component}); - const formViewDialog = new FormViewDialog(parent, this.dialogOptions); - formViewDialog.buttons = [...formViewDialog.buttons, ...this.extraButtons]; - formViewDialog.on('form_dialog_discarded', parent, () => this.websiteService.context.showPageProperties = false); - formViewDialog.open(); + switch (this.props.mode) { + case 'clone': + this.dialog.add(DuplicatePageDialog, { + pageId: this.props.resId, + onDuplicate: this.props.onClose, + }); + break; + case 'delete': + this.dialog.add(DeletePageDialog, { + pageId: this.props.resId, + onDelete: this.props.onClose, + }); + break; + default: + this.dialog.add(PagePropertiesDialog, { + resId: this.props.resId, + onClose: this.props.onClose, + onRecordSaved: this.props.onRecordSaved, + }); + break; } } - - get pageId() { - return this.props.currentPage || (this.websiteService.currentWebsite && this.websiteService.currentWebsite.metadata.mainObject.id); - } - - get dialogOptions() { - return { - res_model: "website.page", - res_id: this.pageId, - context: { - form_view_ref: 'website.website_page_properties_view_form' - }, - title: this.env._t("Page Properties"), - size: 'medium', - on_saved: (record, changed) => { - if (changed) { - return this.orm.read("website.page", [this.pageId], ['url']).then(res => { - this.websiteService.goToWebsite({websiteId: record.data.website_id.res_id, path: res[0]['url']}); - }); - } - }, - }; - } - - get extraButtons() { - const wrapper = this; - return [{ - text: this.env._t("Duplicate Page"), - icon: 'fa-clone', - classes: 'btn-link ms-auto', - click: function () { - wrapper.dialogService.add(DuplicatePageDialog, { - pageId: wrapper.pageId, - onClose: this.close.bind(this), - }); - }, - }, - { - text: this.env._t("Delete Page"), - icon: 'fa-trash', - classes: 'btn-link', - click: function () { - wrapper.dialogService.add(DeletePageDialog, { - pageId: wrapper.pageId, - onClose: this.close.bind(this), - }); - }, - }]; - } } -PagePropertiesDialogWrapper.template = xml``; -PagePropertiesDialogWrapper.props = { +PagePropertiesDialogManager.template = xml``; +PagePropertiesDialogManager.props = { onClose: { type: Function, optional: true, }, - currentPage: { - type: Number, - optional: true, - }, mode: { type: String, optional: true, }, + resId: { + type: [Number, Boolean], + optional: true, + }, + onRecordSaved: { + type: Function, + optional: true, + } }; diff --git a/addons/website/static/src/components/dialog/page_properties.xml b/addons/website/static/src/components/dialog/page_properties.xml index 84c03d0d6af..b5831e4806d 100644 --- a/addons/website/static/src/components/dialog/page_properties.xml +++ b/addons/website/static/src/components/dialog/page_properties.xml @@ -60,7 +60,7 @@

Are you sure you want to delete this page ?

Don't forget to update all links referring to this page.

- +

I am sure about this.

@@ -75,4 +75,11 @@ + + + + + + + diff --git a/addons/website/static/src/components/fields/fields.js b/addons/website/static/src/components/fields/fields.js index 5d8a5b42baa..93dfb2bea96 100644 --- a/addons/website/static/src/components/fields/fields.js +++ b/addons/website/static/src/components/fields/fields.js @@ -1,24 +1,32 @@ /** @odoo-module **/ import {PageDependencies} from '@website/components/dialog/page_properties'; +import {standardFieldProps} from '@web/views/fields/standard_field_props'; +import {useInputField} from '@web/views/fields/input_field_hook'; +import {useService} from '@web/core/utils/hooks'; import {Switch} from '@website/components/switch/switch'; import AbstractFieldOwl from 'web.AbstractFieldOwl'; import fieldRegistry from 'web.field_registry_owl'; +import {registry} from '@web/core/registry'; +import {formatChar} from '@web/views/fields/formatters'; -const {useState, onWillStart} = owl; +const {Component, useState, onWillStart} = owl; -class FieldPageUrl extends AbstractFieldOwl { +/** + * Displays website page dependencies and URL redirect options when the page URL + * is updated. + */ +class PageUrlField extends Component { setup() { - super.setup(); - this.state = useState({ redirect_old_url: false, - url: this.value, + url: this.props.value, redirect_type: '301', }); + useInputField({getValue: () => this.props.value.url || this.props.value}); this.serverUrl = window.location.origin; - this.pageUrl = this.value; + this.pageUrl = this.props.value; } get enableRedirect() { @@ -27,41 +35,55 @@ class FieldPageUrl extends AbstractFieldOwl { onChangeRedirectOldUrl(value) { this.state.redirect_old_url = value; + this.updateValues(); } - /** - * @override - */ - commitChanges() { - if (this.enableRedirect) { - this._setValue(this.state); - } - return super.commitChanges(); + updateValues() { + // HACK: update redirect data from the URL field. + // TODO: remove this and use a transient model with redirect fields. + this.props.update(this.state); } } -FieldPageUrl.components = {Switch, PageDependencies}; -FieldPageUrl.supportedFieldTypes = ['char']; -FieldPageUrl.template = 'website.FieldPageUrl'; +PageUrlField.components = {Switch, PageDependencies}; +PageUrlField.template = 'website.PageUrlField'; +PageUrlField.props = { + ...standardFieldProps, + placeholder: {type: String, optional: true}, +}; +PageUrlField.extractProps = ({attrs}) => { + return { + placeholder: attrs.placeholder, + }; +}; +PageUrlField.supportedTypes = ['char']; -class FieldPageName extends AbstractFieldOwl { +registry.category("fields").add("page_url", PageUrlField); + +/** + * Used to display key dependencies and warn user about changing a special file + * (website.page & supported mimetype) name, since the key will be updated too. + */ +class PageNameField extends Component { setup() { - super.setup(); + this.orm = useService('orm'); + useInputField({getValue: () => this.props.value || ''}); this.state = useState({ - name: this.value, + name: this.props.value, }); - this.pageName = this.value; + this.pageName = this.props.value; this.supportedMimetypes = {}; onWillStart(() => this.onWillStart()); } + get formattedPageName() { + return formatChar(this.props.value); + } + async onWillStart() { - this.supportedMimetypes = await this.rpc({ - model: 'website', - method: 'guess_mimetype', - }); + this.supportedMimetypes = await this.orm.call('website', 'guess_mimetype', []); } get warnAboutCall() { @@ -76,20 +98,21 @@ class FieldPageName extends AbstractFieldOwl { const ext = '.' + this.pageName.split('.').pop(); return ext in this.supportedMimetypes && ext !== '.html'; } - - /** - * @override - */ - commitChanges() { - if (this.nameChanged) { - this._setValue(this.state.name); - } - return super.commitChanges(); - } } -FieldPageName.components = {PageDependencies}; -FieldPageName.supportedFieldTypes = ['char']; -FieldPageName.template = 'website.FieldPageName'; +PageNameField.components = {PageDependencies}; +PageNameField.template = 'website.PageNameField'; +PageNameField.props = { + ...standardFieldProps, + placeholder: {type: String, optional: true}, +}; +PageNameField.extractProps = ({attrs}) => { + return { + placeholder: attrs.placeholder, + }; +}; +PageNameField.supportedTypes = ['char']; + +registry.category("fields").add("page_name", PageNameField); /** * Displays 'char' field's value prefixed by a FA icon. @@ -111,6 +134,4 @@ class FieldFaPrefix extends AbstractFieldOwl { FieldFaPrefix.supportedFieldTypes = ['char']; FieldFaPrefix.template = 'website.FieldFaPrefix'; -fieldRegistry.add('page_url', FieldPageUrl); -fieldRegistry.add('page_name', FieldPageName); fieldRegistry.add('fa_prefix', FieldFaPrefix); diff --git a/addons/website/static/src/components/fields/fields.xml b/addons/website/static/src/components/fields/fields.xml index 98675d546f5..8c2a42f458a 100644 --- a/addons/website/static/src/components/fields/fields.xml +++ b/addons/website/static/src/components/fields/fields.xml @@ -1,35 +1,65 @@ - -
+ + + + +
-
- +
+
- - + +
-
-
+ - -
- + + + + + +
- +
-
+
diff --git a/addons/website/static/src/components/navbar/navbar.js b/addons/website/static/src/components/navbar/navbar.js index 2495c8c6a86..5dac2fe7ae0 100644 --- a/addons/website/static/src/components/navbar/navbar.js +++ b/addons/website/static/src/components/navbar/navbar.js @@ -6,6 +6,7 @@ import { registry } from "@web/core/registry"; import { patch } from 'web.utils'; import { EditMenuDialog } from '@website/components/dialog/edit_menu'; import { OptimizeSEODialog } from '@website/components/dialog/seo'; +import {PagePropertiesDialog} from '@website/components/dialog/page_properties'; const websiteSystrayRegistry = registry.category('website_systray'); const { useState } = owl; @@ -13,6 +14,7 @@ const { useState } = owl; patch(NavBar.prototype, 'website_navbar', { setup() { this._super(); + this.orm = useService('orm'); this.websiteService = useService('website'); this.websiteContext = useState(this.websiteService.context); @@ -41,8 +43,15 @@ patch(NavBar.prototype, 'website_navbar', { isDisplayed: () => this.canShowAceEditor(), }, 'website.menu_page_properties': { - openWidget: () => this.websiteContext.showPageProperties = true, + Component: PagePropertiesDialog, isDisplayed: () => this.canShowPageProperties(), + getProps: () => ({ + onRecordSaved: (record) => { + return this.orm.read('website.page', [record.resId], ['url']).then(res => { + this.websiteService.goToWebsite({websiteId: record.data.website_id[0], path: res[0]['url']}); + }); + }, + }) }, }; }, diff --git a/addons/website/static/src/js/website_page_list.js b/addons/website/static/src/js/website_page_list.js index bc5aa614dc4..fe6917063e9 100644 --- a/addons/website/static/src/js/website_page_list.js +++ b/addons/website/static/src/js/website_page_list.js @@ -4,7 +4,7 @@ import ListController from 'web.ListController'; import viewRegistry from 'web.view_registry'; import ListView from 'web.ListView'; import {ComponentWrapper} from 'web.OwlCompatibility'; -import {PagePropertiesDialogWrapper} from '@website/components/dialog/page_properties'; +import {PagePropertiesDialogManager} from '@website/components/dialog/page_properties'; import {qweb} from 'web.core'; const WebsitePageListController = ListController.extend({ @@ -86,9 +86,10 @@ const WebsitePageListController = ListController.extend({ * @private */ async _addDialog(record, mode = '') { - this._pagePropertiesDialog = new ComponentWrapper(this, PagePropertiesDialogWrapper, { - currentPage: record.data.id, + this._pagePropertiesDialog = new ComponentWrapper(this, PagePropertiesDialogManager, { + resId: record.data.id, onClose: this._onCloseDialog.bind(this), + onRecordSaved: this.reload.bind(this), mode: mode, }); await this._pagePropertiesDialog.mount(this.el); diff --git a/addons/website/static/src/services/website_service.js b/addons/website/static/src/services/website_service.js index 4f62ef053bb..a29495cfb8b 100644 --- a/addons/website/static/src/services/website_service.js +++ b/addons/website/static/src/services/website_service.js @@ -46,7 +46,6 @@ export const websiteService = { isPublicRootReady: false, snippetsLoaded: false, isMobile: false, - showPageProperties: false, }); const bus = new EventBus(); diff --git a/addons/website/views/website_views.xml b/addons/website/views/website_views.xml index 2b6ea310e5f..654c3cd1c2e 100644 --- a/addons/website/views/website_views.xml +++ b/addons/website/views/website_views.xml @@ -136,7 +136,7 @@ - +