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:
Hippo 2020-05-07 22:57:34 +05:30
parent d2b2bfa2e2
commit 8117d24b4c
4 changed files with 227 additions and 4 deletions

View file

@ -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
View 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
View 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>

View file

@ -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',
}
}
}