├── .babelrc
├── .datignore
├── .gitignore
├── .npmrc
├── LICENSE
├── README.md
├── assets
├── fonts
│ ├── AUTHENTIC-Sans.woff
│ ├── AUTHENTIC-Sans.woff2
│ ├── AUTHENTIC-Untitled.woff
│ ├── AUTHENTIC-Untitled.woff2
│ ├── Astloch-Regular.ttf
│ ├── CoolS-Regular.otf
│ ├── DSEG7Classic-Bold.woff
│ ├── DSEG7Classic-Bold.woff2
│ ├── DSEG7Classic-Light.woff
│ ├── DSEG7Classic-Light.woff2
│ ├── JRUG_PUNK.ttf
│ ├── Pecita.otf
│ ├── Reglo-Bold.otf
│ ├── SFMono-Regular.otf
│ ├── SpaceMono-Regular.ttf
│ ├── Spectral-Bold.ttf
│ ├── Spectral-ExtraLight.ttf
│ ├── Spectral-Regular.ttf
│ ├── Sporting_Grotesque-Bold_web.woff
│ ├── Sporting_Grotesque-Bold_web.woff2
│ ├── Sporting_Grotesque-Regular_web.woff
│ ├── Sporting_Grotesque-Regular_web.woff2
│ ├── UnifrakturMaguntia.ttf
│ ├── WorkSans-Black.woff
│ ├── WorkSans-Black.woff2
│ ├── WorkSans-Regular.woff
│ ├── WorkSans-Regular.woff2
│ ├── WorkSans-Thin.woff
│ ├── WorkSans-Thin.woff2
│ ├── Wremena-Bold.woff
│ ├── Wremena-Light.woff
│ ├── Yatra-Regular.ttf
│ ├── YoungSerif-Regular.otf
│ ├── junicode-boldcondensed-webfont.woff
│ ├── junicode-boldcondensed-webfont.woff2
│ ├── junicode-regularcondensed-webfont.woff
│ ├── junicode-regularcondensed-webfont.woff2
│ ├── labmono-regular-web.woff
│ ├── labmono-regular-web.woff2
│ ├── lunchtype22-light-webfont.woff
│ ├── lunchtype22-light-webfont.woff2
│ ├── lunchtype22-medium-webfont.woff
│ ├── lunchtype22-medium-webfont.woff2
│ ├── lunchtype24-light-expanded-webfont.woff
│ ├── lunchtype24-light-expanded-webfont.woff2
│ ├── lunchtype24-medium-expanded-webfont.woff
│ ├── lunchtype24-medium-expanded-webfont.woff2
│ ├── lunchtype25-light_condensed-webfont.woff
│ ├── lunchtype25-light_condensed-webfont.woff2
│ ├── lunchtype25-medium-condensed-webfont.woff
│ ├── lunchtype25-medium-condensed-webfont.woff2
│ ├── manifont-grotesk_book-dot.woff
│ ├── manifont-grotesk_book.woff
│ ├── terminal-grotesque-webfont.woff
│ ├── terminal-grotesque-webfont.woff2
│ ├── ume-gothic-webfont.woff
│ └── ume-mincho-webfont.woff
├── icon-eye.svg
├── icon-settings.svg
├── icon-trash.svg
├── img
│ ├── bg.jpg
│ ├── big-data-no-thanks.png
│ ├── customizable.svg
│ ├── garden.jpg
│ ├── posterframe.jpg
│ ├── posterframe2.jpg
│ ├── screenshot-2.png
│ ├── screenshot-3.png
│ ├── screenshot-4.png
│ ├── screenshot-5.png
│ ├── screenshot-6.png
│ └── screenshot.png
├── plyr.svg
└── social-image.png
├── content
├── about
│ └── index.txt
├── blog
│ ├── 001-list-jon-kyle
│ │ ├── desktop.png
│ │ ├── index.txt
│ │ ├── obs.astro.ucla.edu.png
│ │ ├── ofhouses.com.png
│ │ ├── primitivetechnology.jpg
│ │ └── walkinginla.com.png
│ ├── 002-list-laurel
│ │ ├── billwurtz.com.png
│ │ ├── delinear.info.png
│ │ ├── gossipsweb.net.png
│ │ ├── index.txt
│ │ └── thecreativeindependent.com.png
│ ├── 003-two-point-oh
│ │ └── index.txt
│ ├── 004-list-lily
│ │ ├── a.carapetis.com_csf.png
│ │ ├── digitalfire.com.png
│ │ ├── flow.png
│ │ ├── index.txt
│ │ └── parks.ca.gov_live_lakeorovillesra_emergency_spillway.png
│ ├── 005-list-carly
│ │ ├── gmail.com.png
│ │ ├── index.txt
│ │ ├── prostheticknowledge.tumblr.com.png
│ │ ├── things-editors-like.png
│ │ └── unti-tled.com.png
│ ├── 006-toby
│ │ ├── arena.png
│ │ ├── currentcondition.org.png
│ │ ├── granolashotgun.com.png
│ │ ├── index.txt
│ │ └── wagon.png
│ ├── 007-seth
│ │ ├── glittering.blue.png
│ │ ├── index.txt
│ │ ├── media.ccc.de.png
│ │ ├── nigelslaterrecipes.png
│ │ └── paradisebackyard.blogspost.com.png
│ ├── 008-jon
│ │ ├── bringatrailer.com.png
│ │ ├── howmanyofme.com.png
│ │ ├── index.txt
│ │ ├── n2yo.com_space-station.png
│ │ └── wikipedia.featured.png
│ ├── 009-yosh
│ │ ├── blog.acolyer.org.png
│ │ ├── cookingwithdog.png
│ │ ├── doc.rust-lang.org.png
│ │ ├── index.txt
│ │ └── lwn.net.png
│ ├── 010-andrew
│ │ ├── index.txt
│ │ ├── insecam.org.png
│ │ ├── shadertoy.com.png
│ │ ├── siggraph.png
│ │ └── thingsmagazine.net.png
│ ├── 011-olly
│ │ ├── gx1000.png
│ │ ├── honestjons.com.png
│ │ ├── index.txt
│ │ ├── milesdavis.png
│ │ └── olafureliasson.net.png
│ ├── 012-authentic
│ │ ├── authentic.svg
│ │ └── index.txt
│ ├── 013-emma
│ │ ├── horvitz.png
│ │ ├── index.txt
│ │ ├── no-home-like-place.com.png
│ │ ├── radio.garden.png
│ │ └── savedbythe-bellhooks.tumblr.com.png
│ ├── 014-matthew
│ │ ├── chneukirchen.org_trivium.png
│ │ ├── democracynow.org.png
│ │ ├── diaxa.com.png
│ │ ├── index.txt
│ │ └── treemap.png
│ ├── 015-hassan
│ │ ├── ebay.com.png
│ │ ├── index.txt
│ │ ├── mixesdb.com.png
│ │ ├── sharknosemeeting.com.png
│ │ └── wallothnesch.com.png
│ ├── 016-update
│ │ ├── custom-css.png
│ │ └── index.txt
│ ├── 017-paul
│ │ ├── aaaaarg.fail.png
│ │ ├── athology.rhizome.org.png
│ │ ├── gifcities.org.png
│ │ ├── index.txt
│ │ └── twitch.com.png
│ ├── 018-ezra
│ │ ├── deverloper.mozilla.org.png
│ │ ├── index.txt
│ │ ├── mrleckey.png
│ │ ├── residentadvisor.net.png
│ │ └── taborrobak.com.png
│ ├── 019-gemma
│ │ ├── arena.png
│ │ ├── humunivers.png
│ │ ├── index.txt
│ │ ├── thegradient.png
│ │ └── tree-fall.png
│ ├── 020-dat
│ │ ├── 1-data-storage.svg
│ │ ├── 2-data-storage.svg
│ │ ├── 3-data-storage.svg
│ │ ├── data-storage.svg
│ │ └── index.txt
│ ├── 021-ben
│ │ ├── bdif.png
│ │ ├── index.txt
│ │ ├── manifestos.png
│ │ ├── reddit.png
│ │ └── surfstation.png
│ ├── 022-lukas
│ │ ├── bit-player.png
│ │ ├── gimmickbook.png
│ │ ├── index.txt
│ │ ├── noaa.png
│ │ └── truisms.png
│ ├── 023-kalli
│ │ ├── earth.png
│ │ ├── if-you-leave.png
│ │ ├── index.txt
│ │ ├── propspaper.png
│ │ └── worrydream.png
│ ├── 024-uli
│ │ ├── 032c-manifesto.png
│ │ ├── arena-explore.png
│ │ ├── babel.png
│ │ ├── index.txt
│ │ └── webshit.png
│ ├── 025-entropy
│ │ ├── entropy.svg
│ │ └── index.txt
│ └── index.txt
├── faq
│ └── index.txt
└── intro
│ ├── index.txt
│ └── loop.vtt
├── index.html
├── package.json
└── src
├── components
├── css.js
├── entry-blog.js
├── entry.js
├── format.js
├── input
│ ├── checkbox.js
│ ├── color.js
│ ├── data.js
│ ├── dropdown.js
│ ├── index.js
│ ├── range.js
│ ├── tags.js
│ ├── text.js
│ ├── textarea.js
│ └── typography.js
├── intro-slideshow.js
├── intro-video.js
├── loading.js
└── search.js
├── containers
├── blog.js
├── content.js
├── entry-list.js
├── entry-navigation.js
├── intro.js
├── notification.js
├── notifications
│ ├── customize-design.js
│ ├── index.js
│ └── p2p.js
├── panel-container.js
├── panel-entry.js
├── panel-options.js
├── suggestions.js
└── wrapper.js
├── db
├── dat.js
├── entries.js
├── index.js
├── localstorage.js
├── options.js
└── user.js
├── design
├── fonts.css
├── index.css
├── index.js
├── simplecolorpicker.css
├── typography.js
├── user.css
└── utils.js
├── helpers
├── scale.js
└── time.js
├── index.js
├── lib
├── blog.js
├── design.js
├── designs.js
├── entries.js
├── scale.js
└── time.js
├── plugins
├── app.js
├── data-storage.js
├── entries.js
├── features.js
├── index.js
├── logger.js
├── notifications.js
├── options-typography.json
├── options.js
├── scroll.js
├── search.js
├── staging.js
├── ui.js
└── user.js
├── sandbox
├── index.js
├── input.js
└── interface.js
└── templates
├── about.js
├── blog-entry.js
├── blog.js
├── data.js
├── faq.js
├── home.js
├── intro.js
├── panel.js
├── reset.js
└── suggestions.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "sourceMap": false,
3 | "presets": [
4 | ["@babel/preset-env", {
5 | "targets": {
6 | "browsers": ["last 2 versions"]
7 | }
8 | }]
9 | ],
10 | "plugins": [
11 | "@babel/plugin-transform-async-to-generator"
12 | ]
13 | }
--------------------------------------------------------------------------------
/.datignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # system
2 | .DS_Store
3 | .well-known
4 |
5 | # logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 |
10 | # dependency directories
11 | node_modules
12 |
13 | # database
14 | .db
15 | .dat
16 |
17 | # directories
18 | bundles
19 |
20 | # files
21 | credentials.json
22 | bundle.css
23 | bundle.js
24 | dat.json
25 | *.mp4
26 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
8 |
35 | `
36 |
37 | function handleClick (event) {
38 | emit('entries:dismiss', { id: data.id })
39 | }
40 |
41 | function handleClickEdit (event) {
42 | var staging = state.entries.all[data.id]
43 |
44 | // toggle
45 | if (data.id === state.staging.entry.id) {
46 | emit('staging:reset')
47 | emit('ui:panel', { view: '' })
48 | } else {
49 | if (data.id !== undefined && staging !== undefined) {
50 | emit('staging:entry', staging)
51 | emit('ui:panel', { view: 'entry' })
52 | }
53 | }
54 |
55 | event.preventDefault()
56 | }
57 |
58 | function handleClickDelete () {
59 | emit('staging:reset')
60 | emit('entries:remove', { id: data.id })
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/components/format.js:
--------------------------------------------------------------------------------
1 | var raw = require('choo/html/raw')
2 | var md = require('marked')
3 |
4 | module.exports = format
5 |
6 | function format (str) {
7 | return raw(md(str || ''))
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/input/checkbox.js:
--------------------------------------------------------------------------------
1 | var Component = require('choo/component')
2 | var html = require('choo/html')
3 | var xtend = require('xtend')
4 |
5 | module.exports = class Checkbox extends Component {
6 | constructor (name, state, emit) {
7 | super()
8 |
9 | this.local = {
10 |
11 | }
12 |
13 | this.handleChange = this.handleChange.bind(this)
14 | }
15 |
16 | handleChange () {
17 | if (
18 | this.local &&
19 | this.local.onChange &&
20 | typeof this.local.onChange === 'function'
21 | ) {
22 | this.local.onChange({
23 | value: !this.local.value
24 | })
25 | }
26 | }
27 |
28 | createElement (props) {
29 | this.local = xtend(this.local, props)
30 |
31 | var input = Input({
32 | onChange: this.handleChange,
33 | icon: this.local.icon || '✓',
34 | value: this.local.value
35 | })
36 |
37 | return Container({
38 | name: this.local.name || 'Untitled'
39 | }, input)
40 | }
41 |
42 | update (props) {
43 | return true
44 | }
45 | }
46 |
47 | function Container (props, children) {
48 | return html`
49 |
50 |
51 | ${props.name}
52 |
53 | ${children}
54 |
55 | `
56 | }
57 |
58 | function Input (props = { }) {
59 | return [
60 | html`
61 |
67 | `,
68 | html`
69 |
78 | ${props.value ? props.icon : ''}
79 |
80 | `
81 | ]
82 | }
83 |
--------------------------------------------------------------------------------
/src/components/input/color.js:
--------------------------------------------------------------------------------
1 | var ColorPicker = require('simple-color-picker')
2 | var Component = require('choo/component')
3 | var tinycolor = require('tinycolor2')
4 | var html = require('choo/html')
5 | var xtend = require('xtend')
6 |
7 | class Picker extends Component {
8 | constructor (name, state, emit) {
9 | super()
10 |
11 | this.frame
12 | this.local = {
13 | active: false,
14 | color: ''
15 | }
16 |
17 | this.handleClickSwatch = this.handleClickSwatch.bind(this)
18 | }
19 |
20 | load (element) {
21 | var self = this
22 |
23 | // skip if we have a color picker
24 | if (this.colorPicker) return
25 |
26 | this.colorPicker = new ColorPicker({
27 | color: tinycolor(this.local.color).toHexString(),
28 | el: element,
29 | width: 200,
30 | height: 200
31 | })
32 |
33 | this.colorPicker.onChange(function (data) {
34 | var current = self.local.color
35 | var update = tinycolor(data).toRgb()
36 | if (
37 | update.r === current.r &&
38 | update.g === current.r &&
39 | update.b === current.b
40 | ) return // skip if its the same
41 | self.local.color = tinycolor(data)
42 | if (typeof self.local.handleChange === 'function') {
43 | self.local.handleChange({
44 | rgb: update
45 | })
46 | }
47 | })
48 |
49 | this.colorPicker.$el.style.display = 'none'
50 | }
51 |
52 | unload () {
53 | this.local.active = false
54 | this.colorPicker.$el.style.display = 'none'
55 | }
56 |
57 | handleClickSwatch () {
58 | var cover = this.element.querySelector('[data-cover]')
59 | this.local.active = !this.local.active
60 | this.colorPicker.$el.style.display = this.local.active ? '' : 'none'
61 | if (cover) cover.style.display = this.local.active ? 'block' : 'none'
62 | }
63 |
64 | elCover () {
65 | return html`
66 |
72 | `
73 | }
74 |
75 | elCurrent () {
76 | var color = this.local.color
77 | return html`
78 |
82 | ${this.local.data.name}
83 |
94 |
95 | `
96 | }
97 |
98 | createElement (props) {
99 | this.local = xtend(this.local, props)
100 | return html`
101 |
102 | ${this.elCurrent()}
103 | ${this.colorPicker ? this.colorPicker.$el : ''}
104 | ${this.elCover()}
105 |
106 | `
107 | }
108 |
109 | update (props) {
110 | var elSwatch = this.element.querySelector('[data-swatch]')
111 | elSwatch.style.background = `rgb(${props.color.r}, ${props.color.g}, ${props.color.b})`
112 | this.local.color = props.color
113 |
114 | if (this.colorPicker) {
115 | this.colorPicker.setColor(tinycolor(props.color))
116 | }
117 |
118 | return false
119 | }
120 | }
121 |
122 | module.exports = Picker
123 |
--------------------------------------------------------------------------------
/src/components/input/data.js:
--------------------------------------------------------------------------------
1 | var Component = require('choo/component')
2 | var html = require('choo/html')
3 | var xtend = require('xtend')
4 |
5 | module.exports = class Data extends Component {
6 | constructor (id, state, emit) {
7 | super(id, state, emit)
8 |
9 | this.state = state
10 | this.emit = emit
11 | this.local = { }
12 |
13 | this.handleClick = this.handleClick.bind(this)
14 | }
15 |
16 | update (props) {
17 | return true
18 | }
19 |
20 | createElement (props) {
21 | this.local = xtend(this.local, props)
22 | var isDat = this.state.datastorage.isDat
23 |
24 | return html`
25 |
26 |
27 |
Data storage
28 |
→
29 |
${this.getStorageName()}
30 |
View
31 |
32 |
33 | `
34 | }
35 |
36 | getStorageName () {
37 | var archiveTitle = this.state.datastorage.archive.title
38 |
39 | // is dat
40 | if (this.state.datastorage.isDat) {
41 | // archive loaded
42 | if (archiveTitle) return archiveTitle
43 | else return 'Select an archive'
44 | } else {
45 | return 'Local browser'
46 | }
47 | }
48 |
49 | handleClick (event) {
50 | if (this.state.datastorage.isDat) {
51 | this.emit(this.state.events.DATA_DAT_LOAD)
52 | } else {
53 | // if not dat, go to data management
54 | // this.emit(this.state.events.PUSHSTATE, '/data')
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/components/input/dropdown.js:
--------------------------------------------------------------------------------
1 | var objectValues = require('object-values')
2 | var Component = require('choo/component')
3 | var html = require('choo/html')
4 | var xtend = require('xtend')
5 |
6 | module.exports = class Dropdown extends Component {
7 | constructor (name, state, emit) {
8 | super()
9 | this.state = state
10 | this.emit = emit
11 |
12 | this.local = {
13 | active: false,
14 | current: { }
15 | }
16 |
17 | this.handleOptionClick = this.handleOptionClick.bind(this)
18 | this.handleCurrentClick = this.handleCurrentClick.bind(this)
19 | this.handleScroll = this.handleScroll.bind(this)
20 | }
21 |
22 | handleOptionClick (data, event) {
23 | if (this.local.handleOptionClick) {
24 | this.local.handleOptionClick(data)
25 | }
26 | }
27 |
28 | handleCurrentClick (event) {
29 | if (this.local.handleCurrentClick) {
30 | this.local.handleCurrentClick()
31 | }
32 | this.local.active = !this.local.active
33 | this.rerender()
34 | }
35 |
36 | handleScroll (event) {
37 | event.stopPropagation()
38 | }
39 |
40 | elOption (data) {
41 | return html`
42 |
this.handleOptionClick(event)}
45 | style="
46 | font-family: ${data.value}, sans-serif;
47 | font-weight: ${data.weight || 400};
48 | font-style: ${data.style || 'normal'};
49 | "
50 | >
51 |
${data.name}
52 | ${data.author
53 | ? html`
54 |
61 | `
62 | : ''
63 | }
64 |
65 | `
66 | }
67 |
68 | elContainer () {
69 | var options = objectValues(this.local.options)
70 | return html`
71 |
77 | yo
78 |
79 | `
80 | }
81 |
82 | elCurrent () {
83 | return html`
84 |
this.handleCurrentClick({ }, event)}
87 | >
88 |
89 | ${this.local.name}
90 |
91 |
92 | `
93 | }
94 |
95 | createElement (props) {
96 | this.local = xtend(this.local, props)
97 | return html`
98 |
99 | ${this.elCurrent()}
100 | ${this.elContainer()}
101 |
102 | `
103 | }
104 |
105 | update (props) {
106 | return (
107 | props.current.key !== this.local.current.key ||
108 | props.current.active !== this.local.current.active
109 | )
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/components/input/index.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 |
3 | var inputTypography = require('./typography')
4 | var inputCheckbox = require('./checkbox')
5 | var inputTextarea = require('./textarea')
6 | var inputColor = require('./color')
7 | var inputRange = require('./range')
8 | var inputData = require('./data')
9 | var inputText = require('./text')
10 |
11 | module.exports = componentInput
12 |
13 | function componentInput (state, emit, option) {
14 | switch (option.type) {
15 | case 'text':
16 | return state
17 | .cache(inputText, 'panel:' + option.key)
18 | .render({
19 | key: option.key,
20 | name: option.name,
21 | value: state.options.values[option.key],
22 | onChange: function (data) {
23 | emit('options:values', {
24 | key: option.key,
25 | value: data.value
26 | })
27 | }
28 | })
29 | case 'textarea':
30 | return state
31 | .cache(inputTextarea, 'panel:' + option.key)
32 | .render({
33 | key: option.key,
34 | name: option.name,
35 | value: state.options.values[option.key],
36 | onInput: function (data) {
37 | emit('options:values', {
38 | key: option.key,
39 | value: data.value
40 | })
41 | }
42 | })
43 | case 'checkbox':
44 | return state
45 | .cache(inputCheckbox, 'panel:' + option.key)
46 | .render({
47 | key: option.key,
48 | name: option.name,
49 | value: state.options.values[option.key],
50 | onChange: function (data) {
51 | emit('options:values', {
52 | key: option.key,
53 | value: data.value
54 | })
55 | }
56 | })
57 | case 'color':
58 | var color = state.options.values[option.key]
59 | color = color || { r: 0, g: 0, b: 0 }
60 | return state
61 | .cache(inputColor, 'panel:' + option.key)
62 | .render({
63 | data: option,
64 | color: color,
65 | handleChange: function (value) {
66 | emit('options:values', {
67 | key: option.key,
68 | value: value.rgb
69 | })
70 | }
71 | })
72 | case 'range':
73 | return state
74 | .cache(inputRange, 'panel:' + option.key)
75 | .render({
76 | name: option.name,
77 | value: state.options.values[option.key],
78 | scaleValue: option.scaleValue,
79 | unit: option.unit,
80 | min: option.min,
81 | max: option.max,
82 | showValue: option.showValue,
83 | onInput: function (data) {
84 | emit('options:values', {
85 | key: option.key,
86 | value: data.value
87 | })
88 | }
89 | })
90 | case 'typography':
91 | return state
92 | .cache(inputTypography, 'panel' + option.key)
93 | .render({
94 | current: state.options.values.font,
95 | options: state.options.typography,
96 | children: option.children,
97 | handleCurrentClick: function () {
98 | emit('options:typography')
99 | },
100 | handleOptionClick: function (data) {
101 | emit('options:values', {
102 | key: option.key,
103 | value: data
104 | })
105 | }
106 | })
107 | case 'data':
108 | return state
109 | .cache(inputData, 'panel' + option.key)
110 | .render({ })
111 | default:
112 | return ''
113 | }
114 | }
--------------------------------------------------------------------------------
/src/components/input/range.js:
--------------------------------------------------------------------------------
1 | var Component = require('choo/component')
2 | var html = require('choo/html')
3 | var xtend = require('xtend')
4 |
5 | module.exports = class Range extends Component {
6 | constructor (name, state, emit) {
7 | super()
8 | this.local = { }
9 | this.handleInput = this.handleInput.bind(this)
10 | }
11 |
12 | handleInput (event) {
13 | if (
14 | this.local &&
15 | this.local.onInput &&
16 | typeof this.local.onInput === 'function'
17 | ) {
18 | var value = Math.floor(parseInt(event.target.value) / 10)
19 | this.local.onInput({
20 | value: value
21 | })
22 | }
23 | }
24 |
25 | createElement (props) {
26 | this.local = xtend(this.local, props)
27 |
28 | var input = Input({
29 | onInput: this.handleInput,
30 | value: this.local.value
31 | })
32 |
33 | var value = this.local.showValue
34 | ? [Value({ value: this.local.value, max: props.max, unit: props.unit, scale: props.scaleValue })]
35 | : ''
36 |
37 | return Container({
38 | name: this.local.name || 'Untitled'
39 | }, [input, value])
40 | }
41 |
42 | update (props) {
43 | return props.value !== this.local.value
44 | }
45 | }
46 |
47 | function Container (props, children) {
48 | return html`
49 |
50 |
51 | ${props.name}
52 |
53 | ${children}
54 |
55 | `
56 | }
57 |
58 | function Input (props = { }) {
59 | return [
60 | html`
61 |
70 | `,
71 | html`
72 |
80 | `
81 | ]
82 | }
83 |
84 | function Value (props = { }) {
85 | var value = props.scale ? Math.floor(props.value / 100 * props.max) : props.value
86 | if (props.unit) value += ' ' + props.unit
87 | return html`
88 |
89 | ${value}
90 |
91 | `
92 | }
93 |
--------------------------------------------------------------------------------
/src/components/input/tags.js:
--------------------------------------------------------------------------------
1 | var Component = require('choo/component')
2 | var tagsInput = require('tags-input')
3 | var html = require('choo/html')
4 | var xtend = require('xtend')
5 |
6 | module.exports = class Tags extends Component {
7 | constructor (name, state, emit) {
8 | super()
9 |
10 | this.local = {
11 | value: [ ],
12 | valueStart: [ ]
13 | }
14 |
15 | this.handleChange = this.handleChange.bind(this)
16 | }
17 |
18 | load (element) {
19 | tagsInput(this.element.querySelector('input'))
20 | }
21 |
22 | createElement (props) {
23 | this.local = xtend(this.local, props)
24 | if (!this.local.valueStart) this.local.valueStart = this.local.value
25 |
26 | return Container(
27 | { name: this.local.name || 'Untitled' },
28 | html`
`
33 | )
34 | }
35 |
36 | handleChange (event) {
37 | if (
38 | this.local &&
39 | this.local.onChange &&
40 | typeof this.local.onChange === 'function'
41 | ) {
42 | var value = event.target.value.split(',')
43 | if (!arraysEqual(this.local.value, value)) {
44 | this.local.value = value
45 | this.local.onChange({ value: value })
46 | }
47 | }
48 | }
49 |
50 | update (props) {
51 | var value = props.value || [ ]
52 |
53 | if (!arraysEqual(value, this.local.value)) {
54 | var el = this.element.querySelector('.tags-input')
55 | var elInput = this.element.querySelector('input')
56 | if (el) this.element.removeChild(el)
57 | this.local.value = value
58 | elInput.value = value
59 | tagsInput(elInput)
60 | }
61 |
62 | return false
63 | }
64 | }
65 |
66 | function Container (props, children) {
67 | return html`
68 |
69 | ${children}
70 |
71 | `
72 | }
73 |
74 | function arraysEqual (a, b) {
75 | if (a === b) return true
76 | if (a == null || b == null) return false
77 | if (a.length != b.length) return false
78 |
79 | for (var i = 0; i < a.length; ++i) {
80 | if (a[i] !== b[i]) return false
81 | }
82 |
83 | return true
84 | }
85 |
--------------------------------------------------------------------------------
/src/components/input/text.js:
--------------------------------------------------------------------------------
1 | var Component = require('choo/component')
2 | var html = require('choo/html')
3 | var xtend = require('xtend')
4 |
5 | module.exports = class Text extends Component {
6 | constructor (name, state, emit) {
7 | super()
8 |
9 | this.local = {
10 | autofocus: false,
11 | required: false
12 | }
13 |
14 | this.handleInput = this.handleInput.bind(this)
15 | }
16 |
17 | load () {
18 | if (this.local.autofocus) {
19 | this.element.querySelector('input').focus()
20 | }
21 | }
22 |
23 | handleInput (event) {
24 | if (
25 | this.local &&
26 | this.local.onInput &&
27 | typeof this.local.onInput === 'function'
28 | ) {
29 | this.local.onInput({
30 | value: event.target.value
31 | })
32 | }
33 | }
34 |
35 | update (props) {
36 | return true
37 | }
38 |
39 | createElement (props) {
40 | this.local = xtend(this.local, props)
41 |
42 | var input = Input({
43 | key: this.local.key,
44 | name: this.local.name,
45 | style: this.local.style || '',
46 | value: this.local.value || '',
47 | onInput: this.handleInput
48 | })
49 |
50 | return Container({
51 | name: this.local.name || 'Untitled'
52 | }, input)
53 | }
54 | }
55 |
56 | function Container (props = { }, children) {
57 | return html`
58 |
59 | ${children}
60 |
61 | `
62 | }
63 |
64 | function Input (props = { }) {
65 | return html`
66 |
75 | `
76 | }
77 |
--------------------------------------------------------------------------------
/src/components/input/textarea.js:
--------------------------------------------------------------------------------
1 | var Component = require('choo/component')
2 | var raw = require('choo/html/raw')
3 | var html = require('choo/html')
4 | var xtend = require('xtend')
5 |
6 | var libDesign = require('../../lib/design')
7 |
8 | module.exports = class Text extends Component {
9 | constructor (name, state, emit) {
10 | super()
11 |
12 | this.local = {
13 | active: false,
14 | autofocus: false,
15 | required: false
16 | }
17 |
18 | this.handleKeydown = this.handleKeydown.bind(this)
19 | this.handleInput = this.handleInput.bind(this)
20 | this.handleToggle = this.handleToggle.bind(this)
21 | this.handleReset = this.handleReset.bind(this)
22 | }
23 |
24 | load () {
25 | if (this.local.autofocus) {
26 | this.element.querySelector('input').focus()
27 | }
28 | }
29 |
30 | handleInput (event) {
31 | if (
32 | this.local &&
33 | this.local.onInput &&
34 | typeof this.local.onInput === 'function'
35 | ) {
36 | this.local.onInput({
37 | value: event.target.value
38 | })
39 | }
40 | }
41 |
42 | handleKeydown (event) {
43 | var keyCode = event.keyCode || event.which
44 | var element = event.target
45 |
46 | // only listen for tab
47 | if (keyCode !== 9) return
48 |
49 | event.preventDefault()
50 | var start = element.selectionStart
51 | var end = element.selectionEnd
52 |
53 | // set textarea value to: text before caret + tab + text after caret
54 | var spaces = " "
55 | element.value = element.value.substring(0, start) + spaces + element.value.substring(end)
56 | this.handleInput({ target: { value: element.value } })
57 |
58 | // put caret at right position again
59 | element.selectionStart = element.selectionEnd = start + spaces.length
60 | }
61 |
62 | update (props) {
63 | return (
64 | props.value !== this.local.value
65 | )
66 | }
67 |
68 | createElement (props) {
69 | this.local = xtend(this.local, props)
70 |
71 | var input = Input({
72 | key: this.local.key,
73 | name: this.local.name,
74 | style: this.local.style || '',
75 | value: this.local.value || '',
76 | onInput: this.handleInput,
77 | onKeydown: this.handleKeydown
78 | })
79 |
80 | return html`
81 |
82 |
86 | ${this.local.name}
87 |
88 | ${this.local.active ? '↑' : '↓'}
89 |
90 |
91 | ${this.local.active ? this.createReset() : ''}
92 | ${this.local.active ? input : ''}
93 |
94 | `
95 | }
96 |
97 | createReset () {
98 | return html`
99 |
Reset
103 | `
104 | }
105 |
106 | handleReset () {
107 | this.handleInput({ target: { value: libDesign.getCssDefaults() } })
108 | }
109 |
110 | handleToggle () {
111 | this.local.active = !this.local.active
112 | this.rerender()
113 | }
114 | }
115 | function Input (props) {
116 | return html`
117 |
126 | `
127 | }
128 |
--------------------------------------------------------------------------------
/src/components/input/typography.js:
--------------------------------------------------------------------------------
1 | var objectValues = require('object-values')
2 | var Component = require('choo/component')
3 | var html = require('choo/html')
4 | var xtend = require('xtend')
5 |
6 | var typography = require('../../design/typography')
7 |
8 | class Typography extends Component {
9 | constructor (name, state, emit) {
10 | super()
11 | this.state = state
12 | this.emit = emit
13 |
14 | this.local = {
15 | active: false,
16 | current: { }
17 | }
18 |
19 | this.handleOptionClick = this.handleOptionClick.bind(this)
20 | this.handleCurrentClick = this.handleCurrentClick.bind(this)
21 | this.handleScroll = this.handleScroll.bind(this)
22 | }
23 |
24 | handleOptionClick (data, event) {
25 | if (this.local.handleOptionClick) {
26 | this.local.handleOptionClick(data)
27 | }
28 | }
29 |
30 | handleCurrentClick (data, event) {
31 | if (this.local.handleCurrentClick) {
32 | this.local.handleCurrentClick()
33 | }
34 | this.local.active = !this.local.active
35 | this.rerender()
36 | }
37 |
38 | handleScroll (event) {
39 | event.stopPropagation()
40 | }
41 |
42 | elOption (data) {
43 | return html`
44 |
this.handleOptionClick(data, event)}
47 | style="
48 | font-family: ${data.value}, sans-serif;
49 | font-weight: ${data.weight || 400};
50 | font-style: ${data.style || 'normal'};
51 | "
52 | >
53 |
${data.name}
54 | ${data.author
55 | ? html`
56 |
63 | `
64 | : ''
65 | }
66 |
67 | `
68 | }
69 |
70 | elContainer () {
71 | var options = objectValues(this.local.options)
72 | return html`
73 |
79 | ${this.local.children}
80 | ${options.map(option => this.elOption(option))}
81 |
82 | `
83 | }
84 |
85 | elCurrent () {
86 | return html`
87 |
this.handleCurrentClick({ }, event)}
90 | >
91 |
92 | Font
93 |
94 |
95 | ${this.local.current.name}
96 |
97 |
98 | `
99 | }
100 |
101 | createElement (props) {
102 | this.local = xtend(this.local, props)
103 | return html`
104 |
105 | ${this.elCurrent()}
106 | ${this.elContainer()}
107 |
108 | `
109 | }
110 |
111 | update (props) {
112 | return (
113 | props.current.key !== this.local.current.key ||
114 | props.current.active !== this.local.current.active
115 | )
116 | }
117 | }
118 |
119 | module.exports = Typography
120 |
--------------------------------------------------------------------------------
/src/components/intro-slideshow.js:
--------------------------------------------------------------------------------
1 | var Component = require('choo/component')
2 | var html = require('choo/html')
3 | var xtend = require('xtend')
4 |
5 | module.exports = class Slideshow extends Component {
6 | constructor (name, state, emit) {
7 | super()
8 |
9 | this.local = {
10 | index: 0,
11 | src: [
12 | '/assets/img/screenshot.png',
13 | '/assets/img/screenshot-2.png',
14 | '/assets/img/screenshot-3.png',
15 | '/assets/img/screenshot-4.png',
16 | '/assets/img/screenshot-5.png',
17 | '/assets/img/screenshot-6.png'
18 | ]
19 | }
20 |
21 | // randomize the starting point
22 | this.local.index = Math.floor(Math.random() * this.local.src.length)
23 |
24 | // events
25 | this.handleClick = this.handleClick.bind(this)
26 | }
27 |
28 | load (element) {
29 | this.start()
30 | }
31 |
32 | unload (element) {
33 | this.stop()
34 | }
35 |
36 | start () {
37 | this.tick = setInterval(() => {
38 | this.local.index += 1
39 | this.rerender()
40 | }, 3000)
41 | }
42 |
43 | stop () {
44 | clearInterval(this.tick)
45 | }
46 |
47 | createElement (props) {
48 | props = props || { }
49 | this.local = xtend(this.local, props)
50 |
51 | var slide = mod(this.local.index, this.local.src.length)
52 | var next = mod(this.local.index + 1, this.local.src.length)
53 |
54 | return html`
55 |
56 |
61 |
62 |
63 | `
64 | }
65 |
66 | handleClick () {
67 | this.local.index += 1
68 | this.stop()
69 | this.start()
70 | this.rerender()
71 | }
72 |
73 | update (props) {
74 | return false
75 | }
76 | }
77 |
78 | function mod (num, mod) {
79 | var remain = num % mod
80 | return Math.floor(remain >= 0 ? remain : remain + mod)
81 | }
82 |
--------------------------------------------------------------------------------
/src/components/intro-video.js:
--------------------------------------------------------------------------------
1 | var Component = require('choo/component')
2 | var html = require('choo/html')
3 | var css = require('sheetify')
4 | var Plyr = require('plyr')
5 |
6 | css('plyr/dist/plyr.css')
7 |
8 | module.exports = class IntroVideo extends Component {
9 | constructor (name, state, emit) {
10 | super()
11 | this.state = state
12 | this.emit = emit
13 | this.local = { }
14 | }
15 |
16 | createElement (props) {
17 | return html`
18 |
19 |
20 |
21 |
22 |
23 |
24 | `
25 | }
26 |
27 | update (props) {
28 | return false
29 | }
30 |
31 | load (element) {
32 | setTimeout(() => {
33 | element.style.opacity = 1
34 | this.player = new Plyr(element.querySelector('video'), {
35 | controls: ['progress', /*'mute',*/ 'fullscreen'],
36 | captions: { active: true },
37 | settings: false,
38 | loadSprite: false,
39 | iconUrl: '/assets/plyr.svg'
40 | })
41 |
42 | this.player.toggleControls(false)
43 |
44 | this.player.on('play', () => {
45 | this.emit(this.state.events.UI_UPDATE, { introActive: true, introStarted: true })
46 | })
47 |
48 | this.player.on('pause', () => {
49 | this.emit(this.state.events.UI_UPDATE, { introActive: false })
50 | })
51 |
52 | this.player.on('controlsshown', () => {
53 | if (this.player.stopped) this.player.toggleControls(false)
54 | })
55 | }, 400)
56 | }
57 |
58 | unload (element) {
59 | delete this.player
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/components/loading.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 |
3 | module.exports = componentLoading
4 |
5 | function componentLoading (state, emit) {
6 | return html`
`
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/search.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 |
3 | module.exports = Search
4 |
5 | function Search (props = { }) {
6 | return html`
7 |
search
8 | `
9 | }
10 |
--------------------------------------------------------------------------------
/src/containers/blog.js:
--------------------------------------------------------------------------------
1 | var Component = require('choo/component')
2 | var html = require('choo/html')
3 | var xtend = require('xtend')
4 |
5 | var entryBlog = require('../components/entry-blog')
6 |
7 | module.exports = class Blog extends Component {
8 | constructor (name, state, emit) {
9 | super()
10 | this.state = state
11 | this.emit = emit
12 | this.local = {
13 | entries: [ ]
14 | }
15 | }
16 |
17 | createElement (props) {
18 | this.local = xtend(this.local, props)
19 | return html`
20 |
21 | ${this.createEntries(this.local.entries)}
22 |
23 | `
24 | }
25 |
26 | createEntries (entries) {
27 | var state = this.state
28 | var emit = this.emit
29 | return entries.map(function (props) {
30 | return html`
31 |
32 | ${entryBlog(state, emit, props)}
33 | ${createFooter(props)}
34 |
35 | `
36 | })
37 | }
38 |
39 | update (props) {
40 | return props.entries.length !== this.local.entries.length
41 | }
42 | }
43 |
44 | function createFooter (props) {
45 | return html`
46 |
49 | `
50 | }
--------------------------------------------------------------------------------
/src/containers/content.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 |
3 | var Panel = require('../containers/panel-container')
4 | var loading = require('../components/loading')
5 |
6 | module.exports = containerContent
7 |
8 | function containerContent (state, emit, children) {
9 | var panelProps = {
10 | isHoverActive: true && !state.ui.mobile,
11 | view: state.ui.panel.view,
12 | navChildren: html`
13 |
14 | ← Feed
15 |
16 | `
17 | }
18 |
19 | if (
20 | state.ui.panel.view !== '' &&
21 | !state.ui.panel.loadedContent
22 | ) {
23 | emit('ui:panel', { view: '', loadedContent: true })
24 | setTimeout(() => emit(state.events.RENDER), 20)
25 | }
26 |
27 | if (!state.site.loaded) {
28 | emit(state.events.CONTENT_LOAD)
29 | }
30 |
31 | return [
32 | Panel(state, panelProps, emit),
33 | createNavigation(state, emit),
34 | createContent()
35 | ]
36 |
37 | function createContent () {
38 | return html`
39 |
40 | ${state.site.loaded
41 | ? children
42 | : html`
${loading()}
`
43 | }
44 | ${createFooter(state, emit)}
45 |
46 | `
47 | }
48 | }
49 |
50 | function createNavigation (state, emit) {
51 | var pages = state.page('/').pages().visible().sortBy('name', 'asc').toArray()
52 |
53 | return html`
54 |
60 |
61 |
64 | ${pages.map(createLink)}
65 |
66 |
67 | `
68 |
69 | function createLink (props) {
70 | var isActive = state.href.indexOf(props.url) >= 0
71 | return html`
72 |
78 | `
79 | }
80 | }
81 |
82 | function createFooter (state, emit) {
83 | return html`
84 |
85 |
Hardly Everything
86 |
2018
87 |
88 | `
89 | }
90 |
--------------------------------------------------------------------------------
/src/containers/entry-list.js:
--------------------------------------------------------------------------------
1 | var objectValues = require('object-values')
2 | var html = require('choo/html')
3 | var dayjs = require('dayjs')
4 |
5 | var libEntries = require('../lib/entries')
6 | var Entry = require('../components/entry')
7 |
8 | module.exports = EntryList
9 |
10 | function EntryList (state, emit) {
11 | var entriesAll = Object.keys(state.entries.all)
12 | var elsEntries = entries()
13 | var isEntriesAll = entriesAll.length > 0
14 |
15 | var elContent = (isEntriesAll && elsEntries.length)
16 | ? elsEntries
17 | : (isEntriesAll && !elsEntries.length && !state.search.term)
18 | ? emptyEl()
19 | : (isEntriesAll && !elsEntries.length && state.search.term)
20 | ? emptySearchEl()
21 | : elEntriesNone(state, emit)
22 |
23 | var styleMobile = state.ui.mobile
24 | ? 'margin-top: 4.5rem;'
25 | : 'min-height: 100vh;'
26 |
27 | return html`
28 |
29 |
30 |
${elContent}
31 | ${isPaginatable() ? createPaginate() : ''}
32 |
33 |
34 | `
35 |
36 | function isPaginatable () {
37 | return state.entries.active.length > elsEntries.length
38 | }
39 |
40 | function createCounter () {
41 | return html`
42 |
43 | ${elsEntries.length}/${entriesAll.length}
44 |
45 | `
46 | }
47 |
48 | function createPaginate () {
49 | return html`
50 |
•••
55 | `
56 | }
57 |
58 | function entries () {
59 | var end = state.ui.pagination.page * state.ui.pagination.limit
60 | return state.entries.active
61 | .slice(0, end)
62 | .map(entry => Entry(state, entry, emit))
63 | }
64 |
65 | function handlePaginate (event) {
66 | emit(state.events.UI_PAGINATE, {
67 | page: state.ui.pagination.page + 1
68 | })
69 | }
70 | }
71 |
72 | function emptyEl () {
73 | return html`
74 |
75 | Nothing more for today
76 |
77 | `
78 | }
79 |
80 | function emptySearchEl () {
81 | return html`
82 |
83 | No matching entries
84 |
85 | `
86 | }
87 |
88 | function elEntriesNone (state, emit) {
89 | return html`
90 |
No entries to see here!
91 | `
92 | }
93 |
--------------------------------------------------------------------------------
/src/containers/entry-navigation.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 |
3 | module.exports = view
4 |
5 | function view(state, emit) {
6 | var shouldSearchHide =
7 | state.ui.entriesViewAll && !state.ui.mobile && state.entries.amount
8 |
9 | return html`
10 |
16 |
19 |
31 |
${elSearch()}
32 |
33 | `
34 |
35 | function handleAllClick() {
36 | emit('search:update', { value: '', render: false })
37 | emit('ui:update', { entriesViewAll: !state.ui.entriesViewAll })
38 | }
39 |
40 | function elSearch() {
41 | if (!state.features.search) {
42 | return ''
43 | }
44 |
45 | return html`
46 |
47 | ${navigationSearch({
48 | value: state.search.term,
49 | onFocus: function () {
50 | emit('search:update', {
51 | hidePanel: true,
52 | })
53 | },
54 | onInput: function (data) {
55 | emit('search:update', {
56 | all: true,
57 | value: data.value,
58 | hidePanel: true,
59 | render: true,
60 | })
61 | },
62 | })}
63 |
64 | `
65 | }
66 | }
67 |
68 | function navigationSearch(props = {}) {
69 | return html`
70 |
79 | `
80 |
81 | function handleInput(event) {
82 | if (props.onInput) {
83 | props.onInput({
84 | value: event.target.value,
85 | })
86 | }
87 | }
88 |
89 | function handleFocus(event) {
90 | if (props.onFocus) {
91 | props.onFocus({
92 | value: event.target.value,
93 | })
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/containers/notification.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 |
3 | var notifications = require('./notifications')
4 |
5 | module.exports = componentNotification
6 |
7 | function componentNotification (state, emit) {
8 | var notification = notifications[state.notifications.active]
9 | if (notification) return notification(state, emit)
10 | }
11 |
--------------------------------------------------------------------------------
/src/containers/notifications/customize-design.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 |
3 | var libDesign = require('../../lib/design')
4 |
5 | module.exports = customizeDesign
6 |
7 | function customizeDesign (state, emit) {
8 | return html`
9 |
10 | Freshen things up by customizing or randomizing the designDismiss
11 |
12 | `
13 |
14 | function handleDismissClick (event) {
15 | emit(state.events.USER_NOTIFIED, { id: 'customizeDesign' })
16 | }
17 |
18 | function handleRandomClick (event) {
19 | var design = libDesign.getRandomDesign()
20 | emit('options:replace', design)
21 | }
22 |
23 | function handleOpenClick (event) {
24 | emit('ui:panel', { view: 'options'} )
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/containers/notifications/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | customizeDesign: require('./customize-design'),
3 | p2p: require('./p2p')
4 | }
5 |
--------------------------------------------------------------------------------
/src/containers/notifications/p2p.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 |
3 | var libDesign = require('../../lib/design')
4 |
5 | module.exports = customizeDesign
6 |
7 | function customizeDesign (state, emit) {
8 | return html`
9 |
10 | Enjoy offline accessibility and save data locally by visiting with
Beaker Browser Dismiss
11 |
12 | `
13 |
14 | function handleDismissClick (event) {
15 | emit(state.events.USER_NOTIFIED, { id: 'p2p' })
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/containers/panel-container.js:
--------------------------------------------------------------------------------
1 | var objectValues = require('object-values')
2 | var html = require('choo/html')
3 |
4 | var panelOptions = require('../containers/panel-options')
5 | var panelEntry = require('../containers/panel-entry')
6 |
7 | var hideFrame
8 |
9 | module.exports = view
10 |
11 | function view (state, props, emit) {
12 | props = props || { }
13 |
14 | var views = {
15 | entry: {
16 | title: state.staging.entry.id
17 | ? 'Edit'
18 | : html`
Follow`,
19 | path: 'entry',
20 | view: () => panelEntry(state, emit)
21 | },
22 | options: {
23 | title: 'Options',
24 | path: 'options',
25 | view: () => panelOptions(state, emit)
26 | }
27 | }
28 |
29 | var view = views[props.view]
30 | var content = (
31 | view && view.view &&
32 | typeof view.view === 'function'
33 | ) ? html`
34 |
39 |
40 |
41 |
46 | ${view.view()}
47 |
48 |
49 |
50 | `
51 | : ''
52 |
53 | return html`
54 |
59 |
63 |
${navigation()}
66 | ${content}
67 |
68 |
69 | `
70 |
71 | function navigation () {
72 | return html`
73 |
74 | ${props.navChildren}
75 | ${objectValues(views)
76 | .filter(view => view.active !== false)
77 | .map(navigationLink)
78 | }
79 |
80 | `
81 | }
82 |
83 | function navigationLink (view) {
84 | var active = view.path === props.view
85 | var href = active && state.href !== ''
86 | ? '/'
87 | : '/panel/' + view.path
88 |
89 | return html`
90 |
${view.title}
99 | `
100 |
101 | function handleLinkEnter () {
102 | clearTimeout(hideFrame)
103 | if (
104 | props.isHoverActive &&
105 | view.path !== props.view
106 | ) {
107 | emit('ui:panel', { view: view.path })
108 | }
109 | }
110 | }
111 |
112 | function handleContainerEnter () {
113 | clearTimeout(hideFrame)
114 | }
115 |
116 | function handleContainerClick (event) {
117 | if (event.target.hasAttribute('data-panel')) {
118 | emit('staging:reset')
119 | if (props.isHoverActive) {
120 | emit('ui:panel', { view: '' })
121 | } else {
122 | emit('pushState', '/')
123 | }
124 | }
125 | }
126 |
127 | function handlePanelLeave (event) {
128 | if (props.isHoverActive && props.view) {
129 | clearTimeout(hideFrame)
130 | hideFrame = setTimeout(() => hide(), 750)
131 | }
132 | }
133 |
134 | function hide () {
135 | emit('staging:reset')
136 | emit('ui:panel', { view: '' })
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/containers/panel-entry.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 | var xtend = require('xtend')
3 |
4 | var { intToRest } = require('../lib/time')
5 | var InputRange = require('../components/input/range')
6 | var InputText = require('../components/input/text')
7 | var InputTags = require('../components/input/tags')
8 |
9 | module.exports = panelEntry
10 |
11 | function panelEntry (state, emit) {
12 | return html`
13 |
131 | `
132 |
133 | function getTime (value) {
134 | return intToRest({
135 | value: value
136 | })
137 | }
138 |
139 | function handleSubmit (event) {
140 | if (state.staging.entry.id) {
141 | emit('entries:update', state.staging.entry)
142 | } else {
143 | emit('entries:add', state.staging.entry)
144 | }
145 |
146 | emit('ui:panel', { view: '' })
147 | event.preventDefault()
148 | }
149 |
150 | function reset () {
151 | emit('staging:reset')
152 | emit('pushState', '/')
153 | }
154 |
155 | function remove (id) {
156 | reset()
157 | emit('ui:panel', { view: '' })
158 | emit('entries:remove', { id: id })
159 | }
160 |
161 | function isTagsVisible () {
162 | return state.features && state.features.tags
163 | }
164 |
165 | function isStepTwo () {
166 | return state.staging.entry.url
167 | }
168 |
169 | function isStepThree () {
170 | return state.staging.entry.url && state.staging.entry.title
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/src/containers/panel-options.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 | var xtend = require('xtend')
3 |
4 | var input = require('../components/input')
5 |
6 | module.exports = view
7 |
8 | function view (state, emit) {
9 | var disabled = isDisabled()
10 |
11 | return html`
12 |
16 |
17 |
18 | ${input(state, emit, xtend(state.options.design.font, {
19 | children: createFontOptions()
20 | }))}
21 |
22 |
23 |
24 |
25 |
26 | ${input(state, emit, state.options.design.scale)}
27 |
28 |
29 |
30 |
31 | ${input(state, emit, state.options.design.spacing)}
32 |
33 |
34 |
35 |
36 |
37 |
38 | ${input(state, emit, state.options.design.colorBg)}
39 |
40 |
41 | ${input(state, emit, state.options.design.colorText)}
42 |
43 |
44 |
45 |
46 | Invert
47 |
48 |
49 |
50 |
51 |
52 | ${input(state, emit, state.options.design.css)}
53 |
54 |
55 |
56 |
57 | ${input(state, emit, state.options.design.entropy)}
58 |
59 |
60 |
61 |
62 | ${input(state, emit, state.options.design.newTab)}
63 |
64 |
65 |
66 |
67 | ${input(state, emit, state.options.design.autoDismiss)}
68 |
69 |
70 |
71 |
72 | ${input(state, emit, state.options.data)}
73 |
74 |
75 |
92 | ${disabled ? createOverlay() : ''}
93 |
94 | `
95 |
96 | function isDisabled () {
97 | return !state.entries.amount && state.href !== '/panel/options'
98 | }
99 |
100 | function createOverlay () {
101 | return html`
102 |
110 | `
111 | }
112 |
113 | function createFontOptions () {
114 | return html`
115 |
116 |
117 | ${input(state, emit, state.options.design.uppercase)}
118 |
119 |
120 | ${input(state, emit, state.options.design.hyphenate)}
121 |
122 |
123 | `
124 | }
125 |
126 | function handleInvertClick () {
127 | emit('options:invert')
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/containers/wrapper.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 | var css = require('../components/css')
3 |
4 | module.exports = wrapper
5 |
6 | function wrapper(view) {
7 | return function (state, emit) {
8 | return state.app.loaded
9 | ? container(view(state, emit))
10 | : container(loading())
11 |
12 | function container(content) {
13 | return html`
14 |
15 | ${!state.user.cyclesDismissed
16 | ? html`
17 |
37 |
42 | `
43 | : ''}
44 | ${css(state, emit)} ${content} ${preloadFonts()}
45 |
46 | `
47 | }
48 | }
49 | }
50 |
51 | function loading() {
52 | return html`
`
53 | }
54 |
55 | function preloadFonts() {
56 | return html`
57 |
58 |
mono
59 |
serif
60 |
61 | `
62 | }
63 |
--------------------------------------------------------------------------------
/src/db/dat.js:
--------------------------------------------------------------------------------
1 | var xtend = require('xtend')
2 |
3 | var saveTimeouts = { }
4 | var modalActive
5 | var archive
6 |
7 | module.exports = { get, save, reset }
8 |
9 | async function load () {
10 | // load from localstorage
11 | var archiveUrl = window.localStorage.archiveUrl
12 | if (modalActive) return // skip if already choosing
13 |
14 | if (!archiveUrl) {
15 | // modalActive = true
16 | archive = await DatArchive.selectArchive({
17 | title: 'Select an archive to use as your user profile',
18 | buttonLabel: 'Select profile',
19 | filters: { isOwner: true }
20 | })
21 | window.localStorage.archiveUrl = archive.url
22 | } else {
23 | archive = await DatArchive.load(archiveUrl)
24 | }
25 | // modalActive = false
26 | }
27 |
28 | async function reset () {
29 | // reset
30 | var currentUrl = window.localStorage.archiveUrl
31 |
32 | try {
33 | window.localStorage.archiveUrl = ''
34 | return await load()
35 | } catch (err) {
36 | window.localStorage.archiveUrl = currentUrl
37 | return false
38 | }
39 | }
40 |
41 | async function get (namespace, callback) {
42 | if (window.localStorage.archiveUrl) {
43 | // load
44 | if (!archive) await load()
45 |
46 | try {
47 | var state = await archive.readFile(namespace + '.json')
48 | var output = JSON.parse(state)
49 | if (typeof callback === 'function') callback(output)
50 | return output
51 | } catch (err) {
52 | if (typeof callback === 'function') callback({ })
53 | }
54 | } else {
55 | // skip if no archive selected
56 | if (typeof callback === 'function') callback({ })
57 | }
58 | }
59 |
60 | async function save (namespace, state, callback) {
61 | var output = { }
62 |
63 | // load
64 | if (!archive) {
65 | await load()
66 | output = await get(namespace)
67 | }
68 |
69 | // throttle saving
70 | clearTimeout(saveTimeouts[namespace])
71 | saveTimeouts[namespace] = setTimeout(async function () {
72 | // write state
73 | await archive.writeFile(
74 | namespace + '.json',
75 | JSON.stringify(xtend(output, state), { }, 2)
76 | )
77 | //callback
78 | if (typeof callback === 'function') callback()
79 | }, 500)
80 | }
81 |
--------------------------------------------------------------------------------
/src/db/entries.js:
--------------------------------------------------------------------------------
1 | var db = require('./index')
2 | var namespace = 'entries'
3 |
4 | module.exports = { add, update, remove, dismiss, get }
5 |
6 | function add (data, state) {
7 | db.save(namespace, state)
8 | }
9 |
10 | function update (data, state) {
11 | db.save(namespace, state)
12 | }
13 |
14 | function remove (data, state) {
15 | db.save(namespace, state)
16 | }
17 |
18 | function dismiss (data, state) {
19 | db.save(namespace, state)
20 | }
21 |
22 | function get (cb) {
23 | db.get(namespace, cb)
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/src/db/index.js:
--------------------------------------------------------------------------------
1 | module.exports = load()
2 |
3 | function load () {
4 | return (typeof DatArchive !== 'undefined')
5 | ? require('./dat')
6 | : require('./localstorage')
7 | }
8 |
--------------------------------------------------------------------------------
/src/db/localstorage.js:
--------------------------------------------------------------------------------
1 | var STORAGE_ID = 'asdf_'
2 | var dat = require('./dat')
3 |
4 | module.exports = { save, get }
5 |
6 | function get (namespace, cb) {
7 | try {
8 | var result = JSON.parse(window.localStorage[STORAGE_ID + namespace])
9 | if (cb && typeof cb === 'function') cb(result)
10 | } catch (err) {
11 | if (cb && typeof cb === 'function') cb({ })
12 | }
13 | }
14 |
15 | function save (namespace, state, cb) {
16 | window.localStorage[STORAGE_ID + namespace] = JSON.stringify(state)
17 | if (cb && typeof cb === 'function') cb()
18 | }
19 |
--------------------------------------------------------------------------------
/src/db/options.js:
--------------------------------------------------------------------------------
1 | var db = require('./index')
2 | var namespace = 'options'
3 |
4 | module.exports = { get, update }
5 |
6 | function update (data, state) {
7 | db.save(namespace, state)
8 | }
9 |
10 | function get (cb) {
11 | db.get(namespace, cb)
12 | }
13 |
--------------------------------------------------------------------------------
/src/db/user.js:
--------------------------------------------------------------------------------
1 | var db = require('./index')
2 | var namespace = 'user'
3 |
4 | module.exports = { get, update }
5 |
6 | function update (data, state) {
7 | db.save(namespace, state)
8 | }
9 |
10 | function get (cb) {
11 | db.get(namespace, cb)
12 | }
13 |
--------------------------------------------------------------------------------
/src/design/index.js:
--------------------------------------------------------------------------------
1 | var css = require('sheetify')
2 |
3 | css('nanoreset')
4 | css('./index.css')
5 | css('./fonts.css')
6 | css('./utils.js')
7 | css('./simplecolorpicker.css')
8 |
--------------------------------------------------------------------------------
/src/design/simplecolorpicker.css:
--------------------------------------------------------------------------------
1 | .Scp {
2 | -webkit-user-select: none;
3 | -moz-user-select: none;
4 | -ms-user-select: none;
5 | user-select: none;
6 | position: relative;
7 | background: #000;
8 | border-radius: 2px;
9 | padding: 2px!important;
10 | display: flex;
11 | position: absolute;
12 | top: -7rem;
13 | left: 2rem;
14 | z-index: 9999;
15 | }
16 |
17 | .Scp:before {
18 | content: '';
19 | display: none;
20 | position: absolute;
21 | top: -1rem;
22 | left: 2rem;
23 | width: 0;
24 | height: 0;
25 | border-style: solid;
26 | border-width: 0 1rem 1rem 1rem;
27 | border-color: transparent transparent #000 transparent;
28 | }
29 |
30 | .Scp-saturation {
31 | position: relative;
32 | height: 100%;
33 | cursor: crosshair;
34 | background: linear-gradient(to right, #fff, #f00);
35 | }
36 |
37 | .Scp-brightness {
38 | width: 100%;
39 | height: 100%;
40 | background: linear-gradient(rgba(255,255,255,0), #000);
41 | }
42 | .Scp-sbSelector {
43 | border: 2px solid #fff;
44 | position: absolute;
45 | width: 14px;
46 | height: 14px;
47 | background: #fff;
48 | border-radius: 10px;
49 | top: -7px;
50 | left: -7px;
51 | box-sizing: border-box;
52 | z-index: 10;
53 | }
54 | .Scp-hue {
55 | width: 19px;
56 | margin-left: 2px;
57 | height: 100%;
58 | position: relative;
59 | background: linear-gradient(#f00 0%, #f0f 17%, #00f 34%, #0ff 50%, #0f0 67%, #ff0 84%, #f00 100%);
60 | cursor: ns-resize;
61 | }
62 | .Scp-hSelector {
63 | position: absolute;
64 | background: #fff;
65 | border-bottom: 1px solid #000;
66 | right: -3px;
67 | width: 10px;
68 | height: 2px;
69 | }
70 |
--------------------------------------------------------------------------------
/src/design/typography.js:
--------------------------------------------------------------------------------
1 | var webfontloader = require('webfontloader')
2 |
3 | exports.local = (cb) => {
4 | if (cb && typeof cb === 'function') {
5 | cb()
6 | }
7 | }
8 |
9 | exports.load = (data, emit) => {
10 | switch (data.host) {
11 | case 'google':
12 | var value = data.weight
13 | ? data.value + ':' + data.weight + (data.style === 'italic' ? 'i' : '')
14 | : data.value
15 | return webfontloader.load({
16 | google: {
17 | families: [value]
18 | },
19 | fontactive: function () {
20 | emit('options:loaded', { typeCustom: true })
21 | emit('options:typography', {
22 | key: data.key,
23 | value: { active: true }
24 | })
25 | emit('app:render')
26 | }
27 | })
28 | case 'local':
29 | return webfontloader.load({
30 | custom: { families: [data.value] },
31 | fontactive: function () {
32 | emit('options:loaded', { typeCustom: true })
33 | emit('options:typography', {
34 | key: data.key,
35 | value: { active: true }
36 | })
37 | emit('app:render')
38 | }
39 | })
40 | default:
41 | emit('options:loaded', { typeCustom: true })
42 | emit('app:render')
43 | return false
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/design/user.css:
--------------------------------------------------------------------------------
1 | .design-container {
2 | /* layout */
3 | --gutter: calc(var(--spacing) * 0.8) var(--spacing);
4 |
5 | /* typography */
6 | color: var(--foreground);
7 | font-family: var(--font-family);
8 | font-weight: var(--font-weight);
9 | font-style: var(--font-style);
10 | font-size: var(--font-size);
11 | text-transform: var(--font-uppercase);
12 | hyphens: var(--font-hyphenate);
13 | line-height: 1.2;
14 | }
--------------------------------------------------------------------------------
/src/design/utils.js:
--------------------------------------------------------------------------------
1 | var gr8 = require('gr8')
2 | var lilcss = require('lilcss')
3 |
4 | var output = ''
5 | var utils = [ ]
6 |
7 | var lilsrc = [
8 | 'containers/*.js',
9 | 'components/**/*.js',
10 | 'templates/*.js',
11 | 'sandbox/*.js',
12 | 'index.js'
13 | ].map(p => 'src/' + p)
14 |
15 | var lilopts = {
16 | ignore: ['psa', 'psr', 't0', 'b0', 'l0', 'r0']
17 | }
18 |
19 | utils.push({
20 | prop: { ps: 'position' },
21 | vals: { st: 'sticky' }
22 | })
23 |
24 | utils.push({
25 | prop: 'width',
26 | unit: '%',
27 | vals: [50]
28 | })
29 |
30 | utils.push({
31 | prop: { wrem: 'width' },
32 | unit: 'rem',
33 | vals: [40, 60]
34 | })
35 |
36 | utils.push({
37 | prop: { wmxrem: 'max-width' },
38 | unit: 'rem',
39 | vals: [40, 50, 60, 70]
40 | })
41 |
42 | utils.push({
43 | prop: { mwrem: 'max-width' },
44 | unit: 'rem',
45 | vals: [43]
46 | })
47 |
48 | utils.push({
49 | prop: { op: 'opacity' },
50 | vals: [{ 33: 0.3 }]
51 | })
52 |
53 | utils.push({
54 | prop: { ptvh: 'padding-top' },
55 | unit: 'vh',
56 | vals: [25, 50, 75, 100]
57 | })
58 |
59 | utils.push({
60 | prop: { mtpx: 'margin-top' },
61 | unit: 'rem',
62 | vals: [0, 2].map(function (size) { return { [size]: size / 10 } })
63 | })
64 |
65 | output = gr8({
66 | breakpoints: {
67 | sm: 600,
68 | md: 800,
69 | lg: 1000
70 | },
71 | lineHeight: [1, 1.2, 1.5],
72 | fontSize: [0.7, 0.8, 1, 1.2, 1.4, 1.5, 1.6, 1.8, 2, 3, 4]
73 | .map(function (size) {
74 | return { [size.toString().replace('.', '-')]: size * 1.5 }
75 | }),
76 | spacing: [0, 0.25, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 3.6, 4, 4.5, 5]
77 | .map(function (size) {
78 | return { [size.toString().replace('.', '-')]: size * 1.25 }
79 | }),
80 | responsive: true,
81 | utils: utils
82 | })
83 |
84 | // remove unused classes
85 | if (process.env.NODE_ENV === 'production') {
86 | output = lilcss(output, lilsrc, lilopts)
87 | }
88 |
89 | module.exports = output
90 |
--------------------------------------------------------------------------------
/src/helpers/scale.js:
--------------------------------------------------------------------------------
1 | const x = require('xtend')
2 |
3 | const linearConversion = opts => {
4 | const options = x({
5 | value: 30,
6 | in: {
7 | min: 0,
8 | max: 100
9 | },
10 | out: {
11 | min: 25,
12 | max: 75
13 | }
14 | }, opts)
15 |
16 | const input = ((options.value - options.in.min) / (options.in.max - options.in.min))
17 | const output = (options.out.max - options.out.min)
18 | const result = input * output + options.out.min
19 |
20 | return result
21 | }
22 |
23 | module.exports = {
24 | linearConversion
25 | }
--------------------------------------------------------------------------------
/src/helpers/time.js:
--------------------------------------------------------------------------------
1 | const x = require('xtend')
2 |
3 | const intToRest = opts => {
4 | const options = x({
5 | min: 1,
6 | max: 100,
7 | value: 4,
8 | breakpoints: [{
9 | interval: 'days',
10 | result: {
11 | min: 1,
12 | max: 30
13 | },
14 | range: {
15 | min: 0,
16 | max: 0.6
17 | }
18 | }, {
19 | interval: 'weeks',
20 | result: {
21 | min: 1,
22 | max: 6
23 | },
24 | range: {
25 | min: 0.6,
26 | max: 0.8
27 | }
28 | }, {
29 | interval: 'months',
30 | result: {
31 | min: 1,
32 | max: 12
33 | },
34 | range: {
35 | min: 0.8,
36 | max: 1
37 | }
38 | }]
39 | }, opts)
40 |
41 | const value = options.value / options.max
42 |
43 | const breakpoint = options.breakpoints
44 | .find(bp => {
45 | if (
46 | value >= bp.range.min &&
47 | value < bp.range.max
48 | ) {
49 | return true
50 | } else if (
51 | options.breakpoints.indexOf(bp) ===
52 | options.breakpoints.length - 1
53 | ) {
54 | return true
55 | } else {
56 | return false
57 | }
58 | })
59 |
60 | const scale = {
61 | input: (value - breakpoint.range.min)
62 | / (breakpoint.range.max - breakpoint.range.min),
63 | output: (breakpoint.result.max - breakpoint.result.min)
64 | + breakpoint.result.min
65 | }
66 |
67 | const result = {
68 | duration: Math.ceil(scale.input * scale.output),
69 | interval: breakpoint.interval
70 | }
71 |
72 | return {
73 | duration: result.duration < 1 ? 1 : result.duration,
74 | interval: result.interval
75 | }
76 | }
77 |
78 | module.exports = {
79 | intToRest
80 | }
81 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | require('intersection-observer')
2 | require('babel-polyfill')
3 | require('./design')
4 |
5 | var html = require('choo/html')
6 | var choo = require('choo')
7 |
8 | var wrapper = require('./containers/wrapper')
9 | var app = choo()
10 |
11 | // plugins
12 | require('./plugins').forEach(plugin => app.use(plugin))
13 | app.use(require('enoki/choo')('content', {
14 | autoload: false
15 | }))
16 |
17 | // app
18 | app.route('/', wrapper(require('./templates/home')))
19 | app.route('/all', wrapper(require('./templates/home')))
20 | app.route('/suggestions', wrapper(require('./templates/suggestions')))
21 |
22 | // content
23 | app.route('/about', wrapper(require('./templates/about')))
24 | app.route('/blog', wrapper(require('./templates/blog')))
25 | app.route('/blog/:entry', wrapper(require('./templates/blog-entry')))
26 | app.route('/faq', wrapper(require('./templates/faq')))
27 | app.route('/intro', wrapper(require('./templates/intro')))
28 |
29 | // panel
30 | app.route('/panel', wrapper(require('./templates/panel')))
31 | app.route('/panel/:view', wrapper(require('./templates/panel')))
32 | app.route('/panel/:view/:id', wrapper(require('./templates/panel')))
33 |
34 | // data
35 | app.route('/data', wrapper(require('./templates/data')))
36 | app.route('/data/:command', wrapper(require('./templates/data')))
37 | app.route('/reset', wrapper(require('./templates/reset')))
38 |
39 | // dev
40 | if (process.env.NODE_ENV === 'development') {
41 | // app.route('/sandbox', wrapper(require('./sandbox')))
42 | // app.route('/sandbox/:component', wrapper(require('./sandbox')))
43 | }
44 |
45 | // start
46 | module.exports = app.mount('body')
47 |
--------------------------------------------------------------------------------
/src/lib/blog.js:
--------------------------------------------------------------------------------
1 | var objectKeys = require('object-keys')
2 | var xtend = require('xtend')
3 |
4 | module.exports = {
5 | getSuggestions,
6 | getRandomSuggestions
7 | }
8 |
9 | function getRandomSuggestions (state) {
10 | return getSuggestions(state)
11 | }
12 |
13 | function getSuggestions (state) {
14 | return state.page('/blog')
15 | .pages()
16 | .visible()
17 | .sortBy('date', 'desc')
18 | .toArray()
19 | .filter(function (page) {
20 | return page.category === 'list'
21 | })
22 | .reduce(function (res, cur) {
23 | objectKeys(cur.links)
24 | .forEach(function (key) {
25 | return res.push(xtend(
26 | cur.links[key], {
27 | author: cur.author,
28 | authorUrl: cur.authorUrl,
29 | interval: key
30 | }
31 | ))
32 | })
33 | return res
34 | }, [ ])
35 | }
36 |
--------------------------------------------------------------------------------
/src/lib/design.js:
--------------------------------------------------------------------------------
1 | var optionsTypography = require('../plugins/options-typography')
2 | var objectKeys = require('object-keys')
3 | var deepEqual = require('deep-equal')
4 |
5 | var designs = require('./designs')
6 | var designsAll = objectKeys(designs)
7 | var randomized = [ ]
8 |
9 | module.exports = {
10 | isDesignDefault,
11 | getRandomDesign,
12 | getDesignDefaults,
13 | getCssDefaults
14 | }
15 |
16 | function isDesignDefault (state, opts) {
17 | var defaults = getDesignDefaults()
18 | var current = objectKeys(defaults).reduce(function (res, cur) {
19 | res[cur] = state.options.values[cur]
20 | return res
21 | }, { })
22 | return deepEqual(defaults, current)
23 | }
24 |
25 | function getDesignDefaults () {
26 | return {
27 | invert: false,
28 | colorBg: { a: 1, r: 255, g: 255, b: 255 },
29 | colorText: { a: 1, r: 0, g: 0, b: 0 },
30 | font: optionsTypography.system,
31 | uppercase: false,
32 | hyphenate: false,
33 | scale: 40,
34 | spacing: 36
35 | }
36 | }
37 |
38 | function getRandomDesign () {
39 | // reset
40 | if (designsAll.length === 0) {
41 | designsAll = randomized
42 | randomized = [ ]
43 | }
44 |
45 | var randomKey = designsAll[Math.floor(Math.random() * designsAll.length)]
46 | designsAll.splice(designsAll.indexOf(randomKey), 1)
47 | randomized.push(randomKey)
48 | return designs[randomKey]
49 | }
50 |
51 | function getCssDefaults () {
52 | return `
53 | .design-container {
54 | color: var(--foreground);
55 | font-family: var(--font-family);
56 | font-weight: var(--font-weight);
57 | font-style: var(--font-style);
58 | font-size: var(--font-size);
59 | text-transform: var(--font-uppercase);
60 | hyphens: var(--font-hyphenate);
61 | line-height: 1.2;
62 |
63 | --gutter:
64 | calc(var(--spacing) * 0.8)
65 | var(--spacing);
66 | }
67 | `
68 | }
--------------------------------------------------------------------------------
/src/lib/designs.js:
--------------------------------------------------------------------------------
1 | var type = require('../plugins/options-typography')
2 |
3 | exports.simpleDesign = {
4 | colorBg: { a:1, r: 0, g: 0, b: 255 },
5 | colorText: { a:1, r: 255, g: 255, b: 255 },
6 | font: type.reglo,
7 | uppercase: true,
8 | hyphenate: false,
9 | scale: 30,
10 | spacing: 40,
11 | }
12 |
13 | exports.greenOne = {
14 | "colorBg": { "r": 0, "g": 131, "b": 87, "a": 1 },
15 | "colorText": { "r": 255, "g": 255, "b": 255, "a": 1 },
16 | "font": type.wremenaLight,
17 | "uppercase": false,
18 | "hyphenate": false,
19 | "scale": 42,
20 | "spacing": 30
21 | }
22 |
23 | exports.purpleExpanded = {
24 | "colorBg": { "r": 186, "g": 82, "b": 195, "a": 1 },
25 | "colorText": { "r": 255, "g": 255, "b": 255, "a": 1 },
26 | "font": type.lunchtype24MediumExpanded,
27 | "uppercase": false,
28 | "hyphenate": false,
29 | "scale": 32,
30 | "spacing": 10
31 | }
32 |
33 | exports.paper = {
34 | "colorBg": { "r": 231, "g": 231, "b": 231, "a": 1 },
35 | "colorText": { "r": 46, "g": 46, "b": 46, "a": 1 },
36 | "font": type.junicodeBoldCondensed,
37 | "uppercase": false,
38 | "hyphenate": true,
39 | "scale": 63,
40 | "spacing": 18
41 | }
42 |
43 | // red
44 | exports.redone = {
45 | "colorBg": { "r": 255, "g": 205, "b": 205, "a": 1 },
46 | "colorText": { "r": 255, "g": 0, "b": 0, "a": 1 },
47 | "font": type.yatraRegular,
48 | "uppercase": false,
49 | "hyphenate": false,
50 | "scale": 32,
51 | "spacing": 41
52 | }
53 |
54 | exports.blueSerif = {
55 | "colorBg": { "r": 210, "g": 252, "b": 255, "a": 1 },
56 | "colorText": { "r": 0, "g": 8, "b": 255, "a": 1 },
57 | "font": type.sportingGrotesque,
58 | "uppercase": false,
59 | "hyphenate": false,
60 | "scale": 30,
61 | "spacing": 74
62 | }
63 |
64 | exports.authentic = {
65 | "colorBg": { "r": 170, "g": 170, "b": 170, "a": 1 },
66 | "colorText": { "r": 255, "g": 255, "b": 255, "a": 1 },
67 | "font": type.authenticSans,
68 | "uppercase": false,
69 | "hyphenate": false,
70 | "scale": 33,
71 | "spacing": 30
72 | }
73 |
74 | exports.punky = {
75 | "colorBg": { "r": 255, "g": 255, "b": 255, "a": 1 },
76 | "colorText": { "r": 255, "g": 83, "b": 177, "a": 1 },
77 | "font": type.jrugPunk,
78 | "uppercase": false,
79 | "hyphenate": false,
80 | "scale": 33,
81 | "spacing": 30
82 | }
83 |
84 | exports.fraktur = {
85 | "colorBg": { "r": 0, "g": 0, "b": 0, "a": 1 },
86 | "colorText": { "r": 255, "g": 255, "b": 255, "a": 1 },
87 | "font": type.unifrakturMaguntia,
88 | "uppercase": false,
89 | "hyphenate": false,
90 | "scale": 70,
91 | "spacing": 53
92 | }
93 |
94 | exports.spectral = {
95 | "colorBg": { "r": 255, "g": 218, "b": 218, "a": 1 },
96 | "colorText": { "r": 86, "g": 91, "b": 131, "a": 1 },
97 | "font": type.spectralExtraLight,
98 | "uppercase": false,
99 | "hyphenate": false,
100 | "scale": 46,
101 | "spacing": 72
102 | }
103 |
104 | exports.terminal = {
105 | "colorBg": {
106 | "r": 207,
107 | "g": 207,
108 | "b": 207,
109 | "a": 1
110 | },
111 | "colorText": {
112 | "r": 0,
113 | "g": 28,
114 | "b": 255,
115 | "a": 1
116 | },
117 | "font": type.terminalGrotesque,
118 | "uppercase": false,
119 | "hyphenate": false,
120 | "scale": 46,
121 | "spacing": 72
122 | }
123 |
124 | exports.curvyPurple = {
125 | "invert": true,
126 | "newTab": true,
127 | "autoDismiss": true,
128 | "colorBg": {
129 | "r": 245,
130 | "g": 245,
131 | "b": 245,
132 | "a": 1
133 | },
134 | "colorText": {
135 | "r": 100,
136 | "g": 0,
137 | "b": 255,
138 | "a": 1
139 | },
140 | "font": type.pecita,
141 | "uppercase": false,
142 | "hyphenate": false,
143 | "scale": 54,
144 | "spacing": 68
145 | }
146 |
--------------------------------------------------------------------------------
/src/lib/entries.js:
--------------------------------------------------------------------------------
1 | var dayjs = require('dayjs')
2 |
3 | module.exports = {
4 | getDismissedDate
5 | }
6 |
7 | function getDismissedDate (entry) {
8 | return dayjs(entry.dateDismissed)
9 | .add(entry.duration, entry.interval)
10 | .startOf('day')
11 | .toDate()
12 | }
13 |
--------------------------------------------------------------------------------
/src/lib/scale.js:
--------------------------------------------------------------------------------
1 | const x = require('xtend')
2 |
3 | const linearConversion = opts => {
4 | const options = x({
5 | value: 30,
6 | in: {
7 | min: 0,
8 | max: 100
9 | },
10 | out: {
11 | min: 25,
12 | max: 75
13 | }
14 | }, opts)
15 |
16 | const input = ((options.value - options.in.min) / (options.in.max - options.in.min))
17 | const output = (options.out.max - options.out.min)
18 | const result = input * output + options.out.min
19 |
20 | return result
21 | }
22 |
23 | module.exports = {
24 | linearConversion
25 | }
26 |
--------------------------------------------------------------------------------
/src/lib/time.js:
--------------------------------------------------------------------------------
1 | const x = require('xtend')
2 |
3 | const intToRest = opts => {
4 | const options = x({
5 | min: 1,
6 | max: 100,
7 | value: 4,
8 | breakpoints: [{
9 | interval: 'days',
10 | result: {
11 | min: 1,
12 | max: 30
13 | },
14 | range: {
15 | min: 0,
16 | max: 0.6
17 | }
18 | }, {
19 | interval: 'weeks',
20 | result: {
21 | min: 1,
22 | max: 6
23 | },
24 | range: {
25 | min: 0.6,
26 | max: 0.8
27 | }
28 | }, {
29 | interval: 'months',
30 | result: {
31 | min: 1,
32 | max: 12
33 | },
34 | range: {
35 | min: 0.8,
36 | max: 1
37 | }
38 | }]
39 | }, opts)
40 |
41 | const value = options.value / options.max
42 |
43 | const breakpoint = options.breakpoints
44 | .find(bp => {
45 | if (
46 | value >= bp.range.min &&
47 | value < bp.range.max
48 | ) {
49 | return true
50 | } else if (
51 | options.breakpoints.indexOf(bp) ===
52 | options.breakpoints.length - 1
53 | ) {
54 | return true
55 | } else {
56 | return false
57 | }
58 | })
59 |
60 | const scale = {
61 | input: (value - breakpoint.range.min) /
62 | (breakpoint.range.max - breakpoint.range.min),
63 | output: (breakpoint.result.max - breakpoint.result.min) +
64 | breakpoint.result.min
65 | }
66 |
67 | const result = {
68 | duration: Math.ceil(scale.input * scale.output),
69 | interval: breakpoint.interval
70 | }
71 |
72 | return {
73 | duration: result.duration < 1 ? 1 : result.duration,
74 | interval: result.interval
75 | }
76 | }
77 |
78 | module.exports = {
79 | intToRest
80 | }
81 |
--------------------------------------------------------------------------------
/src/plugins/app.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = app
3 |
4 | function app (state, emitter) {
5 | state.app = {
6 | loaded: false
7 | }
8 |
9 | // render
10 | emitter.on('app:render', function (data) {
11 | emitter.emit('entries:render')
12 | emitter.emit('render')
13 | })
14 |
15 | // check
16 | emitter.on('*', checkLoad)
17 |
18 | // load fallback
19 | emitter.on('DOMContentLoaded', function () {
20 | setTimeout(() => {
21 | if (!state.app.loaded) handleLoad()
22 | }, 3000)
23 | })
24 |
25 | // have we loaded?
26 | function checkLoad (data) {
27 | if (
28 | state.entries.loaded &&
29 | state.options.loaded.typeCustom &&
30 | state.options.loaded.typeLocal &&
31 | state.options.loaded.data &&
32 | !state.app.loaded
33 | ) { handleLoad() }
34 | }
35 |
36 | // all good
37 | function handleLoad () {
38 | state.app.loaded = true
39 | emitter.emit('app:render')
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/plugins/data-storage.js:
--------------------------------------------------------------------------------
1 | var db = require('../db')
2 |
3 | module.exports = pluginData
4 |
5 | function pluginData (state, emitter) {
6 | state.datastorage = getDefaultState()
7 |
8 | // load the archive if there is one
9 | if (state.datastorage.archiveUrl) setArchive()
10 |
11 | // events
12 | state.events.DATA_DAT_LOAD = 'data:dat:load'
13 | emitter.on(state.events.DATA_DAT_LOAD, handleDatLoad)
14 |
15 | async function handleDatLoad (data) {
16 | emitter.emit(state.events.UI_PANEL, { view: '' })
17 | var reset = await db.reset()
18 | if (reset !== false) {
19 | state.datastorage = getDefaultState()
20 | if (state.datastorage.archiveUrl) setArchive()
21 | emitter.emit(state.events.ENTRIES_LOAD)
22 | emitter.emit(state.events.OPTIONS_LOAD)
23 | }
24 | }
25 |
26 | async function setArchive () {
27 | try {
28 | var archive = await DatArchive.load(state.datastorage.archiveUrl)
29 | state.datastorage.archive = await archive.getInfo()
30 | emitter.emit(state.events.RENDER)
31 | } catch (err) { }
32 | }
33 |
34 | function getDefaultState() {
35 | return {
36 | isDat: (typeof DatArchive !== 'undefined'),
37 | archiveUrl: window.localStorage.archiveUrl,
38 | archive: { }
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/src/plugins/features.js:
--------------------------------------------------------------------------------
1 | module.exports = features
2 |
3 | var beta = process.env.NODE_ENV === 'development'
4 |
5 | function features (state, emitter) {
6 | state.features = {
7 | tags: true,
8 | search: true
9 | }
10 |
11 | emitter.on('feature:enable', function (data) {
12 | if (data.feature) {
13 | state.features[data.feature] = true
14 | }
15 |
16 | if (data.render !== false) {
17 | emitter.emit('app:render')
18 | }
19 | })
20 |
21 | emitter.on('feature:disable', function (data) {
22 | if (data.feature) {
23 | state.features[data.feature] = false
24 | }
25 |
26 | if (data.render !== false) {
27 | emitter.emit('app:render')
28 | }
29 | })
30 | }
31 |
--------------------------------------------------------------------------------
/src/plugins/index.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | // require('./logger'),
3 | require('./data-storage'),
4 | require('./features'),
5 | require('./entries'),
6 | require('./search'),
7 | require('./options'),
8 | require('./ui'),
9 | require('./staging'),
10 | require('./user'),
11 | require('./app'),
12 | require('./scroll'),
13 | require('./notifications')
14 | ]
15 |
--------------------------------------------------------------------------------
/src/plugins/logger.js:
--------------------------------------------------------------------------------
1 | module.exports = logger
2 |
3 | function logger (state, emitter) {
4 | state.debug = process.env.NODE_ENV === 'development'
5 |
6 | if (state.debug) {
7 | emitter.on('*', function (messageName, data) {
8 | console.log('event', messageName, data)
9 | })
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/plugins/notifications.js:
--------------------------------------------------------------------------------
1 | var libDesign = require('../lib/design')
2 |
3 | module.exports = pluginNotifications
4 |
5 | function pluginNotifications (state, emitter) {
6 | state.notifications = {
7 | active: ''
8 | }
9 |
10 | state.events.NOTIFICATION = 'notification'
11 | state.events.NOTIFICATIONS = 'notifications'
12 |
13 | emitter.on(state.events.DOMCONTENTLOADED, handleLoad)
14 |
15 | function handleLoad () {
16 | setTimeout(function () {
17 | // design adjustments
18 | if (
19 | !state.user.notified['customizeDesign'] &&
20 | libDesign.isDesignDefault(state)
21 | ) {
22 | state.notifications.active = 'customizeDesign'
23 | emitter.emit('app:render')
24 | return
25 | }
26 |
27 | if (
28 | !state.user.notified['p2p'] &&
29 | typeof WebArchive === 'undefined'
30 | ) {
31 | state.notifications.active = 'p2p'
32 | emitter.emit('app:render')
33 | }
34 | }, 1000)
35 | }
36 |
37 | function handleNotification (data) {
38 | var data = data || { }
39 | var shouldRender = data.render !== false
40 | if (shouldRender) emitter.emit('app:render')
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/plugins/options.js:
--------------------------------------------------------------------------------
1 | var objectValues = require('object-values')
2 | var clone = require('clone-deep')
3 | var xtend = require('xtend')
4 |
5 | var libDesign = require('../lib/design')
6 | var optionsTypography = require('./options-typography')
7 | var typography = require('../design/typography')
8 | var db = require('../db/options')
9 |
10 | module.exports = Options
11 |
12 | function Options (state, emitter) {
13 | state.options = getDefaultState()
14 |
15 | // events
16 | state.events.OPTIONS_LOAD = 'options:load'
17 |
18 | // listen
19 | emitter.on('DOMContentLoaded', handleLoad)
20 | emitter.on(state.events.OPTIONS_LOAD, handleLoad)
21 |
22 | // values
23 | emitter.on('options:values', function (data) {
24 | var newState = clone(state.options.values)
25 | newState[data.key] = data.value
26 |
27 | if (data.key === 'font') {
28 | typography.load(data.value, function (event, _data) {
29 | emitter.emit(event, _data)
30 | })
31 | }
32 |
33 | db.update(data, newState)
34 | emitter.emit('options:update', newState)
35 | })
36 |
37 | emitter.on('options:replace', function (data) {
38 | var newState = xtend(state.options.values, data)
39 | db.update(data, newState)
40 | emitter.emit('options:update', newState)
41 | })
42 |
43 | // update
44 | emitter.on('options:update', function (data) {
45 | state.options.values = xtend(state.options.values, data)
46 | emitter.emit('app:render')
47 | })
48 |
49 | // reset
50 | emitter.on('options:reset', function (data) {
51 | var defaults = getDefaultState().values
52 | db.update({ }, defaults)
53 | emitter.emit('options:update', defaults)
54 | })
55 |
56 | // loaded
57 | emitter.on('options:loaded', function (data) {
58 | state.options.loaded = xtend(state.options.loaded, data)
59 | emitter.emit('app:render')
60 | })
61 |
62 | emitter.on('options:invert', function (data) {
63 | var newState = clone(state.options.values)
64 |
65 | newState.invert = !state.options.values.invert
66 | newState.colorBg = state.options.values.colorText
67 | newState.colorText = state.options.values.colorBg
68 |
69 | db.update({ }, newState)
70 | emitter.emit('options:update', newState)
71 | })
72 |
73 | emitter.on('options:typography', function () {
74 | var options = objectValues(state.options.typography)
75 | options.forEach(function (data) {
76 | typography.load(data, () => emitter.emit)
77 | })
78 | })
79 |
80 | // type
81 | function handleLoad () {
82 | var defaults = getDefaultState()
83 |
84 | typography.local(function () {
85 | emitter.emit('options:loaded', { typeLocal: true })
86 | emitter.emit('app:render')
87 | })
88 |
89 | // init
90 | db.get(function (data) {
91 | data = xtend(defaults.values, data)
92 |
93 | if (data.font) {
94 | typography.load(data.font, function () {
95 | emitter.emit('options:loaded', { typeCustom: true })
96 | })
97 | } else {
98 | typography.load(state.options.values.font, function () {
99 | emitter.emit('options:loaded', { typeCustom: true })
100 | })
101 | }
102 |
103 | emitter.emit('options:update', data)
104 | emitter.emit('options:loaded', { data: true })
105 | emitter.emit('app:render')
106 | })
107 | }
108 | }
109 |
110 | function getDefaultState () {
111 | return {
112 | data: {
113 | name: 'Data',
114 | key: 'data',
115 | type: 'data',
116 | visible: true
117 | },
118 | design: {
119 | colorBg: {
120 | name: 'Background',
121 | key: 'colorBg',
122 | type: 'color',
123 | visible: true
124 | },
125 | colorText: {
126 | name: 'Text',
127 | key: 'colorText',
128 | type: 'color',
129 | visible: true
130 | },
131 | font: {
132 | name: 'Font',
133 | key: 'font',
134 | type: 'typography',
135 | visible: true
136 | },
137 | uppercase: {
138 | name: 'Uppercase',
139 | key: 'uppercase',
140 | type: 'checkbox',
141 | visible: true
142 | },
143 | hyphenate: {
144 | name: 'Hyphenate',
145 | key: 'hyphenate',
146 | type: 'checkbox',
147 | visible: true
148 | },
149 | scale: {
150 | name: 'Scale',
151 | key: 'scale',
152 | type: 'range',
153 | min: 5,
154 | max: 72,
155 | showValue: false,
156 | visible: true
157 | },
158 | spacing: {
159 | name: 'Space',
160 | key: 'spacing',
161 | type: 'range',
162 | min: 0,
163 | max: 25,
164 | showValue: false,
165 | visible: true
166 | },
167 | invert: {
168 | name: 'Invert',
169 | key: 'invert',
170 | visible: false
171 | },
172 | entropy: {
173 | name: 'Randomly offset rest duration',
174 | key: 'entropy',
175 | type: 'range',
176 | min: 0,
177 | max: 7,
178 | unit: 'days',
179 | scaleValue: true,
180 | showValue: true,
181 | visible: true
182 | },
183 | newTab: {
184 | name: 'Open links in a new window',
185 | key: 'newTab',
186 | type: 'checkbox',
187 | visible: true
188 | },
189 | autoDismiss: {
190 | name: 'Auto Hide Entries',
191 | key: 'autoDismiss',
192 | type: 'checkbox',
193 | visible: false
194 | },
195 | css: {
196 | name: 'Edit your custom CSS',
197 | key: 'css',
198 | type: 'textarea',
199 | visible: true
200 | }
201 | },
202 | values: xtend({
203 | invert: false,
204 | newTab: true,
205 | autoDismiss: true,
206 | entropy: 0,
207 | css: libDesign.getCssDefaults()
208 | }, libDesign.getDesignDefaults()),
209 | loaded: {
210 | typeLocal: false,
211 | typeCustom: false,
212 | data: false
213 | },
214 | typography: optionsTypography
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/src/plugins/scroll.js:
--------------------------------------------------------------------------------
1 | module.exports = pluginScroll
2 |
3 | function pluginScroll (state, emitter) {
4 | emitter.on(state.events.PUSHSTATE, function () {
5 | window.scrollTo(0, 0)
6 | })
7 | }
8 |
--------------------------------------------------------------------------------
/src/plugins/search.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = search
3 |
4 | function search (state, emitter) {
5 | state.search = {
6 | term: '',
7 | active: false,
8 | options: {
9 | enabled: true
10 | }
11 | }
12 |
13 | emitter.on('search:update', function (data = { }) {
14 | // check if active
15 | if (!isEnabled()) return
16 |
17 | // search term
18 | if (data.value !== undefined) {
19 | state.search.term = data.value
20 | }
21 |
22 | // show all if not already
23 | if (data.all && !state.ui.entriesViewAll) {
24 | emitter.emit('ui:update', { entriesViewAll: true })
25 | }
26 |
27 | emitter.emit(state.events.UI_PAGINATE, {
28 | page: 1,
29 | render: false
30 | })
31 |
32 | // hide the panel
33 | if (data.hidePanel && state.ui.panel.view) {
34 | emitter.emit('ui:panel', { view: '' })
35 | }
36 |
37 | // rendering
38 | if (data.render) {
39 | emitter.emit('app:render')
40 | }
41 | })
42 |
43 | function isEnabled () {
44 | return state.features && state.features.search
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/plugins/staging.js:
--------------------------------------------------------------------------------
1 | const xtend = require('xtend')
2 |
3 | module.exports = Staging
4 |
5 | function Staging (state, emitter) {
6 | state.staging = getEmptyState()
7 |
8 | emitter.on('staging:reset', function (data) {
9 | state.staging = getEmptyState()
10 | emitter.emit('app:render')
11 | })
12 |
13 | emitter.on('staging:entry', function (data) {
14 | state.staging.entry = xtend(state.staging.entry, data)
15 | emitter.emit('app:render')
16 | })
17 | }
18 |
19 | function getEmptyState () {
20 | return {
21 | id: '',
22 | entry: {
23 | title: '',
24 | tags: [ ],
25 | duration: 7,
26 | interval: 'days',
27 | visited: 0,
28 | timeRange: 20,
29 | url: ''
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/plugins/ui.js:
--------------------------------------------------------------------------------
1 | var dayjs = require('dayjs')
2 | var xtend = require('xtend')
3 |
4 | module.exports = pluginUi
5 |
6 | var resizeFrame
7 |
8 | function pluginUi (state, emitter) {
9 | state.ui = {
10 | date: dayjs().format('MMMM D'),
11 | loaded: false,
12 | stagingActive: false,
13 | introActive: false,
14 | entriesViewAll: false,
15 | mobile: false,
16 | pagination: {
17 | page: 1,
18 | limit: 15
19 | },
20 | panel: {
21 | active: false,
22 | view: ''
23 | }
24 | }
25 |
26 | state.events.UI_PANEL = 'ui:panel'
27 | state.events.UI_UPDATE = 'ui:update'
28 | state.events.UI_PAGINATE = 'ui:paginate'
29 |
30 | emitter.on(state.events.UI_UPDATE, function (data) {
31 | state.ui = xtend(state.ui, data)
32 | emitter.emit('app:render')
33 | })
34 |
35 | emitter.on(state.events.UI_PANEL, function (data) {
36 | var render = state.ui.panel.view !== data.view
37 | state.ui.panel = xtend(state.ui.panel, data)
38 | if (render) emitter.emit('app:render', state.events.UI_PANEL)
39 | })
40 |
41 | emitter.on(state.events.UI_PAGINATE, function (data) {
42 | data = data || { }
43 | var shouldRender = data.render !== false
44 | delete data.render
45 | state.ui.pagination = xtend(state.ui.pagination, data)
46 | if (shouldRender) emitter.emit('app:render')
47 | })
48 |
49 | emitter.on('DOMContentLoaded', function () {
50 | setMobile()
51 | window.addEventListener('resize', setMobile)
52 | })
53 |
54 | function setMobile () {
55 | clearTimeout(resizeFrame)
56 | resizeFrame = setTimeout(function () {
57 | state.ui.mobile = window.innerWidth <= 600
58 | emitter.emit('app:render')
59 | }, 100)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/plugins/user.js:
--------------------------------------------------------------------------------
1 | var clone = require('clone-deep')
2 | var xtend = require('xtend')
3 |
4 | var db = require('../db/user')
5 |
6 | module.exports = user
7 |
8 | function user(state, emitter) {
9 | state.user = getState()
10 |
11 | state.events.USER_LOAD = 'user:load'
12 | state.events.USER_RESET = 'user:reset'
13 | state.events.USER_LOADED = 'user:loaded'
14 | state.events.USER_UPDATE = 'user:update'
15 | state.events.USER_FEATURE = 'user:feature'
16 | state.events.USER_NOTIFIED = 'user:notified'
17 | state.events.USER_ANALYTICS = 'user:analytics'
18 | state.events.USER_CYCLES_DISMISS = 'user:cycles:dismiss'
19 |
20 | emitter.on('DOMContentLoaded', function () {
21 | db.get(
22 | function (data) {
23 | emitter.emit(state.events.USER_LOAD, data)
24 | },
25 | function () {
26 | emitter.emit(state.events.USER_LOADED, data)
27 | }
28 | )
29 | })
30 |
31 | emitter.on(state.events.USER_LOADED, function (data) {
32 | // state.user.analytics.visits += 1
33 | // state.user.analytics.lastvisit = new Date().toISOString()
34 | // emitter.emit('user:update')
35 | })
36 |
37 | emitter.on(state.events.USER_ANALYTICS, function (data) {
38 | state.user.analytics = xtend(state.user.analytics, data)
39 | emitter.emit('user:update')
40 | })
41 |
42 | emitter.on('pushState', function (data) {
43 | // if (
44 | // process.env.NODE_ENV === 'production' &&
45 | // window.ga &&
46 | // typeof window.ga === 'function'
47 | // ) {
48 | // window.ga('set', 'page', window.location.pathname)
49 | // window.ga('send', 'pageview')
50 | // }
51 | })
52 |
53 | emitter.on(state.events.USER_LOAD, function (data) {
54 | state.user = xtend(state.user, data)
55 | emitter.emit(state.events.USER_LOADED, data)
56 | })
57 |
58 | emitter.on(state.events.USER_UPDATE, function (data) {
59 | db.update(data, state.user)
60 | emitter.emit('app:render')
61 | })
62 |
63 | // user prefs for features
64 | emitter.on(state.events.USER_FEATURE, function (data) {
65 | if (typeof data === 'object') {
66 | state.user.features = xtend(state.user.features, data)
67 | }
68 |
69 | if (data.render !== false) {
70 | emitter.emit('app:render')
71 | }
72 | })
73 |
74 | // cycles dismissed
75 | emitter.on(state.events.USER_CYCLES_DISMISS, function (data) {
76 | console.log('yo')
77 | state.user.cyclesDismissed = true
78 | emitter.emit(state.events.USER_UPDATE)
79 | })
80 |
81 | // notified
82 | emitter.on(state.events.USER_NOTIFIED, function (data) {
83 | var data = data || {}
84 | if (!data.id) return
85 | state.notifications.active = '' // whoops
86 | state.user.notified[data.id] = true
87 | emitter.emit(state.events.USER_UPDATE)
88 | })
89 |
90 | emitter.on(state.events.USER_RESET, function (data) {
91 | state.user = getState()
92 | window.localStorage.archiveUrl = ''
93 | emitter.emit(state.events.USER_UPDATE)
94 | })
95 | }
96 |
97 | function getState() {
98 | return {
99 | credentials: {
100 | email: '',
101 | photoURL: '',
102 | uuid: '',
103 | },
104 | features: {
105 | search: true,
106 | },
107 | analytics: {
108 | authenticated: false,
109 | visits: 0,
110 | lastvisit: undefined,
111 | },
112 | notified: {},
113 | signedIn: false,
114 | cyclesDismissed: false,
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/sandbox/index.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 | var ov = require('object-values')
3 | var xd = require('xtend')
4 |
5 | var input = require('./input')
6 | var ui = require('./interface')
7 |
8 | module.exports = Sandbox
9 |
10 | function Sandbox (state, emit) {
11 | var components = [ ...input, ...ui ]
12 | .map(function (component) {
13 | return component({ }, log)
14 | })
15 |
16 | var navigationComponents = ov(components)
17 | .map(function (component) {
18 | var active = state.params.component === component.key
19 | return html`
20 |
${component.name}
27 | `
28 | })
29 |
30 | return html`
31 |
32 | ${sidebar()}
33 | ${content()}
34 |
35 | `
36 |
37 | function sidebar (props = { }) {
38 | return html`
39 |
40 |
41 | ${navigationComponents}
42 |
43 |
44 | `
45 | }
46 |
47 | function content (props = { }) {
48 | var examples = components
49 | .filter(function (_component) {
50 | return _component.key === state.params.component
51 | })
52 |
53 | if (examples.length <= 0) {
54 | return wrapper(html`
55 |
Components not found
56 | `)
57 | }
58 |
59 | return wrapper(
60 | examples.map(function (component) {
61 | if (!isComponentValid(component)) {
62 | return html`
63 |
Component can not mount
64 | `
65 | }
66 | return component.variations
67 | .map(function (variation) {
68 | return example(
69 | variation,
70 | h(component.template, variation.props)
71 | )
72 | })
73 | })
74 | )
75 | }
76 |
77 | function log (data) {
78 | console.log('sandbox', data)
79 | }
80 | }
81 |
82 | function wrapper (children) {
83 | return html`
84 |
85 | ${children}
86 |
87 | `
88 | }
89 |
90 | function example (props, children) {
91 | return html`
92 |
93 |
94 |
95 |
${JSON.stringify(props.props)}
96 |
97 | ${children}
98 |
99 |
100 | `
101 | }
102 |
103 | function isComponentValid (props) {
104 | return props &&
105 | props.template &&
106 | props.variations
107 | }
108 |
--------------------------------------------------------------------------------
/src/sandbox/input.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = [
3 | checkbox,
4 | range,
5 | text,
6 | tags
7 | ]
8 |
9 | function checkbox (state, emit) {
10 | return {
11 | key: 'checkbox',
12 | name: 'Checkbox',
13 | template: require('../components/input/checkbox'),
14 | variations: [{
15 | name: 'Default',
16 | props: {
17 | key: 'one',
18 | name: 'Test',
19 | value: true,
20 | onChange: function (data) {
21 | emit({
22 | component: 'Checkbox',
23 | data: data
24 | })
25 | }
26 | }
27 | }, {
28 | name: 'Inactive',
29 | props: {
30 | key: 'two',
31 | value: false
32 | }
33 | }, {
34 | name: 'Custom icon',
35 | props: {
36 | key: 'three',
37 | name: 'Custom icon',
38 | icon: '🙃',
39 | value: true
40 | }
41 | }]
42 | }
43 | }
44 |
45 | function range (state, emit) {
46 | return {
47 | key: 'range',
48 | name: 'Range',
49 | template: require('../components/input/range'),
50 | variations: [{
51 | name: 'Default',
52 | props: {
53 | key: 'one',
54 | name: 'Test',
55 | value: 50,
56 | onInput: function (data) {
57 | emit({
58 | component: 'Range',
59 | data: data
60 | })
61 | }
62 | }
63 | }, {
64 | name: 'Value',
65 | props: {
66 | key: 'one',
67 | name: 'Test',
68 | showValue: true,
69 | value: 10
70 | }
71 | }]
72 | }
73 | }
74 |
75 | function text (state, emit) {
76 | return {
77 | key: 'text',
78 | name: 'Text',
79 | template: require('../components/input/text'),
80 | variations: [{
81 | name: 'Default',
82 | props: {
83 | key: 'one',
84 | name: 'Test',
85 | onInput: function (data) {
86 | emit({
87 | component: 'Text',
88 | data: data
89 | })
90 | }
91 | }
92 | }, {
93 | name: 'Value',
94 | props: {
95 | key: 'one',
96 | name: 'Test',
97 | value: 'This one has a value'
98 | }
99 | }, {
100 | name: 'Autofocus',
101 | props: {
102 | key: 'one',
103 | name: 'Autofocus example',
104 | autofocus: true
105 | }
106 | }]
107 | }
108 | }
109 |
110 | function tags (state, emit) {
111 | return {
112 | key: 'tags',
113 | name: 'Tags',
114 | template: require('../components/input/tags'),
115 | variations: [{
116 | name: 'Default',
117 | props: {
118 | key: 'one',
119 | name: 'Test',
120 | value: [ ],
121 | onChange: function (data) {
122 | emit({
123 | component: 'Tags',
124 | data: data
125 | })
126 | }
127 | }
128 | }, {
129 | name: 'Tags',
130 | props: {
131 | key: 'two',
132 | value: ['one', 'two', 'three'],
133 | onChange: function (data) {
134 | emit({
135 | component: 'Tags',
136 | data: data
137 | })
138 | }
139 | }
140 | }]
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/sandbox/interface.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = [
3 | search
4 | ]
5 |
6 | function search (state, emit) {
7 | return {
8 | key: 'search',
9 | name: 'Search',
10 | template: require('../components/search'),
11 | variations: [{
12 | name: 'Default',
13 | props: {
14 | value: true,
15 | onChange: function (data) {
16 | emit({
17 | component: 'Search',
18 | data: data
19 | })
20 | }
21 | }
22 | }]
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/templates/about.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 |
3 | var containerContent = require('../containers/content')
4 | var format = require('../components/format')
5 |
6 | module.exports = view
7 |
8 | function view (state, emit) {
9 | return containerContent(state, emit, content(state, emit))
10 | }
11 |
12 | function content (state, emit) {
13 | return html`
14 |
15 |
16 | ${format(state.page().v('text'))}
17 |
18 |
19 | `
20 | }
21 |
--------------------------------------------------------------------------------
/src/templates/blog-entry.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 |
3 | var containerContent = require('../containers/content')
4 | var entryBlog = require('../components/entry-blog')
5 | var format = require('../components/format')
6 |
7 | module.exports = view
8 |
9 | function view (state, emit) {
10 | return containerContent(state, emit, content(state, emit))
11 | }
12 |
13 | function content (state, emit) {
14 | var page = state.page().v()
15 |
16 | return html`
17 |
18 | ${entryBlog(state, emit, page)}
19 |
20 | `
21 | }
22 |
--------------------------------------------------------------------------------
/src/templates/blog.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 |
3 | var Content = require('../containers/content')
4 | var Blog = require('../containers/blog')
5 |
6 | module.exports = view
7 |
8 | function view (state, emit) {
9 | return Content(state, emit, content(state, emit))
10 | }
11 |
12 | function content (state, emit) {
13 | var entries = state.page()
14 | .children()
15 | .visible()
16 | .sortBy('date', 'desc')
17 | .toArray()
18 |
19 | return state.cache(Blog, 'blog').render({
20 | entries: entries
21 | })
22 | }
23 |
--------------------------------------------------------------------------------
/src/templates/data.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 | var ov = require('object-values')
3 |
4 | var css = require('../components/css')
5 |
6 | var navigationOpts = {
7 | export: {
8 | key: 'export',
9 | text: 'Export'
10 | },
11 | import: {
12 | key: 'import',
13 | text: 'Import'
14 | },
15 | design: {
16 | key: 'design',
17 | text: 'Design'
18 | }
19 | }
20 |
21 | var getCommand = command => command || 'export'
22 |
23 | module.exports = view
24 |
25 | function handleImportClick (event, emit) {
26 | var input = event.target.parentNode.querySelector('textarea')
27 | var value = input.value
28 |
29 | try {
30 | var result = JSON.parse(value)
31 | emit('entries:reset', result)
32 | alert('imported!')
33 | } catch (err) {
34 | alert('Please enter valid JSON')
35 | console.warn(err)
36 | }
37 | }
38 |
39 | function elNavigation (state, emit) {
40 | var command = getCommand(state.params.command)
41 | var opts = ov(navigationOpts)
42 |
43 | var elsOpts = opts.map((opt, i) => html`
44 |
48 |
49 | ${opt.text}
50 |
51 |
52 | `)
53 |
54 | return html`
58 | ${elsOpts}
59 |
×
64 |
`
65 | }
66 |
67 | function elImport (state, emit) {
68 | return html`
69 |
74 |
handleImportClick(event, emit)}
77 | >Submit
78 |
`
79 | }
80 |
81 | function elExport (state, emit) {
82 | var entries = state.entries.all
83 | return html`
84 |
85 |
90 |
91 | `
92 | }
93 |
94 | function elDesign (state, emit) {
95 | var options = state.options.values
96 | return html`
97 |
98 |
103 |
104 | `
105 | }
106 |
107 | function view (state, emit) {
108 | var command = getCommand(state.params.command)
109 | var elContent = getContent()
110 |
111 | return html`
112 | ${elNavigation(state, emit)}
113 | ${elContent(state, emit)}
114 | ${css(state, emit)}
115 |
`
116 |
117 | function getContent () {
118 | switch (state.params.command) {
119 | case 'import':
120 | return elImport
121 | case 'design':
122 | return elDesign
123 | default:
124 | return elExport
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/templates/faq.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 |
3 | var containerContent = require('../containers/content')
4 | var format = require('../components/format')
5 |
6 | module.exports = view
7 |
8 | function view (state, emit) {
9 | return containerContent(state, emit, content(state, emit))
10 | }
11 |
12 | function content (state, emit) {
13 | var text = state.page().v('text') || ''
14 | var answers = text
15 | .split('##')
16 | .filter(str => str)
17 | .map(str => '##' + str)
18 |
19 | return html`
20 |
21 |
22 | ${answers.map(createAnswer)}
23 |
24 |
25 | `
26 |
27 | function createAnswer (props) {
28 | return html`
29 |
30 | ${format(props)}
31 |
32 | `
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/templates/home.js:
--------------------------------------------------------------------------------
1 | var EntryNavigation = require('../containers/entry-navigation')
2 | var Notification = require('../containers/notification')
3 | var Panel = require('../containers/panel-container')
4 | var Intro = require('../containers/intro')
5 | var EntryList = require('../containers/entry-list')
6 |
7 | module.exports = view
8 |
9 | function view (state, emit) {
10 | var panelProps = {
11 | isHoverActive: true && !state.ui.mobile,
12 | view: state.ui.panel.view
13 | }
14 |
15 | // hide if nothing to load
16 | if (!state.app.loaded) return ''
17 |
18 | // all
19 | if (state.route === 'all' && !state.ui.entriesViewAll) {
20 | emit('ui:update', { entriesViewAll: true })
21 | }
22 |
23 | // non all
24 | if (
25 | state.route !== 'all' &&
26 | state.ui.entriesViewAll &&
27 | !state.search.term
28 | ) {
29 | emit('ui:update', { entriesViewAll: false })
30 | }
31 |
32 | return [
33 | Panel(state, panelProps, emit),
34 | EntryNavigation(state, emit),
35 | createContent()
36 | ]
37 |
38 | function createNotification () {
39 | if (state.notifications.active) {
40 | return Notification(state, emit)
41 | }
42 | }
43 |
44 | function createContent () {
45 | return (state.entries.amount === 0)
46 | ? createIntro(state, emit)
47 | : [EntryList(state, emit), createNotification()]
48 | }
49 |
50 | function createIntro () {
51 | // show if we have’t
52 | if (!state.ui.mobile && !state.ui.panel.loaded) {
53 | setTimeout(function () {
54 | emit('ui:panel', { view: 'entry', loaded: true })
55 | }, 1)
56 | }
57 | return Intro(state, emit)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/templates/intro.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 |
3 | var containerContent = require('../containers/content')
4 | var IntroVideo = require('../components/intro-video')
5 |
6 | module.exports = view
7 |
8 | function view (state, emit) {
9 | return containerContent(state, emit, content(state, emit))
10 | }
11 |
12 | function content (state, emit) {
13 | return html`
14 |
15 |
Coming soon
16 | ${state.cache(IntroVideo, 'intro-video').render()}
17 |
18 | `
19 | }
20 |
--------------------------------------------------------------------------------
/src/templates/panel.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 |
3 | var EntryNavigation = require('../containers/entry-navigation')
4 | var Panel = require('../containers/panel-container')
5 | var EntryList = require('../containers/entry-list')
6 |
7 | module.exports = view
8 |
9 | function view (state, emit) {
10 | var panelProps = {
11 | isHoverActive: false,
12 | view: state.params.view
13 | }
14 |
15 | return [
16 | Panel(state, panelProps, emit),
17 | !state.ui.mobile ? EntryList(state, emit) : '',
18 | EntryNavigation(state, emit),
19 | !state.ui.mobile ? createOverlay() : html`
`
20 | ]
21 |
22 | function createOverlay () {
23 | return html`
24 |
29 | `
30 | }
31 |
32 | function handleOverlayClick () {
33 | emit('pushState', '/')
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/templates/reset.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 | var css = require('../components/css')
3 |
4 | var resetAll = (emit) => {
5 | emit('entries:reset')
6 | emit('options:reset')
7 | emit('user:reset')
8 | }
9 |
10 | var resetEntries = (emit) => emit('entries:reset')
11 | var resetOptions = (emit) => emit('options:reset')
12 | var resetUser = (emit) => emit('user:reset')
13 |
14 | module.exports = (state, emit) => {
15 | var elContainer = content => html`
${content}
`
18 |
19 | var elReset = html`
20 |
21 |
resetAll(emit)}
24 | >
25 | Reset Everything
26 |
27 |
28 |
29 |
30 |
resetOptions(emit)}
33 | >
34 | Reset Options
35 |
36 |
37 |
38 |
39 |
resetEntries(emit)}
42 | >
43 | Reset Entries
44 |
45 |
46 |
`
47 |
48 | var elConfirmation = html`
49 | Your local data has been reset
50 |
`
51 |
52 | return html`
53 | ${elContainer(elReset)}
54 | ${css(state, emit)}
55 |
`
56 | }
57 |
--------------------------------------------------------------------------------
/src/templates/suggestions.js:
--------------------------------------------------------------------------------
1 | var html = require('choo/html')
2 |
3 | var EntryNavigation = require('../containers/entry-navigation')
4 | var Suggestions = require('../containers/suggestions')
5 | var Panel = require('../containers/panel-container')
6 | var EntryList = require('../containers/entry-list')
7 | var loading = require('../components/loading')
8 |
9 | module.exports = viewSuggestions
10 |
11 | function viewSuggestions (state, emit) {
12 | // hide the nav
13 | if (!state.ui.panel.view !== '' && !state.ui.panel.loadedAbout) {
14 | emit('ui:panel', { view: '', loadedAbout: true })
15 | }
16 |
17 | var panelProps = {
18 | isHoverActive: true && !state.ui.mobile,
19 | view: state.ui.panel.view
20 | }
21 |
22 | if (!state.site.loaded) {
23 | emit(state.events.CONTENT_LOAD)
24 | return loading()
25 | }
26 |
27 | return [
28 | Panel(state, panelProps, emit),
29 | EntryNavigation(state, emit),
30 | EntryList(state, emit),
31 | createContent()
32 | ]
33 |
34 | function createContent () {
35 | return html`
36 |
41 | ${state.cache(Suggestions, 'suggestions').render()}
42 |
43 | `
44 | }
45 |
46 | function handleContainerClick (event) {
47 | var isContainer = event.target.hasAttribute('data-suggestions')
48 | if (isContainer && state.entries.amount) emit('pushState', '/')
49 | }
50 | }
51 |
--------------------------------------------------------------------------------