diff --git a/setup/website_local_font/odoo/addons/website_local_font b/setup/website_local_font/odoo/addons/website_local_font new file mode 120000 index 0000000000..3738faf0a2 --- /dev/null +++ b/setup/website_local_font/odoo/addons/website_local_font @@ -0,0 +1 @@ +../../../../website_local_font \ No newline at end of file diff --git a/setup/website_local_font/setup.py b/setup/website_local_font/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/website_local_font/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/website_local_font/README.rst b/website_local_font/README.rst new file mode 100644 index 0000000000..90579bb5be --- /dev/null +++ b/website_local_font/README.rst @@ -0,0 +1,114 @@ +================== +Website Local Font +================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:21948d7b105e692895d067c8fee8367e630601f87e0a96393bed77d7fbf1a58b + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-LGPL--3-blue.png + :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html + :alt: License: LGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fwebsite-lightgray.png?logo=github + :target: https://github.com/OCA/website/tree/16.0/website_local_font + :alt: OCA/website +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/website-16-0/website-16-0-website_local_font + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/website&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to add local fonts on Odoo website. + +By default,Odoo only allows to upload fonts available from Google(fonts.google.com) and they can be made available locally or from Google's server in addition to the fonts made available via website theme modules.But there's no option to upload and have local fonts available across all website themes. + + +To overcome this limitation, this module has been developed to allow users to upload proprietary (or custom) fonts in the most widely used font formats - otf,ttf,woff and woff2 and make it available across all website themes. + +The fonts can be deleted as well if not needed anymore. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +To upload local fonts and use it for the website, you need to : + +1.Go to website (with editor enabled),click on 'Edit' and go to 'THEMES' section.Under Font Family, you would find an option 'Add a Local Font'. + + .. image:: https://raw.githubusercontent.com/OCA/website/16.0/website_local_font/static/description/AddALocalFontOption.png + +2.When you click on the 'Add a Local Font' option, a wizard would open with two fields- 'Font File' and 'Font Name'.Upload the file with one of the supported file formats and the font name would be automatically updated based on the file.You can update the name or leave it as it is. + + .. image:: https://raw.githubusercontent.com/OCA/website/16.0/website_local_font/static/description/SaveTheFont.png + +3.Click on 'Save & Reload' Button, you will see the font is applied to the elements of the website. + + .. image:: https://raw.githubusercontent.com/OCA/website/16.0/website_local_font/static/description/FontAppliedOnWebsite.png + +4.If you want to see the list of local fonts or delete the locally uploaded fonts,click on 'Edit' on website and go to 'THEMES' section.Under Font Family, you would find the newly added font. + + .. image:: https://raw.githubusercontent.com/OCA/website/16.0/website_local_font/static/description/NewlyAddedFont.png + +5.If you want to delete this locally added font , you can click on the delete icon next to it.Once, you click on the delete icon, it would open up a confirmation dialog. + + .. image:: https://raw.githubusercontent.com/OCA/website/16.0/website_local_font/static/description/DeleteFontConfirmation.png + +6.Once you click on 'Ok' button, the page would be refreshed and you would see the font is changed for the website and also the font is not found in the list. + + .. image:: https://raw.githubusercontent.com/OCA/website/16.0/website_local_font/static/description/FontDeleted.png + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Onestein + +Contributors +~~~~~~~~~~~~ + +* `Onestein `_: + + * Anjeel Haria + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/website `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/website_local_font/__init__.py b/website_local_font/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/website_local_font/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/website_local_font/__manifest__.py b/website_local_font/__manifest__.py new file mode 100644 index 0000000000..91e83acb21 --- /dev/null +++ b/website_local_font/__manifest__.py @@ -0,0 +1,32 @@ +# Copyright 2023 Onestein- Anjeel Haria +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +{ + "name": "Website Local Font", + "summary": "Allows to add local fonts on Odoo website", + "version": "16.0.1.0.0", + "category": "Website", + "website": "https://github.com/OCA/website", + "author": "Onestein, Odoo Community Association (OCA)", + "license": "LGPL-3", + "installable": True, + "depends": ["website"], + "assets": { + "website.assets_wysiwyg": [ + "website_local_font/static/src/js/snippets.options.js", + ], + "web._assets_primary_variables": [ + ("prepend", "website_local_font/static/src/scss/primary_variables.scss"), + ], + "web._assets_secondary_variables": [ + ( + "replace", + "website/static/src/scss/secondary_variables.scss", + "website_local_font/static/src/scss/secondary_variables.scss", + ), + ], + "web.assets_backend": [ + "website_local_font/static/src/xml/website.editor.xml", + ], + }, +} diff --git a/website_local_font/examples/AmaticSC-Bold.woff b/website_local_font/examples/AmaticSC-Bold.woff new file mode 100644 index 0000000000..fa2541e954 Binary files /dev/null and b/website_local_font/examples/AmaticSC-Bold.woff differ diff --git a/website_local_font/examples/RacingSansOne-Regular.ttf b/website_local_font/examples/RacingSansOne-Regular.ttf new file mode 100644 index 0000000000..bbd337e1b6 Binary files /dev/null and b/website_local_font/examples/RacingSansOne-Regular.ttf differ diff --git a/website_local_font/examples/Trueno-wml2.otf b/website_local_font/examples/Trueno-wml2.otf new file mode 100644 index 0000000000..82a39527e3 Binary files /dev/null and b/website_local_font/examples/Trueno-wml2.otf differ diff --git a/website_local_font/models/__init__.py b/website_local_font/models/__init__.py new file mode 100644 index 0000000000..8046e41dfa --- /dev/null +++ b/website_local_font/models/__init__.py @@ -0,0 +1,2 @@ +from . import assets +from . import ir_attachment diff --git a/website_local_font/models/assets.py b/website_local_font/models/assets.py new file mode 100644 index 0000000000..d337a7e0cd --- /dev/null +++ b/website_local_font/models/assets.py @@ -0,0 +1,35 @@ +# Copyright 2023 Onestein - Anjeel Haria +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import re + +from odoo import api, models + + +class Assets(models.AbstractModel): + _inherit = "web_editor.assets" + + @api.model + def make_scss_customization(self, url, values): + """ + Added handling for local fonts deletion and addition in scss + """ + delete_attachment_id = values.pop("delete-local-font-attachment-id", None) + if delete_attachment_id: + delete_attachment_id = int(delete_attachment_id) + self.env["ir.attachment"].search( + [ + "|", + ("id", "=", delete_attachment_id), + ("original_id", "=", delete_attachment_id), + ] + ).unlink() + + local_fonts = values.get("local-fonts") + if local_fonts and local_fonts != "null": + local_fonts = dict(re.findall(r"'([^']+)': '?(\d*)", local_fonts)) + for font_name in local_fonts: + if local_fonts[font_name]: + local_fonts[font_name] = int(local_fonts[font_name]) + values["local-fonts"] = str(local_fonts).replace("{", "(").replace("}", ")") + return super(Assets, self).make_scss_customization(url, values) diff --git a/website_local_font/models/ir_attachment.py b/website_local_font/models/ir_attachment.py new file mode 100644 index 0000000000..d7769315e7 --- /dev/null +++ b/website_local_font/models/ir_attachment.py @@ -0,0 +1,47 @@ +# Copyright 2023 Onestein - Anjeel Haria +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + + +import base64 + +from odoo import models + + +class Attachment(models.Model): + _inherit = "ir.attachment" + + def add_local_font(self, font_name, extension, file_data): + font_attachment = self.create( + { + "name": f"local-font-{font_name}", + "type": "binary", + "datas": file_data, + "mimetype": "font/" + extension, + "public": True, + } + ) + if extension == "otf": + font_format = "opentype" + elif extension == "ttf": + font_format = "truetype" + else: + font_format = extension + src = "url(/web/content/%s/%s) format('%s')" % ( + font_attachment.id, + f"local-font-{font_name}", + font_format, + ) + file_string = ( + "@font-face { \n" " font-family: %s; \n" "src:%s; \n" "}" % (font_name, src) + ) + font_css_attachment = self.create( + { + "datas": base64.b64encode(file_string.encode("utf-8")), + "name": f"{font_name} (local-font)", + "type": "binary", + "mimetype": "text/css", + "public": True, + } + ) + font_attachment.original_id = font_css_attachment.id + return font_css_attachment.id diff --git a/website_local_font/readme/CONTRIBUTORS.rst b/website_local_font/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..c7a489545f --- /dev/null +++ b/website_local_font/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `Onestein `_: + + * Anjeel Haria diff --git a/website_local_font/readme/DESCRIPTION.rst b/website_local_font/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..431c3728d2 --- /dev/null +++ b/website_local_font/readme/DESCRIPTION.rst @@ -0,0 +1,8 @@ +This module allows to add local fonts on Odoo website. + +By default,Odoo only allows to upload fonts available from Google(fonts.google.com) and they can be made available locally or from Google's server in addition to the fonts made available via website theme modules.But there's no option to upload and have local fonts available across all website themes. + + +To overcome this limitation, this module has been developed to allow users to upload proprietary (or custom) fonts in the most widely used font formats - otf,ttf,woff and woff2 and make it available across all website themes. + +The fonts can be deleted as well if not needed anymore. diff --git a/website_local_font/readme/USAGE.rst b/website_local_font/readme/USAGE.rst new file mode 100644 index 0000000000..d74b10a2d2 --- /dev/null +++ b/website_local_font/readme/USAGE.rst @@ -0,0 +1,25 @@ +To upload local fonts and use it for the website, you need to : + +1.Go to website (with editor enabled),click on 'Edit' and go to 'THEMES' section.Under Font Family, you would find an option 'Add a Local Font'. + + .. image:: ../static/description/AddALocalFontOption.png + +2.When you click on the 'Add a Local Font' option, a wizard would open with two fields- 'Font File' and 'Font Name'.Upload the file with one of the supported file formats and the font name would be automatically updated based on the file.You can update the name or leave it as it is. + + .. image:: ../static/description/SaveTheFont.png + +3.Click on 'Save & Reload' Button, you will see the font is applied to the elements of the website. + + .. image:: ../static/description/FontAppliedOnWebsite.png + +4.If you want to see the list of local fonts or delete the locally uploaded fonts,click on 'Edit' on website and go to 'THEMES' section.Under Font Family, you would find the newly added font. + + .. image:: ../static/description/NewlyAddedFont.png + +5.If you want to delete this locally added font , you can click on the delete icon next to it.Once, you click on the delete icon, it would open up a confirmation dialog. + + .. image:: ../static/description/DeleteFontConfirmation.png + +6.Once you click on 'Ok' button, the page would be refreshed and you would see the font is changed for the website and also the font is not found in the list. + + .. image:: ../static/description/FontDeleted.png diff --git a/website_local_font/static/description/AddALocalFontOption.png b/website_local_font/static/description/AddALocalFontOption.png new file mode 100644 index 0000000000..eb1960e6ff Binary files /dev/null and b/website_local_font/static/description/AddALocalFontOption.png differ diff --git a/website_local_font/static/description/DeleteFontConfirmation.png b/website_local_font/static/description/DeleteFontConfirmation.png new file mode 100644 index 0000000000..0f229dc38d Binary files /dev/null and b/website_local_font/static/description/DeleteFontConfirmation.png differ diff --git a/website_local_font/static/description/FontAppliedOnWebsite.png b/website_local_font/static/description/FontAppliedOnWebsite.png new file mode 100644 index 0000000000..f5a1eea6c1 Binary files /dev/null and b/website_local_font/static/description/FontAppliedOnWebsite.png differ diff --git a/website_local_font/static/description/FontDeleted.png b/website_local_font/static/description/FontDeleted.png new file mode 100644 index 0000000000..ae8cdab1bf Binary files /dev/null and b/website_local_font/static/description/FontDeleted.png differ diff --git a/website_local_font/static/description/NewlyAddedFont.png b/website_local_font/static/description/NewlyAddedFont.png new file mode 100644 index 0000000000..ab891942ea Binary files /dev/null and b/website_local_font/static/description/NewlyAddedFont.png differ diff --git a/website_local_font/static/description/SaveTheFont.png b/website_local_font/static/description/SaveTheFont.png new file mode 100644 index 0000000000..b7b96b434d Binary files /dev/null and b/website_local_font/static/description/SaveTheFont.png differ diff --git a/website_local_font/static/description/icon.png b/website_local_font/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/website_local_font/static/description/icon.png differ diff --git a/website_local_font/static/description/index.html b/website_local_font/static/description/index.html new file mode 100644 index 0000000000..560d3d813d --- /dev/null +++ b/website_local_font/static/description/index.html @@ -0,0 +1,456 @@ + + + + + + +Website Local Font + + + +
+

Website Local Font

+ + +

Beta License: LGPL-3 OCA/website Translate me on Weblate Try me on Runboat

+

This module allows to add local fonts on Odoo website.

+

By default,Odoo only allows to upload fonts available from Google(fonts.google.com) and they can be made available locally or from Google’s server in addition to the fonts made available via website theme modules.But there’s no option to upload and have local fonts available across all website themes.

+

To overcome this limitation, this module has been developed to allow users to upload proprietary (or custom) fonts in the most widely used font formats - otf,ttf,woff and woff2 and make it available across all website themes.

+

The fonts can be deleted as well if not needed anymore.

+

Table of contents

+ +
+

Usage

+

To upload local fonts and use it for the website, you need to :

+

1.Go to website (with editor enabled),click on ‘Edit’ and go to ‘THEMES’ section.Under Font Family, you would find an option ‘Add a Local Font’.

+
+https://raw.githubusercontent.com/OCA/website/16.0/website_local_font/static/description/AddALocalFontOption.png +
+

2.When you click on the ‘Add a Local Font’ option, a wizard would open with two fields- ‘Font File’ and ‘Font Name’.Upload the file with one of the supported file formats and the font name would be automatically updated based on the file.You can update the name or leave it as it is.

+
+https://raw.githubusercontent.com/OCA/website/16.0/website_local_font/static/description/SaveTheFont.png +
+

3.Click on ‘Save & Reload’ Button, you will see the font is applied to the elements of the website.

+
+https://raw.githubusercontent.com/OCA/website/16.0/website_local_font/static/description/FontAppliedOnWebsite.png +
+

4.If you want to see the list of local fonts or delete the locally uploaded fonts,click on ‘Edit’ on website and go to ‘THEMES’ section.Under Font Family, you would find the newly added font.

+
+https://raw.githubusercontent.com/OCA/website/16.0/website_local_font/static/description/NewlyAddedFont.png +
+

5.If you want to delete this locally added font , you can click on the delete icon next to it.Once, you click on the delete icon, it would open up a confirmation dialog.

+
+https://raw.githubusercontent.com/OCA/website/16.0/website_local_font/static/description/DeleteFontConfirmation.png +
+

6.Once you click on ‘Ok’ button, the page would be refreshed and you would see the font is changed for the website and also the font is not found in the list.

+
+https://raw.githubusercontent.com/OCA/website/16.0/website_local_font/static/description/FontDeleted.png +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Onestein
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/website project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/website_local_font/static/src/js/snippets.options.js b/website_local_font/static/src/js/snippets.options.js new file mode 100644 index 0000000000..88c6a4d614 --- /dev/null +++ b/website_local_font/static/src/js/snippets.options.js @@ -0,0 +1,269 @@ +/* Copyright 2023 Onestein - Anjeel Haria + * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ +odoo.define("website_local_font.editor.snippets.options", function (require) { + "use strict"; + + const website_editor_snippet_options = require("website.editor.snippets.options"); + const FontFamilyPickerUserValueWidget = + website_editor_snippet_options.FontFamilyPickerUserValueWidget; + var core = require("web.core"); + var Dialog = require("web.Dialog"); + const weUtils = require("web_editor.utils"); + var _t = core._t; + var qweb = core.qweb; + var options = require("web_editor.snippets.options"); + + website_editor_snippet_options.FontFamilyPickerUserValueWidget.include({ + xmlDependencies: ( + FontFamilyPickerUserValueWidget.prototype.xmlDependencies || [] + ).concat(["/website_local_font/static/src/xml/website.editor.xml"]), + events: _.extend({}, FontFamilyPickerUserValueWidget.prototype.events || {}, { + "click .o_we_add_local_font_btn": "_onAddLocalFontClick", + "click .o_we_delete_local_font_btn": "_onDeleteLocalFontClick", + }), + /** + * @override + */ + start: async function () { + await this._super(...arguments); + const style = window.getComputedStyle(document.documentElement); + const nbFonts = + parseInt(weUtils.getCSSVariableValue("number-of-fonts", style)) || []; + const localFontsProperty = weUtils.getCSSVariableValue( + "local-fonts", + style + ); + this.localFonts = localFontsProperty + ? localFontsProperty.slice(1, -1).split(/\s*,\s*/g) + : []; + const fontEls = []; + const methodName = this.el.dataset.methodName || "customizeWebsiteVariable"; + const variable = this.el.dataset.variable; + _.times(nbFonts, (fontNb) => { + const realFontNb = fontNb + 1; + const fontEl = document.createElement("we-button"); + fontEl.classList.add(`o_we_option_font_${realFontNb}`); + fontEl.dataset.variable = variable; + fontEl.dataset[methodName] = weUtils.getCSSVariableValue( + `font-number-${realFontNb}`, + style + ); + fontEl.dataset.font = realFontNb; + fontEls.push(fontEl); + this.menuEl.appendChild(fontEl); + }); + if (this.localFonts.length) { + console.log(this.localFonts); + const localFontsEls = fontEls.splice(-this.localFonts.length); + localFontsEls.forEach((el, index) => { + $(el).append( + core.qweb.render("website.delete_local_font_btn", { + index: index, + }) + ); + }); + } + + if (this.googleLocalFonts.length) { + const googleLocalFontsEls = fontEls.splice( + -this.googleLocalFonts.length + ); + googleLocalFontsEls.forEach((el, index) => { + $(el).append( + core.qweb.render("website.delete_google_font_btn", { + index: index, + local: true, + }) + ); + }); + } + + if (this.googleFonts.length) { + const googleFontsEls = fontEls.splice(-this.googleFonts.length); + googleFontsEls.forEach((el, index) => { + $(el).append( + core.qweb.render("website.delete_google_font_btn", { + index: index, + }) + ); + }); + } + $(this.menuEl).append( + $( + core.qweb.render("website.add_google_font_btn", { + variable: variable, + }) + ) + ); + $(this.menuEl).append( + $( + qweb.render("website.add_local_font_btn", { + variable: variable, + }) + ) + ); + }, + + _saveLocalFont: async function () { + var widgetObj = this.getParent(); + const variable = widgetObj._methodsParams.variable; + const inputEl = this.el.querySelector(".o_local_input_font"); + const fileEl = this.el.querySelector(".local_font_selection_input"); + if (!inputEl) { + return; + } + const font_name = inputEl.value; + if (!font_name) { + inputEl.classList.add("is-invalid"); + return; + } else if (!fileEl.files) { + fileEl.classList.add("is-invalid"); + return; + } + var allowedExtensions = /(\.otf|\.ttf|\.woff|\.woff2)$/i; + var file = fileEl.files[0]; + var filename = file.name; + if (!allowedExtensions.exec(filename)) { + inputEl.classList.add("is-invalid"); + fileEl.classList.add("is-invalid"); + fileEl.value = ""; + inputEl.value = ""; + return false; + } + var extension = filename.substring( + filename.lastIndexOf(".") + 1, + filename.length + ); + + const reader = new FileReader(); + const readPromise = new Promise((resolve, reject) => { + reader.addEventListener("load", () => resolve(reader.result)); + reader.addEventListener("abort", reject); + reader.addEventListener("error", reject); + reader.readAsDataURL(fileEl.files[0]); + }); + const file_data = await readPromise; + this._rpc({ + model: "ir.attachment", + method: "add_local_font", + args: [, font_name, extension, file_data.split(",")[1]], + }).then((attach_id) => { + widgetObj.localFonts.push(`'${font_name}': ` + attach_id); + widgetObj.trigger_up("google_fonts_custo_request", { + values: {[variable]: `"${font_name}"`}, + googleFonts: widgetObj.googleFonts, + googleLocalFonts: widgetObj.googleLocalFonts, + localFonts: widgetObj.localFonts, + }); + }); + }, + + _onAddLocalFontClick: function () { + const dialog = new Dialog(this, { + title: _t("Add a Local Font"), + $content: $(qweb.render("website.dialog.addLocalFont")), + buttons: [ + { + text: _t("Save & Reload"), + classes: "btn-primary", + click: this._saveLocalFont, + }, + { + text: _t("Discard"), + close: true, + }, + ], + }); + dialog.opened().then(function () { + dialog.$(".local_font_selection_input").change(function (e) { + if (e.target.files) { + var filename = e.target.files[0].name; + var allowedExtensions = /(\.otf|\.ttf|\.woff|\.woff2)$/i; + if (!allowedExtensions.exec(filename)) { + e.target.classList.add("is-invalid"); + e.classList.add("is-invalid"); + e.target.value = ""; + e.value = ""; + return false; + } + if (filename) { + $(".o_local_input_font").val( + filename.substring(0, filename.lastIndexOf(".")) + ); + } + } + }); + }); + dialog.open(); + }, + /** + * @private + * @param {Event} ev + */ + _onDeleteLocalFontClick: async function (ev) { + ev.preventDefault(); + const values = {}; + + const save = await new Promise((resolve) => { + Dialog.confirm( + this, + _t( + "Deleting a font requires a reload of the page. This will save all your changes and reload the page, are you sure you want to proceed?" + ), + { + confirm_callback: () => resolve(true), + cancel_callback: () => resolve(false), + } + ); + }); + if (!save) { + return; + } + // Remove Local font + const localFontIndex = parseInt(ev.target.dataset.fontIndex); + console.log(localFontIndex); + const localFont = this.localFonts[localFontIndex].split(":"); + const localFontName = localFont[0]; + values["delete-local-font-attachment-id"] = localFont[1]; + this.localFonts.splice(localFontIndex, 1); + // Adapt font variable indexes to the removal + const style = window.getComputedStyle(document.documentElement); + _.each( + FontFamilyPickerUserValueWidget.prototype.fontVariables, + (variable) => { + const value = weUtils.getCSSVariableValue(variable, style); + if (value.substring(1, value.length - 1) === localFontName) { + // If an element is using the local font being removed, reset + // it to the theme default. + values[variable] = "null"; + } + } + ); + + this.trigger_up("google_fonts_custo_request", { + values: values, + googleFonts: this.googleFonts, + googleLocalFonts: this.googleLocalFonts, + localFonts: this.localFonts, + }); + }, + }); + + options.Class.include({ + /** + * @private + * @param {OdooEvent} ev + */ + _onGoogleFontsCustoRequest: function (ev) { + const values = ev.data.values ? _.clone(ev.data.values) : {}; + const localFonts = ev.data.localFonts || ev.target.localFonts; + if (localFonts !== undefined && localFonts.length) { + values["local-fonts"] = "(" + localFonts.join(", ") + ")"; + } else { + values["local-fonts"] = "null"; + } + ev.data.values = values; + this._super(...arguments); + }, + }); +}); diff --git a/website_local_font/static/src/scss/primary_variables.scss b/website_local_font/static/src/scss/primary_variables.scss new file mode 100644 index 0000000000..4ca5ead311 --- /dev/null +++ b/website_local_font/static/src/scss/primary_variables.scss @@ -0,0 +1,111 @@ +/* Copyright 2023 Onestein - Anjeel Haria + * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ + +// Added local-fonts +$o-base-website-values-palette: ( + "font-size-base": 1rem, + // Need a set value as the value is used in bootstrap_overridden files + "google-fonts": null, + "google-local-fonts": null, + "local-fonts": null, + "body-image": null, + "body-image-type": "image", + // 'image' or 'pattern' + "layout": "full", + // 'full' / 'boxed' + "color-palettes-name": null, + // Default to the individual variables for each color palette type + "btn-primary-outline": false, + "btn-secondary-outline": false, + "link-underline": "hover", + // 'never' / 'hover' / 'always' + "btn-ripple": false, + "btn-padding-y": null, + // Default to BS + "btn-padding-x": null, + // Default to BS + "btn-font-size": null, + // Default to BS + "btn-padding-y-sm": null, + // Default to portal value + "btn-padding-x-sm": null, + // Default to portal value + "btn-font-size-sm": null, + // Default to BS + "btn-padding-y-lg": null, + // Default to BS + "btn-padding-x-lg": null, + // Default to BS + "btn-font-size-lg": null, + // Default to BS + "btn-border-width": null, + // Default to BS + "btn-border-radius": null, + // Default to BS + "btn-border-radius-sm": null, + // Default to BS + "btn-border-radius-lg": null, + // Default to BS + "input-padding-y": null, + // Default to BS + "input-padding-x": null, + // Default to BS + "input-font-size": null, + // Default to BS + "input-padding-y-sm": null, + // Default to BS + "input-padding-x-sm": null, + // Default to BS + "input-font-size-sm": null, + // Default to BS + "input-padding-y-lg": null, + // Default to BS + "input-padding-x-lg": null, + // Default to BS + "input-font-size-lg": null, + // Default to BS + "input-border-width": null, + // Default to BS + "input-border-radius": null, + // Default to BS + "input-border-radius-sm": null, + // Default to BS + "input-border-radius-lg": null, + // Default to BS + // A key from the $o-theme-font-configs map (null = default to the first key) + "font": null, + "headings-font": null, + "navbar-font": null, + "buttons-font": null, + // Gradients + "menu-gradient": null, + "header-boxed-gradient": null, + "footer-gradient": null, + "copyright-gradient": null, + "header-template": "default", + // 'default' / 'hamburger' / 'vertical' / 'sidebar' + "header-font-size": null, + // Default to BS (normal font-size) + "header-links-style": "default", + // 'default' / 'fill' / 'outline' / 'pills' / 'block' / 'border-bottom' + "logo-height": null, + // Default to navbar height (see portal) + "hamburger-type": "default", + // 'default' / 'off-canvas' + "hamburger-position": "left", + // 'left' / 'center' / 'right' + "menu-border-width": null, + // Default to classes used on the template + "menu-border-style": solid, + // Default to classes used on the template + "menu-border-radius": null, + // Default to classes used on the template + "menu-box-shadow": null, + // Default to classes used on the template + "sidebar-width": 18.75rem, + // 300px + "footer-template": "default", + "footer-effect": null, + // null / 'slideout_slide_hover' / 'slideout_shadow' + "footer-scrolltop": false, +); diff --git a/website_local_font/static/src/scss/secondary_variables.scss b/website_local_font/static/src/scss/secondary_variables.scss new file mode 100644 index 0000000000..60facf9c96 --- /dev/null +++ b/website_local_font/static/src/scss/secondary_variables.scss @@ -0,0 +1,368 @@ +/* Copyright 2023 Onestein - Anjeel Haria + * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ +//------------------------------------------------------------------------------ +// Website customizations +//------------------------------------------------------------------------------ + +// Complete the base website values palette with the first defined font +$-first-font-name: nth(map-keys($o-theme-font-configs), 1); +@each $alias, $key in $o-font-aliases-to-keys { + @if map-get($o-base-website-values-palette, $key) == null { + $o-base-website-values-palette: map-merge( + $o-base-website-values-palette, + ( + $key: $-first-font-name, + ) + ); + } +} + +@function o-add-font-config($values) { + @each $alias, $key in $o-font-aliases-to-keys { + $font-name: map-get($values, $key); + $font-config: o-safe-get($o-theme-font-configs, $font-name, ()); + $font-properties: o-safe-get($font-config, "properties", ()); + $type-font-properties: o-safe-get($font-properties, $alias, ()); + $values: map-merge($values, $type-font-properties); + } + @return $values; +} + +// Some fonts have been renamed in a stable version, and for retro compatibility +// for users which have a custom user_values.css as attachment with an old font +// already used, we map the old font with the new `similar` font +$o-fonts-similar: ( + "Droid Serif": "Noto Serif", + "SinKinSans": "Spartan", + "Proxima": "Montserrat", + "Comic Sans MS": "Comic Neue", + "Fontastique": "Bubblegum Sans", + "Luminari": "Eagle Lake", + "Fecske": "Marcellus", + "Din Alternate": "Roboto", +); + +@function o-map-font-aliases($values) { + $-values: $values; + @each $key in map-values($o-font-aliases-to-keys) { + $value: map-get($values, $key); + @if ($value and map-has-key($o-fonts-similar, $value)) { + $-values: map-merge( + $-values, + ( + $key: map-get($o-fonts-similar, $value), + ) + ); + } + } + @return $-values; +} + +// By default, most website palette values are null. Each null value is +// automatically replaced with corresponsing values in chosen default values +// palette. +$o-user-website-values: o-map-force-nulls($o-user-website-values); +$o-user-website-values: o-map-font-aliases($o-user-website-values); +$-website-values-default: o-safe-nth( + $o-website-values-palettes, + $o-website-values-palette-number, + () +); +$-website-values-default: map-merge( + $o-base-website-values-palette, + $-website-values-default +); +$-actual-user-website-values-palette: map-merge( + $-website-values-default, + $o-user-website-values +); +// Default font selection + User font selection have been merged, now need to +// add the right associated font default config +$-actual-user-website-values-palette: o-add-font-config( + $-actual-user-website-values-palette +); +// Reforce the properties which already had a set values in the user map (the +// font properties override the default palette values but not the user ones) +$-actual-user-website-values-palette: map-merge( + $-actual-user-website-values-palette, + $o-user-website-values +); +$o-website-values-palettes: append( + $o-website-values-palettes, + $-actual-user-website-values-palette +); + +// Enable last website values palette, which is now the user customized one +$o-website-values-palette-number: length($o-website-values-palettes); +$o-website-values: $-actual-user-website-values-palette !default; +@function o-website-value($key) { + @return map-get($o-website-values, $key); +} + +$o-theme-navbar-logo-height: o-website-value("logo-height") !default; +$o-theme-navbar-fixed-logo-height: o-website-value("fixed-logo-height") !default; + +//------------------------------------------------------------------------------ +// Colors +//------------------------------------------------------------------------------ + +// First change the palette selection to the actual user choice if any, keeping +// compatibility with old numbers too. +$-color-palette-number: o-website-value("color-palettes-number") or + if(variable-exists(o-color-palette-number), $o-color-palette-number, null); // Only in old databases +$-color-palette-name: o-website-value("color-palettes-name"); +$-gray-color-palette-name: $-color-palette-name; +$-theme-color-palette-name: $-color-palette-name; + +// If defined palette number but no *user* defined palette name, this is an +// old database with a old palette selection, we have to find the name from +// the old number +@if ( + $-color-palette-number and not + map-get($o-user-website-values, "color-palettes-name") +) { + $-compat: $o-color-palettes-compatibility-indexes; + $-color-palette-name: map-get($-compat, $-color-palette-number) or ""; + + $-compat: $o-gray-color-palettes-compatibility-indexes or + $o-color-palettes-compatibility-indexes; + $-gray-color-palette-name: map-get($-compat, $-color-palette-number) or ""; + + $-compat: $o-theme-color-palettes-compatibility-indexes or + $o-color-palettes-compatibility-indexes; + $-theme-color-palette-name: map-get($-compat, $-color-palette-number) or ""; +} + +@if ($-color-palette-name) { + $o-color-palette-name: $-color-palette-name; +} +@if ($-gray-color-palette-name) { + $o-gray-color-palette-name: $-gray-color-palette-name; +} +@if ($-theme-color-palette-name) { + $o-theme-color-palette-name: $-theme-color-palette-name; +} + +$o-has-customized-13-0-color-system: not not + ( + map-get($o-user-theme-color-palette, "primary") or + map-get($o-user-theme-color-palette, "secondary") or + map-get($o-user-theme-color-palette, "alpha") or + map-get($o-user-theme-color-palette, "beta") or + map-get($o-user-theme-color-palette, "gamma") or + map-get($o-user-theme-color-palette, "delta") or + map-get($o-user-theme-color-palette, "epsilon") + ); + +$o-has-customized-colors: not not + ( + length(map-keys($o-user-color-palette)) > 0 or + map-get($o-user-theme-color-palette, "success") or + map-get($o-user-theme-color-palette, "info") or + map-get($o-user-theme-color-palette, "warning") or + map-get($o-user-theme-color-palette, "danger") + ); + +// Color palette +// ------------- + +// By default, most user color palette values are null. Each null value is +// automatically replaced with corresponsing colors in chosen default color +// palette. +$o-user-color-palette: o-map-force-nulls($o-user-color-palette); +$-palette-default: map-get($o-color-palettes, $o-color-palette-name) or (); +$-actual-user-color-palette: map-merge($-palette-default, $o-user-color-palette); +// Compatibility with old values in old names +@each $name, + $custom-name + in ( + // Each of those values were either a number for a color combination, a + // string for a color name or a color. Now they should only be a number for + // a color combination and the other value types for the color name/value + // are handled by another variable. + "menu": + "menu-custom", + "header-boxed": "header-boxed-custom", + "footer": "footer-custom", + "copyright": "copyright-custom" + ) +{ + $-base-value: map-get($-actual-user-color-palette, $name); + @if $-base-value and $-base-value != "NULL" and type-of($-base-value) != "number" { + $-base-custom-value: map-get($-actual-user-color-palette, $custom-name); + $-actual-user-color-palette: map-merge( + $-actual-user-color-palette, + ( + $name: 1, + $custom-name: $-base-custom-value or $-base-value, + ) + ); + } +} +$o-color-palettes: map-merge( + $o-color-palettes, + ( + "user-palette": $-actual-user-color-palette, + ) +); + +// Gray palette +// ------------ + +// By default, most user gray palette values are null. Each null value is +// automatically replaced with corresponsing colors in chosen default color +// palette. +$o-user-gray-color-palette: o-map-force-nulls($o-user-gray-color-palette); +$-palette-default: map-get($o-gray-color-palettes, $o-gray-color-palette-name) or (); +$-actual-user-gray-color-palette: map-merge( + $-palette-default, + $o-user-gray-color-palette +); +$o-gray-color-palettes: map-merge( + $o-gray-color-palettes, + ( + "user-palette": $-actual-user-gray-color-palette, + ) +); + +// Theme color palette +// ------------------- + +// alpha -> epsilon colors are from the old color system, this is kept for +// compatibility: Generate default theme color scheme if alpha is set +$-alpha: map-get($o-user-theme-color-palette, "alpha"); +@if ($-alpha) { + $o-user-theme-color-palette: map-merge( + ( + beta: lighten(desaturate($-alpha, 60%), 30%), + gamma: desaturate(adjust-hue($-alpha, -45deg), 10%), + delta: desaturate(adjust-hue($-alpha, 45deg), 10%), + epsilon: desaturate(adjust-hue($-alpha, 180deg), 10%), + ), + $o-user-theme-color-palette + ); +} + +// By default, all user theme color palette values are null. Each null value is +// automatically replaced with corresponsing colors in chosen default theme +// color palette. +$o-user-theme-color-palette: o-map-force-nulls($o-user-theme-color-palette); +$-palette-default: map-get($o-theme-color-palettes, $o-theme-color-palette-name) or (); +$-actual-user-theme-color-palette: map-merge( + $-palette-default, + $o-user-theme-color-palette +); +// Always remove the primary/secondary which were customizable in some theme +// in Odoo <= 13.3. The customer can always rechoose the right color in the +// Odoo color system as the first two ones are mapped to primary/secondary. +$-actual-user-theme-color-palette: map-remove( + $-actual-user-theme-color-palette, + "primary", + "secondary" +); +$o-theme-color-palettes: map-merge( + $o-theme-color-palettes, + ( + "user-palette": $-actual-user-theme-color-palette, + ) +); + +// --- + +// Enable last color and theme color palettes, which are now the user customized +// color palettes. +$o-original-color-palette-name: $o-color-palette-name; +$o-color-palette-name: "user-palette"; +$o-gray-color-palette-name: "user-palette"; +$o-theme-color-palette-name: "user-palette"; + +$o-we-auto-contrast-exclusions: () !default; +$o-we-auto-contrast-exclusions: join( + $o-we-auto-contrast-exclusions, + map-keys($o-user-color-palette) +); + +//------------------------------------------------------------------------------ +// Fonts +//------------------------------------------------------------------------------ + +// Merge base fonts with user-added google fonts +@each $font-name in (o-website-value("google-fonts") or ()) { + $o-theme-font-configs: map-merge( + $o-theme-font-configs, + ( + $font-name: ( + "family": ( + quote($font-name), + sans-serif, + ), + "url": quote($font-name) + ":300,300i,400,400i,700,700i", + ), + ) + ); +} + +// Add locally hosted google fonts +@each $font-name, $font-attach-id in (o-website-value("google-local-fonts") or ()) { + $o-theme-font-configs: map-merge( + $o-theme-font-configs, + ( + $font-name: ( + "family": ( + quote($font-name), + sans-serif, + ), + "attachment": $font-attach-id, + "name": quote($font-name), + ), + ) + ); +} + +// Add locally hosted fonts +@each $font-name, $font-attach-id in (o-website-value("local-fonts") or ()) { + $o-theme-font-configs: map-merge( + $o-theme-font-configs, + ( + $font-name: ( + "family": ( + quote($font-name), + sans-serif, + ), + "attachment": $font-attach-id, + "name": quote($font-name), + ), + ) + ); +} + +// Add odoo unicode support for all fonts +@each $font-name, $font-config in $o-theme-font-configs { + $o-theme-font-configs: map-merge( + $o-theme-font-configs, + ( + $font-name: + map-merge( + $font-config, + ( + "family": + o-add-unicode-support-font(map-get($font-config, "family")), + ) + ), + ) + ); +} + +// Function which allows to retrieve a base info (family, url, properties) about +// a component (base, navbar, ...)'s font. The font name is retrievable via a +// simple o-website-value call. +@function o-get-font-info($alias: "base", $config-key: "family") { + $key: map-get($o-font-aliases-to-keys, $alias); + $font-name: o-website-value($key); + $-font-config: o-safe-get($o-theme-font-configs, $font-name, ()); + @return map-get($-font-config, $config-key); +} +$o-theme-font: o-get-font-info("base") or (sans-serif) !default; +$o-theme-headings-font: o-get-font-info("headings") or $o-theme-font !default; +$o-theme-navbar-font: o-get-font-info("navbar") or $o-theme-font !default; +$o-theme-buttons-font: o-get-font-info("buttons") or $o-theme-font !default; diff --git a/website_local_font/static/src/xml/website.editor.xml b/website_local_font/static/src/xml/website.editor.xml new file mode 100644 index 0000000000..5bc5a1dc69 --- /dev/null +++ b/website_local_font/static/src/xml/website.editor.xml @@ -0,0 +1,51 @@ + + + + + +
+
+ +
+ Supported file formats are otf,ttf,woff,woff2 +
+ +
+ +
+
+

Adding a font requires a reload of the page. This will save all your changes.

+
+ + + Delete this font + + Delete this font + + + + + + Add a Local Font + + +
diff --git a/website_local_font/tests/__init__.py b/website_local_font/tests/__init__.py new file mode 100644 index 0000000000..222c089bcd --- /dev/null +++ b/website_local_font/tests/__init__.py @@ -0,0 +1 @@ +from . import test_website_local_font diff --git a/website_local_font/tests/test_website_local_font.py b/website_local_font/tests/test_website_local_font.py new file mode 100644 index 0000000000..17c1502cad --- /dev/null +++ b/website_local_font/tests/test_website_local_font.py @@ -0,0 +1,104 @@ +# Copyright 2023 Onestein - Anjeel Haria +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import base64 + +from odoo.modules.module import get_module_resource +from odoo.tests import common + + +def test_font_file_import(font_file_name): + font_file_path = get_module_resource( + "website_local_font", + "examples", + font_file_name, + ) + font_file = base64.b64encode(open(font_file_path, "rb").read()) + return font_file + + +class TestIrAttachment(common.TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.IrAttachment = cls.env["ir.attachment"] + cls.Web_Editor_Assets = cls.env["web_editor.assets"] + + def test_add_local_font_otf(self): + font_css_attachment_id = self.IrAttachment.add_local_font( + "Trueno-wml2", "otf", test_font_file_import("Trueno-wml2.otf") + ) + self.assertRecordValues( + self.IrAttachment.browse(font_css_attachment_id), + [ + { + "name": "Trueno-wml2 (local-font)", + "mimetype": "text/css", + } + ], + ) + + def test_add_local_font_ttf(self): + font_css_attachment_id = self.IrAttachment.add_local_font( + "RacingSansOne-Regular", + "ttf", + test_font_file_import("RacingSansOne-Regular.ttf"), + ) + self.assertRecordValues( + self.IrAttachment.browse(font_css_attachment_id), + [ + { + "name": "RacingSansOne-Regular (local-font)", + "mimetype": "text/css", + } + ], + ) + + def test_add_local_font_woff(self): + font_css_attachment_id = self.IrAttachment.add_local_font( + "AmaticSC-Bold", "woff", test_font_file_import("AmaticSC-Bold.woff") + ) + self.assertRecordValues( + self.IrAttachment.browse(font_css_attachment_id), + [ + { + "name": "AmaticSC-Bold (local-font)", + "mimetype": "text/css", + } + ], + ) + + def test_add_local_font_and_make_scss_customization(self): + font_css_attachment_id = self.IrAttachment.add_local_font( + "AmaticSC-Bold", "woff", test_font_file_import("AmaticSC-Bold.woff") + ) + attachment = self.env["ir.attachment"].browse(font_css_attachment_id) + scss_file_url = "/website/static/src/scss/options/user_values.scss" + self.Web_Editor_Assets.make_scss_customization( + scss_file_url, + {"local-fonts": "('AmaticSC-Bold': '" + str(font_css_attachment_id) + ")'"}, + ) + custom_url = self.Web_Editor_Assets._make_custom_asset_url( + scss_file_url, "web.assets_common" + ) + attachment.write({"url": custom_url}) + custom_attachment = self.Web_Editor_Assets._get_custom_attachment(custom_url) + custom_attachment_string = custom_attachment.raw.decode("utf-8") + self.assertEqual( + font_css_attachment_id, custom_attachment.id, "Local Font is added" + ) + self.assertIn("AmaticSC-Bold", custom_attachment_string, "Local Font is added") + + def test_delete_local_font_and_make_scss_customization(self): + font_css_attachment_id = self.IrAttachment.add_local_font( + "AmaticSC-Bold", "woff", test_font_file_import("AmaticSC-Bold.woff") + ) + self.Web_Editor_Assets.make_scss_customization( + "/website/static/src/scss/options/user_values.scss", + {"delete-local-font-attachment-id": font_css_attachment_id}, + ) + self.assertNotIn( + font_css_attachment_id, + self.IrAttachment.search([]).ids, + "Local Font is deleted", + ) diff --git a/website_prevent_cls/README.rst b/website_prevent_cls/README.rst new file mode 100644 index 0000000000..8f6d308067 --- /dev/null +++ b/website_prevent_cls/README.rst @@ -0,0 +1,97 @@ +============================================= +Website Prevent Cumulative Layout Shift (CLS) +============================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:0a240861f217148390386bf8a5ec4f51c42cead4385f99593157eda2a4b5d568 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fwebsite-lightgray.png?logo=github + :target: https://github.com/OCA/website/tree/13.0/website_prevent_cls + :alt: OCA/website +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/website-13-0/website-13-0-website_prevent_cls + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/website&target_branch=13.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module can improve the Google PageSpeed score of individual website pages. + +In particular, it improves the score for Cumulative Layout Shift(CLS) by adding +the width and height to the images in the page. + +It also provides quick access to adjust the Width/height instead of using the +default optimize option when uploading an image. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +To use this module: + + * By default, it will add the selected width/height when uploading a new image + * When editing the image, in the Description pop-up, there's a provision to + adjust the size. + +Known issues / Roadmap +====================== + + * This does not add width/height for images that were already uploaded. These would either have to be reuploaded, or a migration script is needed. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Sunflower IT + +Contributors +~~~~~~~~~~~~ + +* Dan Kiplangat +* Tom Blauwendraat + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/website `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/website_prevent_cls/__init__.py b/website_prevent_cls/__init__.py new file mode 100644 index 0000000000..31660d6a96 --- /dev/null +++ b/website_prevent_cls/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import models diff --git a/website_prevent_cls/__manifest__.py b/website_prevent_cls/__manifest__.py new file mode 100644 index 0000000000..cab917ced7 --- /dev/null +++ b/website_prevent_cls/__manifest__.py @@ -0,0 +1,15 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Website Prevent Cumulative Layout Shift (CLS)", + "version": "13.0.2.0.1", + "author": "Sunflower IT,Odoo Community Association (OCA)", + "license": "AGPL-3", + "website": "https://github.com/OCA/website", + "category": "Website", + "summary": "Website Prevent Cumulative Layout Shift (CLS) to improve " + "Pagespeed score", + "depends": ["website"], + "data": ["templates/assets.xml"], + "installable": True, +} diff --git a/website_prevent_cls/i18n/website_prevent_cls.pot b/website_prevent_cls/i18n/website_prevent_cls.pot new file mode 100644 index 0000000000..2e5b0dbb6f --- /dev/null +++ b/website_prevent_cls/i18n/website_prevent_cls.pot @@ -0,0 +1,60 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * website_prevent_cls +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: website_prevent_cls +#. openerp-web +#: code:addons/website_prevent_cls/static/src/xml/alt_dialog.xml:0 +#, python-format +msgid "Height" +msgstr "" + +#. module: website_prevent_cls +#: model:ir.model,name:website_prevent_cls.model_ir_qweb +msgid "Qweb" +msgstr "" + +#. module: website_prevent_cls +#: model:ir.model,name:website_prevent_cls.model_ir_qweb_field_image +msgid "Qweb Field Image" +msgstr "" + +#. module: website_prevent_cls +#. openerp-web +#: code:addons/website_prevent_cls/static/src/xml/alt_dialog.xml:0 +#, python-format +msgid "Reduce the size as much as possible to increase performance." +msgstr "" + +#. module: website_prevent_cls +#. openerp-web +#: code:addons/website_prevent_cls/static/src/xml/alt_dialog.xml:0 +#, python-format +msgid "Size" +msgstr "" + +#. module: website_prevent_cls +#. openerp-web +#: code:addons/website_prevent_cls/static/src/xml/alt_dialog.xml:0 +#, python-format +msgid "Width" +msgstr "" + +#. module: website_prevent_cls +#. openerp-web +#: code:addons/website_prevent_cls/static/src/xml/alt_dialog.xml:0 +#: code:addons/website_prevent_cls/static/src/xml/alt_dialog.xml:0 +#, python-format +msgid "px" +msgstr "" diff --git a/website_prevent_cls/models/__init__.py b/website_prevent_cls/models/__init__.py new file mode 100644 index 0000000000..015b539cd6 --- /dev/null +++ b/website_prevent_cls/models/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import ir_qweb_fields diff --git a/website_prevent_cls/models/ir_qweb_fields.py b/website_prevent_cls/models/ir_qweb_fields.py new file mode 100644 index 0000000000..a30e4463c7 --- /dev/null +++ b/website_prevent_cls/models/ir_qweb_fields.py @@ -0,0 +1,49 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, models +from odoo.tools import ormcache +from odoo.tools.image import base64_to_image + + +class IrQweb(models.AbstractModel): + _inherit = "ir.qweb" + + def _post_processing_att(self, tagName, atts, options): + atts = super(IrQweb, self)._post_processing_att(tagName, atts, options) + + width = options.get("explicit_image_width") + height = options.get("explicit_image_height") + if width and height: + atts["width"] = width + atts["height"] = height + return atts + + +class Image(models.AbstractModel): + _inherit = "ir.qweb.field.image" + + @ormcache("base64_signature") + @api.model + def _get_image_size(self, record, field_name, base64_signature): + base64_source = record[field_name] + image = base64_to_image(base64_source) + width, height = image.size + return width, height + + @api.model + def record_to_html(self, record, field_name, options): + base64_signature = False + if record and hasattr(record, field_name) and record[field_name]: + base64_signature = record[field_name][:256] + # don't process empty source or SVG + if not base64_signature or base64_signature[:1] in (b"P", "P"): + return super(Image, self).record_to_html(record, field_name, options) + + width, height = self._get_image_size(record, field_name, base64_signature) + + template_options = options.get("template_options", {}).copy() + template_options["explicit_image_width"] = width + template_options["explicit_image_height"] = height + options["template_options"] = template_options + + return super(Image, self).record_to_html(record, field_name, options) diff --git a/website_prevent_cls/readme/CONTRIBUTORS.rst b/website_prevent_cls/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..cb606ac20f --- /dev/null +++ b/website_prevent_cls/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Dan Kiplangat +* Tom Blauwendraat diff --git a/website_prevent_cls/readme/DESCRIPTION.rst b/website_prevent_cls/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..1540dea3e3 --- /dev/null +++ b/website_prevent_cls/readme/DESCRIPTION.rst @@ -0,0 +1,7 @@ +This module can improve the Google PageSpeed score of individual website pages. + +In particular, it improves the score for Cumulative Layout Shift(CLS) by adding +the width and height to the images in the page. + +It also provides quick access to adjust the Width/height instead of using the +default optimize option when uploading an image. diff --git a/website_prevent_cls/readme/ROADMAP.rst b/website_prevent_cls/readme/ROADMAP.rst new file mode 100644 index 0000000000..d1e15083db --- /dev/null +++ b/website_prevent_cls/readme/ROADMAP.rst @@ -0,0 +1 @@ + * This does not add width/height for images that were already uploaded. These would either have to be reuploaded, or a migration script is needed. diff --git a/website_prevent_cls/readme/USAGE.rst b/website_prevent_cls/readme/USAGE.rst new file mode 100644 index 0000000000..357591325d --- /dev/null +++ b/website_prevent_cls/readme/USAGE.rst @@ -0,0 +1,5 @@ +To use this module: + + * By default, it will add the selected width/height when uploading a new image + * When editing the image, in the Description pop-up, there's a provision to + adjust the size. diff --git a/website_prevent_cls/static/description/icon.png b/website_prevent_cls/static/description/icon.png new file mode 100644 index 0000000000..3a0328b516 Binary files /dev/null and b/website_prevent_cls/static/description/icon.png differ diff --git a/website_prevent_cls/static/description/index.html b/website_prevent_cls/static/description/index.html new file mode 100644 index 0000000000..3b9ac0f9e3 --- /dev/null +++ b/website_prevent_cls/static/description/index.html @@ -0,0 +1,447 @@ + + + + + + +Website Prevent Cumulative Layout Shift (CLS) + + + +
+

Website Prevent Cumulative Layout Shift (CLS)

+ + +

Beta License: AGPL-3 OCA/website Translate me on Weblate Try me on Runboat

+

This module can improve the Google PageSpeed score of individual website pages.

+

In particular, it improves the score for Cumulative Layout Shift(CLS) by adding +the width and height to the images in the page.

+

It also provides quick access to adjust the Width/height instead of using the +default optimize option when uploading an image.

+

Table of contents

+ +
+

Usage

+

To use this module:

+
+
    +
  • By default, it will add the selected width/height when uploading a new image
  • +
  • When editing the image, in the Description pop-up, there’s a provision to +adjust the size.
  • +
+
+
+
+

Known issues / Roadmap

+
+
    +
  • This does not add width/height for images that were already uploaded. These would either have to be reuploaded, or a migration script is needed.
  • +
+
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Sunflower IT
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/website project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/website_prevent_cls/static/src/js/alt_dialog.js b/website_prevent_cls/static/src/js/alt_dialog.js new file mode 100644 index 0000000000..e6687b56b2 --- /dev/null +++ b/website_prevent_cls/static/src/js/alt_dialog.js @@ -0,0 +1,27 @@ +odoo.define("website_prevent_cls.alt_dialog", function(require) { + "use strict"; + + var AltDialog = require("wysiwyg.widgets.AltDialog"); + + AltDialog.include({ + xmlDependencies: AltDialog.prototype.xmlDependencies.concat([ + "/website_prevent_cls/static/src/xml/alt_dialog.xml", + ]), + + init: function(parent, options, media) { + this._super.apply(this, arguments); + if (media.width && media.height) { + this.image_width = media.width; + this.image_height = media.height; + } + }, + + save: function() { + var newWidth = this.$("#image_width").val(); + var newHeight = this.$("#image_height").val(); + $(this.media).attr("width", newWidth); + $(this.media).attr("height", newHeight); + return this._super.apply(this, arguments); + }, + }); +}); diff --git a/website_prevent_cls/static/src/js/widget_media.js b/website_prevent_cls/static/src/js/widget_media.js new file mode 100644 index 0000000000..d2460b9e6d --- /dev/null +++ b/website_prevent_cls/static/src/js/widget_media.js @@ -0,0 +1,22 @@ +odoo.define("website_prevent_cls.widget_media", function(require) { + "use strict"; + + var FileWidget = require("wysiwyg.widgets.media").FileWidget; + + FileWidget.include({ + _save: function() { + var self = this; + var res = this._super.apply(this, arguments); + res.then(function() { + // Add the dimensions here + var $media = self.media; + var img = self.selectedAttachments[0]; + if (img.image_width && img.image_height) { + $media.width = img.image_width; + $media.height = img.image_height; + } + }); + return res; + }, + }); +}); diff --git a/website_prevent_cls/static/src/xml/alt_dialog.xml b/website_prevent_cls/static/src/xml/alt_dialog.xml new file mode 100644 index 0000000000..19afd6c3ea --- /dev/null +++ b/website_prevent_cls/static/src/xml/alt_dialog.xml @@ -0,0 +1,59 @@ + + +
+ +
+ +
+
+
+
+
Width
+
+ +
+
px
+
+
+
+
+ +
+
+
+
+
Height
+
+ +
+
px
+
+
+
+
+
+
+ +
diff --git a/website_prevent_cls/templates/assets.xml b/website_prevent_cls/templates/assets.xml new file mode 100644 index 0000000000..967bd9104e --- /dev/null +++ b/website_prevent_cls/templates/assets.xml @@ -0,0 +1,19 @@ + + +