diff --git a/CHANGELOG.md b/CHANGELOG.md
index 58dee305d..01460435a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,24 @@
# Changelog
-## [v2.0.0-alpha.5](https://github.com/marcantondahmen/automad/commit/3443ff5f06d1a52661f7f5318ee183248e95a4d3)
+## [v2.0.0-alpha.6](https://github.com/marcantondahmen/automad/commit/2f32588222c4829ca3fad8009d9093949f48360d)
-Sun, 30 Jun 2024 19:35:04 +0200
+Sun, 15 Sep 2024 19:28:10 +0200
+
+### New Features
+
+- **ui**: improve button loading animation ([7fbb813ef](https://github.com/marcantondahmen/automad/commit/7fbb813ef89b19da0b16662764da3fa914a9aa7c))
+- add customization fields for CSS and JS code and files ([660691c24](https://github.com/marcantondahmen/automad/commit/660691c2459c33750436a57e50ad7d8237b18e47))
+- add support for remote webp images ([0f1885dd9](https://github.com/marcantondahmen/automad/commit/0f1885dd9d1236549d45b90a936e98ed24f39fab))
+- move mail config to a separate file ([b48b25329](https://github.com/marcantondahmen/automad/commit/b48b253292e950dcbd74d8bd81f898e9933c31c2))
+
+### Bugfixes
+
+- **ui**: fix visibility of navbar items on medium size screens ([2f3258822](https://github.com/marcantondahmen/automad/commit/2f32588222c4829ca3fad8009d9093949f48360d))
+- fix processing of nested in-page editing buttons ([4fa5eb0d3](https://github.com/marcantondahmen/automad/commit/4fa5eb0d3cad40ee39338c975eb427b0c68c6674))
+
+## [v2.0.0-alpha.5](https://github.com/marcantondahmen/automad/commit/09e8864bdc5a62ba735aa0a7f08d358e92aaa735)
+
+Sun, 30 Jun 2024 19:41:20 +0200
### New Features
@@ -329,16 +345,3 @@ Mon, 9 Aug 2021 23:21:36 +0200
### Bugfixes
- **ui**: fix updating links to images that belong to the page they are used on ([723a6be37](https://github.com/marcantondahmen/automad/commit/723a6be37fb283fdcd42a5a365e6089509a25139))
-
-## [v1.8.2](https://github.com/marcantondahmen/automad/commit/e070a89209c2e90eadb9be4b77580beef6aa75d1)
-
-Sun, 8 Aug 2021 22:25:34 +0200
-
-### New Features
-
-- **samples**: add pagelist example page ([2a033f4da](https://github.com/marcantondahmen/automad/commit/2a033f4dada04e3d2fd7d24f2d2d6b1bfd04986e))
-- **samples**: add tags and filters to example pages ([d18f2482b](https://github.com/marcantondahmen/automad/commit/d18f2482b2f258d7b42a6609a439769773ffb7f8))
-
-### Bugfixes
-
-- **themes**: fix thumbnail visibility ([45ed2eee5](https://github.com/marcantondahmen/automad/commit/45ed2eee5f1cf0b81148678820b6a796bf4791e2))
diff --git a/README.md b/README.md
index 2c5a7cd5e..c32aa1fc7 100644
--- a/README.md
+++ b/README.md
@@ -27,14 +27,12 @@ In case you quickly want to try out Automad without setting up a server first, j
## Installation
-Note that this repository only contains source code. Please follow the instructions below in order to install a fully bundled
-version of Automad using [Docker](https://docker.com) or [Composer](https://getcomposer.org).
-It is also possible to manually [download](https://github.com/automadcms/automad-dist/archive/refs/heads/master.zip)
-and [install](#manual-installation) Automad.
+Note that this repository only contains source code. Please follow the instructions below in order to install a fully bundled version of Automad using [Docker](https://github.com/automadcms/automad-docker) or [Composer](https://packagist.org/packages/automad/automad).
+It is also possible to manually [download](https://github.com/automadcms/automad-dist/archive/refs/heads/master.zip) and [install](#manual-installation) Automad.
### Composer
-The fastest way to get Automad up and running is to use Composer.
+The fastest way to get Automad up and running is to use [Composer](https://packagist.org/packages/automad/automad).
```bash
composer create-project automad/automad . v2.x-dev
@@ -44,7 +42,7 @@ Follow this [guide](https://automad.org/version-2#getting-started) to finish the
### Docker
-It is also possible to run Automad in a [Docker](https://hub.docker.com/r/automad/automad) container including **Nginx** and **PHP 8.3**.
+It is also possible to run Automad in a [Docker](https://github.com/automadcms/automad-docker) container including **Nginx** and **PHP 8.3**.
```bash
docker run -dp 80:80 -v ./app:/app --name mysite automad/automad:v2
@@ -93,6 +91,7 @@ In case you are interested in contributing, the following types of contribution
- [Publishing packages](https://automad.org/developer-guide/publishing-packages) like themes or extensions to the Automad package [browser](https://packages.automad.org)
- Giving feedback and helping to grow a [community](https://automad.org/discuss)
- Reporting bugs or requesting features at [GitHub](https://github.com/marcantondahmen/automad/issues)
+- Reporting [security vulnerabilities](https://github.com/marcantondahmen/automad/security)
However, I do not exclude at this point using parts of Automad's source in future projects under different licenses. In order to avoid having to ask anybody for permission when doing so, I will not accept any contributions to **this** repository. Please understand that pull requests will therefore be ignored.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..e9afc7fb0
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,58 @@
+# Reporting Vulnerabilities
+
+Security should be taken seriously whenever private data and/or the digital distribution of any data is at play. Automad is **no exception** here.
+
+Whenever you encounter a security vulnerability, please feel free to [report it privately](https://github.com/marcantondahmen/automad/security/advisories/new) and provide the following information:
+
+- A brief description of the vulnerability
+- A use case for an exploit and a valid attack vector
+- All required steps in order to enable an attacker to exploit the vulnerability
+
+> [!IMPORTANT]
+> Please note that pull-requests for this repository will be ignored as stated in the README.
+
+## Quality of Reports
+
+Unfortunately false positive vulnerability reports pose a substantial threat to cybersecurity since maintainers of open-source projects keep on drowning in reports. This implies that real threats will not get the attention that will be required to handle them properly and with care.
+
+However, _all reported vulnerabilities are reviewed_ and handled with priority as soon as possible.
+
+Please note that after an initial triage only reports of exploitable vulnerabilities with realistic attack vectors are followed up. Please make sure that you are famliliar with Automad's architecture and fully understand its implications for security as described below.
+
+## Architecture
+
+Automad is a _flat-file_ content management system that doesn't have a database. Content is stored on disk in `.json` files. Pages are only rendered and saved as static `.html` files when content has changed. From a security perspective, this architecture has significant advantages over database driven websites.
+
+### Users and Roles
+
+Automad only knows two types of users — _visitors_ and _admins_. Only admins can create, delete or modify content and change settings. Visitors can only view content.
+
+Only admins have actual user accounts on an Automad installation. They all share the same privileges. Usually there is only a single admin but it is possible to add additional ones via invitations. Visitors have no user account.
+
+### Sessions
+
+On every visit of an Automad site, a session is created on the server for both types of users — visitors as well as admins. On the client, a cookie is created that only contains the session id in order to identify a session. The session id and also the cookie itself don't contain any personal data or any data that can be used in order to identify an actual person.
+
+When a visitor visits the site, also the user's session on the server doesn't contain or store any personal data. In fact it stays empty except a user chooses to persist preferences such as language or color scheme settings and as long as the installation and templates support such features.
+
+Regarding Automad's core functionality, the session is only used to verify whether a user is signed in as an admin and therefore authorized to edit content — this is not only true for the dashboard but for the entire site in order to enable admins to edit content in the in-page editing mode.
+
+After successfully being authenticated, the _username_ and a _csrf token_ will be stored in a user's session. During password reset requests a reset token may be stored temporarily as well. Automad itself will not store any other data than the aforementioned.
+
+### Implications for Security
+
+In order to fully understand possible attack vectors and the severity of reported vulnerabilities, one has to take the architectural concept, the way sessions work in Automad and the limitation of the visitor role into account. Generally, vulnerabilities can be broken down into two categories — _XSS (cross-site-scripting)_ and _CSRF (cross-site-request-forgery)_.
+
+#### XSS
+
+In general, XSS attacks imply that an **unauthorized** user can store malicious code in some kind of data store due to the lack of sanitization of user input. This code is then typically executed in the browser by other users and can therefore be used for stealing user related data such as cookies. Typically forum software or commenting systems are exposed to such attack vectors since anybody can register and post content. In such scenarios a proper sanitization of user input is mandatory.
+
+In Automad this kind of attacks are technically not possible due to the nature of the underlying architecture. The input of unprivileged users such as visitors is never stored or used in any way to permanently alter the system as it would be the case in a commenting system or forum.
+
+As previously described, only admins can create, update or delete content. Please note that this also includes the ability to install templates and modify them. An admin is allowed to add executable JavaScript code to a site. It cannot be stressed enough that this ability itself doesn't pose a threat and also is fundamentally different to the nature of an XSS attack. Admins are by design privileged users that on one hand must understand their responsibility and on the other hand need the necessary freedom to actually keep a site running. This concept is not new and applies to almost every system that is connected to the internet.
+
+Therefore the only type of user that can act as a malicious party are admins. Since visitors have no session data on the server or inside of the cookie, even a hacked admin account can't steal relevant data. This alone renders XSS attacks useless.
+
+#### CSRF
+
+In contrast to XSS attacks, CSRF attacks potentially pose a real threat. Automad has standard measures in place in order to prevent CSRF attacks.
diff --git a/automad/lang/english.json b/automad/lang/english.json
index 8b2c6d21d..04d631a69 100644
--- a/automad/lang/english.json
+++ b/automad/lang/english.json
@@ -10,8 +10,8 @@
"addWatermarkTitle": "Choose the watermark type",
"addedSuccess": "Successfully added",
"adjustTab": "Adjust",
- "alignLeft": "Align left",
"alignCenter": "Align center",
+ "alignLeft": "Align left",
"alignRight": "Align right",
"alreadyExists": "already exists!",
"annotateTab": "Draw",
@@ -20,33 +20,33 @@
"back": "Back",
"backgroundColor": "Background Color",
"backgroundImage": "Background Image",
+ "blockquote": "Blockquote",
+ "blur": "Blur",
+ "blurTool": "Blur",
"bold": "Bold",
"borderColor": "Border Color",
"borderRadius": "Corner Radius",
- "borderWidth": "Border Width",
"borderStyle": "Border Style",
- "blur": "Blur",
- "blurTool": "Blur",
- "blockquote": "Blockquote",
+ "borderWidth": "Border Width",
"brightnessTool": "Brightness",
"browseFiles": "Browse Files",
- "buttonsBlockTitle": "Buttons",
- "buttonsBlockSettings": "Settings",
"buttonsBlockAlignment": "Edit alignment",
"buttonsBlockGap": "Gap between buttons",
"buttonsBlockPlaceholder": "Enter text ...",
- "caption": "Caption",
+ "buttonsBlockSettings": "Settings",
+ "buttonsBlockTitle": "Buttons",
"cacheClearedSuccess": "The cache got cleared successfully!",
"cacheDisabled": "Caching is disabled",
"cacheEnabled": "Caching is enabled",
"cachePurgedError": "The cache directory could not be purged!",
"cachePurgedSuccess": "The cache directory got purged successfully!",
"cancel": "Cancel",
+ "caption": "Caption",
"changesLoseConfirmation": "All changes will be lost",
"changesLoseConfirmationHint": "Are you sure you want to continue?",
"cinemascope": "Cinemascope",
- "classicTv": "Classic TV",
"className": "CSS Class",
+ "classicTv": "Classic TV",
"clickToDelete": "Click to delete",
"close": "Close",
"code": "Source Code",
@@ -67,6 +67,10 @@
"currentPassword": "Current Password",
"currentPasswordError": "The current password is wrong!",
"custom": "Custom",
+ "customCSS": "Custom CSS",
+ "customCSSFile": "Custom CSS File",
+ "customJS": "Custom JavaScript",
+ "customJSFile": "Custom JavaScript File",
"dashboardTitle": "Dashboard",
"date": "Date",
"debugDisabled": "Debugging is disabled",
@@ -74,6 +78,7 @@
"delete": "Delete",
"deletePage": "Delete Page",
"deleteSelected": "Delete Selected",
+ "delimiter": "Delimiter",
"deteledSuccess": "Successfully removed",
"disable": "Disable",
"discardImageChanges": "Are you sure you want to close the editor without saving changes to the image?",
@@ -83,40 +88,39 @@
"duplicate": "Duplicate",
"duplicatePage": "Duplicate Page",
"edit": "Edit",
- "editorPlaceholder": "Click here to add content",
"editFileInfo": "Edit File Info",
"editImage": "Edit Image",
"editStyle": "Edit Style",
+ "editorPlaceholder": "Click here to add content",
"ellipse": "Ellipse",
"ellipseTool": "Ellipse",
"email": "Email address",
- "emailFrom": "Email sender address",
"emailAutomatic": "This email has been sent automatically. Please do not reply to this email.",
+ "emailFrom": "Email sender address",
"emailHello": "Hello",
"emailInviteButton": "Create Password Now",
"emailInviteSubject": "You have been added as a new user!",
"emailInviteText": "a new user account on {} has been created for you. You can use the button below to create your password.",
+ "emailRequiredError": "Please enter a valid email address",
"emailResetPasswordSubject": "Your authentication token",
"emailResetPasswordTextBottom": "In case you did not initiate this request yourself, you can safely ignore this message.",
"emailResetPasswordTextTop": "the requested authentication token for your account on {} can be found below. You can use that token in order to create a new password for you now.",
- "emailRequiredError": "Please enter a valid email address",
"enable": "Enable",
"extension": "Extension",
- "delimiter": "Delimiter",
"feedDisabled": "The RSS feed is disabled",
"feedEnabled": "The RSS feed is enabled",
"fetchingDataError": "Error fetching data from API!",
- "fieldsColors": "Colors",
"fieldsContent": "Content",
+ "fieldsCustomize": "Customize",
"fieldsSettings": "Settings",
"file": "File",
- "files": "Files",
"fileName": "Filename",
- "filelistBlockTitle": "Filelist",
+ "filelistBlockDefaultFile": "Default",
"filelistBlockFile": "Template file",
"filelistBlockPattern": "File pattern",
- "filelistBlockDefaultFile": "Default",
"filelistBlockSortOrder": "Sort order",
+ "filelistBlockTitle": "Filelist",
+ "files": "Files",
"filterContent": "Filter Content",
"filtersTab": "Filters",
"finetuneTab": "Finetune",
@@ -125,45 +129,45 @@
"fontFamily": "Font family",
"fontSize": "Font Size",
"forgotPassword": "Forgot Password",
- "galleryBlockTitle": "Image Gallery",
"galleryBlockLayout": "Layout",
- "galleryBlockLayoutColumns": "Column Layout",
- "galleryBlockLayoutRows": "Row Layout",
+ "galleryBlockLayoutCleanBottom": "Clean bottom edge",
"galleryBlockLayoutColumnWidth": "Column Width",
- "galleryBlockLayoutRowHeight": "Row Height",
+ "galleryBlockLayoutColumns": "Column Layout",
"galleryBlockLayoutGap": "Gap",
- "galleryBlockLayoutCleanBottom": "Clean bottom edge",
+ "galleryBlockLayoutRowHeight": "Row Height",
+ "galleryBlockLayoutRows": "Row Layout",
+ "galleryBlockTitle": "Image Gallery",
"heading": "Heading",
"headings": "Headings",
"hidePage": "Hide Page in Navigation",
"horizontal": "Horizontal",
"hsvTool": "HSV",
"hue": "Hue",
- "i18nEnabled": "Language routing enabled",
"i18nDisabled": "Language routing disabled",
+ "i18nEnabled": "Language routing enabled",
"imageDimensionsHoverTitle": "Saved image size (width x height)",
- "imageTool": "Image",
- "imageSlideshowBlockTitle": "Image Slideshow",
- "imageSlideshowBlockSettings": "Settings",
- "imageSlideshowBlockEffect": "Effect",
- "imageSlideshowBlockImageWidth": "Image Width",
- "imageSlideshowBlockImageHeight": "Image Height",
- "imageSlideshowBlockSpaceBetween": "Space between images in pixels",
- "imageSlideshowBlockSlidesPerView": "Images per view",
- "imageSlideshowBlockLoop": "Continous looping",
"imageSlideshowBlockAutoplay": "Autoplay mode",
"imageSlideshowBlockBreakpoints": "Number of images based on block size",
- "imageSlideshowBlockBreakpointsHelp": "Add breakpoints as pairs of a minimum block size and a number of images per view, separated by a colon. Multiple breakpoints can be separated by whitespace.",
"imageSlideshowBlockBreakpointsError": "Please enter valid breakpoints that include block width and image width separate by a colon.",
+ "imageSlideshowBlockBreakpointsHelp": "Add breakpoints as pairs of a minimum block size and a number of images per view, separated by a colon. Multiple breakpoints can be separated by whitespace.",
+ "imageSlideshowBlockEffect": "Effect",
+ "imageSlideshowBlockImageHeight": "Image Height",
+ "imageSlideshowBlockImageWidth": "Image Width",
+ "imageSlideshowBlockLoop": "Continous looping",
+ "imageSlideshowBlockSettings": "Settings",
+ "imageSlideshowBlockSlidesPerView": "Images per view",
+ "imageSlideshowBlockSpaceBetween": "Space between images in pixels",
+ "imageSlideshowBlockTitle": "Image Slideshow",
+ "imageTool": "Image",
"import": "Import",
"importFailedError": "The file import has failed!",
"importFromUrl": "Import",
"importUrl": "Enter a file URL",
"importing": "Importing ...",
- "indent": "Indent",
- "inlineCode": "Inline code",
"inPageEdit": "In-Page Edit Mode",
"inPagePlaceholder": "Add content here",
+ "indent": "Indent",
+ "inlineCode": "Inline code",
"insertCodeBlock": "Insert code block",
"insertHorizontalLine": "Horizontal line",
"insertImage": "Insert image",
@@ -191,29 +195,29 @@
"link": "Link",
"list": "List",
"loading": "Loading...",
- "mailBlockTitle": "Mail Form",
+ "mailBlockAddressFieldLabel": "Address field label",
+ "mailBlockBodyFieldLabel": "Body field label",
"mailBlockDefaultError": "An error occurred while sending mail.",
- "mailBlockDefaultSuccess": "The mail has been sent successfully.",
"mailBlockDefaultLabelAddress": "Email Address",
- "mailBlockDefaultLabelSubject": "Subject",
"mailBlockDefaultLabelBody": "Message",
"mailBlockDefaultLabelSend": "Send Email",
- "mailBlockTo": "Receiving mail address",
- "mailBlockAddressFieldLabel": "Address field label",
- "mailBlockSubjectFieldLabel": "Subject field label",
- "mailBlockBodyFieldLabel": "Body field label",
- "mailBlockSendButtonLabel": "Send button label",
+ "mailBlockDefaultLabelSubject": "Subject",
+ "mailBlockDefaultSuccess": "The mail has been sent successfully.",
"mailBlockError": "Error message",
+ "mailBlockSendButtonLabel": "Send button label",
+ "mailBlockSubjectFieldLabel": "Subject field label",
"mailBlockSuccess": "Success message",
+ "mailBlockTitle": "Mail Form",
+ "mailBlockTo": "Receiving mail address",
"matchRowHeight": "Match Content to Row Height",
"missingPageTitleError": "Title missing!",
"missingTargetPageError": "Please select a page as destination!",
"missingUrlError": "Please enter a valid URL!",
"more": "More",
"moreThemes": "Get more themes here",
+ "moveDown": "Move down",
"movePage": "Move Page",
"moveUp": "Move up",
- "moveDown": "Move down",
"name": "Name",
"nameIsRequired": "Name is required.",
"new": "New",
@@ -246,13 +250,12 @@
"packagesUpdatingAll": "Updating packages ...",
"packagistConnectionError": "Can't connect to the Packagist API",
"padding": "Padding",
- "paddingHorizontal": "Horizontal Padding",
- "paddingVertical": "Vertical Padding",
"paddingBottom": "Spacing Bottom",
+ "paddingHorizontal": "Horizontal Padding",
"paddingLeft": "Spacing Left",
"paddingRight": "Spacing Right",
"paddingTop": "Spacing Top",
- "pageImages": "Page Images",
+ "paddingVertical": "Vertical Padding",
"pageHistory": "Restore a Previous Version",
"pageHistoryNoRevision": "This page has no revisions yet.",
"pageHistoryNotFound": "Revision not found!",
@@ -260,24 +263,25 @@
"pageHistoryRestoreHomeConfirm": "Do you want to restore the page in place?",
"pageHistoryRestoreHomeText": "Restore an older version of the homepage in place.",
"pageHistoryRestoreText": "Restore an older version of the current page as a copy.",
- "pagelistBlockTitle": "Pagelist",
+ "pageImages": "Page Images",
+ "pageNotFoundError": "Page Not Found!",
+ "pageSlug": "Directory Name",
+ "pageTags": "Tags (Separate multiple tags by comma or tab)",
+ "pageTemplate": "Template",
+ "pagelistBlockContext": "Parent page",
"pagelistBlockDefaultFile": "Default",
+ "pagelistBlockExcludeCurrent": "Exclude this page",
+ "pagelistBlockExcludeHidden": "Exclude hidden pages",
"pagelistBlockFile": "Template file",
- "pagelistBlockContext": "Parent page",
- "pagelistBlockSortField": "Sort by",
- "pagelistBlockSortOrder": "Sort order",
- "pagelistBlockType": "Type",
"pagelistBlockFilter": "Filter by tag",
- "pagelistBlockExcludeHidden": "Exclude hidden pages",
- "pagelistBlockExcludeCurrent": "Exclude this page",
"pagelistBlockFilterByTemplate": "Filter by template",
"pagelistBlockFilterByUrl": "Filter by URL",
- "pagelistBlockOffset": "Offset",
"pagelistBlockLimit": "Limit",
- "pageNotFoundError": "Page Not Found!",
- "pageSlug": "Directory Name",
- "pageTags": "Tags (Separate multiple tags by comma or tab)",
- "pageTemplate": "Template",
+ "pagelistBlockOffset": "Offset",
+ "pagelistBlockSortField": "Sort by",
+ "pagelistBlockSortOrder": "Sort order",
+ "pagelistBlockTitle": "Pagelist",
+ "pagelistBlockType": "Type",
"paragraph": "Paragraph",
"password": "Password",
"passwordChangedSuccess": "Your password has been changed successfully!",
@@ -350,9 +354,9 @@
"signedOut": "Signed out",
"signedOutSuccess": "You have been successfully signed out.",
"size": "Size",
- "snippetBlockTitle": "Snippet",
"snippetBlockFile": "Snippet file",
"snippetBlockSnippet": "Code snippet",
+ "snippetBlockTitle": "Snippet",
"square": "Square",
"strikeThrough": "Strike through",
"stroke": "Stroke",
@@ -375,21 +379,21 @@
"systemDebugInfo": "When debugging is enabled, all of Automad's processes will be logged to your browser's console as well as to .json files inside the temporary directory. Debugging is only needed for development or troubleshooting and should be disabled in all other cases.",
"systemI18n": "Internationalization",
"systemI18nCardInfo": "Enabled and configure multilingual content",
- "systemI18nEnable": "Language Routing",
"systemI18nDefault": "Default Locale",
+ "systemI18nEnable": "Language Routing",
"systemI18nInfo": "Internationalization can be enabled in order to route visitors to pages that are served in the language that matches their locale. Adding languages can be achieved by grouping pages of a particular language under a top-level page that has a two letter language code as slug such as /en or /de. The first top-level page serves as fallback for all languages that are not available on a site.",
"systemLanguage": "Language",
"systemLanguageCardInfo": "Change the language for the Automad user interface",
"systemLanguageInfo": "There are several languages available for the Automad user interface. The translations are created automatically. This feature is still in an experimental phase and therefore some translations might not be accurate.",
"systemMail": "Mail",
- "systemMailInfo": "You can configure how emails are sent for registration, account recovery or from contact forms on the website. By default, Automad uses PHP's sendmail function for this. However, it is recommended to send emails via an SMTP server, whose access data can be entered below.",
"systemMailCardInfo": "Configure how emails are sent by your server",
"systemMailConfigError": "Error saving mail configuration.",
- "systemMailSmtpPasswordPlaceholder": "Leave this field empty in order to keep the existing password",
+ "systemMailInfo": "You can configure how emails are sent for registration, account recovery or from contact forms on the website. By default, Automad uses PHP's sendmail function for this. However, it is recommended to send emails via an SMTP server, whose access data can be entered below.",
"systemMailReset": "Do you want to reset the current email configuration?",
"systemMailSendTest": "Send Test Email",
- "systemMailSendTestSuccess": "A test email was successfully sent to",
"systemMailSendTestError": "Error sending test email.",
+ "systemMailSendTestSuccess": "A test email was successfully sent to",
+ "systemMailSmtpPasswordPlaceholder": "Leave this field empty in order to keep the existing password",
"systemRssFeed": "RSS Feed",
"systemRssFeedCardInfo": "Enable and configure the RSS feed for your site",
"systemRssFeedEnable": "RSS Feed",
@@ -431,15 +435,15 @@
"systemUsersSendInvitationSuccess": "The invitation has been sent successfully.",
"systemUsersYou": "You",
"table": "Table",
- "tableWithHeadings": "With headings",
- "tableWithoutHeadings": "Without headings",
"tableAddColumnLeft": "Add column to left",
"tableAddColumnRight": "Add column to right",
- "tableDeleteColumn": "Delete column",
- "tableOfContentsBlockTitle": "Table of Contents",
"tableAddRowAbove": "Add row above",
"tableAddRowBelow": "Add row below",
+ "tableDeleteColumn": "Delete column",
"tableDeleteRow": "Delete row",
+ "tableOfContentsBlockTitle": "Table of Contents",
+ "tableWithHeadings": "With headings",
+ "tableWithoutHeadings": "Without headings",
"textAlignment": "Text alignment",
"textColor": "Text Color",
"textSpacings": "Text spacings",
@@ -453,12 +457,12 @@
"trashIsEmpty": "No pages found",
"trashPermanentlyDelete": "Permanently delete",
"trashPermanentlyDeleteConfirm": "Do you want to permanently delete this page?",
- "trashTitle": "Trash",
"trashRestore": "Restore page",
+ "trashTitle": "Trash",
"tuneOrMove": "Click to tune or drag to move",
- "underline": "Underline",
"unFlipX": "Un-Flip X",
"unFlipY": "Un-Flip Y",
+ "underline": "Underline",
"undoTitle": "Undo last operation",
"unorderedList": "Unordered list",
"unsupportedFileTypeError": "Unsupported file type",
diff --git a/automad/src/client/admin/components/Fields/Code.ts b/automad/src/client/admin/components/Fields/Code.ts
new file mode 100644
index 000000000..d9e6a825e
--- /dev/null
+++ b/automad/src/client/admin/components/Fields/Code.ts
@@ -0,0 +1,118 @@
+/*
+ * ....
+ * .: '':.
+ * :::: ':..
+ * ::. ''..
+ * .:'.. ..':.:::' . :. '':.
+ * :. '' '' '. ::::.. ..:
+ * ::::. ..':.. .'''::::: .
+ * :::::::.. '..:::: :. :::: :
+ * ::'':::::::. ':::.'':.:::: :
+ * :.. ''::::::....': '':: :
+ * :::::. '::::: : .. '' .
+ * .''::::::::... ':::.'' ..'' :.''''.
+ * :..:::''::::: :::::...:'' :..:
+ * ::::::. ':::: :::::::: ..:: .
+ * ::::::::.:::: :::::::: :'':.:: .''
+ * ::: '::::::::.' ''::::: :.' '': :
+ * ::: :::::::::..' :::: ::...' .
+ * ::: .:::::::::: :::: :::: .:'
+ * '::' ''::::::: :::: : :: :
+ * ':::: :::: :'' .:
+ * :::: :::: ..''
+ * :::: ..:::: .:''
+ * '''' '''''
+ *
+ *
+ * AUTOMAD
+ *
+ * Copyright (c) 2024 by Marc Anton Dahmen
+ * https://marcdahmen.de
+ *
+ * Licensed under the MIT license.
+ */
+
+import { create, CSS, FieldTag, FormDataProviders } from '@/admin/core';
+import { CodeEditor } from '@/admin/core/code';
+import { UndoValue } from '@/admin/types';
+import { BaseFieldComponent } from './BaseField';
+
+/**
+ * A code field with a label.
+ *
+ * @extends InputComponent
+ */
+export class CodeComponent extends BaseFieldComponent {
+ /**
+ * The editor value that serves a input value for the parent form.
+ */
+ value: string = '';
+
+ /**
+ * The editor component.
+ */
+ private editor: CodeEditor;
+
+ /**
+ * Get the language based on the field name.
+ *
+ * @param name
+ */
+ private getLanguageFromName = (name: string) => {
+ const sanitized = name.replace(/\W+/g, '');
+
+ if (sanitized.match(/js/i)) {
+ return 'javascript';
+ }
+
+ return 'css';
+ };
+
+ /**
+ * Render the field.
+ */
+ protected createInput(): void {
+ const { name, id, value } = this._data;
+
+ this.setAttribute('name', name);
+ this.value = value as string;
+
+ this.editor = new CodeEditor(
+ create('div', [CSS.codeflask], { id }, this),
+ value as string,
+ this.getLanguageFromName(name),
+ (code) => (this.value = code)
+ );
+ }
+
+ /**
+ * Return the field that is observed for changes.
+ *
+ * @return the input field
+ */
+ getValueProvider(): HTMLElement {
+ return this;
+ }
+
+ /**
+ * A function that can be used to mutate the field value.
+ *
+ * @param value
+ */
+ async mutate(value: UndoValue): Promise {
+ this.editor.codeFlask.updateCode(value);
+ this.value = value;
+ }
+
+ /**
+ * Query the current field value.
+ *
+ * @return the current value
+ */
+ query() {
+ return this.value;
+ }
+}
+
+FormDataProviders.add(FieldTag.code);
+customElements.define(FieldTag.code, CodeComponent);
diff --git a/automad/src/client/admin/components/Forms/Form.ts b/automad/src/client/admin/components/Forms/Form.ts
index 2d8a7187c..e0266c1b9 100644
--- a/automad/src/client/admin/components/Forms/Form.ts
+++ b/automad/src/client/admin/components/Forms/Form.ts
@@ -253,8 +253,9 @@ export class FormComponent extends BaseComponent {
const lockId = App.addNavigationLock();
this.submitButtons.forEach((button) => {
- button.classList.add(CSS.buttonLoading);
- button.prepend(create('am-spinner'));
+ if (button.classList.contains(CSS.button)) {
+ button.classList.add(CSS.buttonLoading);
+ }
});
queryAll(
@@ -285,7 +286,6 @@ export class FormComponent extends BaseComponent {
this.submitButtons.forEach((button) => {
button.classList.remove(CSS.buttonLoading);
- query('am-spinner', button)?.remove();
});
}
diff --git a/automad/src/client/admin/components/Forms/PageDataForm.ts b/automad/src/client/admin/components/Forms/PageDataForm.ts
index e1a42f990..87fa91260 100644
--- a/automad/src/client/admin/components/Forms/PageDataForm.ts
+++ b/automad/src/client/admin/components/Forms/PageDataForm.ts
@@ -45,6 +45,7 @@ import {
Attr,
Binding,
create,
+ createCustomizationFields,
createField,
createFieldSections,
createLabelFromField,
@@ -413,6 +414,8 @@ export class PageDataFormComponent extends FormComponent {
readme,
});
+ createCustomizationFields(fields, this.sections);
+
Object.keys(this.sections).forEach((item: FieldSectionName) => {
fieldGroup({
section: this.sections[item],
diff --git a/automad/src/client/admin/components/Forms/SharedDataForm.ts b/automad/src/client/admin/components/Forms/SharedDataForm.ts
index bb6323fa6..29d11466a 100644
--- a/automad/src/client/admin/components/Forms/SharedDataForm.ts
+++ b/automad/src/client/admin/components/Forms/SharedDataForm.ts
@@ -37,6 +37,7 @@ import {
Attr,
Binding,
create,
+ createCustomizationFields,
createField,
createFieldSections,
EventName,
@@ -217,6 +218,8 @@ export class SharedDataFormComponent extends FormComponent {
name: `data[${App.reservedFields.SYNTAX_THEME}]`,
});
+ createCustomizationFields(fields, this.sections);
+
Object.keys(this.sections).forEach((item: FieldSectionName) => {
fieldGroup({
section: this.sections[item],
diff --git a/automad/src/client/admin/components/Pages/Page.ts b/automad/src/client/admin/components/Pages/Page.ts
index bfe87664e..12185433f 100644
--- a/automad/src/client/admin/components/Pages/Page.ts
+++ b/automad/src/client/admin/components/Pages/Page.ts
@@ -198,9 +198,9 @@ const renderMenu = (): string => {
- ${App.text('fieldsColors')}
+ ${App.text('fieldsCustomize')}
{
- ${App.text('fieldsColors')}
+ ${App.text('fieldsCustomize')}
{
- ${App.text('fieldsColors')}
+ ${App.text('fieldsCustomize')}
{
- ${App.text('fieldsColors')}
+ ${App.text('fieldsCustomize')}
{