diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..d398bf70b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.js] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.github/workflows/build-artifact-iota.yaml b/.github/workflows/build-artifact-iota.yaml index e974def7e..9484e8442 100644 --- a/.github/workflows/build-artifact-iota.yaml +++ b/.github/workflows/build-artifact-iota.yaml @@ -9,15 +9,13 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Use Node.js 14 + - name: Use Node.js 20 uses: actions/setup-node@v2 with: - node-version: '14' - - name: Install dependencies - run: cd src && yarn - - name: add webpack - run: npm link webpack - - name: Build and create zip + node-version: '20' + - name: Install dependencies && build + run: cd src && npm install && npm run build + - name: Create zip run: zip -r liebling.zip ./* -x 'node_modules/*' -x '*src*' - name: 'Upload Artifact' uses: actions/upload-artifact@v3 diff --git a/.github/workflows/build-artifact-shimmer.yaml b/.github/workflows/build-artifact-shimmer.yaml index d86e27315..85b759ed9 100644 --- a/.github/workflows/build-artifact-shimmer.yaml +++ b/.github/workflows/build-artifact-shimmer.yaml @@ -9,12 +9,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Use Node.js 14 + - name: Use Node.js 20 uses: actions/setup-node@v2 with: - node-version: '14' + node-version: '20' - name: Install dependencies && build - run: cd src && yarn && yarn build + run: cd src && npm install && npm run build - name: Create zip run: zip -r liebling.zip ./* -x 'node_modules/*' -x '*src*' - name: 'Upload Artifact' diff --git a/.gitignore b/.gitignore index b994475ab..714987967 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ node_modules .DS_Store .idea -.vscode \ No newline at end of file +.vscode + +.nvmrc \ No newline at end of file diff --git a/README.md b/README.md index 6f59e57e0..3a3bb5178 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ If you find this theme useful, please consider to make a donation to support its -## Ghost 4 +## Ghost 5 -This theme has been optimized for Ghost 4, if you are looking for a Ghost 3 compatible version please refer to [this link](https://github.com/eddiesigner/liebling/releases/tag/v.0.9.4). +This theme has been optimized for Ghost 5, if you are looking for a Ghost 4 compatible version please refer to [this link](https://github.com/eddiesigner/liebling/releases/tag/v1.5.2). ## Preview @@ -30,23 +30,34 @@ To know how to enable the search, comments, newsletter and more, please head to ### General features -* Clean and beautiful design 💅🏼 -* Lightning fast ⚡️ -* Fully responsive, looks great on any device 📱 -* Compatible with modern browsers 💻 -* Fast support 📞 +* Clean and beautiful design +* Lightning fast +* Lightweight and high performance +* Fully responsive, looks great on any device +* Compatible with modern browsers ### Ghost features -* Subscription form [more info here](https://github.com/eddiesigner/liebling/wiki/How-to-enable-subscribers) +* Subscription form * Multiple authors * Logo support * Secondary menu +* Accent color * Featured posts and pages * Post, Page, Tag, Authors, pages * Koenig editor * Bookmark card * Gallery card +* Button card +* NFT card +* Callouts +* Toggles +* Quotes +* Products +* Audio +* Video +* File uploads +* Headers * Blog title and description * Cover image for Home, Post, Page, Tag, Author pages * Author avatar, bio, location, website and social links @@ -59,6 +70,7 @@ To know how to enable the search, comments, newsletter and more, please head to * Dark mode * Search +* Custom settings * Custom Subscribe page * Custom authors page * Custom error page @@ -101,6 +113,9 @@ To know how to enable the search, comments, newsletter and more, please head to * Korean by [chocosobo](https://github.com/chocosobo) * Japanese by [ViaSnake](https://github.com/ViaSnake) * Czech by [Fjuro](https://github.com/Fjuro) +* Swedish by [momeenme](https://github.com/momeenme) +* Slovak by [jjuris](https://github.com/jjuris) +* Thai by [Parinya T.](https://github.com/pickyzz) ## Tests performed @@ -126,13 +141,19 @@ Contributions are very welcome. First, please read the [Pull Request Guidelines] ## Related +* [Glow](https://eddiesigner.gumroad.com/l/glow) - Beautiful, clean, minimalist and powerful Ghost theme perfect for writers which offers a pleasant reading experience. + +* [Bold](https://eddiesigner.gumroad.com/l/ltqtms) - Bold is a Ghost theme designed to be minimal and customizable, allowing individuals to create a sleek and modern online presence. + +* [Galerie](https://eddiesigner.gumroad.com/l/KgroF) - Modern and versatile theme that stands out for its sleek design, it's incredibly fast, it offers a great user experience and it has many options to customise it. + * [Firma](https://gum.co/ZXLha) - Ghost theme that can be used by startups to large companies to publish any kind of articles related to their business. It stands out for its clean design and is able to adapt to any company's identity. * [Weiss Pro](https://gum.co/pzvDn) - Modern and beautiful Ghost theme ready to make your content shine. ## License -Copyright (c) 2019-2020 Eduardo Gómez. Released under the [MIT license](https://github.com/eddiesigner/liebling/blob/master/LICENSE). +Copyright (c) 2019-present Eduardo Gómez. Released under the [MIT license](https://github.com/eddiesigner/liebling/blob/master/LICENSE). ## Credits diff --git a/amp.hbs b/amp.hbs new file mode 100644 index 000000000..c47e12683 --- /dev/null +++ b/amp.hbs @@ -0,0 +1,1180 @@ + + + +
+ + + + +x
"; + return div.childNodes[1]; +} + +function hasLength(input) { + return input.length > 0; +} + +function trim(str) { + return str.replace(/^\s+|\s+$/g, ""); +} + +function flatten(input) { + return [].concat.apply([], input); +} + +function isObject(input) { + return Object.prototype.toString.call(input) === "[object Object]"; +} + +function isArray(input) { + return Object.prototype.toString.call(input) === "[object Array]"; +} + + +/***/ }), + +/***/ "./node_modules/headroom.js/dist/headroom.js": +/*!***************************************************!*\ + !*** ./node_modules/headroom.js/dist/headroom.js ***! + \***************************************************/ +/***/ (function(module) { + +/*! + * headroom.js v0.12.0 - Give your page some headroom. Hide your header until you need it + * Copyright (c) 2020 Nick Williams - http://wicky.nillia.ms/headroom.js + * License: MIT + */ + +(function (global, factory) { + true ? module.exports = factory() : + 0; +}(this, function () { 'use strict'; + + function isBrowser() { + return typeof window !== "undefined"; + } + + /** + * Used to detect browser support for adding an event listener with options + * Credit: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener + */ + function passiveEventsSupported() { + var supported = false; + + try { + var options = { + // eslint-disable-next-line getter-return + get passive() { + supported = true; + } + }; + window.addEventListener("test", options, options); + window.removeEventListener("test", options, options); + } catch (err) { + supported = false; + } + + return supported; + } + + function isSupported() { + return !!( + isBrowser() && + function() {}.bind && + "classList" in document.documentElement && + Object.assign && + Object.keys && + requestAnimationFrame + ); + } + + function isDocument(obj) { + return obj.nodeType === 9; // Node.DOCUMENT_NODE === 9 + } + + function isWindow(obj) { + // `obj === window` or `obj instanceof Window` is not sufficient, + // as the obj may be the window of an iframe. + return obj && obj.document && isDocument(obj.document); + } + + function windowScroller(win) { + var doc = win.document; + var body = doc.body; + var html = doc.documentElement; + + return { + /** + * @see http://james.padolsey.com/javascript/get-document-height-cross-browser/ + * @return {Number} the scroll height of the document in pixels + */ + scrollHeight: function() { + return Math.max( + body.scrollHeight, + html.scrollHeight, + body.offsetHeight, + html.offsetHeight, + body.clientHeight, + html.clientHeight + ); + }, + + /** + * @see http://andylangton.co.uk/blog/development/get-viewport-size-width-and-height-javascript + * @return {Number} the height of the viewport in pixels + */ + height: function() { + return win.innerHeight || html.clientHeight || body.clientHeight; + }, + + /** + * Gets the Y scroll position + * @return {Number} pixels the page has scrolled along the Y-axis + */ + scrollY: function() { + if (win.pageYOffset !== undefined) { + return win.pageYOffset; + } + + return (html || body.parentNode || body).scrollTop; + } + }; + } + + function elementScroller(element) { + return { + /** + * @return {Number} the scroll height of the element in pixels + */ + scrollHeight: function() { + return Math.max( + element.scrollHeight, + element.offsetHeight, + element.clientHeight + ); + }, + + /** + * @return {Number} the height of the element in pixels + */ + height: function() { + return Math.max(element.offsetHeight, element.clientHeight); + }, + + /** + * Gets the Y scroll position + * @return {Number} pixels the element has scrolled along the Y-axis + */ + scrollY: function() { + return element.scrollTop; + } + }; + } + + function createScroller(element) { + return isWindow(element) ? windowScroller(element) : elementScroller(element); + } + + /** + * @param element EventTarget + */ + function trackScroll(element, options, callback) { + var isPassiveSupported = passiveEventsSupported(); + var rafId; + var scrolled = false; + var scroller = createScroller(element); + var lastScrollY = scroller.scrollY(); + var details = {}; + + function update() { + var scrollY = Math.round(scroller.scrollY()); + var height = scroller.height(); + var scrollHeight = scroller.scrollHeight(); + + // reuse object for less memory churn + details.scrollY = scrollY; + details.lastScrollY = lastScrollY; + details.direction = scrollY > lastScrollY ? "down" : "up"; + details.distance = Math.abs(scrollY - lastScrollY); + details.isOutOfBounds = scrollY < 0 || scrollY + height > scrollHeight; + details.top = scrollY <= options.offset[details.direction]; + details.bottom = scrollY + height >= scrollHeight; + details.toleranceExceeded = + details.distance > options.tolerance[details.direction]; + + callback(details); + + lastScrollY = scrollY; + scrolled = false; + } + + function handleScroll() { + if (!scrolled) { + scrolled = true; + rafId = requestAnimationFrame(update); + } + } + + var eventOptions = isPassiveSupported + ? { passive: true, capture: false } + : false; + + element.addEventListener("scroll", handleScroll, eventOptions); + update(); + + return { + destroy: function() { + cancelAnimationFrame(rafId); + element.removeEventListener("scroll", handleScroll, eventOptions); + } + }; + } + + function normalizeUpDown(t) { + return t === Object(t) ? t : { down: t, up: t }; + } + + /** + * UI enhancement for fixed headers. + * Hides header when scrolling down + * Shows header when scrolling up + * @constructor + * @param {DOMElement} elem the header element + * @param {Object} options options for the widget + */ + function Headroom(elem, options) { + options = options || {}; + Object.assign(this, Headroom.options, options); + this.classes = Object.assign({}, Headroom.options.classes, options.classes); + + this.elem = elem; + this.tolerance = normalizeUpDown(this.tolerance); + this.offset = normalizeUpDown(this.offset); + this.initialised = false; + this.frozen = false; + } + Headroom.prototype = { + constructor: Headroom, + + /** + * Start listening to scrolling + * @public + */ + init: function() { + if (Headroom.cutsTheMustard && !this.initialised) { + this.addClass("initial"); + this.initialised = true; + + // defer event registration to handle browser + // potentially restoring previous scroll position + setTimeout( + function(self) { + self.scrollTracker = trackScroll( + self.scroller, + { offset: self.offset, tolerance: self.tolerance }, + self.update.bind(self) + ); + }, + 100, + this + ); + } + + return this; + }, + + /** + * Destroy the widget, clearing up after itself + * @public + */ + destroy: function() { + this.initialised = false; + Object.keys(this.classes).forEach(this.removeClass, this); + this.scrollTracker.destroy(); + }, + + /** + * Unpin the element + * @public + */ + unpin: function() { + if (this.hasClass("pinned") || !this.hasClass("unpinned")) { + this.addClass("unpinned"); + this.removeClass("pinned"); + + if (this.onUnpin) { + this.onUnpin.call(this); + } + } + }, + + /** + * Pin the element + * @public + */ + pin: function() { + if (this.hasClass("unpinned")) { + this.addClass("pinned"); + this.removeClass("unpinned"); + + if (this.onPin) { + this.onPin.call(this); + } + } + }, + + /** + * Freezes the current state of the widget + * @public + */ + freeze: function() { + this.frozen = true; + this.addClass("frozen"); + }, + + /** + * Re-enables the default behaviour of the widget + * @public + */ + unfreeze: function() { + this.frozen = false; + this.removeClass("frozen"); + }, + + top: function() { + if (!this.hasClass("top")) { + this.addClass("top"); + this.removeClass("notTop"); + + if (this.onTop) { + this.onTop.call(this); + } + } + }, + + notTop: function() { + if (!this.hasClass("notTop")) { + this.addClass("notTop"); + this.removeClass("top"); + + if (this.onNotTop) { + this.onNotTop.call(this); + } + } + }, + + bottom: function() { + if (!this.hasClass("bottom")) { + this.addClass("bottom"); + this.removeClass("notBottom"); + + if (this.onBottom) { + this.onBottom.call(this); + } + } + }, + + notBottom: function() { + if (!this.hasClass("notBottom")) { + this.addClass("notBottom"); + this.removeClass("bottom"); + + if (this.onNotBottom) { + this.onNotBottom.call(this); + } + } + }, + + shouldUnpin: function(details) { + var scrollingDown = details.direction === "down"; + + return scrollingDown && !details.top && details.toleranceExceeded; + }, + + shouldPin: function(details) { + var scrollingUp = details.direction === "up"; + + return (scrollingUp && details.toleranceExceeded) || details.top; + }, + + addClass: function(className) { + this.elem.classList.add.apply( + this.elem.classList, + this.classes[className].split(" ") + ); + }, + + removeClass: function(className) { + this.elem.classList.remove.apply( + this.elem.classList, + this.classes[className].split(" ") + ); + }, + + hasClass: function(className) { + return this.classes[className].split(" ").every(function(cls) { + return this.classList.contains(cls); + }, this.elem); + }, + + update: function(details) { + if (details.isOutOfBounds) { + // Ignore bouncy scrolling in OSX + return; + } + + if (this.frozen === true) { + return; + } + + if (details.top) { + this.top(); + } else { + this.notTop(); + } + + if (details.bottom) { + this.bottom(); + } else { + this.notBottom(); + } + + if (this.shouldUnpin(details)) { + this.unpin(); + } else if (this.shouldPin(details)) { + this.pin(); + } + } + }; + + /** + * Default options + * @type {Object} + */ + Headroom.options = { + tolerance: { + up: 0, + down: 0 + }, + offset: 0, + scroller: isBrowser() ? window : null, + classes: { + frozen: "headroom--frozen", + pinned: "headroom--pinned", + unpinned: "headroom--unpinned", + top: "headroom--top", + notTop: "headroom--not-top", + bottom: "headroom--bottom", + notBottom: "headroom--not-bottom", + initial: "headroom" + } + }; + + Headroom.cutsTheMustard = isSupported(); + + return Headroom; + +})); + + +/***/ }), + +/***/ "./node_modules/ieee754/index.js": +/*!***************************************!*\ + !*** ./node_modules/ieee754/index.js ***! + \***************************************/ +/***/ ((__unused_webpack_module, exports) => { + +/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh