Set up Medium fetching via bookmarklet!
Currently this only loads post details; it doesn't process it (ie. download images, etc.) yet.
This commit is contained in:
parent
d2b2bfa2e2
commit
8117d24b4c
4 changed files with 227 additions and 4 deletions
66
server.js
66
server.js
|
@ -4,11 +4,17 @@ const { vueRenderer } = require('@doweb/vuexpress')
|
|||
|
||||
const app = express()
|
||||
|
||||
// ALlow URLencoded API
|
||||
app.use(bodyParser.urlencoded({
|
||||
extended: false
|
||||
}))
|
||||
|
||||
// Allow JSON API
|
||||
app.use(bodyParser('json'))
|
||||
|
||||
// Enable static files
|
||||
app.use(express.static('public'))
|
||||
app.use(express.static('static'))
|
||||
|
||||
// Set up VueXpress
|
||||
let options = {
|
||||
|
@ -30,7 +36,65 @@ app.use(renderer)
|
|||
// Views
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.render('index', { message: 'Hey, welcome to Seance!' })
|
||||
res.render('index')
|
||||
})
|
||||
|
||||
app.get('/fetch/', (req, res) => {
|
||||
res.redirect('/')
|
||||
})
|
||||
|
||||
app.post('/fetch/', (req, res) => {
|
||||
var json
|
||||
var post
|
||||
|
||||
try {
|
||||
json = JSON.parse(req.body.data)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
|
||||
if (json) {
|
||||
post = json.payload.value
|
||||
console.log(post)
|
||||
|
||||
// set author
|
||||
post.author = post.displayAuthor
|
||||
|
||||
// If the author's not available, get it from somewhere else
|
||||
// function courtesy mediumexporter
|
||||
let authors = []
|
||||
if (json.payload.references && json.payload.references.User) {
|
||||
Object.keys(json.payload.references.User).forEach(k => {
|
||||
let u = json.payload.references.User[k]
|
||||
authors.push({
|
||||
name: u.name,
|
||||
username: u.username,
|
||||
userId: u.userId
|
||||
})
|
||||
})
|
||||
post.authors = authors
|
||||
|
||||
if (!post.author) {
|
||||
post.author = authors[0].name
|
||||
}
|
||||
}
|
||||
|
||||
// set featured image
|
||||
if (post.virtuals.previewImage) {
|
||||
post.featuredImage = 'https://cdn-images-1.medium.com/max/800/' + post.virtuals.previewImage.imageId
|
||||
}
|
||||
}
|
||||
|
||||
// render the final post
|
||||
res.render('fetch-medium', {
|
||||
post: {
|
||||
title: post.title,
|
||||
subtitle: post.content.subtitle,
|
||||
author: post.author,
|
||||
featuredImage: post.featuredImage,
|
||||
mediumUrl: post.mediumUrl,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
app.get('/api/', (req, res) => {
|
||||
|
|
77
static/bookmarklet.js
Normal file
77
static/bookmarklet.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
(() => {
|
||||
|
||||
const baseUrl = 'http://localhost:4000'
|
||||
|
||||
function resetTitle() {
|
||||
document.title = document.title.replace('[Loading...] ', '')
|
||||
}
|
||||
|
||||
// Check that we're not on a Seance page
|
||||
if (window.location.href.startsWith(baseUrl)) {
|
||||
alert('That\'s not how to do it, silly!\n\nYou\'re supposed to BOOKMARK this link (by dragging it to the bookmarks bar or right-clicking and saying "save bookmark").\n\nWait till you\'re on a Medium page; don\'t try to start clicking on it right now ;)')
|
||||
|
||||
// Reset title
|
||||
resetTitle()
|
||||
return
|
||||
}
|
||||
|
||||
// Keep the user engaged with a nice bannery message
|
||||
|
||||
var d1 = document.createElement('div')
|
||||
d1.style="position: absolute; top:0; right: 0; left: 0; background-color: turquoise; min-height: 2em; z-index: 100; text-align: center;"
|
||||
|
||||
var p1 = document.createElement('p')
|
||||
p1.innerHTML = 'Seance loading in progress...'
|
||||
p1.style='font-size: 2rem; margin: 2rem; color: white;'
|
||||
|
||||
d1.appendChild(p1)
|
||||
document.body.appendChild(d1)
|
||||
|
||||
// Form sending function
|
||||
function handleJSON(data) {
|
||||
try {
|
||||
data = JSON.parse(data.slice(data.indexOf('{')))
|
||||
} catch (err) {
|
||||
alert('Something went wrong fetching the post. Please make sure you\'re on a Medium site and try again.')
|
||||
|
||||
// Hide the loading screen
|
||||
d1.parentElement.removeChild(d1)
|
||||
|
||||
// Reset the title
|
||||
resetTitle()
|
||||
|
||||
// Give up
|
||||
return
|
||||
}
|
||||
|
||||
// Create a form to hold the data
|
||||
var f = document.createElement('form')
|
||||
f.action=baseUrl + '/fetch/'
|
||||
f.method='post'
|
||||
|
||||
// Create hidden input to hold data
|
||||
i = document.createElement('input')
|
||||
i.name='data'
|
||||
i.type='hidden'
|
||||
i.value=JSON.stringify(data)
|
||||
|
||||
// Assign children to parents
|
||||
f.appendChild(i)
|
||||
document.body.appendChild(f)
|
||||
|
||||
// Submit the form
|
||||
f.submit()
|
||||
}
|
||||
|
||||
// fetch the XML
|
||||
var r = new XMLHttpRequest()
|
||||
r.open('GET', window.location + '?format=json')
|
||||
r.onreadystatechange = function () {
|
||||
if (r.readyState == 4 && r.status == 200) {
|
||||
handleJSON(r.responseText)
|
||||
}
|
||||
}
|
||||
r.setRequestHeader("Content-Type", "application/json;charset=utf-8")
|
||||
r.send(null)
|
||||
|
||||
})()
|
79
views/fetch-medium.vue
Normal file
79
views/fetch-medium.vue
Normal file
|
@ -0,0 +1,79 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<nav class="navbar has-background-light" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item is-size-5" href="/">Seance</a>
|
||||
|
||||
<a role="button" class="navbar navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarMain">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="navbarMain" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
<a class="navbar-item">Home</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
<div class="navbar-item">
|
||||
<div class="button is-primary" v-if="user">{{ user.name }}</div>
|
||||
<div class="button is-primary" v-else>Sign in</div>
|
||||
<input type="button" :value="user ? user.name : 'Parson'">
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="column is-half is-offset-one-quarter has-text-centered" style="margin-top: 10%;">
|
||||
<h1 class="title">{{ title }}</h1>
|
||||
|
||||
<div class="card has-text-left">
|
||||
<header class="card-header">
|
||||
<p class="card-header-title">Fetch from Medium</p>
|
||||
</header>
|
||||
<div class="card-image" v-if="post.featuredImage">
|
||||
<figure class="image is-4by3">
|
||||
<img :src="post.featuredImage" :alt="post.title" />
|
||||
</figure>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<p class="title is-4">{{ post.title }}</p>
|
||||
<p v-if="post.subtitle" class="subtitle is-6">{{ post.subtitle }}</p>
|
||||
<p>by {{ post.author }}</p>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<a :href="post.mediumUrl" v-if="post.mediumUrl" class="card-footer-item">View on Medium</a>
|
||||
<a href="#" class="card-footer-item">Fetch</a>
|
||||
<a href="#" class="card-footer-item">Push to Ghost</a>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Seance',
|
||||
data () {
|
||||
return {
|
||||
title: 'Seance',
|
||||
user: null,
|
||||
host: 'http://localhost:4000',
|
||||
post: {
|
||||
title: '(untitled)',
|
||||
subtitle: '',
|
||||
featuredImage: 'https://via.placeholder.com/400x300/?text=No+preview',
|
||||
author: 'Unknown Author',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../node_modules/bulma/bulma.sass"
|
||||
</style>
|
|
@ -25,9 +25,12 @@
|
|||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container">
|
||||
<div class="column is-half is-offset-one-quarter has-text-centered" style="margin-top: 10%;">
|
||||
<h1 class="title">{{ title }}</h1>
|
||||
<p>{{ message }}</p>
|
||||
<form action="/fetch/medium/" method="post">
|
||||
<input type="search" class="input is-medium is-rounded is-focused" placeholder="Enter a Medium link..."/>
|
||||
<p>or, use the bookmarklet <a class="button is-primary is-small" href="javascript:(()=>{document.title='[Loading...] '+document.title;s=document.createElement('script');s.src='http://localhost:4000/bookmarklet.js';document.body.appendChild(s)})()">Add to Seance</a></p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -39,8 +42,8 @@
|
|||
data () {
|
||||
return {
|
||||
title: 'Seance',
|
||||
message: 'Hi there',
|
||||
user: null,
|
||||
host: 'http://localhost:4000',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue