feat: vuepress replatform init

This commit is contained in:
Ricardo
2020-12-03 14:23:48 +00:00
commit abda08cac1
352 changed files with 48173 additions and 0 deletions
+186
View File
@@ -0,0 +1,186 @@
const { reverse, sortBy } = require('lodash')
const authors = require('./config/authors')
// configure this to an absolute url to enable a generated sitemap & blog RSS feeds
const CANONICAL_BASE = process.env.CANONICAL_BASE || ''
const IPFS_DEPLOY = process.env.IPFS_DEPLOY === 'true' || false
module.exports = {
title: 'IPFS Blog',
description:
'This is the IPFS Starlog, a series of communications about the IPFS Project.',
domain: CANONICAL_BASE,
authors,
locales: {
'/': {
lang: 'en-US',
title: 'IPFS Blog',
description:
'This is the IPFS Starlog, a series of communications about the IPFS Project.',
},
},
head: require('./config/head'),
dest: './public',
markdown: {
extendMarkdown: (md) => {
md.set({
breaks: true,
})
md.use(require('markdown-it-video'))
md.use(require('markdown-it-footnote'))
md.use(require('markdown-it-task-lists'))
md.use(require('markdown-it-deflist'))
},
},
themeConfig: {
dateFormat: 'DD MMMM YYYY',
socialLinks: [],
footerLinks: [],
footerLegal: '',
headerLinks: [],
mobileNavLinks: [],
},
plugins: [
['@vuepress/last-updated'],
[
'vuepress-plugin-clean-urls',
{
normalSuffix: '/',
indexSuffix: '/',
notFoundPath: '/404/',
},
],
[
'vuepress-plugin-canonical',
CANONICAL_BASE
? {
baseURL: CANONICAL_BASE,
stringExtension: true,
}
: false,
],
[require('./plugins/pageData'), { authors }],
[require('./plugins/vuepress-plugin-trigger-scroll')],
// [require('./plugins/vuepress-plugin-ga-dnt'), { ga: 'UA-xxxxxx' }],
['vuepress-plugin-img-lazy'],
[
'@vuepress/blog',
{
feed: {
canonical_base: CANONICAL_BASE,
sort: (entries) => reverse(sortBy(entries, 'date')),
feed_options: {},
feeds: {
rss2: {
enable: true,
},
atom1: {
enable: false,
},
json1: {
enable: false,
},
},
},
sitemap: {
hostname: CANONICAL_BASE,
changefreq: 'weekly',
},
directories: [
{
id: 'blog',
dirname: '_blog',
path: '/',
itemPermalink: '/blog/:slug',
layout: 'Blog',
itemLayout: 'BlogPost',
frontmatter: {
title: 'Blog',
description:
'This is the IPFS Starlog, a series of communications about the IPFS Project.',
},
pagination: {
lengthPerPage: Number.MAX_SAFE_INTEGER,
},
},
],
},
],
[
'vuepress-plugin-seo',
{
siteTitle: (_, $site) => $site.title,
title: ($page, $site) => $page.title || $site.title,
description: ($page, $site) =>
$page.frontmatter.description || $site.description,
author: ($page) => $page.frontmatter.author,
tags: ($page) => $page.frontmatter.tags,
twitterCard: (_) => 'summary_large_image',
type: ($page) =>
['_blog'].some((folder) => $page.regularPath.startsWith('/' + folder))
? 'article'
: 'website',
url: (_, $site, path) => ($site.domain || '') + path,
image: ($page, $site) =>
$page.frontmatter.image
? ($site.domain || '') + $page.frontmatter.image
: ($site.domain || '') + '/images/og-default.jpg',
publishedAt: ($page) =>
$page.frontmatter.date &&
new Date($page.frontmatter.date).toISOString(),
modifiedAt: ($page) =>
$page.lastUpdated && new Date($page.lastUpdated).toISOString(),
customMeta: (add, ctx) => {
const { $site } = ctx
if ($site.authors instanceof Map) {
// select first object from the authors list
const { twitter } = $site.authors.values().next().value
add('twitter:site', twitter)
}
},
},
],
['vuepress-plugin-robots', { host: CANONICAL_BASE }],
[
'@vuepress/html-redirect',
{
duration: 0,
},
],
['vuepress-plugin-ipfs', IPFS_DEPLOY],
],
extraWatchFiles: ['.vuepress/config/head.js', '.vuepress/config/authors.js'],
chainWebpack: (config, isServer) => {
config.module.rules.delete('svg')
// prettier-ignore
config.module
.rule('svg')
.test(/\.(svg)(\?.*)?$/)
.oneOf('svg-sprite')
.include
.add(/svg-icon/)
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.end()
.use('svgo-loader')
.loader('svgo-loader')
.options({
removeDimensions: true,
removeAttrs: {
attrs: '*:(stroke|fill):((?!^none$).)*',
},
})
.end()
.end()
.oneOf('svg-file')
.use('file-loader')
.loader('file-loader')
.options({
name: `assets/img/[name].[hash:8].[ext]`,
})
.end()
.end()
},
}
+37
View File
@@ -0,0 +1,37 @@
/**
* Site authors:
* Use a RFC 3986 safe key in lowercase this object
* is merged into the $page.frontmatter.author data
* at runtime from the original $page.frontmatter.author
* key.
*
* Avatar images should be stored in the site root:
* _assets/avatars/firstname-surname.jpg
*
*/
module.exports = new Map([
[
'protocol-labs',
{
name: 'Protocol Labs',
svgIcon: 'logo-icon',
twitter: '@protocollabs',
},
],
[
'juan-benet',
{
name: 'Juan Benet',
avatar: 'juan-benet.jpg',
twitter: '@juanbenet',
},
],
[
'jesse-clayburgh',
{
name: 'Jesse Clayburgh',
avatar: 'jessie-clayburgh.jpg',
twitter: '@jesseclayburgh',
},
],
])
+30
View File
@@ -0,0 +1,30 @@
const favicons = ['16x16', '32x32', '48x48'].map((size) => [
'link',
{
rel: 'icon',
type: 'image/png',
sizes: size,
href: `/favicon-${size}.png`,
},
])
module.exports = [
['link', { rel: 'stylesheet', href: '/fonts.css' }],
['link', { rel: 'manifest', href: '/site.webmanifest' }],
[
'link',
{ rel: 'mask-icon', href: '/safari-pinned-tab.svg', color: '#16161F' },
],
[
'link',
{
rel: 'apple-touch-icon',
sizes: '180x180',
href: '/apple-touch-icon.png',
},
],
['meta', { name: 'theme-color', content: '#16161F' }],
['meta', { name: 'msapplication-TileColor', content: '#156ff7' }],
['meta', { name: 'apple-mobile-web-app-title', content: 'Protocol Labs' }],
['meta', { name: 'application-name', content: 'Protocol Labs' }],
].concat(favicons)
+34
View File
@@ -0,0 +1,34 @@
const slug = require('slug')
module.exports = (options, context) => ({
extendPageData($page) {
const { frontmatter } = $page
// author config
const authorName = frontmatter.author
if (typeof authorName === 'string') {
const authorKey = slug(authorName, { lower: true })
// setup author stub to keep templates happy
const author = { name: authorName }
// setup the page author object
frontmatter.author = author
frontmatter.authorKey = authorKey
}
// exclude a page from the feed & robots if excluded from sitemap
if (frontmatter.sitemap && frontmatter.sitemap.exclude) {
frontmatter.feed = {
enable: false,
}
const noIndex = { name: 'robots', content: 'noindex' }
if (Array.isArray(frontmatter.meta)) {
frontmatter.meta.push(noIndex)
} else {
frontmatter.meta = [noIndex]
}
}
},
})
@@ -0,0 +1,18 @@
export default {
mounted() {
// track outbound clicks
document.addEventListener('click', this.trackOutbound)
},
beforeDestroy() {
// remove on unmount
document.removeEventListener('click', this.trackOutbound)
},
methods: {
trackOutbound(e) {
if (!window.ga) return
const link = e.target.closest('a')
if (link === null || window.location.host === link.host) return
window.ga('send', 'event', 'outbound', 'click', link.href)
},
},
}
@@ -0,0 +1,65 @@
/* global GA_ID, ga */
export default ({ router, isServer }) => {
// only apply on client
if (isServer) return
const initAnalytics = () => {
// ga integration
if (process.env.NODE_ENV === 'production' && GA_ID) {
;(function (i, s, o, g, r, a, m) {
i.GoogleAnalyticsObject = r
i[r] =
i[r] ||
function () {
;(i[r].q = i[r].q || []).push(arguments)
}
i[r].l = 1 * new Date()
a = s.createElement(o)
m = s.getElementsByTagName(o)[0]
a.async = 1
a.src = g
m.parentNode.insertBefore(a, m)
})(
window,
document,
'script',
'https://www.google-analytics.com/analytics.js',
'ga'
)
ga('create', GA_ID, 'auto')
ga('set', 'anonymizeIp', true)
router.afterEach(function (to) {
ga('set', 'page', to.fullPath)
ga('send', 'pageview')
})
}
}
if (
window.doNotTrack ||
navigator.doNotTrack ||
navigator.msDoNotTrack ||
(window.external && 'msTrackingProtectionEnabled' in window.external)
) {
// DNT available
if (
window.doNotTrack === '1' ||
navigator.doNotTrack === 'yes' ||
navigator.doNotTrack === '1' ||
navigator.msDoNotTrack === '1' ||
(typeof window.external.msTrackingProtectionEnabled === 'function' &&
window.external.msTrackingProtectionEnabled())
) {
// DNT enabled
} else {
// DNT disabled
initAnalytics()
}
} else {
// DNT not supported
initAnalytics()
}
}
+15
View File
@@ -0,0 +1,15 @@
const { path } = require('@vuepress/shared-utils')
// eslint-disable-next-line default-param-last
module.exports = (options = {}, context) => ({
name: 'vuepress-plugin-ga-dnt',
define() {
const { siteConfig = {} } = context
const ga = options.ga || siteConfig.ga
const GA_ID = ga || false
return { GA_ID }
},
clientRootMixin: path.resolve(__dirname, 'clientRootMixin.js'),
enhanceAppFiles: path.resolve(__dirname, 'enhanceAppFile.js'),
})
@@ -0,0 +1,27 @@
const enhanceApp = ({ router, isServer }) => {
// we'll handle the scrolling from here, thanks
// https://dev.to/uwutrinket/fix-scroll-jump---vue-router-45ja
if (!isServer && 'scrollRestoration' in window.history) {
window.history.scrollRestoration = 'manual'
}
router.options.scrollBehavior = (to, from, savedPosition) =>
new Promise((resolve) => {
const position = savedPosition || {}
if (!savedPosition) {
if (to.hash) {
position.selector = to.hash
position.offset = {
x: 0,
}
} else {
position.x = 0
position.y = 0
}
}
router.app.$root.$once('triggerScroll', () => {
router.app.$nextTick(() => resolve(position))
})
})
}
export default enhanceApp
@@ -0,0 +1,6 @@
const path = require('path')
module.exports = {
name: 'vuepress-plugin-trigger-scroll',
enhanceAppFiles: path.resolve(__dirname, 'enhanceApp.js'),
}
+19
View File
@@ -0,0 +1,19 @@
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+162
View File
@@ -0,0 +1,162 @@
<template>
<RouterLink
v-if="isInternal"
:class="[classObject, type === 'link' && linkColor]"
:to="link"
>
{{ item.text }}
<SVGIcon
v-if="iconName"
class="cta__svg w-5 h-5"
:name="iconName"
:title="iconTitle"
/>
</RouterLink>
<a
v-else
:class="[classObject, type === 'link' && linkColor]"
:href="link"
:target="target"
:rel="rel"
>
{{ item.text }}
<SVGIcon
v-if="iconName"
class="cta__svg w-5 h-5"
:name="iconName"
:title="iconTitle"
/>
</a>
</template>
<script>
import SVGIcon from '@theme/components/base/SVGIcon.vue'
import { isExternal, isMailto, isTel, ensureExt } from '../util'
export default {
name: 'CTA',
components: { SVGIcon },
props: {
type: {
type: String,
default: 'link',
},
icon: {
type: String,
default: '',
},
iconTitle: {
type: String,
default: '',
},
iconPosition: {
type: String,
default: 'post',
},
item: {
type: Object,
required: true,
},
linkColor: {
type: String,
default: '',
},
},
computed: {
classObject() {
return {
btn: this.type !== 'link',
[this.type]: true,
[this.iconPosition]: true,
'type-cta': true,
}
},
link() {
return ensureExt(this.item.link)
},
exact() {
if (this.$site.locales) {
return Object.keys(this.$site.locales).some(
(rootLink) => rootLink === this.link
)
}
return this.link === '/'
},
iconName() {
if (this.type === 'link') {
return this.isInternal ? 'arrow-right-icon' : 'arrow-up-icon'
} else {
return this.icon
}
},
isNonHttpURI() {
return isMailto(this.link) || isTel(this.link)
},
isBlankTarget() {
return this.target === '_blank'
},
isInternal() {
return !isExternal(this.link) && !this.isBlankTarget
},
target() {
if (this.isNonHttpURI) {
return null
}
if (this.item.target) {
return this.item.target
}
return isExternal(this.link) ? '_blank' : ''
},
rel() {
if (this.isNonHttpURI) {
return null
}
if (this.item.rel) {
return this.item.rel
}
return this.isBlankTarget ? 'noopener noreferrer' : ''
},
},
}
</script>
<style scoped lang="postcss">
.btn {
@apply rounded-full inline-flex py-2 px-6 items-center;
@apply transition duration-300 ease-in-out;
}
.btn-outline {
@apply border-solid border-2 border-transparent;
background-image: linear-gradient(104.72deg, #1a74fc -4.4%, #4ef286 112.23%);
background-origin: border-box;
box-shadow: 0px 1000px 1px theme('colors.gray.light') inset;
@apply text-deepBlue;
}
.btn-outline:hover {
@apply shadow-none text-white;
}
.btn-fill {
@apply bg-webBlue text-white fill-current;
}
.btn-fill:hover {
@apply bg-deepBlue;
}
.link {
@apply inline-flex items-center;
@apply transition duration-300 ease-in-out;
}
.post {
@apply flex-row;
}
.post .cta__svg {
@apply ml-2;
}
.pre {
@apply flex-row-reverse;
}
.pre .cta__svg {
@apply mr-2;
}
</style>
@@ -0,0 +1,54 @@
<template>
<div :class="theme === 'dark' ? 'text-deepBlue' : 'text-white'">
<LazyImage v-if="image" v-bind="image" class="mb-6" />
<h3 class="type-h3 mb-3 lg:mb-6">{{ title }}</h3>
<p class="type-p1" :class="[{ 'mb-6 lg:mb-8': cta && cta.item }]">
{{ text }}
</p>
<CTA
v-if="cta && cta.item"
v-bind="cta"
:link-color="
theme === 'dark'
? 'text-inherit hover:text-webBlue'
: 'text-inherit border-b border-transparent hover:border-white'
"
/>
</div>
</template>
<script>
import CTA from '@theme/components/CTA.vue'
import LazyImage from '@theme/components/base/LazyImage.vue'
export default {
name: 'ColumnText',
components: { CTA, LazyImage },
props: {
theme: {
type: String,
default: 'dark',
},
title: {
type: String,
default: '',
},
text: {
type: String,
required: true,
},
cta: {
type: Object,
default: null,
},
image: {
type: Object,
default: null,
},
},
}
</script>
<style scoped lang="postcss">
.image--eyebrow {
max-width: 25%;
}
</style>
@@ -0,0 +1,46 @@
<template>
<div
:class="[
$page.frontmatter.slug,
{ 'relative overflow-auto': content.subnavigation },
]"
>
<Subnavigation
v-if="content.subnavigation"
v-bind="content.subnavigation"
/>
<component
:is="block.component"
v-for="(block, index) in content.body"
:key="index"
v-bind="block"
/>
</div>
</template>
<script>
// Import all components that can be rendered from frontmatter array
import Column from '@theme/components/base/Column.vue'
import Container from '@theme/components/base/Container.vue'
import Section from '@theme/components/Section.vue'
import Typography from '@theme/components/Typography.vue'
// layout compositions
export const components = {
Column,
Container,
Section,
Typography,
}
export default {
name: 'DynamicContent',
components,
props: {
content: {
type: Object,
default: () => ({}),
},
},
}
</script>
+71
View File
@@ -0,0 +1,71 @@
<template>
<footer class="footer bg-deepBlue text-white py-8 md:py-16">
<div class="grid grid-cols-12 grid-margins">
<div class="col-start-2 md:col-start-1 col-span-10 md:col-span-12">
<div class="flex flex-col md:flex-row md:items-top md:justify-between">
<div class="flex flex-col md:flex-row md:items-center mb-8 md:mb-0">
<RouterLink
class="hover:opacity-75 transition transition-opacity duration-300 ease-in-out self-start"
to="/"
>
<SVGIcon
name="logo-icon"
title="Protocol Labs"
:class-list="['w-10', 'h-10', 'fill-current']"
/>
</RouterLink>
<ul class="flex flex-col md:flex-row mt-8 md:mt-0 md:ml-24">
<li
v-for="(item, index) in footerLinks"
:key="'link-' + index"
class="md:mr-10 last:mr-0"
:class="[{ 'mb-4': item.children && item.children.length }]"
>
<NavLink
:item="item"
class="type-p3 hover:opacity-75 transition transition-opacity duration-300 ease-in-out font-semibold"
/>
<ul
v-if="item.children && item.children.length"
class="mt-4 mb-4"
>
<li
v-for="(childItem, childIndex) in item.children"
:key="'link-child' + childIndex"
class="mb-2 last:mb-0"
>
<NavLink
:item="childItem"
class="type-p4 hover:opacity-75 transition transition-opacity duration-300 ease-in-out"
/>
</li>
</ul>
</li>
</ul>
</div>
<SocialLinks class="flex items-center" />
</div>
<div
v-if="$site.themeConfig.footerLegal"
class="flex justify-end type-p4 pt-16"
>
<p v-html="$site.themeConfig.footerLegal"></p>
</div>
</div>
</div>
</footer>
</template>
<script>
import NavLink from '@theme/components/NavLink.vue'
import SocialLinks from '@theme/components/SocialLinks'
import SVGIcon from '@theme/components/base/SVGIcon'
export default {
name: 'Footer',
components: { NavLink, SocialLinks, SVGIcon },
computed: {
footerLinks() {
return this.$site.themeConfig.footerLinks
},
},
}
</script>
@@ -0,0 +1,30 @@
<template>
<RouterLink
v-if="isInternal"
:to="link"
:exact="exact"
@click.native="handleAnchorClick"
>
{{ item.text }}
</RouterLink>
<a v-else :href="link" :target="target" :rel="rel">
{{ item.text }}
<OutboundLink v-if="isBlankTarget" />
</a>
</template>
<script>
import link from '@theme/components/mixins/link'
export default {
name: 'NavLink',
mixins: [link],
props: {
item: {
type: Object,
required: true,
},
},
}
</script>
@@ -0,0 +1,32 @@
<template>
<div>
<a
class="rss-link flex items-center text-gray-dark transition-filter duration-700 ease-in-out"
href="/index.xml"
rel="noopener noreferrer"
>
<SVGIcon
class="rss-icon w-6 h-6 fill-current mr-2"
name="rss"
title="rss"
/>
</a>
</div>
</template>
<script>
import SVGIcon from '@theme/components/base/SVGIcon'
export default {
name: 'RSSSubscription',
components: { SVGIcon },
}
</script>
<style lang="postcss" scoped>
.rss-link {
filter: grayscale(1);
&:hover {
filter: grayscale(0);
}
}
</style>
+214
View File
@@ -0,0 +1,214 @@
<template>
<div :class="['section', mergedTheme.background]">
<div class="grid grid-margins grid-cols-12" :class="mergedTheme.grid">
<div v-if="background.type === 'video'" class="absolute inset-0 z-0">
<BackgroundVideo
class="w-full h-full"
v-bind="background"
class-list="object-cover w-full h-full"
/>
</div>
<div v-if="background.type === 'image'" class="absolute inset-0 z-0">
<LazyImage
:class="[
'w-full h-full',
{ 'hidden md:block': background.mobileImg },
]"
v-bind="background.img"
:img-class="['w-full h-full', background.size, background.position]"
/>
<LazyImage
v-if="background.mobileImg"
class="w-full h-full md:hidden"
v-bind="background.mobileImg"
:img-class="['w-full h-full', background.size, background.position]"
/>
</div>
<div
:class="[
'z-10',
mergedTheme.content,
extendedPadding
? ['py-20 lg:pt-240px lg:pb-120px']
: ['py-120px lg:py-140px'],
inset ? 'col-span-10 col-start-2' : 'col-span-12',
]"
>
<div v-if="title" v-transition class="grid grid-cols-12 z-10">
<transition name="slide" appear>
<h2
:class="['col-span-12 section-title', mergedTheme.text]"
:itemprop="mergedTheme.textMeta"
>
<span
v-for="(t, i) in splitTitle"
:key="i"
class="anim"
:style="{
'animation-delay': i * 0.06 + 's',
}"
v-html="t"
/>
</h2>
</transition>
</div>
<slot></slot>
<div v-if="children">
<div v-for="(child, index) in children" :key="index" class="z-10">
<hr
:class="[
children.length > 2 && index === 0 ? 'my-10' : 'my-10 lg:mb-16',
mergedTheme.hr,
{ hidden: !title && index === 0 },
{ hidden: child.hideDivider },
]"
/>
<component :is="child.component" v-bind="child"></component>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import BackgroundVideo from '@theme/components/base/BackgroundVideo.vue'
import ColumnText from '@theme/components/ColumnText.vue'
import Section from '@theme/components/Section.vue'
import Column from '@theme/components/base/Column.vue'
import Container from '@theme/components/base/Container.vue'
import LazyImage from '@theme/components/base/LazyImage.vue'
import TextBlock from '@theme/components/TextBlock.vue'
export default {
name: 'Section',
components: {
BackgroundVideo,
ColumnText,
Column,
Container,
LazyImage,
Section,
TextBlock,
},
props: {
background: {
type: Object,
default: function () {
return { type: 'gradient', gradient: 'bg-gradient-1' }
},
},
children: {
type: Array,
default: function () {
return []
},
},
extendedPadding: {
type: Boolean,
default: false,
},
inset: {
type: Boolean,
default: false,
},
title: {
type: String,
default: null,
},
theme: {
type: Object,
default: () => ({}),
},
},
computed: {
splitTitle() {
return this.title.split(' ').map((s) => s + '&nbsp;')
},
mergedTheme() {
switch (this.background.type) {
case 'gradient': {
return {
...{
background: this.background.gradient,
content: '',
text: 'text-white type-h1 lg:col-span-7',
hr: 'hr-gradient',
},
...this.theme,
}
}
case 'transparent': {
return {
...{
background: 'bg-transparent',
content: '',
text: 'text-deepBlue type-h1 lg:col-span-7',
hr: 'hr-gradient',
},
...this.theme,
}
}
case 'image': {
return {
...{
background: 'relative',
content: '',
text: 'text-white type-h1 lg:col-span-7',
hr: 'hr-transparent',
},
...this.theme,
}
}
case 'video': {
return {
...{
background:
'relative bg-gradient-2 video-min-height flex items-center',
content: 'relative pointer-events-none',
text: 'text-white type-h1 lg:col-span-7',
hr: 'hr-transparent',
},
...this.theme,
}
}
default: {
return {
...{
background: '',
content: '',
text: '',
hr: '',
},
...this.theme,
}
}
}
},
},
}
</script>
<style lang="postcss">
@keyframes translate-slide-up {
0% {
transform: translateY(25%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.section-title .anim {
display: inline-block;
float: left;
animation: translate-slide-up 0.75s cubic-bezier(0.5, 0, 0, 1) both;
}
@screen lg {
.video-min-height {
min-height: 40vh;
}
}
</style>
@@ -0,0 +1,30 @@
<template>
<div>
<a
v-for="(link, index) in socialLinks"
:key="index"
class="mr-5"
:href="link.link"
target="_blank"
rel="noopener noreferrer"
>
<SVGIcon
class="w-8 h-8 fill-current hover:opacity-75 transition transition-opacity duration-300 ease-in-out"
:name="link.icon"
:title="link.text"
/>
</a>
</div>
</template>
<script>
import SVGIcon from '@theme/components/base/SVGIcon'
export default {
name: 'SocialLinks',
components: { SVGIcon },
computed: {
socialLinks() {
return this.$site.themeConfig.socialLinks
},
},
}
</script>
@@ -0,0 +1,53 @@
<template>
<div :class="[theme === 'dark' ? 'text-deepBlue' : 'text-white']">
<LazyImage v-if="image" class="image--eyebrow mb-8" v-bind="image" />
<p v-else-if="eyebrow" class="type-p3 mb-4">{{ eyebrow }}</p>
<p class="type-h4 mb-8" v-html="text" />
<CTA
v-if="cta && cta.item"
link-color="text-white border-b border-transparent hover:border-white"
:icon="cta.icon"
:icon-position="cta.iconPosition"
:item="cta.item"
:type="theme === 'dark' ? 'btn-outline' : 'link'"
/>
</div>
</template>
<script>
import CTA from '@theme/components/CTA.vue'
import LazyImage from '@theme/components/base/LazyImage.vue'
export default {
name: 'TextBlock',
components: { CTA, LazyImage },
props: {
eyebrow: {
type: String,
default: null,
},
text: {
type: String,
required: true,
},
theme: {
type: String,
default: 'dark',
},
cta: {
type: Object,
default: null,
},
image: {
type: Object,
default: null,
},
},
}
</script>
<style scoped lang="postcss">
@screen md {
.image--eyebrow {
max-width: 25%;
}
}
</style>
@@ -0,0 +1,83 @@
<template>
<div class="type-p1 grid-margins">
<h2 class="type-h2 mt-12 mb-12">Typography</h2>
<div class="grid md:grid-cols-4 gap-8 mt-12">
<div>
<h1 class="type-h1">Type style 1</h1>
<h2 class="type-h2">Type style 2</h2>
<h3 class="type-h3">Type style 3</h3>
<h4 class="type-h4">Type style 4</h4>
<h5 class="type-h5 mb-12">Type style 5</h5>
</div>
<ul class="list-inside list-disc">
<li>Unordered list items</li>
<li>Unordered list items</li>
<li>Unordered list items</li>
</ul>
<ol class="list-inside list-decimal">
<li>Ordered list items</li>
<li>Ordered list items</li>
<li>Ordered list items</li>
</ol>
</div>
<div class="grid md:grid-cols-4 gap-8">
<div>
<h3 class="type-h5">P1</h3>
<p class="type-p1">
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
<a>Soluta quibusdam aut incidunt tempore</a> assumenda eos commodi
quia ipsam eveniet perferendis delectus, magni praesentium quas.
Debitis mollitia quas perspiciatis vel quidem.
</p>
</div>
<div>
<h3 class="type-h5">P2</h3>
<p class="type-p2">
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
<a>Soluta quibusdam aut incidunt tempore</a> assumenda eos commodi
quia ipsam eveniet perferendis delectus, magni praesentium quas.
Debitis mollitia quas perspiciatis vel quidem.
</p>
</div>
<div>
<h3 class="type-h5">P3</h3>
<p class="type-p3">
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
<a>Soluta quibusdam aut incidunt tempore</a> assumenda eos commodi
quia ipsam eveniet perferendis delectus, magni praesentium quas.
Debitis mollitia quas perspiciatis vel quidem.
</p>
</div>
<div>
<h3 class="type-h5">P4</h3>
<p class="type-p4">
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
<a>Soluta quibusdam aut incidunt tempore</a> assumenda eos commodi
quia ipsam eveniet perferendis delectus, magni praesentium quas.
Debitis mollitia quas perspiciatis vel quidem.
</p>
</div>
</div>
<h2 class="type-h2 mt-12 mb-12">Buttons</h2>
<div class="grid md:grid-cols-4 gap-8 mb-12">
<div v-for="(button, index) in buttons" :key="index">
<CTA v-bind="button" />
</div>
</div>
</div>
</template>
<script>
import CTA from '@theme/components/CTA.vue'
export default {
name: 'Typography',
components: { CTA },
props: {
buttons: {
type: Array,
default: () => [],
},
},
}
</script>
@@ -0,0 +1,28 @@
<template>
<RouterLink v-if="isInternal" :to="link" :exact="exact">
<slot />
</RouterLink>
<a v-else :href="link" :target="target" :rel="rel">
<slot />
</a>
</template>
<script>
import link from '@theme/components/mixins/link'
export default {
name: 'UnstyledLink',
mixins: [link],
props: {
item: {
type: Object,
default: () => {},
},
to: {
type: String,
default: null,
},
},
}
</script>
@@ -0,0 +1,31 @@
<template>
<div
class="avatar rounded-full overflow-hidden flex items-center justify-center"
>
<SVGIcon
v-if="svgIcon"
:name="svgIcon"
:title="name"
:class-list="['svg-icon', 'fill-current', 'text-white', 'w-1/2', 'h-1/2']"
/>
<LazyImage
v-else-if="avatar"
class="avatar-image"
:src="avatar"
ctx="_assets/avatars/"
:alt="name"
/>
</div>
</template>
<script>
import Author from '@theme/components/mixins/Author'
import LazyImage from '@theme/components/base/LazyImage'
import SVGIcon from '@theme/components/base/SVGIcon'
export default {
name: 'Avatar',
components: { LazyImage, SVGIcon },
mixins: [Author],
}
</script>
@@ -0,0 +1,81 @@
<template>
<div class="relative">
<video
ref="videoElement"
:class="classList"
muted
preload
playsinline
loop
:poster="poster"
>
<source
v-for="(src, type, index) in srcset"
:key="index"
:src="requireAsset(src)"
:type="type"
/>
</video>
<button
aria-label="Toggle Video Play and Pause"
class="absolute z-10"
style="right: 20px; bottom: 20px"
@click="togglePlayPause"
>
<SVGIcon
:class-list="['h-4', 'w-4']"
:name="videoBeingPlayed ? 'pause' : 'play'"
:title="videoBeingPlayed ? 'Pause Video' : 'Play Video'"
/>
</button>
</div>
</template>
<script>
import SVGIcon from '@theme/components/base/SVGIcon.vue'
import requireAsset from '@theme/components/mixins/requireAsset'
let player = null
export default {
name: 'BackgroundVideo',
components: { SVGIcon },
mixins: [requireAsset],
props: {
classList: {
type: String,
default: '',
},
poster: {
type: String,
default: '',
},
srcset: {
type: Object,
required: true,
},
},
data: function () {
return { videoBeingPlayed: false }
},
mounted: function () {
player = this
if (!window.matchMedia('prefers-reduced-motion: reduce)').matches) {
this.playVideo()
}
},
methods: {
togglePlayPause: function () {
return player.videoBeingPlayed ? this.pauseVideo() : this.playVideo()
},
playVideo: function () {
this.$refs.videoElement.play()
player.videoBeingPlayed = true
},
pauseVideo: function () {
this.$refs.videoElement.pause()
player.videoBeingPlayed = false
},
},
}
</script>
@@ -0,0 +1,47 @@
<template>
<div v-transition class="column">
<div class="flex justify-between flex-wrap">
<component
:is="block.component"
v-for="(block, index) in blocks"
:key="index"
v-bind="block"
:component-index="index"
class="w-full mb-20 lg:mb-0 last:mb-0"
:class="computedClasses"
></component>
</div>
</div>
</template>
<script>
import ColumnText from '@theme/components/ColumnText.vue'
export default {
name: 'Column',
components: { ColumnText },
props: {
cols: { type: Number, default: 1 },
blocks: { type: Array, required: true },
},
computed: {
computedClasses() {
const classes = [`col-${this.cols}`]
return classes
},
},
}
</script>
<style scoped lang="postcss">
@screen lg {
.col-2 {
width: calc(40% - 32px);
}
.col-3 {
width: calc(30% - 32px);
}
}
</style>
@@ -0,0 +1,24 @@
<template>
<div class="grid-margins grid grid-cols-12 gap-2 lg:gap-8">
<component :is="child.component" v-bind="child"></component>
</div>
</template>
<script>
// Add as needed
import Column from '@theme/components/base/Column.vue'
import TextBlock from '@theme/components/TextBlock.vue'
export default {
name: 'Container',
components: {
Column,
TextBlock,
},
props: {
child: {
type: Object,
default: () => ({}),
},
},
}
</script>
@@ -0,0 +1,84 @@
<template>
<div>
<v-lazy-image
:alt="alt"
:class="imgClass"
:sizes="sizes"
:srcset="srcsetString"
:src="requireAsset(src, ctx)"
:src-placeholder="requireAsset(srcPlaceholder, ctx)"
loading="lazy"
/>
<p v-if="caption" class="type-p4 mt-3">{{ caption }}</p>
</div>
</template>
<script>
import requireAsset from '@theme/components/mixins/requireAsset'
export default {
name: 'LazyImage',
mixins: [requireAsset],
props: {
alt: {
type: String,
required: true,
},
caption: {
type: String,
default: '',
},
imgClass: {
type: [Array, String],
default: '',
},
src: {
type: String,
required: true,
},
srcset: {
type: Object,
default: null,
},
srcPlaceholder: {
type: String,
default: '',
},
sizes: {
type: String,
default: '',
},
ctx: {
type: String,
default: null,
},
},
computed: {
srcsetString() {
const srcsetObject = this.srcset
const self = this
if (srcsetObject) {
return Object.keys(srcsetObject)
.map(function (key, index) {
return `${self.requireAsset(srcsetObject[key], self.ctx)} ${key}`
})
.join(', ')
}
return ''
},
},
}
</script>
<style lang="postcss" scoped>
.v-lazy-image[src=''] {
@apply text-transparent;
visibility: hidden;
}
.v-lazy-image {
@apply opacity-0 transition-opacity duration-700;
}
.v-lazy-image-loaded {
@apply opacity-100;
}
</style>
@@ -0,0 +1,70 @@
<template>
<RouterLink v-if="isInternal" :to="link" :exact="exact">
{{ item.text }}
</RouterLink>
<a v-else :href="link" :target="target" :rel="rel">
{{ item.text }}
</a>
</template>
<script>
import { isExternal, isMailto, isTel, ensureExt } from '@theme/util'
export default {
name: 'NavLink',
props: {
item: {
type: Object,
required: true,
},
},
computed: {
link() {
return ensureExt(this.item.link)
},
exact() {
if (this.$site.locales) {
return Object.keys(this.$site.locales).some(
(rootLink) => rootLink === this.link
)
}
return this.link === '/'
},
isNonHttpURI() {
return isMailto(this.link) || isTel(this.link)
},
isBlankTarget() {
return this.target === '_blank'
},
isInternal() {
return !isExternal(this.link) && !this.isBlankTarget
},
target() {
if (this.isNonHttpURI) {
return null
}
if (this.item.target) {
return this.item.target
}
return isExternal(this.link) ? '_blank' : ''
},
rel() {
if (this.isNonHttpURI) {
return null
}
if (this.item.rel) {
return this.item.rel
}
return this.isBlankTarget ? 'noopener noreferrer' : ''
},
},
}
</script>
@@ -0,0 +1,61 @@
<template>
<svg
:class="className"
xmlns="http://www.w3.org/2000/svg"
:role="ariaHide ? 'presentation' : 'img'"
:aria-labelledby="ariaHide ? '' : `svg-title--${name}`"
:aria-hidden="ariaHide ? 'true' : 'false'"
:viewBox="icon.viewBox"
>
<title v-if="!ariaHide" :id="`svg-title--${name}`">{{ title }}</title>
<use
:xlink:href="`#${icon.id}`"
xmlns:xlink="http://www.w3.org/1999/xlink"
/>
</svg>
</template>
<script>
export default {
name: 'SvgIcon',
props: {
name: {
type: String,
required: true,
},
title: {
type: String,
default: null,
},
classList: {
type: Array,
default: () => [],
},
ariaHide: {
type: Boolean,
required: false,
},
},
computed: {
icon() {
/* eslint-disable import/no-dynamic-require */
/* eslint-disable global-require */
let icon = require(`@theme/svg-icon/${this.name}.svg`)
if (Object.prototype.hasOwnProperty.call(icon, 'default')) {
icon = icon.default
}
return icon
},
className() {
const classList = [
'svg-icon',
`svg-icon--${this.name}`,
...this.classList,
]
return classList.join(' ')
},
},
}
</script>
@@ -0,0 +1,68 @@
<template>
<transition
:name="name"
:mode="mode"
:appear="appear"
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
>
<main :key="withKey">
<slot></slot>
</main>
</transition>
</template>
<script>
const Func = {
type: Function,
default: () => {},
}
export default {
name: 'Transitions',
props: {
name: {
type: String,
default: 'fade',
},
mode: {
type: String,
default: 'out-in',
},
appear: {
type: Boolean,
default: false,
},
withKey: {
type: String,
default: null,
},
beforeEnter: Func,
enter: Func,
afterEnter: Func,
beforeLeave: Func,
leave: Func,
afterLeave: Func,
},
}
</script>
<style lang="postcss">
.fade-enter-active,
.fade-leave-active {
@apply transition-opacity duration-300 ease-in-out;
}
.fade-enter-to,
.fade-leave {
@apply opacity-100;
}
.fade-enter,
.fade-leave-to {
@apply opacity-0;
}
</style>
@@ -0,0 +1,52 @@
<template>
<div
v-if="activeTags.length"
class="border border-opacity-10 flex items-center rounded px-1 py-2"
>
<span class="p-1">
Displaying
<strong
>{{ numberOfPosts }} result{{ numberOfPosts > 1 ? 's' : '' }}</strong
>
(newest first) with tag{{ numberOfPosts > 1 ? 's' : '' }}:
</span>
<ul class="tags flex" itemprop="keywords">
<li
v-for="tag in activeTags"
:key="tag"
class="bg-gray-pale py-1 px-2 ml-1 rounded cursor-pointer hover:underline post-tag"
@click="tagClick(tag)"
>
<span class="text-blueGreen">x</span> {{ tag }}
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'ActiveTags',
props: {
numberOfPosts: {
type: Number,
required: true,
},
},
computed: {
activeTags() {
return this.$route.query.tags ? this.$route.query.tags.split(',') : []
},
},
methods: {
tagClick(tagToRemove) {
const currentPath = this.$router.history.current.path
const newQuery = {
...this.$route.query,
tags: this.activeTags.filter((tag) => tag !== tagToRemove).join(','),
}
this.$router.replace({ path: currentPath, query: newQuery })
},
},
}
</script>
@@ -0,0 +1,91 @@
<template>
<div
class="card-post group bg-gray-pale rounded overflow-hidden flex flex-col transform hover:scale-105 duration-300 ease-in-out"
itemprop="mainEntityOfPage"
:to="path"
>
<article
itemprop="blogPost"
itemscope
itemtype="https://schema.org/BlogPosting"
>
<div class="cover embed-responsive embed-responsive-og">
<router-link :to="path" class="embed-responsive-item">
<LazyImage
class="h-full p-2"
img-class="h-full"
itemprop="image"
:alt="title"
:src="`/header_images/${
frontmatter.header_image
? frontmatter.header_image
: 'blog-placeholder.png'
}`"
:ctx="regularPath"
/>
</router-link>
</div>
<div class="pt-1 pb-4 px-4 flex flex-grow flex-col">
<router-link :to="path">
<h1 class="type-h5 font-bold text-primary hover:underline">
{{ title }}
</h1>
</router-link>
<div>
<PostMeta
:author="frontmatter.author"
:date="frontmatter.date"
:tags="frontmatter.tags"
class="type-p4 text-primary"
/>
</div>
<footer class="flex-grow">
<p
v-if="frontmatter.description || frontmatter.description"
class="type-p1-serif text-primary"
itemprop="description"
>
{{ frontmatter.description || frontmatter.description }}
</p>
</footer>
</div>
</article>
</div>
</template>
<script>
import LazyImage from '@theme/components/base/LazyImage'
import PostMeta from '@theme/components/blog/PostMeta'
export default {
name: 'BlogCard',
components: { LazyImage, PostMeta },
inheritAttrs: false,
props: {
title: {
type: String,
required: true,
},
frontmatter: {
type: Object,
default: () => ({}),
validator: function (frontmatter) {
if (frontmatter.description && frontmatter.description.length > 200) {
return false
}
return true
},
},
regularPath: {
type: String,
required: true,
},
path: {
type: String,
required: true,
},
},
}
</script>
@@ -0,0 +1,35 @@
<template>
<div>
<h2>
Event organizer, content creator, or journalist? Submit an item or view
the IPFS press kit.
</h2>
<div class="flex">
Prefer your news a different way? Try our
<a class="text-blueGreen hover:underline ml-1" href="#newsletter-form"
>weekly newsletter</a
>,
<a
class="text-blueGreen hover:underline ml-1"
href="/index.xml"
rel="noopener noreferrer"
>RSS</a
>, or social media.
<RSSSubscription class="flex justify-end" />
</div>
</div>
</template>
<script>
import RSSSubscription from '@theme/components/RSSSubscription.vue'
export default {
name: 'LinksAndSocial',
components: {
RSSSubscription,
},
props: {},
computed: {},
methods: {},
}
</script>
@@ -0,0 +1,12 @@
<template>
<div id="#newsletter-form">newsletter form</div>
</template>
<script>
export default {
name: 'NewsletterForm',
props: {},
computed: {},
methods: {},
}
</script>
@@ -0,0 +1,30 @@
<template>
<div
v-if="name"
itemprop="publisher author"
itemtype="http://schema.org/Person"
itemscope
class="flex items-center"
>
<Avatar v-if="avatar || svgIcon" v-bind="$props" class="mr-2 bg-plBlack" />
<span itemprop="name" class="whitespace-no-wrap">{{ name }}</span>
</div>
</template>
<script>
import Avatar from '@theme/components/base/Avatar'
import Author from '@theme/components/mixins/Author'
export default {
name: 'PostAuthor',
components: { Avatar },
mixins: [Author],
}
</script>
<style scoped>
.avatar {
height: 2.3em;
width: 2.3em;
}
</style>
@@ -0,0 +1,61 @@
<template>
<div class="flex flex-col">
<!-- <PostAuthor v-if="author && author.name" v-bind="author" /> -->
<div v-if="date">
<time
class="italic opacity-50"
pubdate
itemprop="datePublished"
:datetime="date"
>
{{ resolvedDate }}
</time>
</div>
<ul v-if="resolvedTags.length" class="tags flex mt-1" itemprop="keywords">
<PostTag v-for="tag in resolvedTags" :key="tag" :tag="tag" />
</ul>
</div>
</template>
<script>
import dayjs from 'dayjs'
import PostTag from '@theme/components/blog/PostTag'
// import PostAuthor from '@theme/components/blog/PostAuthor'
export default {
name: 'PostMeta',
components: {
PostTag,
// PostAuthor,
},
props: {
tags: {
type: [Array, String],
default: () => [],
},
author: {
type: Object,
default: null,
},
date: {
type: String,
default: null,
},
},
computed: {
resolvedDate() {
return dayjs(this.date).format(
this.$themeConfig.dateFormat || 'YYYY-MM-DD'
)
},
resolvedTags() {
if (!this.tags || Array.isArray(this.tags)) return this.tags
return this.tags
.replace(/, /g, ',')
.split(',')
.filter((tag) => tag)
},
},
}
</script>
@@ -0,0 +1,19 @@
<template>
<li class="post-tag p-1 mr-1 bg-white text-blueGreen hover:underline rounded">
<router-link :to="{ path: '/', query: { tags: tag } }">
{{ tag }}
</router-link>
</li>
</template>
<script>
export default {
name: 'PostTag',
props: {
tag: {
type: String,
required: true,
},
},
}
</script>
@@ -0,0 +1,85 @@
<template>
<div class="flex items-center justify-between">
<div>Sort items by type:</div>
<multiselect
v-model="selectedCat"
class="flex-grow mx-2"
:options="categories"
:searchable="false"
/>
<span>and/or</span>
<multiselect
v-model="selectedTags"
class="flex-grow mx-2"
tag-placeholder="search for this text"
placeholder="Search for words or #tags"
:options="tags"
:multiple="true"
:taggable="true"
@tag="handleAddTag"
></multiselect>
<button
class="p-2 text-white bg-blueGreen rounded opacity-75 hover:opacity-75"
@click="handleSearch"
>
Search
</button>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect'
export default {
name: 'SearchCategoriesAndTags',
components: { Multiselect },
props: {
tags: {
type: Array,
default: () => ['list', 'of', 'tags', '#'],
},
categories: {
type: Array,
default: () => ['list', 'of', 'cats'],
},
},
data() {
return {
selectedCat: this.categories[0],
selectedTags: [],
searchedWords: [],
}
},
methods: {
handleSearch() {
const currentPath = this.$router.history.current.path
const tags = this.selectedTags
.filter((tag) => this.tags.includes(tag))
.join(',')
const texts = this.selectedTags
.filter((tag) => !this.tags.includes(tag))
.join(',')
const newQuery = {
...this.$route.query,
tags,
search: texts,
}
this.$router.replace({ path: currentPath, query: newQuery })
},
handleAddTag(text) {
this.selectedTags.push(text)
},
},
}
</script>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
<style>
.multiselect {
width: auto;
}
</style>
@@ -0,0 +1,32 @@
<template>
<div class="grid-margins opacity-85">
<LinksAndSocial />
<SearchCategoriesAndTags class="mt-2" :tags="tags" />
<ActiveTags :number-of-posts="numberOfPosts" class="mt-4" />
</div>
</template>
<script>
import LinksAndSocial from '@theme/components/blog/LinksAndSocial.vue'
import ActiveTags from '@theme/components/blog/ActiveTags.vue'
import SearchCategoriesAndTags from '@theme/components/blog/SearchCategoriesAndTags.vue'
export default {
name: 'SortAndFilter',
components: {
LinksAndSocial,
ActiveTags,
SearchCategoriesAndTags,
},
props: {
tags: {
type: Array,
required: true,
},
numberOfPosts: {
type: Number,
required: true,
},
},
}
</script>
@@ -0,0 +1,122 @@
const transitions = new Map([
[
'fade',
{
setup: ['transition-opacity', 'ease-in-out'],
hidden: ['opacity-0'],
visible: ['opacity-100', 'duration-700'],
threshold: 0.25,
rootMargin: '0px 0px 0px 0px',
},
],
[
'transformX',
{
setup: ['transition', 'transform', 'ease-in-out'],
hidden: ['opacity-0', 'translate-x-32'],
visible: ['opacity-100', 'translate-x-0', 'duration-700'],
threshold: 0.25,
rootMargin: '0px 0px 200px 0px',
},
],
[
'transformY',
{
setup: ['transition', 'transform', 'ease-in-out'],
hidden: ['opacity-0', 'translate-y-32'],
visible: ['opacity-100', 'translate-y-0', 'duration-700'],
threshold: 0.25,
rootMargin: '0px 0px 200px 0px',
},
],
])
function fetchTransitionClasses(transitionName, type) {
const validTransitionType =
typeof transitionName === 'string' && transitions.has(transitionName)
if (validTransitionType) {
return transitions.get(transitionName)[type]
}
return []
}
function reduceMotion() {
return window.matchMedia('prefers-reduced-motion: reduce)').matches
}
const Observer = {
bind: function (el, binding, vnode) {
if (reduceMotion()) {
return
}
const { value } = binding
const validCallback =
value && value.callback && typeof value.callback === 'function'
const transitionName = value && value.transition ? value.transition : 'fade'
const initialClasses = fetchTransitionClasses(transitionName, 'setup')
el.classList.add(...initialClasses)
const transitionDelay =
value && value.delay && typeof value.delay === 'string' ? value.delay : ''
el.style.transitionDelay = transitionDelay
const threshold = transitions.get(transitionName).threshold
const rootMargin = transitions.get(transitionName).rootMargin
vnode.tracker = new IntersectionObserver(
(entries) => {
const ratio = entries[0].intersectionRatio
if (ratio === 0.0 && !vnode.data.isVisible) {
vnode.data.isVisible = false
const visibleClasses = fetchTransitionClasses(
transitionName,
'visible'
)
const hiddenClasses = fetchTransitionClasses(transitionName, 'hidden')
el.classList.remove(...visibleClasses)
el.classList.add(...hiddenClasses)
} else if (ratio >= threshold) {
vnode.data.isVisible = true
const visibleClasses = fetchTransitionClasses(
transitionName,
'visible'
)
const hiddenClasses = fetchTransitionClasses(transitionName, 'hidden')
el.classList.remove(...hiddenClasses)
el.classList.add(...visibleClasses)
}
if (validCallback) {
value.callback({ isIntersecting: vnode.data.isVisible })
}
},
{ rootMargin: rootMargin, threshold: [0, threshold] }
)
},
inserted: function (el, binding, vnode) {
if (el && window.IntersectionObserver && !reduceMotion()) {
vnode.tracker.observe(el)
}
},
unbind: function (el, binding, vnode) {
if (el && vnode.tracker) {
vnode.tracker.disconnect()
vnode.tracker = null
}
},
}
export default {
name: 'Transition',
directive: Observer,
}
@@ -0,0 +1,22 @@
<script>
export default {
props: {
avatar: {
type: String,
default: null,
},
svgIcon: {
type: String,
default: null,
},
twitter: {
type: String,
default: '',
},
name: {
type: String,
required: true,
},
},
}
</script>
@@ -0,0 +1,66 @@
import { isExternal, isMailto, isTel, ensureExt } from '../../util'
export default {
computed: {
link() {
return ensureExt(this.to ? this.to : this.item.link)
},
exact() {
if (this.$site.locales) {
return Object.keys(this.$site.locales).some(
(rootLink) => rootLink === this.link
)
}
return this.link === '/'
},
isNonHttpURI() {
return isMailto(this.link) || isTel(this.link)
},
isBlankTarget() {
return this.target === '_blank'
},
isInternal() {
return !isExternal(this.link) && !this.isBlankTarget
},
target() {
if (!this.item || this.isNonHttpURI) {
return null
}
if (this.item.target) {
return this.item.target
}
return isExternal(this.link) ? '_blank' : ''
},
rel() {
if (!this.item || this.isNonHttpURI) {
return null
}
if (this.item.rel) {
return this.item.rel
}
return this.isBlankTarget ? 'noopener noreferrer' : ''
},
},
methods: {
// TODO: this is temporary and can be removed when router-link upgrades to v4 (https://github.com/vuejs/vue-router/issues/1668)
handleAnchorClick(e) {
// check if is not an anchor link
const hash = e.target.hash
if (hash) {
const targetElement = document.querySelector(hash)
if (targetElement) {
e.preventDefault()
window.scrollTo({ top: targetElement.offsetTop })
}
}
},
},
}
@@ -0,0 +1,27 @@
import { isExternal } from '@theme/util'
const { normalize, isAbsolute } = require('path')
export default {
methods: {
requireAsset: function (assetPath, ctx = this.$page.regularPath) {
// bail if assetPath doesn't exist
if (!assetPath) return ''
// if a url or absolute path simply return the asset link
if (isExternal(assetPath)) return assetPath
if (isAbsolute(assetPath)) return this.withBase(assetPath)
const fullPath = normalize(ctx + assetPath).replace(/^\/|\/$/g, '')
try {
return require('@source/' + fullPath)
} catch (e) {
console.error('could not load asset: ', fullPath)
return ''
}
},
withBase: function (path = '') {
const { $withBase } = this.$root
return path.charAt(0) === '/' ? $withBase.call(this, path) : path
},
},
}
+30
View File
@@ -0,0 +1,30 @@
import './styles/index.css'
import VScrollLock from 'v-scroll-lock'
import VueMq from 'vue-mq'
import { VLazyImagePlugin } from 'v-lazy-image'
import Transition from '@theme/components/directives/Transition.js'
export default ({ Vue, router, siteData }) => {
const { breakpoints } = siteData.themeConfig
/**
* We need to update the Routers push prototype method. It's a known issue where
* vue router won't broadcast a change event when navigating to the same route
* or updating parameters on a route.
* @see: https://github.com/vuejs/vue-router/issues/974
* @see: https://github.com/vuejs/vue-router/issues/3027
*/
const originalPush = router.push
router.push = function push(location, onResolve, onReject) {
if (onResolve || onReject)
return originalPush.call(this, location, onResolve, onReject)
return originalPush.call(this, location)
}
Vue.use(VScrollLock)
Vue.use(VueMq, { breakpoints })
Vue.use(VLazyImagePlugin)
Vue.directive(Transition.name, Transition.directive)
}
+33
View File
@@ -0,0 +1,33 @@
const tailwindConfig = require('./tailwind.config')
// Theme API.
module.exports = (themeConfig, ctx) => {
const { siteConfig, sourceDir, isProd } = ctx
const { breakpoints } = tailwindConfig
// add breakpoints to themeConfig
siteConfig.themeConfig = { ...themeConfig, breakpoints }
const purge = {
enabled: isProd,
content: [
`${sourceDir}/.vuepress/**/*.{vue,js,html,css,styl}`,
`${sourceDir}/**/*.md`,
],
}
const plugins = [
require('tailwindcss')({ ...tailwindConfig, purge }),
require('postcss-nested'),
require('autoprefixer'),
]
/**
* Merge in the site's purgecss config
*/
siteConfig.postcss = { ...(siteConfig.postcss || {}), plugins }
return {
globalLayout: './layouts/GlobalLayout',
}
}
+22
View File
@@ -0,0 +1,22 @@
<template>
<Layout>
<div>
<div>
<h1>404</h1>
<blockquote>Nothing to see here.</blockquote>
<RouterLink to="/">Take me home.</RouterLink>
</div>
</div>
</Layout>
</template>
<script>
import Layout from '@theme/layouts/Layout.vue'
export default {
name: 'NotFound',
components: {
Layout,
},
}
</script>
+112
View File
@@ -0,0 +1,112 @@
<template>
<Layout>
<div class="bg-gradient-6 py-20 text-white">
<div class="grid-margins">
<h1 class="type-h1">
{{ $frontmatter.description }}
</h1>
<h2 class="mt-8 pr-40 type-h4">
All the up-to-date IPFS info you need in one place, from blog posts
and release notes to videos, tutorials, news coverage, and events.
</h2>
</div>
</div>
<div class="pt-8 bg-white">
<SortAndFilter :number-of-posts="publicPages.length" :tags="tags" />
<div
class="grid-margins pt-8 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8"
itemscope
itemtype="http://schema.org/Blog"
>
<Card
v-for="page in publicPages"
:key="page.key"
class="mb-4"
v-bind="page"
/>
</div>
<div>
<router-link v-if="$pagination.hasPrev" :to="$pagination.prevLink"
>Prev</router-link
>
<router-link v-if="$pagination.hasNext" :to="$pagination.nextLink"
>Next</router-link
>
</div>
<NewsletterForm />
</div>
</Layout>
</template>
<script>
import Layout from '@theme/layouts/Layout.vue'
import Card from '@theme/components/blog/Card'
import SortAndFilter from '@theme/components/blog/SortAndFilter'
import NewsletterForm from '@theme/components/blog/NewsletterForm'
import { getTags } from '@theme/util/tagUtils'
export default {
name: 'BlogIndex',
components: {
Card,
Layout,
SortAndFilter,
NewsletterForm,
},
data: function () {
return {
numberOfPagesToShow: 20,
delayValues: [0, 0.15, 0.3],
tags: [],
}
},
computed: {
activeTags() {
return (this.$route.query.tags || '').split(',')
},
searchedText() {
return (this.$route.query.search || '').split(',')
},
publicPages: function () {
console.log({ tags: this.activeTags, text: this.searchedText })
return this.$pagination.pages.filter((page) => {
for (let i = 0; i < this.activeTags.length; i++) {
if (
!page.frontmatter.tags ||
!page.frontmatter.tags.includes(this.activeTags[i])
) {
return false
}
}
for (let i = 0; i < this.searchedText.length; i++) {
if (
!page.frontmatter.title
.toLocaleLowerCase()
.includes(this.searchedText[i].toLocaleLowerCase())
) {
return false
}
}
return (
page.frontmatter &&
(page.frontmatter.sitemap ? !page.frontmatter.sitemap.exclude : true)
)
})
},
},
mounted() {
this.tags = getTags(this.publicPages)
},
methods: {
delayVal: function () {
this.current =
this.current < this.delayValues.length - 1 ? this.current : -1
return this.delayValues[++this.current]
},
},
}
</script>
+81
View File
@@ -0,0 +1,81 @@
<template>
<Layout>
<article itemscope itemtype="https://schema.org/BlogPosting">
<Section
:title="$page.title"
:background="{
type: 'gradient',
gradient: 'bg-gradient-2',
}"
:theme="{
grid: 'max-w-4xl lg:mx-auto',
text: 'text-white type-h1 lg:col-span-10',
textMeta: 'name headline',
}"
:component-index="0"
><PostMeta
:author="$frontmatter.author"
:date="$frontmatter.date"
:tags="$frontmatter.tags"
class="type-p1 text-white my-4"
/>
</Section>
<div class="max-w-4xl lg:mx-auto">
<div v-if="$frontmatter.image" class="blog type-rich my-12">
<LazyImage
:alt="$page.title"
src-placeholder="/images/blog/og/default.png"
:src="$frontmatter.image"
/>
</div>
<Content itemprop="articleBody" class="blog type-rich my-10" />
<RSSSubscription class="max-w-3xl mb-10 mx-5 lg:mx-auto" />
</div>
</article>
</Layout>
</template>
<script>
import Layout from '@theme/layouts/Layout.vue'
import Section from '@theme/components/Section.vue'
import LazyImage from '@theme/components/base/LazyImage'
import RSSSubscription from '@theme/components/RSSSubscription.vue'
import PostMeta from '@theme/components/blog/PostMeta'
export default {
name: 'BlogPost',
components: {
Layout,
Section,
LazyImage,
PostMeta,
RSSSubscription,
},
}
</script>
<style lang="postcss">
.blog > *:not(.expand) {
@apply max-w-3xl mx-5;
@screen lg {
@apply mx-auto;
}
}
.blog > .expand {
@apply w-full;
> *,
> p > * {
@apply w-full;
}
}
/*
TODO: find a better way to calculate this
when a responsive ratio has a max-width
*/
@screen lg {
.blog .embed-responsive-16by9 {
padding-bottom: 43.25%;
}
}
</style>
@@ -0,0 +1,39 @@
<template>
<Layout>
<Section
title="Pages"
:component-index="0"
:background="{ type: 'gradient', gradient: 'bg-gradient-2' }"
/>
<div class="container grid-margins py-10 type-rich">
<ul>
<li v-for="page in $pagination.pages" :key="page.title">
<router-link class="page-link" :to="page.path">{{
page.title
}}</router-link>
</li>
</ul>
<div>
<router-link v-if="$pagination.hasPrev" :to="$pagination.prevLink"
>Prev</router-link
>
<router-link v-if="$pagination.hasNext" :to="$pagination.nextLink"
>Next</router-link
>
</div>
</div>
</Layout>
</template>
<script>
import Layout from '@theme/layouts/Layout.vue'
import Section from '@theme/components/Section.vue'
export default {
name: 'DirectoryPagination',
components: {
Layout,
Section,
},
}
</script>
@@ -0,0 +1,60 @@
<template>
<main>
<Transition :with-key="$page.key" appear :after-leave="leaveScroll">
<component :is="layout" />
</Transition>
</main>
</template>
<script>
import Vue from 'vue'
import { setGlobalInfo } from '@app/util'
import Transition from '@theme/components/base/Transitions.vue'
import Footer from '@theme/components/Footer.vue'
export default {
name: 'GlobalLayout',
components: {
Footer,
Transition,
},
computed: {
layout() {
const layout = this.getLayout()
setGlobalInfo('layout', layout)
return Vue.component(layout)
},
},
methods: {
leaveScroll() {
// eslint-disable-next-line vue/custom-event-name-casing
this.$root.$emit('triggerScroll')
},
shouldDisplay(name) {
const { display } = this.$page.frontmatter
return display
? display[name] !== undefined
? display[name]
: true
: true
},
getLayout() {
if (this.$page.path) {
const { layout } = this.$page.frontmatter
if (
layout &&
(this.$vuepress.getLayoutAsyncComponent(layout) ||
this.$vuepress.getVueComponent(layout))
) {
return layout
}
return 'Layout'
}
return 'NotFound'
},
},
}
</script>
+22
View File
@@ -0,0 +1,22 @@
<template>
<div>
<slot name="header"></slot>
<DynamicContent
v-if="$page.frontmatter.body"
:content="$page.frontmatter"
/>
<slot></slot>
<slot name="footer"></slot>
</div>
</template>
<script>
import DynamicContent from '@theme/components/DynamicContent.vue'
export default {
name: 'Layout',
components: {
DynamicContent,
},
}
</script>
+27
View File
@@ -0,0 +1,27 @@
<template>
<div>
<Section
:title="$page.title"
:component-index="0"
:background="{ type: 'gradient', gradient: 'bg-gradient-4' }"
/>
<div class="grid-margins">
<Content class="simple-page type-rich my-10 max-w-3xl" />
</div>
</div>
</template>
<script>
import Section from '@theme/components/Section'
export default {
components: {
Section,
},
}
</script>
<style lang="postcss">
.simple-page.type-rich blockquote {
@apply text-lg leading-normal pl-6;
}
</style>
@@ -0,0 +1,25 @@
<template>
<div>
<div v-for="(block, index) in $page.frontmatter.body" :key="index">
<h1 v-if="block.showTitle" class="type-h2 mt-12 mb-12 grid-margins">
{{ block.component }}
</h1>
<component
:is="block.component"
v-bind="block"
:component-index="index"
></component>
</div>
</div>
</template>
<script>
import { components } from '@theme/components/DynamicContent.vue'
export default {
name: 'StyleGuide',
components: {
...components,
},
}
</script>
+22
View File
@@ -0,0 +1,22 @@
<template>
<Layout>
<ul>
<li v-for="tag in $tag.list" :key="tag.name">
<router-link class="page-link" :to="tag.path">{{
tag.name
}}</router-link>
</li>
</ul>
</Layout>
</template>
<script>
import Layout from '@theme/layouts/Layout.vue'
export default {
name: 'TagIndex',
components: {
Layout,
},
}
</script>
+22
View File
@@ -0,0 +1,22 @@
<template>
<Layout>
<ul>
<li v-for="tag in $tag.list" :key="tag.name">
<router-link class="page-link" :to="tag.path">{{
tag.name
}}</router-link>
</li>
</ul>
</Layout>
</template>
<script>
import Layout from '@theme/layouts/Layout.vue'
export default {
name: 'TagItem',
components: {
Layout,
},
}
</script>
@@ -0,0 +1,2 @@
build
node_modules
@@ -0,0 +1 @@
<svg fill="none" height="128" viewBox="0 0 128 128" width="128" xmlns="http://www.w3.org/2000/svg"><g fill="#fff"><path d="m30.05.285683-24.91475 14.374417c-.70848.421-.72816 1.4635 0 1.8845l58.42955 34.4423c.6495.3809 1.4564.3809 2.1058 0l24.9344-14.4144c.7085-.421.7085-1.4635 0-1.8845l-58.4493-34.402317c-.6494-.3809107-1.4563-.3809107-2.1057 0z"/><path d="m96.666.526725-24.9935 14.414475c-.7085.421-.7282 1.4635 0 1.8845l25.2296 14.8555c.6495.3809 1.4563.3809 2.1058.0201l25.0131-14.3944c.709-.4211.728-1.4635 0-1.8846l-25.2493-14.875527c-.6494-.38091-1.4563-.400958-2.1057-.020048z"/><path d="m67.09 58.4854-.1378 29.3502c0 .822.866 1.3632 1.5744.9422l25.4265-14.655c.3345-.2005.5313-.5413.5313-.9423l.1378-29.9716c0-.822-.8659-1.3633-1.5744-.9423l-24.8754 14.3343c-.6495.401-1.0627 1.1026-1.0824 1.8845z"/><path d="m101.471 37.3944 24.954-14.3743c.709-.401 1.594.1203 1.575.9422l-.315 68.4237c0 .7818-.413 1.4835-1.083 1.8644l-58.2521 33.6006c-.7085.401-1.5941-.121-1.5744-.942l.1378-29.3507c0-.7818.4133-1.4835 1.0824-1.8644l31.2517-17.9831c.6691-.3809 1.0826-1.1026 1.0826-1.8845l.078-36.5473c0-.7819.414-1.4836 1.063-1.8846z"/><path d="m61.4209 58.3442-.2558 29.9717c0 .6215-.6691 1.0024-1.2005.7017l-30.6416-18.0231c-.7084-.421-1.594.1002-1.594.9222l-.1575 35.3043c0 .822-.8856 1.343-1.594.922l-24.91479-14.6748c-.669112-.3809-1.06271-1.1026-1.06271-1.8645l.314878-68.6441c0-.822.885592-1.3432 1.594072-.9222l58.44925 34.4424c.6691.3608 1.0824 1.0825 1.0627 1.8644z"/><path d="m35.0494 80.7983 25.0525 14.7753c.6494.3809 1.0627 1.1027 1.0627 1.8845l-.1575 29.4709c0 .822-.8856 1.343-1.594.922l-25.0525-14.776c-.6495-.38-1.0628-1.102-1.0628-1.884l.1575-29.4505c0-.842.8856-1.3632 1.5941-.9422z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1 @@
<svg fill="none" height="128" viewBox="0 0 128 128" width="128" xmlns="http://www.w3.org/2000/svg"><g fill="#16161f"><path d="m30.05.285683-24.91475 14.374417c-.70848.421-.72816 1.4635 0 1.8845l58.42955 34.4423c.6495.3809 1.4564.3809 2.1058 0l24.9344-14.4144c.7085-.421.7085-1.4635 0-1.8845l-58.4493-34.402317c-.6494-.3809107-1.4563-.3809107-2.1057 0z"/><path d="m96.666.526725-24.9935 14.414475c-.7085.421-.7282 1.4635 0 1.8845l25.2296 14.8555c.6495.3809 1.4563.3809 2.1058.0201l25.0131-14.3944c.709-.4211.728-1.4635 0-1.8846l-25.2493-14.875527c-.6494-.38091-1.4563-.400958-2.1057-.020048z"/><path d="m67.09 58.4854-.1378 29.3502c0 .822.866 1.3632 1.5744.9422l25.4265-14.655c.3345-.2005.5313-.5413.5313-.9423l.1378-29.9716c0-.822-.8659-1.3633-1.5744-.9423l-24.8754 14.3343c-.6495.401-1.0627 1.1026-1.0824 1.8845z"/><path d="m101.471 37.3944 24.954-14.3743c.709-.401 1.594.1203 1.575.9422l-.315 68.4237c0 .7818-.413 1.4835-1.083 1.8644l-58.2521 33.6006c-.7085.401-1.5941-.121-1.5744-.942l.1378-29.3507c0-.7818.4133-1.4835 1.0824-1.8644l31.2517-17.9831c.6691-.3809 1.0826-1.1026 1.0826-1.8845l.078-36.5473c0-.7819.414-1.4836 1.063-1.8846z"/><path d="m61.4209 58.3442-.2558 29.9717c0 .6215-.6691 1.0024-1.2005.7017l-30.6416-18.0231c-.7084-.421-1.594.1002-1.594.9222l-.1575 35.3043c0 .822-.8856 1.343-1.594.922l-24.91479-14.6748c-.669112-.3809-1.06271-1.1026-1.06271-1.8645l.314878-68.6441c0-.822.885592-1.3432 1.594072-.9222l58.44925 34.4424c.6691.3608 1.0824 1.0825 1.0627 1.8644z"/><path d="m35.0494 80.7983 25.0525 14.7753c.6494.3809 1.0627 1.1027 1.0627 1.8845l-.1575 29.4709c0 .822-.8856 1.343-1.594.922l-25.0525-14.776c-.6495-.38-1.0628-1.102-1.0628-1.884l.1575-29.4505c0-.842.8856-1.3632 1.5941-.9422z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,76 @@
const favicons = require('favicons')
const path = require('path')
const fs = require('fs-extra')
const source = path.resolve(__dirname, 'favicon.svg') // Source image(s). `string`, `buffer` or array of `string`
const buildDir = path.resolve(__dirname, 'build')
const configuration = {
path: '/', // Path for overriding default icons path. `string`
appName: 'Protocol Labs', // Your application's name. `string`
appShortName: 'Protocol Labs', // Your application's short_name. `string`. Optional. If not set, appName will be used
appDescription:
'Protocol Labs is building the next generation of the internet', // Your application's description. `string`
developerName: null, // Your (or your developer's) name. `string`
developerURL: null, // Your (or your developer's) URL. `string`
dir: 'auto', // Primary text direction for name, short_name, and description
lang: 'en-US', // Primary language for name and short_name
background: '#156ff7', // Background colour for flattened icons. `string`
theme_color: '#16161F', // Theme color user for example in Android's task switcher. `string`
appleStatusBarStyle: 'black-translucent', // Style for Apple status bar: "black-translucent", "default", "black". `string`
display: 'minimal-ui', // Preferred display mode: "fullscreen", "standalone", "minimal-ui" or "browser". `string`
orientation: 'any', // Default orientation: "any", "natural", "portrait" or "landscape". `string`
scope: '/', // set of URLs that the browser considers within your app
start_url: '/?source=pwa', // Start URL when launching the application from a device. `string`
version: '1.0', // Your application's version string. `string`
logging: true, // Print logs to console? `boolean`
pixel_art: false, // Keeps pixels "sharp" when scaling up, for pixel art. Only supported in offline mode.
loadManifestWithCredentials: false, // Browsers don't send cookies when fetching a manifest, enable this to fix that. `boolean`
icons: {
// Platform Options:
// - offset - offset in percentage
// - background:
// * false - use default
// * true - force use default, e.g. set background for Android icons
// * color - set background for the specified icons
// * mask - apply mask in order to create circle icon (applied by default for firefox). `boolean`
// * overlayGlow - apply glow effect after mask has been applied (applied by default for firefox). `boolean`
// * overlayShadow - apply drop shadow after mask has been applied .`boolean`
//
android: { offset: '20' }, // Create Android homescreen icon. `boolean` or `{ offset, background, mask, overlayGlow, overlayShadow }`
appleIcon: { offset: '20' }, // Create Apple touch icons. `boolean` or `{ offset, background, mask, overlayGlow, overlayShadow }`
appleStartup: false, // Create Apple startup images. `boolean` or `{ offset, background, mask, overlayGlow, overlayShadow }`
coast: false, // Create Opera Coast icon. `boolean` or `{ offset, background, mask, overlayGlow, overlayShadow }`
favicons: true, // Create regular favicons. `boolean` or `{ offset, background, mask, overlayGlow, overlayShadow }`
firefox: false, // Create Firefox OS icons. `boolean` or `{ offset, background, mask, overlayGlow, overlayShadow }`
windows: false, // Create Windows 8 tile icons. `boolean` or `{ offset, background, mask, overlayGlow, overlayShadow }`
yandex: false, // Create Yandex browser icon. `boolean` or `{ offset, background, mask, overlayGlow, overlayShadow }`
},
}
const callback = function (error, response) {
if (error) {
console.log(error.message) // Error description e.g. "An unknown error has occurred"
return
}
response.images.forEach((image) => writeFile(image.name, image.contents))
response.files.forEach((file) => writeFile(file.name, file.contents))
writeFile('html.json', JSON.stringify(response.html))
}
var writeFile = function (fileName, content) {
fs.writeFile(path.resolve(buildDir, fileName), content, (err) => {
if (err) {
console.error(err)
}
// file written successfully
})
}
fs.emptyDir(buildDir)
.then(() => {
favicons(source, configuration, callback)
})
.catch((err) => {
console.error(err)
})
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,16 @@
{
"name": "favicons-gen",
"version": "1.0.0",
"description": "",
"main": "favicons.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Chris Waring <chris@wwaves.co> (https://wwaves.co/)",
"license": "MIT",
"dependencies": {
"favicons": "^6.0.0",
"fs-extra": "^9.0.0"
}
}
+42
View File
@@ -0,0 +1,42 @@
const path = require('path')
const fs = require('fs')
const directoryPath = path.join(__dirname, '../../public/images')
function pbcopy(data) {
const proc = require('child_process').spawn('pbcopy')
proc.stdin.write(data)
proc.stdin.end()
}
const collabs = []
let data = ''
const hexStub = (file) => {
const ext = path.extname(file)
const fileName = path.basename(file, ext)
return `
- component: Hexagon
backgroundImage:
alt: ${fileName}
src: /images/${file}
srcset:
1x: /images/${file}
2x: /images/${fileName}@2x${ext}
`
}
fs.readdir(directoryPath, function (err, files) {
if (err) {
return console.log('Unable to scan directory: ' + err)
}
files.forEach(function (file) {
if (/^collab/.test(file)) {
if (!file.includes('@2x')) {
collabs.push(file)
}
}
})
collabs.map((collab) => (data += hexStub(collab)))
pbcopy(data)
})
@@ -0,0 +1,33 @@
.embed-responsive {
position: relative;
display: block;
height: 0;
padding: 0;
overflow: hidden;
.embed-responsive-item,
iframe,
embed,
object,
video {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
}
}
.embed-responsive-16by9 {
padding-bottom: 56.25%;
}
.embed-responsive-og {
padding-bottom: 52.63%;
}
.embed-responsive-4by3 {
padding-bottom: 75%;
}
@@ -0,0 +1,35 @@
@import './typography.css';
@import './embed-responsive.css';
/* grid margins comp */
.grid-margins {
@apply mx-15px;
}
@screen md {
.grid-margins {
@apply mx-58px;
}
}
/* 1440 max width + 58px left & right padding */
@media only screen and (min-width: 1556px) {
.grid-margins {
@apply max-w-screen-xxl mx-auto;
}
}
/* hr-gradient comp */
.hr-gradient,
.hr-transparent {
height: 1px;
@apply border-0;
}
.hr-gradient {
@apply bg-gradient-3;
}
.hr-transparent {
@apply bg-white opacity-60;
}
.border-gray-30 {
border-color: rgba(142, 142, 147, 0.3);
}
@@ -0,0 +1,64 @@
/* apply custom rich type formatting */
.type-rich {
@apply text-primary;
> * + * {
margin-top: 1.5em;
}
li + li {
margin-top: 1em;
}
b,
strong {
@apply font-bold;
}
i,
em {
@apply italic;
}
ul {
@apply list-disc;
}
ol {
@apply list-decimal;
}
ul,
ol {
@apply pl-5;
}
li > ul,
li > ol {
@apply pt-5;
}
blockquote {
@apply text-black;
@apply pl-8 relative;
&:before {
content: '';
width: 4px;
@apply bg-gradient-5 h-full left-0 absolute;
}
}
a {
@apply transition duration-200;
}
a.header-anchor {
font-size: 0.85em;
margin-left: -0.87em;
padding-right: 0.23em;
margin-top: 0.125em;
@apply opacity-0 float-left;
}
a.header-anchor:hover {
@apply underline;
}
h1:hover .header-anchor,
h2:hover .header-anchor,
h3:hover .header-anchor,
h4:hover .header-anchor,
h5:hover .header-anchor,
h6:hover .header-anchor {
@apply opacity-100;
}
}
+5
View File
@@ -0,0 +1,5 @@
@import './tailwind.css';
body {
@apply bg-gray-light antialiased;
}
+4
View File
@@ -0,0 +1,4 @@
@tailwind base;
@tailwind components;
@import './components/index.css';
@tailwind utilities;
@@ -0,0 +1 @@
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12.4072 4.46973L18.1797 10.2422L12.4072 16.0146" stroke="currentColor" stroke-width="1.6"/><line x1="17.675" y1="10.285" x2="1.66" y2="10.227" stroke="currentColor" stroke-width="1.6"/></svg>

After

Width:  |  Height:  |  Size: 296 B

@@ -0,0 +1 @@
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.59766 5.08203L15.7611 5.08203L15.7611 13.2455" stroke="currentColor" stroke-width="1.6"/><line x1="15.435" y1="5.47" x2="4.069" y2="16.752" stroke="currentColor" stroke-width="1.6"/></svg>

After

Width:  |  Height:  |  Size: 295 B

+1
View File
@@ -0,0 +1 @@
<svg fill="none" height="21" viewBox="0 0 22 21" width="22" xmlns="http://www.w3.org/2000/svg"><path d="m16.6953 8.40723-5.7724 5.77247-5.77251-5.77247" stroke="#002256" stroke-width="1.6"/></svg>

After

Width:  |  Height:  |  Size: 196 B

@@ -0,0 +1 @@
<svg fill="none" height="52" viewBox="0 0 52 52" width="52" xmlns="http://www.w3.org/2000/svg"><g stroke="#fff" stroke-width="2"><path d="m15.2929 36.2929 21-21"/><path d="m15.6016 15.2934 21.1551 21.1551"/></g></svg>

After

Width:  |  Height:  |  Size: 217 B

@@ -0,0 +1 @@
<svg width="45" height="45" viewBox="0 0 45 45" fill="none" xmlns="http://www.w3.org/2000/svg"><line x1="5" y1="17" x2="40" y2="17" stroke="currentColor" stroke-width="2"/><line x1="5" y1="26" x2="40" y2="26" stroke="currentColor" stroke-width="2"/></svg>

After

Width:  |  Height:  |  Size: 255 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 34 34"><path d="M24.208 6H10.792A4.793 4.793 0 006 10.792v13.416A4.793 4.793 0 0010.792 29h13.416A4.793 4.793 0 0029 24.208V10.792A4.793 4.793 0 0024.208 6zM13.667 24.208h-2.875V13.667h2.875zM12.229 12.45c-.93 0-1.677-.757-1.677-1.687s.748-1.696 1.677-1.696 1.677.757 1.677 1.686a1.69 1.69 0 01-1.677 1.697zm12.938 11.758h-2.875v-5.366c0-3.23-3.834-2.98-3.834 0v5.366h-2.875V13.667h2.875v1.696c1.342-2.482 6.709-2.664 6.709 2.377z"/></svg>

After

Width:  |  Height:  |  Size: 492 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill-rule="evenodd" d="M20.156 5.697c-.38-.218-.378-.572 0-.788l6.526-3.746c.38-.218.997-.217 1.375 0l6.525 3.746c.38.218.378.572 0 .788l-6.525 3.747c-.38.217-.997.216-1.375 0zm7.947 6.197c0-.436.31-.968.687-1.185l6.524-3.744c.378-.218.686-.034.686.407v18.22c0 .443-.314.982-.698 1.203L19.435 35.9c-.385.222-.698.046-.698-.388v-7.493c0-.435.313-.968.688-1.183l7.989-4.585c.38-.22.688-.754.688-1.186zM7.894 30.132c0 .436-.31.613-.687.396l-6.52-3.742C.306 26.568 0 26.03 0 25.59V7.37c0-.443.315-.621.698-.4l15.867 9.105c.385.221.698.756.698 1.19v7.492c0 .436-.312.61-.69.394l-7.99-4.585c-.38-.22-.689-.038-.689.394zM25.202 10.28c.386.221.389.579.011.795l-6.527 3.747c-.38.218-1.002.214-1.386-.006L1.43 5.706c-.386-.22-.389-.578-.011-.795l6.528-3.747c.38-.218 1.002-.214 1.386.006zm1.433 9.946c0 .435-.31.967-.687 1.182l-6.524 3.745c-.378.218-.687.039-.687-.395v-7.49c0-.436.31-.968.687-1.184l6.525-3.744c.378-.218.687-.039.687.394zm-10.06 6.61c.38.217.688.75.688 1.183v7.493c0 .436-.31.612-.687.395l-6.522-3.742c-.378-.218-.686-.75-.686-1.183v-7.494c0-.436.31-.612.686-.395z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+1
View File
@@ -0,0 +1 @@
<svg fill="none" height="14" viewBox="0 0 12 14" width="12" xmlns="http://www.w3.org/2000/svg"><g fill="#fff"><rect height="13.5" rx="1" width="3.75" y=".5"/><rect height="13.5" rx="1" width="3.75" x="8.25" y=".5"/></g></svg>

After

Width:  |  Height:  |  Size: 225 B

+1
View File
@@ -0,0 +1 @@
<svg fill="none" height="15" viewBox="0 0 12 15" width="12" xmlns="http://www.w3.org/2000/svg"><path d="m10.6432 6.652c.6267.39167.6267 1.30433 0 1.696l-9.1132 5.6958c-.666048.4162-1.52999966-.0626-1.52999962-.848l.00000049-11.39155c.00000004-.78544.86395213-1.264281 1.52999913-.848001z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 308 B

+6
View File
@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="12" fill="#156FF7"/>
<path d="M8 18C9.10457 18 10 17.1046 10 16C10 14.8954 9.10457 14 8 14C6.89543 14 6 14.8954 6 16C6 17.1046 6.89543 18 8 18Z" fill="#ffffff"/>
<path d="M14 17.11C14 17.346 13.9062 17.5724 13.7393 17.7393C13.5724 17.9062 13.346 18 13.11 18C12.874 18 12.6476 17.9062 12.4807 17.7393C12.3138 17.5724 12.22 17.346 12.22 17.11C12.22 15.6964 11.6584 14.3407 10.6589 13.3411C9.65931 12.3416 8.3036 11.78 6.89 11.78C6.77312 11.78 6.65739 11.757 6.54941 11.7123C6.44143 11.6675 6.34332 11.602 6.26068 11.5193C6.17803 11.4367 6.11247 11.3386 6.06775 11.2306C6.02302 11.1226 6 11.0069 6 10.89C6 10.7731 6.02302 10.6574 6.06775 10.5494C6.11247 10.4414 6.17803 10.3433 6.26068 10.2607C6.34332 10.178 6.44143 10.1125 6.54941 10.0677C6.65739 10.023 6.77312 10 6.89 10C8.77569 10 10.5841 10.7491 11.9175 12.0825C13.2509 13.4159 14 15.2243 14 17.11Z" fill="#ffffff"/>
<path d="M18 17.08C17.9762 17.3087 17.8685 17.5205 17.6976 17.6745C17.5268 17.8285 17.305 17.9137 17.075 17.9137C16.845 17.9137 16.6232 17.8285 16.4524 17.6745C16.2815 17.5205 16.1738 17.3087 16.15 17.08C16.1474 14.6329 15.1741 12.2867 13.4437 10.5563C11.7133 8.82594 9.36713 7.85265 6.92 7.85C6.69126 7.82618 6.47945 7.71846 6.32548 7.54763C6.17151 7.3768 6.0863 7.15498 6.0863 6.925C6.0863 6.69502 6.17151 6.4732 6.32548 6.30237C6.47945 6.13154 6.69126 6.02382 6.92 6C8.37504 6 9.81584 6.28659 11.1601 6.84341C12.5044 7.40024 13.7259 8.21638 14.7547 9.24526C15.7836 10.2741 16.5998 11.4956 17.1566 12.8399C17.7134 14.1842 18 15.625 18 17.08Z" fill="#ffffff"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1 @@
<svg height="34" viewBox="0 0 34 34" width="34" xmlns="http://www.w3.org/2000/svg"><path d="m13.0553 25.6358c8.0155 0 12.4015-6.647 12.4015-12.4015 0-.187 0-.374-.0085-.561.85-.612 1.5895-1.3855 2.176-2.261-.782.3485-1.6235.578-2.5075.6885.901-.5355 1.5895-1.39403 1.921-2.41403-.8415.5015-1.7765.8585-2.771 1.054-.799-.85-1.9295-1.377-3.179-1.377-2.4055 0-4.3605 1.95503-4.3605 4.36053 0 .34.0425.6715.1105.9945-3.621-.1785-6.834-1.921-8.98449-4.55603-.374.646-.5865 1.39403-.5865 2.19303 0 1.513.7735 2.8475 1.938 3.6295-.714-.0255-1.3855-.221-1.972-.544v.0595c0 2.108 1.5045 3.876 3.49349 4.2755-.3655.102-.74799.153-1.14749.153-.2805 0-.5525-.0255-.816-.0765.5525 1.734 2.16749 2.992 4.07149 3.026-1.496 1.173-3.37449 1.87-5.41449 1.87-.3485 0-.697-.017-1.037-.0595 1.921 1.224 4.21599 1.9465 6.67249 1.9465z"/></svg>

After

Width:  |  Height:  |  Size: 821 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 35 35"><defs/><path d="M25.949 18.022c0 1.395-.175 2.787-.175 2.787s-.174 1.133-.696 1.656c-.61.696-1.395.696-1.742.696-2.352.175-5.923.175-5.923.175s-4.442 0-5.748-.175c-.349-.086-1.22-.086-1.916-.696-.523-.523-.697-1.656-.697-1.656s-.174-1.392-.174-2.787v-1.306c0-1.394.174-2.787.174-2.787s.174-1.132.697-1.655c.61-.696 1.393-.696 1.741-.696 2.352-.175 5.923-.175 5.923-.175s3.571 0 5.923.175c.347 0 1.045 0 1.742.696.522.523.696 1.655.696 1.655s.175 1.393.175 2.787v1.306zM15.67 19.591l4.616-2.352-4.616-2.44v4.792zm-11.67-2.178c0 7.404 6.009 13.413 13.412 13.413 7.403 0 13.413-6.01 13.413-13.413C30.913 10.009 24.816 4 17.413 4 10.01 4 4 10.01 4 17.413z"/></svg>

After

Width:  |  Height:  |  Size: 720 B

+242
View File
@@ -0,0 +1,242 @@
const breakpoints = { sm: 640, md: 768, lg: 1024, xl: 1280, xxl: 1440 }
const theme = {
extend: {
spacing: {
'15px': '15px',
'58px': '58px',
'120px': '120px',
'140px': '140px',
'180px': '180px',
'240px': '240px',
},
screens: Object.fromEntries(
Object.entries(breakpoints).map(([k, v]) => [k, `${v}px`])
),
letterSpacing: {
tight: '-0.01em',
loose: '0.01em',
},
lineHeight: {
120: '1.2',
125: '1.25',
130: '1.3',
140: '1.4',
150: '1.5',
},
maxWidth: {
'1/6': '16.666667%',
'1/5': '20%',
'1/4': '25%',
'1/3': '33.33%',
},
zIndex: {
'-1': '-1',
'-10': '-10',
},
minHeight: {
500: '500px',
},
rotate: {
5: '5deg',
},
fontSize: {
12: '0.75rem',
14: '0.875rem',
16: '1rem',
18: '1.125rem',
20: '1.25rem',
22: '1.375rem',
24: '1.5rem',
28: '1.75rem',
30: '1.875rem',
35: '2.1875rem',
36: '2.25rem',
50: '3.125rem',
},
opacity: {
60: '.6',
},
colors: {
webBlue: '#156FF7',
deepBlue: '#002256',
plBlack: '#16161F',
blueGreen: '#3e9096',
gray: {
dark: '#707175',
default: '#d1d1d6',
light: '#f5f6f7',
pale: '#edf0f4',
},
},
borderColor: {
'gray-dark': '#707175',
'gray-default': '#d1d1d6',
},
textColor: {
primary: '#16161F',
},
transitionDuration: {
2000: '2000ms',
},
gridTemplateColumns: {
'2-offset-left': 'repeat(1, minmax(0, 1fr) minmax(0, 2fr))',
'2-offset-right': 'repeat(1, minmax(0, 2fr) minmax(0, 1fr))',
},
},
fontFamily: {
sans: ['aileron', 'sans-serif'],
serif: ['source-serif-pro', 'serif'],
},
textStyles: (theme) => ({
h1: {
fontFamily: theme('fontFamily.sans'),
lineHeight: theme('lineHeight.120'),
fontWeight: theme('fontWeight.semibold'),
letterSpacing: theme('letterSpacing.tight'),
fontSize: theme('fontSize.35'),
'@screen sm': {
fontSize: theme('fontSize.50'),
},
},
h2: {
fontSize: theme('fontSize.30'),
lineHeight: theme('lineHeight.120'),
fontFamily: theme('fontFamily.sans'),
fontWeight: theme('fontWeight.bold'),
letterSpacing: theme('letterSpacing.tight'),
'@screen sm': {
fontSize: theme('fontSize.36'),
lineHeight: theme('lineHeight.125'),
},
},
h3: {
fontFamily: theme('fontFamily.serif'),
fontWeight: theme('fontWeight.semibold'),
fontSize: theme('fontSize.24'),
letterSpacing: theme('letterSpacing.normal'),
lineHeight: theme('lineHeight.130'),
'@screen sm': {
fontSize: theme('fontSize.30'),
},
},
h4: {
fontFamily: theme('fontFamily.serif'),
fontWeight: theme('fontWeight.normal'),
fontSize: theme('fontSize.24'),
letterSpacing: theme('letterSpacing.normal'),
lineHeight: theme('lineHeight.130'),
'@screen sm': {
fontSize: theme('fontSize.28'),
lineHeight: theme('lineHeight.140'),
},
},
h5: {
fontFamily: theme('fontFamily.sans'),
fontWeight: theme('fontWeight.normal'),
fontSize: theme('fontSize.18'),
letterSpacing: theme('letterSpacing.normal'),
lineHeight: theme('lineHeight.130'),
'@screen sm': {
fontSize: theme('fontSize.22'),
},
},
p1: {
fontFamily: theme('fontFamily.sans'),
fontWeight: theme('fontWeight.normal'),
fontSize: theme('fontSize.16'),
letterSpacing: theme('letterSpacing.normal'),
lineHeight: theme('lineHeight.130'),
'@screen sm': {
fontSize: theme('fontSize.18'),
lineHeight: theme('lineHeight.140'),
},
},
'p1-serif': {
fontFamily: theme('fontFamily.serif'),
fontWeight: theme('fontWeight.normal'),
fontSize: theme('fontSize.16'),
lineHeight: theme('lineHeight.150'),
letterSpacing: theme('letterSpacing.tight'),
'@screen sm': {
fontSize: theme('fontSize.18'),
},
},
p2: {
fontFamily: theme('fontFamily.serif'),
fontWeight: theme('fontWeight.semibold'),
fontSize: theme('fontSize.18'),
letterSpacing: theme('letterSpacing.normal'),
lineHeight: theme('lineHeight.130'),
},
p3: {
fontFamily: theme('fontFamily.sans'),
fontWeight: theme('fontWeight.normal'),
fontSize: theme('fontSize.16'),
letterSpacing: theme('letterSpacing.normal'),
lineHeight: theme('lineHeight.140'),
},
p4: {
fontFamily: theme('fontFamily.sans'),
fontWeight: theme('fontWeight.normal'),
fontSize: theme('fontSize.12'),
letterSpacing: theme('letterSpacing.normal'),
lineHeight: theme('lineHeight.150'),
},
cta: {
fontFamily: theme('fontFamily.sans'),
fontWeight: theme('fontWeight.semibold'),
fontSize: theme('fontSize.16'),
lineHeight: theme('lineHeight.130'),
letterSpacing: theme('letterSpacing.loose'),
},
link: {
fontWeight: theme('fontWeight.bold'),
color: theme('colors.webBlue'),
'&:hover': {
textDecoration: 'underline',
},
},
rich: {
extends: 'p1-serif',
h1: {
extends: 'h1',
},
h2: {
extends: 'h2',
},
h3: {
extends: 'h3',
},
h4: {
extends: 'h4',
},
h5: {
extends: 'h5',
},
h6: {
extends: 'h6',
},
a: {
extends: 'link',
},
blockquote: {
extends: 'h4',
},
},
}),
}
module.exports = {
breakpoints,
theme,
variants: {
margin: ['responsive', 'first', 'last'],
scale: ['group-hover', 'hover'],
rotate: ['group-hover'],
opacity: ['group-hover', 'responsive'],
},
plugins: [
require('tailwindcss-typography')({ componentPrefix: 'type-' }),
require('./tailwind.gradients'),
],
}
+25
View File
@@ -0,0 +1,25 @@
const plugin = require('tailwindcss/plugin')
module.exports = plugin(function ({ addUtilities }) {
const newUtilities = {
'.bg-gradient-1': {
background: 'linear-gradient(311.2deg, #1a1c49 41.59%, #367ce4 121.07%)',
},
'.bg-gradient-2': {
background: 'linear-gradient(323.03deg, #1f6ce0 20.31%, #4df185 118.99%)',
},
'.bg-gradient-3': {
background: 'linear-gradient(104.24deg, #1a74fc -4.4%, #4ef286 112.23%)',
},
'.bg-gradient-4': {
background: 'linear-gradient(294.43deg, #0819ae 10.18%, #2166cd 100%)',
},
'.bg-gradient-5': {
background: 'linear-gradient(287.44deg, #2166cd 10.18%, #0819ae 100%)',
},
'.bg-gradient-6': {
background: 'linear-gradient(to bottom,#041727 0,#062b3f 100%)',
},
}
addUtilities(newUtilities, ['responsive', 'hover'])
})
+245
View File
@@ -0,0 +1,245 @@
export const hashRE = /#.*$/
export const extRE = /\.(md|html)$/
export const endingSlashRE = /\/$/
export const outboundRE = /^[a-z]+:/i
export function normalize(path) {
return decodeURI(path).replace(hashRE, '').replace(extRE, '')
}
export function getHash(path) {
const match = path.match(hashRE)
if (match) {
return match[0]
}
}
export function isExternal(path) {
return outboundRE.test(path)
}
export function isMailto(path) {
return /^mailto:/.test(path)
}
export function isTel(path) {
return /^tel:/.test(path)
}
export function ensureExt(path) {
if (isExternal(path)) {
return path
}
const hashMatch = path.match(hashRE)
const hash = hashMatch ? hashMatch[0] : ''
const normalized = normalize(path)
if (endingSlashRE.test(normalized)) {
return path
}
return normalized + '.html' + hash
}
export function isActive(route, path) {
const routeHash = decodeURIComponent(route.hash)
const linkHash = getHash(path)
if (linkHash && routeHash !== linkHash) {
return false
}
const routePath = normalize(route.path)
const pagePath = normalize(path)
return routePath === pagePath
}
export function resolvePage(pages, rawPath, base) {
if (isExternal(rawPath)) {
return {
type: 'external',
path: rawPath,
}
}
if (base) {
rawPath = resolvePath(rawPath, base)
}
const path = normalize(rawPath)
for (let i = 0; i < pages.length; i++) {
if (normalize(pages[i].regularPath) === path) {
return Object.assign({}, pages[i], {
type: 'page',
path: ensureExt(pages[i].path),
})
}
}
console.error(
`[vuepress] No matching page found for sidebar item "${rawPath}"`
)
return {}
}
function resolvePath(relative, base, append) {
const firstChar = relative.charAt(0)
if (firstChar === '/') {
return relative
}
if (firstChar === '?' || firstChar === '#') {
return base + relative
}
const stack = base.split('/')
// remove trailing segment if:
// - not appending
// - appending to trailing slash (last segment is empty)
if (!append || !stack[stack.length - 1]) {
stack.pop()
}
// resolve relative path
const segments = relative.replace(/^\//, '').split('/')
for (let i = 0; i < segments.length; i++) {
const segment = segments[i]
if (segment === '..') {
stack.pop()
} else if (segment !== '.') {
stack.push(segment)
}
}
// ensure leading slash
if (stack[0] !== '') {
stack.unshift('')
}
return stack.join('/')
}
/**
* @param { Page } page
* @param { string } regularPath
* @param { SiteData } site
* @param { string } localePath
* @returns { SidebarGroup }
*/
export function resolveSidebarItems(page, regularPath, site, localePath) {
const { pages, themeConfig } = site
const localeConfig =
localePath && themeConfig.locales
? themeConfig.locales[localePath] || themeConfig
: themeConfig
const pageSidebarConfig =
page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar
if (pageSidebarConfig === 'auto') {
return resolveHeaders(page)
}
const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar
if (!sidebarConfig) {
return []
} else {
const { base, config } = resolveMatchingConfig(regularPath, sidebarConfig)
return config ? config.map((item) => resolveItem(item, pages, base)) : []
}
}
/**
* @param { Page } page
* @returns { SidebarGroup }
*/
function resolveHeaders(page) {
const headers = groupHeaders(page.headers || [])
return [
{
type: 'group',
collapsable: false,
title: page.title,
path: null,
children: headers.map((h) => ({
type: 'auto',
title: h.title,
basePath: page.path,
path: page.path + '#' + h.slug,
children: h.children || [],
})),
},
]
}
export function groupHeaders(headers) {
// group h3s under h2
headers = headers.map((h) => Object.assign({}, h))
let lastH2
headers.forEach((h) => {
if (h.level === 2) {
lastH2 = h
} else if (lastH2) {
;(lastH2.children || (lastH2.children = [])).push(h)
}
})
return headers.filter((h) => h.level === 2)
}
export function resolveNavLinkItem(linkItem) {
return Object.assign(linkItem, {
type: linkItem.items && linkItem.items.length ? 'links' : 'link',
})
}
/**
* @param { Route } route
* @param { Array<string|string[]> | Array<SidebarGroup> | [link: string]: SidebarConfig } config
* @returns { base: string, config: SidebarConfig }
*/
export function resolveMatchingConfig(regularPath, config) {
if (Array.isArray(config)) {
return {
base: '/',
config: config,
}
}
for (const base in config) {
if (ensureEndingSlash(regularPath).indexOf(encodeURI(base)) === 0) {
return {
base,
config: config[base],
}
}
}
return {}
}
function ensureEndingSlash(path) {
return /(\.html|\/)$/.test(path) ? path : path + '/'
}
function resolveItem(item, pages, base, groupDepth = 1) {
if (typeof item === 'string') {
return resolvePage(pages, item, base)
} else if (Array.isArray(item)) {
return Object.assign(resolvePage(pages, item[0], base), {
title: item[1],
})
} else {
if (groupDepth > 3) {
console.error('[vuepress] detected a too deep nested sidebar group.')
}
const children = item.children || []
if (children.length === 0 && item.path) {
return Object.assign(resolvePage(pages, item.path, base), {
title: item.title,
})
}
return {
type: 'group',
path: item.path,
title: item.title,
sidebarDepth: item.sidebarDepth,
children: children.map((child) =>
resolveItem(child, pages, base, groupDepth + 1)
),
collapsable: item.collapsable !== false,
}
}
}
+15
View File
@@ -0,0 +1,15 @@
export const getTags = (posts) => {
const tags = []
posts.forEach((post) => {
const postTags = post.frontmatter.tags.replaceAll(', ', ',').split(',')
for (let i = 0; i < postTags.length; i++) {
if (postTags[i] && !tags.includes(postTags[i])) {
tags.push(postTags[i])
}
}
})
return tags.sort()
}
+28
View File
@@ -0,0 +1,28 @@
// eslint-disable-next-line default-param-last
export default function trapFocus(tabItems = [], escapeCallback, e) {
const keyCode = e.keyCode || e.which
if (keyCode === 27) {
// Handles escape key
if (escapeCallback) {
escapeCallback()
}
} else if (
e.target === tabItems[tabItems.length - 1] &&
!e.shiftKey &&
keyCode === 9
) {
// Handles tabbing past the last menu item to return to menu toggle button
e.preventDefault()
tabItems[0].focus()
} else if (e.target === tabItems[0] && e.shiftKey && keyCode === 9) {
// Handles shift-tabbing past the first menu item
e.preventDefault()
tabItems[tabItems.length - 1].focus()
} else if (keyCode === 32) {
// Handles spacebar for click
// e.target.click()
}
}
+5
View File
@@ -0,0 +1,5 @@
# Hello World
Welcome to the VuePress website starter kit
{{ $site }}
+32
View File
@@ -0,0 +1,32 @@
---
date: 2015-05-05
url: /0-hello-worlds/
title: Hello Worlds
description:
author: Juan Benet
---
```sh
> echo "hello worlds" | ipfs add
added QmZ4tDuvesekSs4qM5ZBKpXiZGun7S2CYtEZRB3DYXkjGx
> ipfs cat QmZ4tDuvesekSs4qM5ZBKpXiZGun7S2CYtEZRB3DYXkjGx
hello worlds
```
Greetings Internet!
This post kicks off the official IPFS (InterPlanetary File System) Blog. This is long overdue-- the project is many months old. We will be making a series of posts explaining various aspects of IPFS, its development, our growing community, and so on. This blog will also be used to make all important announcements henceforth.
<!--more-->
You can't _yet_ "follow" the blog with ipfs, but we're working on it and expect to have it working soon. For now, you can follow one of these ways:
- HTTP: https://blog.ipfs.io
- Git: `git clone https://github.com/ipfs/blog`
- GitHub: click watch at https://github.com/ipfs/blog
- RSS: [follow RSS Feed](https://blog.ipfs.io/index.xml)
- IPFS: https://gateway.ipfs.io/ipns/blog.ipfs.io
Don't miss any InterPlanetary updates!
![](earthrise.png)
@@ -0,0 +1,61 @@
---
date: 2015-07-11
url: /1-run-ipfs-on-docker/
title: Run IPFS in a Docker container
description:
author: Kyle Drake
---
In recent years, Docker and a few other projects have redefined how we run server applications. In the future, we might be running containerized apps in our personal devices. At its core, this fast-paced improvement is a combination of good interfaces to standardize how to do things, and great tooling to make using containers easy.
The IPFS Project has many things planned for the world of containers. The most interesting is using IPFS to distribute containers hyper efficiently across data-centers and the internet. We will be discussing many of these things in upcoming posts, but first things first. This post is a quick guide for running an IPFS node directly within Docker.
The IPFS team has provided an [IPFS Docker image](https://registry.hub.docker.com/r/ipfs/go-ipfs), which is syncronized with the latest commits to [go-ipfs](https://github.com/ipfs/go-ipfs). It only takes a few commands to try it out!
```sh
> mkdir /tmp/ipfs-docker-staging
> mkdir /tmp/ipfs-docker-data
> docker run -d --name ipfs-node \
-v /tmp/ipfs-docker-staging:/export -v /tmp/ipfs-docker-data:/data/ipfs \
-p 8080:8080 -p 4001:4001 -p 127.0.0.1:5001:5001 \
ipfs/go-ipfs:latest
faa8f714398c7a1a5a29adc2aed01857b41444ed53ec11863a3136ad37c8064c
```
Port `8080` is the HTTP Gateway, which allows you to query ipfs data with your browser ([see this example](http://gateway.ipfs.io/ipfs/QmVyS3iAy7mvDA2HqQWm2aqZDcGDH3bCRLFkEutfBWNBqN/)), port `4001` is what swarm port IPFS uses to communicate with other nodes, and port `5001` is used for the local API. We bind `5001` only on `127.0.0.1` because it should not be exposed to the outside world. The `faa8f7143...` is the docker container id.
We've mounted a data and staging volume. The `data` volume is used to store the IPFS local repo (config and database), and `staging` is a directory you can use for staging files for command line usage (such as `ipfs add`). If you're only using the API, you can omit the staging directory volume. And of course, feel free to put those directories somewhere other than `/tmp`.
Now what? Your node is running. You can issue commands directly to the containerized ipfs with `docker exec <container-id> <ipfs-cmd>`. For example, you can try `ipfs swarm peers` to see who you are connected to:
```sh
# let's set $cid = <container-id> for easy access
> cid=faa8f714398c7a1a5a29adc2aed01857b41444ed53ec11863a3136ad37c8064c
> docker exec $cid ipfs swarm peers
/ip4/104.236.179.241/tcp/4001/ipfs/QmSoLpPVmHKQ4XTPdz8tjDFgdeRFkpV8JgYq8JVJ69RrZm
/ip4/128.199.219.111/tcp/4001/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu
/ip4/162.243.248.213/tcp/4001/ipfs/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm
/ip4/178.62.61.185/tcp/4001/ipfs/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3
```
And of course, you can `add` or `cat` content as usual:
```sh
> echo "hello from dockerized ipfs" >/tmp/ipfs-docker-staging/hello
> docker exec $cid ipfs add /export/hello
added QmcDge1SrsTBU8b9PBGTGYguNRnm84Kvg8axfGURxqZpR1 /export/hello
> docker exec $cid ipfs cat /ipfs/QmSvCqazpuuib8qyRyddyFemLc2qmRukLLy8YfkdRPEXoQ
hello there!
```
Your dockerized IPFS is now also running a Gateway at `http://<ip-address-of-the-computer>:8080`. You can try it out with `curl`, or with your browser:
```sh
> curl http://localhost:8080/ipfs/QmcDge1SrsTBU8b9PBGTGYguNRnm84Kvg8axfGURxqZpR1
hello from dockerized ipfs
```
[Kubernetes 1.0](http://kuberneteslaunch.com) comes out next week, so after that, we'll try using it to build a cluster of IPFS nodes that can store any kind of data and be able to retreive it from any other IPFS node. Not just with IPFS nodes in your cluster, but with everyone!
<iframe src="./ascii" style="width: 737px; height: 509px; overflow: hidden;" scrolling="no"></iframe>
<p class="powered">asciicast powered by <a href="https://asciinema.org/" target="_top">asciinema</a></p>
+101
View File
@@ -0,0 +1,101 @@
---
date: 2015-11-27
url: /3-ipscend/
tags: codec
title: ipscend - Publish static web content to IPFS
description:
author: David Dias
---
[![](img/ipscend.png)](https://github.com/diasdavid/ipscend)
[`ipscend`](https://github.com/diasdavid/ipscend) is a new tool to help developers publish their static web content to IPFS and share it easily, while keeping history and more. It is heavily inspired by previous static web content publishing tools, like GitHub Pages and surge.
## Features
Currently, `ipscend` offers a set of features, accessible through a CLI, and installable through npm (`npm i -g ipscend`), which enable a simple workflow for working in your Web page/app and publishing it to the IPFS network.
- `ipscend browse` - Opens the last published version of your application in the browser.
- `ipscend init` - Initializes your project. Asks for the folder where the web application will be available and stores an `ipscend.json` object in your current path to store all the metadata it generates, such as published versions and taken screenshots.
- `ipscend preview` - Serves your application on a local static file server, so that you can try it out before you feel ready to publish it.
- `ipscend publish` - Publishes the current state of your application to IPFS and stores a reference to it.
- `ipscend versions` - Prints out the published versions for the app and its respective timestamp.
- `ipscend screenshot` - Opens a screenshot preview of all the published versions of your app. In order to generate the screenshots, you must first run `ipscend screenshot --gen`.
![](img/ipscend-screenshot.gif)
An `ipscend.json` object for a project with some published versions will look like:
```bash
$ cat ipscend.json
{
"versions": [
{
"hash": "QmQhNMwk7fThpwRNUR3bStb1eA7aeFJaApZjDaQCnykowL",
"timestamp": "2015-11-14T14:50:10.998Z",
"snapshot": "QmNMqiKZG7gCsnaQFTqG3AUeVhA1n8byy974Yqn3qRGZcJ"
},
{
"hash": "QmSAmgQPCWjbrpbYHZQ2rVkH7a9vavubG1Jzv5CjDWrUmt",
"timestamp": "2015-11-14T14:51:00.860Z",
"snapshot": "QmcCNrn72FuHWkXtpJuUYfbH87d61qa6PSagUbLiK6VfLJ"
},
{
"hash": "QmVNgdUoBQHiBhSeDe2z8LttJaDZq7JZi17sR1SPnJmjMh",
"timestamp": "2015-11-14T14:51:24.379Z",
"snapshot": "QmP5NuGdozeWaZqEdY1zBpcupB6qQ66AWReqm4L2vJzt73"
}
],
"path": "src/public"
}
```
## Workflow
In order to get started, all you need to do is initiate your ipscend in your web page/app project:
```bash
$ ipscend init
This utility will walk you through creating a ipscend.json file.
Path of your Web Application (project)? (public) src/public
$ cat ipscend.json
{
"versions": [],
"path": "src/public"
}
```
Once you are ready to publish, run the `ipscend publish` command:
```bash
$ ipscend publish
{ hash: 'QmVNgdUoBQHiBhSeDe2z8LttJaDZq7JZi17sR1SPnJmjMh',
timestamp: Fri Nov 27 2015 10:23:37 GMT+0000 (WET) }
published src/public QmVNgdUoBQHiBhSeDe2z8LttJaDZq7JZi17sR1SPnJmjMh
$ cat ipscend.json
{
"versions": [
{
"hash": "QmVNgdUoBQHiBhSeDe2z8LttJaDZq7JZi17sR1SPnJmjMh",
"timestamp": "2015-11-27T16:23:37.971Z"
}
],
"path": "src/public"
```
Grab that hash and share it with your friends, by sending them a link to ipfs.io, appending "/ipfs/Hash" (e.g. https:/ipfs.io/ipfs/ QmVNgdUoBQHiBhSeDe2z8LttJaDZq7JZi17sR1SPnJmjMh).
If you want to use your awesome.domain.com to load your page from IPFS, you can check how to do it now at https://github.com/diasdavid/ipscend#use-ipfs-to-host-your-webpage-using-a-standard-domain-includes-cool-dns-trick.
## Awesome (FUTURE)!
`ipscend` is still in its humble beginnings. Some of the ideas and plans to build in the future include being able to:
- Extract the version from the VCS itself (https://github.com/ipfs/notes/issues/23), so that every commit can be a different working version, allowing you to test every commit in your CI.
- Roll back history, following the 'time machine' analogy.
- Update your DNS provider automatically, avoiding having to use an external tool like [`dnslink-deploy`](https://github.com/ipfs/dnslink-deploy).
- Enable reviewers to write notes.
- Take screenshots in every browser version, so that we can use the timeline to see if there was any regression at a point in time, which might happen in a specific browser.
- moaaaar :D If you have ideas or want to contribute, ipscend is fully MIT Licensed, so feel free to open a issue or PR on https://github.com/diasdavid/ipscend.
A big thank you to [Andrés Gutgon](https://github.com/andresgutgon) who made the screenshot preview look [really good](https://github.com/diasdavid/ipscend-screenshot-visualizer/pull/1)
+118
View File
@@ -0,0 +1,118 @@
---
date: 2015-12-08
url: /8-registry-mirror/
tags: modules
title: Stellar Module Management - Install your Node.js modules using IPFS
description:
author: David Dias
draft: true
---
![](img/node-interactive-logo.png)
Node.js Interactive, the first Node.js conference organized by the Linux Foundation, happened on Dec 8-9 of 2015. There were hundreds of participants, and dozens of really amazing talks divided in 3 specific tracks: backend, frontend and IoT.
I was fortunate to attend and present a project we've been developing at [Protocol Labs](https://ipn.io), that builds on on top of [IPFS, the InterPlanetary FileSystem](https://ipfs.io).
You can learn about that project in this blog post, check out the [talk slides](http://www.slideshare.net/DavidDias11/nodejs-interactive) or wait for the video recording of the talk. I will update this blog post when that happens.
## Enter registry-mirror
![](img/enter-registry-mirror.png)
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io) [![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/) [![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs)
`registry-mirror` enables distributed discovery of npm modules by fetching and caching the latest state of npm through IPNS, the InterPlanetary Naming System. With this state, a node in the network is capable of querying IPFS network for an npm module's cryptographic hash, fetching it from any peer that has it available.
`registry-mirror` is open source, MIT licensed and available at [github.com/diasdavid/registry-mirror](https://github.com/diasdavid/registry-mirror).
## Getting started
In order to get started, you must first be sure that you are running IPFS 0.4.0. IPFS 0.4.0 is not yet released, but you can already use it by compiling from source or downloading the pre-built binary.
#### Compiling from source
You can find a tutorial on how to compile and install IPFS from source at [https://github.com/ipfs/go-ipfs#build-from-source](https://github.com/ipfs/go-ipfs#build-from-source). Just make sure to change to the `dev0.4.0` branch, as 0.4.0 isn't released yet.
Please make sure you have go 1.5.2 or above installed.
#### Downloading pre-built Binary
Download the pre-built binary for your OS and Arch at [gobuilder](https://gobuilder.me/github.com/ipfs/go-ipfs/cmd/ipfs?branch=v0.4.0-dev).
#### Installing and running registry-mirror
Once you have IPFS 0.4.0 available, install registry-mirror by running the following command (you should have Node.js 4 and npm 2 or above available):
```bash
$ npm i registry-mirror -g
# ...
```
Then start your IPFS daemon, run:
```bash
$ ipfs daemon
Initializing daemon...
Swarm listening on /ip4/127.0.0.1/tcp/4001
Swarm listening on /ip4/172.19.248.69/tcp/4001
Swarm listening on /ip6/::1/tcp/4001
API server listening on /ip4/127.0.0.1/tcp/5001
Gateway (readonly) server listening on /ip4/127.0.0.1/tcp/8080
Daemon is ready
```
After, run registry-mirror daemon with the `--ipfs` option:
```bash
$ registry-mirror daemon --ipfs --port=9595
IPFS mode ON
registry-mirror [info] output dir: /npm-registry/
registry-mirror [info] listening:127.0.0.1:9595
registry-mirror [info] Updated /npm-registry to: /ipfs/QmSjG9fadu4mPdtRsQYtXhwwCBouFEPiYHtVf8f4iH6vwj
```
Now, to install a module using IPFS, you only need to set this local registry when running an `npm install`. This can be done through [config](https://docs.npmjs.com/cli/config) or a command line argument:
```bash
$ npm i bignumber --registry=http://localhost:9595
npm http request GET http://localhost:9595/bignumber
npm http 200 http://localhost:9595/bignumber
npm http fetch GET http://localhost:9595/bignumber/-/bignumber-1.1.0.tgz
npm http fetch 200 http://localhost:9595/bignumber/-/bignumber-1.1.0.tgz
/Users/david/Documents/code/ipfs/ip-npm/node-interactive
└── bignumber@1.1.0
```
## Features
`registry-mirror` itself is quite a simple application, as most of the heavy lifting is done by [IPFS](https://ipfs.io). IPFS's distributed nature affords a set of really nice features as a transport layer that `registry-mirror` leverages to create its service.
#### Find where the module lives without having to hit the backbone
With `registry-mirror`, a registry becomes a curated list of hashes. While the modules live in the network, as soon as `registry-mirror` caches this list locally (which it gets from the IPFS network), it has a list of the hashes of the modules that a user might need in the future. With this list, a user doesn't have to know of the whereabouts of a module until it needs to request it from the network.
This list is fetched and kept up to date through IPNS. This ensures secure distribution, as IPNS records and validated with the publisher's priate key.
#### Work offline/disconnected
Just like git, `registry-mirror` is able to work offline and/or in a disconnected scenario. As long as the module you are looking for exists in the network you are currently in, IPFS would be able to find it through its Peer and Content Routing (e.g. with a DHT).
#### Enable several registries to coexist
Once the notion of a registry becomes a curated list of modules available, enabling more than one registry to exist becomes simpler. This scenario can be especially interesting for private networks such as the ones within companies and organizations that don't want their modules to be publicly known and available.
#### Run only what you were looking for
Just like git, IPFS verifies the content received using cryptographic hashing, making sure it is exactly what was requested -- you can always be sure that what you are running is what you asked for.
#### Faster
By leveraging local and network caches efficiently, downloading your dependencies can be much faster as it avoids going to npm's servers or CDN all the time. This can be crucial in high latency networks or more remote areas.
## Demo Video
<iframe src="https://player.vimeo.com/video/147968322" width="500" height="281" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe> <p><a href="https://vimeo.com/147968322">registry-mirror demo</a> from <a href="https://vimeo.com/daviddias">David Dias</a> on <a href="https://vimeo.com">Vimeo</a>.</p>
## A special thanks
A very big thank you goes to [Bryan English](https://github.com/bengl) and everyone that was involved in the [discussion](https://github.com/ipfs/notes/issues/2) and contributed to make this possible.
+129
View File
@@ -0,0 +1,129 @@
---
date: 2016-02-12
url: /9-v04x-migration/
tags: gateway, bootstrap, infrastructure
title: Migrating ipfs.io from go-ipfs 0.3.x to 0.4.0
description:
author: Lars Gierth
---
Good news everyone! We'll soon release go-ipfs 0.4.0,
which contains lots of great changes and enhancements.
An upcoming post will detail the improvements of 0.4.0.
This post is about important things to be aware of when upgrading.
Users who already rely on go-ipfs or the ipfs.io services should pay special attention.
There are breaking changes
which prevent 0.4.x nodes from communicating with 0.3.x nodes.
We know breaking changes are painful. We avoid them.
In this case, the improvement makes IPFS substantially more upgradable.
It's one of those things that "should've been different" from the start.
Having the freedom to improve the core protocol is why IPFS is still in alpha phase.
Nevertheless, we take service disruption very seriously even in these early days.
We know thousands of developers use IPFS,
and hundreds of thousands of people rely on IPFS links to our gateway.
So we planned a smooth upgrade path.
The breaking change is at the wire protocol level,
which means there will be, and already are, two separate networks.
We'll call these networks v03x and v04x.
IPFS nodes built from the master branch are already part of v04x.
The Docker image `ipfs/go-ipfs` is built from master (until 0.4.0 is released)
and thus also part of v04x.
ipfs.io provides two essential services to the community, which are affected by this:
the public HTTP-to-IPFS gateway and the default bootstrappers.
We'll continue to support them for the v03x network until the **end of April 2016**.
Please note that we won't support 0.3.x with patches or new features.
All development effort is directed towards 0.4.0, and you should update as soon as possible.
- [How do I update go-ipfs to 0.4.0?](#how-do-i-update-go-ipfs-to-0-4-0-)
- [The public gateways at ipfs.io](#the-public-gateways-at-ipfs-io)
- [Solarnet: the default bootstrappers](#solarnet-the-default-bootstrappers)
- [How does this affect me?](#how-does-this-affect-me-)
## How do I update go-ipfs to 0.4.0?
Until we release 0.4.0, you can update by [building from source][ipfs-source].
After the release, you can:
- Use the brand new [ipfs-update tool][ipfs-update] to update go-ipfs **(recommended)**
- Download [the new go-ipfs binaries][ipfs-binary] and install them.
- Download [the new source][ipfs-github] and [build it yourself][ipfs-source].
Please note that installation with `go get` does not work at this time.
We are experimenting with [gx, the new IPFS-based package manager][gx].
[ipfs-update]: http://dist.ipfs.io/#ipfs-update
[ipfs-binary]: http://dist.ipfs.io/#go-ipfs
[ipfs-github]: https://github.com/ipfs/go-ipfs
[ipfs-source]: https://github.com/ipfs/go-ipfs#build-from-source
[gx]: https://github.com/whyrusleeping/gx
## The public gateways at ipfs.io
For the time being, https://ipfs.io uses both v03x and v04x to service requests.
Content available in either network will be served just fine.
Whichever responds successfully first (2xx or 3xx status code),
gets to serve its content. The other response is discarded.
If the first response is not successful (4xx/5xx, connection errors),
it is discarded and the second response is served, regardless of its status code.
We're using the [multireq proxy][multireq] to accomplish this multiplexing behaviour.
`multireq` is a new tool, so if you notice any weirdness with gateway requests,
[please let us know][infra-issues].
If you want to target a specific network, use v03x.ipfs.io or v04x.ipfs.io.
These domain names will stay around as long as the respective network is
supported by the public gateway.
All of the above also applies to the `/api` endpoint (the readonly API),
which is part of the gateway.
Expect the **v03x gateway** to be **supported until the end of April 2016.**
After that day, ipfs.io will be served by v04x exclusively,
and v03x.ipfs.io will no longer work.
[multireq]: https://github.com/whyrusleeping/multireq
[infra-issues]: https://github.com/ipfs/infrastructure/issues/
## Solarnet: the default bootstrappers
We call the 8 bootstrap nodes in go-ipfs's default config the default bootstrappers.
Their PeerIDs start with `QmSoL` (for Solarnet),
and all of them use `/tcp/4001` and/or `/udp/4002/utp`.
Use `ipfs bootstrap` to see or modify the bootstrappers currently used by your IPFS node.
In order to balance the default bootstrappers over v03x and v04x,
a few of them bind v04x to these ports, and a few bind v03x.
The respective other network is bound to `/tcp/14001` and `/udp/14002/utp`.
This means that all 8 hosts run bootstrappers for both networks,
but are available as default bootstrappers only to one.
We'll gradually shift the v03x/v04x ratio to v04x.
Expect at least **two v03x bootstrappers** to be **supported until the end of April 2016.**
## How does this affect me?
Check which version of go-ipfs you are running: `ipfs version`
If you rely on the public gateways,
we ask you to please upgrade to 0.4.0 as soon as you can.
You can subscribe [to this issue][ready-yet] to be notified when 0.4.0 is released.
If you're running an 0.3.x node, it won't be able to communicate
with any node which has updated to 0.4.x, let alone exchange data.
You can access data added by 0.4.0 nodes using the public gateway.
Likewise, data added by your 0.3.x node is still available on the public gateway.
If you're running an 0.4.0 node, all is well in the world.
You don't need to do anything.
Thank you for bearing with this important change.
We seek to provide a smooth transition for everyone,
and wish we didn't have to bother you with this at all.
[ready-yet]: https://github.com/ipfs/go-ipfs/issues/2334
+314
View File
@@ -0,0 +1,314 @@
---
date: 2016-04-07
url: /14-ipfs-0-4-0-released/
tags: modules
title: go-ipfs 0.4.0 has been released
description:
author: Kyle Drake and @whyrusleeping
---
[go-ipfs 0.4.0](http://dist.ipfs.io/#go-ipfs) has been
released! Among the many changes are a revamped implementation of the IPFS
communication protocols, increased performance, improvements to IPNS (the
Interplanetary Naming System), many bugfixes, and lots of new features to make
IPFS even more powerful.
![0.4.0](fireworks.jpg)
## The IPFS **Files API**
In 0.4.0, we've added a new feature, the Files API, available through the
subcommand: `ipfs files`. This subcommand allows a program to interact with IPFS
using familiar filesystem operations, namely: creating directories, reading, writing
and deleting files, listing out different directories, and so on.
This feature enables any application that uses a filesystem-like backend
to use IPFS for storage without changing the application logic at all.
It is used like so:
```sh
> ipfs files mkdir /cats
> ipfs files ls /
cats
> echo bar | ipfs files write --create /cats/foo
> ipfs files ls /cats
foo
> ipfs files read /cats/foo
bar
> ipfs files stat /
QmNU8HmaeRa8VtfqAoQRJhSE5Zx54vyYf2nT1bDGcYXaNv
...
# /ipfs/QmNU8HmaeRa8VtfqAoQRJhSE5Zx54vyYf2nT1bDGcYXaNv is a snapshot
# of this file system!
# You can see it locally or on the public gateway:
# https://ipfs.io/ipfs/QmNU8HmaeRa8VtfqAoQRJhSE5Zx54vyYf2nT1bDGcYXaNv
# Subsequent edits will produce a new /ipfs address for the root directory.
```
One great example of this is [ipfs-blob-store](https://github.com/ipfs/ipfs-blob-store),
an IPFS-backed storage driver that implements the
[blob-store-interface](https://github.com/maxogden/abstract-blob-store), so that any app
that uses any other blob-store storage driver (S3, IndexedDB, LevelDB, etc) can now use
IPFS. [registry-mirror](https://github.com/diasdavid/registry-mirror) uses this
approach to mirror the npm registry onto IPFS.
We are looking forward to seeing more use cases of this new convenient and powerful API.
## Why we're changing the protocol
The most important change allows IPFS implementations to use pluggable stream
multiplexers, such as [yamux](https://github.com/hashicorp/yamux),
[spdystream](https://github.com/docker/spdystream), or
[muxado](https://github.com/inconshreveable/muxado). Instead of locking IPFS
permanently into a single multiplexer that won't work for every language or
situation, this change allows each IPFS implementation to implement the
multiplexers that they choose to, and then negotiate which to use during the
initial connection handshake.
This modularity with stream muxing makes it easier for certain languages to
improve performance. For example, the Go programming language may have muxado
and yamux implementations that are really good, but many languages lack good (or
any) implementations of them. Or take Node.js, which works well with
spdy-transport, and it would be nice to take advantage of that. And
then there are options like
[multiplex](https://github.com/maxogden/multiplex), which may not have the
same performance, but are much easier to implement.
So, by supporting as many muxers as we can, we get to choose the best
multiplexers for the job. It also makes it much easier to implement the IPFS
protocols in a new language. And of course, if a better multiplexers standard
comes along, it will be easier to upgrade IPFS to support it in the future.
The same code that allows us to easily select a stream muxer is also being
used to select which IPFS sub-protocol to use for any given stream between
peers. Now, if we need to make a breaking protocol change to any of those (like
the DHT or bitswap) we can do so seamlessly, and provide easy backwards
compatibility. We won't need to "break" anything because we will be able to
have nodes run multiple protocols at the same time for compatibility.
In addition to the multiplexers changes, the protocol revamp has also improved
efficiency and performance in a few important ways, including the elimination of
a double wrapping of the length prefixer, and the removal of some unnecessary
round trips between nodes.
### Breaking changes
This release contains a **breaking change** to the network wire protocol in the
form of a major refactor and upgrade to the libp2p handshake protocol. Because
of the refactor, **all IPFS daemons earlier than 0.4.0 will not be able to
communicate with the newest version**. It is strongly recommended that everyone
running an IPFS node upgrades to the latest version as soon as possible, as
these nodes will, after a certain time, no longer be able to communicate with
the majority of the network until they are upgraded. There are instructions on
how to update below.
Refactoring the protocol is not something to be done lightly. But at this early
stage, this is necessary to ensure we have the right design for IPFS in place
for the future. It's better to improve the protocol now during this alpha stage
of the project than when there are a lot more people running nodes across a
lot of different implementations.
One of the important changes that's been made to the protocol means that _there
should never be a breaking change like this again_. This is due to a change to
allow nodes to announce the version of the protocol they are using when
connecting to other IPFS nodes. The goal is to roll any future protocol changes
into the implementations gradually, so that we can still support legacy
protocols for a period of time, making it easier to deprecate old versions over
time.
### The public gateway and bootstrappers
We provide two essential public services to the IPFS community: the public
gateway at https://ipfs.io and the default bootstrap nodes. We're making sure
that despite the breaking changes, both will continue to work with 0.4.x and
0.3.x for a while. You can read more about the details of this in an earlier
blog post: [Migrating ipfs.io from go-ipfs 0.3.x to 0.4.0](../9-v04x-migration).
We expect to keep this grace period open until the end of April 2016.
## Changes to the repo
We made a few changes to our on disk storage format (called the 'repo').
The way object pinning (`ipfs pin add`) works has also been upgraded to be much
more efficient, which will improve the overall speed of adding and downloading
IPFS data. Previously, when you pinned a file recursively, it would add a
recursive type pin to the root, and then add an indirect type pin to each child
node. This made enumeration for GC convenient, but was very slow and made a lot
of things needlessly complicated. We have switched to just adding the recursive
pin to the root object, and then doing the enumeration of child nodes when we
actually run a garbage collection.
Because of this change, you will need to run a migration (from repo version 2
to version 3). If you update with the `ipfs-update` tool, this will be done for
you automatically. If you updated manually, and did not run the migration, ipfs
will fail to run, and print a message saying that there is a mismatch in the repo
versions.
## Other improvements and fixes
In addition to a major protocol improvement and upgrade, this release adds a lot
of new functionality, performance speedups, and stability fixes that make this
the best version of IPFS to date.
This release also includes performance and usability improvements to IPNS,
which is IPFS's mutability layer. IPNS creates a link from an IPFS node's
public key to the hash of an arbitrary objects hash, in a way that is
cryptographically verifiable. We call this "publishing" a hash, and you can try
it out using the `ipfs publish` command. By allowing users to change what the
pubkey hash points to, we provide users with a single hash they can give to their
users to get the latest version of their data. This creates a seamless way to
use IPFS to verify content, and to distribute content via trustless nodes in a
smart, safe way. This brings IPFS closer to the goal of being a global
filesystem of data, that can allow everyone in the world to help serve the
world's data in a way that enriches and empowers everybody.
## How to upgrade
Depending on how you initially installed IPFS, there are several ways to
upgrade. If you installed IPFS with a pre-built binary, you can either head over
to [dist.ipfs.io](http://dist.ipfs.io/#go-ipfs) and grab the latest version
from there. Or alternatively, from the same page you can grab the `ipfs-update`
binary, and use it to perform the upgrade for you. If you installed from
source, you can simply update your git repo to the `v0.4.0` tag, run `make toolkit_upgrade && make install`.
Please upgrade your IPFS nodes as soon as you can, so you can take advantage
of the improvements!
## Changelog
This is a major release with plenty of new features and bugfixes.
It also includes breaking changes which make it incompatible with v0.3.x
on the networking layer.
- Major Changes
- Multistream
- The addition of multistream is a breaking change on the networking layer,
but gives IPFS implementations the ability to mix and match different
stream multiplexers, e.g. yamux, spdystream, or muxado.
This adds a ton of flexibility on one of the lower layers of the protocol,
and will help us avoid further breaking protocol changes in the future.
- Files API
- The new `files` command and API allow a program to interact with IPFS
using familiar filesystem operations, namely: creating directories,
reading, writing, and deleting files, listing out different directories,
and so on. This feature enables any other application that uses a
filesystem-like backend for storage, to use IPFS as its storage driver
without having change the application logic at all.
- Gx
- go-ipfs now uses [gx](https://github.com/whyrusleeping/gx) to manage its
dependencies. This means that under the hood, go-ipfs's dependencies are
backed by IPFS itself! It also means that go-ipfs is no longer installed
using `go get`. Use `make install` instead.
- New Features
- Web UI
- Update to new version which is compatible with 0.4.0. (@dignifiedquire)
- Networking
- Implement uTP transport. (@whyrusleeping)
- Allow multiple addresses per configured bootstrap node. (@whyrusleeping)
- IPNS
- Improve IPNS resolution performance. (@whyrusleeping)
- Have dnslink prefer `TXT _dnslink.example.com`, allows usage of CNAME records. (@Kubuxu)
- Prevent `ipfs name publish` when `/ipns` is mounted. (@noffle)
- Repo
- Improve performance of `ipfs add`. (@whyrusleeping)
- Add `Datastore.NoSync` config option for flatfs. (@rht)
- Implement mark-and-sweep GC. (@whyrusleeping)
- Allow for GC during `ipfs add`. (@whyrusleeping)
- Add `ipfs repo stat` command. (@tmg, @diasdavid)
- General
- Add support for HTTP OPTIONS requests. (@lidel)
- Add `ipfs diag cmds` to view active API requests (@whyrusleeping)
- Add an `IPFS_LOW_MEM` environment veriable which relaxes Bitswap's memory usage. (@whyrusleeping)
- The Docker image now lives at `ipfs/go-ipfs` and has been completely reworked. (@lgierth)
- Security fixes
- The gateway path prefix added in v0.3.10 was vulnerable to cross-site
scripting attacks. This release introduces a configurable list of allowed
path prefixes. It's called `Gateway.PathPrefixes` and takes a list of
strings, e.g. `["/blog", "/foo/bar"]`. The v0.3.x line will not receive any
further updates, so please update to v0.4.0 as soon as possible. (@lgierth)
- Incompatible Changes
- Install using `make install` instead of `go get` (@whyrusleeping)
- Rewrite pinning to store pins in IPFS objects. (@tv42)
- Bump fs-repo version to 3. (@whyrusleeping)
- Use multistream muxer (@whyrusleeping)
- The default for `--type` in `ipfs pin ls` is now `all`. (@chriscool)
- Bug Fixes
- Remove msgio double wrap. (@jbenet)
- Buffer msgio. (@whyrusleeping)
- Perform various fixes to the FUSE code. (@tv42)
- Compute `ipfs add` size in background to not stall add operation. (@whyrusleeping)
- Add option to have `ipfs add` include top-level hidden files. (@noffle)
- Fix CORS checks on the API. (@rht)
- Fix `ipfs update` error message. (@tomgg)
- Resolve paths in `ipfs pin rm` without network lookup. (@noffle)
- Detect FUSE unmounts and track mount state. (@noffle)
- Fix go1.6rc2 panic caused by CloseNotify being called from wrong goroutine. (@rwcarlsen)
- Bump DHT kvalue from 10 to 20. (@whyrusleeping)
- Put public key and IPNS entry to DHT in parallel. (@whyrusleeping)
- Fix panic in CLI argument parsing. (@whyrusleeping)
- Fix range error by using larger-than-zero-length buffer. (@noffle)
- Fix yamux hanging issue by increasing AcceptBacklog. (@whyrusleeping)
- Fix double Transport-Encoding header bug. (@whyrusleeping)
- Fix uTP panic and file descriptor leak. (@whyrusleeping)
- Tool Changes
- Add `--pin` option to `ipfs add`, which defaults to `true` and allows `--pin=false`. (@eminence)
- Add arguments to `ipfs pin ls`. (@chriscool)
- Add `dns` and `resolve` commands to read-only API. (@Kubuxu)
- Add option to display headers for `ipfs object links`. (@palkeo)
- General Codebase Changes
- Check Golang version in Makefile. (@chriscool)
- Improve Makefile. (@tomgg)
- Remove dead Jenkins CI code. (@lgierth)
- Add locking interface to blockstore. (@whyrusleeping)
- Add Merkledag FetchGraph and EnumerateChildren. (@whyrusleeping)
- Rename Lock/RLock to GCLock/PinLock. (@jbenet)
- Implement pluggable datastore types. (@tv42)
- Record datastore metrics for non-default datastores. (@tv42)
- Allow multistream to have zero-rtt stream opening. (@whyrusleeping)
- Refactor `ipnsfs` into a more generic and well tested `mfs`. (@whyrusleeping)
- Grab more peers if bucket doesn't contain enough. (@whyrusleeping)
- Use CloseNotify in gateway. (@whyrusleeping)
- Flatten multipart file transfers. (@whyrusleeping)
- Send updated DHT record fixes to peers who sent outdated records. (@whyrusleeping)
- Replace go-psutil with go-sysinfo. (@whyrusleeping)
- Use ServeContent for index.html. (@AtnNn)
- Refactor `object patch` API to not store data in URL. (@whyrusleeping)
- Use mfs for `ipfs add`. (@whyrusleeping)
- Add `Server` header to API responses. (@Kubuxu)
- Wire context directly into HTTP requests. (@rht)
- Wire context directly into GetDAG operations within GC. (@rht)
- Vendor libp2p using gx. (@whyrusleeping)
- Use gx vendored packages instead of Godeps. (@whyrusleeping)
- Simplify merkledag package interface to ease IPLD inclusion. (@mildred)
- Add default option value support to commands lib. (@whyrusleeping)
- Refactor merkledag fetching methods. (@whyrusleeping)
- Use net/url to escape paths within Web UI. (@noffle)
- Deprecated key.Pretty(). (@MichealMure)
- Documentation
- Fix and update help text for **every** `ipfs` command. (@RichardLitt)
- Change sample API origin settings from wildcard (`*`) to `example.com`. (@Kubuxu)
- Improve documentation of installation process in README. (@whyrusleeping)
- Improve windows.md. (@chriscool)
- Clarify instructions for installing from source. (@noffle)
- Make version checking more robust. (@jedahan)
- Assert the source code is located within GOPATH. (@whyrusleeping)
- Remove mentions of `/dns` from `ipfs dns` command docs. (@lgierth)
- Testing
- Refactor iptb tests. (@chriscool)
- Improve t0240 sharness test. (@chriscool)
- Make bitswap tests less flaky. (@whyrusleeping)
- Use TCP port zero for ipfs daemon in sharness tests. (@whyrusleeping)
- Improve sharness tests on AppVeyor. (@chriscool)
- Add a pause to fix timing on t0065. (@whyrusleeping)
- Add support for arbitrary TCP ports to t0060-daemon.sh. (@noffle)
- Make t0060 sharness test use TCP port zero. (@whyrusleeping)
- Randomized ipfs stress testing via randor (@dignifiedquire)
- Stress test pinning and migrations (@whyrusleeping)
+69
View File
@@ -0,0 +1,69 @@
---
date: 2016-06-01
url: /17-distributions/
tags: dist, distributions
title: IPFS distributions
description:
author: Richard Littauer
---
[![](img/screenshot.png)](https://dist.ipfs.io/)
[dist.ipfs.io](https://dist.ipfs.io/) is the new distributions page for IPFS. This is the new one-stop-shop for finding and downloading all official binaries that the IPFS Team produces.
## The IPFS Distributions Website
The distributions website itself is served by, hosted, and distributed through IPFS. The website assets and all of the binaries form one large IPFS content graph. This means that you can view and use this website through any IPFS node, even your own local ipfs node. All you need to do is to run an IPFS daemon and direct your browser to http://localhost:8080/ipns/dist.ipfs.io. Of course, you'll need to be online and connected to the internet, so you can find other ipfs nodes that have this website.
### Download Deduplication
If you download files from dist.ipfs.io using your local IPFS node, future downloads **may be** much faster. When you click to download a file, your browser will ask to download it from your local IPFS node. In turn, your IPFS node will fetch the relevant content from other nodes in the network, and return it to your browser. Your browser will place the file in your Downloads folder, or wherever you directed it to.
However, once your local IPFS node has fetched the content, it will cache it locally for some time. This makes subsequent downloads of the exact same content instantaneous! Your browser asks the IPFS node for the content; the node already has it and simply returns it, without ever having to connect to other nodes. This also means that if other IPFS nodes in your local area network try to download the file, they may be able to fetch it from your node. Once you have the content locally, this can even work while disconnected from the internet!
It may also make downloading **new** versions much faster, because different versions of large binary files often have lots of duplicated data. IPFS represents files as a Merkle DAG (a datastructure similar to a [merkle tree](https://en.wikipedia.org/wiki/Merkle_tree)), much like Git or BitTorrent. Unlike them, when IPFS imports files, it chunks them to deduplicate similar data within single files. So when you need to download a new version, you only download the parts that are new or different - this can make your future downloads faster!
## Project details
Every distribution has a section, which includes:
- The distribution name and a short description;
- The current version number and release date;
- The software license (usually MIT);
- A download button that detects your platform and automatically suggests the appropriate distribution for you;
- A grid with download links for all supported platforms (operating system and architectures);
- A `Changelog`, a link to a summary of all version changes;
- An `All Versions`, a link to view and download previous versions.
The `All Versions` link on each distribution shows directory listings for all the available versions, and a `versions` file ([example](https://dist.ipfs.io/go-ipfs/versions)). This file can be used by tools, such as [ipfs-update](https://dist.ipfs.io/#ipfs-update), to find all the available versions and download the latest.
The directory listing of each version ([example](https://dist.ipfs.io/go-ipfs/v0.3.11)) has all the platform archives (`.zip` or `.tar.gz`), a `README.md` and a `dist.json` which describe the release for humans and machines. It is meant to be easily consumed and used by tools.
The site is also used directly by [`ipfs-update`](https://github.com/ipfs/ipfs-update) to update IPFS.
## Project Prerequisites
In order to be added to the distributions page, a product must:
- Originate from the IPFS community;
- Have high-quality UX and documentation;
- Be well maintained and active.
If you think that a product should be there that isn't, get in touch.
## Future Plans
In the future, we hope to:
- Enable code signing, for progress on this subject you can check this [pull request](https://github.com/ipfs/distributions/pull/51).
- Enable closer integration with package managers.
- Add more products.
- Host more screenshots of the tools directly in the Distributions page.
- Import binaries more intelligently in order to enhance change of deduplication.
- Add the software license (usually MIT) to each distribution.
## Contribute
We welcome any and all sorts of contributions! File issues or send us patches at [ipfs/distributions](https://github.com/ipfs/distributions).
Last but not least, a huge thank you to [@dignifiedquire](https://github.com/dignifiedquire) for his amazing work with the Dist page. None of this would be possible without him.
+24
View File
@@ -0,0 +1,24 @@
---
date: 2016-07-09
url: /18-v03x-shutdown/
tags: operations, v03x, gateway, bootstrap
title: go-ipfs 0.3.x network shutdown on July 14th
description:
author: Lars Gierth
---
The [0.4.x (v04x) series][v04x] of go-ipfs has brought plenty of useful features, and it has been a great success for the whole IPFS community.
As we're getting closer to the release of go-ipfs 0.4.3, we're also getting closer to ceasing the [remaining support that we've given to the 0.3.x (v03x) series][migration] of go-ipfs. It's been a good while since we last reminded everyone that the v03x network will be discontinued, and we want to use this opportunity to make sure that nobody is taken by surprise.
We'll be **shutting down** the v03x bootstrappers and gateways on **Thursday, July 14th**. After this date, with go-ipfs 0.3.x:
- The bootstrap nodes configured by default will no longer allow you to connect.
- The public HTTP-to-IPFS gateway at [ipfs.io][gw] will no longer be able to access data from v03x networks.
We ask you to update to the latest 0.4.x version, available through [dist.ipfs.io][dist]. If you can't update just yet, you can still connect your v03x nodes to each other by using the `ipfs bootstrap` or `ipfs swarm connect` commands.
[migration]: https://ipfs.io/blog/9-v04x-migration/
[v04x]: https://ipfs.io/blog/14-ipfs-0-4-0-released/
[dist]: https://dist.ipfs.io
[gw]: https://ipfs.io
@@ -0,0 +1,44 @@
---
date: 2016-09-20
url: /19-ipfs-0-4-3-released/
tags: modules
title: go-ipfs 0.4.3 has been released
description:
author: Lars Gierth
---
[go-ipfs 0.4.3](https://dist.ipfs.io/#go-ipfs) has been released today,
and we're incredibly proud as it's the fastest and most stable IPFS ever.
Give it a try as soon as you can, we're sure you'll like it as much as we do.
## What's changed?
- Runtime performance has improved all over the place!
Bitswap, Content Routing and Peer Routing, Datastore, Blockstore, you name it.
- Memory usage has improved as Provider records are now stored on disk.
- We now use Golang 1.7, which produces smaller and faster binaries.
- Connectivity between nodes is stabler than ever,
as we fixed transport issues that were previously thought to be NAT-related.
- The go-ipfs daemon now automatically raises the limit of file descriptors if needed.
- The daemon now automatically runs fs-repo migrations.
- The daemon got new flags:
- `ipfs daemon --offline` can be used to disable all swarm networking.
- `ipfs daemon --migrate` automatically runs any pending fs-repo migrations.
- The Datastore got a few more config options:
- `Datastore.HashOnRead` allows you to verify data on read access.
- `Datastore.BloomFilterSize` tunes the new lookup cache.
And plenty of bug fixes as well as improvements to documentation and test coverage.
[Take a look at the Changelog](https://github.com/ipfs/go-ipfs/blob/master/CHANGELOG.md).
## How to upgrade
Depending on how you initially installed IPFS, there are several ways to
upgrade. If you installed IPFS with a pre-built binary, you can either head over
to [dist.ipfs.io](https://dist.ipfs.io/#go-ipfs) and grab the latest version
from there. Or alternatively, from the same page you can grab the `ipfs-update`
binary, and use it to perform the upgrade for you. If you installed from
source, you can simply run `git checkout v0.4.3`, then run `make install`.
Please upgrade your IPFS nodes as soon as you can,
so you can take advantage of the improvements.
+23
View File
@@ -0,0 +1,23 @@
---
date: 2016-09-27
url: /20-q3-review/
title: Join us for the Q3 Roadmap review calls
description:
author: Richard littauer
---
Our next weekly call will be focused on roadmaps. We will be planning our roadmaps for the rest of 2016. _You're invited to join in!_ We want to make sure the community is heard and involved as much as possible.
If you're interested in seeing the progress we've made with IPFS over the last few months or if you have something you want to see done in Q4, tune into these calls and chime in on IRC.
You are welcome to dial into these calls as a participant if any of the following are true:
- You maintain software that relies directly on IPFS
- You have done work on js-ipfs, go-ipfs, libp2p, orbit, etc.
- You plan to do work on the these projects in the coming months
To prepare for these calls, take a look at the [video calls from last week](https://www.youtube.com/watch?v=cp0acLtBGvE), where we reviewed the third quarter roadmaps.
This is a diversion from our usual weekly schedule. As you may know, every Monday we have a series of calls to align work on IPFS for the following week. We announce the calls each week in an issue in [ipfs/pm](https://github.com/ipfs/pm/issues) and on the [#ipfs IRC channel](http://webchat.freenode.net/?channels=%23ipfs). Next week, instead of following [our usual routine](https://github.com/ipfs/pm#sprints-wip), we're going to look at roadmaps. We will cover some of the work we plan to do in the next few months. In mid-October all of the project leads will be meeting in person to lay out our finalized roadmap for the rest of 2016.
If you'd like to weigh in on the process for the next two weeks, check out [this issue](https://github.com/ipfs/pm/issues/202) and let us know! If you have never participated in the calls before and you plan to dial in as a participant on these roadmapping calls, please let us know you're coming by leaving a comment on that github issue.
@@ -0,0 +1,81 @@
---
date: 2016-10-11
url: /21-go-ipfs-0-4-4-released/
title: go-ipfs 0.4.4 has been released
description:
author: whyrusleeping & Lars Gierth
---
[go-ipfs 0.4.4](https://dist.ipfs.io/#go-ipfs) has been released today,
including an important hotfix for a bug we discovered in how _pinning_ works.
If you had a large number of pins, new pins would overwrite existing pins.
Apart from the hotfix, this release is equal to the previous release 0.4.3.
- [How pinning works](#how-pinning-works)
- [The bug](#the-bug)
- [Find out if you're affected](#find-out-if-you-re-affected)
- [How to upgrade](#how-to-upgrade)
## How pinning works
Pinning is a means of persisting data in your IPFS repo after adding or fetching it.
It'll prevent objects from getting removed by garbage collection or other methods
of cleaning up the IPFS repo. There are three ways an object can be pinned:
- **Direct:** Only this object is pinned. Its children aren't pinned.
- **Recursive:** This object and all its children are pinned.
If the recursive pin is removed, the children aren't pinned any longer either.
- **Indirect:** This object is pinned because one of its parents is pinned.
If the pins of all parents are removed, this object isn't pinned any longer.
The `ipfs add` command adds a _recursive pin_ for the added file by default.
With `--pin=false`, it skips pinning. Similarly, the default pin type for
`ipfs pin add` is _recursive_. With `--recursive=false` this changes to _direct_.
For more information on how pinning works, check out `ipfs pin --help` and `ipfs add --help`.
## The bug
Direct and recursive pins are stored in separate so-called _pinsets_.
Indirect pins aren't stored, since they're derived from recursive pins.
Once you had more than 8192 pins, recursive or direct, an
issue with the recursive hash trie implementation caused hash table buckets to
be overwritten, resulting in only 256 pins remaining in the pinset. After that,
the bug wouldn't be triggered again until the number of pins exceeded 8192 again.
The 256 pins that remained would be random.
We fixed this by instead making sure that each item in a pinset be put into its
own bucket, and by modulo'ing hash output from this process into the final key
space. The details for this can be seen
[in this pull request](https://github.com/ipfs/go-ipfs/pull/3273). We added a
stress test to make sure that this doesn't happen in the future, and will
redouble our efforts to make sure that our test suites are more robust to ensure
that these kinds of problems do not happen in the future.
For now, don't run `ipfs repo gc` on sensitive data that is not otherwise backed up,
as IPFS is still not 1.0 and our development may still find problems.
[@kyledrake](https://github.com/kyledrake) of [neocities.org](https://neocities.org)
pointed out this bug to us; thank you, Kyle!
## Find out if you're affected
If you think you have experienced this issue and have _not_ run a garbage
collection, you can still find the 'lost' pins. We have written a new tool
called 'ipfs-see-all' that allows you to try and recover any old pins that are
still in your local repo. The tool is available on [our distributions
page](https://dist.ipfs.io), or, if you prefer building from source, head over
to [the GitHub repo](https://github.com/whyrusleeping/ipfs-see-all). Once you
have the tool, invoke it as `ipfs-see-all lost-pins` and it will scan for and
print out every pin object that is not actually pinned in your pinset. Note
that this may contain anything you have manually unpinned.
## How to upgrade
Depending on how you initially installed IPFS, there are several ways to
upgrade. If you installed IPFS with a pre-built binary, you can head over
to [dist.ipfs.io](https://dist.ipfs.io/#go-ipfs) and grab the latest version
from there. Or alternatively, from the same page you can grab the `ipfs-update`
binary, and use it to perform the upgrade for you. If you installed from
source, you can simply run `git checkout v0.4.4`, then run `make install`.
+136
View File
@@ -0,0 +1,136 @@
---
date: 2016-11-14
url: /22-run-ipfs-on-a-vps/
title: Run IPFS latest on a VPS
description:
author: Richard Littauer
---
The best way to provide content using [IPFS](https://ipfs.io) is to run your own IPFS node. You can do this by running an IPFS node on your personal computer, but that will only work as long as your computer is running. For users running mostly from laptops or with bandwidth constraints, it is useful to run IPFS nodes in a datacenter, and pinning the content there too. This ensures your content is replicated, online, and available to other nodes on the network.
VPS instances provided by [Digital Ocean](https://www.digitalocean.com/), [Ramnode](http://ramnode.com/), [Linode](https://www.linode.com/), [Vultr](https://www.vultr.com/) and many other providers allow you to quickly setup your own Linux server with the reliability of a managed dedicated server without the full cost. This is a quick guide to setting up your own dedicated IPFS node on a VPS. We'll be using [Ubuntu](http://www.ubuntu.com/) 14.04LTS 64-bit for the example.
## Installing IPFS
First, let's get the packages we'll need to install IPFS:
```sh
> apt-get update
> apt-get install tar wget
```
Now you can download the latest build of IPFS from the [install page](https://ipfs.io/docs/install/). We'll be using Linux x86_64:
```sh
> wget https://dist.ipfs.io/go-ipfs/v0.4.14/go-ipfs_v0.4.14_linux-amd64.tar.gz
> tar xfv go-ipfs_v0.4.14_linux-amd64.tar.gz
# Move it into your bin. This requires root permissions.
> sudo cp go-ipfs/ipfs /usr/local/bin/
```
It's usually not a good idea to run a public-facing service as root. So we'll create a user account to run IPFS in and switch to it:
```sh
> adduser ipfs
> su ipfs
```
## Adding content to IPFS
<!-- {{< asciinema id="player-container" data="asciicast-85339.json" width="88" height="50" >}} -->
First let's initialize the IPFS config:
```sh
> ipfs init
initializing ipfs node at ~/.ipfs
generating 2048-bit RSA keypair...done
peer identity: QmSyPpT59gXxtnLRZePQBthJd934iy17bmQesgHUAw25pB
to get started, enter:
ipfs cat /ipfs/QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG/readme
```
Note that your peer ID will be different, and that your node init file will be in your default user directory.
IPFS works by actively seeking nearby nodes to connect to, which is a good thing for performance and availability, particularly in home and office networks. This causes addresses in the networks to be dialed that may not be there. Unfortunately, some VPS providers incorrectly classify this as suspicious activity, and some even have blocked nodes for doing so. To avoid this, let's add two things to the config file:
```sh
# 1. disable mDNS discovery
ipfs config --json Discovery.MDNS.Enabled false
# 2. filter out local network addresses
ipfs config --json Swarm.AddrFilters '[
"/ip4/10.0.0.0/ipcidr/8",
"/ip4/100.64.0.0/ipcidr/10",
"/ip4/169.254.0.0/ipcidr/16",
"/ip4/172.16.0.0/ipcidr/12",
"/ip4/192.0.0.0/ipcidr/24",
"/ip4/192.0.0.0/ipcidr/29",
"/ip4/192.0.0.8/ipcidr/32",
"/ip4/192.0.0.170/ipcidr/32",
"/ip4/192.0.0.171/ipcidr/32",
"/ip4/192.0.2.0/ipcidr/24",
"/ip4/192.168.0.0/ipcidr/16",
"/ip4/198.18.0.0/ipcidr/15",
"/ip4/198.51.100.0/ipcidr/24",
"/ip4/203.0.113.0/ipcidr/24",
"/ip4/240.0.0.0/ipcidr/4"
]'
```
Now you're ready to start IPFS!
```sh
> ipfs daemon &
[1] 16252
Initializing daemon...
Adjusting current ulimit to 1024.
> Swarm listening on /ip4/127.0.0.1/tcp/4001
Swarm listening on /ip4/172.20.20.20/tcp/4001
Swarm listening on /ip4/73.114.34.208/tcp/37131
Swarm listening on /ip6/::1/tcp/4001
API server listening on /ip4/127.0.0.1/tcp/5001
Gateway (readonly) server listening on /ip4/127.0.0.1/tcp/8080
Daemon is ready
```
This will run your daemon in the background, so you won't need to switch to a new window. You can make sure it is running using the `jobs` command.
Give it a minute to connect to some other IPFS nodes, and then test that it's working by running a quick test:
```sh
> echo "hello world" | ipfs add
added QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o
```
Now run this command to make sure that your IPFS node had this content locally:
```sh
> ipfs refs local | grep QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o
QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o
```
To check if your IPFS node has this content pinned, you can run `ipfs pin ls`:
```
> ipfs pin ls | grep QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o
QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o recursive
```
In this example, the hash is returned because `ipfs add` pins a file passed to it by default. If the hash is not returned, then the content will be removed at the next garbage collection. To stop this from happening, let's pin it:
```sh
> ipfs pin add QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o
pinned QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o recursively
```
If you want to have IPFS boot at startup, add an entry to `/etc/rc.local`. You can run this command as root to quickly add it:
```sh
sed -i -e '$i /bin/su ipfs -c "/usr/local/bin/ipfs daemon &"\n' /etc/rc.local
```
We also have [init scripts](https://github.com/ipfs/examples/tree/master/examples/init) to help you launch IPFS on start.
This process will simplify in the future when IPFS starts being packaged with distributions (`apt-get install ipfs`). But until then, this will get you started with IPFS experimentation on your own server. Run `ipfs help` to get a list of things you can do, and let us know if you run into any issues.
+111
View File
@@ -0,0 +1,111 @@
---
date: 2017-03-24
url: /23-js-ipfs-0-23/
title: js-ipfs 0.23.0 released
description:
author: David Dias & Victor Bjelkholm
header_image: js-ipfs-placeholder.png
---
Today we're happy to announce that we have released js-ipfs version 0.23.0.
## Highlights
- DAG API (IPLD Support)
- Interoperability with go-ipfs
- Bootstrap nodes
- Easier initialization
- Datastore
- New tutorials
- `jsipfs add --wrap-with-directory` feature
- Support for unixfs sharding
## Installation
```bash
npm install --save ipfs@0.23.0
```
## Full Details
### ✨ A new way API is born, welcome to `.dag`
The new DAG API (available through `ipfs.dag`) offers a new way to create and operate over any MerkleGraph, today it has support for the IPFS MerkleDAG (referenced as dag-cbor), the new dag-cbor (which lets you drop json into IPFS seamlessly) and a preview of Ethereum.
You can learn how to use it through:
- [docs](https://github.com/ipfs/interface-ipfs-core/tree/master/API/dag#dag-api)
- [examples](https://github.com/ipfs/js-ipfs/tree/master/examples/dag#create-and-resolve-through-graphs-with-the-dag-api)
- [video running through the demos](https://www.youtube.com/watch?v=drULwJ_ZDRQ)
If you are new to the Merkle Forest, make sure to watch @jbenet's talk ["Enter the Merkle Forest"](https://www.youtube.com/watch?v=Bqs_LzBjQyk)
### 🙌🏽 Interoperability with go-ipfs is here!
This took us more time than what we had initially expected, however, now it is a thing of the past, you can dial to a go-ipfs node and exchange files without going through complicated set ups, it just works™
If you would like to know more about the issue we faced, you can find more info here: ["Stream Muxing issues between go-ipfs and js-ipfs are a thing of the past"](https://github.com/ipfs/js-ipfs/issues/721)
**Note:** Interop is only fully available with go-ipfs 0.4.7 and onwards, if you haven't updated yet, please do so by visiting http://dist.ipfs.io/.
### 🌍 js-ipfs will now also bootstrap with bootstrap nodes as well
Same way that go-ipfs does, now your jsipfs daemon will bootstrap itself with the bootstraper nodes. This was easy once we had the Stream Muxing figured out.
```sh
> jsipfs swarm peers
/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
/ip4/104.236.176.52/tcp/4001/ipfs/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z
/ip4/104.236.179.241/tcp/4001/ipfs/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM
/ip4/162.243.248.213/tcp/4001/ipfs/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm
/ip4/128.199.219.111/tcp/4001/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu
/ip4/104.236.76.40/tcp/4001/ipfs/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64
/ip4/178.62.158.247/tcp/4001/ipfs/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd
/ip4/178.62.61.185/tcp/4001/ipfs/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3
/ip4/104.236.151.122/tcp/4001/ipfs/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx
```
### ⚡️ Starting an IPFS instance is easier than ever
We've heard you, starting an ipfs instance was cumbersome 3 step process, but not anymore!
Now, all you need to do to start an instance is:
```javascript
const IPFS = require('ipfs')
const node = new IPFS()
node.on('start', () => {
// Your node is now ready to use \o/
})
```
That's it! See more [Usage examples in the README](https://github.com/ipfs/js-ipfs#ipfs-core-use-ipfs-as-a-module)
### 💾 Datastore is here!
We've migrated away from [pull-blob-store](https://github.com/ipfs/interface-pull-blob-store)/[blob-store](https://github.com/maxogden/abstract-blob-store) to [`datastore`](https://github.com/ipfs/interface-datastore), the storage interface that is used in go-ipfs. This was a requirement towards implementing the DHT on js-ipfs.
### 👩🏽‍🏫 New tutorial! Transfer files between browser and desktop nodes
We've build a new Tutorial in how to use js-ipfs that explain how to interact with other nodes, from connecting, discovering and exchanging files. The tutorial is the most bare bones possible (i.e no frameworks) so that it focus on IPFS.
Find this tutorial at our [examples folder](https://github.com/ipfs/js-ipfs/tree/master/examples/transfer-files)
### 👏🏽 jsipfs add --wrap-with-directory is now a feature!
Thanks to @harshjv, now you can add files wrapped in a directory just like go-ipfs. Example:
```sh
> jsipfs add <filename> --wrap-with-directory
> jsipfs add <filename -w # alias
```
# Exciting future (soon™)
Here is a quick list of things that we will be heads down after this release
- Circuit Relay - We are building Circuit Relay in order to let browser nodes to connect to any node in the network (e.g when two nodes do not have a common transport). Track the development and spec here -- https://github.com/libp2p/specs/tree/master/relay, https://github.com/libp2p/js-libp2p-circuit/pull/1.
- DHT - The DHT is the second last piece that needs to be built (the first being relay) in order to give browser nodes the ability to discover the location of content by themselves. Track dev at the repo https://github.com/libp2p/js-libp2p-dht
- Parity to IPFS - We want IPFS to access the Ethereum blockchain on demand (i.e: Without having to constantly transferring the blocks over), for that, the EthereumJS team had a brilliant idea of building a storage backend for js-ipfs that uses Parity, so that we can 'read' blocks from the blockchain. Track at: https://github.com/ipfs/js-ipfs/issues/763
- Torrent support - We started working in getting Torrent files supported in js-ipfs (same way we do for Ethereum). This will give the ability to fetch data from the BitTorrent network through js-ipfs as well. Track progress here: https://github.com/ipfs/js-ipfs/issues/779
@@ -0,0 +1,199 @@
---
date: 2017-05-04
url: /24-uncensorable-wikipedia/
title: Uncensorable Wikipedia on IPFS
description:
author: The IPFS Team
---
_**UPDATE:** There are now [English](https://ipfs.io/ipfs/QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco) and [Kurdish](https://ipfs.io/ipfs/QmWY4KZXKTuspGSwYVDNbNLLZcmSiQ63Mdmz7eRd4KzBbb) versions of Wikipedia on IPFS as well as the Turkish verison. You can find the latest hashes for all of our Wikipedia snapshots in [this YAML file](https://github.com/ipfs/distributed-wikipedia-mirror/blob/master/snapshot-hashes.yml)_
> There is more than one way to burn a book. And the world is full of people running about with lit matches.
> -- Ray Bradbury, Fahrenheit 451
> The Internet treats censorship as a malfunction and routes around it.
> -- John Perry Barlow
We are happy to announce that **we have published a [snapshot of tr.wikipedia.org, the Turkish version of Wikipedia](https://ipfs.io/ipfs/QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX/wiki/Anasayfa.html) on IPFS**. There will be Arabic, Kurdish and English versions coming shortly. This blog post includes information on how you can access those snapshots, how you can help mirror them, and why it's so powerful to put content like this on IPFS.
The effort to put snapshots of Wikipedia onto IPFS is an independent effort undertaken by the IPFS maintainers. It is not affiliated with the Wikimedia Foundation and is not connected with the volunteers who contribute to Wikipedia articles.
## What Triggered This Announcement
At 8am local time on April 29th, Wikipedia went dark for everyone in Turkey. According to the independent watchdog group [Turkey Blocks](https://turkeyblocks.org), the Turkish government has issued a court order that permanently restricts access to the online encyclopedia.
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">Confirmed: All editions of the <a href="https://twitter.com/hashtag/Wikipedia?src=hash">#Wikipedia</a> online encyclopedia blocked in <a href="https://twitter.com/hashtag/Turkey?src=hash">#Turkey</a> as of 8:00AM local time<a href="https://t.co/ybFolRmsOs">https://t.co/ybFolRmsOs</a> <a href="https://t.co/hI9tn4bHe5">pic.twitter.com/hI9tn4bHe5</a></p>&mdash; Turkey Blocks (@TurkeyBlocks) <a href="https://twitter.com/TurkeyBlocks/status/858189777585262592">April 29, 2017</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
A main goal of the IPFS Project is improving humanity's access to information. We strongly oppose the censorship of history, of news, of free thought, of discourse, and of compendiums of vital information such as Wikipedia. Free access to information is key to modern human life, to a free society, and to a flourishing culture. We're alarmed by the erosion of civil liberties wherever it occurs, and we want to help people like the citizens of Turkey preserve freedom of information, even in the face of a tightening iron fist.
Upon hearing the news, we revived an effort to put snapshots of Wikipedia on IPFS, so that people may be able to read it in a decentralized and distributed way. This can help people to at least _view_ all of the Wikipedia content, even if they cannot reach Wikipedia.org itself.
## Quick Background: IPFS and Content Addressing
**[IPFS](https://ipfs.io) -- the Inter-Planetary File System** -- is a new internet protocol that makes the web faster, safer, and more open. IPFS changes the _addressing_ of information, moving from _location addressing_ to _content addressing_. You can find out more about IPFS at [the IPFS Website](https://ipfs.io) or by [watching this talk](https://www.youtube.com/watch?v=2RCwZDRwk48).
_Content addressing_ is a technique to reference files or data by a unique fingerprint derived from the contents of the file or data itself. Content addressing is implemented with [cryptographic hashing](https://simple.wikipedia.org/wiki/Cryptographic_hash_function), so that content addresses are secure, permanent, and derived directly from the content itself. Information systems like IPFS use content addressing to ensure files, websites, and webapps can move around the network and be distributed by any computer securely and with perfect fidelity. This means that the contents of websites like Wikipedia can be moved around and accessed in a peer-to-peer, decentralized way, much like BitTorrent or even email. This works even if access to the specific https://en.wikipedia.org servers is censored. To find out more about content addressing, you can watch [this part of a talk](https://youtu.be/2RCwZDRwk48?t=847) or [this excellent post](https://ipfs.io/ipfs/QmNhFJjGcMPqpuYfxL62VVB9528NXqDNMFXiqN5bgFYiZ1/its-time-for-the-permanent-web.html).
### Why put Wikipedia on IPFS?
By putting important information like Wikipedia onto the decentralized web, we open many avenues for people to access, hold, cite, and use that information in more durable ways. This is a quick summary of the ways IPFS makes these things possible. To learn more, visit https://ipfs.io.
In short, content on IPFS is **harder to attack** and easier to distribute because its **peer-to-peer and decentralized**.
**Even if the original publisher is taken down, the content can be served by anyone who has it.** As long as at least one node on the network has a copy of the content, everyone will be able to get it. This means the responsibility for serving content can change over time without changing the way people link to the content and without any doubt that the content you're reading is exactly the content that was originally published.
**The content you download is cryptographically verified** to ensure that it hasnt been tampered with.
**IPFS can work in partitioned networks** - you dont need a stable connection to the rest of the web in order to access content through IPFS. As long as your node can connect to at least one node with the content you want, it works!
**As soon as any node has the content, everyone's links start working.** Even if someone destroys all the copies on the network, it only takes one node adding the content in order to restore availability.
**If one IPFS gateway gets blocked, you can use another one.** IPFS gateways are all capable of serving the same content, so youre not stuck relying on one point of failure.
**Lightening the load**: With IPFS, people viewing the content are also helping distribute the content (unless they opt out) and anyone can choose to pin a copy of some content on their node in order to help with access and preservation.
**You can read anonymously.** As with HTTP, IPFS can work over Tor and other anonymity systems
**IPFS does not rely on DNS**. If someone blocks your access to DNS or spoofs DNS in your network, it will not prevent IPFS nodes from resolving content over the peer-to-peer network. Even if you're using the DNSlink feature of IPFS, you just need to find a gateway that _does_ have access to DNS. As long as the gateway you're relying on has access to DNS it will be able to resolve your DNSlink addresses.
**IPFS does not rely on the Certificate Authority System**, so bad or corrupt Certificate Authorities do not impact it.
**You can move content via [sneakernet](https://en.wikipedia.org/wiki/Sneakernet)!** _This is very useful in areas with poor connectivity, due to resource limitations, security reasons, or censorship._ Even if your network is physically disconnected from the rest of the internet, you can write content from IPFS onto USB drives or other external drives, physically move them to computers connected to a new network, and re-publish the content on the new network. Even though you're on a separate network, IPFS will let nodes access the content using the same identifiers in both networks as long as at least one node on the network has that content.
**IPFS nodes work hard to find each other on the network** and to reconnect with each other after connections get cut.
(experimental) **You can even form private IPFS networks** to share information _only_ with computers you've chosen to connect with.
## Wikipedia on IPFS -- Background
### What does it mean to put Wikipedia on IPFS?
The idea of putting Wikipedia on IPFS has been around for a while. Every few months or so someone revives the threads. You can find such discussions in [this github issue about archiving Wikipedia](https://github.com/ipfs/archives/issues/20), [this issue about possible integrations with Wikipedia](https://github.com/ipfs/notes/issues/46), and [this proposal for a new project](https://github.com/ipfs/notes/issues/47#issuecomment-140587530).
We have two consecutive goals regarding Wikipedia on IPFS: Our first goal is to create periodic read-only snapshots of Wikipedia. A second goal will be to create a full-fledged read-write version of Wikipedia. This second goal would connect with the Wikimedia Foundations bigger, longer-running conversation about decentralizing Wikipedia, which you can read about at https://strategy.m.wikimedia.org/wiki/Proposal:Distributed_Wikipedia
### (Goal 1) Read-Only Wikipedia on IPFS
The easy way to get Wikipedia content on IPFS is to periodically -- say every week -- take snapshots of all the content and add it to IPFS. That way the majority of Wikipedia users -- who only read Wikipedia and dont edit -- could use all the information on Wikipedia with all the benefits of IPFS. Users couldn't edit it, but users could download and archive swaths of articles, or even the whole thing. People could serve it to each other peer-to-peer, reducing the bandwidth load on Wikipedia servers. People could even distribute it to each other in closed, censored, or resource-constrained networks -- with IPFS, peers do not need to be connected to the original source of the content, being connected to anyone who has the content is enough. Effectively, the content can jump from computer to computer in a peer-to-peer way, and avoid having to connect to the content source or even the internet backbone. We've been in discussions with many groups about the potential of this kind of thing, and how it could help billions of people around the world to access information better -- either free of censorship, or circumventing serious bandwidth or latency constraints.
So far, we have achieved part of this goal: we have static snapshots of all of Wikipedia on IPFS. This is already a huge result that will help people access, keep, archive, cite, and distribute lots of content. In particular, we hope that this distribution helps people in Turkey, who find themselves in a tough situation. We are still working out a process to continue updating these snapshots, we hope to have someone at Wikimedia in the loop as they are the authoritative source of the content. **If you could help with this, please get in touch with us at wikipedia-project@ipfs.io.**
### (Goal 2) Fully Read-Write Wikipedia on IPFS
The long term goal is to get the full-fledged read-write Wikipedia to work on top of IPFS. This is much more difficult because for a read-write application like Wikipedia to leverage the distributed nature of IPFS, we need to change the how the applications write data. A read-write Wikipedia on IPFS would allow completely decentralized and extremely difficult to censor operation. In addition to all the benefits of the static version above, the users of a read-write Wikipedia on IPFS could write content from anywhere and publish it, even without being directly connected to any wikipedia.org servers. There would be automatic version control and version history archiving. We could allow people to view, edit, and publish in completely encrypted contexts, which is important to people in highly repressive regions of the world.
A full read-write version (2) would require a strong collaboration with Wikipedia.org itself, and finishing work on important dynamic content challenges -- we are working on all the technology (2) needs, but it's not ready for prime-time yet. We will update when it is.
## Wikipedia on IPFS -- The Release Today
Today, we are releasing the first full static snapshot on IPFS of all of https://tr.wikipedia.org. We describe how to access it and how we did this below. This snapshot was taken on `2017-04-30`. Over the coming days we will also release snapshots of the Arabic (https://ar.wikipedia.org) and Kurdish (https://ku.wikipedia.org) versions of Wikipedia. There's an English snapshot coming too. The English version of Wikipedia is taking longer to load onto IPFS because it's much bigger than the others (20 times larger), with many more links. We will post updates when we add snapshots of each new language.
The unique identifier (cryptographic hash) for the snapshot of tr.wikipedia.org from April 30th is:
- Turkish Wikipedia (30 April 2017): [/ipfs/QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX/wiki/Anasayfa.html](https://ipfs.io/ipfs/QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX/wiki/Anasayfa.html)
This link points to a specific snapshot. It will always point to that specific day's snapshot. To find the most up-to-date snapshot of Wikipedia on IPFS you can use this IPNS link, which will resolve to the latest snapshot whenever we release a new one:
- Turkish Wikipedia (most recent snapshot, resolved through IPNS): [/ipns/QmVH1VzGBydSfmNG7rmdDjAeBZ71UVeEahVbNpFQtwZK8W/wiki/Anasayfa.html](https://ipfs.io/ipns/QmVH1VzGBydSfmNG7rmdDjAeBZ71UVeEahVbNpFQtwZK8W/wiki/Anasayfa.html)
### Human-readable links
For your convenience we have set up a domain name and DNS entry at [tr.wikipedia-on-ipfs.org](http://tr.wikipedia-on-ipfs.org) that will resolve to the current IPFS snapshot.
If you are not able to access ipfs.io or wikipedia-on-ipfs.org, you can use this dnslink URL to access the content -- simply replace `ipfs.io` with the address of any IPFS gateway:
- Turkish Wikipedia: [https://ipfs.io/ipns/tr.wikipedia-on-ipfs.org](https://ipfs.io/ipns/tr.wikipedia-on-ipfs.org/wiki/Anasayfa.html)
Failing that, use the (less readable) IPFS or IPNS links from above.
## How to access Wikipedia on IPFS
IPFS makes it possible to access information through many different paths. If one path is closed, you have other options. Some of the options are very simple. Others require a bit more effort. In most cases you can use [this link](https://ipfs.io/ipfs/QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX/wiki/Anasayfa.html) to view the Wikipedia snapshot through the ipfs-to-http gateway at ipfs.io. If that path doesn't work, for example if ipfs.io is blocked in your area, you can use the same basic information from that ipfs.io link and access Wikipedia through a different path.
We've written a number of tutorials describing the many different ways to access content through IPFS, including ways to access IPFS anonymously through Tor. You can read those tutorials in the current draft of [The Decentralized Web Primer](https://dweb-primer.ipfs.io/avenues-for-access/), which is available [online](https://dweb-primer.ipfs.io/avenues-for-access/) or as a [downloadable PDF](https://dweb-primer.ipfs.io/decentralized-web-primer.pdf), [epub](https://dweb-primer.ipfs.io/decentralized-web-primer.epub), or [mobi](https://dweb-primer.ipfs.io/decentralized-web-primer.mobi)
Your main options for accessing the snapshot of Wikipedia are: _(depending on your network, some of these won't work)_
- **Option**: Use the ipfs.io gateway to access the 30 April 2017 snapshot: https://ipfs.io/ipfs/QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX/wiki/Anasayfa.html
- **Option**: Use the ipfs.io gateway to access the latest version: https://ipfs.io/ipns/QmVH1VzGBydSfmNG7rmdDjAeBZ71UVeEahVbNpFQtwZK8W/wiki/Anasayfa.html
- **Option**: Connect with the IPFS network over Tor (this is experimental). Read [this tutorial on Tor gateways](https://dweb-primer.ipfs.io/avenues-for-access/lessons/tor-gateways.html).
- **Option**: Install an IPFS node on your computer and access Wikipedia through that node (requires [using the command line](http://lifehacker.com/5633909/who-needs-a-mouse-learn-to-use-the-command-line-for-almost-anything). _This is the most reliable method because it retrieves the content directly from the IPFS peer-to-peer network)_
1. Install IPFS [following these instructions](https://dweb-primer.ipfs.io/install-ipfs/). Use the most recent verison of IPFS: 0.4.9-rc2 or higher if possible.
2. Start your IPFS node by running `ipfs daemon` so it can connect to the network.
3. Read the content through your IPFS node's local HTTP gateway by visiting:
- 30 April 2017 snapshot: http://localhost:8080/ipfs/QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX/wiki/Anasayfa.html
- latest snapshot: http://localhost:8080/ipns/QmVH1VzGBydSfmNG7rmdDjAeBZ71UVeEahVbNpFQtwZK8W/wiki/Anasayfa.html
**Coming soon:** We've almost finished creating a web browser extension that will allow you to access IPFS directly from your web browser without installing any additional external tools. Watch https://github.com/ipfs/in-web-browsers for the announcement when we release that browser extension.
## How to Mirror Wikipedia on an IPFS Node
If you want to help make this information available, you can install IPFS on your computer and _"pin"_ the Wikipedia snapshot(s) on that IPFS node. Here are instructions for doing that.
**Prerequisites**:
- Requires [using the command line](http://lifehacker.com/5633909/who-needs-a-mouse-learn-to-use-the-command-line-for-almost-anything)
- You need to have enough storage space to hold the snapshot(s) you want to serve.
- Turkish snapshot requires 10 GB
- Turkish, Arabic and Kurdish snapshots combined together will require 25 GB
- English snapshot will require 250 GB
- _If possible, use a machine with a public IP address._ If you want to run an ipfs-to-http gateway that lets people access the IPFS content using their web browswer, you need to ensure that your machine can be reached with a public IP address.
If you don't have enough storage space to hold full copies of the snapshot(s), you can still run an IPFS gateway so that people can rely on you to retrieve the content from the IPFS network on the fly.
**Steps**:
1. Install IPFS [following these instructions](https://dweb-primer.ipfs.io/install-ipfs/). Use the most recent verison of IPFS -- 0.4.9-rc2 or higher if possible.
2. Start your IPFS node by running `ipfs daemon` so it can connect to the network.
3. Pin the snapshot(s) onto your machine
- Pin Turkish Wikipedia: `ipfs pin add QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX`
4. If your machine has a public IP addresses, tell people the address of your gateway. They can use that address to request IPFS content from their web browsers.
**Alternative: Using a Sneakernet to Mirror data**
I'm not kidding. If you'd like to access this content via [sneakernet](https://en.wikipedia.org/wiki/Sneakernet), IPFS is just fine with that. To find out how, follow the instructions in [this tutorial](https://dweb-primer.ipfs.io/avenues-for-access/lessons/sneakernets.html)
## How we made this Wikipedia snapshot
The steps we followed to create these first snapshots were:
1. Download the latest snapshot of Wikipedia (in ZIM format) from http://wiki.kiwix.org/wiki/Content_in_all_languages
2. Unpack the ZIM snapshot using https://github.com/dignifiedquire/zim/commit/a283151105ab4c1905d7f5cb56fb8eb2a854ad67
3. Add mirror info and search bar to the snapshot using the script [here](https://github.com/ipfs/distributed-wikipedia-mirror/blob/master/execute-changes.sh)
4. Add the snapshot to IPFS with the command `ipfs add -w -r --raw-leaves $upacked_wiki`
This work was primarily done by [@Kubuxu](https://github.com/kubuxu) [@dignifiedquire](https://github.com/dignifiedquire) and [@lgierth](https://github.com/lgierth) with help from [@whyrusleeping](https://github.com/whyrusleeping). They used code originally written by [@eminence](https://github.com/eminence)
## How to add new Wikipedia snapshots to IPFS
If you would like to repeat this process, adding an updated Wikipedia snapshot on IPFS, you can follow the instructions at https://github.com/ipfs/distributed-wikipedia-mirror. We will keep that page up to date as we improve the process.
## How we will publish the hashes of new snapshots
Whenever we add an updated snapshot of Wikipedia to IPFS, we will announce it in a few ways.
1. We will tweet the new snapshot hash on the [@ipfsbot](https://twitter.com/ipfsbot) account along with the hashtag [#WikipediaOnIPFS](https://twitter.com/search?f=tweets&q=WikipediaOnIPFS)
2. We will update this IPNS entry to point to the latest snapshot:
- Turkish Wikipedia: [/ipns/QmVH1VzGBydSfmNG7rmdDjAeBZ71UVeEahVbNpFQtwZK8W/wiki/Anasayfa.html](https://ipfs.io/ipns/QmVH1VzGBydSfmNG7rmdDjAeBZ71UVeEahVbNpFQtwZK8W/wiki/Anasayfa.html)
The DNS entry at [tr.wikipedia-on-ipfs.org](https://tr.wikipedia-on-ipfs.org) is mapped to the IPNS value, so it will also give you the latest available snapshot (if DNS and IPNS are working properly in your area).
## Who controls the information
At the moment this is an experiment, done in haste in response to current events. Employees of Protocol Labs are building the Wikipedia snapshots on IPFS and pinning that content onto some of the IPFS nodes that they run.
If people start relying on this information over time, it will be important to establish a clear chain of custody, ideally with people at Wikimedia foundation building the snapshots and publishing the hashes. Protocol Labs is exploring possibilities in this direction.
## Review
- You can access a snapshot of Wikipedia through any IPFS gateway
- Turkish Wikipedia (30 April 2017 snapshot): [/ipfs/QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX/wiki/Anasayfa.html](https://ipfs.io/ipfs/QmT5NvUtoM5nWFfrQdVrFtvGfKFmG7AHE8P34isapyhCxX/wiki/Anasayfa.html)
- These are read-only snapshots. Supporting full read-and-write would take further work.
- IPFS links use cryptographic hashes, so you dont have to worry about spoofing
- The IPNS hashes point to the most recent snapshot
- Turkish Wikipedia (most recent snapshot): [/ipns/QmVH1VzGBydSfmNG7rmdDjAeBZ71UVeEahVbNpFQtwZK8W/wiki/Anasayfa.html](https://ipfs.io/ipns/QmVH1VzGBydSfmNG7rmdDjAeBZ71UVeEahVbNpFQtwZK8W/wiki/Anasayfa.html)
- If people start relying on this information, we will encourage Wikimedia to take over generating these snapshots
- We are encouraging Wikimedia to publish their own IPNS hash that is always up to date AND is cryptographically signed by Wikimedia
- (if wikimedia updates DNS) if you have access to a gateway thats outside of Turkey, you will be able to use the convenient path [/ipns/wikipedia.org](https://ipfs.io/ipns/wikipedia.org) instead of using hashes in your IPFS links
- If you want to mirror the data, run an ipfs node and pin the Wikipedia data onto your node
+126
View File
@@ -0,0 +1,126 @@
---
date: 2017-05-17
url: /25-pubsub/
title: Take a look at pubsub on IPFS
description:
author: Jeromy Johnson
---
We recently merged a simple, experimental pubsub implementation into IPFS. This
implementation is just a beginning. It is far from the performance and security goals
we will achieve in our long-term target. However, even this early implementation opens
the doors to several useful and interesting new applications.
In this post, I will point out some applications of this technology, show how to
get started started using `ipfs pubsub`, and discuss upcoming improvements.
## Why pubsub?
[Publish-Subscribe](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern),
called 'pubsub' for short, is a pattern often used to handle events in
large-scale networks. 'Publishers' send messages classified by topic or content and
'subscribers' receive only the messages they are interested in, all without direct
connections between publishers and subscribers. This approach offers much greater
network scalability and flexibility.
Some applications include collaborative document editing, "dynamic" website
content, chat applications, multiplayer games, continuously evolving datasets,
and webservice workers passing around messages. It gives us ways to make IPFS fast
for large-scale networks such as datacenters, local area networks, and large p2p
applications. In the near future, IPNS records will be pushed over pubsub, allowing
lightning fast updates of peers' IPNS entries. Peers could use pubsub to track the
head of a [merkle-linked global log](https://en.wikipedia.org/wiki/Blockchain).
## Getting started with pubsub for go-ipfs
_Note: There is also a js-ipfs implementation of pubsub. Documentation will come soon._
First, you'll need to enable the pubsub code. Make sure you're running go-ipfs 0.4.5 or
above. Once you have that version of ipfs installed, start the daemon with:
```sh
> ipfs daemon --enable-pubsub-experiment
```
This will tell ipfs to create and enable the pubsub service. It also implies
that you will only be able to use pubsub with other peers who choose to enable
it.
To subscribe to the topic `foo`, run:
```sh
> ipfs pubsub sub foo
```
Now, any messages for the topic `foo` will print to your console.
To publish a message to the topic `foo`, open up another terminal and run:
```sh
> ipfs pubsub pub foo "hello world"
```
You should see "hello world" printed out in the first terminal. You can also
run the `pub` command on any other connected ipfs node and your node will
receive the message. Messages are routed through connected, subscribed peers.
This means that if peers A, B, and C are all subscribed to `foo`, A is connected
to B, and B is connected to C, but A is not directly connected to C, A will
still receive messages that C published to `foo` through B. This can be very
useful for routing messages in networks with poor NAT traversal or otherwise
suboptimal connectivity.
To see all peers with pubsub enabled, check the output of:
```sh
> ipfs pubsub peers
```
To see all the topics you are currently subscribed to, run:
```sh
> ipfs pubsub ls
```
## Pubsub in the wild
As an example, we have integrated pubsub into [Orbit](https://github.com/orbitdb/orbit).
This allows Orbit to provide a fully distributed, peer-to-peer chat without _any_
server anywhere. We are also actively working to put Conflict-Free Replicated Data
Types (CRDTs) on IPFS pubsub using libraries like Y.js and swarm.js.
Together, pubsub and CRDTs open new doors for collaborative editing of distributed
content. We are working with [@edsilv](https://github.com/edsilv) and
[@aeschylus](https://github.com/aeschylus) to prepare a demo of IPFS in two
[IIIF](http://iiif.io/about/) image viewers
[#240](https://github.com/ipfs/notes/issues/240). This will showcase how to
collaboratively annotate images from repositories dispersed around the world.
The demo will take place at the [IIIF 2017 Conference](https://2017iiifconferencethevatican.sched.com/event/AChW/presentation-interoperable-peer-to-peer-research-with-iiif-and-ipfs-room-5.0) in The Vatican.
We will publish a video of the demo, along with all of the code.
## What's next?
The next two areas of focus for IPFS pubsub are authentication and message routing.
Currently, any peer can publish to any pubsub topic. We plan to implement an
authenticated mode for pubsub topics, where only authorized peers — those given a
cryptographic key or capability — can publish messages. We are still working out
the sharing and capability granting model.
After that, we plan to improve message routing. The current routing algorithm
floods messages to every subscriber, resulting in some peers receiving the same
message multiple times. We affectionately call this approach "floodsub". We plan
to replace it with a more efficient routing algorithm, which will go a long way
towards reducing overhead and improving scalability.
Please note that this is a simple first-blush implementation of the technology.
It has known limitations that we will address in future iterations. As it is
today, the pubsub implementation can be quite bandwidth intensive. It works well
for apps with few peers in the group, but does not scale. We have designed a more
robust underlying algorithm that will scale to much larger use cases but we wanted
to ship this simple implementation so you can begin using it for your applications.
## Enjoy!
All that said, we hope you give `ipfs pubsub` a try. You can head over to the
[Discussion Forum](https://discuss.ipfs.io/categories) to ask questions, get help,
or simply let us know how it goes.
+64
View File
@@ -0,0 +1,64 @@
---
date: 2017-05-25
url: /26-js-ipfs-0-24/
title: js-ipfs 0.24.0 released
description:
author: David Dias
header_image: js-ipfs-placeholder.png
---
I am pleased to announce to everyone in our community that js-ipfs 0.24.0 has been successfully launched! This new minor release brings new features, bug fixes and new examples so that you can jump in and start hacking your IPFS enabled apps right away!
<blockquote class="twitter-tweet" data-conversation="none" data-lang="en"><p lang="en" dir="ltr"><a href="https://twitter.com/IPFSbot">@IPFSbot</a> Woot! As promissed, new version of js-ipfs released!<br><br>🚀 `ipfs@0.24.0 [18:39] Published to npm.` 🚀<br><br>Release log here: <a href="https://t.co/20O2L2Scq5">https://t.co/20O2L2Scq5</a></p>&mdash; David Dias (@daviddias) <a href="https://twitter.com/daviddias/status/867512323732365312">May 24, 2017</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
# Highlights
### 👢 WebSockets DNS Bootstrappers by default on the browser
With this release, you wont need to connect to Bootstrapper nodes manually, they will dialed from the start through their WebSockets endpoints. This also means that weve successfully deployed DNS support for multiaddrs, so that you can host your js-ipfs enabled app behind an HTTPS domain and dial to the Bootstrappers through WSS.
### 🎈WebRTC is now a default transport
We now include WebRTC multiaddr by default on new js-ipfs init calls. This means that both your Node.js and Browser nodes will be able to dial each other using WebRTC and discover other nodes in the network through signalling Peer Discovery.
Caveat for Linux/Windows users: Due to limited support of Node.js wrtc module, you will have to set an environment variable or an Experimental flag. Instructions are in the [README](https://github.com/ipfs/js-ipfs#advanced-options-when-creating-an-ipfs-node) to learn how to do so.
### 🗺 1st Phase of DHT Implementation Complete
We now have a working DHT implementation, which means that js-libp2p and js-ipfs are now capable of Peer Routing and Content Routing. This feature should be treated as experimental for the time being, and is behind a [feature flag](https://github.com/ipfs/js-ipfs#advanced-options-when-creating-an-ipfs-node) until a few remaining interoperability issues are cleared out.
### 🕸 WebWorker and ServiceWorker Support
Now you can run js-ipfs in a WebWorker or a ServiceWorker without encountering any stoppers. We also added a rule to our tests to be run inside a WebWorker to ensure that this feature stays intact.
We've recorded a live demo of a Service Worker with js-ipfs running during IPFS All Hands on May 22. You can see this recording and many others on the IPFS Youtube video channel. You can find the code for this demo at [ipfs-service-worker](https://github.com/ipfs/ipfs-service-worker).
<iframe width="560" height="315" src="https://www.youtube.com/embed/xnX0Mz4mPQI" frameborder="0" allowfullscreen></iframe>
### 📶 Better reconnect handling with WebRTC Transport
You can now switch off your wifi or close your laptop and your js-ipfs node will be able to recover any lost connections graciously.
### 💅🏽 Refreshed libp2p API
As a byproduct of shipping new features in js-ipfs, js-libp2p got a refreshed API, documentation and examples. Please do check them out and try libp2p at [js-libp2p](https://github.com/libp2p/js-libp2p)
### 📦 Updated Packages table
Always wondered of how many pieces IPFS is built with? Check the updated Packages table at -- https://github.com/ipfs/js-ipfs#packages.
A **big thank you** goes for everyone who helped make this release possible! We really appreciate all the code contributions, reviews and testing we get from you ❤️.
# Want to get started using js-ipfs today?
We've been building a [series of examples](https://github.com/ipfs/js-ipfs/tree/master/examples) to help everyone get started using js-ipfs. With this release, these examples got even simpler, requiring less configuration. Go to [js-ipfs github repo and check the examples folder](https://github.com/ipfs/js-ipfs/tree/master/examples), containing:
- [How to start a node and add a file to IPFS](https://github.com/ipfs/js-ipfs/tree/master/examples/basics)
- [Learn how to transfer files between nodes over multiple transports](https://github.com/ipfs/js-ipfs/tree/master/examples/transfer-files)
- [Learn how to manipulate IPLD Graphs with the DAG API](https://github.com/ipfs/js-ipfs/tree/master/examples/dag)
- [Wanna try resolving Eth blocks on IPFS?](https://github.com/ipfs/js-ipfs/tree/master/examples/explore-ethereum)
If you run into any hurdles, please open an issue on [ipfs/js-ipfs/issues](https://github.com/ipfs/js-ipfs/issues).
Thank you for your attention, I bid you a good day!

Some files were not shown because too many files have changed in this diff Show More