Allow Seance to be hosted on a subpath

Now, instead of having to host Seance right at example.com, you
can also host it underneath at example.com/seance by changing the
appropriate setting!
This commit is contained in:
Hippo 2021-07-25 21:39:08 +05:30
parent ab5ced7c6a
commit ae1aacd17c
7 changed files with 74 additions and 21 deletions

View file

@ -52,6 +52,9 @@ files, and for your Ghost API interface. The parameters to set are:
* `GHOST_URL` - URL of your Ghost installation
* `GHOST_VERSION` - 'v2' or 'v3' depending on which version you're using
* `GHOST_ADMIN_KEY` - 'Admi API key for Ghost'
* `BASE_PATH` - Subdomain to serve the website on (eg. `/seance`).
Default: `/`. If you're not planning to use the Seance server, then
you can safely ignore this option.
In case you're wondering about the WebDAV server: that's the setup we
use at Snipette. We'd like to eventually let you upload directly through
@ -103,7 +106,16 @@ file via the "Labs" section. This is required becaues Ghost doesn't
let you directly add users; it only lets you import them.
Seance also attempts to fetch the Medium user's profile image and upload
it via WebDAV. The JSON file will link to the WebDAV-uploaded image
it via WebDAV. The JSON file will link to the WebDAV-uploaded image.
## Run the Seance Server
The server is mainly needed when Seance'ing draft Medium posts, because
Medium doesn't let you access those anonymously so you need to resort
to bookmarklets instead. But it also comes in handy as a nicer,
friendlier interface for sending out posts for those who don't want to
use the command-line. The engine is more or less the same; it's just
the interface that's different!
# credits

20
cli.js
View file

@ -136,6 +136,26 @@ program.command('setup')
config.ghost.admin_key = res.admin_key
console.log(`Right. So that's Ghost ${config.ghost.version} running at ${config.ghost.url} with key ${config.ghost.admin_key}`)
console.log('\n\nNow, a bit about the server.')
console.log(
'This server usually runs on the main path (eg. example.com/).' +
'If you want to host seance on a different subpath (eg. ' +
'example.com/seance) then please enter that last part of the' +
'string now (eg. /seance).'
)
res = await prompt.get([
{ name: 'base_path', default: config.basePath || '/' },
])
if (res.base_path == '/') {
console.log('Okay. Serving on the root path then :)')
} else {
config.basePath = res.base_path
console.log(
'Right, so we\'ll be serving at yoursite.com' +
`${config.basePath} then :)`
)
}
console.log(
'\n\nA final thing. Do you have a "scissors" or other image ' +
'used as a separator in your article? If so, enter the path ' +

View file

@ -95,6 +95,13 @@ let config = convict({
env: 'SEPARATOR_IMAGE',
default: null,
},
basePath: {
doc: 'Base subpath on which Seance is hosted (eg. /seance)',
format: 'String', // TODO: validate by checking path
env: 'BASE_PATH',
default: null,
nullable: true,
},
})
// Load configs from home directory, if present

View file

@ -5,6 +5,7 @@ const slugify = require('underscore.string/slugify')
const fs = require('fs')
const { Seance } = require ('./seance')
const config = require('./config')
const app = express()
var expressWs = require('express-ws')(app)
@ -18,8 +19,12 @@ app.use(bodyParser.urlencoded({
app.use(bodyParser('json'))
// Enable static files
app.use(express.static('public'))
app.use(express.static('static'))
app.use('/', express.static('public')) // basePath is prefixed later
app.use(config.basePath || '/', express.static('static'))
// Router
var router = express.Router()
app.use(config.basePath || '/', router)
// Set up VueXpress
let options = {
@ -29,12 +34,12 @@ let options = {
metaInfo: {
title: 'Seance',
script: [
{ type: 'text/javascript', src: '/app.js' },
{ type: 'text/javascript', src: config.basePath + '/app.js' },
],
},
extractCSS: true,
cssOutputPath: '/css/styles.css',
publicPath: 'public',
publicPath: 'public' + config.basePath,
compilerConfig: {
// custom webpack config
},
@ -46,19 +51,22 @@ let options = {
}
const renderer = vueRenderer(options)
app.use(renderer)
router.use(renderer)
// Views
app.get('/', (req, res) => {
router.get('/', (req, res) => {
res.render('index', {
baseUrl: req.hostname.startsWith('localhost')
? req.protocol + '://' + req.headers.host
: '//' + req.hostname, // auto choose http or https
seanceUrl: req.hostname.startsWith('localhost')
? req.headers.host + req.baseUrl
: req.hostname + req.baseUrl,
protocol: req.hostname.startsWith('localhost')
? req.protocol + '://'
: '//',
})
})
app.post('/fetch', (req, res) => {
router.post('/fetch', (req, res) => {
var json
var post
@ -110,6 +118,9 @@ app.post('/fetch', (req, res) => {
// render the final post
res.render('fetch-medium', {
seanceUrl: req.hostname.startsWith('localhost')
? req.headers.host + req.baseUrl
: req.hostname + req.baseUrl,
post: {
title: post.title,
subtitle: post.content.subtitle,
@ -125,14 +136,14 @@ app.get('/fetch', (req, res) => {
res.redirect(303, '/')
})
app.get('/api', (req, res) => {
router.get('/api', (req, res) => {
res.json({
status: 'success',
message: 'Welcome to the Seance API :)',
})
})
app.ws('/ws/fetch-medium', (ws, req) => {
router.ws('/ws/fetch-medium', (ws, req) => {
ws.on('message', async(msg) => {
command = msg.split(' ')
@ -182,7 +193,7 @@ app.ws('/ws/fetch-medium', (ws, req) => {
})
})
app.ws('/ws/push-ghost', (ws, req) => {
router.ws('/ws/push-ghost', (ws, req) => {
ws.on('message', async(msg) => {
// respond to keepalive

View file

@ -11,12 +11,13 @@ function addNotification(msg, className='is-warning') {
function mediumFetch(e) {
if(e) { e.preventDefault() }
const baseUrl = document.querySelector('[data-seance-url]').dataset.seanceUrl
const postSlug = document.querySelector('[data-post-slug]').dataset.postSlug
const mediumUrl = document.querySelector('[data-post-mediumurl]').dataset.postMediumurl
var password = document.querySelector('input[name=password]').value
console.log('Fetching ' + postSlug)
const socketProtocol = (window.location.protocol === 'https:' ? 'wss:' : 'ws:')
const socketUrl = socketProtocol + '//' + window.location.host + '/ws/fetch-medium/'
const socketUrl = socketProtocol + '//' + baseUrl + '/ws/fetch-medium/'
const socket = new WebSocket(socketUrl)
// set up the div

View file

@ -2,7 +2,7 @@
<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 class="navbar-item is-size-5" :href="seanceUrl + '/'">Seance</a>
<a role="button" class="navbar navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarMain">
<span aria-hidden="true"></span>
@ -44,7 +44,7 @@
</div>
<footer class="card-footer" id="statusbar">
<a :href="post.mediumUrl" v-if="post.mediumUrl" class="card-footer-item">View on Medium</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" onclick="mediumFetch(event)" :data-post-slug="post.slug" :data-post-mediumurl="post.mediumUrl" :data-seance-url="seanceUrl">Fetch</a>
<a href="#" class="card-footer-item" onclick="ghostPush(event)">Push to Ghost</a>
</footer>
</div>
@ -62,6 +62,7 @@
title: 'Seance',
user: null,
host: 'http://localhost:4000',
seanceUrl: 'http://localhost:4000',
post: {
title: '(untitled)',
subtitle: '',

View file

@ -2,7 +2,7 @@
<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 class="navbar-item is-size-5" :href="'//' + seanceUrl + '/'">Seance</a>
<a role="button" class="navbar navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarMain">
<span aria-hidden="true"></span>
@ -27,7 +27,7 @@
<div class="column is-half is-offset-one-quarter has-text-centered" style="margin-top: 10%;">
<h1 class="title">{{ title }}</h1>
<form action="/fetch/medium/" method="post">
<form :action="'/' + seanceUrl + '/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="bookmarklet">Add to Seance</a></p>
</form>
@ -43,12 +43,13 @@
return {
title: 'Seance',
user: null,
baseUrl: 'http://localhost:4000',
seanceUrl: 'localhost:4000',
protocol: '//',
}
},
computed: {
bookmarklet () {
return `javascript:(()=>{document.title='[Loading...] '+document.title;s=document.createElement('script');s.src='${this.baseUrl}/bookmarklet.js';s.dataset.seanceUrl='${this.baseUrl}';document.body.appendChild(s)})()`
return `javascript:(()=>{document.title='[Loading...] '+document.title;s=document.createElement('script');s.src='${this.protocol}${this.seanceUrl}/bookmarklet.js';s.dataset.seanceUrl='${this.protocol}${this.seanceUrl}';document.body.appendChild(s)})()`
}
}
}