From 0c3eac238976c32c29320abb6c57ea5f5ea04edf Mon Sep 17 00:00:00 2001 From: Vladyslav Zubko <42296182+what1s1ove@users.noreply.github.com> Date: Thu, 30 May 2024 15:23:47 +0300 Subject: [PATCH] feat(whatislove-dev): add webmentions to articles wd-512 (#533) * feat(whatislove-dev): add button block wd-512 * feat(whatislove-dev): add input block wd-512 * feat(whatislove-dev): add mentions markup wd-512 * feat(whatislove-dev): add webmentions to articles wd-512 * build(whatislove-dev): add cron rebuild functionality wd-512 * build(whatislove-dev): add api keys to cd action wd-512 * ci(whatislove-dev): add secrets to ci action wd-512 --- .github/workflows/cd.yml | 6 +- .github/workflows/ci.yml | 4 + .gitignore | 3 + apps/whatislove-dev/.env.example | 10 + apps/whatislove-dev/eleventy.config.js | 17 +- apps/whatislove-dev/package.json | 3 +- apps/whatislove-dev/src/data/environment.js | 16 + apps/whatislove-dev/src/data/mentions.js | 287 ++++++++++++++++++ apps/whatislove-dev/src/images/icons.svg | 10 +- apps/whatislove-dev/src/images/stickers.svg | 12 +- .../src/includes/pagination-list.njk | 2 +- apps/whatislove-dev/src/layouts/article.njk | 92 +++++- apps/whatislove-dev/src/pages/404/index.njk | 2 +- apps/whatislove-dev/src/pages/index/index.njk | 2 +- .../src/styles/blocks/article.css | 5 + .../src/styles/blocks/articles.css | 6 - .../src/styles/blocks/button.css | 11 + .../src/styles/blocks/input.css | 10 + .../src/styles/blocks/mentions.css | 182 +++++++++++ .../src/styles/blocks/pagination.css | 12 +- apps/whatislove-dev/src/styles/index.css | 3 + package-lock.json | 17 +- 22 files changed, 682 insertions(+), 30 deletions(-) create mode 100644 apps/whatislove-dev/.env.example create mode 100644 apps/whatislove-dev/src/data/environment.js create mode 100644 apps/whatislove-dev/src/data/mentions.js create mode 100644 apps/whatislove-dev/src/styles/blocks/button.css create mode 100644 apps/whatislove-dev/src/styles/blocks/input.css create mode 100644 apps/whatislove-dev/src/styles/blocks/mentions.css diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 7e3da078..cfbdb66a 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,6 +1,8 @@ name: Continuous Delivery on: + schedule: + - cron: '0 0 * * *' push: branches: - main @@ -28,7 +30,7 @@ jobs: dependencies: name: Install Dependencies needs: release - if: ${{ needs.release.outputs.release_created }} + if: ${{ needs.release.outputs.release_created || github.event_name == 'schedule' }} runs-on: ubuntu-latest steps: - name: Code Checkout @@ -88,6 +90,8 @@ jobs: run: npm run build env: VITE_DB_API_KEY: ${{ secrets.DB_API_KEY }} + DEVTO_API_KEY: ${{ secrets.DEVTO_API_KEY }} + WEBMENTION_API_KEY: ${{ secrets.WEBMENTION_API_KEY }} - name: Upload Artifact uses: actions/upload-artifact@v4 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c86077ea..5f6f85fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,10 @@ jobs: - name: Code Building run: npm run build + env: + VITE_DB_API_KEY: ${{ secrets.DB_API_KEY }} + DEVTO_API_KEY: ${{ secrets.DEVTO_API_KEY }} + WEBMENTION_API_KEY: ${{ secrets.WEBMENTION_API_KEY }} - name: Code Linting run: npm run ci:lint diff --git a/.gitignore b/.gitignore index 3231fc57..2e770949 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ node_modules build process.mp4 +# cache +.cache + # misc .DS_Store .env diff --git a/apps/whatislove-dev/.env.example b/apps/whatislove-dev/.env.example new file mode 100644 index 00000000..a05fb2ad --- /dev/null +++ b/apps/whatislove-dev/.env.example @@ -0,0 +1,10 @@ +# +# COMMON +# +ENVIRONMENT=development + +# +# API +# +DEVTO_API_KEY=1234567890 +WEBMENTION_API_KEY=0987654321 diff --git a/apps/whatislove-dev/eleventy.config.js b/apps/whatislove-dev/eleventy.config.js index 6dc03f19..841d5a61 100644 --- a/apps/whatislove-dev/eleventy.config.js +++ b/apps/whatislove-dev/eleventy.config.js @@ -10,9 +10,9 @@ import { parseHTML } from 'linkedom' import markdownIt from 'markdown-it' import { existsSync } from 'node:fs' import { mkdir, readFile, writeFile } from 'node:fs/promises' -import process from 'node:process' import svgo from 'svgo' +import { default as environment } from './src/data/environment.js' import { addToc, replaceImgWrapper } from './src/transforms/transforms.js' /** @typedef {import('./package.json')} */ @@ -45,7 +45,7 @@ let CollectionPath = /** @type {const} */ ({ PAGES: `src/pages/!(404)/index.njk`, }) -let isDevelopment = process.env[`NODE_ENV`] === `development` +let isDevelopment = environment.COMMON.ENVIRONMENT === `development` let rawPackageJson = await readFile(new URL(`package.json`, import.meta.url)) let packageJson = /** @type {(text: string) => PackageJson} */ (JSON.parse)( rawPackageJson.toString(), @@ -95,11 +95,14 @@ let init = (config) => { }) config.addFilter(`dateFormatted`, (value) => { - return /** @type {Date} */ (value).toLocaleString(`en-US`, { - day: `numeric`, - month: `short`, - year: `numeric`, - }) + return new Date(/** @type {Date | string} */ (value)).toLocaleString( + `en-US`, + { + day: `numeric`, + month: `short`, + year: `numeric`, + }, + ) }) config.addFilter(`shuffle`, (items) => { diff --git a/apps/whatislove-dev/package.json b/apps/whatislove-dev/package.json index 47d6cacb..dd0d3cb7 100644 --- a/apps/whatislove-dev/package.json +++ b/apps/whatislove-dev/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "start": "concurrently -k -p \"{name}: {time}\" -n \"CLIENT,SERVER\" -c \"green,blue\" \"npm:start:client\" \"npm:start:server\"", - "start:client": "NODE_ENV=development npx @11ty/eleventy --serve --quiet", + "start:client": "npx @11ty/eleventy --serve --quiet", "start:server": "json-server --watch src/database.json --port 3001", "build:clean": "rm -rf build", "build": "npm run build:clean && npx @11ty/eleventy", @@ -34,6 +34,7 @@ "@types/markdown-it": "14.1.1", "bemlint": "1.7.0", "browserslist": "4.22.1", + "dotenv": "16.4.5", "esbuild": "0.19.5", "html-minifier-terser": "7.2.0", "json-server": "0.17.4", diff --git a/apps/whatislove-dev/src/data/environment.js b/apps/whatislove-dev/src/data/environment.js new file mode 100644 index 00000000..7aa0a9a6 --- /dev/null +++ b/apps/whatislove-dev/src/data/environment.js @@ -0,0 +1,16 @@ +import 'dotenv/config' +import process from 'node:process' + +let data = { + API: { + DEVTO_API_KEY: /** @type {string} */ (process.env[`DEVTO_API_KEY`]), + WEBMENTION_API_KEY: /** @type {string} */ ( + process.env[`WEBMENTION_API_KEY`] + ), + }, + COMMON: { + ENVIRONMENT: /** @type {string} */ (process.env[`ENVIRONMENT`]), + }, +} + +export default data diff --git a/apps/whatislove-dev/src/data/mentions.js b/apps/whatislove-dev/src/data/mentions.js new file mode 100644 index 00000000..dd6e86d9 --- /dev/null +++ b/apps/whatislove-dev/src/data/mentions.js @@ -0,0 +1,287 @@ +import { parseHTML } from 'linkedom' + +import { default as environment } from './environment.js' + +let MENTION_PARAGRAPH_COUNT = /** @type {const} */ (2) +let MENTION_LAST_PARAGRAPH_IDX = /** @type {const} */ (-1) + +/** + * @typedef {{ + * author: { + * name: string + * avatarUrl: string | undefined + * } + * content: string + * date: Date + * fromType: string | undefined + * url: string + * }} + */ +let Mention + +/** + * @typedef {{ + * likesCount: number + * repostsCount: number + * mentions: Mention[] + * }} + */ +let PageMention + +/** @typedef {Record} */ +let PagesMentions + +/** + * @template {unknown} T + * @param {string} url + * @param {RequestInit} [options] + * @returns {Promise} + */ +let load = (url, options) => { + return fetch(url, options).then((response) => { + return /** @type {() => Promise} */ (response.json)() + }) +} + +/** + * @param {string} html + * @returns {string} + */ +let prepareMentionContent = (html) => { + let window = parseHTML(` + + + ${html} + + `) + + let paragraphNodes = [...window.document.querySelectorAll(`p`)] + let mentionParagraphNodes = paragraphNodes.slice(0, MENTION_PARAGRAPH_COUNT) + + if (paragraphNodes.length > MENTION_PARAGRAPH_COUNT) { + let lastComment = /** @type {HTMLParagraphElement} */ ( + mentionParagraphNodes.at(MENTION_LAST_PARAGRAPH_IDX) + ) + + lastComment.innerHTML = `${lastComment.innerHTML} … see more` + } + + window.document.body.innerHTML = `` + window.document.body.append(...mentionParagraphNodes) + + return window.document.body.innerHTML +} + +/** API: Dev.to */ + +/** + * @typedef {{ + * canonical_url: string + * id: number + * positive_reactions_count: number + * }} + */ +let ArticleDevto + +/** + * @typedef {{ + * body_html: string + * children: CommentDevto[] + * created_at: string + * id_code: string + * user: { + * profile_image_90: string + * username: string + * } + * }} + */ +let CommentDevto + +/** + * @template {unknown} T + * @param {string} path + * @returns {Promise} + */ +let callDevtoApi = (path) => { + return load(`https://dev.to/api${path}`, { + headers: { + 'api-key': environment.API.DEVTO_API_KEY, + }, + }) +} + +/** + * @param {CommentDevto[]} comments + * @returns {CommentDevto[]} + */ +let getAllDevtoComments = (comments) => { + return comments.flatMap((comment) => [ + comment, + ...(comment.children.length > 0 + ? getAllDevtoComments(comment.children) + : []), + ]) +} + +/** @returns {Promise} */ +let getDevtoPageMention = async () => { + let articles = await /** @type {Promise} */ ( + callDevtoApi(`/articles/me`) + ) + let pageMentions = /** @type {PagesMentions} */ ({}) + + for (let article of articles) { + let comments = await /** @type {Promise} */ ( + callDevtoApi(`/comments?a_id=${article.id}`) + ) + + let allComments = getAllDevtoComments(comments) + + let articleUrl = new URL(article.canonical_url) + + pageMentions[articleUrl.pathname] = { + likesCount: article.positive_reactions_count, + mentions: allComments.map((comment) => ({ + author: { + avatarUrl: comment.user.profile_image_90, + name: `@${comment.user.username}`, + }, + content: prepareMentionContent(comment.body_html), + date: new Date(comment.created_at), + fromType: `devto`, + url: `https://dev.to/${comment.user.username}/comment/${comment.id_code}`, + })), + repostsCount: 0, + } + } + + return pageMentions +} + +/** API: Webmentions */ + +/** + * @typedef {{ + * 'wm-target': string + * 'wm-property': 'like-of' | 'repost-of' | 'in-reply-to' + * 'author'?: { + * photo: string + * name: string + * } + * 'content': { + * html: string + * } + * 'published': string + * 'url': string + * }} + */ +let WebMentionItem + +/** + * @typedef {{ + * children: WebMentionItem[] + * }} + */ +let WebMentions + +let webMentionSourceHostToFromType = /** @type {const} */ ({ + 'mastodon.social': `mastodon`, +}) + +/** + * @template {unknown} T + * @param {string} path + * @returns {Promise} + */ +let callWebMentionApi = (path) => { + return load( + `https://webmention.io/api${path}?token=${environment.API.WEBMENTION_API_KEY}`, + ) +} + +/** @returns {Promise} */ +let getWebMentionsPageMention = async () => { + let webMentions = await /** @type {Promise} */ ( + callWebMentionApi(`/mentions.jf2`) + ) + let pageMentions = /** @type {PagesMentions} */ ({}) + + for (let webMention of webMentions.children) { + let targetUrl = new URL(webMention[`wm-target`]) + + if (pageMentions[targetUrl.pathname] === undefined) { + pageMentions[targetUrl.pathname] = { + likesCount: 0, + mentions: [], + repostsCount: 0, + } + } + + let currentPageMentions = /** @type {PageMention} */ ( + pageMentions[targetUrl.pathname] + ) + + if (webMention[`wm-property`] === `like-of`) { + currentPageMentions.likesCount++ + } + + if (webMention[`wm-property`] === `repost-of`) { + currentPageMentions.repostsCount++ + } + + if (webMention[`wm-property`] === `in-reply-to`) { + let host = new URL(webMention.url).host + let fromType = + webMentionSourceHostToFromType[ + /** @type {keyof webMentionSourceHostToFromType} */ (host) + ] + + currentPageMentions.mentions.push({ + author: { + avatarUrl: webMention.author?.photo ?? undefined, + name: webMention.author?.name ?? host, + }, + content: prepareMentionContent(webMention.content.html), + date: new Date(webMention.published), + fromType, + url: webMention.url, + }) + } + } + + return pageMentions +} + +/** @returns {Promise} */ +let loader = async () => { + let mentionLoaders = [getDevtoPageMention, getWebMentionsPageMention] + let allPagesMentions = /** @type {PagesMentions} */ ({}) + + for (let mentionLoader of mentionLoaders) { + let fetchedPageMentions = await mentionLoader() + + for (let [pageUrl, pageMentions] of Object.entries( + fetchedPageMentions, + )) { + if (allPagesMentions[pageUrl] === undefined) { + allPagesMentions[pageUrl] = { + likesCount: 0, + mentions: [], + repostsCount: 0, + } + } + + let currentPageMentions = /** @type {PageMention} */ ( + allPagesMentions[pageUrl] + ) + + currentPageMentions.likesCount += pageMentions.likesCount + currentPageMentions.repostsCount += pageMentions.repostsCount + currentPageMentions.mentions.push(...pageMentions.mentions) + } + } + + return allPagesMentions +} + +export default loader diff --git a/apps/whatislove-dev/src/images/icons.svg b/apps/whatislove-dev/src/images/icons.svg index 7336b596..af67688f 100644 --- a/apps/whatislove-dev/src/images/icons.svg +++ b/apps/whatislove-dev/src/images/icons.svg @@ -53,6 +53,15 @@ + + + + + + + + + @@ -84,5 +93,4 @@ - diff --git a/apps/whatislove-dev/src/images/stickers.svg b/apps/whatislove-dev/src/images/stickers.svg index c55f9aa8..bb87cafd 100644 --- a/apps/whatislove-dev/src/images/stickers.svg +++ b/apps/whatislove-dev/src/images/stickers.svg @@ -171,7 +171,6 @@ - @@ -209,4 +208,15 @@ + + + + + + + + + + + diff --git a/apps/whatislove-dev/src/includes/pagination-list.njk b/apps/whatislove-dev/src/includes/pagination-list.njk index 0cad660a..54541792 100644 --- a/apps/whatislove-dev/src/includes/pagination-list.njk +++ b/apps/whatislove-dev/src/includes/pagination-list.njk @@ -14,7 +14,7 @@ {% set href = pagination.hrefs[loop.index0] %}
  • {{ content | safe }} +
    +
    +

    Webmentions

    +

    + If you liked this article and think others should read it, please share it. +

    +
    +
      + +
    • + {{ mentionData.repostsCount or 0 }} reposts +
    • +
    • + {{ mentionData.mentions.length or 0 }} comments +
    • +
    + {% if mentionData.mentions.length %} +
    + {% endif %} +
    +

    + These are webmentions via the + IndieWeb and + webmention.io. Mention this post from your + site: +

    +
    + + + +
    +
    +
    {% endblock %} @@ -58,6 +148,6 @@ {% endblock %} diff --git a/apps/whatislove-dev/src/pages/404/index.njk b/apps/whatislove-dev/src/pages/404/index.njk index b4b436a2..518c4d0d 100644 --- a/apps/whatislove-dev/src/pages/404/index.njk +++ b/apps/whatislove-dev/src/pages/404/index.njk @@ -30,6 +30,6 @@ stickersCount: 3 {% endblock %} diff --git a/apps/whatislove-dev/src/pages/index/index.njk b/apps/whatislove-dev/src/pages/index/index.njk index 7233b6f8..a68bbd10 100644 --- a/apps/whatislove-dev/src/pages/index/index.njk +++ b/apps/whatislove-dev/src/pages/index/index.njk @@ -115,7 +115,7 @@ stickersCount: 0

    Lastest Article

    {{ articleList(items=collections.articles | reverse, className="articles__article-list", maxCount=ARTICLES_COUNT) }} - View all articles + View all articles
    {{ channel() }} {% endblock %} diff --git a/apps/whatislove-dev/src/styles/blocks/article.css b/apps/whatislove-dev/src/styles/blocks/article.css index bd0a1dcc..2ec1fa75 100644 --- a/apps/whatislove-dev/src/styles/blocks/article.css +++ b/apps/whatislove-dev/src/styles/blocks/article.css @@ -63,3 +63,8 @@ content: " \220E"; } } + +.article__mentions { + padding-block: var(--gap) 0; + border-block-start: 1px solid hsl(var(--color-page-background-highlight)); +} diff --git a/apps/whatislove-dev/src/styles/blocks/articles.css b/apps/whatislove-dev/src/styles/blocks/articles.css index ef7ef8fd..dc16346d 100644 --- a/apps/whatislove-dev/src/styles/blocks/articles.css +++ b/apps/whatislove-dev/src/styles/blocks/articles.css @@ -14,11 +14,5 @@ .articles__all { place-self: center; - padding-block: 10px; padding-inline: 40px; - font-weight: 600; - color: hsl(var(--color-red-100)); - text-decoration: none; - background-color: hsl(var(--color-red-100) / 6%); - border-radius: 5px; } diff --git a/apps/whatislove-dev/src/styles/blocks/button.css b/apps/whatislove-dev/src/styles/blocks/button.css new file mode 100644 index 00000000..cc7e32cf --- /dev/null +++ b/apps/whatislove-dev/src/styles/blocks/button.css @@ -0,0 +1,11 @@ +.button { + --button-color: var(--color-red-100); + --button-background-color: var(--color-red-100) / 6%; + + padding: 10px; + font-weight: 700; + color: hsl(var(--button-color)); + text-decoration: none; + background-color: hsl(var(--button-background-color)); + border-radius: 5px; +} diff --git a/apps/whatislove-dev/src/styles/blocks/input.css b/apps/whatislove-dev/src/styles/blocks/input.css new file mode 100644 index 00000000..c7908c3a --- /dev/null +++ b/apps/whatislove-dev/src/styles/blocks/input.css @@ -0,0 +1,10 @@ +.input { + padding-block: 10px; + padding-inline: 20px; + font-family: inherit; + font-size: inherit; + color: hsl(var(--color-gray-100)); + background-color: hsl(var(--color-intro-background)); + border: 1px solid hsl(var(--color-page-background-highlight)); + border-radius: 5px; +} diff --git a/apps/whatislove-dev/src/styles/blocks/mentions.css b/apps/whatislove-dev/src/styles/blocks/mentions.css new file mode 100644 index 00000000..98657349 --- /dev/null +++ b/apps/whatislove-dev/src/styles/blocks/mentions.css @@ -0,0 +1,182 @@ +.mentions { + display: grid; + gap: 25px; + padding-block: var(--padding-block-box) 0; +} + +.mentions__header { + display: grid; + gap: 5px; +} + +.mentions__title { + margin: 0; + font-size: var(--font-size-heading-2); + font-weight: 600; + color: hsl(var(--color-red-100)); +} + +.mentions__description { + margin: 0; + color: hsl(var(--color-gray-100)); +} + +.mentions__stats-list { + display: flex; + gap: 10px; + padding: 0; + margin: 0; + list-style: none; +} + +.mentions__stats-item { + display: inline-flex; + gap: 10px; + align-items: center; + padding-block: 9px; + padding-inline: 20px; + font-size: var(--font-size-heading-3); + font-weight: 700; + line-height: 1; + background-color: hsl(var(--color-intro-background)); + border-radius: 8px; + + &[class*="--likes"] { + &::before { + content: "\2764\FE0F"; + } + } + + &[class*="--reposts"] { + &::before { + content: "\1F501"; + } + } + + &[class*="--comments"] { + &::before { + content: "\1F4AC"; + } + } +} + +.mentions__list { + display: grid; + gap: 15px; + padding: 0; + margin: 0; + list-style: none; +} + +.mentions__item { + display: grid; + grid-template-columns: 1fr max-content; + gap: 15px; + padding-block: 15px; + padding-inline: 20px; + background-color: hsl(var(--color-intro-background)); +} + +.mentions__author { + display: flex; + gap: 10px; + align-items: center; +} + +.mentions__author-picture { + position: relative; + + &::before { + position: absolute; + inset-block-start: -5px; + inset-inline-end: -8px; + inline-size: 17px; + aspect-ratio: 1; + content: ""; + background-repeat: no-repeat; + background-position: 50%; + background-size: contain; + } + + &[class*="--devto"] { + &::before { + background-image: url("/images/stickers.svg#devto"); + } + } + + &[class*="--mastodon"] { + &::before { + background-image: url("/images/stickers.svg#mastodon"); + } + } +} + +.mentions__author-avatar { + display: block; + border-radius: 50%; + fill: hsl(var(--color-icon)); +} + +.mentions__author-name { + margin: 0; + font-size: var(--font-size-heading-3); +} + +.mentions__date { + place-self: start end; + color: hsl(var(--color-gray-100)); +} + +.mentions__content { + margin: 0; +} + +.mentions__form-wrapper { + display: grid; + gap: 10px; +} + +.mentions__form-description { + margin: 0; + color: hsl(var(--color-gray-100)); + + a { + color: hsl(var(--color-red-100)); + text-underline-offset: 3px; + } +} + +.mentions__origin { + --size-icon: 25px; + + place-self: end; + + &::before { + inline-size: var(--size-icon); + block-size: var(--size-icon); + content: ""; + background-color: hsl(var(--color-gray-100)); + mask-image: url("/images/icons.svg#origin"); + mask-repeat: no-repeat; + mask-position: 50%; + mask-size: 15px; + } +} + +.mentions__form { + display: grid; + gap: 10px; + + @media (--width-tablet) { + grid-template-columns: 1fr auto; + } +} + +.mentions__form-button { + place-self: center; + padding-inline: 30px; + + @media (--width-tablet) { + place-self: initial; + } +} diff --git a/apps/whatislove-dev/src/styles/blocks/pagination.css b/apps/whatislove-dev/src/styles/blocks/pagination.css index c0709ecf..beaf1299 100644 --- a/apps/whatislove-dev/src/styles/blocks/pagination.css +++ b/apps/whatislove-dev/src/styles/blocks/pagination.css @@ -49,18 +49,16 @@ } .pagination__number-link { - display: flex; + box-sizing: border-box; + display: inline-flex; place-content: center; inline-size: 43px; block-size: var(--block-size); - font-weight: bold; - line-height: 1; color: hsl(var(--color)); - text-underline-offset: 3px; - border-radius: 5px; + background-color: unset; &[aria-current="page"] { - color: hsl(var(--color-red-100)); - background-color: hsl(var(--color-red-100) / 6%); + color: hsl(var(--button-color)); + background-color: hsl(var(--button-background-color)); } } diff --git a/apps/whatislove-dev/src/styles/index.css b/apps/whatislove-dev/src/styles/index.css index 3024ae50..255a56dc 100644 --- a/apps/whatislove-dev/src/styles/index.css +++ b/apps/whatislove-dev/src/styles/index.css @@ -5,7 +5,9 @@ @import url("./blocks/page.css"); @import url("./blocks/main.css"); @import url("./blocks/action.css"); +@import url("./blocks/button.css"); @import url("./blocks/switch.css"); +@import url("./blocks/input.css"); @import url("./blocks/shapes.css"); @import url("./blocks/scroll-shadows.css"); @import url("./blocks/skip-link.css"); @@ -33,4 +35,5 @@ @import url("./blocks/channel.css"); @import url("./blocks/not-found.css"); @import url("./blocks/articles.css"); +@import url("./blocks/mentions.css"); @import url("./blocks/article.css"); diff --git a/package-lock.json b/package-lock.json index f99033a3..d90c580a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -91,7 +91,7 @@ }, "apps/whatislove-dev": { "name": "@whatislove.dev/whatislove-dev", - "version": "1.43.0", + "version": "1.48.0", "dependencies": { "@whatislove.dev/shared": "*", "focus-trap": "7.5.4", @@ -109,6 +109,7 @@ "@types/markdown-it": "14.1.1", "bemlint": "1.7.0", "browserslist": "4.22.1", + "dotenv": "16.4.5", "esbuild": "0.19.5", "html-minifier-terser": "7.2.0", "json-server": "0.17.4", @@ -9156,6 +9157,18 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", @@ -20543,7 +20556,7 @@ }, "packages/shared": { "name": "@whatislove.dev/shared", - "version": "1.8.0", + "version": "1.9.0", "hasInstallScript": true } }