diff --git a/.forestry/front_matter/templates/blog-post.yml b/.forestry/front_matter/templates/blog-post.yml
index 0cd0a1de..6babd9e5 100644
--- a/.forestry/front_matter/templates/blog-post.yml
+++ b/.forestry/front_matter/templates/blog-post.yml
@@ -27,23 +27,16 @@ fields:
- type: datetime
name: date
label: Date
- description: '"Published on" date. Remember this is only a list date for
- display; posts are not automatically published!'
+ description: '"Published on" date.
To schedule this post for future publishing, simply
+ set the date to the future.
WARNING: Data
+ is saved in the ipfs/ipfs-blog
+ public GitHub repo as soon as you hit the save button. So any sensitive information
+ will also be publicly available on GitHub, but hidden from the website blog.ipfs.io.'
config:
required: true
date_format: YYYY/MM/DD
export_format: YYYY-MM-DD
-- name: publishing_date
- type: datetime
- label: Publishing Date
- config:
- required: false
- date_format:
- time_format:
- display_utc: true
- description: When the post should be published.
Use this to schedule publishing
- the post in the future.
If you don't want to schedule, just ignore this.
- default: now
+ display_utc: false
- type: text
name: permalink
label: Permalink
diff --git a/.forestry/front_matter/templates/newslinks.yml b/.forestry/front_matter/templates/newslinks.yml
index 0a7a1e28..10a109f2 100644
--- a/.forestry/front_matter/templates/newslinks.yml
+++ b/.forestry/front_matter/templates/newslinks.yml
@@ -35,17 +35,23 @@ fields:
export_format: YYYY-MM-DD
description: For events, use the date of the event itself.
For
all other link types, use the date the original item was published.
- - name: publishing_date
+ - name: publish_date
type: datetime
- label: Publishing Date
- description: When the link should be published.
Use this to schedule publishing
- the link in the future.
If you don't want to schedule, just ignore this.
+ label: Publish Date
+ description: 'When
+ the link should be published.
Use this to schedule publishing the link in
+ the future.
If you don't want to schedule, leave this blank.
WARNING:
+ Data is saved in the ipfs/ipfs-blog public GitHub repo as soon as
+ you hit the Save button. This means anything you save will also be publicly
+ visible on GitHub, but not on the blog.ipfs.io site. Be careful if you are drafting something sensitive!'
config:
required: false
date_format:
time_format:
display_utc: true
- default: now
+ export_format: YYYY-MM-DDThh:mm:ssZ
+ default: ''
- type: text
name: path
label: URL
diff --git a/.github/workflows/scheduled-publishing.yml b/.github/workflows/scheduled-publishing.yml
new file mode 100644
index 00000000..25b24736
--- /dev/null
+++ b/.github/workflows/scheduled-publishing.yml
@@ -0,0 +1,26 @@
+name: Check for scheduled posts to publish
+on:
+ schedule:
+ - cron: '* 4,16 * * *'
+jobs:
+ check-n-publish:
+ name: Check for scheduled posts and publish if any found
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v2
+ - name: Install dependencies
+ run: npm ci
+ - name: Check for scheduled posts
+ run: echo "::set-output name=should_publish::$(${{ github.workspace }}/scripts/scheduled-publishing.js)"
+ id: should_publish_step
+ - name: Trigger Fleek Build
+ if: ${{ steps.should_publish_step.outputs.should_publish == 'true' }}
+ id: deploy
+ uses: fleekhq/action-deploy@v1
+ with:
+ apiKey: ${{ secrets.FLEEK_API_KEY }}
+ - name: Scheduled posts published
+ if: ${{ steps.should_publish_step.outputs.should_publish == 'true' }}
+ run: echo "Check https://blog.ipfs.io"
+
diff --git a/package-lock.json b/package-lock.json
index da1a75bb..f0a78098 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -33,6 +33,7 @@
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-vue": "^7.9.0",
+ "gray-matter": "^4.0.2",
"husky": "^6.0.0",
"lint-staged": "^10.5.4",
"lodash": "^4.17.21",
@@ -41,6 +42,7 @@
"markdown-it-task-lists": "^2.1.1",
"markdown-it-video": "^0.6.3",
"npm-run-all": "^4.1.5",
+ "p-map": "^4.0.0",
"postcss": "^7.0.35",
"prettier": "^2.2.1",
"slug": "^4.0.4",
diff --git a/package.json b/package.json
index ead31a79..b7947fc9 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-vue": "^7.9.0",
+ "gray-matter": "^4.0.2",
"husky": "^6.0.0",
"lint-staged": "^10.5.4",
"lodash": "^4.17.21",
@@ -38,6 +39,7 @@
"markdown-it-task-lists": "^2.1.1",
"markdown-it-video": "^0.6.3",
"npm-run-all": "^4.1.5",
+ "p-map": "^4.0.0",
"postcss": "^7.0.35",
"prettier": "^2.2.1",
"slug": "^4.0.4",
diff --git a/scripts/scheduled-publishing.js b/scripts/scheduled-publishing.js
new file mode 100755
index 00000000..3cf9f6b8
--- /dev/null
+++ b/scripts/scheduled-publishing.js
@@ -0,0 +1,98 @@
+#!/usr/bin/env node
+
+'use strict'
+
+/**
+ * Scheduled Publishing of posts and links.
+ * This script checks for any blog posts and links that are scheduled to
+ * be published, but are to be published today. If there is at least
+ * one found, a build is trigged in fleek, updating the website
+ * with the latest data.
+ */
+
+const fs = require('fs')
+const path = require('path')
+
+const pMap = require('p-map')
+const matter = require('gray-matter')
+const dayjs = require('dayjs')
+
+const isBetween = require('dayjs/plugin/isBetween')
+dayjs.extend(isBetween)
+
+async function main() {
+ // read files contents concurrently, 10 at a time
+ const contentDir = path.resolve(__dirname, '../src/_blog')
+ const files = await pMap(
+ await fs.promises.readdir(contentDir),
+ (filename) => {
+ if (!filename.endsWith('.md')) {
+ return null
+ }
+
+ return fs.promises.readFile(path.resolve(contentDir, filename), 'utf8')
+ },
+ { concurrency: 10 }
+ )
+
+ const output = files
+ // exclude possible nulls
+ .filter((result) => !!result)
+ // process files into publishable items (posts or links)
+ .reduce((items, fileContent) => {
+ const metadata = matter(fileContent).data
+
+ if (metadata.type && metadata.data) {
+ // links
+ items.push(...metadata.data)
+ } else {
+ items.push(metadata)
+ }
+
+ return items
+ }, [])
+ // check if there is at least one publish date in the future
+ .some((item) => {
+ let publishDate
+
+ // blog post
+ if (item.permalink) {
+ publishDate = item.date
+ }
+
+ // links
+ if (item.url && item.publish_date) {
+ publishDate = item.publish_date
+ }
+
+ if (!publishDate) {
+ return false
+ }
+
+ // we trim to the hour to avoid counting the initial delays on triggering
+ // the job, which could cause the time calculations to exclude specific
+ // scheduled dates and times.
+ const now = dayjs(new Date()).startOf('hour')
+ const timeMatch = dayjs(new Date(publishDate)).isBetween(
+ now.subtract(12, 'hour'),
+ now
+ )
+
+ // using stderr since stdout is used for GH workflow output
+ if (timeMatch) {
+ console.error(`Found post/link to publish: "${item.title}"`)
+ }
+
+ return timeMatch
+ })
+
+ if (!output) {
+ console.error('Nothing found to publish.')
+ }
+
+ // output true or false: should trigger publish or not
+ // GH Workflow step reads from stdout
+ console.log(output)
+}
+
+main()
diff --git a/src/.vuepress/plugins/pageData.js b/src/.vuepress/plugins/pageData.js
index e503fa2a..79d5e59f 100644
--- a/src/.vuepress/plugins/pageData.js
+++ b/src/.vuepress/plugins/pageData.js
@@ -1,4 +1,32 @@
const slug = require('slug')
+const dayjs = require('dayjs')
+
+function shouldBeHidden(frontmatter) {
+ const isDateInFuture = (date) => dayjs(new Date(date)).isAfter(dayjs())
+ let shouldHide = frontmatter.hidden || false
+
+ // sitemap config
+ if (frontmatter.sitemap) {
+ shouldHide = shouldHide || frontmatter.sitemap.exclude
+ }
+
+ // scheduled posts
+ // see auto-publishing of scheduled posts here: https://github.com/ipfs/ipfs-blog/issues/147
+ if (
+ !shouldHide &&
+ frontmatter.permalink && // permalink is unique to posts
+ frontmatter.date
+ ) {
+ shouldHide = shouldHide || isDateInFuture(frontmatter.date)
+ }
+
+ // scheduled links (path is unique to links)
+ if (!shouldHide && frontmatter.path && frontmatter.publish_date) {
+ shouldHide = shouldHide || isDateInFuture(frontmatter.publish_date)
+ }
+
+ return shouldHide
+}
module.exports = (options, context) => ({
extendPageData($page) {
@@ -18,8 +46,8 @@ module.exports = (options, context) => ({
frontmatter.authorKey = authorKey
}
- // exclude a page from the feed & robots if excluded from sitemap
- if (frontmatter.sitemap && frontmatter.sitemap.exclude) {
+ // exclude hidden pages (sitemap config, future publishes, etc)
+ if (shouldBeHidden(frontmatter)) {
frontmatter.feed = {
enable: false,
}
@@ -30,6 +58,18 @@ module.exports = (options, context) => ({
} else {
frontmatter.meta = [noIndex]
}
+
+ frontmatter.hidden = true
+ $page.hidden = true
+ }
+
+ // set links has hidden (future publishes, etc)
+ if (frontmatter.data) {
+ frontmatter.data.forEach((item) => {
+ if (shouldBeHidden(item)) {
+ item.hidden = true
+ }
+ })
}
},
})
diff --git a/src/.vuepress/theme/layouts/Blog.vue b/src/.vuepress/theme/layouts/Blog.vue
index 087f5478..ad47102d 100644
--- a/src/.vuepress/theme/layouts/Blog.vue
+++ b/src/.vuepress/theme/layouts/Blog.vue
@@ -129,14 +129,8 @@ export default {
return false
}
- if (
- page.frontmatter &&
- (page.frontmatter.sitemap ? !page.frontmatter.sitemap.exclude : true)
- ) {
- result.push({
- ...page,
- category: defaultCategory,
- })
+ if (page.frontmatter && !page.frontmatter.hidden) {
+ result.push({ ...page, category: defaultCategory })
}
})
diff --git a/src/.vuepress/theme/layouts/BlogPost.vue b/src/.vuepress/theme/layouts/BlogPost.vue
index d2b5a6c9..50d4d681 100644
--- a/src/.vuepress/theme/layouts/BlogPost.vue
+++ b/src/.vuepress/theme/layouts/BlogPost.vue
@@ -1,5 +1,5 @@
-
+
({
showComments: null,
}),
+ computed: {
+ // hidden field set in plugins/pageData.js
+ isVisible() {
+ return !this.$root.$page.hidden
+ },
+ },
mounted() {
+ if (!this.isVisible) {
+ // path to 404 is relative to support ipfs sub path deployments
+ return this.$router.replace({ path: '../404' })
+ }
+
const ipfsPathRegExp = /^(\/(?:ipfs|ipns)\/[^/]+)/
const ipfsPathPrefix =
(window.location.pathname.match(ipfsPathRegExp) || [])[1] || ''
diff --git a/src/.vuepress/theme/util/blogUtils.js b/src/.vuepress/theme/util/blogUtils.js
index fb913ac5..6acdce26 100644
--- a/src/.vuepress/theme/util/blogUtils.js
+++ b/src/.vuepress/theme/util/blogUtils.js
@@ -1,4 +1,4 @@
-export const checkItem = ({
+export function checkItem({
postType,
tags,
title,
@@ -7,7 +7,7 @@ export const checkItem = ({
searchedText = [],
activeCategory = '',
activeAuthor = '',
-}) => {
+}) {
if (activeCategory && decodeURI(activeCategory) !== postType) {
return false
}
@@ -40,13 +40,13 @@ export const checkItem = ({
return true
}
-export const parseProtectedPost = (
+export function parseProtectedPost(
post,
activeTags = [],
searchedText = [],
activeCategory = '',
activeAuthor = ''
-) => {
+) {
if (!post.frontmatter.data) {
return []
}
@@ -55,6 +55,7 @@ export const parseProtectedPost = (
post.frontmatter.data.forEach((item) => {
if (
+ item.hidden ||
!checkItem({
postType: post.frontmatter.type,
tags: item.tags,
diff --git a/src/_blog/086-ipfs-in-opera-for-android.md b/src/_blog/086-ipfs-in-opera-for-android.md
index 241ffdba..5f087fec 100644
--- a/src/_blog/086-ipfs-in-opera-for-android.md
+++ b/src/_blog/086-ipfs-in-opera-for-android.md
@@ -7,6 +7,7 @@ author: Dietrich Ayala
header_image: "/086-ipfs-in-opera-for-android-header-image.png"
tags:
- browsers
+- opera
- mobile
---
diff --git a/src/_blog/events.md b/src/_blog/events.md
index 35465f4c..10fd0381 100644
--- a/src/_blog/events.md
+++ b/src/_blog/events.md
@@ -6,7 +6,6 @@ sitemap:
data:
- title: IPFS Community Meetup, April 2021
date: 2021-04-27
- publishing_date:
path: https://www.meetup.com/San-Francisco-IPFS/events/276123324/
card_image: "/2021-04-15-cardheader-ipfs-meetup-nft-april2021.png"
tags:
@@ -14,7 +13,6 @@ data:
- official meetup
- title: Cal Hacks hello:world
date: 2021-04-16
- publishing_date: 2021-04-15 17:06:47 +0000
path: https://helloworld.calhacks.io/
card_image: "/2021-04-15-cardheader-hello-world.png"
tags:
diff --git a/src/_blog/welcome-to-ipfs-weekly-131.md b/src/_blog/welcome-to-ipfs-weekly-131.md
index 083598bc..2ba5cba4 100644
--- a/src/_blog/welcome-to-ipfs-weekly-131.md
+++ b/src/_blog/welcome-to-ipfs-weekly-131.md
@@ -1,5 +1,4 @@
---
-publishing_date:
title: Welcome to IPFS Weekly 131
description: Uniswap Interface hosting on IPFS, a call for Filecoin user testers,
and more from around the ecosystem!