diff --git a/.nvmrc b/.nvmrc index eb800ed45..1efe0ac63 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v18.19.0 +v20.15.1 diff --git a/README.md b/README.md index b3f5a0bb8..24f0726e4 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [Publii](https://getpublii.com/) is a desktop-based CMS for Windows, Mac and Linux that makes creating static websites fast and hassle-free, even for beginners. -**Current version: 0.45.2 (build 16609)** +**Current version: 0.46.0 (build 16889)** ## Why Publii? Unlike static-site generators that are often unwieldy and difficult to use, Publii provides an @@ -82,4 +82,4 @@ Support this project by becoming a sponsor. Your logo will show up here with a l ## License -Copyright (c) 2022 TidyCustoms. General Public License v3.0, read [LICENSE](https://getpublii.com/license.html) for details. +Copyright (c) 2024 TidyCustoms. General Public License v3.0, read [LICENSE](https://getpublii.com/license.html) for details. diff --git a/app/back-end/app-preload.js b/app/back-end/app-preload.js index c7a34cf90..2f4f6481e 100644 --- a/app/back-end/app-preload.js +++ b/app/back-end/app-preload.js @@ -50,11 +50,19 @@ contextBridge.exposeInMainWorld('mainProcessAPI', { 'app-post-load', 'app-post-save', 'app-post-cancel', + 'app-page-load', + 'app-page-save', + 'app-page-cancel', + 'app-pages-hierarchy-load', + 'app-pages-hierarchy-save', 'app-image-upload', 'app-image-upload-remove', 'app-post-delete', 'app-post-duplicate', 'app-post-status-change', + 'app-page-delete', + 'app-page-duplicate', + 'app-page-status-change', 'app-site-regenerate-thumbnails', 'app-site-abort-regenerate-thumbnails', 'app-preview-render', @@ -67,6 +75,7 @@ contextBridge.exposeInMainWorld('mainProcessAPI', { 'app-license-accept', 'app-deploy-render-abort', 'app-deploy-abort', + 'app-deploy-continue', 'app-deploy-render', 'app-deploy-upload', 'app-sync-is-done', @@ -115,7 +124,8 @@ contextBridge.exposeInMainWorld('mainProcessAPI', { 'app-wxr-import-progress', 'app-show-search-form', 'block-editor-undo', - 'block-editor-redo' + 'block-editor-redo', + 'no-remote-files' ]; if (validChannels.includes(channel)) { @@ -158,6 +168,12 @@ contextBridge.exposeInMainWorld('mainProcessAPI', { 'app-post-deleted', 'app-post-duplicated', 'app-post-status-changed', + 'app-page-loaded', + 'app-page-saved', + 'app-page-deleted', + 'app-page-duplicated', + 'app-page-status-changed', + 'app-pages-hierarchy-loaded', 'app-site-regenerate-thumbnails-error', 'app-site-regenerate-thumbnails-success', 'app-preview-rendered', diff --git a/app/back-end/app.js b/app/back-end/app.js index a4b4c95bb..69839d5e0 100644 --- a/app/back-end/app.js +++ b/app/back-end/app.js @@ -14,6 +14,7 @@ const url = require('url'); const { screen, shell, nativeTheme, Menu, dialog, BrowserWindow } = require('electron'); // Collection classes const Posts = require('./posts.js'); +const Pages = require('./pages.js'); const Tags = require('./tags.js'); const Authors = require('./authors.js'); const Themes = require('./themes.js'); @@ -224,9 +225,11 @@ class App { this.db = new DBUtils(new Database(dbPath)); let tags = new Tags(this, {site}); let posts = new Posts(this, {site}); + let pages = new Pages(this, {site}); let authors = new Authors(this, {site}); let themes = new Themes(this, {site}); let themeDir = path.join(siteDir, 'input', 'themes', themes.currentTheme(true)); + let themeOverridesDir = path.join(siteDir, 'input', 'themes', themes.currentTheme(true) + '-override'); let themeConfig = Themes.loadThemeConfig(themeConfigPath, themeDir); let menuStructure = fs.readFileSync(menuConfigPath, 'utf8'); let parsedMenuStructure = {}; @@ -240,14 +243,18 @@ class App { return { status: true, posts: posts.load(), + pages: pages.load(), tags: tags.load(), authors: authors.load(), postsTags: posts.loadTagsXRef(), postsAuthors: posts.loadAuthorsXRef(), + pagesAuthors: pages.loadAuthorsXRef(), postTemplates: themes.loadPostTemplates(), + pageTemplates: themes.loadPageTemplates(), tagTemplates: themes.loadTagTemplates(), authorTemplates: themes.loadAuthorTemplates(), themes: themes.load(), + themeHasOverrides: Utils.dirExists(themeOverridesDir), themeSettings: themeConfig, menuStructure: parsedMenuStructure, siteDir: siteDir diff --git a/app/back-end/author.js b/app/back-end/author.js index ed042d444..b6aa7363a 100644 --- a/app/back-end/author.js +++ b/app/back-end/author.js @@ -2,6 +2,7 @@ const fs = require('fs-extra'); const path = require('path'); const Model = require('./model.js'); const Authors = require('./authors.js'); +const Pages = require('./pages.js'); const Posts = require('./posts.js'); const slug = require('./helpers/slug'); const ImageHelper = require('./helpers/image.helper.js'); @@ -23,6 +24,7 @@ class Author extends Model { this.id = parseInt(authorData.id, 10); this.authorsData = new Authors(appInstance, authorData); this.postsData = new Posts(appInstance, authorData); + this.pagesData = new Pages(appInstance, authorData); this.storeMode = storeMode; if (authorData.additionalData) { @@ -133,6 +135,7 @@ class Author extends Model { message: 'author-added', authorID: this.id, postsAuthors: this.postsData.loadAuthorsXRef(), + pagesAuthors: this.pagesData.loadAuthorsXRef(), authors: this.authorsData.load() }; } @@ -292,7 +295,7 @@ class Author extends Model { featuredImage = path.parse(this.additionalData.featuredImage).base; } - // If post is cancelled - get the previous featured image + // If author is cancelled - get the previous featured image if (cancelEvent && this.id !== 0) { let featuredImageSqlQuery = `SELECT additional_data FROM authors WHERE id = @id`; @@ -313,6 +316,12 @@ class Author extends Model { authorDir = 'temp'; } + let imagesInAuthorViewSettings = []; + + if (this.additionalData && this.additionalData.viewConfig) { + imagesInAuthorViewSettings = Object.values(this.additionalData.viewConfig).filter(item => item.type === "image").map(item => item.value); + } + // Iterate through images for (let i in images) { let imagePath = images[i]; @@ -323,7 +332,14 @@ class Author extends Model { continue; } - if ((cancelEvent && authorDir === 'temp') || featuredImage !== imagePath) { + // Remove files which does not exist as featured image and authorViewSettings + if( + (cancelEvent && authorDir === 'temp') || + ( + imagesInAuthorViewSettings.indexOf(imagePath) === -1 && + featuredImage !== imagePath + ) + ) { try { fs.unlinkSync(fullPath); } catch(e) { @@ -337,8 +353,11 @@ class Author extends Model { // Clean unused avatar images let themesHelper = new Themes(this.application, { site: this.site }); let themeConfigPath = path.join(this.application.sitesDir, this.site, 'input', 'config', 'theme.config.json'); - let themeConfigString = fs.readFileSync(themeConfigPath, 'utf8'); - themesHelper.checkAndCleanImages(themeConfigString); + + if (fs.fileExists(themeConfigPath)) { + let themeConfigString = fs.readFileSync(themeConfigPath, 'utf8'); + themesHelper.checkAndCleanImages(themeConfigString); + } } /* diff --git a/app/back-end/builddata.json b/app/back-end/builddata.json index ff0e326d9..a493ca240 100644 --- a/app/back-end/builddata.json +++ b/app/back-end/builddata.json @@ -1,4 +1,4 @@ { - "version": "0.45.2", - "build": 16609 + "version": "0.46.0", + "build": 16889 } diff --git a/app/back-end/events/_modules.js b/app/back-end/events/_modules.js index 43af97f19..2f98ffe09 100644 --- a/app/back-end/events/_modules.js +++ b/app/back-end/events/_modules.js @@ -5,6 +5,7 @@ module.exports = { AppEvents: require('./app.js'), CreditsEvents: require('./credits'), ImageUploaderEvents: require('./image-uploader.js'), + PageEvents: require('./page.js'), PostEvents: require('./post.js'), SiteEvents: require('./site.js'), TagEvents: require('./tag.js'), diff --git a/app/back-end/events/deploy.js b/app/back-end/events/deploy.js index 79d1c58d0..3c1230b4e 100644 --- a/app/back-end/events/deploy.js +++ b/app/back-end/events/deploy.js @@ -65,6 +65,21 @@ class DeployEvents { event.sender.send('app-deploy-aborted', true); }); + ipcMain.on('app-deploy-continue', function() { + if (self.deploymentProcess) { + try { + self.deploymentProcess.send({ + type: 'continue-sync' + }); + + self.deploymentProcess = false; + } catch(e) { + console.log(e); + self.deploymentProcess = false; + } + } + }); + ipcMain.on('app-deploy-test', async (event, data) => { try { await this.testConnection(data.deploymentConfig, data.siteName, data.uuid); @@ -162,7 +177,7 @@ class DeployEvents { }); this.deploymentProcess.on('message', function(data) { - if(data.type === 'web-contents') { + if (data.type === 'web-contents') { if(data.value) { self.app.mainWindow.webContents.send(data.message, data.value); } else { diff --git a/app/back-end/events/page.js b/app/back-end/events/page.js new file mode 100644 index 000000000..a6c4e7f46 --- /dev/null +++ b/app/back-end/events/page.js @@ -0,0 +1,102 @@ +const fs = require('fs'); +const path = require('path'); +const ipcMain = require('electron').ipcMain; +const Page = require('../page.js'); + +/* + * Events for the IPC communication regarding pages + */ + +class PageEvents { + constructor(appInstance) { + this.app = appInstance; + + // Load + ipcMain.on('app-page-load', function (event, pageData) { + let page = new Page(appInstance, pageData); + let result = page.load(); + event.sender.send('app-page-loaded', result); + }); + + // Save + ipcMain.on('app-page-save', function (event, pageData) { + let page = new Page(appInstance, pageData); + let result = page.save(); + event.sender.send('app-page-saved', result); + }); + + // Delete + ipcMain.on('app-page-delete', function (event, pageData) { + let result = false; + + for(let i = 0; i < pageData.ids.length; i++) { + let page = new Page(appInstance, { + site: pageData.site, + id: pageData.ids[i] + }); + + result = page.delete(); + } + + event.sender.send('app-page-deleted', result); + }); + + // Delete + ipcMain.on('app-page-duplicate', function (event, pageData) { + let result = false; + + for(let i = 0; i < pageData.ids.length; i++) { + let page = new Page(appInstance, { + site: pageData.site, + id: pageData.ids[i] + }); + + result = page.duplicate(); + } + + event.sender.send('app-page-duplicated', result); + }); + + // Status change + ipcMain.on('app-page-status-change', function (event, pageData) { + let result = false; + + for(let i = 0; i < pageData.ids.length; i++) { + let page = new Page(appInstance, { + site: pageData.site, + id: pageData.ids[i] + }); + + result = page.changeStatus(pageData.status, pageData.inverse); + } + + event.sender.send('app-page-status-changed', result); + }); + + // Cancelled edition + ipcMain.on('app-page-cancel', function(event, pageData) { + let page = new Page(appInstance, pageData); + let result = page.checkAndCleanImages(true); + event.sender.send('app-page-cancelled', result); + }); + + // Load pages hierarchy + ipcMain.on('app-pages-hierarchy-load', (event, siteName) => { + let pagesFile = path.join(this.app.sitesDir, siteName, 'input', 'config', 'pages.config.json'); + + if (fs.existsSync(pagesFile)) { + event.sender.send('app-pages-hierarchy-loaded', JSON.parse(fs.readFileSync(pagesFile, { encoding: 'utf8' }))); + } else { + event.sender.send('app-pages-hierarchy-loaded', null); + } + }); + + // Save pages hierarchy + ipcMain.on('app-pages-hierarchy-save', (event, pagesData) => { + let pagesFile = path.join(this.app.sitesDir, pagesData.siteName, 'input', 'config', 'pages.config.json'); + fs.writeFileSync(pagesFile, JSON.stringify(pagesData.hierarchy, null, 4), { encoding: 'utf8' }); + }); + } +} + +module.exports = PageEvents; diff --git a/app/back-end/events/preview.js b/app/back-end/events/preview.js index eaca5f725..d718026b9 100644 --- a/app/back-end/events/preview.js +++ b/app/back-end/events/preview.js @@ -88,6 +88,10 @@ class PreviewEvents { errorDesc = data.result[0].message + "\n\n" + data.result[0].desc; } + if (typeof errorDesc === 'object') { + errorDesc = errorDesc.translation; + } + event.sender.send('app-preview-render-error', { message: [{ message: errorTitle, @@ -170,7 +174,7 @@ class PreviewEvents { url = path.join(basePath, 'index.html'); - if (mode === 'tag' || mode === 'post' || mode === 'author') { + if (mode === 'tag' || mode === 'post' || mode === 'page' || mode === 'author') { url = path.join(basePath, 'preview.html'); } diff --git a/app/back-end/events/site.js b/app/back-end/events/site.js index e89af7fd6..514e727ce 100644 --- a/app/back-end/events/site.js +++ b/app/back-end/events/site.js @@ -54,6 +54,7 @@ class SiteEvents { // Prepare settings config.settings.name = slug(config.settings.name); + config.settings.advanced.urls.postsPrefix = slug(config.settings.advanced.urls.postsPrefix); config.settings.advanced.urls.tagsPrefix = slug(config.settings.advanced.urls.tagsPrefix); config.settings.advanced.urls.authorsPrefix = slug(config.settings.advanced.urls.authorsPrefix); config.settings.advanced.urls.pageName = slug(config.settings.advanced.urls.pageName); @@ -392,6 +393,7 @@ class SiteEvents { newConfig: { config: themeConfig.config, customConfig: themeConfig.customConfig, + pageConfig: themeConfig.pageConfig, postConfig: themeConfig.postConfig, tagConfig: themeConfig.tagConfig, authorConfig: themeConfig.authorConfig, diff --git a/app/back-end/image.js b/app/back-end/image.js index 6a8032e40..7dc3490ca 100644 --- a/app/back-end/image.js +++ b/app/back-end/image.js @@ -23,6 +23,8 @@ class Image extends Model { if (imageData.id === 'website') { this.id = 'website'; + } else if (imageData.id === 'defaults') { + this.id = 'defaults'; } // App instance @@ -95,9 +97,18 @@ class Image extends Model { let galleryDirPath = ''; let responsiveDirPath = ''; - if (this.imageType === 'pluginImages') { + if (this.id === 'defaults' && this.imageType === 'contentImages') { + dirPath = path.join(this.siteDir, 'input', 'media', 'posts', 'defaults'); + responsiveDirPath = path.join(this.siteDir, 'input', 'media', 'posts', 'defaults', 'responsive'); + } else if (this.id === 'defaults' && this.imageType === 'tagImages') { + dirPath = path.join(this.siteDir, 'input', 'media', 'tags', 'defaults'); + responsiveDirPath = path.join(this.siteDir, 'input', 'media', 'tags', 'defaults', 'responsive'); + } else if (this.id === 'defaults' && this.imageType === 'authorImages') { + dirPath = path.join(this.siteDir, 'input', 'media', 'authors', 'defaults'); + responsiveDirPath = path.join(this.siteDir, 'input', 'media', 'authors', 'defaults', 'responsive'); + } else if (this.imageType === 'pluginImages') { dirPath = path.join(this.siteDir, 'input', 'media', 'plugins', this.pluginDir); - } else if (this.id === 'website') { + } else if (this.id === 'website' || this.imageType === 'optionImages') { dirPath = path.join(this.siteDir, 'input', 'media', 'website'); responsiveDirPath = path.join(this.siteDir, 'input', 'media', 'website', 'responsive'); } else if (this.imageType === 'tagImages' && this.id) { diff --git a/app/back-end/modules/deploy/deployment.js b/app/back-end/modules/deploy/deployment.js index 4d858a1b2..f4e83b3b7 100644 --- a/app/back-end/modules/deploy/deployment.js +++ b/app/back-end/modules/deploy/deployment.js @@ -23,8 +23,8 @@ const ManualDeployment = require('./manual.js'); * (S)FTP(S), * S3 server, * Git - * Github Pages (deprecated), - * Gitlab Pages (deprecated), + * Github Pages, + * Gitlab Pages, * Netlify, * Google Cloud, * Manually @@ -38,7 +38,7 @@ class Deployment { * @param sitesDir * @param siteConfig */ - constructor(appDir, sitesDir, siteConfig, useAltFtp) { + constructor (appDir, sitesDir, siteConfig, useAltFtp) { this.appDir = appDir; this.siteConfig = siteConfig; this.siteName = this.siteConfig.name; @@ -60,7 +60,7 @@ class Deployment { * @param deploymentConfig * @param siteName */ - async testConnection(app, deploymentConfig, siteName, uuid) { + async testConnection (app, deploymentConfig, siteName, uuid) { let connection = false; switch(deploymentConfig.protocol) { @@ -89,7 +89,7 @@ class Deployment { /** * Inits connection */ - async initSession() { + async initSession () { switch(this.siteConfig.deployment.protocol) { case 'sftp': case 'sftp+key': this.client = new SFTP(this); break; @@ -115,7 +115,7 @@ class Deployment { /** * Set input directory on local machine */ - setInput() { + setInput () { // Set the output dir as a source of the files to upload let basePath = path.join(this.sitesDir, this.siteName); this.inputDir = path.join(basePath, 'output'); @@ -125,8 +125,8 @@ class Deployment { /** * Sets output directory on the server */ - setOutput(useEmpty = false) { - if(useEmpty) { + setOutput (useEmpty = false) { + if (useEmpty) { this.outputDir = ''; } else { this.outputDir = this.siteConfig.deployment.path; @@ -218,70 +218,48 @@ class Deployment { fileContent = fileContent.toString(); } - let syncRevisionPath = path.join(this.configDir, 'sync-revision.json'); let content = JSON.parse(fileContent); - let revisionID = false; - - if (fs.existsSync(syncRevisionPath)) { - let syncRevisionContent = fs.readFileSync(syncRevisionPath); - syncRevisionContent = JSON.parse(syncRevisionContent); - revisionID = syncRevisionContent.revision; - } if (content.revision) { - let filesToCheck = fs.readFileSync(path.join(this.configDir, 'files-remote.json')); + let syncRevisionPath = path.join(this.configDir, 'sync-revision.json'); + let revisionID = false; + + if (fs.existsSync(syncRevisionPath)) { + let syncRevisionContent = fs.readFileSync(syncRevisionPath); + syncRevisionContent = JSON.parse(syncRevisionContent); + revisionID = syncRevisionContent.revision; + } if (revisionID) { let isExpectedCopy = revisionID === content.revision; this.compareFilesList(isExpectedCopy); } else { + let filesToCheck = fs.readFileSync(path.join(this.configDir, 'files-remote.json')); let checkSum = crypto.createHash('md5').update(filesToCheck).digest('hex'); let isExpectedCopy = checkSum === content.revision; this.compareFilesList(isExpectedCopy); } - - return; + } else { + fs.writeFileSync(path.join(this.configDir, 'files-remote.json'), fileContent); + this.compareFilesList(true); } - - // when files on server uses old format - download it and use as remote files list - fs.writeFileSync(path.join(this.configDir, 'files-remote.json'), fileContent); - this.compareFilesList(true); } catch (e) { this.compareFilesList(false); } } - /** - * Save files.publii.json as files-remote.json and store checksum in the file - */ - replaceSyncInfoFiles () { - let inputListPath = path.join(this.inputDir, 'files.publii.json'); - let remoteListPath = path.join(this.configDir, 'files-remote.json'); - let syncRevisionPath = path.join(this.configDir, 'sync-revision.json'); - let contentToSave = fs.readFileSync(inputListPath); - let newContent = `{ "revision": "${this.syncRevision}" }`; - fs.writeFileSync(remoteListPath, contentToSave); - fs.writeFileSync(inputListPath, newContent); - fs.writeFileSync(syncRevisionPath, newContent); - } - /** * Compares remote and local files lists * * @param remoteFileListExists */ - compareFilesList(remoteFileListExists = false) { - let localFiles = fs.readFileSync(path.join(this.inputDir, 'files.publii.json'), 'utf8'); + compareFilesList (remoteFileListExists = false) { let remoteFiles = false; - if(localFiles) { - localFiles = JSON.parse(localFiles); - } - - if(remoteFileListExists) { + if (remoteFileListExists) { remoteFiles = fs.readFileSync(path.join(this.configDir, 'files-remote.json'), 'utf8'); - if(remoteFiles) { + if (remoteFiles) { try { remoteFiles = JSON.parse(remoteFiles); @@ -297,24 +275,43 @@ class Deployment { } } - if(!remoteFiles) { - remoteFiles = []; + // wait for user interaction if there are no remote files list and syncDate exists under site configuration + if (!remoteFiles && this.siteConfig.syncDate) { + process.send({ + type: 'web-contents', + message: 'no-remote-files', + value: false + }); + return; + } + + this.continueSync(remoteFiles); + } + + /** + * Wait for user answer or just continue sync if remote files list exists + */ + continueSync (remoteFiles) { + let localFiles = fs.readFileSync(path.join(this.inputDir, 'files.publii.json'), 'utf8'); + + if (localFiles) { + localFiles = JSON.parse(localFiles); } // Detect files to remove let filesToRemove = []; - for(let remoteFile of remoteFiles) { + for (let remoteFile of remoteFiles) { let fileFounded = false; - for(let localFile of localFiles) { - if(localFile.path === remoteFile.path) { + for (let localFile of localFiles) { + if (localFile.path === remoteFile.path) { fileFounded = true; break; } } - if(!fileFounded) { + if (!fileFounded) { if ( (this.siteConfig.deployment.protocol === 'google-cloud' || this.siteConfig.deployment.protocol === 'gitlab-pages') && remoteFile.type === 'directory' @@ -332,10 +329,10 @@ class Deployment { // Detect files to upload let filesToUpload = []; - for(let localFile of localFiles) { + for (let localFile of localFiles) { let fileShouldBeUploaded = true; - for(let remoteFile of remoteFiles) { + for (let remoteFile of remoteFiles) { if( localFile.path === remoteFile.path && localFile.md5 === remoteFile.md5 @@ -347,7 +344,10 @@ class Deployment { if (fileShouldBeUploaded) { if ( - (this.siteConfig.deployment.protocol === 'google-cloud' || this.siteConfig.deployment.protocol === 'gitlab-pages') && + ( + this.siteConfig.deployment.protocol === 'google-cloud' || + this.siteConfig.deployment.protocol === 'gitlab-pages' + ) && localFile.type === 'directory' ) { continue; @@ -363,7 +363,7 @@ class Deployment { this.filesToRemove = filesToRemove; this.filesToUpload = filesToUpload; - if(this.siteConfig.deployment.protocol === 's3') { + if (this.siteConfig.deployment.protocol === 's3') { this.operationsCounter = this.filesToRemove.filter(file => file.type === 'file').length + this.filesToUpload.filter(file => file.type === 'file').length + 1; } else { @@ -390,9 +390,7 @@ class Deployment { /** * Move files or directories to the beginning */ - sortFiles() { - let self = this; - + sortFiles () { this.filesToRemove = this.filesToRemove.sort(function(fileA, fileB) { if(fileA.type === 'directory') { return -1; @@ -428,19 +426,19 @@ class Deployment { // Reorder directories to put higher order directories at the beginning this.filesToUpload = this.filesToUpload.sort(function(fileA, fileB) { - if(fileA.type === 'directory' && fileB.type === 'directory') { - if(fileA.path.length <= fileB.path.length) { + if (fileA.type === 'directory' && fileB.type === 'directory') { + if (fileA.path.length <= fileB.path.length) { return 1; } else { return -1; } } - if(fileA.type === 'directory') { + if (fileA.type === 'directory') { return 1; } - if(fileB.type === 'directory') { + if (fileB.type === 'directory') { return -1; } @@ -455,23 +453,23 @@ class Deployment { /** * Removes file */ - removeFile() { - if(this.siteConfig.deployment.protocol === 's3') { + removeFile () { + if (this.siteConfig.deployment.protocol === 's3') { this.client.removeFile(); return; } - if(this.siteConfig.deployment.protocol === 'gitlab-pages') { + if (this.siteConfig.deployment.protocol === 'gitlab-pages') { this.client.startSync(); return; } let self = this; - if(this.filesToRemove.length > 0) { + if (this.filesToRemove.length > 0) { let fileToRemove = this.filesToRemove.pop(); - if(fileToRemove.type === 'file') { + if (fileToRemove.type === 'file') { this.client.removeFile(normalizePath(path.join(this.outputDir, fileToRemove.path))); } else { this.client.removeDirectory(normalizePath(path.join(this.outputDir, fileToRemove.path))); @@ -495,13 +493,13 @@ class Deployment { /** * Uploads file */ - uploadFile() { + uploadFile () { let self = this; - if(this.filesToUpload.length > 0) { + if (this.filesToUpload.length > 0) { let fileToUpload = this.filesToUpload.pop(); - if(fileToUpload.type === 'file') { + if (fileToUpload.type === 'file') { this.client.uploadFile( normalizePath(path.join(this.inputDir, fileToUpload.path)), normalizePath(path.join(this.outputDir, fileToUpload.path)) @@ -518,7 +516,10 @@ class Deployment { message: 'app-uploading-progress', value: { progress: 98, - operations: [self.currentOperationNumber ,self.operationsCounter] + operations: [ + self.currentOperationNumber, + self.operationsCounter + ] } }); @@ -535,7 +536,7 @@ class Deployment { * * @returns {Array} */ - readDirRecursiveSync(dir, filelist) { + readDirRecursiveSync (dir, filelist) { let self = this; let files = fs.readdirSync(dir); filelist = filelist || []; @@ -564,7 +565,7 @@ class Deployment { * @param files * @param suffix */ - saveConnectionFilesLog(files, suffix = '') { + saveConnectionFilesLog (files, suffix = '') { if (suffix !== '') { suffix = '-' + suffix; } diff --git a/app/back-end/modules/deploy/ftp-alt.js b/app/back-end/modules/deploy/ftp-alt.js index 9a87006c1..7c72df9fb 100644 --- a/app/back-end/modules/deploy/ftp-alt.js +++ b/app/back-end/modules/deploy/ftp-alt.js @@ -143,8 +143,6 @@ class FTPAlt { } }); - this.deployment.replaceSyncInfoFiles(); - try { await this.connection.uploadFrom( normalizePath(path.join(this.deployment.inputDir, 'files.publii.json')), diff --git a/app/back-end/modules/deploy/ftp.js b/app/back-end/modules/deploy/ftp.js index 65823a61d..40a6cd502 100644 --- a/app/back-end/modules/deploy/ftp.js +++ b/app/back-end/modules/deploy/ftp.js @@ -201,8 +201,6 @@ class FTP { } }); - this.deployment.replaceSyncInfoFiles(); - this.connection.put( normalizePath(path.join(this.deployment.inputDir, 'files.publii.json')), normalizePath(path.join(this.deployment.outputDir, 'files.publii.json')), diff --git a/app/back-end/modules/deploy/gitlab-pages.js b/app/back-end/modules/deploy/gitlab-pages.js index cd0d4332a..c0ba0e719 100644 --- a/app/back-end/modules/deploy/gitlab-pages.js +++ b/app/back-end/modules/deploy/gitlab-pages.js @@ -429,8 +429,6 @@ class GitlabPages { updateFilesListFile () { this.setUploadProgress(98); - this.deployment.replaceSyncInfoFiles(); - let localFilesListPath = path.join(this.deployment.inputDir, 'files.publii.json'); let localFilesContent = fs.readFileSync(localFilesListPath); let actionType = 'create'; diff --git a/app/back-end/modules/deploy/google-cloud.js b/app/back-end/modules/deploy/google-cloud.js index fa910a858..fe7a79dc7 100644 --- a/app/back-end/modules/deploy/google-cloud.js +++ b/app/back-end/modules/deploy/google-cloud.js @@ -125,8 +125,6 @@ class GoogleCloud { } }); - self.deployment.replaceSyncInfoFiles(); - this.connection.upload(fileToUpload, { destination: fileDestination }, function(err) { diff --git a/app/back-end/modules/deploy/manual.js b/app/back-end/modules/deploy/manual.js index 2ac190460..855d0f1f2 100644 --- a/app/back-end/modules/deploy/manual.js +++ b/app/back-end/modules/deploy/manual.js @@ -16,8 +16,7 @@ class ManualDeployment { async initConnection() { this.deployment.setInput(); this.deployment.prepareLocalFilesList(); - this.deployment.replaceSyncInfoFiles(); - + switch(this.deployment.siteConfig.deployment.manual.output) { case 'catalog': this.returnCatalog(); break; case 'zip-archive': this.returnZipArchive(); break; diff --git a/app/back-end/modules/deploy/netlify.js b/app/back-end/modules/deploy/netlify.js index 6cc949ad8..f62c4f521 100644 --- a/app/back-end/modules/deploy/netlify.js +++ b/app/back-end/modules/deploy/netlify.js @@ -40,8 +40,8 @@ class Netlify { localDir = this.deployment.inputDir; client = new NetlifyAPI({ - accessToken: token, - siteID: siteID, + accessToken: (token).toString().trim(), + siteID: (siteID).toString().trim(), inputDir: localDir }, { onStart: this.onStart.bind(this), @@ -161,8 +161,8 @@ class Netlify { } client = new NetlifyAPI({ - accessToken: token, - siteID: siteID, + accessToken: (token).toString().trim(), + siteID: (siteID).toString().trim(), inputDir: '' }); diff --git a/app/back-end/modules/deploy/s3.js b/app/back-end/modules/deploy/s3.js index aff9b8444..92d784e58 100644 --- a/app/back-end/modules/deploy/s3.js +++ b/app/back-end/modules/deploy/s3.js @@ -155,7 +155,6 @@ class S3 { async uploadNewFileList() { this.sendProgress(99); - this.deployment.replaceSyncInfoFiles(); let fileName = 'files.publii.json'; if (typeof this.prefix === 'string' && this.prefix !== '') { diff --git a/app/back-end/modules/deploy/sftp.js b/app/back-end/modules/deploy/sftp.js index b01f07cd3..a5906ab4c 100644 --- a/app/back-end/modules/deploy/sftp.js +++ b/app/back-end/modules/deploy/sftp.js @@ -157,8 +157,6 @@ class SFTP { } }); - self.deployment.replaceSyncInfoFiles(); - this.connection.put( normalizePath(path.join(self.deployment.inputDir, 'files.publii.json')), normalizePath(path.join(self.deployment.outputDir, 'files.publii.json')), diff --git a/app/back-end/modules/import/import.js b/app/back-end/modules/import/import.js index 76feb115c..c6951bdb6 100644 --- a/app/back-end/modules/import/import.js +++ b/app/back-end/modules/import/import.js @@ -97,6 +97,7 @@ class Import { this.parser.importTagsData(); this.parser.getImageURLs(); this.parser.importPostsData(); + this.parser.importPagesData(); this.parser.importImages(); } } diff --git a/app/back-end/modules/import/wxr-parser.js b/app/back-end/modules/import/wxr-parser.js index f15a3b493..76b5c8a0c 100644 --- a/app/back-end/modules/import/wxr-parser.js +++ b/app/back-end/modules/import/wxr-parser.js @@ -9,6 +9,7 @@ const slug = require('./../../helpers/slug'); const Author = require('./../../author.js'); const Tag = require('./../../tag.js'); const Post = require('./../../post.js'); +const Page = require('./../../page.js'); const Utils = require('./../../helpers/utils.js'); /** @@ -31,13 +32,15 @@ class WxrParser { this.temp = { authors: [], posts: [], + pages: [], tags: [], images: [], mapping: { authors: [], tags: [], images: [], - posts: [] + posts: [], + pages: [] }, imagesQueue: {} }; @@ -145,11 +148,11 @@ class WxrParser { items = items.filter(item => item['wp:post_type'] === filterType); } - if(items && items.length) { + if(items && (items.length || items.length === 0)) { return items.length; } - if(items) { + if(typeof items === 'object') { return 1; } @@ -375,7 +378,7 @@ class WxrParser { importPostsData() { let posts = this.parsedContent.rss.channel['item']; let newPost; - posts = posts ? posts.filter(item => this.postTypes.indexOf(item['wp:post_type']) !== -1) : false; + posts = posts ? posts.filter(item => this.postTypes.indexOf(item['wp:post_type']) !== -1 && item['wp:post_type'] !== 'page') : false; if(!posts) { return; @@ -490,6 +493,117 @@ class WxrParser { } } + /** + * Import pages data + */ + importPagesData() { + if (this.postTypes.indexOf('page') === -1) { + console.log('(!) Pages import is disabled'); + return; + } + + let pages = this.parsedContent.rss.channel['item']; + let newPage; + pages = pages ? pages.filter(item => item['wp:post_type'] === 'page') : false; + + if(!pages) { + console.log('(!) No pages to import'); + return; + } + + let untitledPagesCount = 1; + + console.log('(X) pages:', pages); + + for(let i = 0; i < pages.length; i++) { + if (!pages[i].title) { + console.log('(!) Empty page title detected - fallback to "Untitled #X" title'); + pages[i].title = 'Untitled #' + untitledPagesCount++; + } + + // For each page item insert post object + let pageImages = this.getPostImages(pages[i]['content:encoded']); + let pageSlug = slug(pages[i].title); + let pageAuthor = this.temp.authors[slug(pages[i]['dc:creator'])]; + let pageText = this.preparePostText(pages[i]['content:encoded'], pageImages); + let pageStatus = pages[i]['wp:status'] === 'draft' ? 'draft,is-page' : 'published,is-page' + let pageTitle = (pages[i].title).toString(); + + if(!this.importAuthors) { + pageAuthor = '1'; + } + + newPage = new Page(this.appInstance, { + id: 0, + site: this.siteName, + title: pageTitle, + slug: pageSlug, + author: pageAuthor, + status: pageStatus, + text: pageText, + creationDate: moment(pages[i]['wp:post_date']).format('x'), + modificationDate: moment().format('x'), + template: '', + additionalData: '', + pageViewSettings: '' + }, false); + + let newPageResult = newPage.save(); + let newPageID = newPageResult.postID; + + this.temp.pages[pageSlug] = newPageID; + this.temp.mapping.pages[pages[i]['wp:post_id']] = newPageID; + + // Create queue for download images + if(pageImages.length) { + this.temp.imagesQueue[newPageID] = pageImages; + } + + let featuredImage = this.getFeaturedPostImage(pages[i]); + let fileName = false; + + if(featuredImage) { + fileName = path.parse(featuredImage).base; + + if(!this.temp.imagesQueue[newPageID]) { + this.temp.imagesQueue[newPageID] = []; + } + + this.temp.imagesQueue[newPageID].push(featuredImage); + } + + if(fileName) { + let featuredPageImageSqlQuery = newPage.db.prepare(`INSERT INTO posts_images VALUES(NULL, @newPageID, @fileName, '', '', @config)`); + featuredPageImageSqlQuery.run({ + newPageID: newPageID, + fileName: fileName, + config: '{"alt":"","caption":"","credits":""}' + }); + + let featuredPageID = newPage.db.prepare('SELECT last_insert_rowid() AS id').get().id; + let featuredPageIdUpdate = newPage.db.prepare(`UPDATE posts SET featured_image_id = @featuredPostID WHERE id = @newPageID`); + + featuredPageIdUpdate.run({ + featuredPageID, + newPageID + }); + } + + process.send({ + type: 'progress', + message: { + translation: 'core.wpImport.pagesProgressInfo', + translationVars: { + progress: (i + 1), + total: pages.length + } + } + }); + + console.log('-> Imported page (' + (i+1) + ' / ' + pages.length + '): ' + pageTitle); + } + } + /** * Create array with all available images for download */ diff --git a/app/back-end/modules/render-html/contexts/404.js b/app/back-end/modules/render-html/contexts/404.js index c831d8308..4096c761e 100644 --- a/app/back-end/modules/render-html/contexts/404.js +++ b/app/back-end/modules/render-html/contexts/404.js @@ -24,6 +24,7 @@ class RendererContext404 extends RendererContext { this.authors = this.renderer.commonData.authors; this.featuredPosts = this.renderer.commonData.featuredPosts.homepage; this.hiddenPosts = this.renderer.commonData.hiddenPosts; + this.pages = this.renderer.commonData.pages; this.metaTitle = this.siteConfig.advanced.errorMetaTitle.replace(/%sitename/g, siteName); this.metaDescription = this.siteConfig.advanced.errorMetaDescription.replace(/%sitename/g, siteName); @@ -34,18 +35,26 @@ class RendererContext404 extends RendererContext { if (this.metaDescription === '') { this.metaDescription = this.siteConfig.advanced.metaDescription.replace(/%sitename/g, siteName); } + + // mark tags as main tags + let mainTagsIds = this.mainTags.map(tag => tag.id); + this.tags = this.tags.map(tag => { + tag.isMainTag = mainTagsIds.includes(tag.id); + return tag; + }); } /** * Preparing the loaded data */ prepareData() { - let self = this; this.title = this.siteConfig.name; this.featuredPosts = this.featuredPosts || []; this.featuredPosts = this.featuredPosts.map(post => this.renderer.cachedItems.posts[post.id]); this.hiddenPosts = this.hiddenPosts || []; this.hiddenPosts = this.hiddenPosts.map(post => this.renderer.cachedItems.posts[post.id]); + this.pages = this.pages || []; + this.pages = this.pages.map(page => this.renderer.cachedItems.pages[page.id]); } /** @@ -66,6 +75,7 @@ class RendererContext404 extends RendererContext { featuredPosts: this.featuredPosts, hiddenPosts: this.hiddenPosts, tags: this.tags, + pages: this.pages, mainTags: this.mainTags, authors: this.authors, metaTitleRaw: this.metaTitle, diff --git a/app/back-end/modules/render-html/contexts/author.js b/app/back-end/modules/render-html/contexts/author.js index 3eeb6e5ce..dfa035ffc 100644 --- a/app/back-end/modules/render-html/contexts/author.js +++ b/app/back-end/modules/render-html/contexts/author.js @@ -43,6 +43,7 @@ class RendererContextAuthor extends RendererContext { status LIKE '%published%' AND status NOT LIKE '%hidden%' AND status NOT LIKE '%trashed%' AND + status NOT LIKE '%is-page%' AND ${includeFeaturedPosts} authors LIKE @authorID ORDER BY @@ -65,6 +66,14 @@ class RendererContextAuthor extends RendererContext { this.authors = this.renderer.commonData.authors; this.featuredPosts = this.renderer.commonData.featuredPosts.author; this.hiddenPosts = this.renderer.commonData.hiddenPosts; + this.pages = this.renderer.commonData.pages; + + // mark tags as main tags + let mainTagsIds = this.mainTags.map(tag => tag.id); + this.tags = this.tags.map(tag => { + tag.isMainTag = mainTagsIds.includes(tag.id); + return tag; + }); } prepareData() { @@ -75,6 +84,8 @@ class RendererContextAuthor extends RendererContext { this.featuredPosts = this.featuredPosts.map(post => this.renderer.cachedItems.posts[post.id]); this.hiddenPosts = this.hiddenPosts || []; this.hiddenPosts = this.hiddenPosts.map(post => this.renderer.cachedItems.posts[post.id]); + this.pages = this.pages || []; + this.pages = this.pages.map(page => this.renderer.cachedItems.pages[page.id]); let shouldSkipFeaturedPosts = RendererHelpers.getRendererOptionValue('authorsIncludeFeaturedInPosts', this.themeConfig) === false; let featuredPostsNumber = RendererHelpers.getRendererOptionValue('authorsFeaturedPostsNumber', this.themeConfig); @@ -149,6 +160,7 @@ class RendererContextAuthor extends RendererContext { title: this.metaTitle !== '' ? this.metaTitle : this.title, author: this.author, posts: this.posts, + pages: this.pages, featuredPosts: this.featuredPosts, hiddenPosts: this.hiddenPosts, tags: this.tags, diff --git a/app/back-end/modules/render-html/contexts/feed.js b/app/back-end/modules/render-html/contexts/feed.js index c826d3a5e..27d2d17f1 100644 --- a/app/back-end/modules/render-html/contexts/feed.js +++ b/app/back-end/modules/render-html/contexts/feed.js @@ -33,6 +33,7 @@ class RendererContextFeed extends RendererContext { status LIKE '%published%' AND ${featuredPostsCondition} status NOT LIKE '%hidden%' AND + status NOT LIKE '%is-page%' AND status NOT LIKE '%trashed%' ORDER BY created_at DESC diff --git a/app/back-end/modules/render-html/contexts/home.js b/app/back-end/modules/render-html/contexts/home.js index 0bd5abf40..98a004a95 100644 --- a/app/back-end/modules/render-html/contexts/home.js +++ b/app/back-end/modules/render-html/contexts/home.js @@ -38,6 +38,7 @@ class RendererContextHome extends RendererContext { status LIKE '%published%' AND status NOT LIKE '%hidden%' AND status NOT LIKE '%trashed%' AND + status NOT LIKE '%is-page%' AND status NOT LIKE '%excluded_homepage%' ORDER BY ${this.postsOrdering} @@ -65,8 +66,16 @@ class RendererContextHome extends RendererContext { this.menus = this.renderer.commonData.menus; this.unassignedMenus = this.renderer.commonData.unassignedMenus; this.authors = this.renderer.commonData.authors; + this.pages = this.renderer.commonData.pages; this.featuredPosts = this.renderer.commonData.featuredPosts.homepage; this.hiddenPosts = this.renderer.commonData.hiddenPosts; + + // mark tags as main tags + let mainTagsIds = this.mainTags.map(tag => tag.id); + this.tags = this.tags.map(tag => { + tag.isMainTag = mainTagsIds.includes(tag.id); + return tag; + }); } prepareData() { @@ -77,6 +86,8 @@ class RendererContextHome extends RendererContext { this.featuredPosts = this.featuredPosts.map(post => this.renderer.cachedItems.posts[post.id]); this.hiddenPosts = this.hiddenPosts || []; this.hiddenPosts = this.hiddenPosts.map(post => this.renderer.cachedItems.posts[post.id]); + this.pages = this.pages || []; + this.pages = this.pages.map(page => this.renderer.cachedItems.pages[page.id]); let shouldSkipFeaturedPosts = RendererHelpers.getRendererOptionValue('includeFeaturedInPosts', this.themeConfig) == false; let featuredPostsNumber = RendererHelpers.getRendererOptionValue('featuredPostsNumber', this.themeConfig); @@ -104,6 +115,7 @@ class RendererContextHome extends RendererContext { hiddenPosts: this.hiddenPosts, tags: this.tags, mainTags: this.mainTags, + pages: this.pages, authors: this.authors, metaTitleRaw: this.metaTitle, metaDescriptionRaw: this.metaDescription, @@ -136,12 +148,10 @@ class RendererContextHome extends RendererContext { FROM posts WHERE - status LIKE '%published%' - AND - status NOT LIKE '%hidden%' - AND - status NOT LIKE '%trashed%' - AND + status LIKE '%published%' AND + status NOT LIKE '%hidden%' AND + status NOT LIKE '%trashed%' AND + status NOT LIKE '%is-page%' AND status NOT LIKE '%excluded_homepage%' ${includeFeaturedPosts} GROUP BY diff --git a/app/back-end/modules/render-html/contexts/page-preview.js b/app/back-end/modules/render-html/contexts/page-preview.js new file mode 100644 index 000000000..a56b49e54 --- /dev/null +++ b/app/back-end/modules/render-html/contexts/page-preview.js @@ -0,0 +1,373 @@ +// Necessary packages +const fs = require('fs'); +const path = require('path'); +const sizeOf = require('image-size'); +const sqlString = require('sqlstring'); +const normalizePath = require('normalize-path'); +const slug = require('./../../../helpers/slug'); +const RendererContext = require('../renderer-context.js'); +const RendererHelpers = require('./../helpers/helpers.js'); +const URLHelper = require('../helpers/url.js'); +const ContentHelper = require('../helpers/content.js'); + +/** + * Class used create context + * for the single page theme previews + */ + +class RendererContextPagePreview extends RendererContext { + loadData() { + // Prepare data + this.pageID = parseInt(this.renderer.postData.postID, 10); + this.title = this.renderer.postData.title; + this.pageImage = this.renderer.postData.featuredImage; + this.editor = this.renderer.postData.additionalData.editor; + // Retrieve all tags + this.allTags = this.getAllTags(); + // Retrieve menu data + this.menus = this.getMenus(); + } + + prepareData() { + let pageURL = this.siteConfig.domain + '/preview.html'; + let preparedText = this.prepareContent(this.renderer.postData.text, this.renderer.postData.id); + let hasCustomExcerpt = false; + let readmoreMatches = preparedText.match(/\/gmi); + + if (readmoreMatches && readmoreMatches.length) { + hasCustomExcerpt = true; + } + + this.page = { + id: this.renderer.postData.id, + title: this.renderer.postData.title, + slug: this.renderer.postData.slug, + author: this.renderer.cachedItems.authors[this.renderer.postData.author], + url: pageURL, + text: preparedText.replace(/\/gmi, ''), + excerpt: ContentHelper.prepareExcerpt(this.themeConfig.config.excerptLength, preparedText), + createdAt: this.renderer.postData.creationDate, + modifiedAt: this.renderer.postData.modificationDate, + status: this.renderer.postData.status, + featuredImage: {}, + hasGallery: preparedText.indexOf('class="gallery') !== -1, + isFeatured: this.renderer.postData.status.indexOf('featured') > -1, + isHidden: this.renderer.postData.status.indexOf('hidden') > -1, + isExcludedOnHomepage: this.renderer.postData.status.indexOf('excluded_homepage') > -1, + hasGallery: preparedText.indexOf('class="gallery') !== -1, + template: this.renderer.postData.template, + hasCustomExcerpt: hasCustomExcerpt + }; + + if (this.pageImage) { + this.page.featuredImage = this.getPageFeaturedImages(this.page.id, true); + } + + this.metaTitle = 'It is an example value for the preview mode'; + this.metaDescription = 'It is an example value for the preview mode'; + this.metaRobots = 'It is an example value for the preview mode'; + } + + setContext() { + this.loadData(); + this.prepareData(); + + let metaRobotsValue = this.metaRobots; + + if(this.siteConfig.advanced.noIndexThisPage) { + metaRobotsValue = 'noindex,nofollow'; + } + + this.context = { + title: this.metaTitle !== '' ? this.metaTitle : this.title, + page: this.page, + tags: this.allTags, + metaTitleRaw: this.metaTitle, + metaDescriptionRaw: this.metaDescription, + metaRobotsRaw: metaRobotsValue, + siteOwner: this.renderer.cachedItems.authors[1], + menus: this.menus.assigned + }; + } + + getContext(pageID) { + this.pageID = pageID; + this.setContext(); + + return this.context; + } + + getPageFeaturedImages(pageID, mainPage = false) { + let pageImage = false; + + // Retrieve post image + if(mainPage === true) { + pageImage = { + id: 0, + url: this.renderer.postData.featuredImageFilename, + additional_data: JSON.stringify(this.renderer.postData.featuredImageData) + }; + } else { + pageImage = this.db.prepare(` + SELECT + pi.id AS id, + pi.url AS url, + pi.additional_data AS additional_data + FROM + posts as p + LEFT JOIN + posts_images as pi + ON + p.featured_image_id = pi.id + WHERE + p.id = @pageID + ORDER BY + pi.id DESC + LIMIT 1 + `).get({ + pageID: pageID + }); + } + + if (pageImage && pageImage.url) { + let url = ''; + let alt = ''; + let caption = ''; + let credits = ''; + let imageDimensions = false; + + if (pageImage.additional_data) { + let data = JSON.parse(pageImage.additional_data); + let pageDirectory = pageID; + + if(pageDirectory === 0) { + pageDirectory = 'temp'; + } + + let imagePath = URLHelper.createImageURL(this.inputDir, pageDirectory, pageImage.url); + let domain = this.siteConfig.domain; + + url = URLHelper.createImageURL(domain, pageDirectory, pageImage.url); + alt = data.alt; + caption = data.caption; + credits = data.credits; + + try { + imageDimensions = sizeOf(imagePath); + } catch(e) { + console.log('page-preview.js: wrong image path - missing dimensions'); + imageDimensions = false; + } + } else { + return false; + } + + let featuredImageSrcSet = false; + let featuredImageSizes = false; + + if(!this.isGifOrSvg(url)) { + let useWebp = false; + + if (this.renderer.siteConfig?.advanced?.forceWebp) { + useWebp = true; + } + + featuredImageSrcSet = ContentHelper.getFeaturedImageSrcset(url, this.themeConfig, useWebp); + featuredImageSizes = ContentHelper.getFeaturedImageSizes(this.themeConfig); + } else { + featuredImageSrcSet = ''; + featuredImageSizes = ''; + } + + let featuredImageData = { + id: pageImage.id, + url: url, + alt: alt, + caption: caption, + credits: credits, + height: imageDimensions.height, + width: imageDimensions.width, + srcset: featuredImageSrcSet, + sizes: featuredImageSizes + }; + + // Create alternative names for dimensions + let dimensions = false; + + if ( + this.themeConfig.files && + this.themeConfig.files.responsiveImages + ) { + if ( + this.themeConfig.files.responsiveImages.featuredImages && + this.themeConfig.files.responsiveImages.featuredImages.dimensions + ) { + dimensions = this.themeConfig.files.responsiveImages.featuredImages.dimensions; + } else if ( + this.themeConfig.files.responsiveImages.contentImages && + this.themeConfig.files.responsiveImages.contentImages.dimensions + ) { + dimensions = this.themeConfig.files.responsiveImages.featuredImages.dimensions; + } + + if (dimensions) { + let dimensionNames = Object.keys(dimensions); + + for (let dimensionName of dimensionNames) { + let base = path.parse(url).base; + let filename = path.parse(url).name; + let extension = path.parse(url).ext; + let newFilename = filename + '-' + dimensionName + extension; + let capitalizedDimensionName = dimensionName.charAt(0).toUpperCase() + dimensionName.slice(1); + + if(!this.isGifOrSvg(url)) { + featuredImageData['url' + capitalizedDimensionName] = url.replace(base, newFilename); + } else { + featuredImageData['url' + capitalizedDimensionName] = url; + } + } + } + } + + return featuredImageData; + } + + return false; + } + + prepareContent(originalText, pageID) { + let self = this; + let domain = normalizePath(self.siteConfig.domain); + domain = URLHelper.fixProtocols(domain); + + // Get media URL + let pageDirectory = pageID; + + if(pageDirectory === 0) { + pageDirectory = 'temp'; + } + + let domainMediaPath = domain + '/media/posts/' + pageDirectory + '/'; + + // Replace domain name constat with real URL to media directory + let preparedText = originalText.split('#DOMAIN_NAME#').join(domainMediaPath); + preparedText = ContentHelper.parseText(preparedText, this.editor); + + // Remove TOC plugin ID attributes when TOC does not exist + if (preparedText.indexOf('class="post__toc') === -1) { + preparedText = preparedText.replace(/\sid="mcetoc_[a-z0-9]*?"/gmi, ''); + } + + // Reduce download="download" to download + preparedText = preparedText.replace(/download="download"/gmi, 'download'); + + // Remove content for AMP or non-AMP depending from ampMode value + preparedText = preparedText.replace(/.*<\/publii-amp>/gmi, ''); + preparedText = preparedText.replace(//gmi, ''); + preparedText = preparedText.replace(/<\/publii-non-amp>/gmi, ''); + + // Remove read more text + preparedText = preparedText.replace(/\/gmi, ''); + + // Remove the last empty paragraph + preparedText = preparedText.replace(/

 <\/p>\s?$/gmi, ''); + + let useWebp = false; + + if (this.renderer.siteConfig?.advanced?.forceWebp) { + useWebp = true; + } + + // Find all images and add srcset and sizes attributes + if (this.siteConfig.responsiveImages) { + preparedText = preparedText.replace(/].*?\sloading="[^>].*?>)/gmi, '].*?\sloading="[^>].*?>)/gmi, '].*?\sloading="[^>].*?>)/gmi, '].*?\sloading="[^>].*?>)/gmi, ' + preparedText = preparedText.replace(/(\s*?)?]*?(class=".*?").*?>(\s*?<\/p>)?/gmi, function(matches, p1, classes) { + return '

' + matches.replace('

', '').replace(//, '').replace(classes, '') + '
'; + }); + + // Fix some specific syntax cases for double figure elements + preparedText = preparedText.replace(/
[\s]*?
([\s\S]*?)<\/figure>[\s]*?<\/figure>/gmi, '
$1
'); + preparedText = preparedText.replace(/
[\s]*?
([\s\S]*?)<\/figure>[\s]*?
([\s\S]*?)<\/figcaption>[\s]*?<\/figure>/gmi, '
$1
$2
'); + } + + // Remove contenteditable attributes + preparedText = preparedText.replace(/contentEditable=".*?"/gi, ''); + + if (this.editor === 'tinymce') { + // Wrap galleries with classes into div with gallery-wrapper CSS class + preparedText = preparedText.replace(/
?/gmi, function(matches, classes) { + return ''; + }); + } + + // Remove paragraphs around '); + + // Wrap iframes into
+ preparedText = preparedText.replace(/(?[\s\S]*?)([\s\S]*?<\/iframe>)/gmi, function(matches) { + if (matches.indexOf('data-responsive="false"') > -1) { + return matches; + } + + return '
' + matches + '
'; + }); + + // Remove CDATA sections inside scripts added by TinyMCE + preparedText = preparedText.replace(/\\/\/ \<\!\[CDATA\[/g, ''); + + return preparedText; + } + + /** + * Detects if image is a GIF or SVG + */ + isGifOrSvg(url) { + if(url.slice(-4) === '.gif' || url.slice(-4) === '.svg') { + return true; + } + + return false; + } +} + +module.exports = RendererContextPagePreview; diff --git a/app/back-end/modules/render-html/contexts/page.js b/app/back-end/modules/render-html/contexts/page.js new file mode 100644 index 000000000..a60698689 --- /dev/null +++ b/app/back-end/modules/render-html/contexts/page.js @@ -0,0 +1,150 @@ +// Necessary packages +const RendererContext = require('../renderer-context.js'); +const stripTags = require('striptags'); + +/** + * Class used create context + * for the single page theme views + */ +class RendererContextPage extends RendererContext { + loadData() { + // Retrieve meta data + let metaDataQuery = this.db.prepare(`SELECT value FROM posts_additional_data WHERE post_id = @pageID AND key = '_core'`); + this.metaData = metaDataQuery.get({ pageID: this.pageID}); + this.allTags = this.renderer.commonData.tags.filter(tag => tag.additionalData.isHidden !== true); + this.mainTags = this.renderer.commonData.mainTags.filter(maintag => maintag.additionalData.isHidden !== true); + this.menus = this.renderer.commonData.menus; + this.unassignedMenus = this.renderer.commonData.unassignedMenus; + this.authors = this.renderer.commonData.authors; + this.featuredPosts = this.renderer.commonData.featuredPosts.homepage; + this.hiddenPosts = this.renderer.commonData.hiddenPosts; + this.pages = this.renderer.commonData.pages; + + // mark tags as main tags + let mainTagsIds = this.mainTags.map(tag => tag.id); + this.allTags = this.allTags.map(tag => { + tag.isMainTag = mainTagsIds.includes(tag.id); + return tag; + }); + } + + prepareData() { + this.page = this.renderer.cachedItems.pages[this.pageID]; + this.featuredPosts = this.featuredPosts || []; + this.featuredPosts = this.featuredPosts.map(post => this.renderer.cachedItems.posts[post.id]); + this.hiddenPosts = this.hiddenPosts || []; + this.hiddenPosts = this.hiddenPosts.map(post => this.renderer.cachedItems.posts[post.id]); + this.pages = this.pages || []; + this.pages = this.pages.map(page => this.renderer.cachedItems.pages[page.id]); + this.metaTitle = this.siteConfig.advanced.pageMetaTitle; + this.metaDescription = this.siteConfig.advanced.pageMetaDescription; + this.canonicalUrl = this.page.url; + this.hasCustomCanonicalUrl = false; + this.metaRobots = ''; + + if (this.siteConfig.advanced.pageMetaDescription === '') { + this.metaDescription = stripTags(this.page.excerpt).replace(/\n/gmi, ''); + } + + if(this.metaData && this.metaData.value) { + let results = JSON.parse(this.metaData.value); + + if (results.metaTitle) { + this.metaTitle = results.metaTitle; + } + + if (results.metaDesc) { + this.metaDescription = results.metaDesc; + } + + if (results.metaRobots) { + this.metaRobots = results.metaRobots; + } + + if (results.canonicalUrl) { + this.canonicalUrl = results.canonicalUrl; + this.hasCustomCanonicalUrl = true; + this.metaRobots = ''; + } + } + + let siteName = this.siteConfig.name; + + if(this.siteConfig.displayName) { + siteName = this.siteConfig.displayName; + } + + if (this.metaTitle === '') { + this.metaTitle = this.siteConfig.advanced.metaTitle.replace(/%sitename/g, siteName); + } + + if (this.metaDescription === '') { + this.metaDescription = this.siteConfig.advanced.metaDescription.replace(/%sitename/g, siteName); + } + } + + setContext() { + this.loadData(); + this.prepareData(); + + let metaRobotsValue = this.metaRobots; + + if(this.siteConfig.advanced.noIndexThisPage) { + metaRobotsValue = 'noindex,nofollow'; + } + + let siteName = this.siteConfig.name; + + if (this.siteConfig.displayName) { + siteName = this.siteConfig.displayName; + } + + // Detect if the page title is empty + if (this.metaTitle === '') { + this.metaTitle = this.siteConfig.advanced.pageMetaTitle.replace(/%pagetitle/g, this.page.title) + .replace(/%sitename/g, siteName) + .replace(/%authorname/g, this.page.author.name); + } else { + this.metaTitle = this.metaTitle.replace(/%pagetitle/g, this.page.title) + .replace(/%sitename/g, siteName) + .replace(/%authorname/g, this.page.author.name); + } + + // If still meta title is empty - use page title + if (this.metaTitle === '') { + this.metaTitle = this.page.title; + } + + this.metaDescription = this.metaDescription.replace(/%pagetitle/g, this.page.title) + .replace(/%sitename/g, siteName) + .replace(/%authorname/g, this.page.author.name); + + this.context = { + title: this.metaTitle, + page: this.page, + featuredPosts: this.featuredPosts, + hiddenPosts: this.hiddenPosts, + tags: this.allTags, + mainTags: this.mainTags, + authors: this.authors, + pages: this.pages, + metaTitleRaw: this.metaTitle, + metaDescriptionRaw: this.metaDescription, + metaRobotsRaw: metaRobotsValue, + hasCustomCanonicalUrl: this.hasCustomCanonicalUrl, + canonicalUrl: this.canonicalUrl, + siteOwner: this.renderer.cachedItems.authors[1], + menus: this.menus, + unassignedMenus: this.unassignedMenus + }; + } + + getContext(pageID) { + this.pageID = pageID; + this.setContext(); + + return this.context; + } +} + +module.exports = RendererContextPage; diff --git a/app/back-end/modules/render-html/contexts/post-preview.js b/app/back-end/modules/render-html/contexts/post-preview.js index 26dd565ff..d9d386f6e 100644 --- a/app/back-end/modules/render-html/contexts/post-preview.js +++ b/app/back-end/modules/render-html/contexts/post-preview.js @@ -167,6 +167,7 @@ class RendererContextPostPreview extends RendererContext { p.id != @postID AND p.status LIKE '%published%' AND p.status NOT LIKE '%trashed%' AND + p.status NOT LIKE '%is-page%' AND p.status NOT LIKE '%hidden%' ${tagsCondition} GROUP BY @@ -189,6 +190,7 @@ class RendererContextPostPreview extends RendererContext { id != @postID AND status LIKE '%published%' AND status NOT LIKE '%trashed%' AND + status NOT LIKE '%is-page%' AND status NOT LIKE '%hidden%' ORDER BY ${temporaryPostsOrdering} @@ -267,6 +269,7 @@ class RendererContextPostPreview extends RendererContext { p.id != @postID AND p.status LIKE '%published%' AND p.status NOT LIKE '%trashed%' AND + p.status NOT LIKE '%is-page%' AND p.status NOT LIKE '%hidden%' ${conditions} GROUP BY diff --git a/app/back-end/modules/render-html/contexts/post.js b/app/back-end/modules/render-html/contexts/post.js index 56de1c930..5d12d2975 100644 --- a/app/back-end/modules/render-html/contexts/post.js +++ b/app/back-end/modules/render-html/contexts/post.js @@ -20,6 +20,14 @@ class RendererContextPost extends RendererContext { this.authors = this.renderer.commonData.authors; this.featuredPosts = this.renderer.commonData.featuredPosts.homepage; this.hiddenPosts = this.renderer.commonData.hiddenPosts; + this.pages = this.renderer.commonData.pages; + + // mark tags as main tags + let mainTagsIds = this.mainTags.map(tag => tag.id); + this.allTags = this.allTags.map(tag => { + tag.isMainTag = mainTagsIds.includes(tag.id); + return tag; + }); } prepareData() { @@ -29,6 +37,8 @@ class RendererContextPost extends RendererContext { this.featuredPosts = this.featuredPosts.map(post => this.renderer.cachedItems.posts[post.id]); this.hiddenPosts = this.hiddenPosts || []; this.hiddenPosts = this.hiddenPosts.map(post => this.renderer.cachedItems.posts[post.id]); + this.pages = this.pages || []; + this.pages = this.pages.map(page => this.renderer.cachedItems.pages[page.id]); this.metaTitle = this.siteConfig.advanced.postMetaTitle; this.metaDescription = this.siteConfig.advanced.postMetaDescription; this.canonicalUrl = this.post.url; @@ -171,6 +181,7 @@ class RendererContextPost extends RendererContext { p.id != @postID AND p.status LIKE '%published%' AND p.status NOT LIKE '%trashed%'AND + p.status NOT LIKE '%is-page%' AND p.status NOT LIKE '%hidden%' ${tagsCondition} GROUP BY @@ -200,6 +211,7 @@ class RendererContextPost extends RendererContext { id != @postID AND status LIKE '%published%' AND status NOT LIKE '%trashed%' AND + status NOT LIKE '%is-page%' AND status NOT LIKE '%hidden%' ORDER BY ${temporaryPostsOrdering} @@ -307,6 +319,7 @@ class RendererContextPost extends RendererContext { p.id != @postID AND p.status LIKE '%published%' AND p.status NOT LIKE '%trashed%' AND + p.status NOT LIKE '%is-page%' AND p.status NOT LIKE '%hidden%' ${conditionsLowerPriority} `; @@ -331,6 +344,7 @@ class RendererContextPost extends RendererContext { p.id != @postID AND p.status LIKE '%published%' AND p.status NOT LIKE '%trashed%' AND + p.status NOT LIKE '%is-page%' AND p.status NOT LIKE '%hidden%' ${conditions} ${secondQuery} @@ -410,6 +424,7 @@ class RendererContextPost extends RendererContext { tags: this.allTags, mainTags: this.mainTags, authors: this.authors, + pages: this.pages, metaTitleRaw: this.metaTitle, metaDescriptionRaw: this.metaDescription, metaRobotsRaw: metaRobotsValue, diff --git a/app/back-end/modules/render-html/contexts/search.js b/app/back-end/modules/render-html/contexts/search.js index 310e5d4d9..08e3f4d22 100644 --- a/app/back-end/modules/render-html/contexts/search.js +++ b/app/back-end/modules/render-html/contexts/search.js @@ -35,6 +35,14 @@ class RendererContextSearch extends RendererContext { this.authors = this.renderer.commonData.authors; this.featuredPosts = this.renderer.commonData.featuredPosts.homepage; this.hiddenPosts = this.renderer.commonData.hiddenPosts; + this.pages = this.renderer.commonData.pages; + + // mark tags as main tags + let mainTagsIds = this.mainTags.map(tag => tag.id); + this.tags = this.tags.map(tag => { + tag.isMainTag = mainTagsIds.includes(tag.id); + return tag; + }); } /** @@ -46,6 +54,8 @@ class RendererContextSearch extends RendererContext { this.featuredPosts = this.featuredPosts.map(post => this.renderer.cachedItems.posts[post.id]); this.hiddenPosts = this.hiddenPosts || []; this.hiddenPosts = this.hiddenPosts.map(post => this.renderer.cachedItems.posts[post.id]); + this.pages = this.pages || []; + this.pages = this.pages.map(page => this.renderer.cachedItems.pages[page.id]); } /** @@ -68,6 +78,7 @@ class RendererContextSearch extends RendererContext { tags: this.tags, mainTags: this.mainTags, authors: this.authors, + pages: this.pages, metaTitleRaw: this.metaTitle, metaDescriptionRaw: this.metaDescription, metaRobotsRaw: metaRobotsValue, diff --git a/app/back-end/modules/render-html/contexts/tag.js b/app/back-end/modules/render-html/contexts/tag.js index 01b9467eb..4724cf2dd 100644 --- a/app/back-end/modules/render-html/contexts/tag.js +++ b/app/back-end/modules/render-html/contexts/tag.js @@ -43,6 +43,7 @@ class RendererContextTag extends RendererContext { p.status LIKE '%published%' AND p.status NOT LIKE '%hidden%' AND p.status NOT LIKE '%trashed%' AND + p.status NOT LIKE '%is-page%' AND ${includeFeaturedPosts} pt.tag_id = @tagID ORDER BY @@ -65,6 +66,14 @@ class RendererContextTag extends RendererContext { this.authors = this.renderer.commonData.authors; this.featuredPosts = this.renderer.commonData.featuredPosts.tag; this.hiddenPosts = this.renderer.commonData.hiddenPosts; + this.pages = this.renderer.commonData.pages; + + // mark tags as main tags + let mainTagsIds = this.mainTags.map(tag => tag.id); + this.tags = this.tags.map(tag => { + tag.isMainTag = mainTagsIds.includes(tag.id); + return tag; + }); } prepareData() { @@ -84,6 +93,8 @@ class RendererContextTag extends RendererContext { this.featuredPosts = this.featuredPosts.map(post => this.renderer.cachedItems.posts[post.id]); this.hiddenPosts = this.hiddenPosts || []; this.hiddenPosts = this.hiddenPosts.map(post => this.renderer.cachedItems.posts[post.id]); + this.pages = this.pages || []; + this.pages = this.pages.map(page => this.renderer.cachedItems.pages[page.id]); let shouldSkipFeaturedPosts = RendererHelpers.getRendererOptionValue('tagsIncludeFeaturedInPosts', this.themeConfig) === false; let featuredPostsNumber = RendererHelpers.getRendererOptionValue('tagsFeaturedPostsNumber', this.themeConfig); @@ -151,6 +162,7 @@ class RendererContextTag extends RendererContext { title: this.metaTitle !== '' ? this.metaTitle : this.title, tag: this.tag, posts: this.posts, + pages: this.pages, featuredPosts: this.featuredPosts, hiddenPosts: this.hiddenPosts, tags: this.tags, diff --git a/app/back-end/modules/render-html/contexts/tags.js b/app/back-end/modules/render-html/contexts/tags.js index 170ebcf4d..170a5e2b6 100644 --- a/app/back-end/modules/render-html/contexts/tags.js +++ b/app/back-end/modules/render-html/contexts/tags.js @@ -14,6 +14,14 @@ class RendererContextTags extends RendererContext { this.authors = this.renderer.commonData.authors; this.featuredPosts = this.renderer.commonData.featuredPosts.tag; this.hiddenPosts = this.renderer.commonData.hiddenPosts; + this.pages = this.renderer.commonData.pages; + + // mark tags as main tags + let mainTagsIds = this.mainTags.map(tag => tag.id); + this.tags = this.tags.map(tag => { + tag.isMainTag = mainTagsIds.includes(tag.id); + return tag; + }); } prepareData() { @@ -28,6 +36,8 @@ class RendererContextTags extends RendererContext { this.featuredPosts = this.featuredPosts.map(post => this.renderer.cachedItems.posts[post.id]); this.hiddenPosts = this.hiddenPosts || []; this.hiddenPosts = this.hiddenPosts.map(post => this.renderer.cachedItems.posts[post.id]); + this.pages = this.pages || []; + this.pages = this.pages.map(page => this.renderer.cachedItems.pages[page.id]); // Prepare meta data this.metaTitle = this.siteConfig.advanced.tagsMetaTitle.replace(/%sitename/g, siteName); @@ -56,6 +66,7 @@ class RendererContextTags extends RendererContext { title: this.metaTitle !== '' ? this.metaTitle : this.title, featuredPosts: this.featuredPosts, hiddenPosts: this.hiddenPosts, + pages: this.pages, tags: this.tags, tagsNumber: this.tags.length, mainTags: this.mainTags, diff --git a/app/back-end/modules/render-html/handlebars/helpers/_modules.js b/app/back-end/modules/render-html/handlebars/helpers/_modules.js index f7a6fdba5..9f358f4b4 100644 --- a/app/back-end/modules/render-html/handlebars/helpers/_modules.js +++ b/app/back-end/modules/render-html/handlebars/helpers/_modules.js @@ -38,6 +38,9 @@ module.exports = { jsonify: require('./jsonify.js'), reverse: require('./reverse.js'), orderby: require('./orderby.js'), + getPageHelper: require('./get-page.js'), + getPagesHelper: require('./get-pages.js'), + getPagesByCustomFieldHelper: require('./get-pages-by-custom-field.js'), getPostHelper: require('./get-post.js'), getPostsHelper: require('./get-posts.js'), getPostByTagsHelper: require('./get-post-by-tags.js'), diff --git a/app/back-end/modules/render-html/handlebars/helpers/get-page.js b/app/back-end/modules/render-html/handlebars/helpers/get-page.js new file mode 100644 index 000000000..778eaf195 --- /dev/null +++ b/app/back-end/modules/render-html/handlebars/helpers/get-page.js @@ -0,0 +1,27 @@ +/** + * Helper for loading page data + * + * {{#getPage PAGE_ID}} + *

{{ title }}

+ *
{{{ excerpt }}}
+ * {{/getPage}} + * + * IMPORTANT: It requires availability of the @website.contentStructure global variable + */ +function getPageHelper(rendererInstance, Handlebars) { + Handlebars.registerHelper('getPage', function (pageID, options) { + if (!rendererInstance.contentStructure.pages) { + return 'Error: @website.contentStructure global variable is not available.'; + } + + let pageData = rendererInstance.contentStructure.pages.filter(page => page.id === pageID); + + if(!pageData.length) { + return ''; + } + + return options.fn(pageData[0]); + }); +} + +module.exports = getPageHelper; diff --git a/app/back-end/modules/render-html/handlebars/helpers/get-pages-by-custom-field.js b/app/back-end/modules/render-html/handlebars/helpers/get-pages-by-custom-field.js new file mode 100644 index 000000000..6123185b4 --- /dev/null +++ b/app/back-end/modules/render-html/handlebars/helpers/get-pages-by-custom-field.js @@ -0,0 +1,194 @@ +/** + * Helper for loading pages data which contains a specific custom fields + * + * QueryString options: + * * count - how many pages should be included in the result + * * customField - which custom field should be used + * * customFieldValue - which value of custom field is expected + * * customFieldCompare - default: 'equals', other available values: 'not-equals', 'greater', 'greater-equals', 'lesser', 'lesser-equals' (for numeric values), 'starts-with', 'ends-with', 'contains', 'not-contains' (for string values) + * * excluded - which pages should be excluded + * * offset - how many pages to skip + * * orderby - order field or customField + * * ordering - order direction - asc, desc, random + * * orderbyCompareLanguage - if orderby=customField, you can specify in which language ordering will be done. + * + * {{#getPagesByCustomFields "count=5&customField=test&customFieldValue=10&customFieldCompare=not-equals&excluded=1,2&offset=10&orderby=modified_at&ordering=desc"}} + *

{{ title }}

+ *
{{{ excerpt }}}
+ * {{/getPagesByCustomFields}} + * + * IMPORTANT: It requires availability of the @website.contentStructure global variable + */ +function getPagesByCustomFieldsHelper (rendererInstance, Handlebars) { + Handlebars.registerHelper('getPagesByCustomFields', function (queryString, options) { + if (!rendererInstance.contentStructure.pages) { + return 'Error: @website.contentStructure global variable is not available.'; + } + + let count; + let offset = 0; + let orderby = false; + let ordering = 'desc'; + let customField = false; + let customFieldValue = false; + let customFieldCompare = 'equals'; + let compareLanguage = false; + + queryString = queryString.split('&').map(pair => pair.split('=')); + let queryStringData = {}; + + for (let i = 0; i < queryString.length; i++) { + let key = queryString[i][0]; + let value = queryString[i][1]; + queryStringData[key] = value; + } + + if (queryStringData['count']) { + count = parseInt(queryStringData['count'], 10); + + if (count === -1) { + count = 999; + } + } + + if (queryStringData['excluded']) { + excludedPages = queryStringData['excluded']; + } + + if (queryStringData['customField']) { + customField = queryStringData['customField']; + } + + if (queryStringData['customFieldValue']) { + customFieldValue = queryStringData['customFieldValue']; + } + + if (queryStringData['customFieldCompare']) { + customFieldCompare = queryStringData['customFieldCompare']; + } + + if (queryStringData['offset']) { + offset = parseInt(queryStringData['offset']); + } + + if (queryStringData['orderby']) { + orderby = queryStringData['orderby']; + } + + if (queryStringData['ordering']) { + ordering = queryStringData['ordering']; + } + + if (queryStringData['orderbyCompareLanguage']) { + compareLanguage = queryStringData['orderbyCompareLanguage']; + } + + let pagesData; + let content = ''; + let filteredPages = JSON.parse(JSON.stringify(rendererInstance.contentStructure.pages)); + + if (typeof excludedPages === 'number' || (typeof excludedPages === 'string' && excludedPages !== '')) { + if (typeof excludedPages === 'number') { + let excludedPage = excludedPages; + filteredPages = filteredPages.filter(page => page.id !== excludedPage) + } else { + excludedPages = excludedPages.split(',').map(n => parseInt(n, 10)); + filteredPages = filteredPages.filter(page => excludedPages.indexOf(page.id) === -1); + } + } + + filteredPages = filteredPages.filter(page => { + if (!page.pageViewConfig[customField]) { + return false; + } + + switch (customFieldCompare) { + case 'equals': + return page.pageViewConfig[customField] == customFieldValue; + case 'not-equals': + return page.pageViewConfig[customField] != customFieldValue; + case 'greater': + return parseInt(page.pageViewConfig[customField], 10) > parseInt(customFieldValue, 10); + case 'greater-equals': + return parseInt(page.pageViewConfig[customField], 10) >= parseInt(customFieldValue, 10); + case 'lesser': + return parseInt(page.pageViewConfig[customField], 10) < parseInt(customFieldValue, 10); + case 'lesser-equals': + return parseInt(page.pageViewConfig[customField], 10) <= parseInt(customFieldValue, 10); + case 'starts-with': + return page.pageViewConfig[customField].indexOf(customFieldValue) === 0; + case 'ends-with': + return page.pageViewConfig[customField].lastIndexOf(customFieldValue) === page.pageViewConfig[customField].length - customFieldValue.length; + case 'contains': + return page.pageViewConfig[customField].indexOf(customFieldValue) !== -1; + case 'not-contains': + return page.pageViewConfig[customField].indexOf(customFieldValue) === -1; + } + }); + + pagesData = filteredPages; + + if (orderby && ordering && ordering !== 'random') { + pagesData.sort((itemA, itemB) => { + if (orderby === 'customField') { + if (isNaN(itemA.pageViewConfig[customField]) && isNaN(itemB.pageViewConfig[customField])) { + if (ordering === 'asc') { + if (compareLanguage) { + return itemA.pageViewConfig[customField].localeCompare(itemB.pageViewConfig[customField], compareLanguage); + } else { + return itemA.pageViewConfig[customField].localeCompare(itemB.pageViewConfig[customField]); + } + } else { + if (compareLanguage) { + return -(itemA.pageViewConfig[customField].localeCompare(itemB.pageViewConfig[customField], compareLanguage)); + } else { + return -(itemA.pageViewConfig[customField].localeCompare(itemB.pageViewConfig[customField])); + } + } + } else { + if (ordering === 'asc') { + return parseInt(itemA.pageViewConfig[customField], 10) - parseInt(itemB.pageViewConfig[customField], 10); + } else { + return parseInt(itemB.pageViewConfig[customField], 10) - parseInt(itemA.pageViewConfig[customField], 10); + } + } + } + + if (orderby !== 'customField') { + if(typeof itemA[orderby] === 'string') { + if (ordering === 'asc') { + return itemA[orderby].localeCompare(itemB[orderby]); + } else { + return -(itemA[orderby].localeCompare(itemB[orderby])); + } + } else { + if (ordering === 'asc') { + return itemA[orderby] - itemB[orderby]; + } else { + return itemB[orderby] - itemA[orderby]; + } + } + } + }); + } else if (ordering === 'random') { + pagesData.sort(() => 0.5 - Math.random()); + } + + for (let i = offset; i < count + offset; i++) { + if (pagesData.length >= i + 1) { + options.data.index = i; + content += options.fn(pagesData[i]); + } else { + break; + } + } + + if (content === '') { + return ''; + } + + return content; + }); +} + +module.exports = getPagesByCustomFieldsHelper; diff --git a/app/back-end/modules/render-html/handlebars/helpers/get-pages.js b/app/back-end/modules/render-html/handlebars/helpers/get-pages.js new file mode 100644 index 000000000..03be0614f --- /dev/null +++ b/app/back-end/modules/render-html/handlebars/helpers/get-pages.js @@ -0,0 +1,45 @@ +/** + * Helper for loading posts data + * + * {{#getPages "PAGE_ID_1,PAGE_ID_2,PAGE_ID_N" "prefix" "suffix"}} + *
+ *

{{ title }}

+ *
{{{ excerpt }}}
+ *
+ * {{/getPages}} + * + * Pages are ordered by the ID order in the string. + * + * The second parameter creates HTML prefix, the third parameter creates HTML suffix for the generated output. + * + * IMPORTANT: It requires availability of the @website.contentStructure global variable + */ +function getPagesHelper(rendererInstance, Handlebars) { + Handlebars.registerHelper('getPages', function (pageIDs, prefix, suffix, options) { + if (!rendererInstance.contentStructure.pages) { + return 'Error: @website.contentStructure global variable is not available.'; + } + + let content = ''; + pageIDs = pageIDs.split(',').map(n => parseInt(n, 10)); + + for (let i = 0; i < pageIDs.length; i++) { + let pageData = rendererInstance.contentStructure.pages.filter(page => page.id === pageIDs[i]); + + if (pageData.length) { + options.data.index = i; + content += options.fn(pageData[0]); + } + } + + if (content === '') { + return ''; + } + + content = [prefix, content, suffix].join(''); + + return content; + }); +} + +module.exports = getPagesHelper; diff --git a/app/back-end/modules/render-html/handlebars/helpers/image-dimensions.js b/app/back-end/modules/render-html/handlebars/helpers/image-dimensions.js index 772945fd0..d1dbd38b5 100644 --- a/app/back-end/modules/render-html/handlebars/helpers/image-dimensions.js +++ b/app/back-end/modules/render-html/handlebars/helpers/image-dimensions.js @@ -12,6 +12,10 @@ const normalizePath = require('normalize-path'); */ function imageDimensionsHelper(rendererInstance, Handlebars) { Handlebars.registerHelper('imageDimensions', function (url) { + if (!url) { + return ''; + } + url = normalizePath(url); let basicUrl = normalizePath(rendererInstance.siteConfig.domain); let basicDir = normalizePath(rendererInstance.inputDir); diff --git a/app/back-end/modules/render-html/handlebars/helpers/is.js b/app/back-end/modules/render-html/handlebars/helpers/is.js index c6e22ce2c..377457ec9 100644 --- a/app/back-end/modules/render-html/handlebars/helpers/is.js +++ b/app/back-end/modules/render-html/handlebars/helpers/is.js @@ -5,7 +5,7 @@ * * {{#is 'tag,post'}} * - * Available phrases: index, tag, post, author, 404, search, pagination, index-pagination, tag-pagination, author-pagination + * Available phrases: index, tag, post, page, author, 404, search, pagination, index-pagination, tag-pagination, author-pagination * * @returns {callback} */ diff --git a/app/back-end/modules/render-html/handlebars/helpers/json-ld.js b/app/back-end/modules/render-html/handlebars/helpers/json-ld.js index 9d0593550..12b25003b 100644 --- a/app/back-end/modules/render-html/handlebars/helpers/json-ld.js +++ b/app/back-end/modules/render-html/handlebars/helpers/json-ld.js @@ -20,7 +20,7 @@ function jsonLDHelper(rendererInstance, Handlebars) { jsonLDObject['@context'] = 'http://schema.org'; // Detect if the page is not a post page - if(context.data.context.indexOf('post') === -1) { + if (context.data.context.indexOf('post') === -1 && context.data.context.indexOf('page') === -1) { jsonLDObject['@type'] = 'Organization'; jsonLDObject['name'] = context.data.website.name; @@ -65,24 +65,27 @@ function jsonLDHelper(rendererInstance, Handlebars) { jsonLDObject['sameAs'].push(context.data.config.custom.socialYoutube); } } - } else { // JSON-LD for posts + } else { // JSON-LD for posts/pages + let itemData = context.data.root.post; + + if (!itemData) { + itemData = context.data.root.page; + } + jsonLDObject['@type'] = 'Article'; jsonLDObject['mainEntityOfPage'] = { "@type": "WebPage", - "@id": context.data.root.post.url, + "@id": itemData.url, }; - jsonLDObject['headline'] = context.data.root.post.title; - jsonLDObject['datePublished'] = moment(context.data.root.post.createdAt).format('YYYY-MM-DDTHH:mm'); - jsonLDObject['dateModified'] = moment(context.data.root.post.modifiedAt).format('YYYY-MM-DDTHH:mm'); - - if( - context.data.root.post.featuredImage.url || - context.data.website.logo - ) { + jsonLDObject['headline'] = itemData.title; + jsonLDObject['datePublished'] = moment(itemData.createdAt).format('YYYY-MM-DDTHH:mm'); + jsonLDObject['dateModified'] = moment(itemData.modifiedAt).format('YYYY-MM-DDTHH:mm'); + + if (itemData.featuredImage.url || context.data.website.logo) { let imageUrl = ''; - if(context.data.root.post.featuredImage && context.data.root.post.featuredImage.url) { - imageUrl = context.data.root.post.featuredImage.url; + if (itemData.featuredImage && itemData.featuredImage.url) { + imageUrl = itemData.featuredImage.url; } else { imageUrl = context.data.website.logo; } @@ -123,15 +126,19 @@ function jsonLDHelper(rendererInstance, Handlebars) { if (context.data.root.metaDescriptionRaw) { jsonLDObject['description'] = context.data.root.metaDescriptionRaw.replace(/"/g, "'"); } else { - jsonLDObject['description'] = context.data.root.post.excerpt; + jsonLDObject['description'] = itemData.excerpt; } - if (context.data.root.post.author && context.data.root.post.author.name) { - let authorUrl = context.data.website.baseUrl + context.data.config.site.urls.authorsPrefix + '/' + context.data.root.post.author.username + '/'; + if (itemData.author && itemData.author.name) { + let authorUrl = context.data.website.baseUrl + context.data.config.site.urls.authorsPrefix + '/' + itemData.author.username + '/'; + + if (context.data.config.site.urls.postsPrefix && context.data.config.site.urls.authorsPrefixAfterPostsPrefix) { + authorUrl = context.data.website.baseUrl + context.data.config.site.urls.postsPrefix + '/' + context.data.config.site.urls.authorsPrefix + '/' + itemData.author.username + '/'; + } jsonLDObject['author'] = { "@type": "Person", - "name": context.data.root.post.author.name, + "name": itemData.author.name, "url": authorUrl }; } diff --git a/app/back-end/modules/render-html/handlebars/helpers/menu-url.js b/app/back-end/modules/render-html/handlebars/helpers/menu-url.js index ae3deebde..5ddb99e25 100644 --- a/app/back-end/modules/render-html/handlebars/helpers/menu-url.js +++ b/app/back-end/modules/render-html/handlebars/helpers/menu-url.js @@ -8,6 +8,7 @@ const slug = require('./../../../../helpers/slug'); * * Available types: * - post + * - page * - tag * - frontpage * - tags @@ -22,9 +23,13 @@ function menuURLHelper(rendererInstance, Handlebars) { let baseUrl = rendererInstance.siteConfig.domain; // Link to the single post pages - if(this.type === 'post') { - if(rendererInstance.siteConfig.advanced.urls.cleanUrls) { - output = baseUrl + '/' + this.link + '/'; + if (this.type === 'post') { + if (rendererInstance.siteConfig.advanced.urls.cleanUrls) { + if (rendererInstance.siteConfig.advanced.urls.postsPrefix) { + output = baseUrl + '/' + rendererInstance.siteConfig.advanced.urls.postsPrefix + '/' + this.link + '/'; + } else { + output = baseUrl + '/' + this.link + '/'; + } // In the preview mode we have to load URLs with // index.html as filesystem on OS doesn't behave // as the server environment and not redirect to @@ -33,73 +38,121 @@ function menuURLHelper(rendererInstance, Handlebars) { output += 'index.html'; } } else { - output = baseUrl + '/' + this.link + '.html'; + if (rendererInstance.siteConfig.advanced.urls.postsPrefix) { + output = baseUrl + '/' + rendererInstance.siteConfig.advanced.urls.postsPrefix + '/' + this.link + '.html'; + } else { + output = baseUrl + '/' + this.link + '.html'; + } + } + } + + // Link to the single page + if (this.type === 'page') { + let parentItems = rendererInstance.cachedItems.pagesStructureHierarchy[this.linkID]; + let pageSlug = this.link; + + if (rendererInstance.siteConfig.advanced.urls.cleanUrls && parentItems && parentItems.length) { + let slugs = []; + + for (let i = 0; i < parentItems.length; i++) { + if (rendererInstance.cachedItems.pages[parentItems[i]]) { + slugs.push(rendererInstance.cachedItems.pages[parentItems[i]].slug); + } + } + + slugs.push(this.link); + pageSlug = slugs.join('/'); + } + + if (rendererInstance.siteConfig.advanced.urls.cleanUrls) { + output = baseUrl + '/' + pageSlug + '/'; + // In the preview mode we have to load URLs with + // index.html as filesystem on OS doesn't behave + // as the server environment and not redirect to + // a proper URL + if (rendererInstance.previewMode || rendererInstance.siteConfig.advanced.urls.addIndex) { + output += 'index.html'; + } + } else { + output = baseUrl + '/' + pageSlug + '.html'; } } // Link to the tag pages - if(this.type === 'tag') { + if (this.type === 'tag') { output = baseUrl + '/' + this.link + '/'; - if(rendererInstance.siteConfig.advanced.urls.tagsPrefix !== '') { + if (rendererInstance.siteConfig.advanced.urls.tagsPrefix !== '') { output = baseUrl + '/' + rendererInstance.siteConfig.advanced.urls.tagsPrefix + '/' + this.link + '/'; } + if (rendererInstance.siteConfig.advanced.urls.postsPrefix && rendererInstance.siteConfig.advanced.urls.tagsPrefixAfterPostsPrefix) { + output = baseUrl + '/' + rendererInstance.siteConfig.advanced.urls.postsPrefix + '/' + rendererInstance.siteConfig.advanced.urls.tagsPrefix + '/' + this.link + '/'; + } + // In the preview mode we have to load URLs with // index.html as filesystem on OS doesn't behave // as the server environment and not redirect to // a proper URL - if(rendererInstance.previewMode || rendererInstance.siteConfig.advanced.urls.addIndex) { + if (rendererInstance.previewMode || rendererInstance.siteConfig.advanced.urls.addIndex) { output += 'index.html'; } } // Link to the author pages - if(this.type === 'author') { + if (this.type === 'author') { output = baseUrl + '/' + rendererInstance.siteConfig.advanced.urls.authorsPrefix + '/' + slug(this.link) + '/'; + if (rendererInstance.siteConfig.advanced.urls.postsPrefix && rendererInstance.siteConfig.advanced.urls.authorsPrefixAfterPostsPrefix) { + output = baseUrl + '/' + rendererInstance.siteConfig.advanced.urls.postsPrefix + '/' + rendererInstance.siteConfig.advanced.urls.authorsPrefix + '/' + slug(this.link) + '/'; + } + // In the preview mode we have to load URLs with // index.html as filesystem on OS doesn't behave // as the server environment and not redirect to // a proper URL - if(rendererInstance.previewMode || rendererInstance.siteConfig.advanced.urls.addIndex) { + if (rendererInstance.previewMode || rendererInstance.siteConfig.advanced.urls.addIndex) { output += 'index.html'; } } // Link to the frontpage - just the page domain name - if(this.type === 'frontpage') { + if (this.type === 'frontpage') { output = baseUrl + '/'; // In the preview mode we have to load URLs with // index.html as filesystem on OS doesn't behave // as the server environment and not redirect to // a proper URL - if(rendererInstance.previewMode || rendererInstance.siteConfig.advanced.urls.addIndex) { + if (rendererInstance.previewMode || rendererInstance.siteConfig.advanced.urls.addIndex) { output += 'index.html'; } } // Link to the tags list - just the page domain name with tags prefix - if(this.type === 'tags') { + if (this.type === 'tags') { output = baseUrl + '/' + rendererInstance.siteConfig.advanced.urls.tagsPrefix + '/'; + if (rendererInstance.siteConfig.advanced.urls.postsPrefix && rendererInstance.siteConfig.advanced.urls.tagsPrefixAfterPostsPrefix) { + output = baseUrl + '/' + rendererInstance.siteConfig.advanced.urls.postsPrefix + '/' + rendererInstance.siteConfig.advanced.urls.tagsPrefix + '/'; + } + // In the preview mode we have to load URLs with // index.html as filesystem on OS doesn't behave // as the server environment and not redirect to // a proper URL - if(rendererInstance.previewMode || rendererInstance.siteConfig.advanced.urls.addIndex) { + if (rendererInstance.previewMode || rendererInstance.siteConfig.advanced.urls.addIndex) { output += 'index.html'; } } // External links which should start with protocol - if(this.type === 'external') { + if (this.type === 'external') { output = this.link; } // Internal links which should start with the page domain name - if(this.type === 'internal') { + if (this.type === 'internal') { output = baseUrl + '/' + this.link; } diff --git a/app/back-end/modules/render-html/handlebars/helpers/page-url.js b/app/back-end/modules/render-html/handlebars/helpers/page-url.js index 08c72e6ff..1c8558e02 100644 --- a/app/back-end/modules/render-html/handlebars/helpers/page-url.js +++ b/app/back-end/modules/render-html/handlebars/helpers/page-url.js @@ -15,6 +15,10 @@ function pageURLHelper(rendererInstance, Handlebars) { path.push(context); } + if (context === '' && rendererInstance.siteConfig.advanced.urls.postsPrefix) { + path.push(rendererInstance.siteConfig.advanced.urls.postsPrefix); + } + // Skip page/X for URLs in page = 1 if (number > 1) { path.push(rendererInstance.siteConfig.advanced.urls.pageName); diff --git a/app/back-end/modules/render-html/handlebars/helpers/responsive-image-attributes.js b/app/back-end/modules/render-html/handlebars/helpers/responsive-image-attributes.js index 3b4440246..12ff485d7 100644 --- a/app/back-end/modules/render-html/handlebars/helpers/responsive-image-attributes.js +++ b/app/back-end/modules/render-html/handlebars/helpers/responsive-image-attributes.js @@ -5,7 +5,7 @@ const responsiveSizes = require('./responsive-sizes.js'); /** * Helper for sizes attribute for the images from options * - * {{responsiveImageAttributes @config.custom.imageOptionName [group]}} + * {{responsiveImageAttributes @config.custom.imageOptionName [type] [group]}} * * {{responsiveImageAttributes 'featuredImage' srcset.post sizes.post}} * {{responsiveImageAttributes 'tagImage' srcset.post sizes.post}} @@ -15,7 +15,15 @@ const responsiveSizes = require('./responsive-sizes.js'); */ function responsiveImageAttributesHelper(rendererInstance, Handlebars) { Handlebars.registerHelper('responsiveImageAttributes', function (firstParam, secondParam, thirdParam) { - if (firstParam === 'featuredImage' || firstParam === 'tagImage' || firstParam === 'authorImage') { + if (!firstParam) { + return ''; + } + + if ( + firstParam === 'featuredImage' || + firstParam === 'tagImage' || + firstParam === 'authorImage' + ) { if (secondParam && thirdParam) { return new Handlebars.SafeString('srcset="' + secondParam + '" sizes="' + thirdParam + '"'); } @@ -23,8 +31,21 @@ function responsiveImageAttributesHelper(rendererInstance, Handlebars) { return ''; } - let srcSet = responsiveSrcSet.returnSrcSetAttribute.bind(rendererInstance)(firstParam); - let sizes = responsiveSizes.returnSizesAttribute.bind(rendererInstance)(secondParam); + let srcSet = responsiveSrcSet.returnSrcSetAttribute.bind(rendererInstance)(firstParam, secondParam, thirdParam); + + if (typeof secondParam !== 'string') { + if (firstParam.indexOf('/media/authors/') > -1) { + secondParam = 'authorImages'; + } else if (firstParam.indexOf('/media/tags/') > -1) { + secondParam = 'tagImages'; + } else if (firstParam.indexOf('/media/posts/') > -1 || firstParam.indexOf('/media/pages/') > -1) { + secondParam = 'contentImages'; + } else if (firstParam.indexOf('/media/website/') > -1) { + secondParam = 'optionImages'; + } + } + + let sizes = responsiveSizes.returnSizesAttribute.bind(rendererInstance)(secondParam, thirdParam); if (srcSet) { return new Handlebars.SafeString(srcSet + ' ' + sizes); diff --git a/app/back-end/modules/render-html/handlebars/helpers/responsive-sizes.js b/app/back-end/modules/render-html/handlebars/helpers/responsive-sizes.js index d98d389e2..a49446d55 100644 --- a/app/back-end/modules/render-html/handlebars/helpers/responsive-sizes.js +++ b/app/back-end/modules/render-html/handlebars/helpers/responsive-sizes.js @@ -4,7 +4,7 @@ const UtilsHelper = require('./../../../../helpers/utils.js'); /** * Helper for sizes attribute for the images from options * - * {{responsiveSizes [group]}} + * {{responsiveSizes type [group]}} * * @returns {string} - string with the sizes attribute */ @@ -12,42 +12,32 @@ function responsiveSizesHelper(rendererInstance, Handlebars) { Handlebars.registerHelper('responsiveSizes', returnSizesAttribute.bind(rendererInstance)); } -function returnSizesAttribute (group) { - let output = ''; - - // Check if the responsive config exists - if(!UtilsHelper.responsiveImagesConfigExists(this.themeConfig)) { - return output; +function returnSizesAttribute (type, group) { + if (!UtilsHelper.responsiveImagesConfigExists(this.themeConfig)) { + return ''; } + let output = ''; let responsiveConfig = this.themeConfig.files.responsiveImages; + let useType = false; let useGroup = false; - if(typeof group === "string") { + if (typeof type === "string") { + useType = true; + } + + if (typeof group === "string") { useGroup = true; } - // Check for the config - if(useGroup) { - if (responsiveConfig.optionImages && responsiveConfig.optionImages.sizes) { - output = ' sizes="' + responsiveConfig.optionImages.sizes[group] + '" '; - } else if (responsiveConfig.optionImages && responsiveConfig.tagImages.sizes) { - output = ' sizes="' + responsiveConfig.tagImages.sizes[group] + '" '; - } else if (responsiveConfig.optionImages && responsiveConfig.authorImages.sizes) { - output = ' sizes="' + responsiveConfig.authorImages.sizes[group] + '" '; - } else if(responsiveConfig.contentImages && responsiveConfig.contentImages.sizes) { - output = ' sizes="' + responsiveConfig.contentImages.sizes[group] + '" '; - } - } else { - if(responsiveConfig.optionImages && responsiveConfig.optionImages.sizes) { - output = ' sizes="' + responsiveConfig.optionImages.sizes + '" '; - } else if(responsiveConfig.optionImages && responsiveConfig.tagImages.sizes) { - output = ' sizes="' + responsiveConfig.tagImages.sizes + '" '; - } else if(responsiveConfig.optionImages && responsiveConfig.authorImages.sizes) { - output = ' sizes="' + responsiveConfig.authorImages.sizes + '" '; - } else if(responsiveConfig.contentImages && responsiveConfig.contentImages.sizes) { - output = ' sizes="' + responsiveConfig.contentImages.sizes + '" '; - } + if (!useType) { + return ''; + } + + if (useGroup && responsiveConfig[type] && responsiveConfig[type].sizes && responsiveConfig[type].sizes[group]) { + output = ' sizes="' + responsiveConfig[type].sizes[group] + '" '; + } else if (!useGroup && responsiveConfig[type] && responsiveConfig[type].sizes) { + output = ' sizes="' + responsiveConfig[type].sizes + '" '; } return new Handlebars.SafeString(output); diff --git a/app/back-end/modules/render-html/handlebars/helpers/responsive-srcset.js b/app/back-end/modules/render-html/handlebars/helpers/responsive-srcset.js index 1a7aecdc6..18dc2e90b 100644 --- a/app/back-end/modules/render-html/handlebars/helpers/responsive-srcset.js +++ b/app/back-end/modules/render-html/handlebars/helpers/responsive-srcset.js @@ -7,7 +7,7 @@ const URLHelper = require('./../../helpers/url.js'); /** * Helper for srcset attribute from the provided image * - * {{responsiveSrcSet @config.custom.imageOptionName [group]}} + * {{responsiveSrcSet @config.custom.imageOptionName [type] [group]}} * * @returns {string} - string with the srcset attribute */ @@ -15,7 +15,7 @@ function responsiveSrcSetHelper(rendererInstance, Handlebars) { Handlebars.registerHelper('responsiveSrcSet', returnSrcSetAttribute.bind(rendererInstance)); } -function returnSrcSetAttribute (url, group) { +function returnSrcSetAttribute (url, type, group) { if (!url) { return; } @@ -25,36 +25,41 @@ function returnSrcSetAttribute (url, group) { let dimensions = false; let dimensionsData = false; - // Check if the responsive config exists - if(!UtilsHelper.responsiveImagesConfigExists(this.themeConfig)) { + if (!UtilsHelper.responsiveImagesConfigExists(this.themeConfig)) { return output; } // skip GIF and SVG images - if(url.slice(-4) === '.gif' || url.slice(-4) === '.svg') { + if (url.slice(-4) === '.gif' || url.slice(-4) === '.svg') { return output; } - if(typeof group !== "string") { + if (typeof type !== "string") { + type = false; + } + + if (typeof group !== "string") { group = false; } - // Check for the config - if(UtilsHelper.responsiveImagesConfigExists(this.themeConfig, 'optionImages')) { - dimensions = UtilsHelper.responsiveImagesDimensions(this.themeConfig, 'optionImages', group); - dimensionsData = UtilsHelper.responsiveImagesData(this.themeConfig, 'optionImages', group); - } else if(UtilsHelper.responsiveImagesConfigExists(this.themeConfig, 'tagImages')) { - dimensions = UtilsHelper.responsiveImagesDimensions(this.themeConfig, 'tagImages', group); - dimensionsData = UtilsHelper.responsiveImagesData(this.themeConfig, 'tagImages', group); - } else if(UtilsHelper.responsiveImagesConfigExists(this.themeConfig, 'authorImages')) { - dimensions = UtilsHelper.responsiveImagesDimensions(this.themeConfig, 'authorImages', group); - dimensionsData = UtilsHelper.responsiveImagesData(this.themeConfig, 'authorImages', group); - } else if(UtilsHelper.responsiveImagesConfigExists(this.themeConfig, 'contentImages')) { - dimensions = UtilsHelper.responsiveImagesDimensions(this.themeConfig, 'contentImages'); - dimensionsData = UtilsHelper.responsiveImagesData(this.themeConfig, 'contentImages'); + if (!type) { + if (url.indexOf('/media/authors/') > -1) { + type = 'authorImages'; + } else if (url.indexOf('/media/tags/') > -1) { + type = 'tagImages'; + } else if (url.indexOf('/media/posts/') > -1 || url.indexOf('/media/pages/') > -1) { + type = 'contentImages'; + } else if (url.indexOf('/media/website/') > -1) { + type = 'optionImages'; + } + } + + if (UtilsHelper.responsiveImagesConfigExists(this.themeConfig, type)) { + dimensions = UtilsHelper.responsiveImagesDimensions(this.themeConfig, type, group); + dimensionsData = UtilsHelper.responsiveImagesData(this.themeConfig, type, group); } - if(!dimensions) { + if (!dimensions) { return; } diff --git a/app/back-end/modules/render-html/handlebars/helpers/social-meta-tags.js b/app/back-end/modules/render-html/handlebars/helpers/social-meta-tags.js index 2a8e80e52..d23a19385 100644 --- a/app/back-end/modules/render-html/handlebars/helpers/social-meta-tags.js +++ b/app/back-end/modules/render-html/handlebars/helpers/social-meta-tags.js @@ -43,7 +43,7 @@ function socialMetaTagsHelper(rendererInstance, Handlebars) { } // Get tag values according to the current context - listing or single post page - if(contextData.data.context.indexOf('post') === -1) { + if(contextData.data.context.indexOf('post') === -1 && contextData.data.context.indexOf('page') === -1) { // Data for the index/tag listing page image = contextData.data.website.logo; title = siteName; @@ -65,15 +65,21 @@ function socialMetaTagsHelper(rendererInstance, Handlebars) { } } } else { - // Data for the single post page - image = contextData.data.root.post.featuredImage.url; + // Data for the single post or page + let itemData = contextData.data.root.post; + + if (!itemData) { + itemData = contextData.data.root.page; + } + + image = itemData.featuredImage.url; openGraphType = 'article'; if(!image) { image = contextData.data.website.logo; } - title = contextData.data.root.post.title; + title = itemData.title; if (rendererInstance.siteConfig.advanced.usePageTitleInsteadItemName) { title = contextData.data.root.title; @@ -82,7 +88,7 @@ function socialMetaTagsHelper(rendererInstance, Handlebars) { description = contextData.data.root.metaDescriptionRaw; if(description === '') { - description = contextData.data.root.post.excerpt; + description = itemData.excerpt; } } diff --git a/app/back-end/modules/render-html/helpers/content.js b/app/back-end/modules/render-html/helpers/content.js index cd6631f56..0f578a49e 100644 --- a/app/back-end/modules/render-html/helpers/content.js +++ b/app/back-end/modules/render-html/helpers/content.js @@ -511,6 +511,7 @@ class ContentHelper { */ static setInternalLinks(text, renderer) { text = ContentHelper.prepareInternalLinks(text, renderer, 'post'); + text = ContentHelper.prepareInternalLinks(text, renderer, 'page'); text = ContentHelper.prepareInternalLinks(text, renderer, 'tag'); text = ContentHelper.prepareInternalLinks(text, renderer, 'tags'); text = ContentHelper.prepareInternalLinks(text, renderer, 'author'); @@ -563,6 +564,10 @@ class ContentHelper { let url = '#INTERNAL_LINK#/tags/1'; let link = renderer.siteConfig.domain + '/' + renderer.siteConfig.advanced.urls.tagsPrefix + '/'; + if (renderer.siteConfig.advanced.urls.postsPrefix && renderer.siteConfig.advanced.urls.tagsPrefixAfterPostsPrefix) { + link = renderer.siteConfig.domain + '/' + renderer.siteConfig.advanced.urls.postsPrefix + '/' + renderer.siteConfig.advanced.urls.tagsPrefix + '/'; + } + if (renderer.previewMode || renderer.siteConfig.advanced.urls.addIndex) { link = link + 'index.html'; } @@ -586,11 +591,11 @@ class ContentHelper { let ids = urls.map(url => url.replace('#INTERNAL_LINK#/' + type + '/', '')); let links = {}; - for(let id of ids) { + for (let id of ids) { let baseUrl = '#INTERNAL_LINK#/' + type + '/' + id; let pluralName = type + 's'; - if(renderer.cachedItems[pluralName][id]) { + if (renderer.cachedItems[pluralName][id]) { links[baseUrl] = renderer.cachedItems[pluralName][id].url; } else { console.log('(i) Non-existing link: ' + pluralName + ' (' + id + ')'); diff --git a/app/back-end/modules/render-html/helpers/diffCopy.js b/app/back-end/modules/render-html/helpers/diffCopy.js index 04bc550e2..83019b8e8 100644 --- a/app/back-end/modules/render-html/helpers/diffCopy.js +++ b/app/back-end/modules/render-html/helpers/diffCopy.js @@ -111,17 +111,22 @@ class DiffCopy { } } - static removeUnusedPostFolders (postIDs, baseOutputPath) { + static removeUnusedItemFolders (postIDs, pageIDs, baseOutputPath) { let allPostFolders = fs.readdirSync(baseOutputPath); postIDs = JSON.parse(JSON.stringify(postIDs)); postIDs = postIDs.map(id => (id).toString()); + pageIDs = JSON.parse(JSON.stringify(pageIDs)); + pageIDs = pageIDs.map(id => (id).toString()); for (let i = 0; i < allPostFolders.length; i++) { - if (allPostFolders[i] === '.' || allPostFolders[i] === '..') { + if (allPostFolders[i] === '.' || allPostFolders[i] === '..' || allPostFolders[i] === 'defaults') { continue; } - if (postIDs.indexOf((allPostFolders[i]).toString()) === -1) { + if ( + postIDs.indexOf((allPostFolders[i]).toString()) === -1 && + pageIDs.indexOf((allPostFolders[i]).toString()) === -1 + ) { fs.removeSync(path.join(baseOutputPath, allPostFolders[i])); console.log('[DIFF REMOVE CATALOG]', path.join(baseOutputPath, allPostFolders[i])); } diff --git a/app/back-end/modules/render-html/helpers/files.js b/app/back-end/modules/render-html/helpers/files.js index dd3617779..f40d481c3 100644 --- a/app/back-end/modules/render-html/helpers/files.js +++ b/app/back-end/modules/render-html/helpers/files.js @@ -164,38 +164,28 @@ class Files { * @param outputDir * @param postIDs */ - static async copyMediaFiles (inputDir, outputDir, postIDs) { + static async copyMediaFiles (inputDir, outputDir, postIDs, pageIDs) { let basePathInput = path.join(inputDir, 'media'); let basePathOutput = path.join(outputDir, 'media'); - let dirs = ['website', 'files', 'tags', 'authors']; + let dirs = ['website', 'files', 'tags', 'authors', 'posts/defaults']; if (postIDs[0] === 0) { postIDs[0] = 'temp'; } - for (let i = 0; i < postIDs.length; i++) { - dirs.push('posts/' + postIDs[i]); + if (pageIDs[0] === 0) { + pageIDs[0] = 'temp'; } - try { - if (fs.existsSync(basePathOutput)) { - const stats = fs.lstatSync(basePathOutput); - - if (stats.isSymbolicLink()) { - fs.unlinkSync(basePathOutput); - } - } - } catch (err) { - console.log('[Error] Symlink removal problem: ' + err); + for (let i = 0; i < postIDs.length; i++) { + dirs.push('posts/' + postIDs[i]); } - if (!UtilsHelper.dirExists(path.join(basePathOutput))) { - fs.mkdirSync(path.join(basePathOutput)); + for (let i = 0; i < pageIDs.length; i++) { + dirs.push('posts/' + pageIDs[i]); } - if (!UtilsHelper.dirExists(path.join(basePathOutput, 'posts'))) { - fs.mkdirSync(path.join(basePathOutput, 'posts')); - } + fs.ensureDirSync(path.join(basePathOutput, 'posts')); for (let i = 0; i < dirs.length; i++) { if (!UtilsHelper.dirExists(path.join(basePathInput, dirs[i]))) { @@ -223,7 +213,7 @@ class Files { fs.removeSync(path.join(basePathOutput, 'authors', 'temp')); } - DiffCopy.removeUnusedPostFolders(postIDs, path.join(basePathOutput, 'posts')); + DiffCopy.removeUnusedItemFolders(postIDs, pageIDs, path.join(basePathOutput, 'posts')); } /** diff --git a/app/back-end/modules/render-html/helpers/sitemap.js b/app/back-end/modules/render-html/helpers/sitemap.js index 82c24a1c8..b66583e6d 100644 --- a/app/back-end/modules/render-html/helpers/sitemap.js +++ b/app/back-end/modules/render-html/helpers/sitemap.js @@ -44,7 +44,7 @@ class Sitemap { * Creates sitemap.xml file */ async create () { - this.getPostData(); + this.getData(); await this.getFilesList().then(() => { this.renderXML(); @@ -55,7 +55,7 @@ class Sitemap { /** * Get post data */ - getPostData () { + getData () { let momentOriginalLocale = moment.locale(); moment.locale('en'); @@ -213,23 +213,71 @@ class Sitemap { * Retrieves list of files to parse */ async getFilesList () { - let files = fs.readdirSync(this.baseDirectory); + let files; let internals = this.getInternalsList(); + if (this.siteConfig.advanced.urls.postsPrefix) { + files = fs.readdirSync(path.join(this.baseDirectory, this.siteConfig.advanced.urls.postsPrefix)); + + for (let file of files) { + // Skip hidden files and internal directories + if (file.indexOf('.') === 0 || internals.indexOf(file) > -1) { + continue; + } + + // Detect author pages + if (file === this.siteConfig.advanced.urls.authorsPrefix && this.siteConfig.advanced.urls.authorsPrefixAfterPostsPrefix) { + await this.getAuthorsFilesList(); + continue; + } + + // Detect homepage pagination + if (file === this.siteConfig.advanced.urls.pageName) { + if (!this.siteConfig.advanced.homepageNoIndexPagination && !this.siteConfig.advanced.homepageNoPagination) { + await this.getHomepagePaginationFilesList(); + } + + continue; + } + + // Detect tag pages - when tags prefix is empty + if (file.indexOf('.') === -1 && this.siteConfig.advanced.urls.tagsPrefix === '' && this.siteConfig.advanced.urls.tagsPrefixAfterPostsPrefix) { + await this.getTagsFilesList(file); + continue; + } + + // Detect tag pages - when tags prefix is not empty + if (this.siteConfig.advanced.urls.tagsPrefix !== '' && file === this.siteConfig.advanced.urls.tagsPrefix && this.siteConfig.advanced.urls.tagsPrefixAfterPostsPrefix) { + await this.getTagsFilesList(file, this.siteConfig.advanced.urls.tagsPrefix); + continue; + } + + // Detect post/pages files + await this.getPostsAndPagesFilesList(file, true); + } + } + + files = fs.readdirSync(this.baseDirectory); + for (let file of files) { // Skip hidden files and internal directories if (file.indexOf('.') === 0 || internals.indexOf(file) > -1) { continue; } + // Skip posts prefix directory + if (file === this.siteConfig.advanced.urls.postsPrefix) { + continue; + } + // Detect author pages - if (file === this.siteConfig.advanced.urls.authorsPrefix) { + if (file === this.siteConfig.advanced.urls.authorsPrefix && !this.siteConfig.advanced.urls.authorsPrefixAfterPostsPrefix) { await this.getAuthorsFilesList(); continue; } // Detect homepage pagination - if (file === this.siteConfig.advanced.urls.pageName) { + if (file === this.siteConfig.advanced.urls.pageName && !this.siteConfig.advanced.urls.postsPrefix) { if (!this.siteConfig.advanced.homepageNoIndexPagination && !this.siteConfig.advanced.homepageNoPagination) { await this.getHomepagePaginationFilesList(); } @@ -238,26 +286,26 @@ class Sitemap { } // Detect tag pages - when tags prefix is empty - if (file.indexOf('.') === -1 && this.siteConfig.advanced.urls.tagsPrefix === '') { + if (file.indexOf('.') === -1 && this.siteConfig.advanced.urls.tagsPrefix === '' && !this.siteConfig.advanced.urls.tagsPrefixAfterPostsPrefix) { await this.getTagsFilesList(file); continue; } // Detect tag pages - when tags prefix is not empty - if (this.siteConfig.advanced.urls.tagsPrefix !== '' && file === this.siteConfig.advanced.urls.tagsPrefix) { + if (this.siteConfig.advanced.urls.tagsPrefix !== '' && file === this.siteConfig.advanced.urls.tagsPrefix && !this.siteConfig.advanced.urls.tagsPrefixAfterPostsPrefix) { await this.getTagsFilesList(file, this.siteConfig.advanced.urls.tagsPrefix); continue; } - // Detect post files + // Detect post/pages files with clean URLs disabled if (!this.siteConfig.advanced.urls.cleanUrls && file.indexOf('.html') > 0) { - await this.getPostsFilesList(file); + await this.getPostsAndPagesFilesList(file, false, true); continue; } - // Detect post files + // Detect post/pages files if (this.siteConfig.advanced.urls.cleanUrls) { - await this.getPostsFilesList(file, true); + await this.getPostsAndPagesFilesList(file, true, true); } } } @@ -327,7 +375,14 @@ class Sitemap { return; } - let files = fs.readdirSync(path.join(this.baseDirectory, this.siteConfig.advanced.urls.authorsPrefix)); + let authorsPathToCheck = path.join(this.baseDirectory, this.siteConfig.advanced.urls.authorsPrefix); + let usePostsPrefix = this.siteConfig.advanced.urls.postsPrefix && this.siteConfig.advanced.urls.authorsPrefixAfterPostsPrefix; + + if (usePostsPrefix) { + authorsPathToCheck = path.join(this.baseDirectory, this.siteConfig.advanced.urls.postsPrefix, this.siteConfig.advanced.urls.authorsPrefix); + } + + let files = fs.readdirSync(authorsPathToCheck); for (let file of files) { // Skip files @@ -335,18 +390,21 @@ class Sitemap { continue; } - let authorFileContent = await readFile(path.join(this.baseDirectory, this.siteConfig.advanced.urls.authorsPrefix, file, 'index.html'), 'utf8'); + let authorFileContent = await readFile(path.join(authorsPathToCheck, file, 'index.html'), 'utf8'); if (authorFileContent.indexOf('name="robots" content="noindex') === -1) { - this.fileList.push(this.siteConfig.advanced.urls.authorsPrefix + '/' + file + '/index.html'); + if (usePostsPrefix) { + this.fileList.push(this.siteConfig.advanced.urls.postsPrefix + '/' + this.siteConfig.advanced.urls.authorsPrefix + '/' + file + '/index.html'); + } else { + this.fileList.push(this.siteConfig.advanced.urls.authorsPrefix + '/' + file + '/index.html'); + } if (this.siteConfig.advanced.authorNoIndexPagination || this.siteConfig.advanced.authorNoPagination) { continue; } let paginationPath = path.join( - this.baseDirectory, - this.siteConfig.advanced.urls.authorsPrefix, + authorsPathToCheck, file, this.siteConfig.advanced.urls.pageName ); @@ -362,7 +420,12 @@ class Sitemap { // Add all pages of pagination let pageName = this.siteConfig.advanced.urls.pageName; - this.fileList.push(this.siteConfig.advanced.urls.authorsPrefix + '/' + file + '/' + pageName + '/' + authorFile + '/index.html'); + + if (usePostsPrefix) { + this.fileList.push(this.siteConfig.advanced.urls.postsPrefix + '/' + this.siteConfig.advanced.urls.authorsPrefix + '/' + file + '/' + pageName + '/' + authorFile + '/index.html'); + } else { + this.fileList.push(this.siteConfig.advanced.urls.authorsPrefix + '/' + file + '/' + pageName + '/' + authorFile + '/index.html'); + } } } } @@ -377,13 +440,22 @@ class Sitemap { */ async getTagsFilesList (tagName, prefix = '') { const readFile = util.promisify(fs.readFile); + let postsPrefix = this.siteConfig.advanced.urls.postsPrefix; + let tagsPrefix = this.siteConfig.advanced.urls.tagsPrefix; + let usePostsPrefix = this.siteConfig.advanced.urls.postsPrefix && this.siteConfig.advanced.urls.tagsPrefixAfterPostsPrefix; if (!this.shouldIndexTags()) { return Promise.resolve(); } if (prefix === '') { - let tagFileContent = await readFile(path.join(this.baseDirectory, tagName, 'index.html'), 'utf8'); + let tagFileContent; + + if (usePostsPrefix) { + tagFileContent = await readFile(path.join(this.baseDirectory, postsPrefix, tagName, 'index.html'), 'utf8'); + } else { + tagFileContent = await readFile(path.join(this.baseDirectory, tagName, 'index.html'), 'utf8'); + } // Detect if noindex does not exist in the post file if (tagFileContent.indexOf('name="robots" content="noindex') === -1) { @@ -396,6 +468,10 @@ class Sitemap { let paginationPath = path.join(this.baseDirectory, tagName, this.siteConfig.advanced.urls.pageName); + if (usePostsPrefix) { + paginationPath = path.join(this.baseDirectory, postsPrefix, tagName, this.siteConfig.advanced.urls.pageName); + } + if (fs.existsSync(paginationPath)) { let files = fs.readdirSync(paginationPath); @@ -407,7 +483,12 @@ class Sitemap { // Add all pages of pagination let pageName = this.siteConfig.advanced.urls.pageName; - this.fileList.push(tagName + '/' + pageName + '/' + file + '/index.html'); + + if (usePostsPrefix) { + this.fileList.push(postsPrefix + '/' + tagName + '/' + pageName + '/' + file + '/index.html'); + } else { + this.fileList.push(tagName + '/' + pageName + '/' + file + '/index.html'); + } } } } @@ -415,7 +496,23 @@ class Sitemap { return Promise.resolve(); } - let files = fs.readdirSync(path.join(this.baseDirectory, this.siteConfig.advanced.urls.tagsPrefix)); + let tagsPath = path.join(this.baseDirectory, tagsPrefix); + + if (usePostsPrefix) { + tagsPath = path.join(this.baseDirectory, postsPrefix, tagsPrefix); + } + + if (UtilsHelper.fileExists(path.join(tagsPath, 'index.html'))) { + let tagsFileContent = await readFile(path.join(tagsPath, 'index.html'), 'utf8'); + + if (usePostsPrefix && tagsFileContent.indexOf('name="robots" content="noindex') === -1) { + this.fileList.push(postsPrefix + '/' + tagsPrefix + '/index.html'); + } else if (tagsFileContent.indexOf('name="robots" content="noindex') === -1) { + this.fileList.push(tagsPrefix + '/index.html'); + } + } + + let files = fs.readdirSync(tagsPath); for (let file of files) { // Skip files @@ -423,19 +520,22 @@ class Sitemap { continue; } - let tagFileContent = await readFile(path.join(this.baseDirectory, this.siteConfig.advanced.urls.tagsPrefix, file, 'index.html'), 'utf8'); + let tagFileContent = await readFile(path.join(tagsPath, file, 'index.html'), 'utf8'); // Detect if noindex does not exist in the post file if (tagFileContent.indexOf('name="robots" content="noindex') === -1) { - this.fileList.push(this.siteConfig.advanced.urls.tagsPrefix + '/' + file + '/index.html'); + if (usePostsPrefix) { + this.fileList.push(postsPrefix + '/' + tagsPrefix + '/' + file + '/index.html'); + } else { + this.fileList.push(tagsPrefix + '/' + file + '/index.html'); + } if (this.siteConfig.advanced.tagNoIndexPagination || this.siteConfig.advanced.tagNoPagination) { continue; } let paginationPath = path.join( - this.baseDirectory, - this.siteConfig.advanced.urls.tagsPrefix, + tagsPath, file, this.siteConfig.advanced.urls.pageName ); @@ -451,7 +551,12 @@ class Sitemap { // Add all pages of pagination let pageName = this.siteConfig.advanced.urls.pageName; - this.fileList.push(this.siteConfig.advanced.urls.tagsPrefix + '/' + file + '/' + pageName + '/' + tagFile + '/index.html'); + + if (usePostsPrefix) { + this.fileList.push(postsPrefix + '/' + tagsPrefix + '/' + file + '/' + pageName + '/' + tagFile + '/index.html'); + } else { + this.fileList.push(tagsPrefix + '/' + file + '/' + pageName + '/' + tagFile + '/index.html'); + } } } } @@ -466,7 +571,29 @@ class Sitemap { return; } - let files = fs.readdirSync(path.join(this.baseDirectory, this.siteConfig.advanced.urls.pageName)); + let postsPrefix = this.siteConfig.advanced.urls.postsPrefix; + let files; + let filesPath; + let homeIndexPath; + const readFile = util.promisify(fs.readFile); + + if (postsPrefix) { + filesPath = path.join(this.baseDirectory, postsPrefix, this.siteConfig.advanced.urls.pageName); + files = fs.readdirSync(filesPath); + homeIndexPath = path.join(this.baseDirectory, postsPrefix); + } else { + filesPath = path.join(this.baseDirectory, this.siteConfig.advanced.urls.pageName); + files = fs.readdirSync(filesPath); + homeIndexPath = this.baseDirectory; + } + + if (postsPrefix && this.siteConfig.advanced.usePageAsFrontpage) { + let homeIndexFileContent = await readFile(path.join(homeIndexPath, 'index.html'), 'utf8'); + + if (homeIndexFileContent.indexOf('name="robots" content="noindex') === -1) { + this.fileList.push(postsPrefix + '/index.html'); + } + } for (let file of files) { // Skip files @@ -474,12 +601,16 @@ class Sitemap { continue; } - const readFile = util.promisify(fs.readFile); - let homeFileContent = await readFile(path.join(this.baseDirectory, this.siteConfig.advanced.urls.pageName, file, 'index.html'), 'utf8'); + let homeFileContent = await readFile(path.join(filesPath, file, 'index.html'), 'utf8'); if (homeFileContent.indexOf('name="robots" content="noindex') === -1) { let pageName = this.siteConfig.advanced.urls.pageName; - this.fileList.push(pageName + '/' + file + '/index.html'); + + if (postsPrefix) { + this.fileList.push(postsPrefix + '/' + pageName + '/' + file + '/index.html'); + } else { + this.fileList.push(pageName + '/' + file + '/index.html'); + } } } } @@ -490,9 +621,13 @@ class Sitemap { * @param file * @param cleanUrlsEnabled */ - async getPostsFilesList (file, cleanUrlsEnabled = false) { + async getPostsAndPagesFilesList (file, cleanUrlsEnabled = false, skipPostsPrefix = false) { const readFile = util.promisify(fs.readFile); + if (!skipPostsPrefix && this.siteConfig.advanced.urls.postsPrefix) { + file = path.join(this.siteConfig.advanced.urls.postsPrefix, file); + } + if (!cleanUrlsEnabled) { // Read post file let postFileContent = await readFile(path.join(this.baseDirectory, file), 'utf8'); @@ -539,9 +674,48 @@ class Sitemap { } } + // Look for subpages + let baseDir = path.join(this.baseDirectory, file); + + if (fs.lstatSync(baseDir).isDirectory()) { + let subpages = fs.readdirSync(baseDir); + this.scanSubpages(subpages, file); + } + return Promise.resolve() } + // recusive function to scan subpages + scanSubpages (subpages, currentPath) { + for (let subpage of subpages) { + if (subpage.indexOf('.') > -1) { + continue; + } + + let filePath = path.join(this.baseDirectory, currentPath, subpage, 'index.html'); + + if (fs.existsSync(filePath)) { + let postFileContent = fs.readFileSync(filePath, 'utf8'); + + if (postFileContent.indexOf('name="robots" content="noindex') === -1) { + this.fileList.push(currentPath + '/' + subpage + '/'); + } + } + + let pathToScan = path.join(this.baseDirectory, currentPath, subpage); + + if (!fs.lstatSync(pathToScan).isDirectory()) { + continue; + } + + let subpagesList = fs.readdirSync(pathToScan); + + if (subpagesList.length) { + this.scanSubpages(subpagesList, path.join(currentPath, subpage)); + } + } + } + /** * Returns sitemap XML file */ diff --git a/app/back-end/modules/render-html/helpers/specs/url.spec.js b/app/back-end/modules/render-html/helpers/specs/url.spec.js index abaf46df0..6d59d3cb0 100644 --- a/app/back-end/modules/render-html/helpers/specs/url.spec.js +++ b/app/back-end/modules/render-html/helpers/specs/url.spec.js @@ -52,12 +52,14 @@ describe('URL helper', function() { describe('#URLHelper.createPaginationPermalink', function() { let urlsConfig = { - cleanUrls: false, - tagsPrefix: '', - authorsPrefix: 'authors', - pageName: 'page', - errorPage: '404.html', - searchPage: 'search.html' + urls: { + cleanUrls: false, + tagsPrefix: '', + authorsPrefix: 'authors', + pageName: 'page', + errorPage: '404.html', + searchPage: 'search.html' + } }; it('should create a proper URL for different types of pages', function () { diff --git a/app/back-end/modules/render-html/helpers/template.js b/app/back-end/modules/render-html/helpers/template.js index 3296f4e7a..13f0aab26 100644 --- a/app/back-end/modules/render-html/helpers/template.js +++ b/app/back-end/modules/render-html/helpers/template.js @@ -92,51 +92,90 @@ class TemplateHelper { content = this.compressHTML(content); } + fs.ensureDirSync(path.parse(filePath).dir); fs.writeFile(filePath, content, {'flags': 'w'}); } - saveOutputPostFile(postSlug, content) { + saveOutputPostFile (postSlug, content) { let suffix = '.html'; + let baseDir = this.outputDir; - if(this.siteConfig.advanced.urls.cleanUrls) { + if (this.siteConfig.advanced.urls.postsPrefix) { + baseDir += '/' + this.siteConfig.advanced.urls.postsPrefix; + } + + if (this.siteConfig.advanced.urls.cleanUrls) { suffix = '/index.html'; } - let filePath = path.join(this.outputDir, postSlug + suffix); + let filePath = path.join(baseDir, postSlug + suffix); content = this.compressHTML(content); - if(this.siteConfig.advanced.urls.cleanUrls) { - let dirPath = path.join(this.outputDir, postSlug); - - if(!Utils.dirExists(dirPath)) { - fs.mkdirSync(dirPath); - } + if (this.siteConfig.advanced.urls.cleanUrls) { + let dirPath = path.join(baseDir, postSlug); + fs.ensureDirSync(dirPath); } fs.writeFile(filePath, content, {'flags': 'w'}); } saveOutputHomePaginationFile(pageNumber, content) { - let filePath = path.join(this.outputDir, this.siteConfig.advanced.urls.pageName, pageNumber.toString(), 'index.html'); - let pageDirPath = path.join(this.outputDir, this.siteConfig.advanced.urls.pageName); + let baseDir = this.outputDir; + + if (this.siteConfig.advanced.urls.postsPrefix) { + baseDir += '/' + this.siteConfig.advanced.urls.postsPrefix; + } + + let filePath = path.join(baseDir, this.siteConfig.advanced.urls.pageName, pageNumber.toString(), 'index.html'); + let pageDirPath = path.join(baseDir, this.siteConfig.advanced.urls.pageName); let dirPath = path.join(pageDirPath, pageNumber.toString()); content = this.compressHTML(content); // Create page directory if not exists - let pageDirStat = false; + fs.ensureDirSync(pageDirPath); + // Create dir for specific page + fs.ensureDirSync(dirPath); + // Create index.html file in the created dir + fs.writeFile(filePath, content, {'flags': 'w'}); + } - try { - pageDirStat = fs.statSync(pageDirPath); - } catch(e) {} + saveOutputPageFile (pageID, pageSlug, content, renderer) { + let suffix = '.html'; + let parentItems = renderer.cachedItems.pagesStructureHierarchy[pageID]; + + if (this.siteConfig.advanced.urls.cleanUrls) { + suffix = '/index.html'; + } - if(!pageDirStat) { - fs.mkdirSync(pageDirPath); + // If page is set as frontpage - render it in the root directory + if (this.siteConfig.advanced.usePageAsFrontpage && this.siteConfig.advanced.pageAsFrontpage === pageID) { + let filePath = path.join(this.outputDir, 'index.html'); + content = this.compressHTML(content); + fs.writeFile(filePath, content, {'flags': 'w'}); + return; } - // Create dir for specific page - fs.mkdirSync(dirPath); + if (parentItems && parentItems.length) { + let slugs = []; + + for (let i = 0; i < parentItems.length; i++) { + if (renderer.cachedItems.pages[parentItems[i]]) { + slugs.push(renderer.cachedItems.pages[parentItems[i]].slug); + } + } + + slugs.push(pageSlug); + pageSlug = slugs.join('/'); + } + + let filePath = path.join(this.outputDir, pageSlug + suffix); + content = this.compressHTML(content); + + if (this.siteConfig.advanced.urls.cleanUrls) { + let dirPath = path.join(this.outputDir, pageSlug); + fs.ensureDirSync(dirPath); + } - // Create index.html file in the created dir fs.writeFile(filePath, content, {'flags': 'w'}); } @@ -144,13 +183,13 @@ class TemplateHelper { * Save a compiled Handlebars template for tags list */ saveOutputTagsListFile(content) { - let filePath = path.join(this.outputDir, this.siteConfig.advanced.urls.tagsPrefix, 'index.html'); - let dirPath = path.join(this.outputDir, this.siteConfig.advanced.urls.tagsPrefix); - - if(!Utils.dirExists(dirPath)) { - fs.mkdirSync(dirPath); - } - + let usePostsPrefix = this.siteConfig.advanced.urls.postsPrefix && this.siteConfig.advanced.urls.tagsPrefixAfterPostsPrefix; + let postsPrefix = this.siteConfig.advanced.urls.postsPrefix; + let baseDir = usePostsPrefix ? path.join(this.outputDir, postsPrefix) : this.outputDir; + let tagsPrefix = this.siteConfig.advanced.urls.tagsPrefix; + let filePath = path.join(baseDir, tagsPrefix, 'index.html'); + let dirPath = path.join(baseDir, tagsPrefix); + fs.ensureDirSync(dirPath); content = this.compressHTML(content); fs.writeFile(filePath, content, {'flags': 'w'}); } @@ -160,57 +199,66 @@ class TemplateHelper { * a specified tag filename */ saveOutputTagFile(tagSlug, content, isTagPreview = false) { - let filePath = path.join(this.outputDir, tagSlug, 'index.html'); - let dirPath = path.join(this.outputDir, tagSlug); - let tagsPath = false; + let usePostsPrefix = this.siteConfig.advanced.urls.postsPrefix && this.siteConfig.advanced.urls.tagsPrefixAfterPostsPrefix; + let postsPrefix = this.siteConfig.advanced.urls.postsPrefix; + let baseDir = usePostsPrefix ? path.join(this.outputDir, postsPrefix) : this.outputDir; + let filePath = path.join(baseDir, tagSlug, 'index.html'); + let dirPath = path.join(baseDir, tagSlug); + let tagsPath = usePostsPrefix ? baseDir : false; + let tagsPrefix = this.siteConfig.advanced.urls.tagsPrefix; if (isTagPreview) { filePath = path.join(this.outputDir, 'preview.html'); content = this.compressHTML(content); fs.writeFile(filePath, content, {'flags': 'w'}); return; - } else if (this.siteConfig.advanced.urls.tagsPrefix !== '') { - filePath = path.join(this.outputDir, this.siteConfig.advanced.urls.tagsPrefix, tagSlug, 'index.html'); - dirPath = path.join(this.outputDir, this.siteConfig.advanced.urls.tagsPrefix, tagSlug); - tagsPath = path.join(this.outputDir, this.siteConfig.advanced.urls.tagsPrefix); + } else if (tagsPrefix) { + filePath = path.join(baseDir, tagsPrefix, tagSlug, 'index.html'); + dirPath = path.join(baseDir, tagsPrefix, tagSlug); + tagsPath = path.join(baseDir, tagsPrefix); + } - if(!Utils.dirExists(tagsPath)) { - fs.mkdirSync(tagsPath); - } + if (tagsPath && !Utils.dirExists(tagsPath)) { + fs.ensureDirSync(tagsPath); } content = this.compressHTML(content); - fs.mkdirSync(dirPath); + fs.ensureDirSync(dirPath); fs.writeFile(filePath, content, {'flags': 'w'}); } saveOutputTagPaginationFile(tagSlug, pageNumber, content) { - let filePath = path.join(this.outputDir, tagSlug, this.siteConfig.advanced.urls.pageName, pageNumber.toString(), 'index.html'); - let pageDirPath = path.join(this.outputDir, tagSlug, this.siteConfig.advanced.urls.pageName); + let usePostsPrefix = this.siteConfig.advanced.urls.postsPrefix && this.siteConfig.advanced.urls.tagsPrefixAfterPostsPrefix; + let postsPrefix = this.siteConfig.advanced.urls.postsPrefix; + let baseDir = usePostsPrefix ? path.join(this.outputDir, postsPrefix) : this.outputDir; + let pageName = this.siteConfig.advanced.urls.pageName; + let filePath = path.join(baseDir, tagSlug, pageName, pageNumber.toString(), 'index.html'); + let pageDirPath = path.join(baseDir, tagSlug, pageName); let dirPath = path.join(pageDirPath, pageNumber.toString()); - let tagsPath = false; + let tagsPath = usePostsPrefix ? baseDir : false; + let tagsPrefix = this.siteConfig.advanced.urls.tagsPrefix; - if(this.siteConfig.advanced.urls.tagsPrefix !== '') { - filePath = path.join(this.outputDir, this.siteConfig.advanced.urls.tagsPrefix, tagSlug, this.siteConfig.advanced.urls.pageName, pageNumber.toString(), 'index.html'); - pageDirPath = path.join(this.outputDir, this.siteConfig.advanced.urls.tagsPrefix, tagSlug, this.siteConfig.advanced.urls.pageName); + if (tagsPrefix) { + filePath = path.join(baseDir, tagsPrefix, tagSlug, pageName, pageNumber.toString(), 'index.html'); + pageDirPath = path.join(baseDir, tagsPrefix, tagSlug, pageName); dirPath = path.join(pageDirPath, pageNumber.toString()); - tagsPath = path.join(this.outputDir, this.siteConfig.advanced.urls.tagsPrefix); + tagsPath = path.join(baseDir, tagsPrefix); + } - if(!Utils.dirExists(tagsPath)) { - fs.mkdirSync(tagsPath); - } + if (tagsPath && !Utils.dirExists(tagsPath)) { + fs.ensureDirSync(tagsPath); } content = this.compressHTML(content); // Create page directory if not exists if(!Utils.dirExists(pageDirPath)) { - fs.mkdirSync(pageDirPath); + fs.ensureDirSync(pageDirPath); } // Create dir for specific page if(!Utils.dirExists(dirPath)) { - fs.mkdirSync(dirPath); + fs.ensureDirSync(dirPath); } // Create index.html file in the created dir @@ -222,8 +270,11 @@ class TemplateHelper { * a specified author filename */ saveOutputAuthorFile(authorSlug, content, isAuthorPreview = false) { - let filePath = path.join(this.outputDir, this.siteConfig.advanced.urls.authorsPrefix, authorSlug, 'index.html'); - let dirPath = path.join(this.outputDir, this.siteConfig.advanced.urls.authorsPrefix, authorSlug); + let usePostsPrefix = this.siteConfig.advanced.urls.postsPrefix && this.siteConfig.advanced.urls.authorsPrefixAfterPostsPrefix; + let postsPrefix = this.siteConfig.advanced.urls.postsPrefix; + let baseDir = usePostsPrefix ? path.join(this.outputDir, postsPrefix) : this.outputDir; + let filePath = path.join(baseDir, this.siteConfig.advanced.urls.authorsPrefix, authorSlug, 'index.html'); + let dirPath = path.join(baseDir, this.siteConfig.advanced.urls.authorsPrefix, authorSlug); if (isAuthorPreview) { filePath = path.join(this.outputDir, 'preview.html'); @@ -233,30 +284,23 @@ class TemplateHelper { } content = this.compressHTML(content); - fs.mkdirSync(dirPath); + fs.ensureDirSync(dirPath); fs.writeFile(filePath, content, {'flags': 'w'}); } saveOutputAuthorPaginationFile(authorSlug, pageNumber, content) { - let filePath = path.join(this.outputDir, this.siteConfig.advanced.urls.authorsPrefix, authorSlug, this.siteConfig.advanced.urls.pageName, pageNumber.toString(), 'index.html'); - let pageDirPath = path.join(this.outputDir, this.siteConfig.advanced.urls.authorsPrefix, authorSlug, this.siteConfig.advanced.urls.pageName); + let usePostsPrefix = this.siteConfig.advanced.urls.postsPrefix && this.siteConfig.advanced.urls.authorsPrefixAfterPostsPrefix; + let postsPrefix = this.siteConfig.advanced.urls.postsPrefix; + let baseDir = usePostsPrefix ? path.join(this.outputDir, postsPrefix) : this.outputDir; + let filePath = path.join(baseDir, this.siteConfig.advanced.urls.authorsPrefix, authorSlug, this.siteConfig.advanced.urls.pageName, pageNumber.toString(), 'index.html'); + let pageDirPath = path.join(baseDir, this.siteConfig.advanced.urls.authorsPrefix, authorSlug, this.siteConfig.advanced.urls.pageName); let dirPath = path.join(pageDirPath, pageNumber.toString()); content = this.compressHTML(content); // Create page directory if not exists - let pageDirStat = false; - - try { - pageDirStat = fs.statSync(pageDirPath); - } catch(e) {} - - if(!pageDirStat) { - fs.mkdirSync(pageDirPath); - } - + fs.ensureDirSync(pageDirPath); // Create dir for specific page - fs.mkdirSync(dirPath); - + fs.ensureDirSync(dirPath); // Create index.html file in the created dir fs.writeFile(filePath, content, {'flags': 'w'}); } diff --git a/app/back-end/modules/render-html/helpers/url.js b/app/back-end/modules/render-html/helpers/url.js index 2690644c4..5c512b3ba 100644 --- a/app/back-end/modules/render-html/helpers/url.js +++ b/app/back-end/modules/render-html/helpers/url.js @@ -35,6 +35,10 @@ class URLHelper { if(urlsConfig.tagsPrefix) { url = domain + '/' + urlsConfig.tagsPrefix + '/' + URLHelper.createSlug(tagName) + '/'; + + if (urlsConfig.postsPrefix && urlsConfig.tagsPrefixAfterPostsPrefix) { + url = domain + '/' + urlsConfig.postsPrefix + '/' + urlsConfig.tagsPrefix + '/' + URLHelper.createSlug(tagName) + '/'; + } } if(addIndexHtml) { @@ -48,13 +52,13 @@ class URLHelper { * Creates pagination link for a given URL type * * @param domain - * @param urlsConfig + * @param config * @param pageNumber * @param pageType * @param pageSlug * @returns {*} */ - static createPaginationPermalink(domain, urlsConfig, pageNumber, pageType, pageSlug, addIndexHtml = false) { + static createPaginationPermalink(domain, config, pageNumber, pageType, pageSlug, addIndexHtml = false) { // When there is no link - skip the operations if(pageNumber === false) { return false; @@ -71,17 +75,35 @@ class URLHelper { let pageSuffix = ''; if(pageNumber > 1) { - pageSuffix = urlsConfig.pageName + '/' + pageNumber + '/'; + pageSuffix = config.urls.pageName + '/' + pageNumber + '/'; } let optionalPrefix = ''; + if (pageType === 'home') { + if (config.urls.postsPrefix) { + optionalPrefix = config.urls.postsPrefix + '/'; + } + + if (config.urls.postsPrefix && !config.usePageAsFrontpage && pageNumber === 1) { + optionalPrefix = ''; + } + } + if(pageType === 'author') { - optionalPrefix = urlsConfig.authorsPrefix + '/'; + optionalPrefix = config.urls.authorsPrefix + '/'; + + if (config.urls.postsPrefix && config.urls.authorsPrefixAfterPostsPrefix) { + optionalPrefix = config.urls.postsPrefix + '/' + config.urls.authorsPrefix + '/'; + } } - if(pageType === 'tag' && urlsConfig.tagsPrefix !== '') { - optionalPrefix = urlsConfig.tagsPrefix + '/'; + if(pageType === 'tag' && config.urls.tagsPrefix !== '') { + optionalPrefix = config.urls.tagsPrefix + '/'; + + if (config.urls.postsPrefix && config.urls.tagsPrefixAfterPostsPrefix) { + optionalPrefix = config.urls.postsPrefix + '/' + config.urls.tagsPrefix + '/'; + } } let url = domain + '/' + optionalPrefix + pagePrefix + pageSuffix; @@ -176,15 +198,35 @@ class URLHelper { static prepareSettingsImages(domain, settings) { let groups = Object.keys(settings); - for(let i = 0; i < groups.length; i++) { + for (let i = 0; i < groups.length; i++) { let options = Object.keys(settings[groups[i]]); - for(let j = 0; j < options.length; j++) { - if(typeof settings[groups[i]][options[j]] !== "string") { + for (let j = 0; j < options.length; j++) { + if (Array.isArray(settings[groups[i]][options[j]])) { + let items = settings[groups[i]][options[j]]; + + for (let k = 0; k < items.length; k++) { + let item = items[k]; + + if (typeof item === 'object') { + let keys = Object.keys(item); + + for (let l = 0; l < keys.length; l++) { + let key = keys[l]; + + if (typeof item[key] === 'string' && item[key].indexOf('media/website') > -1) { + item[key] = URLHelper.fixProtocols(normalizePath(domain + '/' + item[key])); + } + } + } + } + } + + if (typeof settings[groups[i]][options[j]] !== "string") { continue; } - if(settings[groups[i]][options[j]].indexOf('media/website') > -1) { + if (settings[groups[i]][options[j]].indexOf('media/website') > -1) { settings[groups[i]][options[j]] = URLHelper.fixProtocols(normalizePath(domain + '/' + settings[groups[i]][options[j]])); } } diff --git a/app/back-end/modules/render-html/helpers/view-settings.js b/app/back-end/modules/render-html/helpers/view-settings.js index 7c4e6b280..c5ddedcbf 100644 --- a/app/back-end/modules/render-html/helpers/view-settings.js +++ b/app/back-end/modules/render-html/helpers/view-settings.js @@ -1,12 +1,21 @@ class ViewSettings { - static override(viewSettings, defaultViewConfig) { + static override(viewSettings, defaultViewConfig, itemData, rendererInstance) { let outputConfig = {}; + // Get default config structure + let viewType = itemData.type; + let viewConfigObject = {} + + if (viewType && (viewType === 'author' || viewType === 'post' || viewType === 'page' || viewType === 'tag')) { + let configField = viewType + 'Config'; + viewConfigObject = JSON.parse(JSON.stringify(rendererInstance.viewConfigStructure[configField])); + } + // Generate default settings structure let defaultViewFields = Object.keys(defaultViewConfig); for(let i = 0; i < defaultViewFields.length; i++) { - let field = viewSettings[defaultViewFields[i]]; + let field = viewConfigObject[defaultViewFields[i]]; let defaultField = defaultViewConfig[defaultViewFields[i]]; if(typeof field !== 'undefined' && (!field.type || (field.type && field.type === 'select'))) { @@ -31,6 +40,21 @@ class ViewSettings { outputConfig[defaultViewFields[i]] = defaultField; } } + } else if (typeof field !== 'undefined' && field.type === 'image') { + let dirName = 'posts'; + + if (itemData.type === 'tag') { + dirName = 'tags'; + } else if (itemData.type === 'author') { + dirName = 'authors'; + } + + if (defaultField) { + let imagePath = '/media/' + dirName + '/defaults/' + defaultField; + outputConfig[defaultViewFields[i]] = rendererInstance.siteConfig.domain + imagePath; + } else { + outputConfig[defaultViewFields[i]] = false; + } } else { if (defaultField === '0') { defaultField = 0; @@ -50,34 +74,49 @@ class ViewSettings { for(let i = 0; i < viewFields.length; i++) { let field = viewSettings[viewFields[i]]; - if(typeof field !== 'undefined' && field.value) { - if(field.value !== "") { - outputConfig[viewFields[i]] = field.value; + if (field.type === 'image') { + if (field.value) { + let dirName = 'posts'; + + if (itemData.type === 'tag') { + dirName = 'tags'; + } else if (itemData.type === 'author') { + dirName = 'authors'; + } + + let imagePath = '/media/' + dirName + '/' + itemData.id + '/' + field.value; + outputConfig[defaultViewFields[i]] = rendererInstance.siteConfig.domain + imagePath; + } + } else { + if(typeof field !== 'undefined' && field.value) { + if(field.value !== "") { + outputConfig[viewFields[i]] = field.value; + } + } else if(typeof field === 'string' && field !== "") { + outputConfig[viewFields[i]] = field; } - } else if(typeof field === 'string' && field !== "") { - outputConfig[viewFields[i]] = field; - } - if((field.type && field.type === 'select') || !field.type) { - if( - field === 0 || - field === '0' || - field.value === 0 || - field.value === '0' - ) { - outputConfig[viewFields[i]] = false; - } else if( - field === 1 || - field === '1' || - field.value === 1 || - field.value === '1' - ) { - outputConfig[viewFields[i]] = true; - } else { - if (typeof field.value !== 'undefined' && field.value !== '') { - outputConfig[viewFields[i]] = JSON.stringify(field.value).replace(/"/g, ''); - } else if (typeof field !== 'object' && field !== '') { - outputConfig[viewFields[i]] = JSON.stringify(field).replace(/"/g, ''); + if((field.type && field.type === 'select') || !field.type) { + if( + field === 0 || + field === '0' || + field.value === 0 || + field.value === '0' + ) { + outputConfig[viewFields[i]] = false; + } else if( + field === 1 || + field === '1' || + field.value === 1 || + field.value === '1' + ) { + outputConfig[viewFields[i]] = true; + } else { + if (typeof field.value !== 'undefined' && field.value !== '') { + outputConfig[viewFields[i]] = JSON.stringify(field.value).replace(/"/g, ''); + } else if (typeof field !== 'object' && field !== '') { + outputConfig[viewFields[i]] = JSON.stringify(field).replace(/"/g, ''); + } } } } diff --git a/app/back-end/modules/render-html/items/author.js b/app/back-end/modules/render-html/items/author.js index 5aefa860e..7acbbfa14 100644 --- a/app/back-end/modules/render-html/items/author.js +++ b/app/back-end/modules/render-html/items/author.js @@ -87,6 +87,14 @@ class AuthorItem { this.renderer.siteConfig.advanced.urls.authorsPrefix + '/' + slug(this.authorData.username) + '/' + addIndexHtml; + + if (this.renderer.siteConfig.advanced.urls.postsPrefix && this.renderer.siteConfig.advanced.urls.authorsPrefixAfterPostsPrefix) { + this.authorData.url = this.renderer.siteConfig.domain + '/' + + this.renderer.siteConfig.advanced.urls.postsPrefix + '/' + + this.renderer.siteConfig.advanced.urls.authorsPrefix + '/' + + slug(this.authorData.username) + '/' + + addIndexHtml; + } } /** diff --git a/app/back-end/modules/render-html/items/featured-image.js b/app/back-end/modules/render-html/items/featured-image.js index 83114bc10..dbcf72688 100644 --- a/app/back-end/modules/render-html/items/featured-image.js +++ b/app/back-end/modules/render-html/items/featured-image.js @@ -8,7 +8,7 @@ const UtilsHelper = require('./../../../helpers/utils'); * Featured image item for the renderer */ class FeaturedImageItem { - constructor(image, rendererInstance, type = 'featuredImages') { + constructor(image, rendererInstance, type = 'featuredImages', cacheItemType = 'post') { this.image = image; this.itemID = parseInt(image.item_id, 10); this.renderer = rendererInstance; @@ -17,6 +17,7 @@ class FeaturedImageItem { this.imageData = {}; this.imageType = type; this.itemType = 'post'; + this.cacheItemType = cacheItemType; if (type === 'tagImages') { this.itemType = 'tag'; @@ -148,11 +149,11 @@ class FeaturedImageItem { */ storeData() { if (this.renderer.plugins.hasModifiers('featuredImageItemData')) { - this.imageData = this.renderer.plugins.runModifiers('featuredImageItemData', this.renderer, this.imageData, this.itemType); + this.imageData = this.renderer.plugins.runModifiers('featuredImageItemData', this.renderer, this.imageData, this.itemType, this.cacheItemType); } // Store tag data without references - this.renderer.cachedItems.featuredImages[this.itemType + 's'][this.itemID] = JSON.parse(JSON.stringify(this.imageData)); + this.renderer.cachedItems.featuredImages[this.cacheItemType + 's'][this.itemID] = JSON.parse(JSON.stringify(this.imageData)); } /** diff --git a/app/back-end/modules/render-html/items/page.js b/app/back-end/modules/render-html/items/page.js new file mode 100644 index 000000000..a72a30ba3 --- /dev/null +++ b/app/back-end/modules/render-html/items/page.js @@ -0,0 +1,158 @@ +const path = require('path'); +const ContentHelper = require('./../helpers/content'); + +/** + * Page item for the renderer + */ +class PageItem { + constructor(page, rendererInstance) { + this.page = page; + this.pageID = parseInt(page.id, 10); + this.renderer = rendererInstance; + this.db = this.renderer.db; + this.themeConfig = this.renderer.themeConfig; + this.siteConfig = this.renderer.siteConfig; + this.pageData = {}; + this.metaData = {}; + this.metaDescription = ''; + this.subpages = this.renderer.cachedItems.pagesStructure[page.id] || []; + + this.getMetaData(); + this.prepareData(); + this.storeData(); + } + + getMetaData () { + let metaDataQuery = this.db.prepare(`SELECT value FROM posts_additional_data WHERE post_id = @pageID AND key = '_core'`); + let metaData = metaDataQuery.get({ pageID: this.page.id}); + + if (metaData && metaData.value) { + this.metaData = JSON.parse(metaData.value); + } + + if (!this.metaData.editor) { + this.metaData.editor = 'tinymce'; + } + + if (this.metaData.metaDesc) { + this.metaDescription = this.metaData.metaDesc; + } + + if (this.metaDescription === '') { + this.metaDescription = this.siteConfig.advanced.metaDescription; + } + } + + prepareData() { + let pageURL = this.siteConfig.domain + '/' + this.page.slug + '.html'; + let preparedText = ContentHelper.prepareContent(this.page.id, this.page.text, this.siteConfig.domain, this.themeConfig, this.renderer, this.metaData.editor); + let preparedExcerpt = ContentHelper.prepareExcerpt(this.themeConfig.config.excerptLength, preparedText); + preparedExcerpt = ContentHelper.setInternalLinks(preparedExcerpt, this.renderer); + let hasCustomExcerpt = false; + let readmoreMatches = preparedText.match(/\/gmi); + + if (readmoreMatches && readmoreMatches.length) { + hasCustomExcerpt = true; + + // Detect if hide of the custom excerpt is enabled + if (this.renderer.siteConfig.advanced.pageUseTextWithoutCustomExcerpt) { + preparedText = preparedText.split(/\/gmi); + preparedText = preparedText[1]; + } else { + preparedText = preparedText.replace(/\/gmi, ''); + } + } + + if (this.siteConfig.advanced.urls.cleanUrls) { + let parentItems = this.renderer.cachedItems.pagesStructureHierarchy[this.page.id]; + let pageSlug = this.page.slug; + + if (this.renderer.siteConfig.advanced.urls.cleanUrls && parentItems && parentItems.length) { + let slugs = []; + + for (let i = 0; i < parentItems.length; i++) { + if (this.renderer.cachedItems.pages[parentItems[i]]) { + slugs.push(this.renderer.cachedItems.pages[parentItems[i]].slug); + } + } + + slugs.push(this.page.slug); + pageSlug = slugs.join('/'); + } + + pageURL = this.siteConfig.domain + '/' + pageSlug + '/'; + + if (this.renderer.previewMode || this.renderer.siteConfig.advanced.urls.addIndex) { + pageURL += 'index.html'; + } + } + + this.pageData = { + id: this.page.id, + title: this.page.title, + author: this.renderer.cachedItems.authors[this.page.authors], + slug: this.page.slug, + url: pageURL, + text: preparedText, + excerpt: preparedExcerpt, + createdAt: this.page.created_at, + modifiedAt: this.page.modified_at, + status: this.page.status, + hasGallery: preparedText.indexOf('class="gallery') !== -1, + template: this.page.template, + hasCustomExcerpt: hasCustomExcerpt, + editor: this.metaData.editor || 'tinymce', + metaDescription: this.metaDescription, + subpages: this.subpages + }; + + if (this.siteConfig.advanced.usePageAsFrontpage && this.siteConfig.advanced.pageAsFrontpage === this.page.id) { + this.pageData.url = this.siteConfig.domain + '/'; + + if (this.renderer.previewMode || this.renderer.siteConfig.advanced.urls.addIndex) { + this.pageData.url += 'index.html'; + } + } + + if (this.pageData.template === '*') { + this.pageData.template = this.themeConfig.defaultTemplates.page; + } + + this.pageData.featuredImage = {}; + + if (this.renderer.cachedItems.featuredImages.pages[this.pageData.id]) { + this.pageData.featuredImage = this.renderer.cachedItems.featuredImages.pages[this.pageData.id]; + } + + if (this.renderer.plugins.hasModifiers('pageTitle')) { + this.pageData.title = this.renderer.plugins.runModifiers('pageTitle', this.renderer, this.pageData.title, { pageData: this.pageData }); + } + + if (this.renderer.plugins.hasModifiers('pageText')) { + this.pageData.text = this.renderer.plugins.runModifiers('pageText', this.renderer, this.pageData.text, { pageData: this.pageData }); + } + + if (this.renderer.plugins.hasModifiers('pageExcerpt')) { + this.pageData.excerpt = this.renderer.plugins.runModifiers('pageExcerpt', this.renderer, this.pageData.excerpt, { pageData: this.pageData }); + } + } + + storeData() { + if (this.renderer.plugins.hasModifiers('pageItemData')) { + this.pageData = this.renderer.plugins.runModifiers('pageItemData', this.renderer, this.pageData); + } + + this.renderer.cachedItems.pages[this.pageID] = JSON.parse(JSON.stringify(this.pageData)); + } + + setInternalLinks() { + let pageText = this.renderer.cachedItems.pages[this.pageID].text; + this.renderer.cachedItems.pages[this.pageID].text = ContentHelper.setInternalLinks(pageText, this.renderer); + } + + setPageViewConfig(config) { + this.renderer.cachedItems.pages[this.pageID].pageViewConfig = config; + } +} + +module.exports = PageItem; diff --git a/app/back-end/modules/render-html/items/post.js b/app/back-end/modules/render-html/items/post.js index 721de64ea..2145d256f 100644 --- a/app/back-end/modules/render-html/items/post.js +++ b/app/back-end/modules/render-html/items/post.js @@ -44,6 +44,11 @@ class PostItem { prepareData() { let postURL = this.siteConfig.domain + '/' + this.post.slug + '.html'; + + if (this.siteConfig.advanced.urls.postsPrefix) { + postURL = this.siteConfig.domain + '/' + this.siteConfig.advanced.urls.postsPrefix + '/' + this.post.slug + '.html'; + } + let preparedText = ContentHelper.prepareContent(this.post.id, this.post.text, this.siteConfig.domain, this.themeConfig, this.renderer, this.metaData.editor); let preparedExcerpt = ContentHelper.prepareExcerpt(this.themeConfig.config.excerptLength, preparedText); preparedExcerpt = ContentHelper.setInternalLinks(preparedExcerpt, this.renderer); @@ -65,6 +70,10 @@ class PostItem { if (this.siteConfig.advanced.urls.cleanUrls) { postURL = this.siteConfig.domain + '/' + this.post.slug + '/'; + if (this.siteConfig.advanced.urls.postsPrefix) { + postURL = this.siteConfig.domain + '/' + this.siteConfig.advanced.urls.postsPrefix + '/' + this.post.slug + '/'; + } + if (this.renderer.previewMode || this.renderer.siteConfig.advanced.urls.addIndex) { postURL += 'index.html'; } diff --git a/app/back-end/modules/render-html/renderer-cache.js b/app/back-end/modules/render-html/renderer-cache.js index 9ea4c8259..8e28ae38e 100644 --- a/app/back-end/modules/render-html/renderer-cache.js +++ b/app/back-end/modules/render-html/renderer-cache.js @@ -1,6 +1,10 @@ +const fs = require('fs'); +const path = require('path'); +const Page = require('./items/page'); const Post = require('./items/post'); const Author = require('./items/author'); const Tag = require('./items/tag'); +const ContentHelper = require('./helpers/content.js'); const FeaturedImage = require('./items/featured-image'); const ViewSettingsHelper = require('./helpers/view-settings.js'); const RendererHelpers = require('./helpers/helpers.js'); @@ -31,11 +35,17 @@ class RendererCache { // We need author posts count before storing tags this.getAuthorPostCounts(); this.getAuthors(); - // Set post-related items + // Set post/page-related items this.getFeaturedPostImages(); + this.getFeaturedPageImages(); this.getPostTags(); - // At the end we get posts as it uses other cached items - this.getPosts(); + // Get pages structure + this.getPagesStructure(); + // At the end we get posts and pages as it uses other cached items + let posts = this.getPosts(); + let pages = this.getPages(); + // Now we can set internal links + this.setInternalLinks(posts, pages); } /** @@ -76,7 +86,10 @@ class RendererCache { let tagViewConfigObject = JSON.parse(JSON.stringify(this.themeConfig.tagConfig)); tags = tags.map(tag => { - let tagViewConfig = this.getViewSettings(tagViewConfigObject, tag); + let tagViewConfig = this.getViewSettings(tagViewConfigObject, tag, { + type: 'tag', + id: tag.id + }); let newTag = new Tag(tag, this.renderer, mainTagIDs); newTag.setTagViewConfig(tagViewConfig); return newTag; @@ -158,7 +171,7 @@ class RendererCache { caption: additionalData.featuredImageCaption, credits: additionalData.featuredImageCredits }) - }, this.renderer, 'authorImages'); + }, this.renderer, 'authorImages', 'author'); } } console.timeEnd('FEATURED AUTHOR IMAGES - STORE'); @@ -186,7 +199,10 @@ class RendererCache { let authorViewConfigObject = JSON.parse(JSON.stringify(this.themeConfig.authorConfig)); authors = authors.map(author => { - let authorViewConfig = this.getViewSettings(authorViewConfigObject, author); + let authorViewConfig = this.getViewSettings(authorViewConfigObject, author, { + type: 'author', + id: author.id + }); let newAuthor = new Author(author, this.renderer); newAuthor.setAuthorViewConfig(authorViewConfig); return newAuthor; @@ -220,7 +236,8 @@ class RendererCache { WHERE p.status LIKE '%published%' AND p.status NOT LIKE '%hidden%' AND - p.status NOT LIKE '%trashed%' + p.status NOT LIKE '%trashed%' AND + p.status NOT LIKE '%is-page%' ${includeFeaturedPosts} GROUP BY a.id @@ -235,7 +252,7 @@ class RendererCache { } /** - * Retrieves featured images data + * Retrieves featured images data for posts */ getFeaturedPostImages() { console.time('FEATURED POST IMAGES - QUERY'); @@ -251,16 +268,51 @@ class RendererCache { posts_images as pi ON p.featured_image_id = pi.id + WHERE + p.status LIKE '%published%' AND + p.status NOT LIKE '%trashed%' AND + p.status NOT LIKE '%is-page%' ORDER BY pi.id DESC `).all(); console.timeEnd('FEATURED POST IMAGES - QUERY'); console.time('FEATURED POST IMAGES - STORE'); - featuredImages.map(image => new FeaturedImage(image, this.renderer, 'featuredImages')); + featuredImages.map(image => new FeaturedImage(image, this.renderer, 'featuredImages', 'post')); console.timeEnd('FEATURED POST IMAGES - STORE'); } + /** + * Retrieves featured images data for pages + */ + getFeaturedPageImages() { + console.time('FEATURED PAGE IMAGES - QUERY'); + let featuredImages = this.db.prepare(` + SELECT + pi.id AS id, + pi.post_id AS item_id, + pi.url AS url, + pi.additional_data AS additional_data + FROM + posts as p + LEFT JOIN + posts_images as pi + ON + p.featured_image_id = pi.id + WHERE + p.status LIKE '%published%' AND + p.status NOT LIKE '%trashed%' AND + p.status LIKE '%is-page%' + ORDER BY + pi.id DESC + `).all(); + console.timeEnd('FEATURED PAGE IMAGES - QUERY'); + + console.time('FEATURED PAGE IMAGES - STORE'); + featuredImages.map(image => new FeaturedImage(image, this.renderer, 'featuredImages', 'page')); + console.timeEnd('FEATURED PAGE IMAGES - STORE'); + } + /** * Retrieves tags featured images data */ @@ -295,7 +347,7 @@ class RendererCache { caption: additionalData.featuredImageCaption, credits: additionalData.featuredImageCredits }) - }, this.renderer, 'tagImages'); + }, this.renderer, 'tagImages', 'tag'); } } console.timeEnd('FEATURED TAG IMAGES - STORE'); @@ -334,6 +386,54 @@ class RendererCache { console.timeEnd('POST TAGS - STORE'); } + /** + * Prepare two hierarchies + */ + getPagesStructure () { + // Pages structure + let pagesConfigPath = path.join(this.renderer.inputDir, 'config', 'pages.config.json'); + + if (fs.existsSync(pagesConfigPath)) { + let pagesStructure = JSON.parse(fs.readFileSync(pagesConfigPath)); + let pagesStructureForHierarchy = JSON.parse(JSON.stringify(pagesStructure)); + let flatPagesStructure = {}; + let pagesStack = [...pagesStructure]; + let hierarchyStructure = {}; + + while (pagesStack.length > 0) { + let page = pagesStack.pop(); + + if (!flatPagesStructure[page.id]) { + flatPagesStructure[page.id] = []; + } + + page.subpages.forEach(subpage => { + flatPagesStructure[page.id].push(subpage.id); + pagesStack.push(subpage); + }); + } + + let hierarchyTraverse = (node, path = []) => { + node.subpages.forEach(subpage => hierarchyTraverse(subpage, [node.id, ...path])); + hierarchyStructure[node.id] = path.reverse(); + }; + + pagesStructureForHierarchy.forEach(page => hierarchyTraverse(page)); + + // Flat hierarchy structure of parents when clean URLs are disabled + if (!this.renderer.siteConfig.advanced.urls.cleanUrls) { + let IDs = Object.keys(hierarchyStructure); + + for (let i = 0; i < IDs.length; i++) { + hierarchyStructure[IDs[i]] = []; + } + } + + this.renderer.cachedItems.pagesStructure = flatPagesStructure; + this.renderer.cachedItems.pagesStructureHierarchy = hierarchyStructure; + } + } + /** * Retrieves posts data */ @@ -346,7 +446,8 @@ class RendererCache { posts WHERE status NOT LIKE '%trashed%' AND - status NOT LIKE '%draft%' + status NOT LIKE '%draft%' AND + status NOT LIKE '%is-page%' ORDER BY id ASC; `).all(); @@ -362,10 +463,41 @@ class RendererCache { return newPost; }); - posts.map(post => { - post.setInternalLinks(); - }); console.timeEnd('POSTS - STORE'); + return posts; + } + + /** + * Retrieves pages data + */ + getPages() { + console.time('PAGES - QUERY'); + let pages = this.db.prepare(` + SELECT + * + FROM + posts + WHERE + status LIKE '%is-page%' AND + status NOT LIKE '%trashed%' AND + status NOT LIKE '%draft%' + ORDER BY + id ASC; + `).all(); + console.timeEnd('PAGES - QUERY'); + + console.time('PAGES - STORE'); + let pageViewConfigObject = JSON.parse(JSON.stringify(this.themeConfig.pageConfig)); + + pages = pages.map(page => { + let pageViewConfig = this.getPageViewSettings(pageViewConfigObject, page.id); + let newPage = new Page(page, this.renderer); + newPage.setPageViewConfig(pageViewConfig); + return newPage; + }); + + console.timeEnd('PAGES - STORE'); + return pages; } /** @@ -397,7 +529,45 @@ class RendererCache { postViewSettings = JSON.parse(postViewData.value); } - return ViewSettingsHelper.override(postViewSettings, defaultPostViewConfig); + return ViewSettingsHelper.override(postViewSettings, defaultPostViewConfig, { + type: 'post', + id: postID + }, this.renderer); + } + + /** + * Retrieve page view settings + * + * @param defaultPageViewConfig + * @param pageID + * + * @returns {object} + */ + getPageViewSettings(defaultPageViewConfig, pageID) { + let pageViewData = false; + let pageViewSettings = {}; + + pageViewData = this.db.prepare(` + SELECT + value + FROM + posts_additional_data + WHERE + post_id = @id + AND + key = 'pageViewSettings' + `).get({ + id: pageID + }); + + if (pageViewData && pageViewData.value) { + pageViewSettings = JSON.parse(pageViewData.value); + } + + return ViewSettingsHelper.override(pageViewSettings, defaultPageViewConfig, { + type: 'page', + id: pageID + }, this.renderer); } /** @@ -408,7 +578,7 @@ class RendererCache { * * @returns {object} */ - getViewSettings(defaultViewConfig, itemData) { + getViewSettings(defaultViewConfig, itemData, itemConfig) { let viewSettings = {}; if (itemData && itemData.additional_data) { @@ -423,7 +593,51 @@ class RendererCache { } } - return ViewSettingsHelper.override(viewSettings, defaultViewConfig); + return ViewSettingsHelper.override(viewSettings, defaultViewConfig, itemConfig, this.renderer); + } + + /** + * Set internal links for tags and authors + */ + setInternalLinks (posts, pages) { + posts.map(post => { + post.setInternalLinks(); + }); + + pages.map(page => { + page.setInternalLinks(); + }); + + let authorIDs = Object.keys(this.renderer.cachedItems.authors); + let tagIDs = Object.keys(this.renderer.cachedItems.tags); + + for (let i = 0; i < authorIDs.length; i++) { + let authorID = authorIDs[i]; + + if (typeof this.renderer.cachedItems.authors[authorID].description === 'string') { + let description = this.renderer.cachedItems.authors[authorID].description; + this.renderer.cachedItems.authors[authorID].description = ContentHelper.setInternalLinks(description, this.renderer); + } + + if (typeof this.renderer.cachedItems.authors[authorID].authorViewConfig === 'object') { + let viewConfig = this.renderer.cachedItems.authors[authorID].authorViewConfig; + this.renderer.cachedItems.authors[authorID].authorViewConfig = JSON.parse(ContentHelper.setInternalLinks(JSON.stringify(viewConfig), this.renderer)); + } + } + + for (let i = 0; i < tagIDs.length; i++) { + let tagID = tagIDs[i]; + + if (typeof this.renderer.cachedItems.tags[tagID].description === 'string') { + let description = this.renderer.cachedItems.tags[tagID].description; + this.renderer.cachedItems.tags[tagID].description = ContentHelper.setInternalLinks(description, this.renderer); + } + + if (typeof this.renderer.cachedItems.tags[tagID].tagViewConfig === 'object') { + let viewConfig = this.renderer.cachedItems.tags[tagID].tagViewConfig; + this.renderer.cachedItems.tags[tagID].tagViewConfig = JSON.parse(ContentHelper.setInternalLinks(JSON.stringify(viewConfig), this.renderer)); + } + } } } diff --git a/app/back-end/modules/render-html/renderer-context.js b/app/back-end/modules/render-html/renderer-context.js index 67c52a6dc..771e7d8e6 100644 --- a/app/back-end/modules/render-html/renderer-context.js +++ b/app/back-end/modules/render-html/renderer-context.js @@ -2,6 +2,7 @@ const fs = require('fs'); const path = require('path'); const slug = require('./../../helpers/slug'); +const ContentHelper = require('./helpers/content'); const URLHelper = require('./helpers/url'); const normalizePath = require('normalize-path'); const Plugins = require('./../../plugins.js'); @@ -55,6 +56,7 @@ class RendererContext { // Retrieve necessary data let tagsData = this.getTagsMenuData(); let postsData = this.getPostsMenuData(); + let pagesData = this.getPagesMenuData(); // Menu config let menuConfigPath = path.join(this.inputDir, 'config', 'menu.config.json'); @@ -76,7 +78,7 @@ class RendererContext { } if (positions[0] === '') { - menuData[i].items = this.prepareMenuItems(menuData[i].items, tagsData, postsData); + menuData[i].items = this.prepareMenuItems(menuData[i].items, tagsData, postsData, pagesData); menus.unassigned[slug(menuData[i].name)] = menuData[i]; } else { for (let j = 0; j < positions.length; j++) { @@ -102,7 +104,7 @@ class RendererContext { } let positionMenuData = JSON.parse(JSON.stringify(menuData[i])); - positionMenuData.items = this.prepareMenuItems(positionMenuData.items, tagsData, postsData, 2, maxLevel + 1); + positionMenuData.items = this.prepareMenuItems(positionMenuData.items, tagsData, postsData, pagesData, 2, maxLevel + 1); menus.assigned[position] = positionMenuData; } } @@ -119,7 +121,7 @@ class RendererContext { return menus; } - prepareMenuItems(items, tagsData, postsData, level = 2, maxLevel = false) { + prepareMenuItems(items, tagsData, postsData, pagesData, level = 2, maxLevel = false) { // When max level is exceed - return items if (maxLevel && maxLevel !== -1 && maxLevel < level) { return []; @@ -127,6 +129,7 @@ class RendererContext { for (let i = 0; i < items.length; i++) { items[i].level = level; + items[i].linkID = items[i].link; if (items[i].type === 'post') { let foundedPost = postsData.filter(post => post.id == items[i].link); @@ -138,10 +141,20 @@ class RendererContext { } } + if (items[i].type === 'page') { + let foundedPage = pagesData.filter(page => page.id == items[i].link); + + if (foundedPage.length && foundedPage[0].status.indexOf('trashed') === -1) { + items[i].link = foundedPage[0].slug; + } else { + items[i] = false; + } + } + if (items[i].type === 'tag') { let foundedTag = tagsData.filter(tag => tag.id == items[i].link); - if(foundedTag.length) { + if (foundedTag.length) { items[i].link = foundedTag[0].slug; } else { items[i] = false; @@ -153,7 +166,7 @@ class RendererContext { } if (items[i] && !items[i].isHidden && items[i].items.length > 0) { - items[i].items = this.prepareMenuItems(items[i].items, tagsData, postsData, level + 1, maxLevel); + items[i].items = this.prepareMenuItems(items[i].items, tagsData, postsData, pagesData, level + 1, maxLevel); } } @@ -177,7 +190,7 @@ class RendererContext { return tags; } - getPostsMenuData() { + getPostsMenuData () { // Retrieve all tags let posts = this.db.prepare(` SELECT @@ -186,6 +199,8 @@ class RendererContext { p.status AS status FROM posts AS p + WHERE + p.status NOT LIKE '%is-page%' ORDER BY id ASC `).all(); @@ -193,6 +208,24 @@ class RendererContext { return posts; } + getPagesMenuData () { + // Retrieve all tags + let pages = this.db.prepare(` + SELECT + p.id AS id, + p.slug AS slug, + p.status AS status + FROM + posts AS p + WHERE + p.status LIKE '%is-page%' + ORDER BY + id ASC + `).all(); + + return pages; + } + getAllTags() { // Retrieve post tags let tags = this.db.prepare(` @@ -324,7 +357,7 @@ class RendererContext { config: URLHelper.prepareSettingsImages(this.siteConfig.domain, { basic: JSON.parse(JSON.stringify(this.themeConfig.config)), site: JSON.parse(JSON.stringify(this.siteConfig.advanced)), - custom: JSON.parse(JSON.stringify(this.themeConfig.customConfig)) + custom: JSON.parse(ContentHelper.setInternalLinks(JSON.stringify(this.themeConfig.customConfig), this.renderer)) }), website: { url: fullURL, @@ -367,7 +400,11 @@ class RendererContext { }; if (context === 'post' && itemConfig) { - this.context.config.post = itemConfig; + this.context.config.post = JSON.parse(ContentHelper.setInternalLinks(JSON.stringify(itemConfig), this.renderer)); + } + + if (context === 'page' && itemConfig) { + this.context.config.page = JSON.parse(ContentHelper.setInternalLinks(JSON.stringify(itemConfig), this.renderer)); } this.renderer.globalContext = this.context; @@ -462,14 +499,26 @@ class RendererContext { posts WHERE status LIKE '%published%' AND - status NOT LIKE '%trashed%' + status NOT LIKE '%trashed%' AND + status NOT LIKE '%is-page%' ORDER BY ${postsOrdering} `).all(); + let pages = this.db.prepare(` + SELECT + id + FROM + posts + WHERE + status LIKE '%published%' AND + status NOT LIKE '%trashed%' AND + status LIKE '%is-page%' + `).all(); let tags = this.db.prepare(`SELECT id FROM tags`).all(); let authors = this.db.prepare(`SELECT id FROM authors`).all(); posts = posts.map(post => JSON.parse(JSON.stringify(this.renderer.cachedItems.posts[post.id]))); + pages = pages.map(page => JSON.parse(JSON.stringify(this.renderer.cachedItems.pages[page.id]))); tags = tags.map(tag => JSON.parse(JSON.stringify(this.renderer.cachedItems.tags[tag.id]))); authors = authors.map(author => JSON.parse(JSON.stringify(this.renderer.cachedItems.authors[author.id]))); @@ -481,10 +530,13 @@ class RendererContext { authors[i].posts = this.getContentStructureAuthorPosts(authors[i].id); } + let pagesStructure = this.renderer.cachedItems.pagesStructure; let finalContentStructure = { - posts: posts, - tags: tags, - authors: authors + pages, + pagesStructure, + posts, + tags, + authors }; if (this.renderer.plugins.hasModifiers('contentStructure')) { @@ -549,6 +601,7 @@ class RendererContext { status LIKE '%published%' AND status NOT LIKE '%hidden%' AND status NOT LIKE '%trashed%' AND + status NOT LIKE '%is-page%' AND authors LIKE @authorID ORDER BY ${postsOrdering} @@ -591,6 +644,7 @@ class RendererContext { status LIKE '%published%' AND status LIKE '%featured%' AND status NOT LIKE '%trashed%' AND + status NOT LIKE '%is-page%' AND status NOT LIKE '%hidden%' ORDER BY ${this.featuredPostsOrdering} @@ -609,6 +663,7 @@ class RendererContext { WHERE status LIKE '%published%' AND status LIKE '%hidden%' AND + status NOT LIKE '%is-page%' AND status NOT LIKE '%trashed%' ORDER BY ${this.hiddenPostsOrdering} @@ -617,42 +672,103 @@ class RendererContext { return results; } + getPages() { + let results = this.db.prepare(` + SELECT + id + FROM + posts + WHERE + status LIKE '%is-page%' AND + status NOT LIKE '%trashed%' + `).all(); + + return results; + } + getPageUrl (context, paginationData, itemSlug) { let pagePart = this.siteConfig.advanced.urls.pageName; + let blogBaseUrl = this.siteConfig.domain; + + if (this.siteConfig.advanced.urls.postsPrefix) { + blogBaseUrl = this.siteConfig.domain + '/' + this.siteConfig.advanced.urls.postsPrefix; + } if (context === 'index' || context === '404' || context === 'search') { if (!paginationData || paginationData.currentPage === 1) { - return this.siteConfig.domain + '/'; + return blogBaseUrl + '/'; } else { - return this.siteConfig.domain + '/' + pagePart + '/' + paginationData.currentPage + '/'; + if (this.siteConfig.advanced.urls.postsPrefix) { + return blogBaseUrl + '/' + this.siteConfig.advanced.urls.postsPrefix + '/' + pagePart + '/' + paginationData.currentPage + '/'; + } + + return blogBaseUrl + '/' + pagePart + '/' + paginationData.currentPage + '/'; } } else if (context === 'tags') { - if (this.siteConfig.advanced.urls.tagsPrefix !== '') { - return this.siteConfig.domain + '/' + this.siteConfig.advanced.urls.tagsPrefix + '/'; + if (this.siteConfig.advanced.urls.tagsPrefix !== '') { + if (this.siteConfig.advanced.urls.postsPrefix && this.siteConfig.advanced.urls.tagsPrefixAfterPostsPrefix) { + return blogBaseUrl + '/' + this.siteConfig.advanced.urls.postsPrefix + '/' + this.siteConfig.advanced.urls.tagsPrefix + '/'; + } + + return blogBaseUrl + '/' + this.siteConfig.advanced.urls.tagsPrefix + '/'; } else { - return this.siteConfig.domain + '/'; + return blogBaseUrl + '/'; } } else if (context === 'author') { if (!paginationData || paginationData.currentPage === 1) { + if (this.siteConfig.advanced.urls.postsPrefix && this.siteConfig.advanced.urls.authorsPrefixAfterPostsPrefix) { + return this.siteConfig.domain + '/' + this.siteConfig.advanced.urls.postsPrefix + '/' + this.siteConfig.advanced.urls.authorsPrefix + '/' + itemSlug + '/'; + } + return this.siteConfig.domain + '/' + this.siteConfig.advanced.urls.authorsPrefix + '/' + itemSlug + '/'; } else { + if (this.siteConfig.advanced.urls.postsPrefix && this.siteConfig.advanced.urls.authorsPrefixAfterPostsPrefix) { + return this.siteConfig.domain + '/' + this.siteConfig.advanced.urls.postsPrefix + '/' + this.siteConfig.advanced.urls.authorsPrefix + '/' + itemSlug + '/' + pagePart + '/' + paginationData.currentPage + '/'; + } + return this.siteConfig.domain + '/' + this.siteConfig.advanced.urls.authorsPrefix + '/' + itemSlug + '/' + pagePart + '/' + paginationData.currentPage + '/'; } } else if (context === 'tag') { if (!paginationData || paginationData.currentPage === 1) { - if (this.siteConfig.advanced.urls.tagsPrefix !== '') { - return this.siteConfig.domain + '/' + this.siteConfig.advanced.urls.tagsPrefix + '/' + itemSlug + '/'; + if (this.siteConfig.advanced.urls.tagsPrefix !== '') { + if (this.siteConfig.advanced.urls.postsPrefix && this.siteConfig.advanced.urls.tagsPrefixAfterPostsPrefix) { + return blogBaseUrl + '/' + this.siteConfig.advanced.urls.postsPrefix + '/' + this.siteConfig.advanced.urls.tagsPrefix + '/' + itemSlug + '/'; + } + + return blogBaseUrl + '/' + this.siteConfig.advanced.urls.tagsPrefix + '/' + itemSlug + '/'; } else { - return this.siteConfig.domain + '/' + itemSlug + '/'; + if (this.siteConfig.advanced.urls.postsPrefix && this.siteConfig.advanced.urls.tagsPrefixAfterPostsPrefix) { + return blogBaseUrl + '/' + this.siteConfig.advanced.urls.postsPrefix + '/' + itemSlug + '/'; + } + + return blogBaseUrl + '/' + itemSlug + '/'; } } else { if (this.siteConfig.advanced.urls.tagsPrefix !== '') { - return this.siteConfig.domain + '/' + this.siteConfig.advanced.urls.tagsPrefix + '/' + itemSlug + '/' + pagePart + '/' + paginationData.currentPage + '/'; + if (this.siteConfig.advanced.urls.postsPrefix && this.siteConfig.advanced.urls.tagsPrefixAfterPostsPrefix) { + return blogBaseUrl + '/' + this.siteConfig.advanced.urls.postsPrefix + '/' + this.siteConfig.advanced.urls.tagsPrefix + '/' + itemSlug + '/' + pagePart + '/' + paginationData.currentPage + '/'; + } + + return blogBaseUrl + '/' + this.siteConfig.advanced.urls.tagsPrefix + '/' + itemSlug + '/' + pagePart + '/' + paginationData.currentPage + '/'; } else { - return this.siteConfig.domain + '/' + itemSlug + '/' + pagePart + '/' + paginationData.currentPage + '/'; + if (this.siteConfig.advanced.urls.postsPrefix && this.siteConfig.advanced.urls.tagsPrefixAfterPostsPrefix) { + return blogBaseUrl + '/' + this.siteConfig.advanced.urls.postsPrefix + '/' + itemSlug + '/' + pagePart + '/' + paginationData.currentPage + '/'; + } + + return blogBaseUrl + '/' + itemSlug + '/' + pagePart + '/' + paginationData.currentPage + '/'; } } } else if (context === 'post') { + if (!this.siteConfig.advanced.urls.cleanUrls) { + if (this.siteConfig.advanced.urls.postsPrefix) { + return blogBaseUrl + '/' + this.siteConfig.advanced.urls.postsPrefix + '/' + itemSlug + '.html'; + } + + return blogBaseUrl + '/' + itemSlug + '.html'; + } else { + return blogBaseUrl + '/' + this.siteConfig.advanced.urls.postsPrefix + '/' + itemSlug + '/'; + } + } else if (context === 'page') { if (!this.siteConfig.advanced.urls.cleanUrls) { return this.siteConfig.domain + '/' + itemSlug + '.html'; } else { @@ -686,7 +802,11 @@ class RendererContext { } getTagsUrl () { - if (this.siteConfig.advanced.urls.tagsPrefix !== '') { + if (this.siteConfig.advanced.urls.tagsPrefix !== '') { + if (this.siteConfig.advanced.urls.postsPrefix && this.siteConfig.advanced.urls.tagsPrefixAfterPostsPrefix) { + return this.siteConfig.domain + '/' + this.siteConfig.advanced.urls.postsPrefix + '/' + this.siteConfig.advanced.urls.tagsPrefix + '/'; + } + return this.siteConfig.domain + '/' + this.siteConfig.advanced.urls.tagsPrefix + '/'; } else { return this.siteConfig.domain + '/'; diff --git a/app/back-end/modules/render-html/renderer.js b/app/back-end/modules/render-html/renderer.js index 98d62b0e8..73ac2a531 100644 --- a/app/back-end/modules/render-html/renderer.js +++ b/app/back-end/modules/render-html/renderer.js @@ -16,7 +16,9 @@ const ViewSettingsHelper = require('./helpers/view-settings.js'); const Themes = require('../../themes.js'); const TemplateHelper = require('./helpers/template.js'); const RendererContext = require('./renderer-context.js'); +const RendererContextPage = require('./contexts/page.js'); const RendererContextPost = require('./contexts/post.js'); +const RendererContextPagePreview = require('./contexts/page-preview.js'); const RendererContextPostPreview = require('./contexts/post-preview.js'); const RendererContextTag = require('./contexts/tag.js'); const RendererContextTags = require('./contexts/tags.js'); @@ -59,12 +61,14 @@ class Renderer { user: false, theme: false }; + this.viewConfigStructure = {}; this.contentStructure = {}; this.commonData = { tags: [], mainTags: [], authors: [], menus: [], + pages: [], featuredPosts: { homepage: [], tag: [], @@ -73,6 +77,8 @@ class Renderer { hiddenPosts: [] }; this.cachedItems = { + pages: {}, + pagesStructure: {}, postTags: {}, posts: {}, tags: {}, @@ -82,6 +88,7 @@ class Renderer { authorsPostCounts: {}, featuredImages: { authors: {}, + pages: {}, posts: {}, tags: {} } @@ -99,7 +106,8 @@ class Renderer { async render(previewMode = false, previewLocation = '', mode = 'full') { this.previewMode = previewMode; this.previewLocation = previewLocation; - this.singlePageMode = mode === 'post'; + this.singlePageMode = mode === 'post' || mode === 'page'; + this.itemType = mode; this.homepageOnlyMode = mode === 'home'; this.tagOnlyMode = mode === 'tag'; this.authorOnlyMode = mode === 'author'; @@ -151,8 +159,12 @@ class Renderer { async renderSite() { try { if (this.singlePageMode) { - await this.renderPostPreview(); - } else if (this.homepageOnlyMode) { + if (this.itemType === 'post') { + await this.renderPostPreview(); + } else if (this.itemType === 'page') { + await this.renderPagePreview(); + } + } else if (this.homepageOnlyMode && !this.siteConfig.advanced.usePageAsFrontpage) { await this.renderHomepagePreview(); } else if (this.tagOnlyMode) { await this.renderTagPreview(); @@ -161,10 +173,10 @@ class Renderer { } else { await this.renderFullPreview(); } - } catch (err) { + } catch (e) { this.errorLog.push({ message: 'An error occurred during rendering process:', - desc: err.message + desc: e.message + "\n\n" + e.stack }); } @@ -221,30 +233,36 @@ class Renderer { * Creates website content */ async generateWWW() { - this.sendProgress(11, 'Generating frontpage'); - this.generateFrontpage(); - this.sendProgress(20, 'Generating posts'); - this.generatePosts(); - - if (RendererHelpers.getRendererOptionValue('createTagPages', this.themeConfig)) { - this.sendProgress(60, 'Generating tag pages'); - this.generateTags(); - this.generateTagsList(); - } + if ((this.homepageOnlyMode && !this.siteConfig.advanced.usePageAsFrontpage) || !this.homepageOnlyMode) { + this.sendProgress(11, 'Generating frontpage'); + this.generateFrontpage(); + this.sendProgress(20, 'Generating posts'); + this.generatePosts(); + this.sendProgress(50, 'Generating pages'); + this.generatePages(); + + if (RendererHelpers.getRendererOptionValue('createTagPages', this.themeConfig)) { + this.sendProgress(60, 'Generating tag pages'); + this.generateTags(); + this.generateTagsList(); + } - if (RendererHelpers.getRendererOptionValue('createAuthorPages', this.themeConfig)) { - this.sendProgress(70, 'Generating author pages'); - this.generateAuthors(); - } + if (RendererHelpers.getRendererOptionValue('createAuthorPages', this.themeConfig)) { + this.sendProgress(70, 'Generating author pages'); + this.generateAuthors(); + } - this.sendProgress(75, 'Generating other pages'); + this.sendProgress(75, 'Generating other pages'); - if (RendererHelpers.getRendererOptionValue('create404page', this.themeConfig)) { - this.generate404s(); - } + if (RendererHelpers.getRendererOptionValue('create404page', this.themeConfig)) { + this.generate404s(); + } - if (RendererHelpers.getRendererOptionValue('createSearchPage', this.themeConfig)) { - this.generateSearch(); + if (RendererHelpers.getRendererOptionValue('createSearchPage', this.themeConfig)) { + this.generateSearch(); + } + } else { + this.generatePages(); } if (!this.siteConfig.deployment.relativeUrls) { @@ -279,6 +297,8 @@ class Renderer { if (type === 'post') { this.generatePost(); + } else if (type === 'page') { + this.generatePage(); } else if (type === 'frontpage') { this.generateFrontpage(); } else if (type === 'tag') { @@ -298,7 +318,21 @@ class Renderer { await FilesHelper.copyAssetsFiles(this.themeDir, this.outputDir, this.themeConfig); FilesHelper.copyDynamicAssetsFiles(this.themeDir, this.outputDir, this.themeConfig); - FilesHelper.copyMediaFiles(this.inputDir, this.outputDir, [this.itemID]); + await FilesHelper.copyMediaFiles(this.inputDir, this.outputDir, [this.itemID], []); + FilesHelper.copyPluginFiles(this.inputDir, this.outputDir, this.pluginsDir); + + this.triggerEvent('afterRender'); + } + + /** + * Renders page preview + */ + async renderPagePreview () { + this.preparePreview('page'); + + await FilesHelper.copyAssetsFiles(this.themeDir, this.outputDir, this.themeConfig); + FilesHelper.copyDynamicAssetsFiles(this.themeDir, this.outputDir, this.themeConfig); + await FilesHelper.copyMediaFiles(this.inputDir, this.outputDir, [this.itemID], []); FilesHelper.copyPluginFiles(this.inputDir, this.outputDir, this.pluginsDir); this.triggerEvent('afterRender'); @@ -311,9 +345,10 @@ class Renderer { this.preparePreview('frontpage'); let postIDs = Object.keys(this.cachedItems.posts); + let pageIDs = Object.keys(this.cachedItems.pages); await FilesHelper.copyAssetsFiles(this.themeDir, this.outputDir, this.themeConfig); FilesHelper.copyDynamicAssetsFiles(this.themeDir, this.outputDir, this.themeConfig); - FilesHelper.copyMediaFiles(this.inputDir, this.outputDir, postIDs); + await FilesHelper.copyMediaFiles(this.inputDir, this.outputDir, postIDs, pageIDs); FilesHelper.copyPluginFiles(this.inputDir, this.outputDir, this.pluginsDir); this.triggerEvent('afterRender'); @@ -326,6 +361,7 @@ class Renderer { this.preparePreview('tag'); let postIDs = Object.keys(this.cachedItems.posts); + let pageIDs = Object.keys(this.cachedItems.pages); let postIDsToRender = []; for (let i = 0; i < postIDs.length; i++) { @@ -339,7 +375,7 @@ class Renderer { await FilesHelper.copyAssetsFiles(this.themeDir, this.outputDir, this.themeConfig); FilesHelper.copyDynamicAssetsFiles(this.themeDir, this.outputDir, this.themeConfig); - FilesHelper.copyMediaFiles(this.inputDir, this.outputDir, postIDsToRender); + await FilesHelper.copyMediaFiles(this.inputDir, this.outputDir, postIDsToRender, pageIDs); FilesHelper.copyPluginFiles(this.inputDir, this.outputDir, this.pluginsDir); this.triggerEvent('afterRender'); @@ -352,6 +388,7 @@ class Renderer { this.preparePreview('author'); let postIDs = Object.keys(this.cachedItems.posts); + let pageIDs = Object.keys(this.cachedItems.pages); let postIDsToRender = []; for (let i = 0; i < postIDs.length; i++) { @@ -365,7 +402,7 @@ class Renderer { await FilesHelper.copyAssetsFiles(this.themeDir, this.outputDir, this.themeConfig); FilesHelper.copyDynamicAssetsFiles(this.themeDir, this.outputDir, this.themeConfig); - FilesHelper.copyMediaFiles(this.inputDir, this.outputDir, postIDsToRender); + await FilesHelper.copyMediaFiles(this.inputDir, this.outputDir, postIDsToRender, pageIDs); FilesHelper.copyPluginFiles(this.inputDir, this.outputDir, this.pluginsDir); this.triggerEvent('afterRender'); @@ -597,35 +634,25 @@ class Renderer { let themeConfigPath = path.join(this.inputDir, 'config', 'theme.config.json'); let tempThemeConfig = Themes.loadThemeConfig(themeConfigPath, this.themeDir); this.themeConfig = JSON.parse(JSON.stringify(tempThemeConfig)); - this.themeConfig.config = {}; - this.themeConfig.customConfig = {}; - this.themeConfig.postConfig = {}; - this.themeConfig.tagConfig = {}; - this.themeConfig.authorConfig = {}; - - for (let i = 0; i < tempThemeConfig.config.length; i++) { - let key = tempThemeConfig.config[i].name; - this.themeConfig.config[key] = tempThemeConfig.config[i].value; - } - - for (let i = 0; i < tempThemeConfig.customConfig.length; i++) { - let key = tempThemeConfig.customConfig[i].name; - this.themeConfig.customConfig[key] = tempThemeConfig.customConfig[i].value; - } - - for (let i = 0; i < tempThemeConfig.postConfig.length; i++) { - let key = tempThemeConfig.postConfig[i].name; - this.themeConfig.postConfig[key] = tempThemeConfig.postConfig[i].value; - } - - for (let i = 0; i < tempThemeConfig.tagConfig.length; i++) { - let key = tempThemeConfig.tagConfig[i].name; - this.themeConfig.tagConfig[key] = tempThemeConfig.tagConfig[i].value; - } - - for (let i = 0; i < tempThemeConfig.authorConfig.length; i++) { - let key = tempThemeConfig.authorConfig[i].name; - this.themeConfig.authorConfig[key] = tempThemeConfig.authorConfig[i].value; + + let configNames = [ + 'config', + 'customConfig', + 'postConfig', + 'pageConfig', + 'tagConfig', + 'authorConfig' + ]; + + for (let configName of configNames) { + this.themeConfig[configName] = {}; + this.viewConfigStructure[configName] = {}; + + for (let i = 0; i < tempThemeConfig[configName].length; i++) { + let key = tempThemeConfig[configName][i].name; + this.themeConfig[configName][key] = tempThemeConfig[configName][i].value; + this.viewConfigStructure[configName][key] = { type: tempThemeConfig[configName][i].type }; + } } } @@ -666,7 +693,7 @@ class Renderer { } catch (e) { this.errorLog.push({ message: 'An error (1001) occurred during parsing ' + allPartials[i] + '.hbs partial file.', - desc: e.message + desc: e.message + "\n\n" + e.stack }); } } @@ -684,6 +711,13 @@ class Renderer { let compiledTemplate = this.compileTemplate(inputFile); if (!compiledTemplate) { + console.timeEnd('HOME'); + return false; + } + + // Don't render homepage and it's pagination if we use page as frontpage and posts prefix is empty + if (this.siteConfig.advanced.usePageAsFrontpage && !this.siteConfig.advanced.urls.postsPrefix) { + console.timeEnd('HOME'); return false; } @@ -712,7 +746,7 @@ class Renderer { } catch (e) { this.errorLog.push({ message: 'An error (1002) occurred during parsing ' + inputFile + ' file.', - desc: e.message + desc: e.message + "\n\n" + e.stack }); return; } @@ -721,7 +755,11 @@ class Renderer { output = this.plugins.runModifiers('htmlOutput', this, output, [this.globalContext, context]); } - this.templateHelper.saveOutputFile('index.html', output); + if (this.siteConfig.advanced.urls.postsPrefix === '' || !this.siteConfig.advanced.usePageAsFrontpage) { + this.templateHelper.saveOutputFile('index.html', output); + } else { + this.templateHelper.saveOutputFile(path.join(this.siteConfig.advanced.urls.postsPrefix, 'index.html'), output); + } } else { let addIndexHtml = this.previewMode || this.siteConfig.advanced.urls.addIndex; @@ -752,8 +790,8 @@ class Renderer { postsPerPage: postsPerPage, nextPage: nextPage, previousPage: previousPage, - nextPageUrl: URLHelper.createPaginationPermalink(this.siteConfig.domain, this.siteConfig.advanced.urls, nextPage, 'home', false, addIndexHtml), - previousPageUrl: URLHelper.createPaginationPermalink(this.siteConfig.domain, this.siteConfig.advanced.urls, previousPage, 'home', false, addIndexHtml) + nextPageUrl: URLHelper.createPaginationPermalink(this.siteConfig.domain, this.siteConfig.advanced, nextPage, 'home', false, addIndexHtml), + previousPageUrl: URLHelper.createPaginationPermalink(this.siteConfig.domain, this.siteConfig.advanced, previousPage, 'home', false, addIndexHtml) }; let additionalContexts = []; @@ -776,7 +814,11 @@ class Renderer { } if (offset === 0) { - this.templateHelper.saveOutputFile('index.html', output); + if (this.siteConfig.advanced.urls.postsPrefix === '' || !this.siteConfig.advanced.usePageAsFrontpage) { + this.templateHelper.saveOutputFile('index.html', output); + } else { + this.templateHelper.saveOutputFile(path.join(this.siteConfig.advanced.urls.postsPrefix, 'index.html'), output); + } } else { // We increase the current page number as we need to start URLs from page/2 this.templateHelper.saveOutputHomePaginationFile(currentPage, output); @@ -806,6 +848,7 @@ class Renderer { posts WHERE status LIKE '%published%' AND + status NOT LIKE '%is-page%' AND status NOT LIKE '%trashed%' ORDER BY id ASC @@ -934,7 +977,7 @@ class Renderer { } inputFile = inputFile.replace('.hbs', '') + (fileSlug === 'DEFAULT' ? '' : '-' + fileSlug) + '.hbs'; - let postConfig = this.overridePostViewSettings(JSON.parse(JSON.stringify(this.themeConfig.postConfig)), postID, true); + let postConfig = this.overrideItemViewSettings(JSON.parse(JSON.stringify(this.themeConfig.postConfig)), postID, 'post', true); this.globalContext = this.createGlobalContext('post', [], false, postSlug, postConfig, context); let output = this.renderTemplate(compiledTemplates[fileSlug], context, this.globalContext, inputFile); @@ -945,35 +988,228 @@ class Renderer { this.templateHelper.saveOutputFile(postSlug + '.html', output); } + /* + * Create page preview + */ + generatePage() { + let pageID = this.itemID; + let pageSlug = 'preview'; + let pageTemplate = this.postData.template; + let inputFile = 'page.hbs'; + + if (pageTemplate === '*') { + if (this.themeConfig.defaultTemplates.page) { + pageTemplate = this.themeConfig.defaultTemplates.page; + } else { + pageTemplate = ''; + } + } + + // Load templates + let compiledTemplates = {}; + compiledTemplates['DEFAULT'] = this.compileTemplate(inputFile); + + if(!compiledTemplates['DEFAULT']) { + return false; + } + + if(typeof pageTemplate === "string" && pageTemplate !== '') { + pageTemplate = [pageTemplate]; + } + + for (let i = 0; i < pageTemplate.length; i++) { + let fileSlug = pageTemplate[i]; + + // When we meet default template - skip the compilation process + if (fileSlug === '') { + continue; + } + + compiledTemplates[fileSlug] = this.compileTemplate('page-' + fileSlug + '.hbs'); + + if (!compiledTemplates[fileSlug]) { + return false; + } + } + + // Render page site + let contextGenerator = new RendererContextPagePreview(this); + let context = contextGenerator.getContext(pageID); + let fileSlug = 'DEFAULT'; + fileSlug = pageTemplate === '' ? 'DEFAULT' : pageTemplate; + + this.menuContext = ['page', pageSlug]; + + if(!compiledTemplates[fileSlug]) { + fileSlug = 'DEFAULT'; + } + + inputFile = inputFile.replace('.hbs', '') + (fileSlug === 'DEFAULT' ? '' : '-' + fileSlug) + '.hbs'; + let pageConfig = this.overrideItemViewSettings(JSON.parse(JSON.stringify(this.themeConfig.pageConfig)), pageID, 'page', true); + this.globalContext = this.createGlobalContext('page', [], false, pageSlug, pageConfig, context); + let output = this.renderTemplate(compiledTemplates[fileSlug], context, this.globalContext, inputFile); + + if (this.plugins.hasModifiers('htmlOutput')) { + output = this.plugins.runModifiers('htmlOutput', this, output, [this.globalContext, context]); + } + + this.templateHelper.saveOutputFile(pageSlug + '.html', output); + } + /* * Override post view settings with the settings of the posts */ - overridePostViewSettings(defaultPostViewConfig, postID, postPreview = false) { - let postViewData = false; - let postViewSettings = {}; + overrideItemViewSettings(defaultViewConfig, itemID, itemType, itemPreview = false) { + let itemViewData = false; + let itemViewSettings = {}; - if(postPreview) { - postViewSettings = this.postData.postViewSettings; + if (itemPreview) { + itemViewSettings = this.postData[itemType === 'post' ? 'postViewSettings' : 'pageViewSettings']; } else { - postViewData = this.db.prepare(` + itemViewData = this.db.prepare(` SELECT value FROM posts_additional_data WHERE - post_id = @postID + post_id = @itemID AND - key = 'postViewSettings' + key = @itemType `).get({ - postID: postID + itemID: itemID, + itemType: itemType + 'ViewSettings' + }); + + if (itemViewData && itemViewData.length) { + itemViewSettings = JSON.parse(itemViewData.value); + } + } + + return ViewSettingsHelper.override(itemViewSettings, defaultViewConfig, { + type: itemType, + id: itemID + }, this); + } + + /* + * Create page sites + */ + generatePages() { + if (!this.themeConfig.supportedFeatures || !this.themeConfig.supportedFeatures.pages) { + console.log('[i] Pages are not supported by the theme'); + return; + } + + console.time('PAGES'); + let pageIDs = []; + let pageSlugs = []; + let pageTemplates = []; + let inputFile = 'page.hbs'; + + // Get pages + let pageData; + + if (this.homepageOnlyMode && this.siteConfig.advanced.usePageAsFrontpage && this.siteConfig.advanced.pageAsFrontpage) { + pageData = this.db.prepare(` + SELECT + id, + slug, + template + FROM + posts + WHERE + status LIKE '%published%' AND + status LIKE '%is-page%' AND + status NOT LIKE '%trashed%' AND + id = ${parseInt(this.siteConfig.advanced.pageAsFrontpage, 10)} + ORDER BY + id ASC + `).all(); + } else { + pageData = this.db.prepare(` + SELECT + id, + slug, + template + FROM + posts + WHERE + status LIKE '%published%' AND + status LIKE '%is-page%' AND + status NOT LIKE '%trashed%' + ORDER BY + id ASC + `).all(); + } + + if (pageData && pageData.length) { + pageIDs = pageData.map(row => row.id); + pageSlugs = pageData.map(row => row.slug); + pageTemplates = pageData.map(row => { + if (row.template === '*') { + return this.themeConfig.defaultTemplates.page + } + + return row.template; }); + } else { + pageIDs = []; + pageSlugs = []; + pageTemplates = []; + } + + // Load templates + let compiledTemplates = {}; + compiledTemplates['DEFAULT'] = this.compileTemplate(inputFile); + + if (!compiledTemplates['DEFAULT']) { + return false; + } + + for (let i = 0; i < pageTemplates.length; i++) { + let fileSlug = pageTemplates[i]; - if (postViewData && postViewData.length) { - postViewSettings = JSON.parse(postViewData.value); + // When we meet default template - skip the compilation process + if (fileSlug === '' || !this.themeConfig.pageTemplates[fileSlug]) { + continue; + } + + compiledTemplates[fileSlug] = this.compileTemplate('page-' + fileSlug + '.hbs'); + + if (!compiledTemplates[fileSlug]) { + return false; } } - return ViewSettingsHelper.override(postViewSettings, defaultPostViewConfig); + // Create global context + let progressIncrease = 40 / pageIDs.length; + + // Render page sites + for (let i = 0; i < pageIDs.length; i++) { + let contextGenerator = new RendererContextPage(this); + let context = contextGenerator.getContext(pageIDs[i]); + let fileSlug = 'DEFAULT'; + fileSlug = pageTemplates[i] === '' ? 'DEFAULT' : pageTemplates[i]; + + this.menuContext = ['page', pageSlugs[i]]; + + if (!compiledTemplates[fileSlug]) { + fileSlug = 'DEFAULT'; + } + + inputFile = inputFile.replace('.hbs', '') + (fileSlug === 'DEFAULT' ? '' : '-' + fileSlug) + '.hbs'; + let pageViewConfig = this.cachedItems.pages[pageIDs[i]].pageViewConfig; + this.globalContext = this.createGlobalContext('page', [], false, pageSlugs[i], pageViewConfig, context); + let output = this.renderTemplate(compiledTemplates[fileSlug], context, this.globalContext, inputFile); + + if (this.plugins.hasModifiers('htmlOutput')) { + output = this.plugins.runModifiers('htmlOutput', this, output, [this.globalContext, context]); + } + + this.templateHelper.saveOutputPageFile(pageIDs[i], pageSlugs[i], output, this); + this.sendProgress(Math.ceil(20 + (progressIncrease * i)), 'Generating pages (' + (i + 1) + '/' + pageIDs.length + ')'); + } + console.timeEnd('PAGES'); } /* @@ -1168,6 +1404,10 @@ class Renderer { tagsContextInUrl = this.siteConfig.advanced.urls.tagsPrefix + '/' + tagSlug; } + if (this.siteConfig.advanced.urls.postsPrefix && this.siteConfig.advanced.urls.tagsPrefixAfterPostsPrefix) { + tagsContextInUrl = this.siteConfig.advanced.urls.postsPrefix + '/' + tagsContextInUrl; + } + let pagination = { context: tagsContextInUrl, pages: Array.from({length: totalPages}, (v, k) => k + 1), @@ -1177,8 +1417,8 @@ class Renderer { postsPerPage: postsPerPage, nextPage: nextPage, previousPage: previousPage, - nextPageUrl: URLHelper.createPaginationPermalink(this.siteConfig.domain, this.siteConfig.advanced.urls, nextPage, 'tag', tagSlug, addIndexHtml), - previousPageUrl: URLHelper.createPaginationPermalink(this.siteConfig.domain, this.siteConfig.advanced.urls, previousPage, 'tag', tagSlug, addIndexHtml) + nextPageUrl: URLHelper.createPaginationPermalink(this.siteConfig.domain, this.siteConfig.advanced, nextPage, 'tag', tagSlug, addIndexHtml), + previousPageUrl: URLHelper.createPaginationPermalink(this.siteConfig.domain, this.siteConfig.advanced, previousPage, 'tag', tagSlug, addIndexHtml) }; let additionalContexts = []; @@ -1239,10 +1479,16 @@ class Renderer { // Create directory for authors let authorsDirPath = path.join(this.outputDir, this.siteConfig.advanced.urls.authorsPrefix); - if (!UtilsHelper.dirExists(authorsDirPath)) { - fs.mkdirSync(authorsDirPath); + if (this.siteConfig.advanced.urls.authorsPrefixAfterPostsPrefix && this.siteConfig.advanced.urls.postsPrefix) { + authorsDirPath = path.join( + this.outputDir, + this.siteConfig.advanced.urls.postsPrefix, + this.siteConfig.advanced.urls.authorsPrefix + ); } + fs.ensureDirSync(authorsDirPath); + // Get authors let authorsIDs = []; let authorsUsernames = []; @@ -1388,6 +1634,10 @@ class Renderer { let previousPage = (currentPage > 1) ? currentPage - 1 : false; let authorsContextInUrl = this.siteConfig.advanced.urls.authorsPrefix + '/' + authorUsername; + if (this.siteConfig.advanced.urls.postsPrefix && this.siteConfig.advanced.urls.authorsPrefixAfterPostsPrefix) { + authorsContextInUrl = this.siteConfig.advanced.urls.postsPrefix + '/' + authorsContextInUrl; + } + let pagination = { context: authorsContextInUrl, pages: Array.from({length: totalPages}, (v, k) => k + 1), @@ -1397,8 +1647,8 @@ class Renderer { postsPerPage: postsPerPage, nextPage: nextPage, previousPage: previousPage, - nextPageUrl: URLHelper.createPaginationPermalink(this.siteConfig.domain, this.siteConfig.advanced.urls, nextPage, 'author', authorUsername, addIndexHtml), - previousPageUrl: URLHelper.createPaginationPermalink(this.siteConfig.domain, this.siteConfig.advanced.urls, previousPage, 'author', authorUsername, addIndexHtml) + nextPageUrl: URLHelper.createPaginationPermalink(this.siteConfig.domain, this.siteConfig.advanced, nextPage, 'author', authorUsername, addIndexHtml), + previousPageUrl: URLHelper.createPaginationPermalink(this.siteConfig.domain, this.siteConfig.advanced, previousPage, 'author', authorUsername, addIndexHtml) }; let additionalContexts = []; @@ -1561,13 +1811,14 @@ class Renderer { try { let generateOverride = UtilsHelper.requireWithNoCache(themeVariablesPath); let customConfig = JSON.parse(JSON.stringify(this.themeConfig.customConfig)); + let pageConfig = JSON.parse(JSON.stringify(this.themeConfig.pageConfig)); let postConfig = JSON.parse(JSON.stringify(this.themeConfig.postConfig)); let commonConfig = JSON.parse(JSON.stringify(this.themeConfig.config)); - return generateOverride(customConfig, postConfig, commonConfig); + return generateOverride(customConfig, postConfig, commonConfig, pageConfig); } catch(e) { this.errorLog.push({ message: 'An error (1003) occurred during preparing CSS theme variables.', - desc: e.message + desc: e.message + "\n\n" + e.stack }); } } @@ -1616,13 +1867,14 @@ class Renderer { try { let generateOverride = UtilsHelper.requireWithNoCache(overridePath); let customConfig = JSON.parse(JSON.stringify(this.themeConfig.customConfig)); + let pageConfig = JSON.parse(JSON.stringify(this.themeConfig.pageConfig)); let postConfig = JSON.parse(JSON.stringify(this.themeConfig.postConfig)); let commonConfig = JSON.parse(JSON.stringify(this.themeConfig.config)); - return generateOverride(customConfig, postConfig, commonConfig); + return generateOverride(customConfig, postConfig, commonConfig, pageConfig); } catch(e) { this.errorLog.push({ message: 'An error (1003) occurred during preparing CSS overrides.', - desc: e.message + desc: e.message + "\n\n" + e.stack }); } } @@ -1713,13 +1965,14 @@ class Renderer { async copyFiles() { console.time("FILES"); let postIDs = Object.keys(this.cachedItems.posts); + let pageIDs = Object.keys(this.cachedItems.pages); + FilesHelper.copyRootFiles(this.inputDir, this.outputDir); await FilesHelper.copyAssetsFiles(this.themeDir, this.outputDir, this.themeConfig); FilesHelper.copyDynamicAssetsFiles(this.themeDir, this.outputDir, this.themeConfig); - await FilesHelper.copyMediaFiles(this.inputDir, this.outputDir, postIDs); + await FilesHelper.copyMediaFiles(this.inputDir, this.outputDir, postIDs, pageIDs); await FilesHelper.copyPluginFiles(this.inputDir, this.outputDir, this.pluginsDir); await FilesHelper.removeEmptyDirectories(this.outputDir); - console.timeEnd("FILES"); } @@ -1727,6 +1980,8 @@ class Renderer { console.time("CONTENT DATA"); let globalContextGenerator = new RendererContext(this); this.cachedItems = { + pages: {}, + pagesStructure: {}, postTags: {}, posts: {}, tags: {}, @@ -1736,6 +1991,7 @@ class Renderer { authorsPostCounts: {}, featuredImages: { authors: {}, + pages: {}, posts: {}, tags: {} } @@ -1757,6 +2013,7 @@ class Renderer { tags: globalContextGenerator.getAllTags(), mainTags: globalContextGenerator.getAllMainTags(), authors: globalContextGenerator.getAuthors(), + pages: globalContextGenerator.getPages(), menus: menus, unassignedMenus: unassignedMenus, featuredPosts: { @@ -1796,7 +2053,7 @@ class Renderer { } catch(e) { this.errorLog.push({ message: 'An error (1001) occurred during parsing ' + inputFile + ' file.', - desc: e.message + desc: e.message + "\n\n" + e.stack }); return false; @@ -1815,7 +2072,7 @@ class Renderer { } catch(e) { this.errorLog.push({ message: 'An error (1002) occurred during parsing ' + inputFile + ' file.', - desc: e.message + desc: e.message + "\n\n" + e.stack }); return ''; diff --git a/app/back-end/page.js b/app/back-end/page.js new file mode 100644 index 000000000..0f4fc613d --- /dev/null +++ b/app/back-end/page.js @@ -0,0 +1,682 @@ +/* + * Page instance + */ + +const fs = require('fs-extra'); +const path = require('path'); +const slug = require('./helpers/slug'); +const Model = require('./model.js'); +const ImageHelper = require('./helpers/image.helper.js'); +const Pages = require('./pages.js'); +const Authors = require('./authors.js'); +const Themes = require('./themes.js'); +const Utils = require('./helpers/utils.js'); +const normalizePath = require('normalize-path'); + +class Page extends Model { + constructor(appInstance, pageData, storeMode = true) { + super(appInstance, pageData); + this.id = parseInt(pageData.id, 10); + this.pagesData = new Pages(appInstance, pageData); + this.authorsData = new Authors(appInstance, pageData); + this.storeMode = storeMode; + + if (pageData.title) { + this.title = pageData.title; + this.slug = slug(pageData.slug); + this.author = Number(pageData.author).toString(); + this.status = pageData.status; + this.creationDate = pageData.creationDate; + this.modificationDate = pageData.modificationDate; + this.template = pageData.template; + this.additionalData = pageData.additionalData; + this.pageViewSettings = pageData.pageViewSettings; + } + + // Separated for the case of use the cancel page event + this.text = pageData.text ? pageData.text : ''; + this.featuredImage = pageData.featuredImage ? pageData.featuredImage : ''; + this.featuredImageFilename = pageData.featuredImageFilename ? pageData.featuredImageFilename : ''; + this.featuredImageData = pageData.featuredImageData ? pageData.featuredImageData : false; + } + + /* + * Load page + */ + load() { + let results = { + "pages": [], + "featuredImage": '', + "mediaPath": path.join(this.siteDir, 'input', 'media', 'posts'), + "author": '' + }; + // Get page data + let pagesSqlQuery = `SELECT * FROM posts WHERE id = @id`; + results.pages = this.db.prepare(pagesSqlQuery).all({ id: this.id }); + + // Get author data + let authorSqlQuery = ` + SELECT + a.id AS id, + a.name AS name + FROM + authors AS a + LEFT JOIN + posts AS p + ON + p.authors = a.id + WHERE + p.id = @id + `; + + results.author = this.db.prepare(authorSqlQuery).all({ id: this.id }); + + // Get image data + let imageSqlQuery = ` + SELECT + pi.url AS url, + pi.additional_data AS additional_data + FROM + posts AS p + LEFT JOIN + posts_images AS pi + ON + p.featured_image_id = pi.id + WHERE + p.id = @id; + `; + results.featuredImage = this.db.prepare(imageSqlQuery).get({ id: this.id }); + + // Get the additional data + let additionalDataSqlQuery = ` + SELECT + * + FROM + posts_additional_data + WHERE + post_id = @id + AND + key = '_core' + `; + let additionalDataResult = this.db.prepare(additionalDataSqlQuery).get({ id: this.id }); + + if(additionalDataResult && additionalDataResult.value) { + results.additionalData = JSON.parse(additionalDataResult.value); + } else { + results.additionalData = {}; + } + // Get page view settings + let pageViewSqlQuery = ` + SELECT + * + FROM + posts_additional_data + WHERE + post_id = @id + AND + key = 'pageViewSettings' + `; + let pageViewResult = this.db.prepare(pageViewSqlQuery).get({ id: this.id }); + + if (pageViewResult && pageViewResult.value) { + results.pageViewSettings = JSON.parse(pageViewResult.value); + let pageViewSettingsKeys = Object.keys(results.pageViewSettings); + + for (let i = 0; i < pageViewSettingsKeys.length; i++) { + if (results.pageViewSettings[pageViewSettingsKeys[i]].type) { + results.pageViewSettings[pageViewSettingsKeys[i]] = results.pageViewSettings[pageViewSettingsKeys[i]].value; + } + } + } else { + results.pageViewSettings = {}; + } + + // Return all results + return results; + } + + /* + * Save page + */ + save() { + let sqlQuery = ''; + this.checkAndPrepareSlug(); + + if (this.id === 0) { + // Add page data + sqlQuery = this.db.prepare(`INSERT INTO posts VALUES(null, @title, @author, @slug, @text, 0, @creationDate, @modificationDate, @status, @template)`); + sqlQuery.run({ + title: this.title, + author: this.author, + slug: this.slug, + text: this.cleanUpContent(this.text), + creationDate: this.creationDate, + modificationDate: this.modificationDate, + status: this.status, + template: this.template + }); + } else { + // Update page data + sqlQuery = this.db.prepare(`UPDATE posts + SET + title = @title, + authors = @author, + slug = @slug, + text = @text, + status = @status, + created_at = @createdAt, + modified_at = @modifiedAt, + template = @template + WHERE + id = @id`); + sqlQuery.run({ + title: this.title, + author: this.author, + slug: this.slug, + text: this.cleanUpContent(this.text), + status: this.status, + createdAt: this.creationDate, + modifiedAt: this.modificationDate, + template: this.template, + id: this.id + }); + } + + // Get the newly added item ID if necessary + if (this.id === 0) { + this.id = this.db.prepare('SELECT last_insert_rowid() AS id').get().id; + + // Move images from the temp directory + let tempDirectoryExists = true; + let tempImagesDir = path.join(this.siteDir, 'input', 'media', 'posts', 'temp'); + + try { + fs.statSync(tempImagesDir).isDirectory(); + } catch (err) { + tempDirectoryExists = false; + } + + if (tempDirectoryExists) { + let finalImagesDir = path.join(this.siteDir, 'input', 'media', 'posts', (this.id).toString()); + fs.copySync(tempImagesDir, finalImagesDir); + fs.removeSync(tempImagesDir); + + // Update text + if(!this.text) { + this.text = ''; + } + + this.text = this.text.replace(/file:(\/){1,}/gmi, 'file:///'); + this.text = this.text.split(normalizePath(tempImagesDir)).join('#DOMAIN_NAME#'); + this.text = this.text.replace(/file:(\/){1,}\#DOMAIN_NAME\#/gmi, '#DOMAIN_NAME#'); + sqlQuery = this.db.prepare(`UPDATE posts + SET + text = @text + WHERE + id = @id`); + sqlQuery.run({ + text: this.text, + id: this.id + }); + } + } + + // Save images + if(this.featuredImage) { + let image = new ImageHelper(this); + image.save(); + } else if(this.id > 0) { + let image = new ImageHelper(this); + image.delete(); + } + + // Save additional data + this.saveAdditionalData(); + + // Save page view settings + this.savePageViewSettings(); + + // Remove unused images + this.checkAndCleanImages(); + + // Return new list of the pages + return { + pageID: this.id, + pages: this.pagesData.load(), + pagesAuthors: this.pagesData.loadAuthorsXRef(), + authors: this.authorsData.load() + }; + } + + /* + * Delete page + */ + delete() { + this.db.exec(`DELETE FROM posts WHERE id = ${parseInt(this.id, 10)}`); + this.db.exec(`DELETE FROM posts_tags WHERE post_id = ${parseInt(this.id, 10)}`); + this.db.exec(`DELETE FROM posts_images WHERE post_id = ${parseInt(this.id, 10)}`); + this.db.exec(`DELETE FROM posts_additional_data WHERE post_id = ${parseInt(this.id, 10)}`); + ImageHelper.deleteImagesDirectory(this.siteDir, 'posts', this.id); + + return true; + } + + /* + * Duplicate page + */ + duplicate() { + // Get the page data + let pageToDuplicate = this.db.prepare(`SELECT * FROM posts WHERE id = @id LIMIT 1`).get({ id: this.id }); + let pageImagesToDuplicate = this.db.prepare(`SELECT * FROM posts_images WHERE post_id = @id`).all({ id: this.id }); + let pageAdditionalDataToDuplicate = this.db.prepare(`SELECT * FROM posts_additional_data WHERE post_id = @id`).all({ id: this.id }); + + // Add duplicate page row + if (pageToDuplicate && pageToDuplicate.id) { + // Change title (suffix with " (copy)") + let copySuffix = 1; + let modifiedTitle = pageToDuplicate.title; + let pageWithTheSameSlug = false; + let modifiedSlug = pageToDuplicate.slug; + + if (modifiedTitle.substr(-7) !== ' (copy)') { + modifiedTitle += ' (copy)'; + } + + if (modifiedSlug.substr(-2).match(/-\d/)) { + modifiedSlug = modifiedSlug.substr(0, modifiedSlug.length - 2); + } + + do { + copySuffix++; + pageWithTheSameSlug = this.db.prepare(`SELECT id FROM posts WHERE slug = @slug LIMIT 1`).get({ slug: modifiedSlug + '-' + copySuffix }); + } while (pageWithTheSameSlug && pageWithTheSameSlug.id); + + modifiedSlug = modifiedSlug + '-' + copySuffix; + let pageStatus = pageToDuplicate.status; + + if (pageStatus.indexOf('draft') === -1) { + pageStatus = pageStatus.replace('published', 'draft'); + + if (pageStatus.indexOf('draft') === -1) { + pageStatus += ',draft'; + } + } + + let newCopyPageSqlQuery = this.db.prepare(`INSERT INTO posts VALUES(null, @title, @authors, @slug, @text, @featured_image_id, @created_at, @modified_at, @status, @template)`); + newCopyPageSqlQuery.run({ + title: modifiedTitle, + authors: pageToDuplicate.authors, + slug: modifiedSlug, + text: pageToDuplicate.text, + featured_image_id: pageToDuplicate.featured_image_id, + created_at: Date.now(), + modified_at: Date.now(), + status: pageStatus, + template: pageToDuplicate.template + }); + } else { + return false; + } + + // Get newly inserted page ID + let copiedPageId = this.db.prepare('SELECT last_insert_rowid() AS id').get().id; + + // Add posts_images row + if (pageImagesToDuplicate.length) { + let imagesCount = pageImagesToDuplicate.length; + + for (let i = 0; i < imagesCount; i++) { + let newCopyPageImagesSqlQuery = this.db.prepare(`INSERT INTO posts_images VALUES(NULL, @copied_page_id, @url, @title, @caption, @additional_data)`); + newCopyPageImagesSqlQuery.run({ + copied_page_id: copiedPageId, + url: pageImagesToDuplicate[i].url, + title: pageImagesToDuplicate[i].title, + caption: pageImagesToDuplicate[i].caption, + additional_data: pageImagesToDuplicate[i].additional_data + }); + } + + // Get newly inserted page ID + let copiedFeaturedImageId = this.db.prepare('SELECT last_insert_rowid() AS id').get().id; + + this.db.prepare(`UPDATE posts SET featured_image_id = @featuredImageID WHERE id = @pageID`).run({ + featuredImageID: copiedFeaturedImageId, + pageID: copiedPageId + }); + } + + // Add posts_additional_data + if (pageAdditionalDataToDuplicate.length) { + let additionalDataCount = pageAdditionalDataToDuplicate.length; + + for (let i = 0; i < additionalDataCount; i++) { + let newCopyPageAdditionalDataSqlQuery = this.db.prepare(`INSERT INTO posts_additional_data VALUES(NULL, @copied_page_id, @key, @value)`); + newCopyPageAdditionalDataSqlQuery.run({ + copied_page_id: copiedPageId, + key: pageAdditionalDataToDuplicate[i].key, + value: pageAdditionalDataToDuplicate[i].value + }); + } + } + + // Copy images + ImageHelper.copyImagesDirectory(this.siteDir, this.id, copiedPageId); + + return true; + } + + /* + * Change page status + */ + changeStatus(status, inverse = false) { + let selectQuery = this.db.prepare(`SELECT status FROM posts WHERE id = @id`).all({ id: this.id }); + let currentStatus = selectQuery[0].status.split(','); + + if (!inverse) { + if(currentStatus.indexOf(status) === -1) { + currentStatus.push(status); + } + } else { + if(currentStatus.indexOf(status) > -1) { + currentStatus = currentStatus.filter((pageStatus) => pageStatus !== status); + } + } + + currentStatus = currentStatus.filter(status => status.trim() !== ''); + + let updateQuery = this.db.prepare(`UPDATE + posts + SET + status = @status + WHERE + id = @id`); + updateQuery.run({ + status: currentStatus.join(','), + id: this.id + }); + + return true; + } + + /* + * Check and prepare page slug + */ + checkAndPrepareSlug(suffix = 0) { + let pageSlug = this.slug; + let restrictedSlugs = []; + + if (this.application.sites[this.site].advanced.urls.cleanUrls) { + restrictedSlugs = [ + 'assets', + 'media', + this.application.sites[this.site].advanced.urls.authorsPrefix + ]; + + if (this.application.sites[this.site].advanced.urls.postsPrefix !== '') { + restrictedSlugs.push(this.application.sites[this.site].advanced.urls.postsPrefix); + } + + if (this.application.sites[this.site].advanced.urls.tagsPrefix !== '') { + restrictedSlugs.push(this.application.sites[this.site].advanced.urls.tagsPrefix); + } + } + + if (suffix > 0) { + pageSlug = this.escape(this.slug + '-' + suffix); + } + + if (restrictedSlugs.indexOf(pageSlug) > -1) { + this.checkAndPrepareSlug(2); + return; + } + + let result = this.db.prepare(`SELECT slug FROM posts WHERE slug LIKE @slug AND id != @id`).all({ + slug: pageSlug, + id: this.id + }); + + if (result && result.length) { + if(suffix === 0) { + suffix = 2; + } else { + suffix++; + } + + this.checkAndPrepareSlug(suffix); + } else { + this.slug = pageSlug; + } + } + + /* + * Remove unused images + */ + checkAndCleanImages(cancelEvent = false) { + let pageDir = this.id; + + if(this.id === 0) { + pageDir = 'temp'; + } + + let imagesDir = path.join(this.siteDir, 'input', 'media', 'posts', (pageDir).toString()); + let galleryImagesDir = path.join(imagesDir, 'gallery'); + let pageDirectoryExists = true; + + try { + fs.statSync(imagesDir).isDirectory(); + } catch (err) { + pageDirectoryExists = false; + } + + if(!pageDirectoryExists) { + return; + } + + let images = fs.readdirSync(imagesDir); + let galleryImages = false; + + if (Utils.dirExists(galleryImagesDir)) { + galleryImages = fs.readdirSync(galleryImagesDir); + } + + // Get previous text if the page is cancelled and it is a not a new page + if (cancelEvent && pageDir !== 'temp') { + let textSqlQuery = ` + SELECT + text + FROM + posts + WHERE + id = @id + `; + + let textResult = this.db.prepare(textSqlQuery).get({ id: pageDir }); + + if (textResult && textResult.text) { + this.text = textResult.text; + } + } + + this.cleanImages(images, imagesDir, cancelEvent); + + if (galleryImages) { + this.cleanImages(galleryImages, galleryImagesDir, cancelEvent); + } + } + + /* + * Removes images from a given image dir + */ + cleanImages(images, imagesDir, cancelEvent) { + let pageDir = this.id; + let featuredImage = path.parse(this.featuredImageFilename).base; + + // If page is cancelled - get the previous featured image + if (cancelEvent && this.id !== 0) { + let featuredImageSqlQuery = ` + SELECT + url + FROM + posts_images + WHERE + post_id = @id + `; + + let featuredImageResult = this.db.prepare(featuredImageSqlQuery).all({ + id: this.id + }); + + if (featuredImageResult && featuredImageResult.url) { + featuredImage = featuredImageResult.url; + } + } + + if (this.id === 0) { + pageDir = 'temp'; + } + + let imagesInPageViewSettings = []; + + if (this.pageViewSettings) { + imagesInPageViewSettings = Object.values(this.pageViewSettings).filter(item => item.type === "image").map(item => item.value); + } + + // Iterate through images + for (let i in images) { + let imagePath = images[i]; + let fullPath = path.join(imagesDir, imagePath); + + // Skip dirs and symlinks + if(imagePath === '.' || imagePath === '..' || imagePath === 'responsive' || imagePath === 'gallery') { + continue; + } + + // Remove files which does not exist in the page text, as featured image and pageViewSettings + if( + (cancelEvent && pageDir === 'temp') || + ( + this.text.indexOf(imagePath) === -1 && + imagesInPageViewSettings.indexOf(imagePath) === -1 && + featuredImage !== imagePath + ) + ) { + try { + fs.unlinkSync(fullPath); + } catch(e) { + console.error(e); + } + + // Remove responsive images + this.removeResponsiveImages(fullPath); + } + } + } + + /* + * Remove unused responsive images + */ + removeResponsiveImages(originalPath) { + let themesHelper = new Themes(this.application, { site: this.site }); + let currentTheme = themesHelper.currentTheme(); + + // If there is no selected theme + if (currentTheme === 'not selected') { + return; + } + + // Load theme config + let themeConfig = Utils.loadThemeConfig(path.join(this.siteDir, 'input'), currentTheme); + + // check if responsive images config exists + if (Utils.responsiveImagesConfigExists(themeConfig)) { + let dimensions = Utils.responsiveImagesDimensions(themeConfig, 'contentImages'); + let featuredDimensions = Utils.responsiveImagesDimensions(themeConfig, 'featuredImages'); + + if (featuredDimensions !== false) { + featuredDimensions.forEach(item => { + if (dimensions.indexOf(item) === -1) { + dimensions.push(item); + } + }); + } + + let responsiveImagesDir = path.parse(originalPath).dir; + responsiveImagesDir = path.join(responsiveImagesDir, 'responsive'); + + if (typeof dimensions === "boolean") { + return; + } + + let forceWebp = !!this.application.sites[this.site]?.advanced?.forceWebp; + + // Remove responsive images of each size + for (let dimensionName of dimensions) { + let filename = path.parse(originalPath).name; + let extension = path.parse(originalPath).ext; + + if (forceWebp && ['.png', '.jpg', '.jpeg'].indexOf(extension.toLowerCase()) > -1) { + extension = '.webp'; + } + + let responsiveImagePath = path.join(responsiveImagesDir, filename + '-' + dimensionName + extension); + + if (Utils.fileExists(responsiveImagePath)){ + fs.unlinkSync(responsiveImagePath); + } + } + } + } + + /* + * Save additional data + */ + saveAdditionalData() { + // Remove old _core additional data + this.db.exec(`DELETE FROM posts_additional_data WHERE post_id = ${parseInt(this.id, 10)} AND key = '_core'`); + + // Convert data to JSON string + if (typeof this.additionalData !== 'object') { + this.additionalData = {}; + } + + let additionalDataToSave = JSON.stringify(this.additionalData); + + // Store the data + let storeSqlQuery = this.db.prepare(`INSERT INTO posts_additional_data VALUES(null, @id, '_core', @data)`); + storeSqlQuery.run({ + id: this.id, + data: additionalDataToSave + }); + } + + /* + * Save additional data + */ + savePageViewSettings() { + // Remove old _core additional data + this.db.exec(`DELETE FROM posts_additional_data WHERE post_id = ${parseInt(this.id, 10)} AND key = 'pageViewSettings'`); + + // Convert data to JSON string + if(typeof this.pageViewSettings !== 'object') { + this.pageViewSettings = {}; + } + + let dataToSave = JSON.stringify(this.pageViewSettings); + + // Store the data + let storeSqlQuery = this.db.prepare(`INSERT INTO posts_additional_data VALUES(null, @id, 'pageViewSettings', @data)`); + storeSqlQuery.run({ + id: this.id, + data: dataToSave + }); + } + + /** + * Clean up page content from wrong code + */ + cleanUpContent (content) { + return content.replace(/\
{{> footer}} diff --git a/app/default-files/default-themes/simple/CHANGELOG.md b/app/default-files/default-themes/simple/CHANGELOG.md new file mode 100644 index 000000000..79b10f7a5 --- /dev/null +++ b/app/default-files/default-themes/simple/CHANGELOG.md @@ -0,0 +1,209 @@ +# Changelog + +## [3.0.0.0] - 2024-07-07 +### Added +- Added support for italic fonts +- Redesigned the theme + +## [2.9.0.0] - 2024-06-30 +### Added +- Added support for Pages +- Added new typography options: now you can adjust letter spacing or line height for both body and heading elements. +- Added a new baseline option to defines a core vertical rhythm unit for consistent spacing across the website + +### Improved +- Minor CSS enhancements + +## [2.8.3.0] - 2023-11-03 +### Fixed +- Resolved issue with missing the responsive thumbnails on the post page + +## [2.8.2.0] - 2023-10-25 +### Fixed +- Resolved checkIf function issue caused by parsing error in Handlebars, occurring when SocialSharing plugin was missing +### Updated +- Updated X Twitter icon + +## [2.8.1.0] - 2023-10-21 +### Fixed +- Fixed the CSS error related to the missing ‘gray-2’ CSS variable + +## [2.8.0.0] - 2023-10-20 +**Note:** The new features and enhancements require at least Publii version 0.43.1! +### Added +- Added a ‘customSocialSharing’ custom HTML position to support the Social Sharing plugin + +## [2.7.0.0] - 2023-10-03 +### Added +- Added more variable fonts +### Improved +- Made minor CSS improvements + +## [2.6.2.0] - 2023-04-17 +### Added +- Added option to disable tag counter on Tags page +### Improved +- Minor CSS enhancements + +## [2.6.1.0] - 2023-04-03 +### Changed +- Reorganized and renamed theme options to improve navigation and readability + +## [2.6.0.0] - 2023-03-29 +**Note:** The new features and enhancements require at least Publii version 0.42.x! +### Added +- Implemented support for new tag and author options +- Added support for new extended menu assignment options +- Removed AMP-related files +- Added the NoScript tag to handle images when JavaScript is disabled +### Improved +- Made minor CSS improvements + +## [2.5.0.0] - 2022-07-07 +**Note:** The new features and enhancements require at least Publii version 0.40.x! +### Added +- Added support for Search plugins +- Added a script to calculate the aspect ratio of the iframes automatically +- Added support for Emended Content Consent +- Updated menu script +- Updated supportedFeatures section + +## [2.4.1.0] - 2022-03-20 +### Adjusted +- Slight Dark mode CSS adjustment + +## [2.4.0.0] - 2022-03-16 +**Note:** The new features and enhancements require at least Publii version 0.39.x! +### Changed +- Changed how Google fonts are handled, from loading them from a CDN to hosting them locally +### Added +- Added Dark Mode (now it supports light, dark, and auto mode) +- Added support for Comment plugins +- Updated Facebook social icon +- Updated avatar code markup +- Changed the way the “Back to top” button works + +## [2.3.3.0] - 2021-11-07 +### Fixed +- Fixed Photoswipe script to open gallery image in the pop-up window directly by firing image URL +- Updated Disqus script (now uses an Intersection Observer to detect / load comment section) +- Updated menu script +### Improved +- Improved accessibility of the navigation menu +### Changed +- Changed the way CSS variables work in the theme (now uses theme-variables.js) +- Small CSS improvements + +## [2.3.0.0] - 2020-09-25 +**Note:** The new features and enhancements require Publii version 0.37.x! +### Added +- Added support for tags page +- Added support for tag featured image +- Added support for author featured image +- Added support for author website field +- Added supported features flags +- Added option to define the number of related posts +- Updated Disqus script + +## [2.2.3.0] - 2020-06-16 +### Updated +- Updated menu script to support the anchors in mobile view +### Fixed +- Fixed mobile menu styling + +## [2.2.2.0] - 2020-06-04 +### Added +- Added aria-label to the search input +### Fixed +- Fixed zoom/in/out gallery option +- Updated font.hbs file +- Fixed WhatsApp share button + +## [2.2.1.0] - 2020-05-25 +**Note:** Before installing, make sure you have installed Publii at least version 0.36.0! If you need to keep using Publii 0.35.3 you can always download the previous version of the theme from [here](https://cdn.getpublii.com/themes/simple_2.1.0.0.zip). +### Added +- Added support for „Enable Responsive Images” option +- Added support for native Lazy Loading +### Adjusted +- Typo adjustment +### Updated +- Updated Photoswipe gallery to 4.1.3 version +### Fixed +- Fixed gallery UI buttons behavior on hover +### Rewritten +- Rewritten to use CSS variables + +## [2.1.0.0] - 2020-01-13 +### Added +- Added Block editor support + +## [2.0.4.0] - 2019-11-27 +**Note:** Before installing, make sure you have installed Publii at least version 35.3 +### Added +- Added support for better display of SVG images + +## [2.0.3.0] - 2019-08-04 +### Optimized +- Optimized image lazyload for the smoothest, fastest experience possible +### Fixed +- Fixed page scrolling when the mobile menu is opened (iOS only) +### Added +- Added WhatsApp share button + +## [2.0.2.1] - 2019-05-25 +### Fixed +- Fixed display of the image in the post content + +## [2.0.2.0] - 2019-05-13 +### Improved +- Improved the way the first menu level is displayed; now the menu items break down on the next line when it is needed +### Fixed +- Fixed the hero section by removing the space between the image and the right side of the browser window + +## [2.0.1.0] - 2019-05-09 +### Rewritten +- A minor redesign, with a rewritten code to make it more efficient +### Added +- An option for displaying a featured image on post list pages +- Overhauled menu system! New scripts, two mobile menu styles (Sidebar and overlay), and a reactive submenu that shifts position when it gets too close to the edge of the browser window +- Responsive iframes (for things like videos, maps etc…) +- No more jQuery; now Simple uses Vanilla scripts +- Expanded hero section controls +- New font selection options +- New ‘Back to Top’ option +- A new gallery style and optimized image lazyload for the smoothest, fastest experience possible + +## [1.7.2.0] - 2019-03-28 +### Added +- Added option to change the overlay of hero section; now it supports gradient or solid color + +## [1.7.1.0] - 2019-03-25 +### Removed +- If the menu is unassigned to any menu position, its HTML markup is no longer displayed +- Removed Google+ service due to its shutdown on April 2 + +## [1.7.0.0] - 2019-03-02 +### Changed +- Gallery styling has been changed +### Centered +- Centered caption in a gallery lightbox +### Fixed +- Fixed pagination ordering +- Fixed Pinterest share button + +## [1.6.1.0] - 2018-12-12 +### Added +- Added support for a table of content +- Updated the lazysizes script to v. 1.4.5 +- Added preload option to an image gallery +- jQuery JavaScript library is now served locally + +## [1.6.0.0] - 2018-10-22 +### Changed +- Changed the CSS style of the
tag +- Moved share icons to the bottom of article +- Updated StumbleUpon share button, now it’s Mix.com +### Fixed +- Fixed the image logo, now it looks well on the mobile devices too +### Removed +- Removed the lazyload from the hero image to speed up its loading diff --git a/app/default-files/default-themes/simple/assets/css/main.css b/app/default-files/default-themes/simple/assets/css/main.css index 83ee0cbee..754518a58 100644 --- a/app/default-files/default-themes/simple/assets/css/main.css +++ b/app/default-files/default-themes/simple/assets/css/main.css @@ -50,7 +50,7 @@ address { } html { - font-size: clamp(1.1rem, 1.1rem + 0.1 * ((100vw - 20rem) / 50), 1.2rem); + font-size: var(--font-size); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; scroll-behavior: smooth; @@ -65,6 +65,7 @@ body { color: var(--text-color); font-family: var(--body-font); font-variation-settings: "wght" var(--font-weight-normal); + letter-spacing: var(--letter-spacing); line-height: var(--line-height); -ms-scroll-chaining: none; overscroll-behavior: none; @@ -107,7 +108,7 @@ p, ul, ol, dl { - margin-top: calc(1.1333333333rem + 0.25vw); + margin-top: calc(var(--baseline) * 4 + 0.25vw); } blockquote, @@ -115,8 +116,8 @@ figure, hr, pre, table { - margin-top: calc(1.7rem + 0.5vw); - margin-bottom: calc(1.7rem + 0.5vw); + margin-top: calc(var(--baseline) * 6 + 0.5vw); + margin-bottom: calc(var(--baseline) * 6 + 0.5vw); } h1, @@ -128,43 +129,44 @@ h6 { color: var(--headings-color); font-family: var(--heading-font); font-variation-settings: "wght" var(--headings-weight); - -webkit-hyphens: manual; - -ms-hyphens: manual; - hyphens: manual; - line-height: 1.2; - margin-top: calc(1.7rem + 1vw); + font-style: var(--headings-style); + -ms-hyphens: manual; + hyphens: manual; + letter-spacing: var(--headings-letter-spacing); + line-height: var(--headings-line-height); + margin-top: calc(var(--baseline) * 6 + 1vw); text-transform: var(--headings-transform); } h1, .h1 { - font-size: clamp(1.5710900065rem, 1.5710900065rem + 1.424540906 * ((100vw - 20rem) / 50), 2.9956309125rem); + font-size: clamp(1.5710900065rem, 1.5710900065rem + 1.424540906 * (100vw - 20rem) / 70, 2.9956309125rem); font-family: var(--heading-font); } h2, .h2 { - font-size: clamp(1.3808408252rem, 1.3808408252rem + 0.9332127447 * ((100vw - 20rem) / 50), 2.3140535699rem); + font-size: clamp(1.3808408252rem, 1.3808408252rem + 0.9332127447 * (100vw - 20rem) / 70, 2.3140535699rem); } h3, .h3 { - font-size: clamp(1.2136296308rem, 1.2136296308rem + 0.4621997101 * ((100vw - 20rem) / 50), 1.6758293408rem); + font-size: clamp(1.2136296308rem, 1.2136296308rem + 0.4621997101 * (100vw - 20rem) / 70, 1.6758293408rem); } h4, .h4 { - font-size: clamp(1.1377777785rem, 1.1377777785rem + 0.1567604947 * ((100vw - 20rem) / 50), 1.2945382732rem); + font-size: clamp(1.1377777785rem, 1.1377777785rem + 0.1567604947 * (100vw - 20rem) / 70, 1.2945382732rem); } h5, .h5 { - font-size: clamp(1.066666667rem, 1.066666667rem + 0.0711111115 * ((100vw - 20rem) / 50), 1.1377777785rem); + font-size: clamp(1.066666667rem, 1.066666667rem + 0.0711111115 * (100vw - 20rem) / 70, 1.1377777785rem); } h6, .h6 { - font-size: clamp(1rem, 1rem + 0 * ((100vw - 20rem) / 50), 1rem); + font-size: clamp(1rem, 1rem + 0 * (100vw - 20rem) / 70, 1rem); } h2 + *, @@ -172,7 +174,7 @@ h3 + *, h4 + *, h5 + *, h6 + * { - margin-top: calc(0.5666666667rem + 0.25vw); + margin-top: calc(var(--baseline) * 2 + 0.25vw); } b, @@ -181,22 +183,14 @@ strong { } blockquote { + border-top: 2px solid var(--dark); + border-bottom: 2px solid var(--dark); color: var(--headings-color); font-family: var(--heading-font); - padding: 3.6833333333rem 0 0; - position: relative; - text-align: center; - font-size: clamp(1.1377777785rem, 1.1377777785rem + 0.2430630467 * ((100vw - 20rem) / 50), 1.3808408252rem); -} -blockquote::before { - color: var(--dark); - content: "“"; - font: normal 320%/0.9 Georgia, Times, Times New Roman, serif; - top: 0.5666666667rem; - position: absolute; - left: 50%; - -webkit-transform: translate(-50%, 0); - transform: translate(-50%, 0); + font-style: italic; + font-variation-settings: "wght" var(--font-weight-bold); + padding: calc(var(--baseline) * 6 + 1vw) 2rem; + font-size: clamp(1.1377777785rem, 1.1377777785rem + 0.1567604947 * (100vw - 20rem) / 70, 1.2945382732rem); } blockquote > :nth-child(1) { margin-top: 0; @@ -204,12 +198,12 @@ blockquote > :nth-child(1) { ul, ol { - margin-left: 2rem; + margin-left: 3ch; } ul > li, ol > li { list-style: inherit; - padding: 0 0 0.2833333333rem 0.85rem; + padding: 0 0 var(--baseline) 1ch; } dl dt { @@ -219,7 +213,7 @@ dl dt { pre { background-color: var(--lighter); font-size: 0.8239746086rem; - padding: 1.7rem; + padding: calc(var(--baseline) * 6); white-space: pre-wrap; word-wrap: break-word; } @@ -247,11 +241,11 @@ table { } table th { font-variation-settings: "wght" var(--font-weight-bold); - padding: 0.7083333333rem 1.1333333333rem; + padding: calc(var(--baseline) * 2.5) calc(var(--baseline) * 4); } table td { border-top: 1px solid var(--light); - padding: 0.7083333333rem 1.1333333333rem; + padding: calc(var(--baseline) * 2.5) calc(var(--baseline) * 4); } .table-striped tr:nth-child(2n) { @@ -270,7 +264,7 @@ figcaption { color: var(--gray); font-style: italic; font-size: 0.7241964329rem; - margin: 0.85rem 0 0; + padding: calc(var(--baseline) * 3) 0 0; text-align: center; } @@ -280,7 +274,7 @@ kbd { color: var(--white); font-family: Menlo, Monaco, Consolas, Courier New, monospace; font-size: 0.8789062495rem; - padding: 0.1416666667rem 0.425rem; + padding: calc(var(--baseline) * 0.5) calc(var(--baseline) * 1.5); } sub, @@ -303,18 +297,18 @@ hr, } hr::before, .separator::before { - content: "···"; + content: "•••"; color: var(--dark); - font-size: 1.2136296308rem; + font-size: 1rem; font-variation-settings: "wght" var(--font-weight-bold); letter-spacing: 1.1377777785rem; padding-left: 1.1377777785rem; } .separator--dot::before { - content: "·"; + content: "•"; color: var(--dark); - font-size: 1.2136296308rem; + font-size: 1rem; font-variation-settings: "wght" var(--font-weight-bold); letter-spacing: 1.1377777785rem; padding-left: 1.1377777785rem; @@ -324,7 +318,7 @@ hr::before, } .separator--long-line::before { content: ""; - height: 1.2136296308rem; + height: 1rem; } .separator--long-line::after { border-top: 1px solid var(--light); @@ -339,23 +333,21 @@ hr::before, .btn, [type=button], [type=submit], button { - background: var(--color); - border: 1px solid var(--color); - border-radius: 2px; - -webkit-box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.08); - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.08); - color: var(--white); + align-items: center; + background: none; + border: 1px solid var(--dark); + border-radius: calc(var(--border-radius) * 10); + color: var(--dark); cursor: pointer; - display: inline-block; - font-family: var(--body-font); - font-size: 0.6789341556rem; + display: inline-flex; + font-family: var(--menu-font); + font-size: 0.8239746086rem; font-variation-settings: "wght" var(--font-weight-bold); overflow: hidden; - padding: 0.5666666667rem 1.4166666667rem; + padding: calc(var(--baseline) * 2) calc(var(--baseline) * 4); text-align: center; -webkit-transition: all 0.24s ease-out; transition: all 0.24s ease-out; - text-transform: uppercase; vertical-align: middle; will-change: transform; } @@ -366,13 +358,6 @@ button { width: 100%; } } -@media all and (max-width: 37.4375em) { - .btn, [type=button], - [type=submit], - button { - margin-bottom: 0.5666666667rem; - } -} .btn:hover, [type=button]:hover, [type=submit]:hover, button:hover, .btn:active, [type=button]:active, @@ -380,11 +365,15 @@ button:hover, .btn:active, [type=button]:active, button:active, .btn:focus, [type=button]:focus, [type=submit]:focus, button:focus { - -webkit-box-shadow: 0 7px 14px rgba(0, 0, 0, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08); - box-shadow: 0 7px 14px rgba(0, 0, 0, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08); - color: var(--white); - -webkit-transform: translateY(-2px); - transform: translateY(-2px); + background-color: var(--dark); + color: var(--helper); +} +.btn--icon { + gap: 0.3rem; + justify-content: center; +} +.btn--icon svg { + stroke: currentColor; } @media all and (min-width: 20em) { .btn + .btn, [type=button] + .btn, @@ -400,16 +389,36 @@ button:focus { [type=button] + button, [type=submit] + button, button + button { - margin-left: 0.5666666667rem; + margin-left: calc(var(--baseline) * 2); + } +} +@media all and (max-width: 37.4375em) { + .btn + .btn, [type=button] + .btn, + [type=submit] + .btn, + button + .btn, .btn + [type=button], [type=button] + [type=button], + [type=submit] + [type=button], + button + [type=button], + .btn + [type=submit], + [type=button] + [type=submit], + [type=submit] + [type=submit], + button + [type=submit], + .btn + button, + [type=button] + button, + [type=submit] + button, + button + button { + margin-bottom: calc(var(--baseline) * 2); } } .btn:disabled, [type=button]:disabled, [type=submit]:disabled, -button:disabled { +button:disabled, .btn[disabled], [disabled][type=button], +[disabled][type=submit], +button[disabled] { background-color: var(--light); border-color: var(--light); color: var(--gray); cursor: not-allowed; + pointer-events: none; } [type=button], @@ -425,8 +434,8 @@ button { fieldset { border: 1px solid var(--light); - margin: calc(1.7rem + 1vw) 0 0; - padding: 1.7rem; + margin: calc(var(--baseline) * 6 + 1vw) 0 0; + padding: calc(var(--baseline) * 6); } fieldset > legend { margin-left: -1rem; @@ -440,7 +449,7 @@ legend { label { font-variation-settings: "wght" 500; - margin: 0 1.1333333333rem 0.85rem 0; + margin: 0 calc(var(--baseline) * 4) calc(var(--baseline) * 3) 0; } [type=text], @@ -457,7 +466,7 @@ select { color: var(--text-color); font-size: 1rem; outline: none; - padding: 0.34rem 0.85rem; + padding: calc(var(--baseline) * 1.2) calc(var(--baseline) * 3); vertical-align: middle; width: 100%; -webkit-appearance: none; @@ -505,12 +514,12 @@ input[type=radio] + label:before { border-radius: 2px; content: ""; display: inline-block; - height: 1.4166666667rem; - line-height: 1.4166666667rem; - margin-right: 1.1333333333rem; + height: calc(var(--baseline) * 5); + line-height: calc(var(--baseline) * 5); + margin-right: calc(var(--baseline) * 4); vertical-align: middle; text-align: center; - width: 1.4166666667rem; + width: calc(var(--baseline) * 5); } input[type=checkbox]:checked + label:before, input[type=radio]:checked + label:before { @@ -529,7 +538,7 @@ input[type=radio]:checked + label:before { } [type=file] { - margin-bottom: 1.7rem; + margin-bottom: calc(var(--baseline) * 6); width: 100%; } @@ -541,12 +550,12 @@ select { select:not([multiple]) { background: url('data:image/svg+xml;utf8,') no-repeat 90% 50%; background-size: 8px; - padding-right: 3.4rem; + padding-right: calc(var(--baseline) * 12); } select[multiple] { border: 1px solid var(--light); - padding: 1.7rem; + padding: calc(var(--baseline) * 6); width: 100%; } select[multiple]:hover { @@ -574,7 +583,7 @@ textarea { align-items: center; display: flex; height: var(--navbar-height); - position: absolute; + position: relative; padding: 0 var(--page-margin); -webkit-transition: background 0.5s ease; transition: background 0.5s ease; @@ -587,29 +596,31 @@ textarea { height: var(--navbar-height); } } -.top.is-visible { - background: var(--section-bg); - -webkit-box-shadow: 0 3px 10px 0 rgba(0, 0, 0, 0.06); - box-shadow: 0 3px 10px 0 rgba(0, 0, 0, 0.06); - opacity: 1; - position: fixed; - -webkit-transform: translate(0, 0); - transform: translate(0, 0); - -webkit-transition: height 0.3s, background 0.3s, opacity 0.24s, -webkit-transform 0.24s; - transition: height 0.3s, background 0.3s, opacity 0.24s, -webkit-transform 0.24s; - transition: transform 0.24s, height 0.3s, background 0.3s, opacity 0.24s; - transition: transform 0.24s, height 0.3s, background 0.3s, opacity 0.24s, -webkit-transform 0.24s; - width: 100%; +.top.sticky { + background: var(--page-bg); + position: sticky; + top: -100px; + -webkit-animation: slideDown 0.5s cubic-bezier(0.17, 0.67, 0, 1) forwards; + animation: slideDown 0.5s cubic-bezier(0.17, 0.67, 0, 1) forwards; } -@media all and (min-width: 56.25em) { - .top.is-hidden { +@-webkit-keyframes slideDown { + from { + opacity: 0; + top: -100px; + } + to { + opacity: 1; + top: 0; + } +} +@keyframes slideDown { + from { opacity: 0; - -webkit-transform: translate(0, -86px); - transform: translate(0, -86px); - -webkit-transition: background 0.3s, color 0.3s, opacity 0.24s, -webkit-transform 0.24s; - transition: background 0.3s, color 0.3s, opacity 0.24s, -webkit-transform 0.24s; - transition: transform 0.24s, background 0.3s, color 0.3s, opacity 0.24s; - transition: transform 0.24s, background 0.3s, color 0.3s, opacity 0.24s, -webkit-transform 0.24s; + top: -100px; + } + to { + opacity: 1; + top: 0; } } @@ -617,14 +628,15 @@ textarea { color: var(--logo-color) !important; font-size: 1.2136296308rem; font-family: var(--logo-font); - font-variation-settings: "wght" var(--font-weight-normal); + font-variation-settings: "wght" var(--font-weight-bold); margin-right: auto; + order: 1; } .logo > img { height: var(--navbar-height); -o-object-fit: contain; object-fit: contain; - padding: 0.5666666667rem 0; + padding: calc(var(--baseline) * 2) 0; width: auto; } @@ -637,94 +649,42 @@ textarea { } } .search__btn { - border: none !important; - background: none !important; - -webkit-box-shadow: none !important; - box-shadow: none !important; + border-color: var(--light); margin: 0; - padding: 0.5666666667rem; - width: auto; + height: 2.2rem; + padding: 0; + width: 2.2rem; } @media all and (min-width: 56.25em) { .search__btn { - margin-left: 1rem; - padding-right: 0; + margin-left: 2rem; } } .search__btn:hover, .search__btn:focus { - -webkit-transform: translateY(0); - transform: translateY(0); -} -.search__btn:hover > svg, .search__btn:focus > svg { - fill: rgba(255, 255, 255, 0.7); -} -.search__btn > svg { - display: block; - fill: var(--white); - height: 15px; - -webkit-transition: all 0.24s ease; - transition: all 0.24s ease; - width: 15px; + border-color: var(--dark); } .search__form { - flex-basis: 90%; + display: flex; + justify-content: space-between; + width: 100%; +} +.search__form button { + width: auto; + flex-shrink: 0; } .search__input { background: none; - border: none !important; - color: var(--white); - display: none; - font-family: var(--heading-font); - margin: 0 !important; - opacity: 0; - padding: 0; - width: 100%; - font-size: clamp(1.2136296308rem, 1.2136296308rem + 0.3574603758 * ((100vw - 20rem) / 50), 1.5710900065rem); -} -.search__close { - background: none !important; - -webkit-box-shadow: none !important; - box-shadow: none !important; border: none; - color: transparent; - cursor: pointer; - margin: 0 !important; - opacity: 0; + border-bottom: 1px solid var(--dark); + display: block; + font-family: var(--heading-font); padding: 0; - height: 1.6rem; - width: 1.6rem; - position: relative; - text-indent: -999rem; -} -.search__close:before, .search__close:after { - background-color: var(--white); - content: ""; - left: 1rem; - height: 1.6rem; - opacity: 1; - position: absolute; - width: 1px; - top: 0; - -webkit-transform: rotate(45deg); - transform: rotate(45deg); - -webkit-transition: all 0.14s ease-out; - transition: all 0.14s ease-out; -} -.search__close:after { - -webkit-transform: rotate(-45deg); - transform: rotate(-45deg); -} -.search__close:hover { - -webkit-transform: translateY(0); - transform: translateY(0); -} -.search__close:hover:before, .search__close:hover:after { - background-color: rgba(255, 255, 255, 0.7); + width: 90%; } .search__overlay { - background-color: var(--section-bg); - -webkit-box-shadow: 0 3px 3px rgba(0, 0, 0, 0.05); - box-shadow: 0 3px 3px rgba(0, 0, 0, 0.05); + background-color: var(--page-bg); + -webkit-box-shadow: 0 3px 30px rgba(0, 0, 0, 0.05); + box-shadow: 0 3px 30px rgba(0, 0, 0, 0.05); left: 0; opacity: 0; position: fixed; @@ -736,64 +696,42 @@ textarea { z-index: 2005; } .search__overlay-inner { + -webkit-animation: slideininput 0.24s 1s forwards; + animation: slideininput 0.24s 1s forwards; align-items: center; display: flex; - height: 4.4rem; + height: calc(var(--navbar-height) * 3); justify-content: space-between; padding: 0 var(--page-margin); -} -@media all and (min-width: 56.25em) { - .search__overlay-inner { - height: 6rem; - } -} -.search__overlay.expanded { - -webkit-transform: translate(0, 0); - transform: translate(0, 0); - opacity: 1; - display: block; - visibility: visible; -} -.search__overlay.expanded .search__input { - -webkit-animation: slideininput 0.24s 0.12s forwards; - animation: slideininput 0.24s 0.12s forwards; - display: block; + opacity: 0; + scale: 0.9; } @-webkit-keyframes slideininput { 60% { opacity: 0; + scale: 0.9; } 100% { opacity: 1; + scale: 1; } } @keyframes slideininput { 60% { opacity: 0; + scale: 0.9; } 100% { opacity: 1; + scale: 1; } } -.search__overlay.expanded .search__close { - -webkit-animation: slideinclose 0.24s 0.12s forwards; - animation: slideinclose 0.24s 0.12s forwards; -} -@-webkit-keyframes slideinclose { - 60% { - opacity: 0; - } - 100% { - opacity: 1; - } -} -@keyframes slideinclose { - 60% { - opacity: 0; - } - 100% { - opacity: 1; - } +.search__overlay.expanded { + -webkit-transform: translate(0, 0); + transform: translate(0, 0); + opacity: 1; + display: block; + visibility: visible; } .navbar { @@ -814,19 +752,18 @@ textarea { .navbar .navbar__menu li { font-family: var(--menu-font); display: block; - font-size: 0.7724761953rem; + font-size: 0.8789062495rem; line-height: var(--line-height); font-variation-settings: "wght" 500; padding: 0; position: relative; - text-transform: uppercase; width: auto; } .navbar .navbar__menu li a, .navbar .navbar__menu li span[aria-haspopup=true] { color: var(--nav-link-color); display: block; - padding: 0 0.5666666667rem; + padding: 0 0.6rem; -webkit-transition: all 0.24s ease-out; transition: all 0.24s ease-out; } @@ -840,7 +777,7 @@ textarea { color: var(--nav-link-color); cursor: default; display: block; - padding: 0 0.5666666667rem; + padding: 0 0.6rem; } .navbar .navbar__menu > li:hover > a, .navbar .navbar__menu > li:hover > span[aria-haspopup=true] { color: var(--nav-link-color-hover); @@ -853,16 +790,17 @@ textarea { -webkit-transform: scale(1); transform: scale(1); visibility: visible; - margin-top: 0.85rem; + margin-top: 0.8rem; } .navbar .has-submenu:active > .navbar__submenu:before, .navbar .has-submenu:focus > .navbar__submenu:before, .navbar .has-submenu:hover > .navbar__submenu:before { content: ""; - height: 0.85rem; + height: 1rem; + left: 0; position: absolute; width: 100%; - top: -0.85rem; + top: -1rem; } .navbar .has-submenu:active > .navbar__submenu.is-right-submenu, .navbar .has-submenu:focus > .navbar__submenu.is-right-submenu, @@ -885,14 +823,12 @@ textarea { margin-top: 0; } .navbar .navbar__submenu { - background: var(--section-bg); - -webkit-box-shadow: 0 5px 5px rgba(0, 0, 0, 0.25); - box-shadow: 0 5px 5px rgba(0, 0, 0, 0.25); - border-radius: var(--border-radius); + background: var(--lighter); + border-radius: calc(var(--border-radius) * 4); left: -9999px; list-style-type: none; margin: 0; - padding: 10px 0; + padding: 1rem 0.85rem; position: absolute; visibility: hidden; white-space: nowrap; @@ -912,12 +848,13 @@ textarea { } .navbar .navbar__submenu li { line-height: 1.5; - font-size: 0.7241964329rem; + font-size: 0.8789062495rem; } .navbar .navbar__submenu li a, .navbar .navbar__submenu li span[aria-haspopup=true] { - color: var(--nav-link-color-hover) !important; - padding: 0.5666666667rem 1.4166666667rem; + border-radius: calc(var(--border-radius) * 3); + color: var(--nav-link-color-hover); + padding: 0.5rem 1rem; -webkit-transition: all 0.24s ease; transition: all 0.24s ease; } @@ -925,32 +862,35 @@ textarea { .navbar .navbar__submenu li span[aria-haspopup=true]:active, .navbar .navbar__submenu li span[aria-haspopup=true]:focus, .navbar .navbar__submenu li span[aria-haspopup=true]:hover { - background: rgba(var(--white-rgb), 0.05); - color: var(--nav-link-color) !important; + background: var(--page-bg); + color: var(--nav-link-color); } .navbar .navbar__submenu li span { color: var(--nav-link-color-hover) !important; - padding: 0.5666666667rem 1.4166666667rem; + padding: 0.5rem 1rem; } .navbar .navbar__submenu li:hover > a, .navbar .navbar__submenu li:hover > span[aria-haspopup=true] { - color: var(--nav-link-color) !important; + color: var(--nav-link-color); } .navbar .navbar__toggle { - background: var(--section-bg); + background: var(--black); -webkit-box-shadow: none; box-shadow: none; border: none; - border-radius: 3px; cursor: pointer; display: block; line-height: 1; margin: 0; overflow: visible; - padding: 1rem; + padding: 0; position: relative; - right: -1rem; + right: 0; + margin-left: 0.75rem; text-transform: none; z-index: 2004; + height: 3.2rem; + padding: 0; + width: 3.2rem; } @media all and (min-width: 56.25em) { .navbar .navbar__toggle { @@ -965,7 +905,7 @@ textarea { transform: none; } .navbar .navbar__toggle-box { - width: 24px; + width: 20px; height: 14px; display: inline-block; position: relative; @@ -978,16 +918,16 @@ textarea { .navbar .navbar__toggle-inner::before { content: ""; display: block; - top: -6px; + top: -5px; } .navbar .navbar__toggle-inner::after { content: ""; display: block; - bottom: -6px; + bottom: -5px; } .navbar .navbar__toggle-inner, .navbar .navbar__toggle-inner::before, .navbar .navbar__toggle-inner::after { - width: 22px; - height: 2px; + width: 20px; + height: 1px; background-color: var(--white); position: absolute; -webkit-transition: opacity 0.14s ease-out, -webkit-transform; @@ -1066,7 +1006,7 @@ textarea { .navbar_mobile_overlay .navbar__menu li span { color: var(--dark); display: block; - padding: 0.5666666667rem; + padding: calc(var(--baseline) * 2); position: relative; } .navbar_mobile_overlay .navbar__menu li a:active, .navbar_mobile_overlay .navbar__menu li a:focus, .navbar_mobile_overlay .navbar__menu li a:hover, @@ -1083,7 +1023,7 @@ textarea { border-style: solid; border-width: 5px 5px 0 5px; border-color: currentColor transparent transparent transparent; - left: 0.5666666667rem; + left: calc(var(--baseline) * 2); top: 15px; position: relative; } @@ -1197,142 +1137,161 @@ textarea { pointer-events: none; } -.site-container { - background: var(--page-bg); - max-width: 100%; - overflow: hidden; -} - .wrapper { -webkit-box-sizing: content-box; box-sizing: content-box; - max-width: var(--entry-width); - margin: 0 auto; - padding: 0 var(--page-margin); + max-width: var(--page-width); + margin-left: auto; + margin-right: auto; + padding-left: var(--page-margin); + padding-right: var(--page-margin); } -.readmore { - display: inline-block; - color: var(--gray); - font-size: 0.8239746086rem; - font-style: italic; - text-decoration: underline; - -webkit-text-decoration-skip: ink; - text-decoration-skip-ink: auto; +.entry-wrapper { + -webkit-box-sizing: content-box; + box-sizing: content-box; + max-width: var(--entry-width); + margin-left: auto; + margin-right: auto; + padding-left: var(--page-margin); + padding-right: var(--page-margin); } .hero { - background: var(--hero-bg); - height: var(--hero-height); position: relative; - top: 0; z-index: 1; } -.hero__content { - bottom: 10%; - color: var(--hero-text-color); - left: 50%; +.hero--noimage::after { + background: var(--dark); + content: ""; + display: block; + height: 1px; + bottom: 0; + width: calc(100% - var(--page-margin) * 2); + z-index: -1; + max-width: var(--page-width); position: absolute; + left: 50%; -webkit-transform: translate(-50%, 0); transform: translate(-50%, 0); - width: 100%; - z-index: 2; } -.hero__content h1 { - color: var(--hero-heading-color); +.hero__content { + padding-bottom: calc(var(--baseline) * 6 + 1.5vw); } .hero__content h1 > sup { font-size: 1.066666667rem; vertical-align: top; } -.hero__content h1 + p { - margin-top: calc(0.2833333333rem + 0.25vw); +.hero__content--centered { + text-align: center; } -.hero__content a { - color: var(--hero-link-color); - -webkit-transition: all 0.24s ease-out; - transition: all 0.24s ease-out; +.hero__content--centered .content__meta { + justify-content: center; } -.hero__content a:hover { - color: var(--hero-link-color-hover); +.hero__cta { + margin-top: calc(var(--baseline) * 6); } -.hero__content a:active { - color: var(--hero-link-color); +.hero__image { + margin: 0 var(--page-margin); } -.hero__content a:focus { - outline: none; +.hero__image-wrapper { + position: relative; + background: var(--lighter); + border-radius: calc(var(--border-radius) * 4); } -.hero__image { - height: 100%; - margin: 0; - position: absolute; - width: 100%; +@media all and (min-width: 56.25em) { + .hero__image-wrapper { + height: var(--hero-height); + } } -.hero__image--overlay::after { - background: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0)), to(rgba(0, 0, 0, 0.64))); - background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.64) 100%); - content: ""; +.hero__image-wrapper img { + border-radius: inherit; display: block; height: 100%; - left: 0; - position: absolute; - top: 0; - width: 100%; -} -.hero__image > img { - display: block; - height: inherit; -o-object-fit: cover; object-fit: cover; - width: inherit; + width: 100%; +} +@media all and (min-width: 56.25em) { + .hero__image-wrapper img { + height: var(--hero-height); + } } .hero__image > figcaption { background: var(--page-bg); - border-radius: calc(4 * var(--border-radius)); - bottom: 1rem; - color: var(--text-color); - display: block; - padding: 0 0.34rem; - position: absolute; - text-align: right; - right: var(--page-margin); - z-index: 3; +} +@media all and (min-width: 56.25em) { + .hero__image > figcaption { + text-align: right; + } } .feed__item { - margin-top: calc(2.55rem + 1.5vw); - padding-bottom: calc(0.5666666667rem + 1vw); + display: flex; + flex-wrap: wrap; + gap: calc(2rem + 2vw); + margin-top: calc(var(--baseline) * 12 + 2vw); +} +@media all and (min-width: 37.5em) { + .feed__item { + flex-wrap: nowrap; + } +} +.feed__item--centered { + justify-content: center; +} +.feed__content { + max-width: var(--entry-width); } .feed__image { - -webkit-box-sizing: content-box; - box-sizing: content-box; - margin: calc(1.7rem + 1vw) auto; - max-width: calc(var(--entry-width) + 20%); - padding: 0 6vw; + background: var(--lighter); + border-radius: calc(var(--border-radius) * 4); + flex-shrink: 0; + height: 100%; + margin: 0; + width: 100%; +} +@media all and (min-width: 37.5em) { + .feed__image { + height: calc(var(--feed-image-size) + 4vw); + width: calc(var(--feed-image-size) + 4vw); + } } .feed__image > img { + border-radius: inherit; display: inline-block; - height: auto; + height: 100%; + -o-object-fit: cover; + object-fit: cover; width: 100%; } +.feed__image--wide { + max-width: var(--page-width); +} .feed__meta { align-items: center; color: var(--gray); display: flex; font-size: 0.8239746086rem; - margin-bottom: calc(-1.1333333333rem - 1vw); + margin-bottom: calc(var(--baseline) * 3); } .feed__author { font-family: var(--menu-font); font-variation-settings: "wght" var(--font-weight-bold); text-decoration: none; } -.feed__date { - color: var(--gray); - font-style: italic; +.feed__author-thumb { + border-radius: 50%; + height: 1.7rem; + margin-right: 0.6rem; + width: 1.7rem; +} +.feed__date { + color: var(--gray); + font-style: italic; } .feed__author + .feed__date { - margin-left: 0.85rem; + margin-left: 0.8rem; } .feed__author + .feed__date::before { content: ""; @@ -1344,16 +1303,22 @@ textarea { vertical-align: middle; } .feed__readmore { - margin-top: calc(1.1333333333rem + 0.25vw); + margin-top: calc(var(--baseline) * 4 + 0.25vw); } -.feed--grid { - display: grid; - grid-template-columns: 100%; - grid-gap: calc(1.7rem + 1.5vw); +.feed__title { margin-top: 0; - padding-top: calc(2.55rem + 1.5vw); +} +.feed--grid { + margin: 0; } @media all and (min-width: 37.5em) { + .feed--grid { + display: grid; + grid-template-columns: 100%; + gap: 0 3rem; + } +} +@media all and (min-width: 56.25em) { .feed--grid { grid-template-columns: repeat(2, 1fr); } @@ -1365,193 +1330,171 @@ textarea { font-size: 1.066666667rem; vertical-align: top; } -.feed--grid-tag-desc > :nth-child(1) { - margin-top: 0; +.feed--grid li { + align-items: center; + list-style: none; + gap: 2rem; + padding: 0; } -.post__image { - display: inline-block; +.content__meta { + margin-top: calc(var(--baseline) * 4 + 0.25vw); + margin-bottom: 0; } -.post__image > img { - display: inline-block; +.content__meta--centered { + justify-content: center; } -.post__image--left { - float: left; - margin-bottom: 1.7rem; - margin-right: 1.7rem; - max-width: 50%; +.content__entry { + margin-top: calc(var(--baseline) * 6 + 1.5vw); } -.post__image--right { - float: right; - margin-bottom: 1.7rem; - margin-left: 1.7rem; - max-width: 50%; +.content__entry > :nth-child(1) { + margin-top: 0; } -.post__image--center { - display: block; - margin-left: auto; - margin-right: auto; - text-align: center; +.content__entry a:not(.btn):not([type=button]):not([type=submit]):not(button):not(.post__toc a):not(.gallery__item a) { + color: var(--link-color-hover); + text-decoration: underline; + text-decoration-thickness: 1px; + text-underline-offset: 0.2em; + -webkit-text-decoration-skip: ink; + text-decoration-skip-ink: auto; } -.post__image--wide { - display: block; +.content__entry a:not(.btn):not([type=button]):not([type=submit]):not(button):not(.post__toc a):not(.gallery__item a):hover, .content__entry a:not(.btn):not([type=button]):not([type=submit]):not(button):not(.post__toc a):not(.gallery__item a):active, .content__entry a:not(.btn):not([type=button]):not([type=submit]):not(button):not(.post__toc a):not(.gallery__item a):focus { + color: var(--link-color); } -@media all and (min-width: 56.25em) { - .post__image--wide { - margin-left: calc(-1 * var(--page-margin)); - margin-right: calc(-1 * var(--page-margin)); - text-align: center; - } - .post__image--wide a, - .post__image--wide img { - display: block; - height: auto; - width: 100%; - } +.content__entry--nospace { + margin-top: 0; } -.post__image--full { - display: block; - margin-left: calc(-50vw + 50%); - margin-right: calc(-50vw + 50%); - text-align: center; +.content__avatar-thumbs { + border-radius: 50%; + height: 4.5rem; + width: 4.5rem; } -.post__image--full a, -.post__image--full img { - display: block; - height: auto; - width: 100%; +.content__footer { + margin-top: calc(var(--baseline) * 9 + 1vw); } -.post__meta { +.content__updated { color: var(--gray); - font-size: 0.8239746086rem; + font-size: 0.8789062495rem; font-style: italic; - margin-bottom: calc(-1.4166666667rem - 1vw); } -.post__meta--author { - border-top: 1px solid var(--hero-border-color); - font-style: normal; - display: inline-block; - margin-top: 1.7rem; - padding-top: 1.4166666667rem; +.content__actions { + align-items: baseline; + display: flex; + flex-basis: auto; + gap: 2rem; + justify-content: space-between; + margin-top: calc(var(--baseline) * 4 + 0.25vw); + position: relative; } -@media all and (min-width: 37.5em) { - .post__meta--author { - margin-top: 2.8333333333rem; - padding-top: 1.7rem; - } +.content__share { + flex-shrink: 0; } -.post__author-thumb { - border-radius: 50%; - height: 1.7rem; - margin-right: 0.5666666667rem; - width: 1.7rem; +.content__share-button { + border-color: var(--light); } -.post__entry { - margin-top: calc(2.55rem + 1.5vw); +.content__share-popup { + background: var(--lighter); + border-radius: calc(var(--border-radius) * 4); + bottom: 140%; + display: none; + padding: 1rem 0.85rem; + position: absolute; + right: 0; + text-align: left; + z-index: 1; } -.post__entry > :nth-child(1) { - margin-top: 0; +.content__share-popup.is-visible { + -webkit-animation: share-popup 0.48s cubic-bezier(0.17, 0.67, 0.6, 1.34) backwards; + animation: share-popup 0.48s cubic-bezier(0.17, 0.67, 0.6, 1.34) backwards; + display: block; } -.post__entry a:not(.btn):not([type=button]):not([type=submit]):not(button):not(.post__toc a):not(.gallery__item a) { - color: var(--link-color-hover); - text-decoration: underline; - text-decoration-thickness: 1px; - text-underline-offset: 0.2em; - -webkit-text-decoration-skip: ink; - text-decoration-skip-ink: auto; +@-webkit-keyframes share-popup { + from { + -webkit-transform: scale(0.9); + transform: scale(0.9); + } + to { + -webkit-transform: scale(1); + transform: scale(1); + } } -.post__entry a:not(.btn):not([type=button]):not([type=submit]):not(button):not(.post__toc a):not(.gallery__item a):hover, .post__entry a:not(.btn):not([type=button]):not([type=submit]):not(button):not(.post__toc a):not(.gallery__item a):active, .post__entry a:not(.btn):not([type=button]):not([type=submit]):not(button):not(.post__toc a):not(.gallery__item a):focus { - color: var(--link-color); +@keyframes share-popup { + from { + -webkit-transform: scale(0.9); + transform: scale(0.9); + } + to { + -webkit-transform: scale(1); + transform: scale(1); + } } -.post__avatar-thumbs { - border-radius: 50%; - height: 4.5333333333rem; - width: 4.5333333333rem; +.content__share-popup > a { + border-radius: calc(var(--border-radius) * 3); + color: var(--text-color); + display: block; + font-family: var(--menu-font); + font-size: 0.8239746086rem; + padding: 0.4rem 0.8rem; } -.post__last-updated { - color: var(--gray); - font-size: 0.7724761953rem; - font-style: italic; - margin-top: 2.2666666667rem; +.content__share-popup > a:hover { + background: var(--page-bg); + color: var(--text-color); + text-decoration: none; } -.post__last-updated + .post__tag { - margin: 1.1333333333rem 0 0; +.content__share-popup > a > svg { + fill: var(--text-color); + display: inline-block; + height: 0.9rem; + margin-right: 0.5666666667rem; + pointer-events: none; + vertical-align: middle; + width: 0.9rem; } -.post__tag { - margin: 2.2666666667rem 0 0; +.content__tag { + margin: 0; font-family: var(--menu-font); font-size: 0.8239746086rem; } -.post__tag > li { - display: inline-block; - margin-right: 0.2833333333rem; +.content__tag > li { + display: inline-flex; + margin: 0.3rem 0.3rem 0.3rem 0; padding: 0; } -.post__tag > li > a { - background: var(--lighter); - border-radius: calc(4 * var(--border-radius)); +.content__tag > li > a { + border: 1px solid var(--light); + border-radius: calc(var(--border-radius) * 10); color: var(--dark); font-size: 0.7241964329rem; font-variation-settings: "wght" var(--font-weight-normal); - padding: 0.425rem 0.85rem; + padding: calc(var(--baseline) * 1) calc(var(--baseline) * 2.5); } -.post__tag > li > a:hover { - background: var(--light); +.content__tag > li > a:hover { + border-color: var(--dark); } -.post__share { +.content__bio { display: flex; - flex-wrap: wrap; - margin: calc(2.8333333333rem + 1vw) -0.2833333333rem 0; -} -.post__share > a { - border-radius: calc(4 * var(--border-radius)); - flex: 1 1 auto; - margin: 0.2833333333rem; - line-height: 0; - padding: 0.7083333333rem 1.1333333333rem; - -webkit-transition: all 0.24s ease-out; - transition: all 0.24s ease-out; - text-align: center; -} -.post__share > a:hover { - -webkit-transform: translate3d(0, -2px, 0); - transform: translate3d(0, -2px, 0); + margin: calc(var(--baseline) * 12 + 1vw) 0; } -.post__share > a span { - color: var(--white); - font-family: var(--menu-font); - font-size: 0.5967194723rem; - font-variation-settings: "wght" var(--font-weight-bold); - margin-left: 0.2833333333rem; - text-transform: uppercase; -} -.post__share > a svg { - fill: var(--white); - height: 18px; - pointer-events: none; - width: 18px; - vertical-align: middle; -} -.post__bio { - align-items: center; - display: flex; - margin: calc(3.4rem + 1vw) 0 calc(3.4rem + 2vw); +@media all and (min-width: 37.5em) { + .content__bio { + align-items: center; + } } @media all and (min-width: 37.5em) { - .post__bio::before { + .content__bio::before { content: ""; border-top: 1px solid var(--light); height: 1px; margin-right: 2rem; - width: 20%; + width: 30%; } } .bio__avatar { border-radius: 50%; flex-shrink: 0; - height: 3rem; + height: 2.5rem; margin-right: 1.2rem; - width: 3rem; + width: 2.5rem; } @media all and (min-width: 37.5em) { .bio__avatar { @@ -1561,23 +1504,15 @@ textarea { } } .bio__name { - font-family: var(--menu-font); - font-size: 1rem; - font-variation-settings: "wght" var(--font-weight-bold); margin: 0; } .bio__desc { font-family: var(--body-font); - font-size: 0.8239746086rem; + font-size: 0.8789062495rem; line-height: 1.5; } -@media all and (min-width: 37.5em) { - .bio__desc { - width: 80%; - } -} .bio__desc > :nth-child(1) { - margin-top: 0.5666666667rem; + margin-top: calc(var(--baseline) * 2); } .bio__desc a { text-decoration: underline; @@ -1601,113 +1536,179 @@ textarea { outline: none; } -.post__nav { - border-top: 1px solid var(--light); - margin-top: calc(2.55rem + 1vw); - padding: 2.55rem var(--page-margin) 2.8333333333rem; - position: relative; +.content__nav { + margin-top: calc(var(--baseline) * 16 + 1vw); +} +.content__nav-inner { + border-top: 1px solid var(--dark); + border-bottom: 1px solid var(--dark); + padding: calc(var(--baseline) * 16) 0; } @media all and (min-width: 37.5em) { - .post__nav-inner { + .content__nav-inner { display: flex; + gap: 1rem; justify-content: space-between; } } -@media all and (min-width: 37.5em) { - .post__nav-prev, .post__nav-next { - align-items: center; - display: flex; +@media all and (min-width: 56.25em) { + .content__nav-inner { + gap: 2rem; } } -.post__nav-prev svg, .post__nav-next svg { - fill: var(--gray); -} @media all and (max-width: 37.4375em) { - .post__nav-prev svg, .post__nav-next svg { - display: none; + .content__nav-prev + .content__nav-next { + margin-top: calc(var(--baseline) * 6 + 1vw); } } @media all and (min-width: 37.5em) { - .post__nav-next { + .content__nav-next { margin-left: auto; text-align: right; } } -@media all and (max-width: 37.4375em) { - .post__nav-prev + .post__nav-next { - margin-top: 1.1333333333rem; - } -} -@media all and (min-width: 37.5em) { - .post__nav-prev + .post__nav-next { - margin-left: 1.7rem; - } -} -.post__nav-link { - font-family: var(--body-font); - font-size: 0.8239746086rem; +.content__nav-link { + font-family: var(--heading-font); + font-variation-settings: "wght" var(--font-weight-bold); font-style: italic; + height: 100%; line-height: 1.5; + display: flex; + gap: 1rem; + justify-content: space-between; + align-items: center; } @media all and (min-width: 37.5em) { - .post__nav-link[rel=prev] { - padding-left: 0.85rem; + .content__nav-link { + gap: 2rem; } } -@media all and (min-width: 37.5em) { - .post__nav-link[rel=next] { - padding-right: 0.85rem; +.content__nav-link > div { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; +} +@media all and (min-width: 56.25em) { + .content__nav-link > div { + -webkit-line-clamp: 4; } } -.post__nav-link > span { +.content__nav-link span { color: var(--gray); display: block; font-size: 0.7724761953rem; font-family: var(--menu-font); font-style: normal; + font-variation-settings: "wght" var(--font-weight-normal); + margin-bottom: var(--baseline); } -.post__nav + .post__related { - margin-top: 0; +.content__nav-image { + flex: 1 0 4rem; + margin: 0; + height: 4rem; } -.post__nav + .post__comments { - border-top: 1px solid var(--light); - margin-top: 0; +@media all and (min-width: 37.5em) and (max-width: 56.1875em) { + .content__nav-image { + flex-basis: 6rem; + height: 6rem; + } } -.post__related { +@media all and (min-width: 56.25em) { + .content__nav-image { + flex-basis: 8rem; + height: 8rem; + } +} +.content__nav-image > img { + border-radius: calc(var(--border-radius) * 4); + display: block; + height: 100%; + -o-object-fit: cover; + object-fit: cover; + width: 100%; +} +.content__related { background: var(--lighter); - margin-top: calc(2.55rem + 1vw); - margin-bottom: calc(-2.55rem - 1vw); - padding: calc(0.85rem + 1vw) 0 calc(3.4rem + 3vw); + margin-top: calc(var(--baseline) * 16 + 1vw); + padding: calc(var(--baseline) * 12 + 3vw) 0; } -.related__item { - margin-top: calc(3.4rem + 1vw); +.related__title { + margin-top: 0; } -.related__item::before { - content: ""; - border-top: 1px solid var(--light); - display: block; - height: 1px; - margin-bottom: 2rem; - width: 20%; + +.content__comments { + margin-top: calc(var(--baseline) * 9); + overflow: hidden; } -.post__related + .post__comments { - border-top: none; +.post__image { + display: inline-block; } -.post + .post__comments { - margin-top: 0; +.post__image a, +.post__image img { + border-radius: calc(var(--border-radius) * 4); + display: inline-block; } -.post + .post__comments .h5 { - margin-top: 0; +.post__image > figcaption { + background-color: var(--page-bg); } -.post__comments { - margin-top: calc(2.55rem + 1vw); - overflow: hidden; +.post__image--left { + float: left; + margin-bottom: 0; + margin-right: 2rem; + max-width: 50%; +} +.post__image--right { + float: right; + margin-bottom: 0; + margin-left: 2rem; + max-width: 50%; +} +.post__image--center { + display: block; + margin-left: auto; + margin-right: auto; + text-align: center; +} +.post__image--wide { + display: block; +} +@media all and (min-width: 56.25em) { + .post__image--wide { + margin-left: calc(-50vw + 50%); + margin-right: calc(-50vw + 50%); + padding: 0 var(--page-margin); + text-align: center; + } + .post__image--wide a, + .post__image--wide img { + display: block; + height: auto; + margin: auto; + max-width: var(--page-width); + width: 100%; + } +} +.post__image--full { + background-color: var(--lighter); + display: block; + margin-left: calc(-50vw + 50%); + margin-right: calc(-50vw + 50%); + text-align: center; +} +.post__image--full a, +.post__image--full img { + border-radius: 0; + display: block; + height: auto; + width: 100%; } .post__video, .post__iframe { display: block; - margin-top: calc(1.7rem + 0.5vw); - margin-bottom: calc(1.7rem + 0.5vw); + margin-top: calc(var(--baseline) * 6 + 0.5vw); + margin-bottom: calc(var(--baseline) * 6 + 0.5vw); overflow: hidden; padding: 0; position: relative; @@ -1728,13 +1729,15 @@ textarea { width: 100%; } .post__toc h3 { + border-bottom: 1px solid var(--dark); font-size: 1rem; margin: 0; + padding-bottom: calc(var(--baseline) * 2 + 0.25vw); } .post__toc ul { counter-reset: item; list-style: decimal; - margin: calc(0.5666666667rem + 0.25vw) 0 0 2ch; + margin: calc(var(--baseline) * 3 + 0.25vw) 0 0 3ch; } .post__toc ul li { counter-increment: item; @@ -1748,19 +1751,16 @@ textarea { } .post__toc ul ul li:before { content: counters(item, ".") ". "; - margin-left: -2ch; + margin-left: -3ch; } .banner { text-align: center; } -.banner--after-post { - margin-top: calc(2.55rem + 1vw); +.banner--after-content { + margin-top: calc(var(--baseline) * 9 + 1vw); } -.page__desc > :nth-child(1) { - margin-top: calc(0.2833333333rem + 0.25vw); -} .page__desc a { text-decoration: underline; text-decoration-thickness: 1px; @@ -1768,27 +1768,25 @@ textarea { -webkit-text-decoration-skip: ink; text-decoration-skip-ink: auto; } -.page--author__avatar { - border-radius: 50%; - height: 3rem; - margin-bottom: calc(-1.4166666667rem - 1vw); - width: 3rem; -} @media all and (min-width: 37.5em) { - .page--author__avatar { - height: 4rem; - width: 4rem; + .page--author__wrapper { + display: flex; + gap: 2rem; } } -.page--author__website { - align-items: center; - display: inline-flex; +@media all and (min-width: 56.25em) { + .page--author__wrapper { + gap: 3rem; + } } -.page--author__website a { - font-family: var(--menu-font); - font-variation-settings: "wght" var(--font-weight-bold); - margin-left: 0.4rem; - text-decoration: none; +.page--author__avatar { + border-radius: 50%; + height: calc(var(--baseline) * 10 + 2vw); + margin-top: calc(var(--baseline) * 6 + 1vw); + width: calc(var(--baseline) * 10 + 2vw); +} +.page--author__website { + margin-top: calc(var(--baseline) * 4 + 0.25vw); } .page--search form { align-items: flex-start; @@ -1797,13 +1795,13 @@ textarea { } @media all and (max-width: 37.4375em) { .page--search input { - margin-bottom: 0.5666666667rem; + margin-bottom: calc(var(--baseline) * 2); } } @media all and (min-width: 20em) { .page--search input { flex: 1 0 auto; - margin-right: 0.5666666667rem; + margin-right: calc(var(--baseline) * 2); } } @media all and (max-width: 37.4375em) { @@ -1812,6 +1810,32 @@ textarea { } } +.subpages__title { + border-top: 1px solid var(--light); + padding-top: calc(var(--baseline) * 6 + 0.5vw); +} +.subpages__list { + list-style: initial; + margin-left: 2ch; +} +.subpages__list ul { + list-style: initial; + margin: 0 0 0 2ch; +} +.subpages__list li { + padding-bottom: 0; +} + +.readmore { + display: inline-block; + color: var(--gray); + font-size: 0.9374999997rem; + font-style: italic; + text-decoration: underline; + -webkit-text-decoration-skip: ink; + text-decoration-skip-ink: auto; +} + .align-left { text-align: left; } @@ -1829,26 +1853,23 @@ textarea { } .msg { - border-left: 4px solid; + border: 1px solid var(--light); + border-left-width: 2px; font-size: 0.9374999997rem; - padding: 1.1333333333rem 1.7rem; + padding: calc(var(--baseline) * 6); position: relative; } .msg--highlight { - background-color: rgba(var(--highlight-color-rgb), 0.4); - border-color: var(--highlight-color); + border-left-color: var(--highlight-color); } .msg--info { - background-color: rgba(var(--info-color-rgb), 0.4); - border-color: var(--info-color); + border-left-color: var(--info-color); } .msg--success { - background-color: rgba(var(--success-color-rgb), 0.4); - border-color: var(--success-color); + border-left-color: var(--success-color); } .msg--warning { - background-color: rgba(var(--warning-color-rgb), 0.4); - border-color: var(--warning-color); + border-left-color: var(--warning-color); } .ordered-list { @@ -1857,7 +1878,7 @@ textarea { .ordered-list li { counter-increment: listCounter; list-style: none; - padding-left: 0.2833333333rem; + padding-left: 0.3rem; position: relative; } .ordered-list li::before { @@ -1873,8 +1894,8 @@ textarea { float: left; font-size: 3.6355864383rem; line-height: 0.7; - margin-right: 0.5666666667rem; - padding: 0.5666666667rem 0.5666666667rem 0.5666666667rem 0; + margin-right: 0.6rem; + padding: calc(var(--baseline) * 2) calc(var(--baseline) * 2) calc(var(--baseline) * 2) 0; } .pec-wrapper { @@ -1886,7 +1907,7 @@ textarea { } .pec-overlay { align-items: center; - background-color: var(--light); + background-color: var(--lighter); font-size: 14px; display: none; height: inherit; @@ -1910,103 +1931,69 @@ textarea { margin: 0 0 1rem; } -.facebook { - background: #0866FF; -} - -.twitter { - background: #000000; -} - -.instagram { - background: #000000; -} - -.vimeo { - background: #1ab7ea; -} - -.pinterest { - background: #bd081c; -} - -.youtube { - background: #cd201f; -} - -.linkedin { - background: #007bb6; -} - -.buffer { - background: #333333; -} - -.mix { - background: #fd8235; -} - -.whatsapp { - background: #25D366; -} - /* Pagination ------------------------ */ .pagination { display: flex; - margin-top: calc(3.4rem + 1vw); + gap: calc(var(--baseline) * 2); + justify-content: center; + margin-top: calc(var(--baseline) * 12 + 1vw); } @media all and (min-width: 56.25em) { .pagination { - margin-top: calc(5.1rem + 1vw); + margin-top: calc(var(--baseline) * 18 + 1vw); } } -.pagination__item + .pagination__item { - margin-left: 1.1333333333rem; -} -.pagination svg { - fill: var(--gray); -} /* Footer ------------------------ */ .footer { - background: var(--section-bg); - font-family: var(--menu-font); - overflow: hidden; - padding: calc(3.4rem + 1vw) var(--page-margin); - margin: calc(2.55rem + 1vw) 0 0; + border-top: 1px solid var(--light); + font-size: 0.9374999997rem; + padding: calc(var(--baseline) * 9 + 1vw) 0 calc(var(--baseline) * 6 + 1vw); + margin: calc(var(--baseline) * 12 + 1vw) auto 0; + max-width: var(--page-width); text-align: center; } +.footer--glued { + border: none; + padding-top: 0; +} +.footer__nav ul { + list-style: none; + margin: 0; +} +.footer__nav ul li { + display: inline-block; + padding: var(--baseline) 0.5rem; +} +* + .footer__social { + margin-top: calc(var(--baseline) * 6 + 0.5vw); +} .footer__social svg { - fill: var(--white); - height: 0.9374999997rem; - margin: 0 0.85rem; - opacity: 0.6; + fill: var(--dark); + height: 1rem; + margin: 0 0.5rem; -webkit-transition: all 0.12s linear 0s; transition: all 0.12s linear 0s; - width: 0.9374999997rem; + width: 1rem; } .footer__social svg:hover { - opacity: 1; + fill: var(--gray); } .footer__copyright { - color: var(--gray); - font-size: 0.6789341556rem; - letter-spacing: 1px; - text-transform: uppercase; - padding: 1.7rem 0; -} -.footer a { - color: var(--white); + font-size: 0.8239746086rem; + margin-top: var(--baseline); } -.footer a:hover { - color: rgba(var(--white-rgb), 0.7); +.footer__copyright > *:first-child { + margin: 0; } .footer__bttop { - bottom: 1.4166666667rem; + background: var(--page-bg); + bottom: calc(var(--baseline) * 5); border-radius: 50%; + border-color: var(--light); line-height: 1; opacity: 0; - padding: 0.51rem; + padding: calc(var(--baseline) * 2); position: fixed; right: 2rem; text-align: center; @@ -2016,25 +2003,20 @@ textarea { } @media all and (min-width: 56.25em) { .footer__bttop { - bottom: 2.8333333333rem; + bottom: calc(var(--baseline) * 10); } } .footer__bttop:hover { + border-color: var(--dark); opacity: 1; } -.footer__bttop > svg { - fill: var(--white); - height: 23px; - margin: 0; - width: 23px; -} .footer__bttop.is-visible { visibility: visible; opacity: 1; } .gallery { - margin: calc(1.7rem + 1vw) calc(var(--gallery-gap) * -1); + margin: calc(var(--baseline) * 6 + 1vw) calc(var(--gallery-gap) * -1); } @media all and (min-width: 20em) { .gallery { @@ -2044,16 +2026,22 @@ textarea { } @media all and (min-width: 56.25em) { .gallery-wrapper--wide { - margin-left: calc(-1 * var(--page-margin)); - margin-right: calc(-1 * var(--page-margin)); + display: flex; + justify-content: center; + margin-left: calc(-50vw + 50%); + margin-right: calc(-50vw + 50%); + padding: 0 var(--page-margin); } .gallery-wrapper--wide .gallery { - width: calc(100% + var(--gallery-gap) * 2); + max-width: var(--page-width); } } -.gallery-wrapper--full { - margin-left: calc(-50vw + 50%); - margin-right: calc(-50vw + 50%); +@media all and (min-width: 56.25em) { + .gallery-wrapper--full { + margin-left: calc(-50vw + 50%); + margin-right: calc(-50vw + 50%); + padding: 0 var(--page-margin); + } } @media all and (min-width: 20em) { .gallery[data-columns="1"] .gallery__item { @@ -2116,6 +2104,7 @@ textarea { } } .gallery__item a { + border-radius: calc(var(--border-radius) * 4); display: block; height: 100%; width: 100%; @@ -2123,6 +2112,7 @@ textarea { .gallery__item a::after { background: -webkit-gradient(linear, left bottom, left top, from(rgba(0, 0, 0, 0.4)), to(rgba(0, 0, 0, 0))); background: linear-gradient(to top, rgba(0, 0, 0, 0.4) 0%, rgba(0, 0, 0, 0) 100%); + border-radius: inherit; bottom: var(--gallery-gap); content: ""; display: block; @@ -2140,6 +2130,7 @@ textarea { opacity: 1; } .gallery__item img { + border-radius: inherit; display: block; height: 100%; -o-object-fit: cover; @@ -2167,6 +2158,11 @@ textarea { .pswp--dark .pswp__bg { background: black; } +.pswp--dark .pswp__button, +.pswp--dark .pswp__button--arrow--left:before, +.pswp--dark .pswp__button--arrow--right:before { + background-image: url(../svg/gallery-icons-light.svg); +} .pswp--light .pswp__bg { background: var(--white); } @@ -2176,6 +2172,11 @@ textarea { .pswp--light .pswp__caption__center { color: var(--text-color); } +.pswp--light .pswp__button, +.pswp--light .pswp__button--arrow--left:before, +.pswp--light .pswp__button--arrow--right:before { + background-image: url(../svg/gallery-icons-dark.svg); +} .pswp .pswp__button { -webkit-box-shadow: none; box-shadow: none; diff --git a/app/default-files/default-themes/simple/assets/css/style.css b/app/default-files/default-themes/simple/assets/css/style.css index 405ebad41..d357ae9bc 100755 --- a/app/default-files/default-themes/simple/assets/css/style.css +++ b/app/default-files/default-themes/simple/assets/css/style.css @@ -1 +1 @@ -@font-face{font-family:Lora;src:url('../dynamic/fonts/lora/lora.woff2') format('woff2 supports variations'),url('../dynamic/fonts/lora/lora.woff2') format('woff2-variations');font-weight:400 700;font-display:swap;font-style:normal}:root{--page-margin:6vw;--entry-width:42rem;--navbar-height:4.4rem;--border-radius:6px;--gallery-gap:0.28333rem;--body-font:'Lora',serif;--heading-font:'Lora',serif;--logo-font:var(--body-font);--menu-font:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--font-weight-normal:450;--font-weight-bold:700;--headings-weight:700;--headings-transform:none;--line-height:1.7;--hero-height:80vh;--hero-bg:#17181E;--hero-heading-color:#FFFFFF;--hero-text-color:rgba(255,255,255,0.75);--hero-link-color:#FFFFFF;--hero-link-color-hover:rgba(255,255,255,0.75);--hero-border-color:rgba(255,255,255,0.3);--white:#FFFFFF;--white-rgb:255,255,255;--black:#000000;--dark:#17181E;--gray:#747577;--light:#E6E7EB;--lighter:#F3F3F3;--page-bg:#FFFFFF;--section-bg:#17181E;--color:#D73A42;--color-rgb:215,58,66;--text-color:#17181E;--headings-color:#17181E;--link-color:#17181E;--link-color-hover:#D73A42;--nav-link-color:rgba(255,255,255,1);--nav-link-color-hover:rgba(255,255,255,.7);--logo-color:#FFFFFF;--highlight-color:#F6DC90;--highlight-color-rgb:246,220,144;--info-color:#A8D8FF;--info-color-rgb:168,216,255;--success-color:#A4E4B2;--success-color-rgb:164,228,178;--warning-color:#FFC1BF;--warning-color-rgb:255,193,191}@media all and (min-width:56.25em){:root{--navbar-height:6rem}}@media (prefers-color-scheme:dark){:root{--white:#FFFFFF;--white-rgb:255,255,255;--black:#FFFFFF;--dark:#CECBCB;--gray:#9D9D9D;--light:#373737;--lighter:#1e1e1e;--page-bg:#181818;--section-bg:#1e1e1e;--color:#FFC074;--color-rgb:255,192,116;--text-color:#BFBFBF;--headings-color:#EEEDED;--link-color:#EEEDED;--link-color-hover:#FFC074;--nav-link-color:rgba(255,255,255,1);--nav-link-color-hover:rgba(255,255,255,.7);--logo-color:#FFFFFF;--highlight-color:#F6DC90;--highlight-color-rgb:24,24,24;--info-color:#5B9ED5;--info-color-rgb:24,24,24;--success-color:#54A468;--success-color-rgb:24,24,24;--warning-color:#FB6762;--warning-color-rgb:24,24,24}}*,:after,:before{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0}article,aside,footer,header,hgroup,main,nav,section{display:block}li{list-style:none}img{height:auto;max-width:100%;vertical-align:top}button,input,select,textarea{font:inherit}address{font-style:normal}::-moz-selection{background:var(--color);color:var(--white)}::selection{background:var(--color);color:var(--white)}html{font-size:clamp(1.1rem, 1.1rem + .1 * ((100vw - 20rem) / 50), 1.2rem);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;scroll-behavior:smooth}html.no-scroll{overflow:hidden;position:fixed}body{background:var(--page-bg);color:var(--text-color);font-family:var(--body-font);font-variation-settings:"wght" var(--font-weight-normal);line-height:var(--line-height);-ms-scroll-chaining:none;overscroll-behavior:none}a{text-decoration:none}a{color:var(--link-color);-webkit-transition:all .24s ease-out;transition:all .24s ease-out}a:hover{color:var(--link-color-hover)}a:active{color:var(--link-color-hover)}a:focus{outline:0}.invert{color:var(--link-color-hover);-webkit-transition:all .24s ease-out;transition:all .24s ease-out}.invert:hover{color:var(--link-color)}.invert:active{color:var(--link-color)}.invert:focus{outline:0}dl,ol,p,ul{margin-top:calc(1.1333333333rem + .25vw)}blockquote,figure,hr,pre,table{margin-top:calc(1.7rem + .5vw);margin-bottom:calc(1.7rem + .5vw)}h1,h2,h3,h4,h5,h6{color:var(--headings-color);font-family:var(--heading-font);font-variation-settings:"wght" var(--headings-weight);-webkit-hyphens:manual;-ms-hyphens:manual;hyphens:manual;line-height:1.2;margin-top:calc(1.7rem + 1vw);text-transform:var(--headings-transform)}.h1,h1{font-size:clamp(1.5710900065rem, 1.5710900065rem + 1.424540906 * ((100vw - 20rem) / 50), 2.9956309125rem);font-family:var(--heading-font)}.h2,h2{font-size:clamp(1.3808408252rem, 1.3808408252rem + .9332127447 * ((100vw - 20rem) / 50), 2.3140535699rem)}.h3,h3{font-size:clamp(1.2136296308rem, 1.2136296308rem + .4621997101 * ((100vw - 20rem) / 50), 1.6758293408rem)}.h4,h4{font-size:clamp(1.1377777785rem, 1.1377777785rem + .1567604947 * ((100vw - 20rem) / 50), 1.2945382732rem)}.h5,h5{font-size:clamp(1.066666667rem, 1.066666667rem + .0711111115 * ((100vw - 20rem) / 50), 1.1377777785rem)}.h6,h6{font-size:clamp(1rem, 1rem + 0 * ((100vw - 20rem) / 50), 1rem)}h2+*,h3+*,h4+*,h5+*,h6+*{margin-top:calc(.5666666667rem + .25vw)}b,strong{font-variation-settings:"wght" var(--font-weight-bold)}blockquote{color:var(--headings-color);font-family:var(--heading-font);padding:3.6833333333rem 0 0;position:relative;text-align:center;font-size:clamp(1.1377777785rem, 1.1377777785rem + .2430630467 * ((100vw - 20rem) / 50), 1.3808408252rem)}blockquote::before{color:var(--dark);content:"“";font:normal 320%/0.9 Georgia,Times,Times New Roman,serif;top:.5666666667rem;position:absolute;left:50%;-webkit-transform:translate(-50%,0);transform:translate(-50%,0)}blockquote>:first-child{margin-top:0}ol,ul{margin-left:2rem}ol>li,ul>li{list-style:inherit;padding:0 0 .2833333333rem .85rem}dl dt{font-variation-settings:"wght" var(--font-weight-bold)}pre{background-color:var(--lighter);font-size:.8239746086rem;padding:1.7rem;white-space:pre-wrap;word-wrap:break-word}pre>code{color:var(--text-color);display:inline-block;font-size:inherit;padding:0}code{background-color:var(--lighter);color:var(--color);font-size:.8239746086rem;font-family:Menlo,Monaco,Consolas,Courier New,monospace}table{border:1px solid var(--light);border-collapse:collapse;border-spacing:0;vertical-align:top;text-align:left;width:100%}table th{font-variation-settings:"wght" var(--font-weight-bold);padding:.7083333333rem 1.1333333333rem}table td{border-top:1px solid var(--light);padding:.7083333333rem 1.1333333333rem}.table-striped tr:nth-child(2n){background:var(--lighter)}.table-bordered td,.table-bordered th{border:1px solid var(--light)}.table-title th{background:var(--lighter)}figcaption{clear:both;color:var(--gray);font-style:italic;font-size:.7241964329rem;margin:.85rem 0 0;text-align:center}kbd{background:var(--dark);border-radius:2px;color:var(--white);font-family:Menlo,Monaco,Consolas,Courier New,monospace;font-size:.8789062495rem;padding:.1416666667rem .425rem}sub,sup{font-size:65%}small{font-size:.8789062495rem}.separator,hr{background:0 0;border:none;height:auto;line-height:1;max-width:none;text-align:center}.separator::before,hr::before{content:"···";color:var(--dark);font-size:1.2136296308rem;font-variation-settings:"wght" var(--font-weight-bold);letter-spacing:1.1377777785rem;padding-left:1.1377777785rem}.separator--dot::before{content:"·";color:var(--dark);font-size:1.2136296308rem;font-variation-settings:"wght" var(--font-weight-bold);letter-spacing:1.1377777785rem;padding-left:1.1377777785rem}.separator--long-line{position:relative}.separator--long-line::before{content:"";height:1.2136296308rem}.separator--long-line::after{border-top:1px solid var(--light);content:"";height:1px;position:absolute;width:100%;top:50%;left:0}.btn,[type=button],[type=submit],button{background:var(--color);border:1px solid var(--color);border-radius:2px;-webkit-box-shadow:0 4px 6px rgba(0,0,0,.1),0 1px 1px rgba(0,0,0,.08);box-shadow:0 4px 6px rgba(0,0,0,.1),0 1px 1px rgba(0,0,0,.08);color:var(--white);cursor:pointer;display:inline-block;font-family:var(--body-font);font-size:.6789341556rem;font-variation-settings:"wght" var(--font-weight-bold);overflow:hidden;padding:.5666666667rem 1.4166666667rem;text-align:center;-webkit-transition:all .24s ease-out;transition:all .24s ease-out;text-transform:uppercase;vertical-align:middle;will-change:transform}@media all and (max-width:19.9375em){.btn,[type=button],[type=submit],button{width:100%}}@media all and (max-width:37.4375em){.btn,[type=button],[type=submit],button{margin-bottom:.5666666667rem}}.btn:active,.btn:focus,.btn:hover,[type=button]:active,[type=button]:focus,[type=button]:hover,[type=submit]:active,[type=submit]:focus,[type=submit]:hover,button:active,button:focus,button:hover{-webkit-box-shadow:0 7px 14px rgba(0,0,0,.1),0 3px 6px rgba(0,0,0,.08);box-shadow:0 7px 14px rgba(0,0,0,.1),0 3px 6px rgba(0,0,0,.08);color:var(--white);-webkit-transform:translateY(-2px);transform:translateY(-2px)}@media all and (min-width:20em){.btn+.btn,.btn+[type=button],.btn+[type=submit],.btn+button,[type=button]+.btn,[type=button]+[type=button],[type=button]+[type=submit],[type=button]+button,[type=submit]+.btn,[type=submit]+[type=button],[type=submit]+[type=submit],[type=submit]+button,button+.btn,button+[type=button],button+[type=submit],button+button{margin-left:.5666666667rem}}.btn:disabled,[type=button]:disabled,[type=submit]:disabled,button:disabled{background-color:var(--light);border-color:var(--light);color:var(--gray);cursor:not-allowed}[type=button],[type=submit],button{-webkit-appearance:none;-moz-appearance:none}::-webkit-search-cancel-button{-webkit-appearance:none}fieldset{border:1px solid var(--light);margin:calc(1.7rem + 1vw) 0 0;padding:1.7rem}fieldset>legend{margin-left:-1rem;padding:0 1rem}legend{font-variation-settings:"wght" 500;padding:0}label{font-variation-settings:"wght" 500;margin:0 1.1333333333rem .85rem 0}[type=email],[type=number],[type=search],[type=tel],[type=text],[type=url],select,textarea{background-color:var(--page-bg);border:none;border:1px solid var(--light);color:var(--text-color);font-size:1rem;outline:0;padding:.34rem .85rem;vertical-align:middle;width:100%;-webkit-appearance:none;-moz-appearance:none}@media all and (min-width:37.5em){[type=email],[type=number],[type=search],[type=tel],[type=text],[type=url],select,textarea{width:auto}}[type=email]:focus,[type=number]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=url]:focus,select:focus,textarea:focus{border-color:var(--dark)}input[type=checkbox],input[type=radio]{opacity:0;position:absolute}input[type=checkbox]+label,input[type=radio]+label{position:relative;margin-left:-1px;cursor:pointer;padding:0}input[type=checkbox]+label:before,input[type=radio]+label:before{background-color:var(--white);border:1px solid var(--light);border-radius:2px;content:"";display:inline-block;height:1.4166666667rem;line-height:1.4166666667rem;margin-right:1.1333333333rem;vertical-align:middle;text-align:center;width:1.4166666667rem}input[type=checkbox]:checked+label:before,input[type=radio]:checked+label:before{content:"";background-image:url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 11 8'%3e%3cpolygon points='9.53 0 4.4 5.09 1.47 2.18 0 3.64 2.93 6.54 4.4 8 5.87 6.54 11 1.46 9.53 0' fill='%23d73a42'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:11px 8px;background-position:50% 50%}input[type=radio]+label:before{border-radius:50%}input[type=radio]:checked+label:before{background-image:url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3ccircle cx='4' cy='4' r='4' fill='%23d73a42'/%3e%3c/svg%3e")}[type=file]{margin-bottom:1.7rem;width:100%}select{max-width:100%;width:auto;position:relative}select:not([multiple]){background:url('data:image/svg+xml;utf8,') no-repeat 90% 50%;background-size:8px;padding-right:3.4rem}select[multiple]{border:1px solid var(--light);padding:1.7rem;width:100%}select[multiple]:hover{border-color:var(--light)}select[multiple]:focus{border-color:var(--dark)}select[multiple]:disabled{background-color:var(--light);cursor:not-allowed}select[multiple]:disabled:hover{border-color:var(--light)}textarea{display:block;overflow:auto;resize:vertical;max-width:100%}.top{align-items:center;display:flex;height:var(--navbar-height);position:absolute;padding:0 var(--page-margin);-webkit-transition:background .5s ease;transition:background .5s ease;width:100%;z-index:2}@media all and (min-width:56.25em){.top{justify-content:space-between;height:var(--navbar-height)}}.top.is-visible{background:var(--section-bg);-webkit-box-shadow:0 3px 10px 0 rgba(0,0,0,.06);box-shadow:0 3px 10px 0 rgba(0,0,0,.06);opacity:1;position:fixed;-webkit-transform:translate(0,0);transform:translate(0,0);-webkit-transition:height .3s,background .3s,opacity .24s,-webkit-transform .24s;transition:height .3s,background .3s,opacity .24s,-webkit-transform .24s;transition:transform .24s,height .3s,background .3s,opacity .24s;transition:transform .24s,height .3s,background .3s,opacity .24s,-webkit-transform .24s;width:100%}@media all and (min-width:56.25em){.top.is-hidden{opacity:0;-webkit-transform:translate(0,-86px);transform:translate(0,-86px);-webkit-transition:background .3s,color .3s,opacity .24s,-webkit-transform .24s;transition:background .3s,color .3s,opacity .24s,-webkit-transform .24s;transition:transform .24s,background .3s,color .3s,opacity .24s;transition:transform .24s,background .3s,color .3s,opacity .24s,-webkit-transform .24s}}.logo{color:var(--logo-color)!important;font-size:1.2136296308rem;font-family:var(--logo-font);font-variation-settings:"wght" var(--font-weight-normal);margin-right:auto}.logo>img{height:var(--navbar-height);-o-object-fit:contain;object-fit:contain;padding:.5666666667rem 0;width:auto}.search{order:2}@media all and (min-width:56.25em){.search{order:3}}.search__btn{border:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important;margin:0;padding:.5666666667rem;width:auto}@media all and (min-width:56.25em){.search__btn{margin-left:1rem;padding-right:0}}.search__btn:focus,.search__btn:hover{-webkit-transform:translateY(0);transform:translateY(0)}.search__btn:focus>svg,.search__btn:hover>svg{fill:rgba(255,255,255,0.7)}.search__btn>svg{display:block;fill:var(--white);height:15px;-webkit-transition:all .24s ease;transition:all .24s ease;width:15px}.search__form{flex-basis:90%}.search__input{background:0 0;border:none!important;color:var(--white);display:none;font-family:var(--heading-font);margin:0!important;opacity:0;padding:0;width:100%;font-size:clamp(1.2136296308rem, 1.2136296308rem + .3574603758 * ((100vw - 20rem) / 50), 1.5710900065rem)}.search__close{background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important;border:none;color:transparent;cursor:pointer;margin:0!important;opacity:0;padding:0;height:1.6rem;width:1.6rem;position:relative;text-indent:-999rem}.search__close:after,.search__close:before{background-color:var(--white);content:"";left:1rem;height:1.6rem;opacity:1;position:absolute;width:1px;top:0;-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-transition:all .14s ease-out;transition:all .14s ease-out}.search__close:after{-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.search__close:hover{-webkit-transform:translateY(0);transform:translateY(0)}.search__close:hover:after,.search__close:hover:before{background-color:rgba(255,255,255,.7)}.search__overlay{background-color:var(--section-bg);-webkit-box-shadow:0 3px 3px rgba(0,0,0,.05);box-shadow:0 3px 3px rgba(0,0,0,.05);left:0;opacity:0;position:fixed;-webkit-transition:all .24s ease-out;transition:all .24s ease-out;top:0;visibility:hidden;width:100%;z-index:2005}.search__overlay-inner{align-items:center;display:flex;height:4.4rem;justify-content:space-between;padding:0 var(--page-margin)}@media all and (min-width:56.25em){.search__overlay-inner{height:6rem}}.search__overlay.expanded{-webkit-transform:translate(0,0);transform:translate(0,0);opacity:1;display:block;visibility:visible}.search__overlay.expanded .search__input{-webkit-animation:slideininput .24s .12s forwards;animation:slideininput .24s .12s forwards;display:block}@-webkit-keyframes slideininput{60%{opacity:0}100%{opacity:1}}@keyframes slideininput{60%{opacity:0}100%{opacity:1}}.search__overlay.expanded .search__close{-webkit-animation:slideinclose .24s .12s forwards;animation:slideinclose .24s .12s forwards}@-webkit-keyframes slideinclose{60%{opacity:0}100%{opacity:1}}@keyframes slideinclose{60%{opacity:0}100%{opacity:1}}.navbar{order:3}.navbar .navbar__menu{display:flex;flex-wrap:wrap;list-style:none;margin:0;padding:0}@media all and (max-width:56.1875em){.navbar .navbar__menu{display:none}}.navbar .navbar__menu li{font-family:var(--menu-font);display:block;font-size:.7724761953rem;line-height:var(--line-height);font-variation-settings:"wght" 500;padding:0;position:relative;text-transform:uppercase;width:auto}.navbar .navbar__menu li a,.navbar .navbar__menu li span[aria-haspopup=true]{color:var(--nav-link-color);display:block;padding:0 .5666666667rem;-webkit-transition:all .24s ease-out;transition:all .24s ease-out}.navbar .navbar__menu li a:active,.navbar .navbar__menu li a:focus,.navbar .navbar__menu li a:hover,.navbar .navbar__menu li span[aria-haspopup=true]:active,.navbar .navbar__menu li span[aria-haspopup=true]:focus,.navbar .navbar__menu li span[aria-haspopup=true]:hover{color:var(--nav-link-color-hover)}.navbar .navbar__menu li span{color:var(--nav-link-color);cursor:default;display:block;padding:0 .5666666667rem}.navbar .navbar__menu>li:hover>a,.navbar .navbar__menu>li:hover>span[aria-haspopup=true]{color:var(--nav-link-color-hover)}.navbar .has-submenu:active>.navbar__submenu,.navbar .has-submenu:focus>.navbar__submenu,.navbar .has-submenu:hover>.navbar__submenu{left:0;opacity:1;-webkit-transform:scale(1);transform:scale(1);visibility:visible;margin-top:.85rem}.navbar .has-submenu:active>.navbar__submenu:before,.navbar .has-submenu:focus>.navbar__submenu:before,.navbar .has-submenu:hover>.navbar__submenu:before{content:"";height:.85rem;position:absolute;width:100%;top:-.85rem}.navbar .has-submenu:active>.navbar__submenu.is-right-submenu,.navbar .has-submenu:focus>.navbar__submenu.is-right-submenu,.navbar .has-submenu:hover>.navbar__submenu.is-right-submenu{left:auto;right:0;-webkit-transform-origin:right top;transform-origin:right top}.navbar .has-submenu .has-submenu:active>.navbar__submenu,.navbar .has-submenu .has-submenu:focus>.navbar__submenu,.navbar .has-submenu .has-submenu:hover>.navbar__submenu{top:0;margin-top:0}.navbar .has-submenu .has-submenu:active>.navbar__submenu.is-right-submenu,.navbar .has-submenu .has-submenu:focus>.navbar__submenu.is-right-submenu,.navbar .has-submenu .has-submenu:hover>.navbar__submenu.is-right-submenu{top:0;margin-top:0}.navbar .navbar__submenu{background:var(--section-bg);-webkit-box-shadow:0 5px 5px rgba(0,0,0,.25);box-shadow:0 5px 5px rgba(0,0,0,.25);border-radius:var(--border-radius);left:-9999px;list-style-type:none;margin:0;padding:10px 0;position:absolute;visibility:hidden;white-space:nowrap;z-index:1;opacity:0;-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:0 top;transform-origin:0 top;-webkit-transition:opacity .15s,-webkit-transform .3s cubic-bezier(.275, 1.375, .8, 1);transition:opacity .15s,-webkit-transform .3s cubic-bezier(.275, 1.375, .8, 1);transition:opacity .15s,transform .3s cubic-bezier(.275, 1.375, .8, 1);transition:opacity .15s,transform .3s cubic-bezier(.275, 1.375, .8, 1),-webkit-transform .3s cubic-bezier(.275, 1.375, .8, 1)}.navbar .navbar__submenu__submenu{z-index:2}.navbar .navbar__submenu li{line-height:1.5;font-size:.7241964329rem}.navbar .navbar__submenu li a,.navbar .navbar__submenu li span[aria-haspopup=true]{color:var(--nav-link-color-hover)!important;padding:.5666666667rem 1.4166666667rem;-webkit-transition:all .24s ease;transition:all .24s ease}.navbar .navbar__submenu li a:active,.navbar .navbar__submenu li a:focus,.navbar .navbar__submenu li a:hover,.navbar .navbar__submenu li span[aria-haspopup=true]:active,.navbar .navbar__submenu li span[aria-haspopup=true]:focus,.navbar .navbar__submenu li span[aria-haspopup=true]:hover{background:rgba(var(--white-rgb),.05);color:var(--nav-link-color)!important}.navbar .navbar__submenu li span{color:var(--nav-link-color-hover)!important;padding:.5666666667rem 1.4166666667rem}.navbar .navbar__submenu li:hover>a,.navbar .navbar__submenu li:hover>span[aria-haspopup=true]{color:var(--nav-link-color)!important}.navbar .navbar__toggle{background:var(--section-bg);-webkit-box-shadow:none;box-shadow:none;border:none;border-radius:3px;cursor:pointer;display:block;line-height:1;margin:0;overflow:visible;padding:1rem;position:relative;right:-1rem;text-transform:none;z-index:2004}@media all and (min-width:56.25em){.navbar .navbar__toggle{display:none}}.navbar .navbar__toggle:focus,.navbar .navbar__toggle:hover{-webkit-box-shadow:none;box-shadow:none;outline:0;-webkit-transform:none;transform:none}.navbar .navbar__toggle-box{width:24px;height:14px;display:inline-block;position:relative}.navbar .navbar__toggle-inner{display:block;top:50%;text-indent:-9999999em}.navbar .navbar__toggle-inner::before{content:"";display:block;top:-6px}.navbar .navbar__toggle-inner::after{content:"";display:block;bottom:-6px}.navbar .navbar__toggle-inner,.navbar .navbar__toggle-inner::after,.navbar .navbar__toggle-inner::before{width:22px;height:2px;background-color:var(--white);position:absolute;-webkit-transition:opacity .14s ease-out,-webkit-transform;transition:opacity .14s ease-out,-webkit-transform;transition:transform,opacity .14s ease-out;transition:transform,opacity .14s ease-out,-webkit-transform}.navbar .navbar__toggle-inner{-webkit-transition-duration:75ms;transition-duration:75ms;-webkit-transition-timing-function:cubic-bezier(0.55,0.055,0.675,0.19);transition-timing-function:cubic-bezier(0.55,0.055,0.675,0.19)}.navbar .navbar__toggle-inner::before{-webkit-transition:top 75ms ease .12s,opacity 75ms ease;transition:top 75ms ease .12s,opacity 75ms ease}.navbar .navbar__toggle-inner::after{-webkit-transition:bottom 75ms ease .12s,-webkit-transform 75ms cubic-bezier(.55, .055, .675, .19);transition:bottom 75ms ease .12s,-webkit-transform 75ms cubic-bezier(.55, .055, .675, .19);transition:bottom 75ms ease .12s,transform 75ms cubic-bezier(.55, .055, .675, .19);transition:bottom 75ms ease .12s,transform 75ms cubic-bezier(.55, .055, .675, .19),-webkit-transform 75ms cubic-bezier(.55, .055, .675, .19)}.navbar .navbar__toggle.is-active .navbar__toggle-inner{-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-transition-delay:0.12s;transition-delay:0.12s;-webkit-transition-timing-function:cubic-bezier(0.215,0.61,0.355,1);transition-timing-function:cubic-bezier(0.215,0.61,0.355,1)}.navbar .navbar__toggle.is-active .navbar__toggle-inner::before{top:0;opacity:0;-webkit-transition:top 75ms ease,opacity 75ms ease .12s;transition:top 75ms ease,opacity 75ms ease .12s}.navbar .navbar__toggle.is-active .navbar__toggle-inner::after{bottom:0;-webkit-transform:rotate(-90deg);transform:rotate(-90deg);-webkit-transition:bottom 75ms ease,-webkit-transform 75ms cubic-bezier(.215, .61, .355, 1) .12s;transition:bottom 75ms ease,-webkit-transform 75ms cubic-bezier(.215, .61, .355, 1) .12s;transition:bottom 75ms ease,transform 75ms cubic-bezier(.215, .61, .355, 1) .12s;transition:bottom 75ms ease,transform 75ms cubic-bezier(.215, .61, .355, 1) .12s,-webkit-transform 75ms cubic-bezier(.215, .61, .355, 1) .12s}.navbar_mobile_overlay{background:var(--page-bg);height:calc(100vh - 4.4rem);left:0;opacity:1;overflow:auto;pointer-events:auto;position:fixed;top:4.4rem;-webkit-transition:all .3s cubic-bezier(0, 0, .3, 1);transition:all .3s cubic-bezier(0, 0, .3, 1);width:100%;z-index:1001}.navbar_mobile_overlay.is-hidden{opacity:0;pointer-events:none}.navbar_mobile_overlay .navbar__menu{margin:24px}.navbar_mobile_overlay .navbar__menu li{list-style:none;margin:0;padding:0;text-align:center}.navbar_mobile_overlay .navbar__menu li a,.navbar_mobile_overlay .navbar__menu li span{color:var(--dark);display:block;padding:.5666666667rem;position:relative}.navbar_mobile_overlay .navbar__menu li a:active,.navbar_mobile_overlay .navbar__menu li a:focus,.navbar_mobile_overlay .navbar__menu li a:hover,.navbar_mobile_overlay .navbar__menu li span:active,.navbar_mobile_overlay .navbar__menu li span:focus,.navbar_mobile_overlay .navbar__menu li span:hover{color:var(--dark)}.navbar_mobile_overlay .navbar__menu li a[aria-haspopup=true]::after,.navbar_mobile_overlay .navbar__menu li span[aria-haspopup=true]::after{content:"";width:0;height:0;border-style:solid;border-width:5px 5px 0 5px;border-color:currentColor transparent transparent transparent;left:.5666666667rem;top:15px;position:relative}.navbar_mobile_overlay .navbar__submenu{margin:0;padding:0;visibility:hidden}.navbar_mobile_overlay .navbar__submenu[aria-hidden=false]{visibility:visible}.navbar_mobile_overlay .navbar__submenu_wrapper{height:0;opacity:0;overflow:hidden;-webkit-transition:all .3s cubic-bezier(.275, 1.375, .8, 1);transition:all .3s cubic-bezier(.275, 1.375, .8, 1)}.navbar_mobile_overlay .navbar__submenu_wrapper.is-active{height:auto;opacity:1}.navbar_mobile_sidebar{background:var(--page-bg);-webkit-box-shadow:0 0 5px rgba(0,0,0,.25);box-shadow:0 0 5px rgba(0,0,0,.25);height:100vh;left:0;max-width:400px;overflow:auto;position:fixed;top:0;-webkit-transition:all .3s cubic-bezier(0, 0, .3, 1);transition:all .3s cubic-bezier(0, 0, .3, 1);width:80%;z-index:1001}.navbar_mobile_sidebar.is-hidden{left:-400px}.navbar_mobile_sidebar .navbar__menu{margin:24px}.navbar_mobile_sidebar .navbar__menu li{font-family:var(--menu-font);font-size:16px;list-style:none;line-height:1.3;margin:0;padding:0}.navbar_mobile_sidebar .navbar__menu li .is-separator,.navbar_mobile_sidebar .navbar__menu li a{color:var(--dark);display:block;padding:10px 20px 10px 0;position:relative}.navbar_mobile_sidebar .navbar__menu li .is-separator:active,.navbar_mobile_sidebar .navbar__menu li .is-separator:focus,.navbar_mobile_sidebar .navbar__menu li .is-separator:hover,.navbar_mobile_sidebar .navbar__menu li a:active,.navbar_mobile_sidebar .navbar__menu li a:focus,.navbar_mobile_sidebar .navbar__menu li a:hover{color:var(--dark)}.navbar_mobile_sidebar .navbar__menu li .is-separator[aria-haspopup=true]::after,.navbar_mobile_sidebar .navbar__menu li a[aria-haspopup=true]::after{content:"";width:0;height:0;border-style:solid;border-width:5px 5px 0 5px;border-color:currentColor transparent transparent transparent;right:0;top:18px;position:absolute}.navbar_mobile_sidebar .navbar__submenu{margin:0 0 0 24px;padding:0;visibility:hidden}.navbar_mobile_sidebar .navbar__submenu[aria-hidden=false]{visibility:visible}.navbar_mobile_sidebar .navbar__submenu_wrapper{height:0;opacity:0;overflow:hidden;-webkit-transition:all .3s cubic-bezier(.275, 1.375, .8, 1);transition:all .3s cubic-bezier(.275, 1.375, .8, 1)}.navbar_mobile_sidebar .navbar__submenu_wrapper.is-active{height:auto;opacity:1}.navbar_mobile_sidebar__overlay{background:rgba(0,0,0,.5);height:100%;opacity:1;pointer-events:auto;position:fixed;top:0;-webkit-transition:all .3s cubic-bezier(0, 0, .3, 1);transition:all .3s cubic-bezier(0, 0, .3, 1);width:100%;z-index:10}.navbar_mobile_sidebar__overlay.is-hidden{opacity:0;pointer-events:none}.site-container{background:var(--page-bg);max-width:100%;overflow:hidden}.wrapper{-webkit-box-sizing:content-box;box-sizing:content-box;max-width:var(--entry-width);margin:0 auto;padding:0 var(--page-margin)}.readmore{display:inline-block;color:var(--gray);font-size:.8239746086rem;font-style:italic;text-decoration:underline;-webkit-text-decoration-skip:ink;text-decoration-skip-ink:auto}.hero{background:var(--hero-bg);height:var(--hero-height);position:relative;top:0;z-index:1}.hero__content{bottom:10%;color:var(--hero-text-color);left:50%;position:absolute;-webkit-transform:translate(-50%,0);transform:translate(-50%,0);width:100%;z-index:2}.hero__content h1{color:var(--hero-heading-color)}.hero__content h1>sup{font-size:1.066666667rem;vertical-align:top}.hero__content h1+p{margin-top:calc(.2833333333rem + .25vw)}.hero__content a{color:var(--hero-link-color);-webkit-transition:all .24s ease-out;transition:all .24s ease-out}.hero__content a:hover{color:var(--hero-link-color-hover)}.hero__content a:active{color:var(--hero-link-color)}.hero__content a:focus{outline:0}.hero__image{height:100%;margin:0;position:absolute;width:100%}.hero__image--overlay::after{background:-webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,0)),to(rgba(0,0,0,.64)));background:linear-gradient(to bottom,rgba(0,0,0,0) 0,rgba(0,0,0,.64) 100%);content:"";display:block;height:100%;left:0;position:absolute;top:0;width:100%}.hero__image>img{display:block;height:inherit;-o-object-fit:cover;object-fit:cover;width:inherit}.hero__image>figcaption{background:var(--page-bg);border-radius:calc(4 * var(--border-radius));bottom:1rem;color:var(--text-color);display:block;padding:0 .34rem;position:absolute;text-align:right;right:var(--page-margin);z-index:3}.feed__item{margin-top:calc(2.55rem + 1.5vw);padding-bottom:calc(.5666666667rem + 1vw)}.feed__image{-webkit-box-sizing:content-box;box-sizing:content-box;margin:calc(1.7rem + 1vw) auto;max-width:calc(var(--entry-width) + 20%);padding:0 6vw}.feed__image>img{display:inline-block;height:auto;width:100%}.feed__meta{align-items:center;color:var(--gray);display:flex;font-size:.8239746086rem;margin-bottom:calc(-1.1333333333rem - 1vw)}.feed__author{font-family:var(--menu-font);font-variation-settings:"wght" var(--font-weight-bold);text-decoration:none}.feed__date{color:var(--gray);font-style:italic}.feed__author+.feed__date{margin-left:.85rem}.feed__author+.feed__date::before{content:"";background:var(--light);display:inline-block;height:1px;margin-right:4px;width:1rem;vertical-align:middle}.feed__readmore{margin-top:calc(1.1333333333rem + .25vw)}.feed--grid{display:grid;grid-template-columns:100%;grid-gap:calc(1.7rem + 1.5vw);margin-top:0;padding-top:calc(2.55rem + 1.5vw)}@media all and (min-width:37.5em){.feed--grid{grid-template-columns:repeat(2,1fr)}}.feed--grid h2{margin-top:0}.feed--grid sup{font-size:1.066666667rem;vertical-align:top}.feed--grid-tag-desc>:first-child{margin-top:0}.post__image{display:inline-block}.post__image>img{display:inline-block}.post__image--left{float:left;margin-bottom:1.7rem;margin-right:1.7rem;max-width:50%}.post__image--right{float:right;margin-bottom:1.7rem;margin-left:1.7rem;max-width:50%}.post__image--center{display:block;margin-left:auto;margin-right:auto;text-align:center}.post__image--wide{display:block}@media all and (min-width:56.25em){.post__image--wide{margin-left:calc(-1 * var(--page-margin));margin-right:calc(-1 * var(--page-margin));text-align:center}.post__image--wide a,.post__image--wide img{display:block;height:auto;width:100%}}.post__image--full{display:block;margin-left:calc(-50vw + 50%);margin-right:calc(-50vw + 50%);text-align:center}.post__image--full a,.post__image--full img{display:block;height:auto;width:100%}.post__meta{color:var(--gray);font-size:.8239746086rem;font-style:italic;margin-bottom:calc(-1.4166666667rem - 1vw)}.post__meta--author{border-top:1px solid var(--hero-border-color);font-style:normal;display:inline-block;margin-top:1.7rem;padding-top:1.4166666667rem}@media all and (min-width:37.5em){.post__meta--author{margin-top:2.8333333333rem;padding-top:1.7rem}}.post__author-thumb{border-radius:50%;height:1.7rem;margin-right:.5666666667rem;width:1.7rem}.post__entry{margin-top:calc(2.55rem + 1.5vw)}.post__entry>:first-child{margin-top:0}.post__entry a:not(.btn):not([type=button]):not([type=submit]):not(button):not(.post__toc a):not(.gallery__item a){color:var(--link-color-hover);text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:0.2em;-webkit-text-decoration-skip:ink;text-decoration-skip-ink:auto}.post__entry a:not(.btn):not([type=button]):not([type=submit]):not(button):not(.post__toc a):not(.gallery__item a):active,.post__entry a:not(.btn):not([type=button]):not([type=submit]):not(button):not(.post__toc a):not(.gallery__item a):focus,.post__entry a:not(.btn):not([type=button]):not([type=submit]):not(button):not(.post__toc a):not(.gallery__item a):hover{color:var(--link-color)}.post__avatar-thumbs{border-radius:50%;height:4.5333333333rem;width:4.5333333333rem}.post__last-updated{color:var(--gray);font-size:.7724761953rem;font-style:italic;margin-top:2.2666666667rem}.post__last-updated+.post__tag{margin:1.1333333333rem 0 0}.post__tag{margin:2.2666666667rem 0 0;font-family:var(--menu-font);font-size:.8239746086rem}.post__tag>li{display:inline-block;margin-right:.2833333333rem;padding:0}.post__tag>li>a{background:var(--lighter);border-radius:calc(4 * var(--border-radius));color:var(--dark);font-size:.7241964329rem;font-variation-settings:"wght" var(--font-weight-normal);padding:.425rem .85rem}.post__tag>li>a:hover{background:var(--light)}.post__share{display:flex;flex-wrap:wrap;margin:calc(2.8333333333rem + 1vw) -.2833333333rem 0}.post__share>a{border-radius:calc(4 * var(--border-radius));flex:1 1 auto;margin:.2833333333rem;line-height:0;padding:.7083333333rem 1.1333333333rem;-webkit-transition:all .24s ease-out;transition:all .24s ease-out;text-align:center}.post__share>a:hover{-webkit-transform:translate3d(0,-2px,0);transform:translate3d(0,-2px,0)}.post__share>a span{color:var(--white);font-family:var(--menu-font);font-size:.5967194723rem;font-variation-settings:"wght" var(--font-weight-bold);margin-left:.2833333333rem;text-transform:uppercase}.post__share>a svg{fill:var(--white);height:18px;pointer-events:none;width:18px;vertical-align:middle}.post__bio{align-items:center;display:flex;margin:calc(3.4rem + 1vw) 0 calc(3.4rem + 2vw)}@media all and (min-width:37.5em){.post__bio::before{content:"";border-top:1px solid var(--light);height:1px;margin-right:2rem;width:20%}}.bio__avatar{border-radius:50%;flex-shrink:0;height:3rem;margin-right:1.2rem;width:3rem}@media all and (min-width:37.5em){.bio__avatar{height:4rem;margin-right:2rem;width:4rem}}.bio__name{font-family:var(--menu-font);font-size:1rem;font-variation-settings:"wght" var(--font-weight-bold);margin:0}.bio__desc{font-family:var(--body-font);font-size:.8239746086rem;line-height:1.5}@media all and (min-width:37.5em){.bio__desc{width:80%}}.bio__desc>:first-child{margin-top:.5666666667rem}.bio__desc a{text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:0.2em;-webkit-text-decoration-skip:ink;text-decoration-skip-ink:auto}.bio__desc a{color:var(--link-color-hover);-webkit-transition:all .24s ease-out;transition:all .24s ease-out}.bio__desc a:hover{color:var(--link-color)}.bio__desc a:active{color:var(--link-color)}.bio__desc a:focus{outline:0}.post__nav{border-top:1px solid var(--light);margin-top:calc(2.55rem + 1vw);padding:2.55rem var(--page-margin) 2.8333333333rem;position:relative}@media all and (min-width:37.5em){.post__nav-inner{display:flex;justify-content:space-between}}@media all and (min-width:37.5em){.post__nav-next,.post__nav-prev{align-items:center;display:flex}}.post__nav-next svg,.post__nav-prev svg{fill:var(--gray)}@media all and (max-width:37.4375em){.post__nav-next svg,.post__nav-prev svg{display:none}}@media all and (min-width:37.5em){.post__nav-next{margin-left:auto;text-align:right}}@media all and (max-width:37.4375em){.post__nav-prev+.post__nav-next{margin-top:1.1333333333rem}}@media all and (min-width:37.5em){.post__nav-prev+.post__nav-next{margin-left:1.7rem}}.post__nav-link{font-family:var(--body-font);font-size:.8239746086rem;font-style:italic;line-height:1.5}@media all and (min-width:37.5em){.post__nav-link[rel=prev]{padding-left:.85rem}}@media all and (min-width:37.5em){.post__nav-link[rel=next]{padding-right:.85rem}}.post__nav-link>span{color:var(--gray);display:block;font-size:.7724761953rem;font-family:var(--menu-font);font-style:normal}.post__nav+.post__related{margin-top:0}.post__nav+.post__comments{border-top:1px solid var(--light);margin-top:0}.post__related{background:var(--lighter);margin-top:calc(2.55rem + 1vw);margin-bottom:calc(-2.55rem - 1vw);padding:calc(.85rem + 1vw) 0 calc(3.4rem + 3vw)}.related__item{margin-top:calc(3.4rem + 1vw)}.related__item::before{content:"";border-top:1px solid var(--light);display:block;height:1px;margin-bottom:2rem;width:20%}.post__related+.post__comments{border-top:none}.post+.post__comments{margin-top:0}.post+.post__comments .h5{margin-top:0}.post__comments{margin-top:calc(2.55rem + 1vw);overflow:hidden}.post__iframe,.post__video{display:block;margin-top:calc(1.7rem + .5vw);margin-bottom:calc(1.7rem + .5vw);overflow:hidden;padding:0;position:relative;width:100%}.post__iframe::before,.post__video::before{display:block;content:"";padding-top:var(--embed-aspect-ratio)}.post__iframe iframe,.post__iframe video,.post__video iframe,.post__video video{border:none;height:100%;left:0;position:absolute;top:0;bottom:0;width:100%}.post__toc h3{font-size:1rem;margin:0}.post__toc ul{counter-reset:item;list-style:decimal;margin:calc(.5666666667rem + .25vw) 0 0 2ch}.post__toc ul li{counter-increment:item;padding:0}.post__toc ul ul{margin-top:0}.post__toc ul ul li{display:block}.post__toc ul ul li:before{content:counters(item, ".") ". ";margin-left:-2ch}.banner{text-align:center}.banner--after-post{margin-top:calc(2.55rem + 1vw)}.page__desc>:first-child{margin-top:calc(.2833333333rem + .25vw)}.page__desc a{text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:0.2em;-webkit-text-decoration-skip:ink;text-decoration-skip-ink:auto}.page--author__avatar{border-radius:50%;height:3rem;margin-bottom:calc(-1.4166666667rem - 1vw);width:3rem}@media all and (min-width:37.5em){.page--author__avatar{height:4rem;width:4rem}}.page--author__website{align-items:center;display:inline-flex}.page--author__website a{font-family:var(--menu-font);font-variation-settings:"wght" var(--font-weight-bold);margin-left:.4rem;text-decoration:none}.page--search form{align-items:flex-start;display:flex;flex-wrap:wrap}@media all and (max-width:37.4375em){.page--search input{margin-bottom:.5666666667rem}}@media all and (min-width:20em){.page--search input{flex:1 0 auto;margin-right:.5666666667rem}}@media all and (max-width:37.4375em){.page--search button{width:100%}}.align-left{text-align:left}.align-right{text-align:right}.align-center{text-align:center}.align-justify{text-align:justify}.msg{border-left:4px solid;font-size:.9374999997rem;padding:1.1333333333rem 1.7rem;position:relative}.msg--highlight{background-color:rgba(var(--highlight-color-rgb),.4);border-color:var(--highlight-color)}.msg--info{background-color:rgba(var(--info-color-rgb),.4);border-color:var(--info-color)}.msg--success{background-color:rgba(var(--success-color-rgb),.4);border-color:var(--success-color)}.msg--warning{background-color:rgba(var(--warning-color-rgb),.4);border-color:var(--warning-color)}.ordered-list{counter-reset:listCounter}.ordered-list li{counter-increment:listCounter;list-style:none;padding-left:.2833333333rem;position:relative}.ordered-list li::before{color:var(--color);content:counter(listCounter,decimal-leading-zero) ".";font-variation-settings:"wght" var(--font-weight-bold);left:-2rem;position:absolute}.dropcap:first-letter{color:var(--headings-color);float:left;font-size:3.6355864383rem;line-height:.7;margin-right:.5666666667rem;padding:.5666666667rem .5666666667rem .5666666667rem 0}.pec-wrapper{height:100%;left:0;position:absolute;top:0;width:100%}.pec-overlay{align-items:center;background-color:var(--light);font-size:14px;display:none;height:inherit;justify-content:center;line-height:1.4;padding:1rem;position:relative;text-align:center}@media all and (min-width:37.5em){.pec-overlay{font-size:16px;line-height:var(--line-height);padding:1rem 2rem}}.pec-overlay.is-active{display:flex}.pec-overlay-inner p{margin:0 0 1rem}.facebook{background:#0866ff}.twitter{background:#000}.instagram{background:#000}.vimeo{background:#1ab7ea}.pinterest{background:#bd081c}.youtube{background:#cd201f}.linkedin{background:#007bb6}.buffer{background:#333}.mix{background:#fd8235}.whatsapp{background:#25d366}.pagination{display:flex;margin-top:calc(3.4rem + 1vw)}@media all and (min-width:56.25em){.pagination{margin-top:calc(5.1rem + 1vw)}}.pagination__item+.pagination__item{margin-left:1.1333333333rem}.pagination svg{fill:var(--gray)}.footer{background:var(--section-bg);font-family:var(--menu-font);overflow:hidden;padding:calc(3.4rem + 1vw) var(--page-margin);margin:calc(2.55rem + 1vw) 0 0;text-align:center}.footer__social svg{fill:var(--white);height:.9374999997rem;margin:0 .85rem;opacity:.6;-webkit-transition:all .12s linear 0s;transition:all .12s linear 0s;width:.9374999997rem}.footer__social svg:hover{opacity:1}.footer__copyright{color:var(--gray);font-size:.6789341556rem;letter-spacing:1px;text-transform:uppercase;padding:1.7rem 0}.footer a{color:var(--white)}.footer a:hover{color:rgba(var(--white-rgb),.7)}.footer__bttop{bottom:1.4166666667rem;border-radius:50%;line-height:1;opacity:0;padding:.51rem;position:fixed;right:2rem;text-align:center;width:auto!important;visibility:hidden;z-index:999}@media all and (min-width:56.25em){.footer__bttop{bottom:2.8333333333rem}}.footer__bttop:hover{opacity:1}.footer__bttop>svg{fill:var(--white);height:23px;margin:0;width:23px}.footer__bttop.is-visible{visibility:visible;opacity:1}.gallery{margin:calc(1.7rem + 1vw) calc(var(--gallery-gap) * -1)}@media all and (min-width:20em){.gallery{display:flex;flex-wrap:wrap}}@media all and (min-width:56.25em){.gallery-wrapper--wide{margin-left:calc(-1 * var(--page-margin));margin-right:calc(-1 * var(--page-margin))}.gallery-wrapper--wide .gallery{width:calc(100% + var(--gallery-gap) * 2)}}.gallery-wrapper--full{margin-left:calc(-50vw + 50%);margin-right:calc(-50vw + 50%)}@media all and (min-width:20em){.gallery[data-columns="1"] .gallery__item{flex:1 0 100%}}@media all and (min-width:30em){.gallery[data-columns="2"] .gallery__item{flex:1 0 50%}}@media all and (min-width:37.5em){.gallery[data-columns="3"] .gallery__item{flex:1 0 33.333%}}@media all and (min-width:56.25em){.gallery[data-columns="4"] .gallery__item{flex:0 1 25%}}@media all and (min-width:56.25em){.gallery[data-columns="5"] .gallery__item{flex:0 1 20%}}@media all and (min-width:56.25em){.gallery[data-columns="6"] .gallery__item{flex:0 1 16.666%}}@media all and (min-width:56.25em){.gallery[data-columns="7"] .gallery__item{flex:1 0 14.285%}}@media all and (min-width:56.25em){.gallery[data-columns="8"] .gallery__item{flex:1 0 12.5%}}.gallery__item{margin:0;padding:var(--gallery-gap);position:relative}@media all and (min-width:20em){.gallery__item{flex:1 0 50%}}@media all and (min-width:30em){.gallery__item{flex:1 0 33.333%}}@media all and (min-width:37.5em){.gallery__item{flex:1 0 25%}}.gallery__item a{display:block;height:100%;width:100%}.gallery__item a::after{background:-webkit-gradient(linear,left bottom,left top,from(rgba(0,0,0,.4)),to(rgba(0,0,0,0)));background:linear-gradient(to top,rgba(0,0,0,.4) 0,rgba(0,0,0,0) 100%);bottom:var(--gallery-gap);content:"";display:block;opacity:0;left:var(--gallery-gap);height:calc(100% - var(--gallery-gap) * 2);position:absolute;right:var(--gallery-gap);top:var(--gallery-gap);-webkit-transition:all .24s ease-out;transition:all .24s ease-out;width:calc(100% - var(--gallery-gap) * 2)}.gallery__item a:hover::after{opacity:1}.gallery__item img{display:block;height:100%;-o-object-fit:cover;object-fit:cover;width:100%}.gallery__item figcaption{bottom:1.2rem;color:var(--white);left:50%;opacity:0;position:absolute;text-align:center;-webkit-transform:translate(-50%,1.2rem);transform:translate(-50%,1.2rem);-webkit-transition:all .24s ease-out;transition:all .24s ease-out}.gallery__item:hover figcaption{opacity:1;-webkit-transform:translate(-50%,0);transform:translate(-50%,0)}.pswp--dark .pswp__bg{background:#000}.pswp--light .pswp__bg{background:var(--white)}.pswp--light .pswp__counter{color:var(--text-color)}.pswp--light .pswp__caption__center{color:var(--text-color)}.pswp .pswp__button{-webkit-box-shadow:none;box-shadow:none;-webkit-transform:none;transform:none}.hero__image--overlay::after{background:linear-gradient(to bottom,transparent 0,rgba(0,0,0,.64) 100%)}img[loading]{opacity:0}img.is-loaded{opacity:1;transition:opacity 1s cubic-bezier(.215, .61, .355, 1)} \ No newline at end of file +@font-face{font-family:Lora;src:url('../dynamic/fonts/lora/lora.woff2') format('woff2');font-weight:400 700;font-display:swap;font-style:normal}@font-face{font-family:Lora;src:url('../dynamic/fonts/lora/lora-italic.woff2') format('woff2');font-weight:400 700;font-display:swap;font-style:italic}:root{--page-margin:6vw;--page-width:66rem;--entry-width:42rem;--navbar-height:4.4rem;--border-radius:3px;--baseline:0.28333rem;--gallery-gap:calc(var(--baseline) * 1.5);--body-font:'Lora',serif;--heading-font:'Lora',serif;--logo-font:var(--body-font);--menu-font:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--font-size:clamp(1.1rem, 1.1rem + (0.09999999999999987 * ((100vw - 20rem) / 70)), 1.2rem);--font-weight-normal:400;--font-weight-bold:600;--line-height:1.7;--letter-spacing:0em;--headings-weight:500;--headings-transform:none;--headings-style:normal;--headings-letter-spacing:0em;--headings-line-height:1.2;--hero-height:50vh;--feed-image-size:8rem;--white:#FFFFFF;--black:#17181E;--helper:#FFFFFF;--dark:#17181E;--gray:#57585a;--light:#CACBCF;--lighter:#F3F3F3;--page-bg:#FFFFFF;--color:#D73A42;--text-color:#17181E;--headings-color:#17181E;--link-color:#17181E;--link-color-hover:#D73A42;--nav-link-color:#17181E;--nav-link-color-hover:#17181E;--logo-color:#17181E;--highlight-color:#FFC700;--info-color:#67B1F3;--success-color:#00A563;--warning-color:#EE4E4E}@media all and (min-width:56.25em){:root{--navbar-height:6rem}}@media (prefers-color-scheme:dark){:root{--white:#FFFFFF;--black:#1e1e1e;--helper:#1e1e1e;--dark:#CECBCB;--gray:#9D9D9D;--light:#373737;--lighter:#1e1e1e;--page-bg:#181818;--color:#FFC074;--text-color:#BFBFBF;--headings-color:#EEEDED;--link-color:#EEEDED;--link-color-hover:#FFC074;--nav-link-color:rgba(255,255,255,1);--nav-link-color-hover:rgba(255,255,255,.7);--logo-color:#FFFFFF;--highlight-color:#F6DC90;--info-color:#5B9ED5;--success-color:#54A468;--warning-color:#FB6762}}*,:after,:before{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0}article,aside,footer,header,hgroup,main,nav,section{display:block}li{list-style:none}img{height:auto;max-width:100%;vertical-align:top}button,input,select,textarea{font:inherit}address{font-style:normal}::-moz-selection{background:var(--color);color:var(--white)}::selection{background:var(--color);color:var(--white)}html{font-size:var(--font-size);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;scroll-behavior:smooth}html.no-scroll{overflow:hidden;position:fixed}body{background:var(--page-bg);color:var(--text-color);font-family:var(--body-font);font-variation-settings:"wght" var(--font-weight-normal);letter-spacing:var(--letter-spacing);line-height:var(--line-height);-ms-scroll-chaining:none;overscroll-behavior:none}a{text-decoration:none}a{color:var(--link-color);-webkit-transition:all .24s ease-out;transition:all .24s ease-out}a:hover{color:var(--link-color-hover)}a:active{color:var(--link-color-hover)}a:focus{outline:0}.invert{color:var(--link-color-hover);-webkit-transition:all .24s ease-out;transition:all .24s ease-out}.invert:hover{color:var(--link-color)}.invert:active{color:var(--link-color)}.invert:focus{outline:0}dl,ol,p,ul{margin-top:calc(var(--baseline) * 4 + .25vw)}blockquote,figure,hr,pre,table{margin-top:calc(var(--baseline) * 6 + .5vw);margin-bottom:calc(var(--baseline) * 6 + .5vw)}h1,h2,h3,h4,h5,h6{color:var(--headings-color);font-family:var(--heading-font);font-variation-settings:"wght" var(--headings-weight);font-style:var(--headings-style);-ms-hyphens:manual;hyphens:manual;letter-spacing:var(--headings-letter-spacing);line-height:var(--headings-line-height);margin-top:calc(var(--baseline) * 6 + 1vw);text-transform:var(--headings-transform)}.h1,h1{font-size:clamp(1.5710900065rem, 1.5710900065rem + 1.424540906 * (100vw - 20rem) / 70, 2.9956309125rem);font-family:var(--heading-font)}.h2,h2{font-size:clamp(1.3808408252rem, 1.3808408252rem + .9332127447 * (100vw - 20rem) / 70, 2.3140535699rem)}.h3,h3{font-size:clamp(1.2136296308rem, 1.2136296308rem + .4621997101 * (100vw - 20rem) / 70, 1.6758293408rem)}.h4,h4{font-size:clamp(1.1377777785rem, 1.1377777785rem + .1567604947 * (100vw - 20rem) / 70, 1.2945382732rem)}.h5,h5{font-size:clamp(1.066666667rem, 1.066666667rem + .0711111115 * (100vw - 20rem) / 70, 1.1377777785rem)}.h6,h6{font-size:clamp(1rem, 1rem + 0 * (100vw - 20rem) / 70, 1rem)}h2+*,h3+*,h4+*,h5+*,h6+*{margin-top:calc(var(--baseline) * 2 + .25vw)}b,strong{font-variation-settings:"wght" var(--font-weight-bold)}blockquote{border-top:2px solid var(--dark);border-bottom:2px solid var(--dark);color:var(--headings-color);font-family:var(--heading-font);font-style:italic;font-variation-settings:"wght" var(--font-weight-bold);padding:calc(var(--baseline) * 6 + 1vw) 2rem;font-size:clamp(1.1377777785rem, 1.1377777785rem + .1567604947 * (100vw - 20rem) / 70, 1.2945382732rem)}blockquote>:first-child{margin-top:0}ol,ul{margin-left:3ch}ol>li,ul>li{list-style:inherit;padding:0 0 var(--baseline) 1ch}dl dt{font-variation-settings:"wght" var(--font-weight-bold)}pre{background-color:var(--lighter);font-size:.8239746086rem;padding:calc(var(--baseline) * 6);white-space:pre-wrap;word-wrap:break-word}pre>code{color:var(--text-color);display:inline-block;font-size:inherit;padding:0}code{background-color:var(--lighter);color:var(--color);font-size:.8239746086rem;font-family:Menlo,Monaco,Consolas,Courier New,monospace}table{border:1px solid var(--light);border-collapse:collapse;border-spacing:0;vertical-align:top;text-align:left;width:100%}table th{font-variation-settings:"wght" var(--font-weight-bold);padding:calc(var(--baseline) * 2.5) calc(var(--baseline) * 4)}table td{border-top:1px solid var(--light);padding:calc(var(--baseline) * 2.5) calc(var(--baseline) * 4)}.table-striped tr:nth-child(2n){background:var(--lighter)}.table-bordered td,.table-bordered th{border:1px solid var(--light)}.table-title th{background:var(--lighter)}figcaption{clear:both;color:var(--gray);font-style:italic;font-size:.7241964329rem;padding:calc(var(--baseline) * 3) 0 0;text-align:center}kbd{background:var(--dark);border-radius:2px;color:var(--white);font-family:Menlo,Monaco,Consolas,Courier New,monospace;font-size:.8789062495rem;padding:calc(var(--baseline) * .5) calc(var(--baseline) * 1.5)}sub,sup{font-size:65%}small{font-size:.8789062495rem}.separator,hr{background:0 0;border:none;height:auto;line-height:1;max-width:none;text-align:center}.separator::before,hr::before{content:"•••";color:var(--dark);font-size:1rem;font-variation-settings:"wght" var(--font-weight-bold);letter-spacing:1.1377777785rem;padding-left:1.1377777785rem}.separator--dot::before{content:"•";color:var(--dark);font-size:1rem;font-variation-settings:"wght" var(--font-weight-bold);letter-spacing:1.1377777785rem;padding-left:1.1377777785rem}.separator--long-line{position:relative}.separator--long-line::before{content:"";height:1rem}.separator--long-line::after{border-top:1px solid var(--light);content:"";height:1px;position:absolute;width:100%;top:50%;left:0}.btn,[type=button],[type=submit],button{align-items:center;background:0 0;border:1px solid var(--dark);border-radius:calc(var(--border-radius) * 10);color:var(--dark);cursor:pointer;display:inline-flex;font-family:var(--menu-font);font-size:.8239746086rem;font-variation-settings:"wght" var(--font-weight-bold);overflow:hidden;padding:calc(var(--baseline) * 2) calc(var(--baseline) * 4);text-align:center;-webkit-transition:all .24s ease-out;transition:all .24s ease-out;vertical-align:middle;will-change:transform}@media all and (max-width:19.9375em){.btn,[type=button],[type=submit],button{width:100%}}.btn:active,.btn:focus,.btn:hover,[type=button]:active,[type=button]:focus,[type=button]:hover,[type=submit]:active,[type=submit]:focus,[type=submit]:hover,button:active,button:focus,button:hover{background-color:var(--dark);color:var(--helper)}.btn--icon{gap:.3rem;justify-content:center}.btn--icon svg{stroke:currentColor}@media all and (min-width:20em){.btn+.btn,.btn+[type=button],.btn+[type=submit],.btn+button,[type=button]+.btn,[type=button]+[type=button],[type=button]+[type=submit],[type=button]+button,[type=submit]+.btn,[type=submit]+[type=button],[type=submit]+[type=submit],[type=submit]+button,button+.btn,button+[type=button],button+[type=submit],button+button{margin-left:calc(var(--baseline) * 2)}}@media all and (max-width:37.4375em){.btn+.btn,.btn+[type=button],.btn+[type=submit],.btn+button,[type=button]+.btn,[type=button]+[type=button],[type=button]+[type=submit],[type=button]+button,[type=submit]+.btn,[type=submit]+[type=button],[type=submit]+[type=submit],[type=submit]+button,button+.btn,button+[type=button],button+[type=submit],button+button{margin-bottom:calc(var(--baseline) * 2)}}.btn:disabled,.btn[disabled],[disabled][type=button],[disabled][type=submit],[type=button]:disabled,[type=submit]:disabled,button:disabled,button[disabled]{background-color:var(--light);border-color:var(--light);color:var(--gray);cursor:not-allowed;pointer-events:none}[type=button],[type=submit],button{-webkit-appearance:none;-moz-appearance:none}::-webkit-search-cancel-button{-webkit-appearance:none}fieldset{border:1px solid var(--light);margin:calc(var(--baseline) * 6 + 1vw) 0 0;padding:calc(var(--baseline) * 6)}fieldset>legend{margin-left:-1rem;padding:0 1rem}legend{font-variation-settings:"wght" 500;padding:0}label{font-variation-settings:"wght" 500;margin:0 calc(var(--baseline) * 4) calc(var(--baseline) * 3) 0}[type=email],[type=number],[type=search],[type=tel],[type=text],[type=url],select,textarea{background-color:var(--page-bg);border:none;border:1px solid var(--light);color:var(--text-color);font-size:1rem;outline:0;padding:calc(var(--baseline) * 1.2) calc(var(--baseline) * 3);vertical-align:middle;width:100%;-webkit-appearance:none;-moz-appearance:none}@media all and (min-width:37.5em){[type=email],[type=number],[type=search],[type=tel],[type=text],[type=url],select,textarea{width:auto}}[type=email]:focus,[type=number]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=url]:focus,select:focus,textarea:focus{border-color:var(--dark)}input[type=checkbox],input[type=radio]{opacity:0;position:absolute}input[type=checkbox]+label,input[type=radio]+label{position:relative;margin-left:-1px;cursor:pointer;padding:0}input[type=checkbox]+label:before,input[type=radio]+label:before{background-color:var(--white);border:1px solid var(--light);border-radius:2px;content:"";display:inline-block;height:calc(var(--baseline) * 5);line-height:calc(var(--baseline) * 5);margin-right:calc(var(--baseline) * 4);vertical-align:middle;text-align:center;width:calc(var(--baseline) * 5)}input[type=checkbox]:checked+label:before,input[type=radio]:checked+label:before{content:"";background-image:url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 11 8'%3e%3cpolygon points='9.53 0 4.4 5.09 1.47 2.18 0 3.64 2.93 6.54 4.4 8 5.87 6.54 11 1.46 9.53 0' fill='%23d73a42'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:11px 8px;background-position:50% 50%}input[type=radio]+label:before{border-radius:50%}input[type=radio]:checked+label:before{background-image:url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3ccircle cx='4' cy='4' r='4' fill='%23d73a42'/%3e%3c/svg%3e")}[type=file]{margin-bottom:calc(var(--baseline) * 6);width:100%}select{max-width:100%;width:auto;position:relative}select:not([multiple]){background:url('data:image/svg+xml;utf8,') no-repeat 90% 50%;background-size:8px;padding-right:calc(var(--baseline) * 12)}select[multiple]{border:1px solid var(--light);padding:calc(var(--baseline) * 6);width:100%}select[multiple]:hover{border-color:var(--light)}select[multiple]:focus{border-color:var(--dark)}select[multiple]:disabled{background-color:var(--light);cursor:not-allowed}select[multiple]:disabled:hover{border-color:var(--light)}textarea{display:block;overflow:auto;resize:vertical;max-width:100%}.top{align-items:center;display:flex;height:var(--navbar-height);position:relative;padding:0 var(--page-margin);-webkit-transition:background .5s ease;transition:background .5s ease;width:100%;z-index:2}@media all and (min-width:56.25em){.top{justify-content:space-between;height:var(--navbar-height)}}.top.sticky{background:var(--page-bg);position:sticky;top:-100px;-webkit-animation:slideDown .5s cubic-bezier(.17,.67,0,1) forwards;animation:slideDown .5s cubic-bezier(.17,.67,0,1) forwards}@-webkit-keyframes slideDown{from{opacity:0;top:-100px}to{opacity:1;top:0}}@keyframes slideDown{from{opacity:0;top:-100px}to{opacity:1;top:0}}.logo{color:var(--logo-color)!important;font-size:1.2136296308rem;font-family:var(--logo-font);font-variation-settings:"wght" var(--font-weight-bold);margin-right:auto;order:1}.logo>img{height:var(--navbar-height);-o-object-fit:contain;object-fit:contain;padding:calc(var(--baseline) * 2) 0;width:auto}.search{order:2}@media all and (min-width:56.25em){.search{order:3}}.search__btn{border-color:var(--light);margin:0;height:2.2rem;padding:0;width:2.2rem}@media all and (min-width:56.25em){.search__btn{margin-left:2rem}}.search__btn:focus,.search__btn:hover{border-color:var(--dark)}.search__form{display:flex;justify-content:space-between;width:100%}.search__form button{width:auto;flex-shrink:0}.search__input{background:0 0;border:none;border-bottom:1px solid var(--dark);display:block;font-family:var(--heading-font);padding:0;width:90%}.search__overlay{background-color:var(--page-bg);-webkit-box-shadow:0 3px 30px rgba(0,0,0,.05);box-shadow:0 3px 30px rgba(0,0,0,.05);left:0;opacity:0;position:fixed;-webkit-transition:all .24s ease-out;transition:all .24s ease-out;top:0;visibility:hidden;width:100%;z-index:2005}.search__overlay-inner{-webkit-animation:slideininput .24s 1s forwards;animation:slideininput .24s 1s forwards;align-items:center;display:flex;height:calc(var(--navbar-height) * 3);justify-content:space-between;padding:0 var(--page-margin);opacity:0;scale:0.9}@-webkit-keyframes slideininput{60%{opacity:0;scale:0.9}100%{opacity:1;scale:1}}@keyframes slideininput{60%{opacity:0;scale:0.9}100%{opacity:1;scale:1}}.search__overlay.expanded{-webkit-transform:translate(0,0);transform:translate(0,0);opacity:1;display:block;visibility:visible}.navbar{order:3}.navbar .navbar__menu{display:flex;flex-wrap:wrap;list-style:none;margin:0;padding:0}@media all and (max-width:56.1875em){.navbar .navbar__menu{display:none}}.navbar .navbar__menu li{font-family:var(--menu-font);display:block;font-size:.8789062495rem;line-height:var(--line-height);font-variation-settings:"wght" 500;padding:0;position:relative;width:auto}.navbar .navbar__menu li a,.navbar .navbar__menu li span[aria-haspopup=true]{color:var(--nav-link-color);display:block;padding:0 .6rem;-webkit-transition:all .24s ease-out;transition:all .24s ease-out}.navbar .navbar__menu li a:active,.navbar .navbar__menu li a:focus,.navbar .navbar__menu li a:hover,.navbar .navbar__menu li span[aria-haspopup=true]:active,.navbar .navbar__menu li span[aria-haspopup=true]:focus,.navbar .navbar__menu li span[aria-haspopup=true]:hover{color:var(--nav-link-color-hover)}.navbar .navbar__menu li span{color:var(--nav-link-color);cursor:default;display:block;padding:0 .6rem}.navbar .navbar__menu>li:hover>a,.navbar .navbar__menu>li:hover>span[aria-haspopup=true]{color:var(--nav-link-color-hover)}.navbar .has-submenu:active>.navbar__submenu,.navbar .has-submenu:focus>.navbar__submenu,.navbar .has-submenu:hover>.navbar__submenu{left:0;opacity:1;-webkit-transform:scale(1);transform:scale(1);visibility:visible;margin-top:.8rem}.navbar .has-submenu:active>.navbar__submenu:before,.navbar .has-submenu:focus>.navbar__submenu:before,.navbar .has-submenu:hover>.navbar__submenu:before{content:"";height:1rem;left:0;position:absolute;width:100%;top:-1rem}.navbar .has-submenu:active>.navbar__submenu.is-right-submenu,.navbar .has-submenu:focus>.navbar__submenu.is-right-submenu,.navbar .has-submenu:hover>.navbar__submenu.is-right-submenu{left:auto;right:0;-webkit-transform-origin:right top;transform-origin:right top}.navbar .has-submenu .has-submenu:active>.navbar__submenu,.navbar .has-submenu .has-submenu:focus>.navbar__submenu,.navbar .has-submenu .has-submenu:hover>.navbar__submenu{top:0;margin-top:0}.navbar .has-submenu .has-submenu:active>.navbar__submenu.is-right-submenu,.navbar .has-submenu .has-submenu:focus>.navbar__submenu.is-right-submenu,.navbar .has-submenu .has-submenu:hover>.navbar__submenu.is-right-submenu{top:0;margin-top:0}.navbar .navbar__submenu{background:var(--lighter);border-radius:calc(var(--border-radius) * 4);left:-9999px;list-style-type:none;margin:0;padding:1rem .85rem;position:absolute;visibility:hidden;white-space:nowrap;z-index:1;opacity:0;-webkit-transform:scale(.8);transform:scale(.8);-webkit-transform-origin:0 top;transform-origin:0 top;-webkit-transition:opacity .15s,-webkit-transform .3s cubic-bezier(.275, 1.375, .8, 1);transition:opacity .15s,-webkit-transform .3s cubic-bezier(.275, 1.375, .8, 1);transition:opacity .15s,transform .3s cubic-bezier(.275, 1.375, .8, 1);transition:opacity .15s,transform .3s cubic-bezier(.275, 1.375, .8, 1),-webkit-transform .3s cubic-bezier(.275, 1.375, .8, 1)}.navbar .navbar__submenu__submenu{z-index:2}.navbar .navbar__submenu li{line-height:1.5;font-size:.8789062495rem}.navbar .navbar__submenu li a,.navbar .navbar__submenu li span[aria-haspopup=true]{border-radius:calc(var(--border-radius) * 3);color:var(--nav-link-color-hover);padding:.5rem 1rem;-webkit-transition:all .24s ease;transition:all .24s ease}.navbar .navbar__submenu li a:active,.navbar .navbar__submenu li a:focus,.navbar .navbar__submenu li a:hover,.navbar .navbar__submenu li span[aria-haspopup=true]:active,.navbar .navbar__submenu li span[aria-haspopup=true]:focus,.navbar .navbar__submenu li span[aria-haspopup=true]:hover{background:var(--page-bg);color:var(--nav-link-color)}.navbar .navbar__submenu li span{color:var(--nav-link-color-hover)!important;padding:.5rem 1rem}.navbar .navbar__submenu li:hover>a,.navbar .navbar__submenu li:hover>span[aria-haspopup=true]{color:var(--nav-link-color)}.navbar .navbar__toggle{background:var(--black);-webkit-box-shadow:none;box-shadow:none;border:none;cursor:pointer;display:block;line-height:1;margin:0;overflow:visible;padding:0;position:relative;right:0;margin-left:.75rem;text-transform:none;z-index:2004;height:3.2rem;padding:0;width:3.2rem}@media all and (min-width:56.25em){.navbar .navbar__toggle{display:none}}.navbar .navbar__toggle:focus,.navbar .navbar__toggle:hover{-webkit-box-shadow:none;box-shadow:none;outline:0;-webkit-transform:none;transform:none}.navbar .navbar__toggle-box{width:20px;height:14px;display:inline-block;position:relative}.navbar .navbar__toggle-inner{display:block;top:50%;text-indent:-9999999em}.navbar .navbar__toggle-inner::before{content:"";display:block;top:-5px}.navbar .navbar__toggle-inner::after{content:"";display:block;bottom:-5px}.navbar .navbar__toggle-inner,.navbar .navbar__toggle-inner::after,.navbar .navbar__toggle-inner::before{width:20px;height:1px;background-color:var(--white);position:absolute;-webkit-transition:opacity .14s ease-out,-webkit-transform;transition:opacity .14s ease-out,-webkit-transform;transition:transform,opacity .14s ease-out;transition:transform,opacity .14s ease-out,-webkit-transform}.navbar .navbar__toggle-inner{-webkit-transition-duration:75ms;transition-duration:75ms;-webkit-transition-timing-function:cubic-bezier(0.55,0.055,0.675,0.19);transition-timing-function:cubic-bezier(0.55,0.055,0.675,0.19)}.navbar .navbar__toggle-inner::before{-webkit-transition:top 75ms ease .12s,opacity 75ms ease;transition:top 75ms ease .12s,opacity 75ms ease}.navbar .navbar__toggle-inner::after{-webkit-transition:bottom 75ms ease .12s,-webkit-transform 75ms cubic-bezier(.55, .055, .675, .19);transition:bottom 75ms ease .12s,-webkit-transform 75ms cubic-bezier(.55, .055, .675, .19);transition:bottom 75ms ease .12s,transform 75ms cubic-bezier(.55, .055, .675, .19);transition:bottom 75ms ease .12s,transform 75ms cubic-bezier(.55, .055, .675, .19),-webkit-transform 75ms cubic-bezier(.55, .055, .675, .19)}.navbar .navbar__toggle.is-active .navbar__toggle-inner{-webkit-transform:rotate(45deg);transform:rotate(45deg);-webkit-transition-delay:0.12s;transition-delay:0.12s;-webkit-transition-timing-function:cubic-bezier(0.215,0.61,0.355,1);transition-timing-function:cubic-bezier(0.215,0.61,0.355,1)}.navbar .navbar__toggle.is-active .navbar__toggle-inner::before{top:0;opacity:0;-webkit-transition:top 75ms ease,opacity 75ms ease .12s;transition:top 75ms ease,opacity 75ms ease .12s}.navbar .navbar__toggle.is-active .navbar__toggle-inner::after{bottom:0;-webkit-transform:rotate(-90deg);transform:rotate(-90deg);-webkit-transition:bottom 75ms ease,-webkit-transform 75ms cubic-bezier(.215, .61, .355, 1) .12s;transition:bottom 75ms ease,-webkit-transform 75ms cubic-bezier(.215, .61, .355, 1) .12s;transition:bottom 75ms ease,transform 75ms cubic-bezier(.215, .61, .355, 1) .12s;transition:bottom 75ms ease,transform 75ms cubic-bezier(.215, .61, .355, 1) .12s,-webkit-transform 75ms cubic-bezier(.215, .61, .355, 1) .12s}.navbar_mobile_overlay{background:var(--page-bg);height:calc(100vh - 4.4rem);left:0;opacity:1;overflow:auto;pointer-events:auto;position:fixed;top:4.4rem;-webkit-transition:all .3s cubic-bezier(0, 0, .3, 1);transition:all .3s cubic-bezier(0, 0, .3, 1);width:100%;z-index:1001}.navbar_mobile_overlay.is-hidden{opacity:0;pointer-events:none}.navbar_mobile_overlay .navbar__menu{margin:24px}.navbar_mobile_overlay .navbar__menu li{list-style:none;margin:0;padding:0;text-align:center}.navbar_mobile_overlay .navbar__menu li a,.navbar_mobile_overlay .navbar__menu li span{color:var(--dark);display:block;padding:calc(var(--baseline) * 2);position:relative}.navbar_mobile_overlay .navbar__menu li a:active,.navbar_mobile_overlay .navbar__menu li a:focus,.navbar_mobile_overlay .navbar__menu li a:hover,.navbar_mobile_overlay .navbar__menu li span:active,.navbar_mobile_overlay .navbar__menu li span:focus,.navbar_mobile_overlay .navbar__menu li span:hover{color:var(--dark)}.navbar_mobile_overlay .navbar__menu li a[aria-haspopup=true]::after,.navbar_mobile_overlay .navbar__menu li span[aria-haspopup=true]::after{content:"";width:0;height:0;border-style:solid;border-width:5px 5px 0 5px;border-color:currentColor transparent transparent transparent;left:calc(var(--baseline) * 2);top:15px;position:relative}.navbar_mobile_overlay .navbar__submenu{margin:0;padding:0;visibility:hidden}.navbar_mobile_overlay .navbar__submenu[aria-hidden=false]{visibility:visible}.navbar_mobile_overlay .navbar__submenu_wrapper{height:0;opacity:0;overflow:hidden;-webkit-transition:all .3s cubic-bezier(.275, 1.375, .8, 1);transition:all .3s cubic-bezier(.275, 1.375, .8, 1)}.navbar_mobile_overlay .navbar__submenu_wrapper.is-active{height:auto;opacity:1}.navbar_mobile_sidebar{background:var(--page-bg);-webkit-box-shadow:0 0 5px rgba(0,0,0,.25);box-shadow:0 0 5px rgba(0,0,0,.25);height:100vh;left:0;max-width:400px;overflow:auto;position:fixed;top:0;-webkit-transition:all .3s cubic-bezier(0, 0, .3, 1);transition:all .3s cubic-bezier(0, 0, .3, 1);width:80%;z-index:1001}.navbar_mobile_sidebar.is-hidden{left:-400px}.navbar_mobile_sidebar .navbar__menu{margin:24px}.navbar_mobile_sidebar .navbar__menu li{font-family:var(--menu-font);font-size:16px;list-style:none;line-height:1.3;margin:0;padding:0}.navbar_mobile_sidebar .navbar__menu li .is-separator,.navbar_mobile_sidebar .navbar__menu li a{color:var(--dark);display:block;padding:10px 20px 10px 0;position:relative}.navbar_mobile_sidebar .navbar__menu li .is-separator:active,.navbar_mobile_sidebar .navbar__menu li .is-separator:focus,.navbar_mobile_sidebar .navbar__menu li .is-separator:hover,.navbar_mobile_sidebar .navbar__menu li a:active,.navbar_mobile_sidebar .navbar__menu li a:focus,.navbar_mobile_sidebar .navbar__menu li a:hover{color:var(--dark)}.navbar_mobile_sidebar .navbar__menu li .is-separator[aria-haspopup=true]::after,.navbar_mobile_sidebar .navbar__menu li a[aria-haspopup=true]::after{content:"";width:0;height:0;border-style:solid;border-width:5px 5px 0 5px;border-color:currentColor transparent transparent transparent;right:0;top:18px;position:absolute}.navbar_mobile_sidebar .navbar__submenu{margin:0 0 0 24px;padding:0;visibility:hidden}.navbar_mobile_sidebar .navbar__submenu[aria-hidden=false]{visibility:visible}.navbar_mobile_sidebar .navbar__submenu_wrapper{height:0;opacity:0;overflow:hidden;-webkit-transition:all .3s cubic-bezier(.275, 1.375, .8, 1);transition:all .3s cubic-bezier(.275, 1.375, .8, 1)}.navbar_mobile_sidebar .navbar__submenu_wrapper.is-active{height:auto;opacity:1}.navbar_mobile_sidebar__overlay{background:rgba(0,0,0,.5);height:100%;opacity:1;pointer-events:auto;position:fixed;top:0;-webkit-transition:all .3s cubic-bezier(0, 0, .3, 1);transition:all .3s cubic-bezier(0, 0, .3, 1);width:100%;z-index:10}.navbar_mobile_sidebar__overlay.is-hidden{opacity:0;pointer-events:none}.wrapper{-webkit-box-sizing:content-box;box-sizing:content-box;max-width:var(--page-width);margin-left:auto;margin-right:auto;padding-left:var(--page-margin);padding-right:var(--page-margin)}.entry-wrapper{-webkit-box-sizing:content-box;box-sizing:content-box;max-width:var(--entry-width);margin-left:auto;margin-right:auto;padding-left:var(--page-margin);padding-right:var(--page-margin)}.hero{position:relative;z-index:1}.hero--noimage::after{background:var(--dark);content:"";display:block;height:1px;bottom:0;width:calc(100% - var(--page-margin) * 2);z-index:-1;max-width:var(--page-width);position:absolute;left:50%;-webkit-transform:translate(-50%,0);transform:translate(-50%,0)}.hero__content{padding-bottom:calc(var(--baseline) * 6 + 1.5vw)}.hero__content h1>sup{font-size:1.066666667rem;vertical-align:top}.hero__content--centered{text-align:center}.hero__content--centered .content__meta{justify-content:center}.hero__cta{margin-top:calc(var(--baseline) * 6)}.hero__image{margin:0 var(--page-margin)}.hero__image-wrapper{position:relative;background:var(--lighter);border-radius:calc(var(--border-radius) * 4)}@media all and (min-width:56.25em){.hero__image-wrapper{height:var(--hero-height)}}.hero__image-wrapper img{border-radius:inherit;display:block;height:100%;-o-object-fit:cover;object-fit:cover;width:100%}@media all and (min-width:56.25em){.hero__image-wrapper img{height:var(--hero-height)}}.hero__image>figcaption{background:var(--page-bg)}@media all and (min-width:56.25em){.hero__image>figcaption{text-align:right}}.feed__item{display:flex;flex-wrap:wrap;gap:calc(2rem + 2vw);margin-top:calc(var(--baseline) * 12 + 2vw)}@media all and (min-width:37.5em){.feed__item{flex-wrap:nowrap}}.feed__item--centered{justify-content:center}.feed__content{max-width:var(--entry-width)}.feed__image{background:var(--lighter);border-radius:calc(var(--border-radius) * 4);flex-shrink:0;height:100%;margin:0;width:100%}@media all and (min-width:37.5em){.feed__image{height:calc(var(--feed-image-size) + 4vw);width:calc(var(--feed-image-size) + 4vw)}}.feed__image>img{border-radius:inherit;display:inline-block;height:100%;-o-object-fit:cover;object-fit:cover;width:100%}.feed__image--wide{max-width:var(--page-width)}.feed__meta{align-items:center;color:var(--gray);display:flex;font-size:.8239746086rem;margin-bottom:calc(var(--baseline) * 3)}.feed__author{font-family:var(--menu-font);font-variation-settings:"wght" var(--font-weight-bold);text-decoration:none}.feed__author-thumb{border-radius:50%;height:1.7rem;margin-right:.6rem;width:1.7rem}.feed__date{color:var(--gray);font-style:italic}.feed__author+.feed__date{margin-left:.8rem}.feed__author+.feed__date::before{content:"";background:var(--light);display:inline-block;height:1px;margin-right:4px;width:1rem;vertical-align:middle}.feed__readmore{margin-top:calc(var(--baseline) * 4 + .25vw)}.feed__title{margin-top:0}.feed--grid{margin:0}@media all and (min-width:37.5em){.feed--grid{display:grid;grid-template-columns:100%;gap:0 3rem}}@media all and (min-width:56.25em){.feed--grid{grid-template-columns:repeat(2,1fr)}}.feed--grid h2{margin-top:0}.feed--grid sup{font-size:1.066666667rem;vertical-align:top}.feed--grid li{align-items:center;list-style:none;gap:2rem;padding:0}.content__meta{margin-top:calc(var(--baseline) * 4 + .25vw);margin-bottom:0}.content__meta--centered{justify-content:center}.content__entry{margin-top:calc(var(--baseline) * 6 + 1.5vw)}.content__entry>:first-child{margin-top:0}.content__entry a:not(.btn):not([type=button]):not([type=submit]):not(button):not(.post__toc a):not(.gallery__item a){color:var(--link-color-hover);text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:0.2em;-webkit-text-decoration-skip:ink;text-decoration-skip-ink:auto}.content__entry a:not(.btn):not([type=button]):not([type=submit]):not(button):not(.post__toc a):not(.gallery__item a):active,.content__entry a:not(.btn):not([type=button]):not([type=submit]):not(button):not(.post__toc a):not(.gallery__item a):focus,.content__entry a:not(.btn):not([type=button]):not([type=submit]):not(button):not(.post__toc a):not(.gallery__item a):hover{color:var(--link-color)}.content__entry--nospace{margin-top:0}.content__avatar-thumbs{border-radius:50%;height:4.5rem;width:4.5rem}.content__footer{margin-top:calc(var(--baseline) * 9 + 1vw)}.content__updated{color:var(--gray);font-size:.8789062495rem;font-style:italic}.content__actions{align-items:baseline;display:flex;flex-basis:auto;gap:2rem;justify-content:space-between;margin-top:calc(var(--baseline) * 4 + .25vw);position:relative}.content__share{flex-shrink:0}.content__share-button{border-color:var(--light)}.content__share-popup{background:var(--lighter);border-radius:calc(var(--border-radius) * 4);bottom:140%;display:none;padding:1rem .85rem;position:absolute;right:0;text-align:left;z-index:1}.content__share-popup.is-visible{-webkit-animation:share-popup .48s cubic-bezier(.17,.67,.6,1.34) backwards;animation:share-popup .48s cubic-bezier(.17,.67,.6,1.34) backwards;display:block}@-webkit-keyframes share-popup{from{-webkit-transform:scale(.9);transform:scale(.9)}to{-webkit-transform:scale(1);transform:scale(1)}}@keyframes share-popup{from{-webkit-transform:scale(.9);transform:scale(.9)}to{-webkit-transform:scale(1);transform:scale(1)}}.content__share-popup>a{border-radius:calc(var(--border-radius) * 3);color:var(--text-color);display:block;font-family:var(--menu-font);font-size:.8239746086rem;padding:.4rem .8rem}.content__share-popup>a:hover{background:var(--page-bg);color:var(--text-color);text-decoration:none}.content__share-popup>a>svg{fill:var(--text-color);display:inline-block;height:.9rem;margin-right:.5666666667rem;pointer-events:none;vertical-align:middle;width:.9rem}.content__tag{margin:0;font-family:var(--menu-font);font-size:.8239746086rem}.content__tag>li{display:inline-flex;margin:.3rem .3rem .3rem 0;padding:0}.content__tag>li>a{border:1px solid var(--light);border-radius:calc(var(--border-radius) * 10);color:var(--dark);font-size:.7241964329rem;font-variation-settings:"wght" var(--font-weight-normal);padding:calc(var(--baseline) * 1) calc(var(--baseline) * 2.5)}.content__tag>li>a:hover{border-color:var(--dark)}.content__bio{display:flex;margin:calc(var(--baseline) * 12 + 1vw) 0}@media all and (min-width:37.5em){.content__bio{align-items:center}}@media all and (min-width:37.5em){.content__bio::before{content:"";border-top:1px solid var(--light);height:1px;margin-right:2rem;width:30%}}.bio__avatar{border-radius:50%;flex-shrink:0;height:2.5rem;margin-right:1.2rem;width:2.5rem}@media all and (min-width:37.5em){.bio__avatar{height:4rem;margin-right:2rem;width:4rem}}.bio__name{margin:0}.bio__desc{font-family:var(--body-font);font-size:.8789062495rem;line-height:1.5}.bio__desc>:first-child{margin-top:calc(var(--baseline) * 2)}.bio__desc a{text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:0.2em;-webkit-text-decoration-skip:ink;text-decoration-skip-ink:auto}.bio__desc a{color:var(--link-color-hover);-webkit-transition:all .24s ease-out;transition:all .24s ease-out}.bio__desc a:hover{color:var(--link-color)}.bio__desc a:active{color:var(--link-color)}.bio__desc a:focus{outline:0}.content__nav{margin-top:calc(var(--baseline) * 16 + 1vw)}.content__nav-inner{border-top:1px solid var(--dark);border-bottom:1px solid var(--dark);padding:calc(var(--baseline) * 16) 0}@media all and (min-width:37.5em){.content__nav-inner{display:flex;gap:1rem;justify-content:space-between}}@media all and (min-width:56.25em){.content__nav-inner{gap:2rem}}@media all and (max-width:37.4375em){.content__nav-prev+.content__nav-next{margin-top:calc(var(--baseline) * 6 + 1vw)}}@media all and (min-width:37.5em){.content__nav-next{margin-left:auto;text-align:right}}.content__nav-link{font-family:var(--heading-font);font-variation-settings:"wght" var(--font-weight-bold);font-style:italic;height:100%;line-height:1.5;display:flex;gap:1rem;justify-content:space-between;align-items:center}@media all and (min-width:37.5em){.content__nav-link{gap:2rem}}.content__nav-link>div{overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical}@media all and (min-width:56.25em){.content__nav-link>div{-webkit-line-clamp:4}}.content__nav-link span{color:var(--gray);display:block;font-size:.7724761953rem;font-family:var(--menu-font);font-style:normal;font-variation-settings:"wght" var(--font-weight-normal);margin-bottom:var(--baseline)}.content__nav-image{flex:1 0 4rem;margin:0;height:4rem}@media all and (min-width:37.5em) and (max-width:56.1875em){.content__nav-image{flex-basis:6rem;height:6rem}}@media all and (min-width:56.25em){.content__nav-image{flex-basis:8rem;height:8rem}}.content__nav-image>img{border-radius:calc(var(--border-radius) * 4);display:block;height:100%;-o-object-fit:cover;object-fit:cover;width:100%}.content__related{background:var(--lighter);margin-top:calc(var(--baseline) * 16 + 1vw);padding:calc(var(--baseline) * 12 + 3vw) 0}.related__title{margin-top:0}.content__comments{margin-top:calc(var(--baseline) * 9);overflow:hidden}.post__image{display:inline-block}.post__image a,.post__image img{border-radius:calc(var(--border-radius) * 4);display:inline-block}.post__image>figcaption{background-color:var(--page-bg)}.post__image--left{float:left;margin-bottom:0;margin-right:2rem;max-width:50%}.post__image--right{float:right;margin-bottom:0;margin-left:2rem;max-width:50%}.post__image--center{display:block;margin-left:auto;margin-right:auto;text-align:center}.post__image--wide{display:block}@media all and (min-width:56.25em){.post__image--wide{margin-left:calc(-50vw + 50%);margin-right:calc(-50vw + 50%);padding:0 var(--page-margin);text-align:center}.post__image--wide a,.post__image--wide img{display:block;height:auto;margin:auto;max-width:var(--page-width);width:100%}}.post__image--full{background-color:var(--lighter);display:block;margin-left:calc(-50vw + 50%);margin-right:calc(-50vw + 50%);text-align:center}.post__image--full a,.post__image--full img{border-radius:0;display:block;height:auto;width:100%}.post__iframe,.post__video{display:block;margin-top:calc(var(--baseline) * 6 + .5vw);margin-bottom:calc(var(--baseline) * 6 + .5vw);overflow:hidden;padding:0;position:relative;width:100%}.post__iframe::before,.post__video::before{display:block;content:"";padding-top:var(--embed-aspect-ratio)}.post__iframe iframe,.post__iframe video,.post__video iframe,.post__video video{border:none;height:100%;left:0;position:absolute;top:0;bottom:0;width:100%}.post__toc h3{border-bottom:1px solid var(--dark);font-size:1rem;margin:0;padding-bottom:calc(var(--baseline) * 2 + .25vw)}.post__toc ul{counter-reset:item;list-style:decimal;margin:calc(var(--baseline) * 3 + .25vw) 0 0 3ch}.post__toc ul li{counter-increment:item;padding:0}.post__toc ul ul{margin-top:0}.post__toc ul ul li{display:block}.post__toc ul ul li:before{content:counters(item, ".") ". ";margin-left:-3ch}.banner{text-align:center}.banner--after-content{margin-top:calc(var(--baseline) * 9 + 1vw)}.page__desc a{text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:0.2em;-webkit-text-decoration-skip:ink;text-decoration-skip-ink:auto}@media all and (min-width:37.5em){.page--author__wrapper{display:flex;gap:2rem}}@media all and (min-width:56.25em){.page--author__wrapper{gap:3rem}}.page--author__avatar{border-radius:50%;height:calc(var(--baseline) * 10 + 2vw);margin-top:calc(var(--baseline) * 6 + 1vw);width:calc(var(--baseline) * 10 + 2vw)}.page--author__website{margin-top:calc(var(--baseline) * 4 + .25vw)}.page--search form{align-items:flex-start;display:flex;flex-wrap:wrap}@media all and (max-width:37.4375em){.page--search input{margin-bottom:calc(var(--baseline) * 2)}}@media all and (min-width:20em){.page--search input{flex:1 0 auto;margin-right:calc(var(--baseline) * 2)}}@media all and (max-width:37.4375em){.page--search button{width:100%}}.subpages__title{border-top:1px solid var(--light);padding-top:calc(var(--baseline) * 6 + .5vw)}.subpages__list{list-style:initial;margin-left:2ch}.subpages__list ul{list-style:initial;margin:0 0 0 2ch}.subpages__list li{padding-bottom:0}.readmore{display:inline-block;color:var(--gray);font-size:.9374999997rem;font-style:italic;text-decoration:underline;-webkit-text-decoration-skip:ink;text-decoration-skip-ink:auto}.align-left{text-align:left}.align-right{text-align:right}.align-center{text-align:center}.align-justify{text-align:justify}.msg{border:1px solid var(--light);border-left-width:2px;font-size:.9374999997rem;padding:calc(var(--baseline) * 6);position:relative}.msg--highlight{border-left-color:var(--highlight-color)}.msg--info{border-left-color:var(--info-color)}.msg--success{border-left-color:var(--success-color)}.msg--warning{border-left-color:var(--warning-color)}.ordered-list{counter-reset:listCounter}.ordered-list li{counter-increment:listCounter;list-style:none;padding-left:.3rem;position:relative}.ordered-list li::before{color:var(--color);content:counter(listCounter,decimal-leading-zero) ".";font-variation-settings:"wght" var(--font-weight-bold);left:-2rem;position:absolute}.dropcap:first-letter{color:var(--headings-color);float:left;font-size:3.6355864383rem;line-height:.7;margin-right:.6rem;padding:calc(var(--baseline) * 2) calc(var(--baseline) * 2) calc(var(--baseline) * 2) 0}.pec-wrapper{height:100%;left:0;position:absolute;top:0;width:100%}.pec-overlay{align-items:center;background-color:var(--lighter);font-size:14px;display:none;height:inherit;justify-content:center;line-height:1.4;padding:1rem;position:relative;text-align:center}@media all and (min-width:37.5em){.pec-overlay{font-size:16px;line-height:var(--line-height);padding:1rem 2rem}}.pec-overlay.is-active{display:flex}.pec-overlay-inner p{margin:0 0 1rem}.pagination{display:flex;gap:calc(var(--baseline) * 2);justify-content:center;margin-top:calc(var(--baseline) * 12 + 1vw)}@media all and (min-width:56.25em){.pagination{margin-top:calc(var(--baseline) * 18 + 1vw)}}.footer{border-top:1px solid var(--light);font-size:.9374999997rem;padding:calc(var(--baseline) * 9 + 1vw) 0 calc(var(--baseline) * 6 + 1vw);margin:calc(var(--baseline) * 12 + 1vw) auto 0;max-width:var(--page-width);text-align:center}.footer--glued{border:none;padding-top:0}.footer__nav ul{list-style:none;margin:0}.footer__nav ul li{display:inline-block;padding:var(--baseline) .5rem}*+.footer__social{margin-top:calc(var(--baseline) * 6 + .5vw)}.footer__social svg{fill:var(--dark);height:1rem;margin:0 .5rem;-webkit-transition:all .12s linear 0s;transition:all .12s linear 0s;width:1rem}.footer__social svg:hover{fill:var(--gray)}.footer__copyright{font-size:.8239746086rem;margin-top:var(--baseline)}.footer__copyright>:first-child{margin:0}.footer__bttop{background:var(--page-bg);bottom:calc(var(--baseline) * 5);border-radius:50%;border-color:var(--light);line-height:1;opacity:0;padding:calc(var(--baseline) * 2);position:fixed;right:2rem;text-align:center;width:auto!important;visibility:hidden;z-index:999}@media all and (min-width:56.25em){.footer__bttop{bottom:calc(var(--baseline) * 10)}}.footer__bttop:hover{border-color:var(--dark);opacity:1}.footer__bttop.is-visible{visibility:visible;opacity:1}.gallery{margin:calc(var(--baseline) * 6 + 1vw) calc(var(--gallery-gap) * -1)}@media all and (min-width:20em){.gallery{display:flex;flex-wrap:wrap}}@media all and (min-width:56.25em){.gallery-wrapper--wide{display:flex;justify-content:center;margin-left:calc(-50vw + 50%);margin-right:calc(-50vw + 50%);padding:0 var(--page-margin)}.gallery-wrapper--wide .gallery{max-width:var(--page-width)}}@media all and (min-width:56.25em){.gallery-wrapper--full{margin-left:calc(-50vw + 50%);margin-right:calc(-50vw + 50%);padding:0 var(--page-margin)}}@media all and (min-width:20em){.gallery[data-columns="1"] .gallery__item{flex:1 0 100%}}@media all and (min-width:30em){.gallery[data-columns="2"] .gallery__item{flex:1 0 50%}}@media all and (min-width:37.5em){.gallery[data-columns="3"] .gallery__item{flex:1 0 33.333%}}@media all and (min-width:56.25em){.gallery[data-columns="4"] .gallery__item{flex:0 1 25%}}@media all and (min-width:56.25em){.gallery[data-columns="5"] .gallery__item{flex:0 1 20%}}@media all and (min-width:56.25em){.gallery[data-columns="6"] .gallery__item{flex:0 1 16.666%}}@media all and (min-width:56.25em){.gallery[data-columns="7"] .gallery__item{flex:1 0 14.285%}}@media all and (min-width:56.25em){.gallery[data-columns="8"] .gallery__item{flex:1 0 12.5%}}.gallery__item{margin:0;padding:var(--gallery-gap);position:relative}@media all and (min-width:20em){.gallery__item{flex:1 0 50%}}@media all and (min-width:30em){.gallery__item{flex:1 0 33.333%}}@media all and (min-width:37.5em){.gallery__item{flex:1 0 25%}}.gallery__item a{border-radius:calc(var(--border-radius) * 4);display:block;height:100%;width:100%}.gallery__item a::after{background:-webkit-gradient(linear,left bottom,left top,from(rgba(0,0,0,.4)),to(rgba(0,0,0,0)));background:linear-gradient(to top,rgba(0,0,0,.4) 0,rgba(0,0,0,0) 100%);border-radius:inherit;bottom:var(--gallery-gap);content:"";display:block;opacity:0;left:var(--gallery-gap);height:calc(100% - var(--gallery-gap) * 2);position:absolute;right:var(--gallery-gap);top:var(--gallery-gap);-webkit-transition:all .24s ease-out;transition:all .24s ease-out;width:calc(100% - var(--gallery-gap) * 2)}.gallery__item a:hover::after{opacity:1}.gallery__item img{border-radius:inherit;display:block;height:100%;-o-object-fit:cover;object-fit:cover;width:100%}.gallery__item figcaption{bottom:1.2rem;color:var(--white);left:50%;opacity:0;position:absolute;text-align:center;-webkit-transform:translate(-50%,1.2rem);transform:translate(-50%,1.2rem);-webkit-transition:all .24s ease-out;transition:all .24s ease-out}.gallery__item:hover figcaption{opacity:1;-webkit-transform:translate(-50%,0);transform:translate(-50%,0)}.pswp--dark .pswp__bg{background:#000}.pswp--dark .pswp__button,.pswp--dark .pswp__button--arrow--left:before,.pswp--dark .pswp__button--arrow--right:before{background-image:url(../svg/gallery-icons-light.svg)}.pswp--light .pswp__bg{background:var(--white)}.pswp--light .pswp__counter{color:var(--text-color)}.pswp--light .pswp__caption__center{color:var(--text-color)}.pswp--light .pswp__button,.pswp--light .pswp__button--arrow--left:before,.pswp--light .pswp__button--arrow--right:before{background-image:url(../svg/gallery-icons-dark.svg)}.pswp .pswp__button{-webkit-box-shadow:none;box-shadow:none;-webkit-transform:none;transform:none}img[loading]{opacity:0}img.is-loaded{opacity:1;transition:opacity 1s cubic-bezier(.215, .61, .355, 1)} \ No newline at end of file diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/adventpro/adventpro-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/adventpro/adventpro-italic.woff2 new file mode 100644 index 000000000..04519b34c Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/adventpro/adventpro-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/aleo/aleo-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/aleo/aleo-italic.woff2 new file mode 100644 index 000000000..c2db53e99 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/aleo/aleo-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/andadapro/andadapro-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/andadapro/andadapro-italic.woff2 new file mode 100644 index 000000000..eb7edaf38 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/andadapro/andadapro-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/archivonarrow/archivonarrow-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/archivonarrow/archivonarrow-italic.woff2 new file mode 100644 index 000000000..96be19803 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/archivonarrow/archivonarrow-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/asap/asap-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/asap/asap-italic.woff2 new file mode 100644 index 000000000..a733a99ad Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/asap/asap-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/besley/besley-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/besley/besley-italic.woff2 new file mode 100644 index 000000000..ad4ad0c0f Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/besley/besley-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/bitter/bitter-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/bitter/bitter-italic.woff2 new file mode 100644 index 000000000..68d8a90d2 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/bitter/bitter-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/brygada1918/brygada1918-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/brygada1918/brygada1918-italic.woff2 new file mode 100644 index 000000000..151dd4e66 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/brygada1918/brygada1918-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/cabin/cabin-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/cabin/cabin-italic.woff2 new file mode 100644 index 000000000..10dacc387 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/cabin/cabin-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/exo/exo-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/exo/exo-italic.woff2 new file mode 100644 index 000000000..d964d847d Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/exo/exo-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/faustina/faustina-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/faustina/faustina-italic.woff2 new file mode 100644 index 000000000..7aeba271c Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/faustina/faustina-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/figtree/figtree-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/figtree/figtree-italic.woff2 new file mode 100644 index 000000000..f4d3e2252 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/figtree/figtree-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/glory/glory-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/glory/glory-italic.woff2 new file mode 100644 index 000000000..3bfbab784 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/glory/glory-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/instrumentsans/instrumentsans-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/instrumentsans/instrumentsans-italic.woff2 new file mode 100644 index 000000000..f5f286130 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/instrumentsans/instrumentsans-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/jetbrainsmono/jetbrainsmono-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/jetbrainsmono/jetbrainsmono-italic.woff2 new file mode 100644 index 000000000..a32555683 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/jetbrainsmono/jetbrainsmono-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/karla/karla-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/karla/karla-italic.woff2 new file mode 100644 index 000000000..d604500e3 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/karla/karla-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/labrada/labrada-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/labrada/labrada-italic.woff2 new file mode 100644 index 000000000..66349f197 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/labrada/labrada-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/librefranklin/librefranklin-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/librefranklin/librefranklin-italic.woff2 new file mode 100644 index 000000000..49904c137 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/librefranklin/librefranklin-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/lora/OFL.txt b/app/default-files/default-themes/simple/assets/dynamic/fonts/lora/OFL.txt index 0f6fdb15a..868bbd46a 100755 --- a/app/default-files/default-themes/simple/assets/dynamic/fonts/lora/OFL.txt +++ b/app/default-files/default-themes/simple/assets/dynamic/fonts/lora/OFL.txt @@ -1,93 +1,93 @@ -Copyright 2011 The Lora Project Authors (https://github.com/cyrealtype/Lora-Cyrillic), with Reserved Font Name "Lora". - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. +Copyright 2011 The Lora Project Authors (https://github.com/cyrealtype/Lora-Cyrillic), with Reserved Font Name "Lora". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/lora/lora-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/lora/lora-italic.woff2 new file mode 100644 index 000000000..8b5cbc575 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/lora/lora-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/manuale/manuale-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/manuale/manuale-italic.woff2 new file mode 100644 index 000000000..bc30468b1 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/manuale/manuale-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/merriweathersans/merriweathersans-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/merriweathersans/merriweathersans-italic.woff2 new file mode 100644 index 000000000..23f0e413a Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/merriweathersans/merriweathersans-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/montserrat/montserrat-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/montserrat/montserrat-italic.woff2 new file mode 100644 index 000000000..a7b5951d8 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/montserrat/montserrat-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/nunito/nunito-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/nunito/nunito-italic.woff2 new file mode 100644 index 000000000..79d8cb904 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/nunito/nunito-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/petrona/petrona-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/petrona/petrona-italic.woff2 new file mode 100644 index 000000000..868239d44 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/petrona/petrona-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/playfairdisplay/playfairdisplay-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/playfairdisplay/playfairdisplay-italic.woff2 new file mode 100644 index 000000000..edd963f87 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/playfairdisplay/playfairdisplay-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/plusjakartasans/plusjakartasans-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/plusjakartasans/plusjakartasans-italic.woff2 new file mode 100644 index 000000000..f1f5cd16f Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/plusjakartasans/plusjakartasans-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/publicsans/publicsans-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/publicsans/publicsans-italic.woff2 new file mode 100644 index 000000000..fc140ea3e Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/publicsans/publicsans-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/raleway/raleway-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/raleway/raleway-italic.woff2 new file mode 100644 index 000000000..7e8271fc5 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/raleway/raleway-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/redhatdisplay/redhatdisplay-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/redhatdisplay/redhatdisplay-italic.woff2 new file mode 100644 index 000000000..01ac448f7 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/redhatdisplay/redhatdisplay-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/redhatmono/redhatmono-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/redhatmono/redhatmono-italic.woff2 new file mode 100644 index 000000000..df47faa60 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/redhatmono/redhatmono-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/rokkitt/rokkitt-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/rokkitt/rokkitt-italic.woff2 new file mode 100644 index 000000000..186611d26 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/rokkitt/rokkitt-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/rubik/rubik-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/rubik/rubik-italic.woff2 new file mode 100644 index 000000000..1e575db73 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/rubik/rubik-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/sourcecodepro/sourcecodepro-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/sourcecodepro/sourcecodepro-italic.woff2 new file mode 100644 index 000000000..87e398ab6 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/sourcecodepro/sourcecodepro-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/urbanist/urbanist-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/urbanist/urbanist-italic.woff2 new file mode 100644 index 000000000..60640bdef Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/urbanist/urbanist-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/worksans/worksans-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/worksans/worksans-italic.woff2 new file mode 100644 index 000000000..8c69a4469 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/worksans/worksans-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/dynamic/fonts/yrsa/yrsa-italic.woff2 b/app/default-files/default-themes/simple/assets/dynamic/fonts/yrsa/yrsa-italic.woff2 new file mode 100644 index 000000000..cfaaef365 Binary files /dev/null and b/app/default-files/default-themes/simple/assets/dynamic/fonts/yrsa/yrsa-italic.woff2 differ diff --git a/app/default-files/default-themes/simple/assets/js/scripts.js b/app/default-files/default-themes/simple/assets/js/scripts.js index 0bf46483f..5faf56d0f 100755 --- a/app/default-files/default-themes/simple/assets/js/scripts.js +++ b/app/default-files/default-themes/simple/assets/js/scripts.js @@ -1,36 +1,28 @@ -// Sticky menu -var new_scroll_position = 0; -var last_scroll_position; -var header = document.getElementById("js-header"); -var stickyMenu = document.getElementById("js-navbar-menu"); - -window.addEventListener('scroll', function (e) { - last_scroll_position = window.scrollY; - - // Scrolling down - if (new_scroll_position < last_scroll_position && last_scroll_position > 40) { - header.classList.remove("is-visible"); - header.classList.add("is-hidden"); - - // Scrolling up - } else if (new_scroll_position > last_scroll_position) { - header.classList.remove("is-hidden"); - header.classList.add("is-visible"); - if (stickyMenu) { - stickyMenu.classList.add("is-sticky"); - } - } - - if (last_scroll_position < 1) { - header.classList.remove("is-visible"); - - if (stickyMenu) { - stickyMenu.classList.remove("is-sticky"); - } - } - - new_scroll_position = last_scroll_position; -}); +// Sticky header position on page scrolling up +const header = document.querySelector('.js-header'); +const stickyClass = 'sticky'; +let lastScrollTop = 0; +let isWaiting = false; + +window.addEventListener('scroll', () => { + if (!isWaiting) { + window.requestAnimationFrame(() => { + let currentScroll = window.scrollY || window.pageYOffset || document.documentElement.scrollTop; + + if (currentScroll > lastScrollTop) { + header.classList.remove(stickyClass); + } else if (currentScroll < lastScrollTop && currentScroll > 0) { + header.classList.add(stickyClass); + } else if (currentScroll <= 0) { + header.classList.remove(stickyClass); + } + + lastScrollTop = currentScroll; + isWaiting = false; + }); + isWaiting = true; + } +}, false); // Dropdown menu (function (menuConfig) { @@ -72,12 +64,12 @@ window.addEventListener('scroll', function (e) { var config = {}; - Object.keys(defaultConfig).forEach(function(key) { + Object.keys(defaultConfig).forEach(function (key) { config[key] = defaultConfig[key]; }); if (typeof menuConfig === 'object') { - Object.keys(menuConfig).forEach(function(key) { + Object.keys(menuConfig).forEach(function (key) { config[key] = menuConfig[key]; }); } @@ -85,7 +77,7 @@ window.addEventListener('scroll', function (e) { /** * Menu initializer */ - function init () { + function init() { if (!document.querySelectorAll(config.wrapperSelector).length) { return; } @@ -108,7 +100,7 @@ window.addEventListener('scroll', function (e) { /** * Function responsible for the submenu positions */ - function initSubmenuPositions () { + function initSubmenuPositions() { var submenuParents = document.querySelectorAll(config.wrapperSelector + ' .' + config.parentItemClass); for (var i = 0; i < submenuParents.length; i++) { @@ -185,7 +177,7 @@ window.addEventListener('scroll', function (e) { /** * Function used to init mobile menu - overlay mode */ - function initMobileMenuOverlay () { + function initMobileMenuOverlay() { var menuWrapper = document.createElement('div'); menuWrapper.classList.add(config.mobileMenuOverlayClass); menuWrapper.classList.add(config.hiddenElementClass); @@ -223,13 +215,13 @@ window.addEventListener('scroll', function (e) { relatedContainer.classList.remove(config.relatedContainerForOverlayMenuClass); } } - }); + }); } /** * Function used to init mobile menu - sidebar mode */ - function initMobileMenuSidebar () { + function initMobileMenuSidebar() { // Create menu structure var menuWrapper = document.createElement('div'); menuWrapper.classList.add(config.mobileMenuSidebarClass); @@ -288,7 +280,7 @@ window.addEventListener('scroll', function (e) { /** * Set aria-hidden="false" for submenus */ - function setAriaForSubmenus (menuWrapper) { + function setAriaForSubmenus(menuWrapper) { var submenus = menuWrapper.querySelectorAll(config.submenuSelector); for (var i = 0; i < submenus.length; i++) { @@ -299,7 +291,7 @@ window.addEventListener('scroll', function (e) { /** * Wrap all submenus into div wrappers */ - function wrapSubmenusIntoContainer (menuWrapper) { + function wrapSubmenusIntoContainer(menuWrapper) { var submenus = menuWrapper.querySelectorAll(config.submenuSelector); for (var i = 0; i < submenus.length; i++) { @@ -313,7 +305,7 @@ window.addEventListener('scroll', function (e) { /** * Initialize submenu toggle events */ - function initToggleSubmenu (menuWrapper) { + function initToggleSubmenu(menuWrapper) { // Init parent menu item events var parents = menuWrapper.querySelectorAll('.' + config.parentItemClass); @@ -325,9 +317,9 @@ window.addEventListener('scroll', function (e) { var content = submenu.firstElementChild; if (submenu.classList.contains(config.openedMenuClass)) { - var height = content.clientHeight; + var height = content.clientHeight; submenu.style.height = height + 'px'; - + setTimeout(function () { submenu.style.height = '0px'; }, 0); @@ -340,10 +332,10 @@ window.addEventListener('scroll', function (e) { content.setAttribute('aria-hidden', true); content.parentNode.firstElementChild.setAttribute('aria-expanded', false); } else { - var height = content.clientHeight; + var height = content.clientHeight; submenu.classList.add(config.openedMenuClass); submenu.style.height = '0px'; - + setTimeout(function () { submenu.style.height = height + 'px'; }, 0); @@ -385,7 +377,7 @@ window.addEventListener('scroll', function (e) { /** * Set aria-* attributes according to the current activity state */ - function initAriaAttributes () { + function initAriaAttributes() { var allAriaElements = document.querySelectorAll(config.wrapperSelector + ' ' + '*[aria-hidden]'); for (var i = 0; i < allAriaElements.length; i++) { @@ -407,7 +399,7 @@ window.addEventListener('scroll', function (e) { /** * Close menu on click link */ - function initClosingMenuOnClickLink () { + function initClosingMenuOnClickLink() { var links = document.querySelectorAll(config.menuSelector + ' a'); for (var i = 0; i < links.length; i++) { @@ -424,7 +416,7 @@ window.addEventListener('scroll', function (e) { /** * Close menu */ - function closeMenu (clickedLink, forceClose) { + function closeMenu(clickedLink, forceClose) { if (forceClose === false) { if (clickedLink.parentNode.classList.contains(config.parentItemClass)) { return; @@ -461,44 +453,40 @@ window.addEventListener('scroll', function (e) { init(); })(window.publiiThemeMenuConfig); -// Load comments -var comments = document.getElementById("js-comments"); - if (comments) { - comments.addEventListener("click", function() { - comments.classList.toggle("is-hidden"); - var container = document.getElementById("js-comments__inner"); - container.classList.toggle("is-visible"); - }); - } - // Load search input area -var searchButton = document.querySelector(".js-search-btn"); - searchOverlay = document.querySelector(".js-search-overlay"); - searchClose = document.querySelector(".js-search-close"); - searchInput = document.querySelector("[type='search']"); - -if (searchButton) { - searchButton.addEventListener("click", function () { - searchOverlay.classList.add("expanded"); - if (searchInput) { - setTimeout(function() { - searchInput.focus(); - }, 60); - } +const searchButton = document.querySelector('.js-search-btn'); +const searchOverlay = document.querySelector('.js-search-overlay'); +const searchInput = document.querySelector('input[type="search"]'); + +if (searchButton && searchOverlay && searchInput) { + searchButton.addEventListener('click', (event) => { + event.stopPropagation(); + searchOverlay.classList.toggle('expanded'); + + if (searchOverlay.classList.contains('expanded')) { + setTimeout(() => { + searchInput.focus(); + }, 60); + } + }); + + searchOverlay.addEventListener('click', (event) => { + event.stopPropagation(); }); - - searchClose.addEventListener("click", function () { + + document.body.addEventListener('click', () => { searchOverlay.classList.remove('expanded'); }); } + // Share buttons pop-up (function () { // share popup - let shareButton = document.querySelector('.js-post__share-button'); - let sharePopup = document.querySelector('.js-post__share-popup'); + const shareButton = document.querySelector('.js-content__share-button'); + const sharePopup = document.querySelector('.js-content__share-popup'); - if (shareButton) { + if (shareButton && sharePopup) { sharePopup.addEventListener('click', function (e) { e.stopPropagation(); }); @@ -515,98 +503,117 @@ if (searchButton) { } // link selector and pop-up window size - var Config = { + const Config = { Link: ".js-share", Width: 500, Height: 500 }; - // add handler links - var slink = document.querySelectorAll(Config.Link); - for (var a = 0; a < slink.length; a++) { - slink[a].onclick = PopupHandler; - } + + // add handler to links + const shareLinks = document.querySelectorAll(Config.Link); + shareLinks.forEach(link => { + link.addEventListener('click', PopupHandler); + }); + // create popup function PopupHandler(e) { - e = (e ? e : window.event); - var t = (e.target ? e.target : e.srcElement); + e.preventDefault(); + + const target = e.target.closest(Config.Link); + if (!target) return; + // hide share popup if (sharePopup) { sharePopup.classList.remove('is-visible'); } + // popup position - var px = Math.floor(((screen.availWidth || 1024) - Config.Width) / 2), - py = Math.floor(((screen.availHeight || 700) - Config.Height) / 2); + const px = Math.floor((window.innerWidth - Config.Width) / 2); + const py = Math.floor((window.innerHeight - Config.Height) / 2); + // open popup - var link_href = t.href ? t.href : t.parentNode.href; - var popup = window.open(link_href, "social", - "width=" + Config.Width + ",height=" + Config.Height + - ",left=" + px + ",top=" + py + - ",location=0,menubar=0,toolbar=0,status=0,scrollbars=1,resizable=1"); + const linkHref = target.href; + const popup = window.open(linkHref, "social", ` + width=${Config.Width}, + height=${Config.Height}, + left=${px}, + top=${py}, + location=0, + menubar=0, + toolbar=0, + status=0, + scrollbars=1, + resizable=1 + `); + if (popup) { popup.focus(); - if (e.preventDefault) e.preventDefault(); - e.returnValue = false; } - - return !!popup; } })(); -// Back to top -var backToTopButton = document.getElementById("backToTop"); -if (backToTopButton) { - window.onscroll = function() {backToTopScrollFunction()}; - - function backToTopScrollFunction() { - if (document.body.scrollTop > 400 || document.documentElement.scrollTop > 400) { - backToTopButton.classList.add("is-visible"); - } else { - backToTopButton.classList.remove("is-visible"); - } - } - - function backToTopFunction() { - document.body.scrollTop = 0; - document.documentElement.scrollTop = 0; - }; -} +// Back to top +document.addEventListener('DOMContentLoaded', () => { + const backToTopButton = document.getElementById('backToTop'); + + if (backToTopButton) { + const backToTopScrollFunction = () => { + if (document.body.scrollTop > 400 || document.documentElement.scrollTop > 400) { + backToTopButton.classList.add('is-visible'); + } else { + backToTopButton.classList.remove('is-visible'); + } + }; + + const backToTopFunction = () => { + window.scrollTo({ + top: 0, + behavior: 'smooth' + }); + }; + + window.addEventListener('scroll', backToTopScrollFunction); + backToTopButton.addEventListener('click', backToTopFunction); + } +}); + // Responsive embeds script (function () { - let wrappers = document.querySelectorAll('.post__video, .post__iframe'); + let wrappers = document.querySelectorAll('.post__video, .post__iframe'); - for (let i = 0; i < wrappers.length; i++) { - let embed = wrappers[i].querySelector('iframe, embed, video, object'); + for (let i = 0; i < wrappers.length; i++) { + let embed = wrappers[i].querySelector('iframe, embed, video, object'); - if (!embed) { - continue; - } + if (!embed) { + continue; + } if (embed.getAttribute('data-responsive') === 'false') { continue; } - let w = embed.getAttribute('width'); - let h = embed.getAttribute('height'); - let ratio = false; - - if (!w || !h) { - continue; - } - - if (w.indexOf('%') > -1 && h.indexOf('%') > -1) { // percentage mode - w = parseFloat(w.replace('%', '')); - h = parseFloat(h.replace('%', '')); - ratio = h / w; - } else if (w.indexOf('%') === -1 && h.indexOf('%') === -1) { // pixels mode - w = parseInt(w, 10); - h = parseInt(h, 10); - ratio = h / w; - } - - if (ratio !== false) { - let ratioValue = (ratio * 100) + '%'; - wrappers[i].setAttribute('style', '--embed-aspect-ratio:' + ratioValue); - } - } + let w = embed.getAttribute('width'); + let h = embed.getAttribute('height'); + let ratio = false; + + if (!w || !h) { + continue; + } + + if (w.indexOf('%') > -1 && h.indexOf('%') > -1) { // percentage mode + w = parseFloat(w.replace('%', '')); + h = parseFloat(h.replace('%', '')); + ratio = h / w; + } else if (w.indexOf('%') === -1 && h.indexOf('%') === -1) { // pixels mode + w = parseInt(w, 10); + h = parseInt(h, 10); + ratio = h / w; + } + + if (ratio !== false) { + let ratioValue = (ratio * 100) + '%'; + wrappers[i].setAttribute('style', '--embed-aspect-ratio:' + ratioValue); + } + } })(); \ No newline at end of file diff --git a/app/default-files/default-themes/simple/assets/js/scripts.min.js b/app/default-files/default-themes/simple/assets/js/scripts.min.js index 7aae32496..333cf83ad 100755 --- a/app/default-files/default-themes/simple/assets/js/scripts.min.js +++ b/app/default-files/default-themes/simple/assets/js/scripts.min.js @@ -1 +1,32 @@ -var new_scroll_position=0;var last_scroll_position;var header=document.getElementById("js-header");var stickyMenu=document.getElementById("js-navbar-menu");window.addEventListener("scroll",function(a){last_scroll_position=window.scrollY;if(new_scroll_position40){header.classList.remove("is-visible");header.classList.add("is-hidden")}else{if(new_scroll_position>last_scroll_position){header.classList.remove("is-hidden");header.classList.add("is-visible");if(stickyMenu){stickyMenu.classList.add("is-sticky")}}}if(last_scroll_position<1){header.classList.remove("is-visible");if(stickyMenu){stickyMenu.classList.remove("is-sticky")}}new_scroll_position=last_scroll_position});(function(e){var d={mobileMenuMode:"overlay",animationSpeed:300,submenuWidth:300,doubleClickTime:500,mobileMenuExpandableSubmenus:false,isHoverMenu:true,wrapperSelector:".navbar",buttonSelector:".navbar__toggle",menuSelector:".navbar__menu",submenuSelector:".navbar__submenu",mobileMenuSidebarLogoSelector:null,mobileMenuSidebarLogoUrl:null,relatedContainerForOverlayMenuSelector:null,ariaButtonAttribute:"aria-haspopup",separatorItemClass:"is-separator",parentItemClass:"has-submenu",submenuLeftPositionClass:"is-left-submenu",submenuRightPositionClass:"is-right-submenu",mobileMenuOverlayClass:"navbar_mobile_overlay",mobileMenuSubmenuWrapperClass:"navbar__submenu_wrapper",mobileMenuSidebarClass:"navbar_mobile_sidebar",mobileMenuSidebarOverlayClass:"navbar_mobile_sidebar__overlay",hiddenElementClass:"is-hidden",openedMenuClass:"is-active",noScrollClass:"no-scroll",relatedContainerForOverlayMenuClass:"is-visible"};var b={};Object.keys(d).forEach(function(p){b[p]=d[p]});if(typeof e==="object"){Object.keys(e).forEach(function(p){b[p]=e[p]})}function o(){if(!document.querySelectorAll(b.wrapperSelector).length){return}c();if(b.mobileMenuMode==="overlay"){m()}else{if(b.mobileMenuMode==="sidebar"){l()}}n();if(!b.isHoverMenu){f()}}function c(){var p=document.querySelectorAll(b.wrapperSelector+" ."+b.parentItemClass);for(var r=0;r'}}p+=document.querySelector(b.menuSelector).outerHTML;r.innerHTML=p;var s=document.createElement("div");s.classList.add(b.mobileMenuSidebarOverlayClass);s.classList.add(b.hiddenElementClass);document.body.appendChild(s);document.body.appendChild(r);if(b.mobileMenuExpandableSubmenus){j(r);a(r)}else{k(r)}r.addEventListener("click",function(t){t.stopPropagation()});s.addEventListener("click",function(){r.classList.add(b.hiddenElementClass);s.classList.add(b.hiddenElementClass);q.classList.remove(b.openedMenuClass);q.setAttribute(b.ariaButtonAttribute,false);document.documentElement.classList.remove(b.noScrollClass)});var q=document.querySelector(b.buttonSelector);q.addEventListener("click",function(){r.classList.toggle(b.hiddenElementClass);s.classList.toggle(b.hiddenElementClass);q.classList.toggle(b.openedMenuClass);q.setAttribute(b.ariaButtonAttribute,q.classList.contains(b.openedMenuClass));document.documentElement.classList.toggle(b.noScrollClass)})}function k(r){var p=r.querySelectorAll(b.submenuSelector);for(var q=0;qv){x.stopPropagation();g(this,true)}}}})}}}}function f(){var q=document.querySelectorAll(b.wrapperSelector+" *[aria-hidden]");for(var r=0;r400||document.documentElement.scrollTop>400){backToTopButton.classList.add("is-visible")}else{backToTopButton.classList.remove("is-visible")}}function backToTopFunction(){document.body.scrollTop=0;document.documentElement.scrollTop=0}}(function(){let wrappers=document.querySelectorAll(".post__video, .post__iframe");for(let i=0;i-1&&h.indexOf("%")>-1){w=parseFloat(w.replace("%",""));h=parseFloat(h.replace("%",""));ratio=h/w}else{if(w.indexOf("%")===-1&&h.indexOf("%")===-1){w=parseInt(w,10);h=parseInt(h,10);ratio=h/w}}if(ratio!==false){let ratioValue=(ratio*100)+"%";wrappers[i].setAttribute("style","--embed-aspect-ratio:"+ratioValue)}}})(); \ No newline at end of file +const header=document.querySelector('.js-header');const stickyClass='sticky';let lastScrollTop=0;let isWaiting=false;window.addEventListener('scroll',()=>{if(!isWaiting){window.requestAnimationFrame(()=>{let currentScroll=window.scrollY||window.pageYOffset||document.documentElement.scrollTop;if(currentScroll>lastScrollTop){header.classList.remove(stickyClass);}else if(currentScroll0){header.classList.add(stickyClass);}else if(currentScroll<=0){header.classList.remove(stickyClass);} +lastScrollTop=currentScroll;isWaiting=false;});isWaiting=true;}},false);(function(menuConfig){var defaultConfig={mobileMenuMode:'overlay',animationSpeed:300,submenuWidth:300,doubleClickTime:500,mobileMenuExpandableSubmenus:false,isHoverMenu:true,wrapperSelector:'.navbar',buttonSelector:'.navbar__toggle',menuSelector:'.navbar__menu',submenuSelector:'.navbar__submenu',mobileMenuSidebarLogoSelector:null,mobileMenuSidebarLogoUrl:null,relatedContainerForOverlayMenuSelector:null,ariaButtonAttribute:'aria-haspopup',separatorItemClass:'is-separator',parentItemClass:'has-submenu',submenuLeftPositionClass:'is-left-submenu',submenuRightPositionClass:'is-right-submenu',mobileMenuOverlayClass:'navbar_mobile_overlay',mobileMenuSubmenuWrapperClass:'navbar__submenu_wrapper',mobileMenuSidebarClass:'navbar_mobile_sidebar',mobileMenuSidebarOverlayClass:'navbar_mobile_sidebar__overlay',hiddenElementClass:'is-hidden',openedMenuClass:'is-active',noScrollClass:'no-scroll',relatedContainerForOverlayMenuClass:'is-visible'};var config={};Object.keys(defaultConfig).forEach(function(key){config[key]=defaultConfig[key];});if(typeof menuConfig==='object'){Object.keys(menuConfig).forEach(function(key){config[key]=menuConfig[key];});} +function init(){if(!document.querySelectorAll(config.wrapperSelector).length){return;} +initSubmenuPositions();if(config.mobileMenuMode==='overlay'){initMobileMenuOverlay();}else if(config.mobileMenuMode==='sidebar'){initMobileMenuSidebar();} +initClosingMenuOnClickLink();if(!config.isHoverMenu){initAriaAttributes();}};function initSubmenuPositions(){var submenuParents=document.querySelectorAll(config.wrapperSelector+' .'+config.parentItemClass);for(var i=0;i';} +menuContentHTML+=document.querySelector(config.menuSelector).outerHTML;menuWrapper.innerHTML=menuContentHTML;var menuOverlay=document.createElement('div');menuOverlay.classList.add(config.mobileMenuSidebarOverlayClass);menuOverlay.classList.add(config.hiddenElementClass);document.body.appendChild(menuOverlay);document.body.appendChild(menuWrapper);if(config.mobileMenuExpandableSubmenus){wrapSubmenusIntoContainer(menuWrapper);initToggleSubmenu(menuWrapper);}else{setAriaForSubmenus(menuWrapper);} +menuWrapper.addEventListener('click',function(e){e.stopPropagation();});menuOverlay.addEventListener('click',function(){menuWrapper.classList.add(config.hiddenElementClass);menuOverlay.classList.add(config.hiddenElementClass);button.classList.remove(config.openedMenuClass);button.setAttribute(config.ariaButtonAttribute,false);document.documentElement.classList.remove(config.noScrollClass);});var button=document.querySelector(config.buttonSelector);button.addEventListener('click',function(){menuWrapper.classList.toggle(config.hiddenElementClass);menuOverlay.classList.toggle(config.hiddenElementClass);button.classList.toggle(config.openedMenuClass);button.setAttribute(config.ariaButtonAttribute,button.classList.contains(config.openedMenuClass));document.documentElement.classList.toggle(config.noScrollClass);});} +function setAriaForSubmenus(menuWrapper){var submenus=menuWrapper.querySelectorAll(config.submenuSelector);for(var i=0;icurrentTime){e.stopPropagation();closeMenu(this,true);}});}}}} +function initAriaAttributes(){var allAriaElements=document.querySelectorAll(config.wrapperSelector+' '+'*[aria-hidden]');for(var i=0;i{event.stopPropagation();searchOverlay.classList.toggle('expanded');if(searchOverlay.classList.contains('expanded')){setTimeout(()=>{searchInput.focus();},60);}});searchOverlay.addEventListener('click',(event)=>{event.stopPropagation();});document.body.addEventListener('click',()=>{searchOverlay.classList.remove('expanded');});} +(function(){const shareButton=document.querySelector('.js-content__share-button');const sharePopup=document.querySelector('.js-content__share-popup');if(shareButton&&sharePopup){sharePopup.addEventListener('click',function(e){e.stopPropagation();});shareButton.addEventListener('click',function(e){e.preventDefault();e.stopPropagation();sharePopup.classList.toggle('is-visible');});document.body.addEventListener('click',function(){sharePopup.classList.remove('is-visible');});} +const Config={Link:".js-share",Width:500,Height:500};const shareLinks=document.querySelectorAll(Config.Link);shareLinks.forEach(link=>{link.addEventListener('click',PopupHandler);});function PopupHandler(e){e.preventDefault();const target=e.target.closest(Config.Link);if(!target)return;if(sharePopup){sharePopup.classList.remove('is-visible');} +const px=Math.floor((window.innerWidth-Config.Width)/2);const py=Math.floor((window.innerHeight-Config.Height)/2);const linkHref=target.href;const popup=window.open(linkHref,"social",`width=${Config.Width},height=${Config.Height},left=${px},top=${py},location=0,menubar=0,toolbar=0,status=0,scrollbars=1,resizable=1`);if(popup){popup.focus();}}})();document.addEventListener('DOMContentLoaded',()=>{const backToTopButton=document.getElementById('backToTop');if(backToTopButton){const backToTopScrollFunction=()=>{if(document.body.scrollTop>400||document.documentElement.scrollTop>400){backToTopButton.classList.add('is-visible');}else{backToTopButton.classList.remove('is-visible');}};const backToTopFunction=()=>{window.scrollTo({top:0,behavior:'smooth'});};window.addEventListener('scroll',backToTopScrollFunction);backToTopButton.addEventListener('click',backToTopFunction);}});(function(){let wrappers=document.querySelectorAll('.post__video, .post__iframe');for(let i=0;i-1&&h.indexOf('%')>-1){w=parseFloat(w.replace('%',''));h=parseFloat(h.replace('%',''));ratio=h/w;}else if(w.indexOf('%')===-1&&h.indexOf('%')===-1){w=parseInt(w,10);h=parseInt(h,10);ratio=h/w;} +if(ratio!==false){let ratioValue=(ratio*100)+'%';wrappers[i].setAttribute('style','--embed-aspect-ratio:'+ratioValue);}}})(); \ No newline at end of file diff --git a/app/default-files/default-themes/simple/assets/js/svg-map.js b/app/default-files/default-themes/simple/assets/js/svg-map.js index 72346a3c8..17d62ac8e 100755 --- a/app/default-files/default-themes/simple/assets/js/svg-map.js +++ b/app/default-files/default-themes/simple/assets/js/svg-map.js @@ -1,24 +1,28 @@ window.publiiSvgFix = { - + "#search": { - "viewbox": "0 0 15 15", - "content": "" + "viewbox": "0 0 24 24", + "content": "" + }, + "#share": { + "viewbox": "0 0 24 24", + "content": "" }, "#arrow-prev": { - "viewbox": "0 0 20 8", - "content": "" + "viewbox": "0 0 24 24", + "content": "" }, "#arrow-next": { - "viewbox": "0 0 20 8", - "content": "" + "viewbox": "0 0 24 24", + "content": "" }, "#toparrow": { - "viewbox": "0 0 23 23", - "content": "" + "viewbox": "0 0 24 24", + "content": "" }, "#website": { "viewbox": "0 0 24 24", - "content": "" + "content": "" }, "#facebook": { "viewbox": "0 0 32 32", diff --git a/app/default-files/default-themes/simple/assets/svg/svg-map.svg b/app/default-files/default-themes/simple/assets/svg/svg-map.svg index bb9250f95..970e16b0a 100755 --- a/app/default-files/default-themes/simple/assets/svg/svg-map.svg +++ b/app/default-files/default-themes/simple/assets/svg/svg-map.svg @@ -1,30 +1,24 @@ - - - - - - - + + + - - - - - - + + + + + + + - - - + + - - + diff --git a/app/default-files/default-themes/simple/author.hbs b/app/default-files/default-themes/simple/author.hbs index 29a00de3f..0ef58d281 100755 --- a/app/default-files/default-themes/simple/author.hbs +++ b/app/default-files/default-themes/simple/author.hbs @@ -1,21 +1,63 @@ {{> head}} -{{> top}} +{{> navbar}}
{{#author}} -
+
+
+
+ {{#if authorViewConfig.displayAvatar}} + {{#if avatar}} + {{avatarImage.alt}} + {{/if}} + {{/if}} +
+

+ {{name}} + {{#if authorViewConfig.displayPostCounter}}({{postsNumber}}){{/if}} +

+ {{#if authorViewConfig.displayDescription}} + {{#if description}} +
{{{description}}}
+ {{/if}} + {{/if}} + {{#if authorViewConfig.displayWebsite}} + {{#if website}} + + {{/if}} + {{/if}} +
+
+
+ {{#if authorViewConfig.displayFeaturedImage}} {{#featuredImage}} {{#if url}} -
- {{alt}} +
+
+ {{alt}} +
{{#checkIfAny caption credits}}
@@ -24,134 +66,91 @@
{{/checkIfAny}}
- {{else}} - {{#checkIfAll @config.custom.uploadHero @config.custom.displayHeroImage}} -
- {{@config.custom.uploadHeroAlt}} - - {{#if @config.custom.uploadHeroCaption}} -
- {{@config.custom.uploadHeroCaption}} -
- {{/if}} -
- {{/checkIfAll}} {{/if}} {{/featuredImage}} {{/if}} -
-
- {{#if authorViewConfig.displayAvatar}} - {{#if avatar}} - {{avatarImage.alt}} - {{/if}} - {{/if}} -

- {{name}} - {{#if authorViewConfig.displayPostCounter}}({{postsNumber}}){{/if}} -

- {{#if authorViewConfig.displayDescription}} - {{#if description}} -
{{{description}}}
- {{/if}} - {{/if}} - {{#if authorViewConfig.displayWebsite}} - {{#if website}} - - {{/if}} - {{/if}} -
-
+ {{#if authorViewConfig.displayPostList}} -
+
{{#each ../posts}} -
-
- {{#checkIfAny @config.custom.authorAvatar @config.custom.authorAuthor @config.custom.authorDate}} +
+ {{#if @config.custom.feedFeaturedImage}} + {{#featuredImage}} + {{#if url}} +
+ {{alt}} +
+ {{/if}} + {{/featuredImage}} + {{/if}} +
+
+ {{#checkIfAny @config.custom.feedAvatar @config.custom.feedAuthor @config.custom.feedDate}}
{{#author}} - {{#if @config.custom.authorAvatar}} + {{#if @config.custom.feedAvatar}} {{#if avatar}} {{/if}} {{/if}} - {{#if @config.custom.authorAuthor}} + {{#if @config.custom.feedAuthor}} {{name}} {{/if}} {{/author}} - {{#if @config.custom.authorDate}} - + {{#if @config.custom.feedDate}} + {{#checkIf @config.custom.feedDateType '==' "published" }} + + {{/checkIf}} + {{#checkIf @config.custom.feedDateType '==' "modified" }} + + {{/checkIf}} {{/if}}
{{/checkIfAny}} -

+

{{title}}

-
- {{#if @config.custom.authorFeaturedImage}} - {{#featuredImage}} - {{#if url}} -
- - {{alt}} - -
- {{/if}} - {{/featuredImage}} +
+ {{#if hasCustomExcerpt}} + {{{ excerpt }}} + {{else}} +

{{{ excerpt }}}

{{/if}} -
- {{#if hasCustomExcerpt}} - {{{ excerpt }}} - {{else}} -

{{{ excerpt }}}

- {{/if}} - {{#if @config.custom.authorReadMore}} - - {{ translate 'post.readMore' }} - {{/if}} -
-
+ {{#if @config.custom.feedtReadMore}} + + {{ translate 'post.readMore' }} + {{/if}} +
+ {{/each}} {{> pagination}}
diff --git a/app/default-files/default-themes/simple/computed-options.js b/app/default-files/default-themes/simple/computed-options.js new file mode 100644 index 000000000..4beeb5e8a --- /dev/null +++ b/app/default-files/default-themes/simple/computed-options.js @@ -0,0 +1,86 @@ +module.exports = function (themeConfig) { + const fontParams = { + 'adventpro': { hasItalic: true }, + 'aleo': { hasItalic: true }, + 'andadapro': { hasItalic: true }, + 'antonio': { hasItalic: false }, + 'archivonarrow': { hasItalic: true }, + 'asap': { hasItalic: true }, + 'assistant': { hasItalic: false }, + 'besley': { hasItalic: true }, + 'bitter': { hasItalic: true }, + 'brygada1918': { hasItalic: true }, + 'cabin': { hasItalic: true }, + 'cairo': { hasItalic: false }, + 'comfortaa': { hasItalic: false }, + 'dancingscript': { hasItalic: false }, + 'dosis': { hasItalic: false }, + 'domine': { hasItalic: false }, + 'exo': { hasItalic: true }, + 'faustina': { hasItalic: true }, + 'figtree': { hasItalic: true }, + 'frankruhllibre': { hasItalic: false }, + 'glory': { hasItalic: true }, + 'gluten': { hasItalic: false }, + 'heebo': { hasItalic: false }, + 'imbue': { hasItalic: false }, + 'instrumentsans': { hasItalic: true }, + 'jetbrainsmono': { hasItalic: true }, + 'jura': { hasItalic: false }, + 'karla': { hasItalic: true }, + 'kreon': { hasItalic: false }, + 'labrada': { hasItalic: true }, + 'lemonada': { hasItalic: false }, + 'lexend': { hasItalic: false }, + 'librefranklin': { hasItalic: true }, + 'lora': { hasItalic: true }, + 'manuale': { hasItalic: true }, + 'manrope': { hasItalic: false }, + 'mavenpro': { hasItalic: false }, + 'merriweathersans': { hasItalic: true }, + 'montserrat': { hasItalic: true }, + 'nunito': { hasItalic: true }, + 'orbitron': { hasItalic: false }, + 'oswald': { hasItalic: false }, + 'petrona': { hasItalic: true }, + 'playfairdisplay': { hasItalic: true }, + 'plusjakartasans': { hasItalic: true }, + 'publicsans': { hasItalic: true }, + 'quicksand': { hasItalic: false }, + 'raleway': { hasItalic: true }, + 'redhatdisplay': { hasItalic: true }, + 'redhatmono': { hasItalic: true }, + 'robotoflex': { hasItalic: false }, + 'robotoslab': { hasItalic: false }, + 'rokkitt': { hasItalic: true }, + 'rubik': { hasItalic: true }, + 'ruda': { hasItalic: false }, + 'smoochsans': { hasItalic: false }, + 'sourcecodepro': { hasItalic: true }, + 'spartan': { hasItalic: false }, + 'system-ui': { hasItalic: false }, + 'urbanist': { hasItalic: true }, + 'worksans': { hasItalic: true }, + 'yanonekaffeesatz': { hasItalic: false }, + 'yrsa': { hasItalic: true } + }; + + let fontBody = themeConfig.customConfig.find(option => option.name === 'fontBody').value; + let fontHeadings = themeConfig.customConfig.find(option => option.name === 'fontHeadings').value; + + let disableFontBodyItalic = themeConfig.customConfig.find(option => option.name === 'disableFontBodyItalic').value; + let disableFontHeadingsItalic = themeConfig.customConfig.find(option => option.name === 'disableFontHeadingsItalic').value; + + return [ + { + name: 'fontBodyItalic', + type: 'checkbox', + value: !disableFontBodyItalic && (fontParams[fontBody]?.hasItalic || false) + }, + { + name: 'fontHeadingsItalic', + type: 'checkbox', + value: !disableFontHeadingsItalic && (fontParams[fontHeadings]?.hasItalic || false) + } + ]; +}; diff --git a/app/default-files/default-themes/simple/config.json b/app/default-files/default-themes/simple/config.json index 33df0b67a..6294cd871 100755 --- a/app/default-files/default-themes/simple/config.json +++ b/app/default-files/default-themes/simple/config.json @@ -1,12 +1,17 @@ { "name": "Simple", - "version": "2.8.3.0", + "version": "3.0.0.0", "author": "TidyCustoms ", "menus": { "mainMenu": { "desc": "", "name": "Main menu", "maxLevels": -1 + }, + "footerMenu": { + "desc": "", + "name": "Footer menu", + "maxLevels": 1 } }, "renderer": { @@ -21,7 +26,8 @@ "createSearchPage": false, "create404page": true, "customHTML": { - "afterPost": "After every post" + "afterPost": "After every post", + "afterPage": "After every page" } }, "supportedFeatures": { @@ -36,10 +42,13 @@ "customSharing": true, "customSearch": true, "customComments": true, - "embedConsents": true + "embedConsents": true, + "pages": true + }, + "pageTemplates": { + "empty": "Empty container" }, "config": [ - { "name": "postsPerPage", "label": "Posts per page", @@ -82,23 +91,51 @@ }, { "name": "pageWidth", - "label": "Post entry width", + "label": "Page width", + "group": "Layout", + "note": "Defines the overall width of the page. This should always be greater than or equal to the Entry width to ensure proper layout. Set to 100% for full-screen width.", + "value": "66rem", + "type": "text" + }, + { + "name": "entryWidth", + "label": "Entry width", "group": "Layout", - "note": "For full-screen width, set to 100%", + "note": "Defines the width of content for posts and pages. Ensure that this value is smaller than or equal to the Page width for proper content display. Set to 100% for full-screen width.", "value": "42rem", "type": "text" + }, + { + "name": "borderRadius", + "label": "Corner Roundness", + "group": "Layout", + "note": "Adjust the roundness of corners for elements like images, buttons or dropdown menus. Increasing this value (in pixels) will make the corners more rounded.", + "value": "3", + "type": "range", + "min": 0, + "max": 100, + "step": 1 + }, + { + "name": "baseline", + "label": "Baseline", + "group": "Layout", + "note": "Defines a core vertical rhythm unit for consistent spacing across the website, used as a multiplier for margins and paddings. Increasing this value enhances spacing, creating an airier layout, while decreasing it compacts content, for tighter visual appeal.", + "value": "0.28333rem", + "type": "text" }, { "name": "separator", "type": "separator", - "label": "Front page", + "label": "Homepage", "group": "Layout", + "note": "This option will be removed in the next theme update. You can now set your homepage natively within the Site Settings!", "size": "small" }, { "name": "frontSource", - "label": "Front page displays", - "group": "Layout", + "label": "Homepage displays", + "group": "Layout", "value": "default", "type": "radio", "options": [ @@ -111,19 +148,20 @@ "value": "post" } ] - }, + }, { "name": "frontPost", "label": "Select a post", "group": "Layout", - "note": "This option allows you to display the selected post on the front page. We strongly recommended 'hide' and 'noindex' the selected post to avoid the duplicate content issue.", + "note": "This option allows you to modify the height of the hero section. Use of VH units is recommended; set to 100vh for full-screen height.", "value": "", - "type": "posts-dropdown", + "type": "posts-dropdown", "dependencies": [ - { - "field": "frontSource", - "value": "post" - }] + { + "field": "frontSource", + "value": "post" + } + ] }, { "name": "frontGallery", @@ -131,42 +169,52 @@ "label": "", "note": "Enable if you want to display a post with image gallery", "value": false, - "type": "checkbox", + "type": "checkbox", "dependencies": [ - { - "field": "frontSource", - "value": "post" - }] + { + "field": "frontSource", + "value": "post" + } + ] }, { "name": "separator", "type": "separator", "label": "Hero section", "group": "Layout", - "size": "small" + "size": "big" }, { - "name": "heightHero", - "label": "Height", + "name": "alignHero", + "label": "Content alignment", "group": "Layout", - "note": "This option allows you to modify the height of the hero section. Use of VH units recommended; for full-screen set to 100vh", - "value": "80vh", - "type": "text" - }, + "value": "left", + "type": "radio", + "options": [ + { + "label": "Left", + "value": "left" + }, + { + "label": "Center", + "value": "center" + } + ] + }, + { - "name": "separator", - "type": "separator", - "label": "", + "name": "textHero", + "label": "Text", "group": "Layout", - "size": "small thin" + "value": "

Discover My Blogging Journey: Adventures, Travels, and Hobbies!

Join me as I share captivating stories, travel experiences, and dive into the joys of my favorite hobbies.

Read more

", + "type": "wysiwyg" }, { - "name": "displayHeroImage", + "name": "heightHero", + "label": "Image height", "group": "Layout", - "label": "Image assignment", - "note": "Display the hero image on all pages including front page, tags, authors, search, 404, also on the post pages when a featured image is unavailable; otherwise, it will be displayed on the front page only. ", - "value": false, - "type": "checkbox" + "value": "50vh", + "type": "text" }, { "name": "uploadHero", @@ -191,227 +239,140 @@ "placeholder": "Add text caption to the hero image", "value": "", "type": "text" - }, + }, { "name": "separator", "type": "separator", - "label": "", - "group": "Layout", - "size": "small thin" - }, - { - "name": "textHero", - "label": "Text", - "group": "Layout", - "value": "

Welcome to my blog :)

Create a unique and beautiful blog quickly and easily.

", - "type": "wysiwyg" - }, - { - "name": "siteOwner", - "group": "Layout", - "label": "Show site owner", - "note": "Display the avatar and the name of the website owner (Main author)", - "value": true, - "type": "checkbox" - }, - { - "name": "separator", - "type": "separator", - "label": "", - "group": "Layout", - "note": "The appearance settings for the hero section can be found under the Colors tab.", - "size": "thin" - }, - { - "name": "separator", - "type": "separator", - "label": "Tags Page", - "note": "The settings below apply to the Tags page, which displays a comprehensive list of all published tags. For options specific to a single tag page, refer to the Tag options tab." , + "label": "Post page", "group": "Layout", "size": "big" }, { - "name": "tagsTagCount", - "group": "Layout", - "label": "Show tag count", - "value": true, - "type": "checkbox" - }, - { - "name": "tagsPostCount", - "group": "Layout", - "label": "Show post count", - "value": true, - "type": "checkbox" - }, - { - "name": "tagsDescription", + "name": "relatedPostsNumber", + "label": "Related post", "group": "Layout", - "label": "Show tags description", - "value": true, - "type": "checkbox" + "note": "Specify number of related posts to show. Zero '0' means no posts.", + "value": "3", + "type": "number" }, + { "name": "separator", "type": "separator", "label": "", "group": "Post list", - "note": "The Post list section offers a range of options for managing the post list displayed on the front page, tag and author pages. In this area, you can easily enable or disable elements such as avatar, date or other elements.", + "note": "This section lets you choose what shows up in your feed on the homepage, tags, and author pages. You can pick and choose which details to show, like author avatars, post dates, and other things.", "size": "" - }, - { - "name": "separator", - "type": "separator", - "label": "Front page", - "group": "Post list", - "note": "The following options apply to the posts displayed on the front page.", - "size": "small" - }, - { - "name": "frontFeaturedImage", - "group": "Post list", - "label": "Show featured image", - "value": false, - "type": "checkbox" - }, - { - "name": "frontAuthor", - "group": "Post list", - "label": "Show author name", - "value": true, - "type": "checkbox" - }, - { - "name": "frontAvatar", - "group": "Post list", - "label": "Show author avatar", - "value": true, - "type": "checkbox" - }, - { - "name": "frontDate", - "group": "Post list", - "label": "Show date", - "value": true, - "type": "checkbox" - }, + }, + { - "name": "frontReadMore", + "name": "alignFeed", + "label": "Content alignment", "group": "Post list", - "label": "Show 'Read more' button", - "value": true, - "type": "checkbox" - }, - + "value": "center", + "type": "radio", + "options": [ + { + "label": "Left", + "value": "left" + }, + { + "label": "Center", + "value": "center" + } + ] + }, { "name": "separator", "type": "separator", - "label": "Tag Page", + "label": "", "group": "Post list", - "note": "The following options apply to the posts displayed on a single tag page.", - "size": "big" + "size": "small thin " }, { - "name": "tagFeaturedImage", + "name": "feedFeaturedImage", "group": "Post list", - "label": "Show featured image", + "label": "Show Featured Image", "value": false, "type": "checkbox" }, { - "name": "tagAuthor", + "name": "feedFeaturedImageSize", + "label": "Image size", "group": "Post list", - "label": "Show author name", - "value": true, - "type": "checkbox" + "note": "Adjust the size of the featured image in REM value.", + "value": "8", + "type": "range", + "min": 1, + "max": 50, + "step": 1, + "dependencies": [ + { + "field": "feedFeaturedImage", + "value": true + } + ] }, { - "name": "tagAvatar", + "name": "feedAvatar", "group": "Post list", "label": "Show author avatar", "value": true, "type": "checkbox" }, { - "name": "tagDate", + "name": "feedAuthor", "group": "Post list", - "label": "Show date", + "label": "Show author name", "value": true, "type": "checkbox" }, { - "name": "tagReadMore", + "name": "feedDate", "group": "Post list", - "label": "Show 'Read more' button", + "label": "Show date", "value": true, "type": "checkbox" }, { - "name": "separator", - "type": "separator", - "label": "Author page", - "group": "Post list", - "note": "The following options apply to the posts displayed on an author page.", - "size": "big" - }, - { - "name": "authorFeaturedImage", - "group": "Post list", - "label": "Show featured image", - "value": false, - "type": "checkbox" - }, - { - "name": "authorAuthor", - "group": "Post list", - "label": "Show author name", - "value": false, - "type": "checkbox" - }, - { - "name": "authorAvatar", + "name": "feedDateType", "group": "Post list", - "label": "Show author avatar", - "value": false, - "type": "checkbox" - }, - { - "name": "authorDate", - "group": "Post list", - "label": "Show date", - "value": true, - "type": "checkbox" + "label": "", + "value": "published", + "type": "radio", + "options": [ + { + "label": "Published date", + "value": "published" + }, + { + "label": "Modified date", + "value": "modified" + } + ], + "dependencies": [ + { + "field": "feedDate", + "value": true + } + ] }, { - "name": "authorReadMore", + "name": "feedtReadMore", "group": "Post list", "label": "Show 'Read more' button", "value": true, "type": "checkbox" - }, - { - "name": "separator", - "type": "separator", - "label": "Post page", - "group": "Layout", - "size": "big" - }, - { - "name": "relatedPostsNumber", - "label": "Related post", - "group": "Layout", - "note": "Specify number of related posts to show. Zero '0' means no posts.", - "value": "3", - "type": "text" - }, - + }, + + { "name": "navbarHeight", "group": "Navbar", "label": "Navbar height", "value": "6rem", "type": "text" - }, + }, { "name": "separator", "type": "separator", @@ -429,12 +390,10 @@ { "label": "Auto", "value": "auto" - }, { "label": "Custom", "value": "custom" - } ] }, @@ -448,11 +407,13 @@ "min": 0, "max": 1000, "step": 10, - "dependencies": [{ - "field": "submenu", - "value": "custom" - }] - }, + "dependencies": [ + { + "field": "submenu", + "value": "custom" + } + ] + }, { "name": "separator", "type": "separator", @@ -484,7 +445,6 @@ "value": true, "type": "checkbox" }, - { "name": "colorScheme", "label": "Select color scheme", @@ -507,182 +467,21 @@ } ] }, - { - "name": "separator", - "type": "separator", - "label": "Light", - "group": "Colors", - "size": "small" - }, { "name": "primaryColor", - "label": "Primary color", + "label": "Primary color (Light mode)", "group": "Colors", "value": "#D73A42", "type": "colorpicker" - }, - { - "name": "headingColor", - "label": "Headings color", - "group": "Colors", - "value": "#17181E", - "type": "colorpicker" - }, - { - "name": "textColor", - "label": "Text color", - "group": "Colors", - "value": "#17181E", - "type": "colorpicker" - }, - { - "name": "separator", - "type": "separator", - "label": "Dark", - "group": "Colors", - "size": "small" }, { "name": "primaryDarkColor", - "label": "Primary color", + "label": "Primary color (Dark mode)", "group": "Colors", "value": "#FFC074", "type": "colorpicker" - }, - { - "name": "headingDarkColor", - "label": "Headings color", - "group": "Colors", - "value": "#EEEDED", - "type": "colorpicker" - }, - { - "name": "textDarkColor", - "label": "Text color", - "group": "Colors", - "value": "#BFBFBF", - "type": "colorpicker" - }, - { - "anchor": "hero", - "name": "separator", - "type": "separator", - "label": "Hero Section", - "group": "Colors", - "size": "big" - }, - { - "name": "heroBackground", - "label": "Background", - "group": "Colors", - "value": "#17181E", - "type": "colorpicker" - }, - { - "name": "heroOverlay", - "label": "Overlay", - "group": "Colors", - "note": "The overlay works with the image and only when it has been fully loaded.", - "value": "gradient", - "type": "select", - "options": [ - { - "label": "Solid color", - "value": "color" - }, - { - "label": "Gradient", - "value": "gradient" - } - ] - }, - { - "name": "heroOverlayColor", - "label": "", - "group": "Colors", - "value": "rgba(0, 0, 0, 0.64)", - "type": "colorpicker", - "dependencies": [{ - "field": "heroOverlay", - "value": "color" - }] - }, - { - "name": "heroOverlayGradient", - "label": "", - "group": "Colors", - "value": "rgba(0, 0, 0, 0.64)", - "type": "colorpicker", - "dependencies": [{ - "field": "heroOverlay", - "value": "gradient" - }] - }, - { - "name": "heroOverlayGradientDirection", - "label": "", - "group": "Colors", - "note": "Gradient direction", - "value": "bottom", - "type": "select", - "options": [ - { - "label": "Top", - "value": "Top" - }, - { - "label": "Right", - "value": "right" - }, - { - "label": "Bottom", - "value": "bottom" - }, - { - "label": "Left", - "value": "left" - } - ], - "dependencies": [{ - "field": "heroOverlay", - "value": "gradient" - }] - }, - { - "name": "heroHeadingColor", - "label": "Heading (H1) color", - "group": "Colors", - "value": "#FFFFFF", - "type": "colorpicker" - }, - { - "name": "heroTextColor", - "label": "Text color", - "group": "Colors", - "value": "rgba(255,255,255,0.75)", - "type": "colorpicker" - }, - { - "name": "heroLink", - "label": "Link color", - "group": "Colors", - "value": "#FFFFFF", - "type": "colorpicker" - }, - { - "name": "heroLinkHover", - "label": "Link hover color", - "group": "Colors", - "value": "rgba(255,255,255,0.75)", - "type": "colorpicker" - }, - { - "name": "heroBorderColor", - "label": "Border color", - "group": "Colors", - "value": "rgba(255,255,255,0.3)", - "type": "colorpicker" }, + { "name": "separator", "type": "separator", @@ -690,7 +489,7 @@ "group": "Fonts", "note": "To explore an extensive list of available fonts, along with detailed information about their typefaces, complete range of weights, and other specifications, please visit our documentation.", "size": "small" - }, + }, { "name": "fontBody", "label": "Body font", @@ -853,7 +652,7 @@ "value": "heebo", "group": "Sans Serif" }, - { + { "label": "Instrument Sans", "value": "instrumentsans", "group": "Sans Serif" @@ -1019,7 +818,21 @@ "group": "Monospace" } ] - }, + }, + { + "name": "disableFontBodyItalic", + "label": "Disable italic style", + "group": "Fonts", + "note": "This option allows you to disable the loading of the dedicated italic version for the body font. The italic font is automatically loaded when selecting a body font that supports it, like Lora, for optimal appearance. However, for performance reasons, you can choose to prevent loading the dedicated italic version by enabling this option.", + "type": "checkbox", + "value": false, + "dependencies": [ + { + "field": "fontBody", + "value": "adventpro,aleo,andadapro,archivonarrow,asap,besley,bitter,brygada1918,cabin,exo,faustina,figtree,glory,instrumentsans,jetbrainsmono,karla,labrada,librefranklin,lora,manuale,merriweathersans,montserrat,nunito,petrona,playfairdisplay,plusjakartasans,publicsans,raleway,redhatdisplay,redhatmono,rokkitt,rubik,sourcecodepro,urbanist,worksans,yrsa" + } + ] + }, { "name": "fontHeadings", "label": "Headings font (H1-H6)", @@ -1182,7 +995,7 @@ "value": "heebo", "group": "Sans Serif" }, - { + { "label": "Instrument Sans", "value": "instrumentsans", "group": "Sans Serif" @@ -1348,7 +1161,21 @@ "group": "Monospace" } ] - }, + }, + { + "name": "disableFontHeadingsItalic", + "label": "Disable italic style", + "group": "Fonts", + "note": "This option allows you to disable loading of the dedicated italic version for the headings font. The italic font is automatically loaded when selecting a headings font that supports it, like Lora, for optimal appearance. However, for performance reasons, you can choose to prevent loading of the dedicated italic version by enabling this option.", + "type": "checkbox", + "value": false, + "dependencies": [ + { + "field": "fontHeadings", + "value": "adventpro,aleo,andadapro,archivonarrow,asap,besley,bitter,brygada1918,cabin,exo,faustina,figtree,glory,instrumentsans,jetbrainsmono,karla,labrada,librefranklin,lora,manuale,merriweathersans,montserrat,nunito,petrona,playfairdisplay,plusjakartasans,publicsans,raleway,redhatdisplay,redhatmono,rokkitt,rubik,sourcecodepro,urbanist,worksans,yrsa" + } + ] + }, { "name": "fontMenu", "label": "Menu font", @@ -1369,7 +1196,7 @@ "value": "var(--heading-font)" } ] - }, + }, { "name": "fontLogo", "label": "Logo font", @@ -1390,8 +1217,7 @@ "value": "var(--heading-font)" } ] - }, - + }, { "name": "separator", "type": "separator", @@ -1420,18 +1246,29 @@ "min": 1, "max": 3, "step": 0.05 - }, + }, { "name": "lineHeight", "label": "Line height", "group": "Fonts", - "note": "The default line-height for the entire website", + "note": "The default line-height for text within body elements, excluding headings.", "value": "1.7", "type": "range", "min": 1, "max": 3, - "step": 0.1 - }, + "step": 0.05 + }, + { + "name": "letterSpacing", + "label": "Letter spacing", + "group": "Fonts", + "note": "Adjusts the spacing between characters for body text elements, excluding headings, in EM.", + "value": "0", + "type": "range", + "min": -1, + "max": 1, + "step": 0.01 + }, { "name": "separator", "type": "separator", @@ -1443,7 +1280,7 @@ { "name": "fontBodyWeight", "label": "Normal font weight", - "group": "Fonts", + "group": "Fonts", "value": "400", "type": "range", "min": 100, @@ -1453,8 +1290,8 @@ { "name": "fontBoldWeight", "label": "Bold font weight", - "group": "Fonts", - "value": "700", + "group": "Fonts", + "value": "600", "type": "range", "min": 100, "max": 900, @@ -1466,21 +1303,64 @@ "label": "Headings", "group": "Fonts", "size": "big" - }, + }, { "name": "fontHeadignsWeight", "label": "H1-H6 font weight", - "group": "Fonts", - "value": "700", + "group": "Fonts", + "value": "500", "type": "range", "min": 100, "max": 900, "step": 1 }, + { + "name": "fontHeadingsStyle", + "label": "H1-H6 font style", + "group": "Fonts", + "value": "normal", + "type": "select", + "options": [ + { + "label": "Normal", + "value": "normal" + }, + { + "label": "Italic", + "value": "italic" + }, + { + "label": "Oblique", + "value": "oblique" + } + ] + }, + { + "name": "fontHeadingsLineHeight", + "label": "H1-H6 line height", + "group": "Fonts", + "note": "The default line-height for heading elements (h1, h2, h3, etc.).", + "value": "1.2", + "type": "range", + "min": 1, + "max": 3, + "step": 0.05 + }, + { + "name": "fontHeadingsletterSpacing", + "label": "H1-H6 letter spacing", + "group": "Fonts", + "note": "Adjusts the spacing between characters for heading elements (h1, h2, h3, etc.) in EM.", + "value": "0", + "type": "range", + "min": -1, + "max": 1, + "step": 0.01 + }, { "name": "fontHeadingsTransform", "label": "H1-H6 text transform", - "group": "Fonts", + "group": "Fonts", "value": "none", "type": "select", "options": [ @@ -1502,7 +1382,14 @@ } ] }, - + { + "name": "separator", + "type": "separator", + "label": "", + "group": "Share Buttons", + "note": "This section allows you to choose your preferred share buttons for various platforms. If you wish to add more, you can install the Social Sharing plugin available on the Publii Marketplace.", + "size": "small" + }, { "name": "shareFacebook", "group": "Share Buttons", @@ -1523,10 +1410,12 @@ "note": "Enter your X account's handle here; it will be used when the X share button is clicked on your site e.g. 'via @XHandle'. If left blank, the default username of @SiteName will be used", "value": "", "type": "text", - "dependencies": [{ - "field": "shareTwitter", - "value": "true" - }] + "dependencies": [ + { + "field": "shareTwitter", + "value": "true" + } + ] }, { "name": "sharePinterest", @@ -1563,7 +1452,15 @@ "value": false, "type": "checkbox" }, - + + { + "name": "separator", + "type": "separator", + "label": "", + "group": "Footer", + "note": "This section allows you to add follow buttons for various social media platforms. If you want to expand the available options, you can install the Follow Buttons plugin available on the Publii Marketplace.", + "size": "small" + }, { "name": "socialButtons", "group": "Footer", @@ -1578,10 +1475,12 @@ "placeholder": "Please enter your Facebook URL", "value": "", "type": "text", - "dependencies": [{ - "field": "socialButtons", - "value": "true" - }] + "dependencies": [ + { + "field": "socialButtons", + "value": "true" + } + ] }, { "name": "socialTwitter", @@ -1590,12 +1489,13 @@ "placeholder": "Please enter your X URL", "value": "", "type": "text", - "dependencies": [{ - "field": "socialButtons", - "value": "true" - }] + "dependencies": [ + { + "field": "socialButtons", + "value": "true" + } + ] }, - { "name": "socialInstagram", "label": "Instagram", @@ -1603,10 +1503,12 @@ "placeholder": "Please enter your Instagram URL", "value": "", "type": "text", - "dependencies": [{ - "field": "socialButtons", - "value": "true" - }] + "dependencies": [ + { + "field": "socialButtons", + "value": "true" + } + ] }, { "name": "socialLinkedin", @@ -1615,10 +1517,12 @@ "placeholder": "Please enter your LinkedIn URL", "value": "", "type": "text", - "dependencies": [{ - "field": "socialButtons", - "value": "true" - }] + "dependencies": [ + { + "field": "socialButtons", + "value": "true" + } + ] }, { "name": "socialVimeo", @@ -1627,10 +1531,12 @@ "placeholder": "Please enter your Vimeo URL", "value": "", "type": "text", - "dependencies": [{ - "field": "socialButtons", - "value": "true" - }] + "dependencies": [ + { + "field": "socialButtons", + "value": "true" + } + ] }, { "name": "socialPinterest", @@ -1639,10 +1545,12 @@ "placeholder": "Please enter your Pinterest URL", "value": "", "type": "text", - "dependencies": [{ - "field": "socialButtons", - "value": "true" - }] + "dependencies": [ + { + "field": "socialButtons", + "value": "true" + } + ] }, { "name": "socialYoutube", @@ -1651,10 +1559,12 @@ "placeholder": "Please enter your Youtube URL", "value": "", "type": "text", - "dependencies": [{ - "field": "socialButtons", - "value": "true" - }] + "dependencies": [ + { + "field": "socialButtons", + "value": "true" + } + ] }, { "name": "separator", @@ -1670,7 +1580,6 @@ "value": "Powered by Publii", "type": "wysiwyg" }, - { "name": "searchFeature", "label": "Search", @@ -1687,23 +1596,21 @@ "value": false, "type": "checkbox" }, - { "name": "galleryItemGap", "label": "Item spacing", "group": "Gallery", - "note": "Use of REM units recommended but you can also use others (px, vw,)", - "value": "0.28333rem", + "note": "You can use the --baseline variable and multiply it, but you can also use other values like px or rem.", + "value": "calc(var(--baseline) * 1.5)", "type": "text" }, - { "name": "separator", "type": "separator", "label": "", "group": "Gallery", "size": "small thin" - }, + }, { "name": "galleryLightboxStyle", "label": "Lightbox style", @@ -1739,7 +1646,7 @@ "label": "", "group": "Gallery", "size": "small thin" - }, + }, { "name": "galleryAdvanced", "label": "Advanced lightbox settings", @@ -1753,10 +1660,12 @@ "group": "Gallery", "value": true, "type": "checkbox", - "dependencies": [{ - "field": "galleryAdvanced", - "value": "true" - }] + "dependencies": [ + { + "field": "galleryAdvanced", + "value": "true" + } + ] }, { "name": "galleryZoom", @@ -1764,10 +1673,12 @@ "group": "Gallery", "value": true, "type": "checkbox", - "dependencies": [{ - "field": "galleryAdvanced", - "value": "true" - }] + "dependencies": [ + { + "field": "galleryAdvanced", + "value": "true" + } + ] }, { "name": "galleryFullscreen", @@ -1775,10 +1686,12 @@ "group": "Gallery", "value": true, "type": "checkbox", - "dependencies": [{ - "field": "galleryAdvanced", - "value": "true" - }] + "dependencies": [ + { + "field": "galleryAdvanced", + "value": "true" + } + ] }, { "name": "galleryShare", @@ -1786,10 +1699,12 @@ "group": "Gallery", "value": true, "type": "checkbox", - "dependencies": [{ - "field": "galleryAdvanced", - "value": "true" - }] + "dependencies": [ + { + "field": "galleryAdvanced", + "value": "true" + } + ] }, { "name": "galleryClose", @@ -1797,10 +1712,12 @@ "group": "Gallery", "value": true, "type": "checkbox", - "dependencies": [{ - "field": "galleryAdvanced", - "value": "true" - }] + "dependencies": [ + { + "field": "galleryAdvanced", + "value": "true" + } + ] }, { "name": "galleryArrows", @@ -1808,10 +1725,12 @@ "group": "Gallery", "value": true, "type": "checkbox", - "dependencies": [{ - "field": "galleryAdvanced", - "value": "true" - }] + "dependencies": [ + { + "field": "galleryAdvanced", + "value": "true" + } + ] }, { "name": "galleryPreloader", @@ -1820,10 +1739,12 @@ "note": "Loading indicator is displayed with 1s delay, so if the photo loads faster it will be invisible.", "value": true, "type": "checkbox", - "dependencies": [{ - "field": "galleryAdvanced", - "value": "true" - }] + "dependencies": [ + { + "field": "galleryAdvanced", + "value": "true" + } + ] }, { "name": "galleryCloseOnScroll", @@ -1832,10 +1753,12 @@ "note": "Option works just for devices without hardware touch support.", "value": true, "type": "checkbox", - "dependencies": [{ - "field": "galleryAdvanced", - "value": "true" - }] + "dependencies": [ + { + "field": "galleryAdvanced", + "value": "true" + } + ] }, { "name": "galleryArrowKeys", @@ -1844,10 +1767,12 @@ "note": "Keyboard left or right arrow key navigation.", "value": true, "type": "checkbox", - "dependencies": [{ - "field": "galleryAdvanced", - "value": "true" - }] + "dependencies": [ + { + "field": "galleryAdvanced", + "value": "true" + } + ] }, { "name": "backToTopButton", @@ -1855,7 +1780,7 @@ "label": "\"Back to top\" button", "value": true, "type": "checkbox" - }, + }, { "name": "separator", "type": "separator", @@ -1863,7 +1788,6 @@ "group": "Additional", "size": "small" }, - { "name": "formatDate", "label": "Date format", @@ -1874,54 +1798,49 @@ { "label": "Nov 1, 2016", "value": "MMM D, YYYY" - - }, + }, { "label": "1 Nov 2016", "value": "D MMM YYYY" - - }, + }, { "label": "November 1, 2016", "value": "MMMM D, YYYY" - }, + }, { "label": "1 November 2016", "value": "D MMMM YYYY" - }, + }, { "label": "Sunday, November 1, 2016", "value": "dddd, MMMM D, YYYY" - }, + }, { "label": "Sunday, 1 November 2016", "value": "dddd, D MMMM YYYY" - }, + }, { "label": "November 1, 2016 10:02:05", "value": "MMMM D, YYYY HH:mm:ss" - - }, + }, { "label": "1 November 2016 10:02:05", "value": "D MMMM YYYY HH:mm:ss" - - }, + }, { "label": "01/21/2016", "value": "MM/DD/YYYY" - }, + }, { "label": "21/01/2016", "value": "DD/MM/YYYY" - }, + }, { "label": "Custom - create your own date format", "value": "custom" - } + } ] }, - { "name": "formatDateCustom", "group": "Additional", @@ -1929,10 +1848,12 @@ "note": "More details you can find here.", "value": "HH:mm:ss YY/MM/DD", "type": "text", - "dependencies": [{ - "field": "formatDate", - "value": "custom" - }] + "dependencies": [ + { + "field": "formatDate", + "value": "custom" + } + ] }, { "name": "separator", @@ -1940,7 +1861,7 @@ "label": "", "group": "Additional", "size": "small" - }, + }, { "name": "lazyLoadEffect", "label": "Lazy load effects", @@ -1955,8 +1876,9 @@ }, { "label": "None", - "value": "none" - }] + "value": "none" + } + ] }, { "name": "separator", @@ -1964,7 +1886,7 @@ "label": "", "group": "Additional", "size": "small" - }, + }, { "name": "faviconUpload", "label": "Upload favicon file", @@ -1988,11 +1910,12 @@ { "label": ".png", "value": "image/png" - }] + } + ] } ], "postConfig": [ - { + { "name": "displayDate", "label": "Display date", "value": 1, @@ -2023,7 +1946,7 @@ "value": 0 } ] - }, + }, { "name": "displayLastUpdatedDate", "label": "Display last updated date", @@ -2072,7 +1995,6 @@ } ] }, - { "name": "displayAuthorBio", "label": "Display author bio", @@ -2105,7 +2027,6 @@ } ] }, - { "name": "displayRelatedPosts", "label": "Display related posts", @@ -2139,6 +2060,126 @@ ] } ], + "pageConfig": [ + { + "name": "displayDate", + "label": "Display date", + "value": 0, + "type": "select", + "pageTemplates": "!empty", + "options": [ + { + "label": "Enabled", + "value": 1 + }, + { + "label": "Disabled", + "value": 0 + } + ] + }, + { + "name": "displayAuthor", + "label": "Display author", + "value": 0, + "type": "select", + "pageTemplates": "!empty", + "options": [ + { + "label": "Enabled", + "value": 1 + }, + { + "label": "Disabled", + "value": 0 + } + ] + }, + { + "name": "displayLastUpdatedDate", + "label": "Display last updated date", + "value": 0, + "type": "select", + "pageTemplates": "!empty", + "options": [ + { + "label": "Enabled", + "value": 1 + }, + { + "label": "Disabled", + "value": 0 + } + ] + }, + { + "name": "displayShareButtons", + "label": "Display share buttons", + "value": 0, + "type": "select", + "pageTemplates": "!empty", + "options": [ + { + "label": "Enabled", + "value": 1 + }, + { + "label": "Disabled", + "value": 0 + } + ] + }, + { + "name": "displayAuthorBio", + "label": "Display author bio", + "value": 0, + "type": "select", + "pageTemplates": "!empty", + "options": [ + { + "label": "Enabled", + "value": 1 + }, + { + "label": "Disabled", + "value": 0 + } + ] + }, + { + "name": "displayChildPages", + "label": "Display child pages", + "value": 0, + "type": "select", + "options": [ + { + "label": "Enabled", + "value": 1 + }, + { + "label": "Disabled", + "value": 0 + } + ] + }, + { + "name": "displayComments", + "label": "Display comments", + "value": 0, + "type": "select", + "pageTemplates": "!empty", + "options": [ + { + "label": "Enabled", + "value": 1 + }, + { + "label": "Disabled", + "value": 0 + } + ] + } + ], "authorConfig": [ { "name": "displayFeaturedImage", @@ -2306,175 +2347,181 @@ } ], "files": { - "ignoreAssets": ["scss", ".DS_Store"], + "ignoreAssets": [ + "scss", + ".DS_Store" + ], "assetsPath": "assets", "useDynamicAssets": true, "partialsPath": "partials", "responsiveImages": { "contentImages": { - "sizes": "100vw", + "sizes": "(max-width: 1920px) 100vw, 1920px", "dimensions": { - "xs": { - "width": 300, + "xs": { + "width": 640, "height": "auto" }, "sm": { - "width": 480, + "width": 768, "height": "auto" }, "md": { - "width": 768, + "width": 1024, "height": "auto" }, "lg": { - "width": 1024, + "width": 1366, "height": "auto" }, "xl": { - "width": 1360, + "width": 1600, "height": "auto" }, "2xl": { - "width": 1600, + "width": 1920, "height": "auto" } } }, - "featuredImages": { - "sizes": "(max-width: 1600px) 100vw, 1600px", + "sizes": { + "hero": "88vw", + "feed": "(min-width: 600px) calc(4.38vw + 143px), 87.86vw" + }, "dimensions": { "xs": { - "width": 300, - "height": "auto" + "width": 640, + "height": "auto", + "group": "hero,feed" }, "sm": { - "width": 480, - "height": "auto" + "width": 768, + "height": "auto", + "group": "hero,feed" }, "md": { - "width": 768, - "height": "auto" + "width": 1024, + "height": "auto", + "group": "hero,feed" }, "lg": { - "width": 1024, - "height": "auto" + "width": 1366, + "height": "auto", + "group": "hero" }, "xl": { - "width": 1360, - "height": "auto" + "width": 1600, + "height": "auto", + "group": "hero" }, "2xl": { - "width": 1600, - "height": "auto" + "width": 1920, + "height": "auto", + "group": "hero" } } }, - "tagImages": { - "sizes": "100vw", + "sizes": "88vw", "dimensions": { "xs": { - "width": 300, + "width": 640, "height": "auto" }, "sm": { - "width": 480, + "width": 768, "height": "auto" }, "md": { - "width": 768, + "width": 1024, "height": "auto" }, "lg": { - "width": 1024, + "width": 1366, "height": "auto" }, "xl": { - "width": 1360, + "width": 1600, "height": "auto" }, "2xl": { - "width": 1600, + "width": 1920, "height": "auto" } } }, - "authorImages": { - "sizes": "100vw", + "sizes": "88vw", "dimensions": { "xs": { - "width": 300, + "width": 640, "height": "auto" }, "sm": { - "width": 480, + "width": 768, "height": "auto" }, "md": { - "width": 768, + "width": 1024, "height": "auto" }, "lg": { - "width": 1024, + "width": 1366, "height": "auto" }, "xl": { - "width": 1360, + "width": 1600, "height": "auto" }, "2xl": { - "width": 1600, + "width": 1920, "height": "auto" } } }, - "optionImages": { - "sizes": "100vw", + "sizes": "88vw", "dimensions": { "xs": { - "width": 300, - "height": 255, - "crop": true + "width": 640, + "height": "auto" }, "sm": { - "width": 480, + "width": 768, "height": "auto" }, "md": { - "width": 768, + "width": 1024, "height": "auto" }, "lg": { - "width": 1024, + "width": 1366, "height": "auto" }, "xl": { - "width": 1360, + "width": 1600, "height": "auto" }, "2xl": { - "width": 1600, + "width": 1920, "height": "auto" } } }, - "galleryImages": { "sizes": "", "dimensions": { "thumbnail": { - "width": 720, - "height": "auto" + "width": 720, + "height": "auto" } } } } }, "customElements": [ - { + { "label": "Ordered list", "cssClasses": "ordered-list", "selector": "ol" @@ -2483,7 +2530,7 @@ "label": "Drop cap", "cssClasses": "dropcap", "selector": "p" - }, + }, { "label": "Info", "cssClasses": "msg msg--info", @@ -2520,4 +2567,4 @@ "selector": "table" } ] -} +} \ No newline at end of file diff --git a/app/default-files/default-themes/simple/dynamic-assets-mapping.js b/app/default-files/default-themes/simple/dynamic-assets-mapping.js index dcd8cd2a8..771023e49 100755 --- a/app/default-files/default-themes/simple/dynamic-assets-mapping.js +++ b/app/default-files/default-themes/simple/dynamic-assets-mapping.js @@ -1,13 +1,93 @@ -/* - * Generate dynamic assets - */ - module.exports = function (themeConfig) { - let fontBody = themeConfig.customConfig['fontBody']; - let fontHeadings = themeConfig.customConfig['fontHeadings']; + let fontBody = themeConfig.customConfig['fontBody']; + let fontHeadings = themeConfig.customConfig['fontHeadings']; + let fontBodyItalic = themeConfig.customConfig['fontBodyItalic']; + let fontHeadingsItalic = themeConfig.customConfig['fontHeadingsItalic']; + + let assets = new Set(); + + const fontParams = { + 'adventpro': { hasItalic: true }, + 'aleo': { hasItalic: true }, + 'andadapro': { hasItalic: true }, + 'antonio': { hasItalic: false }, + 'archivonarrow': { hasItalic: true }, + 'asap': { hasItalic: true }, + 'assistant': { hasItalic: false }, + 'besley': { hasItalic: true }, + 'bitter': { hasItalic: true }, + 'brygada1918': { hasItalic: true }, + 'cabin': { hasItalic: true }, + 'cairo': { hasItalic: false }, + 'comfortaa': { hasItalic: false }, + 'dancingscript': { hasItalic: false }, + 'dosis': { hasItalic: false }, + 'domine': { hasItalic: false }, + 'exo': { hasItalic: true }, + 'faustina': { hasItalic: true }, + 'figtree': { hasItalic: true }, + 'frankruhllibre': { hasItalic: false }, + 'glory': { hasItalic: true }, + 'gluten': { hasItalic: false }, + 'heebo': { hasItalic: false }, + 'imbue': { hasItalic: false }, + 'instrumentsans': { hasItalic: true }, + 'jetbrainsmono': { hasItalic: true }, + 'jura': { hasItalic: false }, + 'karla': { hasItalic: true }, + 'kreon': { hasItalic: false }, + 'labrada': { hasItalic: true }, + 'lemonada': { hasItalic: false }, + 'lexend': { hasItalic: false }, + 'librefranklin': { hasItalic: true }, + 'lora': { hasItalic: true }, + 'manuale': { hasItalic: true }, + 'manrope': { hasItalic: false }, + 'mavenpro': { hasItalic: false }, + 'merriweathersans': { hasItalic: true }, + 'montserrat': { hasItalic: true }, + 'nunito': { hasItalic: true }, + 'orbitron': { hasItalic: false }, + 'oswald': { hasItalic: false }, + 'petrona': { hasItalic: true }, + 'playfairdisplay': { hasItalic: true }, + 'plusjakartasans': { hasItalic: true }, + 'publicsans': { hasItalic: true }, + 'quicksand': { hasItalic: false }, + 'raleway': { hasItalic: true }, + 'redhatdisplay': { hasItalic: true }, + 'redhatmono': { hasItalic: true }, + 'robotoflex': { hasItalic: false }, + 'robotoslab': { hasItalic: false }, + 'rokkitt': { hasItalic: true }, + 'rubik': { hasItalic: true }, + 'ruda': { hasItalic: false }, + 'smoochsans': { hasItalic: false }, + 'sourcecodepro': { hasItalic: true }, + 'spartan': { hasItalic: false }, + 'system-ui': { hasItalic: false }, + 'urbanist': { hasItalic: true }, + 'worksans': { hasItalic: true }, + 'yanonekaffeesatz': { hasItalic: false }, + 'yrsa': { hasItalic: true } + }; + + const addFontAsset = (font, hasItalic) => { + assets.add('/fonts/' + font + '/' + font + '.woff2'); + if (hasItalic && fontParams[font]?.hasItalic) { + assets.add('/fonts/' + font + '/' + font + '-italic.woff2'); + } + }; + + if (fontBody !== 'system-ui') { + addFontAsset(fontBody, fontBodyItalic); + } + + if (fontHeadings !== 'system-ui' && fontHeadings !== fontBody) { + addFontAsset(fontHeadings, fontHeadingsItalic); + } else if (fontHeadingsItalic && !fontBodyItalic && fontParams[fontHeadings]?.hasItalic) { + addFontAsset(fontHeadings, fontHeadingsItalic); + } - return [ - '/fonts/' + fontBody + '/' + fontBody + '.woff2', - '/fonts/' + fontHeadings + '/' + fontHeadings + '.woff2' - ]; + return Array.from(assets); }; diff --git a/app/default-files/default-themes/simple/index.hbs b/app/default-files/default-themes/simple/index.hbs index 0dfbc9bd4..838af4d0c 100755 --- a/app/default-files/default-themes/simple/index.hbs +++ b/app/default-files/default-themes/simple/index.hbs @@ -1,118 +1,114 @@ {{> head}} -{{> top}} +{{> navbar}} {{#checkIf @config.custom.frontSource '==' "post" }} {{> frontpage-post}} {{else}}
-
- {{#if @config.custom.uploadHero}} -
- {{@config.custom.uploadHeroAlt}} - - {{#if @config.custom.uploadHeroCaption}} -
- {{@config.custom.uploadHeroCaption}} -
- {{/if}} -
- {{/if}} +
{{#if @config.custom.textHero}} -
+
{{{@config.custom.textHero}}} - {{#if @config.custom.siteOwner}} - {{#siteOwner}} - - {{/siteOwner}} - {{/if}}
{{/if}} -
-
- {{@config.custom.fontName}} - {{#each posts}} -
-
- {{#checkIfAny @config.custom.frontAvatar @config.custom.frontAuthor @config.custom.frontDate}} -
- {{#author}} - {{#if @config.custom.frontAvatar}} - {{#if avatar}} - - {{/if}} - {{/if}} - {{#if @config.custom.frontAuthor}} - {{name}} - {{/if}} - {{/author}} - {{#if @config.custom.frontDate}} - + {{#if @renderer.isFirstPage}} + {{#if @config.custom.uploadHero}} +
+
+ {{@config.custom.uploadHeroAlt}}
- {{/checkIfAny}} -

- - {{title}} - -

-
- {{#if @config.custom.frontFeaturedImage}} + + {{#if @config.custom.uploadHeroCaption}} +
+ {{@config.custom.uploadHeroCaption}} +
+ {{/if}} +
+ {{/if}} + {{/if}} + +
+ {{#each posts}} +
+ {{#if @config.custom.feedFeaturedImage}} {{#featuredImage}} {{#if url}} -
- - {{alt}} - -
+
+ {{alt}} +
{{/if}} {{/featuredImage}} {{/if}} -
+
+
+ {{#checkIfAny @config.custom.feedAvatar @config.custom.feedAuthor @config.custom.feedDate}} +
+ {{#author}} + {{#if @config.custom.feedAvatar}} + {{#if avatar}} + {{avatarImage.alt}} + {{/if}} + {{/if}} + {{#if @config.custom.feedAuthor}} + {{name}} + {{/if}} + {{/author}} + {{#if @config.custom.feedDate}} + {{#checkIf @config.custom.feedDateType '==' "published" }} + + {{/checkIf}} + {{#checkIf @config.custom.feedDateType '==' "modified" }} + + {{/checkIf}} + {{/if}} +
+ {{/checkIfAny}} +

+ + {{title}} + +

+
{{#if hasCustomExcerpt}} {{{ excerpt }}} {{else}}

{{{ excerpt }}}

{{/if}} - {{#if @config.custom.frontReadMore}} + {{#if @config.custom.feedtReadMore}} {{ translate 'post.readMore' }} {{/if}} diff --git a/app/default-files/default-themes/simple/page-empty.hbs b/app/default-files/default-themes/simple/page-empty.hbs new file mode 100755 index 000000000..ee05cca27 --- /dev/null +++ b/app/default-files/default-themes/simple/page-empty.hbs @@ -0,0 +1,65 @@ +{{> head}} +{{> navbar}} +
+ {{#page}} +
+
+
+
+

{{title}}

+
+
+ + {{#featuredImage}} + {{#if url}} +
+ {{alt}} + + {{#checkIfAny caption credits}} +
+ {{caption}} + {{credits}} +
+ {{/checkIfAny}} +
+ {{/if}} + {{/featuredImage}} +
+ +
+ {{{text}}} +
+ + {{#if @config.page.displayChildPages}} + {{#if subpages}} +
+
+

{{ translate 'page.childPages' }}

+
    + {{> subpages-list}} +
+
+
+ {{/if}} + {{/if}} +
+ + {{#if @customHTML.afterPage}} + + {{/if}} + + {{/page}} +
+{{> footer}} diff --git a/app/default-files/default-themes/simple/page.hbs b/app/default-files/default-themes/simple/page.hbs new file mode 100755 index 000000000..94cde26f6 --- /dev/null +++ b/app/default-files/default-themes/simple/page.hbs @@ -0,0 +1,173 @@ +{{> head}} +{{> navbar}} +
+ {{#page}} +
+
+
+
+

{{title}}

+ {{#checkIfAny @config.page.displayAuthor @config.page.displayDate}} + + {{/checkIfAny}} +
+
+ + {{#featuredImage}} + {{#if url}} +
+
+ {{alt}} +
+ + {{#checkIfAny caption credits}} +
+ {{caption}} + {{credits}} +
+ {{/checkIfAny}} +
+ {{/if}} + {{/featuredImage}} +
+ +
+ {{{text}}} +
+ + {{#checkIfAny @config.page.displayLastUpdatedDate @config.page.displayShareButtons @config.page.displayAuthorBio}} +
+
+ + {{#checkIfAny @config.page.displayLastUpdatedDate @config.page.displayShareButtons}} +
+ {{#if @config.page.displayLastUpdatedDate}} + {{#if modifiedAt}} +

+ {{ translate 'post.lastUpdatedDate' }} + {{#checkIf @config.custom.formatDate '!=' 'custom'}} + {{date modifiedAt @config.custom.formatDate}} + {{else}} + {{date modifiedAt @config.custom.formatDateCustom}} + {{/checkIf}} +

+ {{/if}} + {{/if}} + + {{#if @config.page.displayShareButtons}} + + {{/if}} +
+ {{/checkIfAny}} + + {{#if @config.page.displayAuthorBio}} +
+ {{#author}} + {{#if avatar}} + {{avatarImage.alt}} + {{/if}} +
+

+ +

+ {{#if description}} +
+ {{{description}}} +
+ {{/if}} +
+ {{/author}} +
+ {{/if}} +
+
+ {{/checkIfAny}} +
+ + {{#if @config.page.displayChildPages}} + {{#if subpages}} +
+
+

{{ translate 'page.childPages' }}

+
    + {{> subpages-list}} +
+
+
+ {{/if}} + {{/if}} + + {{#if @config.page.displayComments}} +
+
+ {{{@commentsCustomCode}}} +
+
+ {{/if}} + + {{#if @customHTML.afterPage}} + + {{/if}} + + {{/page}} +
+{{> footer}} diff --git a/app/default-files/default-themes/simple/partials/fonts.hbs b/app/default-files/default-themes/simple/partials/fonts.hbs index 55a6ca94d..8aa99e2f0 100755 --- a/app/default-files/default-themes/simple/partials/fonts.hbs +++ b/app/default-files/default-themes/simple/partials/fonts.hbs @@ -1,8 +1,21 @@ {{#checkIf @config.custom.fontBody '!==' "system-ui"}} - {{#checkIf @config.custom.fontBody '!==' @config.custom.fontHeadings}} - - {{/checkIf}} + + {{#if @config.custom.fontBodyItalic}} + + {{/if}} {{/checkIf}} + {{#checkIf @config.custom.fontHeadings '!==' "system-ui"}} - -{{/checkIf}} \ No newline at end of file + {{#checkIf @config.custom.fontHeadings '!==' @config.custom.fontBody}} + + {{#if @config.custom.fontHeadingsItalic}} + + {{/if}} + {{else}} + {{#unless @config.custom.fontBodyItalic}} + {{#if @config.custom.fontHeadingsItalic}} + + {{/if}} + {{/unless}} + {{/checkIf}} +{{/checkIf}} diff --git a/app/default-files/default-themes/simple/partials/footer.hbs b/app/default-files/default-themes/simple/partials/footer.hbs index cdc97f2f1..178f48f92 100755 --- a/app/default-files/default-themes/simple/partials/footer.hbs +++ b/app/default-files/default-themes/simple/partials/footer.hbs @@ -1,72 +1,81 @@ -
- {{#if @config.custom.socialButtons}} - - {{/if}} - {{#if @config.custom.copyrightText}} - - {{/if}} - {{#if @config.custom.backToTopButton}} - - {{/if}} +
+
+ {{#if menus.footerMenu}} + + {{/if}} + + {{#if @config.custom.copyrightText}} + + {{/if}} + {{#if @config.custom.socialButtons}} + + {{/if}} + + {{#if @config.custom.backToTopButton}} + + {{/if}} +
-
@@ -86,13 +95,11 @@ } {{/if}} -{{#is "post"}} - {{#post}} - {{#if hasGallery}} - {{> photoswipe}} - {{/if}} - {{/post}} -{{/is}} + +{{#checkIfAny post.hasGallery page.hasGallery}} + {{> photoswipe}} +{{/checkIfAny}} + {{#checkIf @config.custom.frontSource '==' "post"}} {{#checkIf @config.custom.frontGallery '==' true}} {{#is "index"}} diff --git a/app/default-files/default-themes/simple/partials/frontpage-post.hbs b/app/default-files/default-themes/simple/partials/frontpage-post.hbs index dbf3a355b..df2c76614 100755 --- a/app/default-files/default-themes/simple/partials/frontpage-post.hbs +++ b/app/default-files/default-themes/simple/partials/frontpage-post.hbs @@ -1,168 +1,164 @@ -
+
{{#getPost @config.custom.frontPost}} -
-
+
+
+
+
+

{{title}}

+ {{#checkIfAny postViewConfig.displayAuthor postViewConfig.displayDate}} + + {{/checkIfAny}} +
+
+ {{#featuredImage}} {{#if url}} -
- - {{alt}} +
+
+ {{alt}} +
{{#checkIfAny caption credits}}
{{caption}} {{credits}}
- {{/checkIfAny}} - + {{/checkIfAny}}
- {{else}} - {{#checkIfAll @config.custom.uploadHero @config.custom.displayHeroImage}} -
- - {{@config.custom.uploadHeroAlt}} - - {{#if @config.custom.uploadHeroCaption}} -
- {{@config.custom.uploadHeroCaption}} -
- {{/if}} - -
- {{/checkIfAll}} {{/if}} - {{/featuredImage}} -
-
- {{#if postViewConfig.displayDate}} - + +
+ {{{text}}} +
+ + {{#checkIfAny postViewConfig.displayLastUpdatedDate postViewConfig.displayTags postViewConfig.displayShareButtons postViewConfig.displayAuthorBio}} +
+
+ {{#if postViewConfig.displayLastUpdatedDate}} + {{#if modifiedAt}} +

+ {{ translate 'post.lastUpdatedDate' }} {{#checkIf @config.custom.formatDate '!=' 'custom'}} - {{date createdAt @config.custom.formatDate}} + {{date modifiedAt @config.custom.formatDate}} {{else}} - {{date createdAt @config.custom.formatDateCustom}} + {{date modifiedAt @config.custom.formatDateCustom}} {{/checkIf}} - -

+

+ {{/if}} {{/if}} -

- {{title}} -

- {{#if postViewConfig.displayAuthor}} - -
-
- -
- {{{text}}} -
- {{#checkIfAny postViewConfig.displayLastUpdatedDate postViewConfig.displayTags postViewConfig.displayAuthorBio}} -
- - {{#if postViewConfig.displayLastUpdatedDate}} - {{#if modifiedAt}} -

- {{ translate 'post.lastUpdatedDate' }} - {{#checkIf @config.custom.formatDate '!=' 'custom'}} - {{date modifiedAt @config.custom.formatDate}} - {{else}} - {{date modifiedAt @config.custom.formatDateCustom}} - {{/checkIf}} -

- {{/if}} - {{/if}} - - {{#if postViewConfig.displayTags}} - {{#if tags}} - - {{/if}} - {{/if}} - - {{#if postViewConfig.displayShareButtons}} - {{#checkIf @plugins.socialSharing '&&' @plugins.socialSharing.state}} - {{{@customSocialSharing}}} - {{else}} - - {{/checkIf}} - {{/if}} - - {{#if postViewConfig.displayAuthorBio}} -
- {{#author}} - {{#if avatar}} - {{avatarImage.alt}} - {{/if}} -
-

- -

- {{#if description}} -
- {{{description}}} -
- {{/if}} -
- {{/author}} -
- {{/if}}
{{/checkIfAny}}
- {{#if postViewConfig.displayComments}} -
-
+ {{#if postViewConfig.displayComments}} +
+
{{{@commentsCustomCode}}}
{{/if}} {{#if @customHTML.afterPost}} -