Allow Seance upload/download through websockets
It's all working! Yay!!
This commit is contained in:
parent
f99272cd84
commit
4cfea0ac3d
5 changed files with 253 additions and 5 deletions
|
@ -25,6 +25,7 @@
|
||||||
"css-loader": "^3.5.3",
|
"css-loader": "^3.5.3",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
"express-ws": "^4.0.0",
|
||||||
"js-yaml": "^3.13.1",
|
"js-yaml": "^3.13.1",
|
||||||
"markdown": "^0.5.0",
|
"markdown": "^0.5.0",
|
||||||
"mediumexporter": "github:badrihippo/mediumexporter#seance-latest",
|
"mediumexporter": "github:badrihippo/mediumexporter#seance-latest",
|
||||||
|
|
91
server.js
91
server.js
|
@ -4,7 +4,10 @@ const { vueRenderer } = require('@doweb/vuexpress')
|
||||||
const slugify = require('underscore.string/slugify')
|
const slugify = require('underscore.string/slugify')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
|
||||||
|
const { Seance } = require ('./seance')
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
|
var expressWs = require('express-ws')(app)
|
||||||
|
|
||||||
// ALlow URLencoded API
|
// ALlow URLencoded API
|
||||||
app.use(bodyParser.urlencoded({
|
app.use(bodyParser.urlencoded({
|
||||||
|
@ -25,6 +28,9 @@ let options = {
|
||||||
watch: true,
|
watch: true,
|
||||||
metaInfo: {
|
metaInfo: {
|
||||||
title: 'Seance',
|
title: 'Seance',
|
||||||
|
script: [
|
||||||
|
{ type: 'text/javascript', src: '/app.js' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
extractCSS: true,
|
extractCSS: true,
|
||||||
cssOutputPath: '/css/styles.css',
|
cssOutputPath: '/css/styles.css',
|
||||||
|
@ -110,6 +116,7 @@ app.post('/fetch', (req, res) => {
|
||||||
author: post.author,
|
author: post.author,
|
||||||
featuredImage: post.featuredImage,
|
featuredImage: post.featuredImage,
|
||||||
mediumUrl: post.mediumUrl,
|
mediumUrl: post.mediumUrl,
|
||||||
|
slug: post.slug,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -125,6 +132,90 @@ app.get('/api', (req, res) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.ws('/ws/fetch-medium', (ws, req) => {
|
||||||
|
ws.on('message', async(msg) => {
|
||||||
|
console.log(`We got: ${msg}`)
|
||||||
|
|
||||||
|
command = msg.split(' ')
|
||||||
|
if (command.length == 3 && command[0] == 'fetch') {
|
||||||
|
const postSlug = command[1]
|
||||||
|
const mediumUrl = command[2]
|
||||||
|
|
||||||
|
const postMetaFile = `${postSlug}.json`
|
||||||
|
|
||||||
|
if (!fs.existsSync(postMetaFile)) {
|
||||||
|
ws.send(`error: post ${postSlug} not yet loaded`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the Seance session
|
||||||
|
seance = new Seance()
|
||||||
|
|
||||||
|
// set up handlers
|
||||||
|
seance.on('update', (e) => {
|
||||||
|
ws.send(`update: ${e.message}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
seance.on('notification', (e) => {
|
||||||
|
ws.send(`notification: ${e.message}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
seance.on('error', (e) => {
|
||||||
|
ws.send(`error: ${e.message}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
seance.fetchFromMedium(postMetaFile)
|
||||||
|
.then((post) => {
|
||||||
|
console.info(`"${post.title}" fetched successfully.`)
|
||||||
|
ws.send(`done: ${post.slug}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
ws.on('close', () => {
|
||||||
|
console.log('socket closed')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
app.ws('/ws/push-ghost', (ws, req) => {
|
||||||
|
ws.on('message', async(msg) => {
|
||||||
|
console.log(`We got: ${msg}`)
|
||||||
|
|
||||||
|
command = msg.split(' ')
|
||||||
|
if (command.length == 2 && command[0] == 'push') {
|
||||||
|
const postSlug = command[1]
|
||||||
|
|
||||||
|
// Start the Seance session
|
||||||
|
seance = new Seance()
|
||||||
|
|
||||||
|
// set up handlers
|
||||||
|
seance.on('update', (e) => {
|
||||||
|
ws.send(`update: ${e.message}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
seance.on('notification', (e) => {
|
||||||
|
ws.send(`notification: ${e.message}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
seance.on('error', (e) => {
|
||||||
|
ws.send(`error: ${e.message}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
seance.pushToGhost(postSlug)
|
||||||
|
.then(() => {
|
||||||
|
console.info(`"${postSlug}" pushed successfully.`)
|
||||||
|
ws.send(`done: ${postSlug}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
ws.on('close', () => {
|
||||||
|
console.log('socket closed')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const port = process.env.PORT || 4000
|
const port = process.env.PORT || 4000
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Listening on ${port}`)
|
console.log(`Listening on ${port}`)
|
||||||
|
|
138
static/app.js
Normal file
138
static/app.js
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
function addNotification(msg, className='is-warning') {
|
||||||
|
var n = document.createElement('div')
|
||||||
|
n.classList.add('notification')
|
||||||
|
n.classList.add(className)
|
||||||
|
n.innerHTML = msg
|
||||||
|
document.getElementById('notifications').appendChild(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
function mediumFetch(e) {
|
||||||
|
if(e) { e.preventDefault() }
|
||||||
|
|
||||||
|
const postSlug = document.querySelector('[data-post-slug]').dataset.postSlug
|
||||||
|
const mediumUrl = document.querySelector('[data-post-mediumurl]').dataset.postMediumurl
|
||||||
|
console.log('Fetching ' + postSlug)
|
||||||
|
const socketProtocol = (window.location.protocol === 'https:' ? 'wss:' : 'ws:')
|
||||||
|
const socketUrl = socketProtocol + '//' + window.location.host + '/ws/fetch-medium/'
|
||||||
|
const socket = new WebSocket(socketUrl)
|
||||||
|
|
||||||
|
// set up the div
|
||||||
|
statusDiv = document.getElementById('statusbar')
|
||||||
|
statusText = document.createElement('p')
|
||||||
|
statusText.innerText = 'Fetching post'
|
||||||
|
statusText.classList.add('card-footer-item')
|
||||||
|
|
||||||
|
// hide buttons
|
||||||
|
for (i=0;i<statusDiv.children.length;i++) {
|
||||||
|
statusDiv.children[i].classList.add('is-hidden')
|
||||||
|
}
|
||||||
|
|
||||||
|
statusDiv.appendChild(statusText)
|
||||||
|
|
||||||
|
socket.onopen = () => {
|
||||||
|
socket.send('fetch ' + postSlug + ' ' + mediumUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onmessage = m => {
|
||||||
|
console.log('Got message: ' + m.data)
|
||||||
|
|
||||||
|
console.log(m.data)
|
||||||
|
if (m.data.indexOf(': ') != -1) {
|
||||||
|
var command = m.data.slice(0, m.data.indexOf(': '))
|
||||||
|
var commandData = m.data.slice(m.data.indexOf(': ')+2)
|
||||||
|
|
||||||
|
statusText.innerHTML = `<b>${command}: </b>${commandData}`
|
||||||
|
|
||||||
|
// if it's done, clean up
|
||||||
|
if (command == 'done') {
|
||||||
|
// remove statusbar
|
||||||
|
statusDiv.removeChild(statusText)
|
||||||
|
|
||||||
|
// show alert
|
||||||
|
addNotification(`${postSlug} has been fetched successfully. You can now push it to Ghost`, 'is-success')
|
||||||
|
|
||||||
|
// show buttons, change text and save slug
|
||||||
|
for (i=0;i<statusDiv.children.length;i++) {
|
||||||
|
if (statusDiv.children[i].innerText.startsWith('Fetch')) {
|
||||||
|
statusDiv.children[i].innerText = 'Fetch again'
|
||||||
|
}
|
||||||
|
if (statusDiv.children[i].innerText == 'Push to Ghost') {
|
||||||
|
statusDiv.children[i].dataset.postSlug = commandData
|
||||||
|
}
|
||||||
|
statusDiv.children[i].classList.remove('is-hidden')
|
||||||
|
}
|
||||||
|
} else if (command == 'notification') {
|
||||||
|
addNotification(commandData)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
statusText.innerHTML = m.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ghostPush(e) {
|
||||||
|
if(e) { e.preventDefault() }
|
||||||
|
|
||||||
|
const postSlug = document.querySelector('[data-post-slug]').dataset.postSlug
|
||||||
|
console.log('Pushing ' + postSlug)
|
||||||
|
const socketProtocol = (window.location.protocol === 'https:' ? 'wss:' : 'ws:')
|
||||||
|
const socketUrl = socketProtocol + '//' + window.location.host + '/ws/push-ghost/'
|
||||||
|
const socket = new WebSocket(socketUrl)
|
||||||
|
|
||||||
|
// set up the div
|
||||||
|
statusDiv = document.getElementById('statusbar')
|
||||||
|
statusText = document.createElement('p')
|
||||||
|
statusText.innerText = 'Pushing to Ghost'
|
||||||
|
statusText.classList.add('card-footer-item')
|
||||||
|
|
||||||
|
// hide buttons
|
||||||
|
for (i=0;i<statusDiv.children.length;i++) {
|
||||||
|
statusDiv.children[i].classList.add('is-hidden')
|
||||||
|
}
|
||||||
|
|
||||||
|
statusDiv.appendChild(statusText)
|
||||||
|
|
||||||
|
socket.onopen = () => {
|
||||||
|
socket.send('push ' + postSlug)
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.onmessage = m => {
|
||||||
|
console.log('Got message: ' + m.data)
|
||||||
|
|
||||||
|
console.log(m.data)
|
||||||
|
|
||||||
|
if (m.data.indexOf(': ') != -1) {
|
||||||
|
var command = m.data.slice(0, m.data.indexOf(': '))
|
||||||
|
var commandData = m.data.slice(m.data.indexOf(': ')+2)
|
||||||
|
|
||||||
|
statusText.innerHTML = `<b>${command}: </b>${commandData}`
|
||||||
|
|
||||||
|
// if it's done, clean up
|
||||||
|
if (command[0] == 'done') {
|
||||||
|
// remove statusbar
|
||||||
|
statusDiv.removeChild(statusText)
|
||||||
|
|
||||||
|
// show alert
|
||||||
|
addNotification(`${postSlug} has been pushed to Ghost`, 'is-success')
|
||||||
|
|
||||||
|
// show buttons and change text
|
||||||
|
for (i=0;i<statusDiv.children.length;i++) {
|
||||||
|
if (statusDiv.children[i].innerText.startsWith('Push')) {
|
||||||
|
statusDiv.children[i].innerText = 'Push again'
|
||||||
|
}
|
||||||
|
statusDiv.children[i].classList.remove('is-hidden')
|
||||||
|
}
|
||||||
|
} else if (command == 'notification') {
|
||||||
|
var n = document.createElement('div')
|
||||||
|
n.classList.add('notification')
|
||||||
|
n.classList.add('is-warning')
|
||||||
|
n.innerHTML = commandData
|
||||||
|
document.getElementById('notifications').appendChild(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
statusText.innerHTML = m.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,8 +26,6 @@
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="column is-half is-offset-one-quarter has-text-centered">
|
<div class="column is-half is-offset-one-quarter has-text-centered">
|
||||||
<h1 class="title">{{ title }}</h1>
|
|
||||||
|
|
||||||
<div class="card has-text-left">
|
<div class="card has-text-left">
|
||||||
<header class="card-header">
|
<header class="card-header">
|
||||||
<p class="card-header-title">Fetch from Medium</p>
|
<p class="card-header-title">Fetch from Medium</p>
|
||||||
|
@ -41,11 +39,12 @@
|
||||||
<p class="title is-4">{{ post.title }}</p>
|
<p class="title is-4">{{ post.title }}</p>
|
||||||
<p v-if="post.subtitle" class="subtitle is-6">{{ post.subtitle }}</p>
|
<p v-if="post.subtitle" class="subtitle is-6">{{ post.subtitle }}</p>
|
||||||
<p>by {{ post.author }}</p>
|
<p>by {{ post.author }}</p>
|
||||||
|
<div class="notifications" id="notifications"></div>
|
||||||
</div>
|
</div>
|
||||||
<footer class="card-footer">
|
<footer class="card-footer" id="statusbar">
|
||||||
<a :href="post.mediumUrl" v-if="post.mediumUrl" class="card-footer-item">View on Medium</a>
|
<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" onclick="mediumFetch(event)" :data-post-slug="post.slug" :data-post-mediumurl="post.mediumUrl">Fetch</a>
|
||||||
<a href="#" class="card-footer-item">Push to Ghost</a>
|
<a href="#" class="card-footer-item" onclick="ghostPush(event)">Push to Ghost</a>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
19
yarn.lock
19
yarn.lock
|
@ -388,6 +388,11 @@ async-foreach@^0.1.3:
|
||||||
resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542"
|
resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542"
|
||||||
integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=
|
integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=
|
||||||
|
|
||||||
|
async-limiter@~1.0.0:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
|
||||||
|
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
|
||||||
|
|
||||||
async@^2.3.0:
|
async@^2.3.0:
|
||||||
version "2.6.3"
|
version "2.6.3"
|
||||||
resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
|
resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
|
||||||
|
@ -2263,6 +2268,13 @@ expand-brackets@^2.1.4:
|
||||||
snapdragon "^0.8.1"
|
snapdragon "^0.8.1"
|
||||||
to-regex "^3.0.1"
|
to-regex "^3.0.1"
|
||||||
|
|
||||||
|
express-ws@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/express-ws/-/express-ws-4.0.0.tgz#dabd8dc974516418902a41fe6e30ed949b4d36c4"
|
||||||
|
integrity sha512-KEyUw8AwRET2iFjFsI1EJQrJ/fHeGiJtgpYgEWG3yDv4l/To/m3a2GaYfeGyB3lsWdvbesjF5XCMx+SVBgAAYw==
|
||||||
|
dependencies:
|
||||||
|
ws "^5.2.0"
|
||||||
|
|
||||||
express@^4.17.1:
|
express@^4.17.1:
|
||||||
version "4.17.1"
|
version "4.17.1"
|
||||||
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
|
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
|
||||||
|
@ -6740,6 +6752,13 @@ wrappy@1:
|
||||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||||
|
|
||||||
|
ws@^5.2.0:
|
||||||
|
version "5.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f"
|
||||||
|
integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==
|
||||||
|
dependencies:
|
||||||
|
async-limiter "~1.0.0"
|
||||||
|
|
||||||
xml2js@^0.4.19:
|
xml2js@^0.4.19:
|
||||||
version "0.4.22"
|
version "0.4.22"
|
||||||
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.22.tgz#4fa2d846ec803237de86f30aa9b5f70b6600de02"
|
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.22.tgz#4fa2d846ec803237de86f30aa9b5f70b6600de02"
|
||||||
|
|
Loading…
Reference in a new issue