From 7c7f4f208c0e75bbdae0b756f4007298dd60d0b0 Mon Sep 17 00:00:00 2001 From: "Glitch (glitch-fediverse-bot)" Date: Thu, 4 Oct 2018 01:33:10 +0000 Subject: [PATCH] Added image upload support for NeoCities with a local storage as a backup. --- .glitch-assets | 1 + README.md | 4 +- bot.js | 64 ++++++++++------- examples/generative-art-bot/README.md | 14 ++++ .../generative-art-bot/routes/bot-endpoint.js | 57 +++++++++++++++ examples/images/README.md | 1 - examples/images/routes/bot-endpoint.js | 40 ----------- examples/tracery/routes/bot-endpoint.js | 1 + helpers/db.js | 13 ++-- helpers/general.js | 67 ++++++++++++++++++ helpers/image-uploader.js | 58 +++++++++++++++ package.json | 3 +- routes/bot-endpoint.js | 1 + routes/index.js | 5 +- routes/post.js | 5 ++ server.js | 9 ++- sessions | Bin 81920 -> 139264 bytes shrinkwrap.yaml | 8 +++ src/styles/{layout.scss => _layout.scss} | 11 ++- src/styles/_typography.scss | 1 + .../{variables.scss => _variables.scss} | 0 src/styles/styles.scss | 4 +- src/styles/typography.scss | 6 -- views/home.handlebars | 12 ++-- views/partials/header.handlebars | 8 +-- views/post.handlebars | 10 ++- 26 files changed, 300 insertions(+), 103 deletions(-) create mode 100644 examples/generative-art-bot/README.md create mode 100644 examples/generative-art-bot/routes/bot-endpoint.js delete mode 100644 examples/images/README.md delete mode 100644 examples/images/routes/bot-endpoint.js create mode 100644 helpers/image-uploader.js rename src/styles/{layout.scss => _layout.scss} (51%) create mode 100644 src/styles/_typography.scss rename src/styles/{variables.scss => _variables.scss} (100%) delete mode 100644 src/styles/typography.scss diff --git a/.glitch-assets b/.glitch-assets index 095c352..21daff0 100644 --- a/.glitch-assets +++ b/.glitch-assets @@ -12,3 +12,4 @@ {"name":"glitch-fediverse-bot-small-1024px.png","date":"2018-09-29T12:49:07.895Z","url":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fglitch-fediverse-bot-small-1024px.png","type":"image/png","size":60397,"imageWidth":1024,"imageHeight":509,"thumbnail":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fthumbnails%2Fglitch-fediverse-bot-small-1024px.png","thumbnailWidth":330,"thumbnailHeight":165,"dominantColor":"rgb(252,252,252)","uuid":"iLCHg6pDTsAX7jkg"} {"name":"glitch-fediverse-bot.png","date":"2018-09-29T12:49:07.963Z","url":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fglitch-fediverse-bot.png","type":"image/png","size":58653,"imageWidth":1920,"imageHeight":1079,"thumbnail":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fthumbnails%2Fglitch-fediverse-bot.png","thumbnailWidth":330,"thumbnailHeight":186,"dominantColor":"rgb(252,252,252)","uuid":"0KNy0WXAgI1pZ0fd"} {"name":"glitch-fediverse-bot-960px.png","date":"2018-09-29T12:49:08.030Z","url":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fglitch-fediverse-bot-960px.png","type":"image/png","size":50867,"imageWidth":960,"imageHeight":540,"thumbnail":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fthumbnails%2Fglitch-fediverse-bot-960px.png","thumbnailWidth":330,"thumbnailHeight":186,"dominantColor":"rgb(252,252,252)","uuid":"8tHyMsnvvVKkjFXF"} +{"name":"glitch-fediverse-bot-with-image.png","date":"2018-10-03T21:11:53.744Z","url":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fglitch-fediverse-bot-with-image.png","type":"image/png","size":469823,"imageWidth":2880,"imageHeight":1465,"thumbnail":"https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fthumbnails%2Fglitch-fediverse-bot-with-image.png","thumbnailWidth":330,"thumbnailHeight":168,"dominantColor":"rgb(252,252,252)","uuid":"oHDFFpP6XyCauPmh"} diff --git a/README.md b/README.md index 719aef9..1b8497a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -*WORK IN PROGRESS* - ![Glitch Fediverse bot](https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fglitch-fediverse-bot-small-1024px.png?1538225347895) @@ -40,7 +38,7 @@ You can use [ES6](http://es6-features.org/#Constants), you script files will be - https://neocities.org/api, https://neocities.org/supporter, https://github.com/neocities/neocities-node - https://www.digitalocean.com/products/spaces/, https://glitch.com/~digitalocean-spaces-example - Flickr API - - as a fallback, use the `.data/img` folder + - ~~as a fallback, use the `.data/img` folder~~ - reply to messages - add link to `/admin` - delete multiple posts at once (on the `/admin` page) diff --git a/bot.js b/bot.js index 5b929e4..44fede3 100644 --- a/bot.js +++ b/bot.js @@ -77,49 +77,61 @@ else{ create_post: function(options, cb){ var bot = this; - if ((!options.content || options.content.trim().length === 0 ) && (!options.thumbnail_url)){ - console.log('error: missing post content') + if ((!options.content || options.content.trim().length === 0 ) && !options.attachment ){ + console.log('error: no post content or attachments'); return false; } - var type = options.type || 'Note', + var post_type = options.type || 'Note', + post_description = options.description, post_date = moment().format(), post_in_reply_to = options.in_reply_to || null, - post_content = options.content || '', - post_thumbnail_url = options.thumbnail_url || ''; + post_content = options.content || options.url || '', + post_attachment = JSON.stringify(options.attachment) || '[]'; db.save_post({ - type: type, + type: post_type, content: post_content, - thumbnail_url: post_thumbnail_url + attachment: post_attachment }, function(err, data){ var post_id = data.lastID; - - var obj = { - '@context': 'https://www.w3.org/ns/activitystreams', - 'id': `${bot_url}/post/${post_id}`, - 'type': 'Create', - 'actor': `${bot_url}/bot`, - 'object': { + + var post_object; + + if ( post_type === 'Note' ){ + post_object = { 'id': `${bot_url}/post/${post_id}`, - 'type': type, + 'type': post_type, 'published': post_date, 'attributedTo': `${bot_url}/bot`, 'content': post_content, 'to': 'https://www.w3.org/ns/activitystreams#Public' + }; + + if (options.attachment){ + var attachments = []; + + options.attachment.forEach(function(attachment){ + attachments.push({ + 'type': 'Image', + 'content': attachment.content, + 'url': attachment.url + }); + }); + post_object.attachment = attachments; } - } - - if (options.thumbnail_url){ - obj.preview = { - 'type': 'Link', - 'href': options.thumbnail_url, - 'mediaType': 'image/png' - }; } + var post = { + '@context': 'https://www.w3.org/ns/activitystreams', + 'id': `${bot_url}/post/${post_id}`, + 'type': 'Create', + 'actor': `${bot_url}/bot`, + 'object': post_object + } + if (options.post_in_reply_to){ - obj.object.inReplyTo = post_in_reply_to; + post.object.inReplyTo = post_in_reply_to; } db.get_followers(function(err, followers){ @@ -130,7 +142,7 @@ else{ if (follower.url){ bot.sign_and_send({ follower: follower, - message: obj + message: post }, function(err, data){ }); @@ -140,7 +152,7 @@ else{ }); if (cb){ - cb(null, obj); + cb(null, post); } }); }, diff --git a/examples/generative-art-bot/README.md b/examples/generative-art-bot/README.md new file mode 100644 index 0000000..1ea9195 --- /dev/null +++ b/examples/generative-art-bot/README.md @@ -0,0 +1,14 @@ +![An example of a bot posting generated image](https://cdn.glitch.com/a4825d5c-d1d6-4780-8464-8636780177ef%2Fglitch-fediverse-bot-with-image.png) + +# Generative art bot + +This is an example of a bot that generates images. It uses an image generator from [generative-art-bot](https://glitch.com/edit/#!/generative-art-bot). + +(Note that the code needed to be slightly modified to work with this project.) + +To try this example, copy the content of `examples/generative-art-bot/routes/bot-endpoint.js` to `routes/bot-endpoint.js` file and run your bot using its endpoint. + +By default, the images are stored in the `.data/img` folder. Glitch only provides ~128MB of storage (there are technical limitations to using the `assets` folder, which gives you additional ~500MB), but you can upload your images to NeoCities, which comes with a free 1GB of space, and has a paid plan ([$5/month](https://neocities.org/supporter)) that offers 50GB. + +Simply sign up for an account at [neocities.org](https://neocities.org/), and save your login information to the `.env` file as `NEOCITIES_USERNAME` and `NEOCITIES_PASSWORD`. + diff --git a/examples/generative-art-bot/routes/bot-endpoint.js b/examples/generative-art-bot/routes/bot-endpoint.js new file mode 100644 index 0000000..6df672f --- /dev/null +++ b/examples/generative-art-bot/routes/bot-endpoint.js @@ -0,0 +1,57 @@ +var express = require('express'), + router = express.Router(), + ColorScheme = require('color-scheme'), + generators = { + triangular_mesh: require(__dirname + '/../generators/triangular-mesh.js') + }, + grammar = require(__dirname + '/../tracery/tracery.js').grammar, + helpers = require(__dirname + '/../helpers/general.js'), + image_uploader = require(__dirname + '/../helpers/image-uploader.js'), + bot = require(__dirname + '/../bot.js'); + +router.get('/', function (req, res) { + var content = grammar.flatten("#origin#"), + scheme = new ColorScheme; + +/* + See https://www.npmjs.com/package/color-scheme#schemes on how to use ColorScheme. +*/ + + scheme.from_hex(helpers.get_random_hex().replace('#','')) + .scheme('mono') + .variation('hard'); + + generators.triangular_mesh({ + width: 800, + height: 360, + colors: scheme.colors() + }, function(err, img_data){ + // console.log({img_url: `${bot.bot_url}/${img_data.path}`}); + + var img_name = img_data.path.replace('img/', ''); + + image_uploader.upload_image(img_data, function(err, img_url, data){ + if (err){ + console.log(err); + } else { + // console.log(img_url); + + bot.create_post({ + type: 'Note', + content: content, + attachment: [ + { + url: img_url, + content: content // Image description here. + } + ] + }, function(err, message){ + res.setHeader('Content-Type', 'application/json'); + res.send(JSON.stringify(message)); + }); + } + }); + }); +}); + +module.exports = router; diff --git a/examples/images/README.md b/examples/images/README.md deleted file mode 100644 index cd12b83..0000000 --- a/examples/images/README.md +++ /dev/null @@ -1 +0,0 @@ -*WORK IN PROGRESS* diff --git a/examples/images/routes/bot-endpoint.js b/examples/images/routes/bot-endpoint.js deleted file mode 100644 index 83726c4..0000000 --- a/examples/images/routes/bot-endpoint.js +++ /dev/null @@ -1,40 +0,0 @@ -var express = require('express'), - router = express.Router(), - ColorScheme = require('color-scheme'), - generators = { - triangular_mesh: require(__dirname + '/../generators/triangular-mesh.js') - }, - grammar = require(__dirname + '/../tracery/tracery.js').grammar, - helpers = require(__dirname + '/../helpers/general.js'), - bot = require(__dirname + '/../bot.js'); - -router.get('/', function (req, res) { - var content = grammar.flatten("#origin#"); - - - var scheme = new ColorScheme; - - scheme.from_hex(helpers.get_random_hex().replace('#','')) - .scheme('mono') - .variation('pale'); - - generators.triangular_mesh({ - width: 1024, - height: 100, - colors: scheme.colors() - }, function(err, img_data){ - console.log({img_url: `${bot.bot_url}/${img_data.path}`}); - - bot.create_post({ - content: content, - thumbnail_url: `${bot.bot_url}/${img_data.path}` - }, function(err, message){ - - res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify(message)); - }); - - }); -}); - -module.exports = router; diff --git a/examples/tracery/routes/bot-endpoint.js b/examples/tracery/routes/bot-endpoint.js index 5333c33..bc071aa 100644 --- a/examples/tracery/routes/bot-endpoint.js +++ b/examples/tracery/routes/bot-endpoint.js @@ -7,6 +7,7 @@ router.get('/', function (req, res) { var content = grammar.flatten("#origin#"); bot.create_post({ + type: 'Note', // See www.w3.org/ns/activitystreams#objects content: content }, function(err, message){ res.setHeader('Content-Type', 'application/json'); diff --git a/helpers/db.js b/helpers/db.js index 54319ed..4beecd5 100644 --- a/helpers/db.js +++ b/helpers/db.js @@ -13,7 +13,7 @@ id INT NOT NULL AUTO_INCREMENT date DATETIME DEFAULT current_timestamp type VARCHAR(255) content TEXT -thumbnail_url TEXT +attachment TEXT [this is a stringified JSON, see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-attachment] Followers table @@ -28,7 +28,7 @@ module.exports = { /* TODO: Rewrite this with promises. */ - db.run('CREATE TABLE IF NOT EXISTS Posts (id INTEGER PRIMARY KEY AUTOINCREMENT, date DATETIME DEFAULT current_timestamp, type VARCHAR(255), content TEXT, thumbnail_url TEXT)', function(err, data){ + db.run('CREATE TABLE IF NOT EXISTS Posts (id INTEGER PRIMARY KEY AUTOINCREMENT, date DATETIME DEFAULT current_timestamp, type VARCHAR(255), content TEXT, attachment TEXT)', function(err, data){ if (err){ console.log(err); } @@ -92,11 +92,14 @@ module.exports = { save_post: function(post_data, cb){ var post_type = post_data.type || 'Note', post_content = post_data.content || '', - post_thumbnail_url = post_data.thumbnail_url || ''; + post_attachment = post_data.attachment.toString() || '[]'; - db.serialize(function() { - db.run(`INSERT INTO Posts (type, content, thumbnail_url) VALUES ("${post_type}", "${post_content}", "${post_thumbnail_url}")`, function(err, data){ + // db.run(`INSERT INTO Posts (type, content, attachment) VALUES ("${post_type}", "${post_content}", "${post_attachment}")`, function(err, data){ + db.run(`INSERT INTO Posts (type, content, attachment) VALUES ('${post_type}', '${post_content}', '${post_attachment}')`, function(err, data){ + if (err){ + console.log(err); + } if (cb){ cb(err, this); } diff --git a/helpers/general.js b/helpers/general.js index 66653b9..eaccd02 100644 --- a/helpers/general.js +++ b/helpers/general.js @@ -1,3 +1,9 @@ +if (typeof module !== 'undefined'){ + var fs = require('fs'), + path = require('path'), + request = require('request'); +} + var helpers = { get_timestamp: function(){ return Math.round((new Date()).getTime() / 1000); @@ -18,6 +24,67 @@ var helpers = { // https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors var f = parseInt(color.slice(1),16),t=percent<0?0:255,p=percent<0?percent*-1:percent,R=f>>16,G=f>>8&0x00FF,B=f&0x0000FF; return `#${(0x1000000+(Math.round((t-R)*p)+R)*0x10000+(Math.round((t-G)*p)+G)*0x100+(Math.round((t-B)*p)+B)).toString(16).slice(1)}`; + }, + load_image_assets: function(cb){ + /* Load images from the assets folder */ + console.log('reading assets folder...') + var that = this; + fs.readFile('./.glitch-assets', 'utf8', function (err, data) { + if (err) { + console.log('error:', err); + return false; + } + data = data.split('\n'); + var data_json = JSON.parse('[' + data.join(',').slice(0, -1) + ']'), + deleted_images = data_json.reduce(function(filtered, data_img) { + if (data_img.deleted) { + var someNewValue = { name: data_img.name, newProperty: 'Foo' } + filtered.push(data_img.uuid); + } + return filtered; + }, []), + img_urls = []; + + for (var i = 0, j = data.length; i < j; i++){ + if (data[i].length){ + var img_data = JSON.parse(data[i]), + image_url = img_data.url; + + if (image_url && deleted_images.indexOf(img_data.uuid) === -1 && that.extension_check(image_url)){ + var file_name = that.get_filename_from_url(image_url).split('%2F')[1]; + // console.log(`- ${file_name}`); + img_urls.push(image_url); + } + } + } + cb(null, img_urls); + }); + }, + extension_check: function(url) { + var file_extension = path.extname(url).toLowerCase(), + extensions = ['.png', '.jpg', '.jpeg', '.gif']; + return extensions.indexOf(file_extension) !== -1; + }, + get_filename_from_url: function(url) { + return url.substring(url.lastIndexOf('/') + 1); + }, + load_image: function(url, cb) { + console.log(`loading remote image: ${url} ...`); + request({url: url, encoding: null}, function (err, res, body) { + if (!err && res.statusCode == 200) { + var b64content = 'data:' + res.headers['content-type'] + ';base64,'; + console.log('image loaded...'); + cb(null, body.toString('base64')); + } else { + console.log('ERROR:', err); + cb(err); + } + }); + }, + download_file: function(uri, filename, cb){ + request.head(uri, function(err, res, body){ + request(uri).pipe(fs.createWriteStream(filename)).on('close', cb); + }); } }; diff --git a/helpers/image-uploader.js b/helpers/image-uploader.js new file mode 100644 index 0000000..7fee522 --- /dev/null +++ b/helpers/image-uploader.js @@ -0,0 +1,58 @@ +var fs = require('fs'), + bot = require(__dirname + '/../bot.js'), + NeoCities = require('neocities'), + use_neocities = false; + + +if (process.env.NEOCITIES_USERNAME && process.env.NEOCITIES_PASSWORD){ + var neocities_api = new NeoCities(process.env.NEOCITIES_USERNAME, process.env.NEOCITIES_PASSWORD); + use_neocities = true; +} + +module.exports = { + upload_image: function(img_data, cb){ + var img_url = `${bot.bot_url}/${img_data.path}`, + img_name = img_data.path.replace('img/', ''); + + if (use_neocities){ + /* + First option, NeoCities. They offer 1GB for free, and with a + paid option ($5/month) you get 50GB. + + https://neocities.org/supporter + + */ + neocities_api.upload([ + { + name: img_name, + path: `.data/img/${img_name}` + } + ], function(resp) { + console.log(resp); + var img_url = null; + + if (resp && resp.result === 'success'){ + fs.unlink(`.data/img/${img_name}`, function(err){ + if (err){ + console.log(err); + } + else{ + console.log('deleted local image'); + } + }); + + img_url = `https://${process.env.NEOCITIES_USERNAME}.neocities.org/${img_name}`; + } + if (cb){ + cb(null, img_url, resp); + } + }); + } else { + /* Fall-back to local storage. It's only ~128 MB, so good luck! */ + if (cb){ + cb(null, img_url); + } + } + } +}; + diff --git a/package.json b/package.json index 2fcfeb0..80126be 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "connect-sqlite3": "^0.9.11", "canvas": "^2.0.0-alpha.17", "color-scheme": "^1.0.1", - "gifencoder": "^1.1.0" + "gifencoder": "^1.1.0", + "neocities": "^0.0.3" }, "engines": { "node": "8.x" diff --git a/routes/bot-endpoint.js b/routes/bot-endpoint.js index 5333c33..bc071aa 100644 --- a/routes/bot-endpoint.js +++ b/routes/bot-endpoint.js @@ -7,6 +7,7 @@ router.get('/', function (req, res) { var content = grammar.flatten("#origin#"); bot.create_post({ + type: 'Note', // See www.w3.org/ns/activitystreams#objects content: content }, function(err, message){ res.setHeader('Content-Type', 'application/json'); diff --git a/routes/index.js b/routes/index.js index 467866a..1b5722c 100644 --- a/routes/index.js +++ b/routes/index.js @@ -25,6 +25,9 @@ router.get('/', function (req, res) { if (data && data.posts && data.posts.length > 0){ data.posts.forEach(function(post){ post.date_formatted = moment(post.date).fromNow(); + try{ + post.attachment = JSON.parse(post.attachment); + } catch(err){ /*noop*/ } }); } @@ -38,7 +41,7 @@ router.get('/', function (req, res) { if (page > 1 && page <= data.page_count){ show_previous_page = true; } - + res.render('../views/home.handlebars', { project_name: process.env.PROJECT_DOMAIN, bot_url: `https://${process.env.PROJECT_DOMAIN}.glitch.me/`, diff --git a/routes/post.js b/routes/post.js index 1511218..c1d51a6 100644 --- a/routes/post.js +++ b/routes/post.js @@ -8,6 +8,11 @@ router.get('/:id', function(req, res) { db.get_post(post_id, function(err, post_data){ if (post_data){ post_data.date_formatted = moment(post_data.date).fromNow();; + + try{ + post_data.attachment = JSON.parse(post_data.attachment); + } catch(err){ /*noop*/ } + res.render('../views/post.handlebars', { project_name: process.env.PROJECT_DOMAIN, diff --git a/server.js b/server.js index e6df6c4..7cb9acb 100644 --- a/server.js +++ b/server.js @@ -5,13 +5,12 @@ var app = require(__dirname + '/app.js'), // db.drop_table('Posts'); // db.drop_table('Followers'); +// db.get_followers(function(err, data){ +// console.log(data); +// }); + db.init(); var listener = app.listen(process.env.PORT, function() { console.log(`app is running on port ${listener.address().port}...`); }); - -// db.get_followers(function(err, data){ -// console.log(data); -// }); - diff --git a/sessions b/sessions index 270815d4bc0ebc25a7f2df0088557a5ebad016b5..e9c334c61638c8e0fdc2571a2fb393e4eadfcbbb 100644 GIT binary patch delta 30962 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 C`UVjI delta 2368 zcmX|D3v^9a8oqmLE$Svlsyf6f!%~mbbSl)Niq4`l9U(Cu#q4uibJjZde*6FTzxTht|J(n) zOB$plON8}NPA$jba73~<^!U5>$68Ks$__))jau6j30hc`L>35Fgc1B6=$SaG?wzoQ zPAEtoN|-w$Aj0RaQEeH-#x6S`%=pgOUhk4qWv%^f*&`kY>)GiNNAmpOthqtLZg$dVY6-CQb$<~L1@Qfd>? zke9V7j&``wL8dqxodfl1T~be|iAq3m$azwOktcN(*OLdr2_caW@D8nj%fpR$Agb0L zXjA;YzI6o!X_8zVyId1e#o~AA`!Y7`ZWVSAi;P*0HxO}Xo0SigBw@VxB57343Dxp1 z+z_r->&pL#`#T?z$Jzvob-g-V-7oYM^PKH)4Z5vfLPS`Evz>dm z>13Jmz&VvGl`Q!^GElmK;*Al;em+GUikIRrIgu~GXK;(IDZ3=cH0>u18z#9g{LA@@ z+F9={Nb(7^mH$!-h-hK7WCv4!gpC_RxVbIMP(1+yR(T-&xQY2o(B9*L z&Rb&O>?#Xf8$IA!JrW)>n}78e;Ai9cr54->#6Wty2cnOdSg3+Z2Cr?5gX!P~ToD7? z)}+C)GM0U^z0CUBnCN+j)<$}DzpX7mopKk zc0}X2_Oi(RL;jijj6c8wKU>L>uJM0Rp9$$gcjaS25N-)ysdc0uDOS#sd*XEQyi_Iz z)eF)nsf!Y>jmFLD8vHfhsd{k{`X|m(-@<(ym$A;hL8g8r_b|Q}U(npfU&Lf%oiW20 zhPnywNC%YxMknWgo!>Y=m5(W{&gITr=S$8A{ompf{Wu@3Z{x3WkM(!;SIHzPU#Qd* zloDMgr@29LmE7aG@*JV!1pC_Z5h2yj=)Gyud<_#Q{)$mf;jn)Jyw_^M+!hNCv3Jy` ztau6FkMqE?yB6GYTad}>Kfc+5(cLXbZ!(E=JH+Lv?^z*AVbwj)h8Bw^l%Y8KOBr%Q z@S;h`p3syd(^&5q3xn^a(O1im1#KT$u=`OQn2+7muVRZn17_Xv!~)-06O2a$a+uD} z-Defw$Q%(pnCN!1d`e-r<$>ZX6ApJ}!r#tJA5vMz7832w274*P-DT6+2 zV_VO`-g_Q;B+|yGs8?m{MzSQYRH2yA?WMse+f@&19((BP3bx=%LJXX`>wz1L8lPdS zcT=boUdhHZgoBD5G!wimD2p8idVOOOxrNm(0KQyylRGdt7hWG~(w40#6SCbF-3N@} zH!X`^w+RZL&1Urso!L9bq($Y3(3=u|n=Y2I%WnNHR>q=sCOxg-LONf=sc<*iP5bCL z=KsG2wbdxCh;5XGPdS96!d9VB$PnTM!vDa3!EfZ}@FV#c?pN+RZm)5XEH(BS>+mUK z2FW&t8J(n*-vnxllk4!CJoV%HHd~f@f-Z}t+I8)(8r6K_Q*E^77N@Jv)C+2zx`rF9 zzD4>Aw^gs&QE5%$rYUE|Aj8ekopXFmDOso}~~p}J(TUPjcDA)MX;ne^%e#w*TdfzgN!_fBHN@J{v; zLE1Q(EfM7H!A^Q+BI-_ePeg9&lWhyovI)%X-@-y~PCx= 0.6' resolution: integrity: sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk= + /neocities/0.0.3: + dependencies: + form-data: 2.3.2 + dev: false + resolution: + integrity: sha1-uFXSRjC2Ec7zUUinedXH73EgDAs= /node-forge/0.6.49: dev: false resolution: @@ -5111,6 +5118,7 @@ specifiers: generate-rsa-keypair: ^0.1.2 gifencoder: ^1.1.0 momentjs: ^2.0.0 + neocities: ^0.0.3 node-openssl-cert: ^0.0.47 node-sass-middleware: ^0.11.0 pem: ^1.13.0 diff --git a/src/styles/layout.scss b/src/styles/_layout.scss similarity index 51% rename from src/styles/layout.scss rename to src/styles/_layout.scss index 8cb8d87..68b396d 100644 --- a/src/styles/layout.scss +++ b/src/styles/_layout.scss @@ -1,4 +1,13 @@ #about-bot.jumbotron{ border-top-left-radius: 0; border-top-right-radius: 0; -} \ No newline at end of file +} + +main{ + max-width: 720px; + margin: 0 auto; +} + +img{ + max-width: 100%; +} diff --git a/src/styles/_typography.scss b/src/styles/_typography.scss new file mode 100644 index 0000000..9132fb6 --- /dev/null +++ b/src/styles/_typography.scss @@ -0,0 +1 @@ +@import "variables"; diff --git a/src/styles/variables.scss b/src/styles/_variables.scss similarity index 100% rename from src/styles/variables.scss rename to src/styles/_variables.scss diff --git a/src/styles/styles.scss b/src/styles/styles.scss index ab10344..87b9098 100644 --- a/src/styles/styles.scss +++ b/src/styles/styles.scss @@ -1,3 +1,3 @@ -// @import "variables"; -// @import "typography"; +@import "variables"; +@import "typography"; @import "layout"; diff --git a/src/styles/typography.scss b/src/styles/typography.scss deleted file mode 100644 index ddee521..0000000 --- a/src/styles/typography.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import "variables"; - -body{ - font-size: 30px; - color: $red; -} \ No newline at end of file diff --git a/views/home.handlebars b/views/home.handlebars index 7fc5308..bee948a 100644 --- a/views/home.handlebars +++ b/views/home.handlebars @@ -3,15 +3,17 @@ {{#each posts}}
- {{#if this.thumbnail_url}} - - {{/if}} - {{#if this.content}} + {{#if this.attachment}} + + + + {{/if}} + {{#equals this.type "Note"}}

{{this.content}}

- {{/if}} + {{/equals}}