diff --git a/client/scss/components/markdown-content.scss b/client/scss/components/markdown-content.scss index 62429477..1e3789ab 100644 --- a/client/scss/components/markdown-content.scss +++ b/client/scss/components/markdown-content.scss @@ -1,7 +1,20 @@ +.has-mkcontent { + width:100%; + overflow:hidden; + + .columns, .column { + width: 100%; + overflow: hidden; + } + +} + .mkcontent { font-size: 14px; color: mc('grey', '700'); padding: 0 0 20px 0; + width: 100%; + overflow: hidden; h1, h2, h3 { font-weight: 400; @@ -243,6 +256,7 @@ padding: 20px 20px 20px 13px; font-family: $core-font-monospace; white-space: pre; + overflow-x: scroll; > code { border-radius: 5px; diff --git a/controllers/auth.js b/controllers/auth.js index f5e157a0..cdb54d1b 100644 --- a/controllers/auth.js +++ b/controllers/auth.js @@ -66,7 +66,7 @@ router.post('/login', bruteforce.prevent, function (req, res, next) { req.brute.reset(function () { return res.redirect('/') }) - }) + }) || true }).catch(err => { // LOGIN FAIL if (err.message === 'INVALID_LOGIN') { diff --git a/fuse.js b/fuse.js index 343ae99f..108f9793 100644 --- a/fuse.js +++ b/fuse.js @@ -70,15 +70,24 @@ console.info(colors.white('└── ') + colors.green('Running global tasks...' let globalTasks = Promise.mapSeries([ () => { - console.info(colors.white(' └── ') + colors.green('Copy + Minify ACE modes to assets...')) - return fs.ensureDirAsync('./assets/js/ace').then(() => { - return fs.readdirAsync('./node_modules/brace/mode').then(modeList => { - return Promise.map(modeList, mdFile => { - console.info(colors.white(' mode-' + mdFile)) - let result = uglify.minify(path.join('./node_modules/brace/mode', mdFile), { output: { 'max_line_len': 1000000 } }) - return fs.writeFileAsync(path.join('./assets/js/ace', 'mode-' + mdFile), result.code) + fs.accessAsync('./assets/js/ace').then(() => { + console.info(colors.white(' └── ') + colors.magenta('ACE modes directory already exists. Task aborted.')) + return true + }).catch(err => { + if (err.code === 'ENOENT') { + console.info(colors.white(' └── ') + colors.green('Copy + Minify ACE modes to assets...')) + return fs.ensureDirAsync('./assets/js/ace').then(() => { + return fs.readdirAsync('./node_modules/brace/mode').then(modeList => { + return Promise.map(modeList, mdFile => { + console.info(colors.white(' mode-' + mdFile)) + let result = uglify.minify(path.join('./node_modules/brace/mode', mdFile), { output: { 'max_line_len': 1000000 } }) + return fs.writeFileAsync(path.join('./assets/js/ace', 'mode-' + mdFile), result.code) + }) + }) }) - }) + } else { + throw err + } }) } ], f => { return f() }) diff --git a/npm/install.js b/npm/install.js index a0653845..42731fd8 100644 --- a/npm/install.js +++ b/npm/install.js @@ -1,145 +1,151 @@ 'use strict' +// ===================================================== +// Wiki.js +// Installation Script +// ===================================================== + const Promise = require('bluebird') +const _ = require('lodash') +const colors = require('colors/safe') const exec = require('execa') const fs = Promise.promisifyAll(require('fs-extra')) const https = require('follow-redirects').https +const inquirer = require('inquirer') +const os = require('os') const path = require('path') const pm2 = Promise.promisifyAll(require('pm2')) const tar = require('tar') const zlib = require('zlib') -const inquirer = require('inquirer') -const colors = require('colors/safe') -const _ = require('lodash') -const os = require('os') -let installDir = path.resolve(__dirname, '../..') +const installDir = path.resolve(__dirname, '../..') -console.info(colors.yellow( - ' __ __ _ _ _ _ \n' + - '/ / /\\ \\ (_) | _(_) (_)___ \n' + - '\\ \\/ \\/ / | |/ / | | / __| \n' + - ' \\ /\\ /| | <| |_ | \\__ \\ \n' + - ' \\/ \\/ |_|_|\\_\\_(_)/ |___/ \n' + - ' |__/\n')) +// ===================================================== +// INSTALLATION TASKS +// ===================================================== -var ora = require('ora')({ text: 'Initializing...', spinner: 'dots12' }).start() - -ora.text = 'Looking for running instances...' -pm2.connectAsync().then(() => { +const tasks = { /** * Stop and delete existing instances */ - return pm2.describeAsync('wiki').then(() => { - ora.text = 'Stopping and deleting process from pm2...' - return pm2.deleteAsync('wiki') - }).catch(err => { // eslint-disable-line handle-callback-err - return true - }) -}).then(() => { + stopAndDeleteInstances () { + ora.text = 'Looking for running instances...' + return pm2.connectAsync().then(() => { + return pm2.describeAsync('wiki').then(() => { + ora.text = 'Stopping and deleting process from pm2...' + return pm2.deleteAsync('wiki') + }).catch(err => { // eslint-disable-line handle-callback-err + return true + }).finally(() => { + pm2.disconnect() + }) + }) + }, /** * Check for sufficient memory */ - if (os.totalmem() < 1024 * 1024 * 768) { - throw new Error('Not enough memory to install dependencies. Minimum is 768 MB.') - } - return true -}).then(() => { + checkRequirements () { + ora.text = 'Checking system requirements...' + if (os.totalmem() < 1024 * 1024 * 768) { + return Promise.reject(new Error('Not enough memory to install dependencies. Minimum is 768 MB.')) + } else { + return Promise.resolve(true) + } + }, /** * Install via local tarball if present */ - let skipHttp = true - let tbPath = path.join(installDir, 'wiki-js.tar.gz') - return fs.accessAsync(tbPath) - .catch(err => { // eslint-disable-line handle-callback-err - skipHttp = false - }).then(() => { - if (skipHttp) { - ora.text = 'Local tarball found. Extracting...' + installFromLocal () { + let hasTarball = true + let tbPath = path.join(installDir, 'wiki-js.tar.gz') + return fs.accessAsync(tbPath) + .catch(err => { // eslint-disable-line handle-callback-err + hasTarball = false + }).then(() => { + if (hasTarball) { + ora.text = 'Local tarball found. Extracting...' + + return new Promise((resolve, reject) => { + fs.createReadStream(tbPath).pipe(zlib.createGunzip()) + .pipe(tar.Extract({ path: installDir })) + .on('error', err => reject(err)) + .on('end', () => { + ora.text = 'Tarball extracted successfully.' + resolve(true) + }) + }) + } else { + return false + } + }) + }, + /** + * Install from GitHub release download + */ + installFromRemote () { + // Fetch version from npm package + return fs.readJsonAsync('package.json').then((packageObj) => { + let versionGet = _.chain(packageObj.version).split('.').take(4).join('.') + let remoteURL = _.replace('https://github.com/Requarks/wiki/releases/download/v{0}/wiki-js.tar.gz', '{0}', versionGet) return new Promise((resolve, reject) => { - fs.createReadStream(tbPath).pipe(zlib.createGunzip()) + // Fetch tarball + ora.text = 'Looking for latest release...' + https.get(remoteURL, resp => { + if (resp.statusCode !== 200) { + return reject(new Error('Remote file not found')) + } + ora.text = 'Remote wiki.js tarball found. Downloading...' + + // Extract tarball + resp.pipe(zlib.createGunzip()) .pipe(tar.Extract({ path: installDir })) .on('error', err => reject(err)) .on('end', () => { ora.text = 'Tarball extracted successfully.' resolve(true) }) + }) }) - } else { - return false - } - }) -}).then((skipHttp) => { + }) + }, /** - * Install via remote tarball + * Install npm dependencies */ - - if (skipHttp) { return true } - + installDependencies () { + ora.text = 'Installing Wiki.js npm dependencies...' + return exec.stdout('npm', ['install', '--only=production', '--no-optional'], { + cwd: installDir + }).then(results => { + ora.text = 'Wiki.js npm dependencies installed successfully.' + return true + }) + }, /** - * Fetch version from npm package + * Create default config.yml file if new installation */ - return fs.readJsonAsync('package.json').then((packageObj) => { - let versionGet = _.chain(packageObj.version).split('.').take(4).join('.') - let remoteURL = _.replace('https://github.com/Requarks/wiki/releases/download/v{0}/wiki-js.tar.gz', '{0}', versionGet) - - return new Promise((resolve, reject) => { - /** - * Fetch tarball - */ - ora.text = 'Looking for latest release...' - https.get(remoteURL, resp => { - if (resp.statusCode !== 200) { - return reject(new Error('Remote file not found')) - } - ora.text = 'Install tarball found. Downloading...' - - /** - * Extract tarball - */ - resp.pipe(zlib.createGunzip()) - .pipe(tar.Extract({ path: installDir })) - .on('error', err => reject(err)) - .on('end', () => { - ora.text = 'Tarball extracted successfully.' - resolve(true) + ensureConfigFile () { + return fs.accessAsync(path.join(installDir, 'config.yml')).then(() => { + // Is Upgrade + ora.succeed('Upgrade completed.') + console.info(colors.yellow('\n!!! IMPORTANT !!!')) + console.info(colors.yellow('Running the configuration wizard is optional but recommended after an upgrade to ensure your config file is using the latest available settings.')) + console.info(colors.yellow('Note that the contents of your config file will be displayed during the configuration wizard. It is therefor highly recommended to run the wizard on a non-publicly accessible port or skip this step completely.\n')) + return true + }).catch(err => { + // Is New Install + if (err.code === 'ENOENT') { + ora.text = 'First-time install, creating a new config.yml...' + return fs.copyAsync(path.join(installDir, 'config.sample.yml'), path.join(installDir, 'config.yml')).then(() => { + ora.succeed('Installation succeeded.') + return true }) - }) + } else { + return err + } }) - }) -}).then(() => { - ora.text = 'Installing Wiki.js npm dependencies...' - return exec.stdout('npm', ['install', '--only=production', '--no-optional'], { - cwd: installDir - }).then(results => { - ora.text = 'Wiki.js npm dependencies installed successfully.' - return true - }) -}).then(() => { - fs.accessAsync(path.join(installDir, 'config.yml')).then(() => { - /** - * Upgrade mode - */ - ora.succeed('Upgrade completed.') - console.info(colors.yellow('\n!!! IMPORTANT !!!')) - console.info(colors.yellow('Running the configuration wizard is optional but recommended after an upgrade to ensure your config file is using the latest available settings.')) - console.info(colors.yellow('Note that the contents of your config file will be displayed during the configuration wizard. It is therefor highly recommended to run the wizard on a non-publicly accessible port or skip this step completely.\n')) - return false - }).catch(err => { - /** - * Install mode - */ - if (err.code === 'ENOENT') { - ora.text = 'First-time install, creating a new config.yml...' - return fs.copyAsync(path.join(installDir, 'config.sample.yml'), path.join(installDir, 'config.yml')).then(() => { - ora.succeed('Installation succeeded.') - return true - }) - } else { - return err - } - }).then((isNewInstall) => { + }, + startConfigurationWizard () { if (process.stdout.isTTY) { inquirer.prompt([{ type: 'list', @@ -192,9 +198,36 @@ pm2.connectAsync().then(() => { } else { console.info(colors.cyan('[WARNING] Non-interactive terminal detected. You must manually start the configuration wizard using the command: node wiki configure')) } + } +} + +// ===================================================== +// INSTALL SEQUENCE +// ===================================================== + +console.info(colors.yellow( + ' __ __ _ _ _ _ \n' + + '/ / /\\ \\ (_) | _(_) (_)___ \n' + + '\\ \\/ \\/ / | |/ / | | / __| \n' + + ' \\ /\\ /| | <| |_ | \\__ \\ \n' + + ' \\/ \\/ |_|_|\\_\\_(_)/ |___/ \n' + + ' |__/\n')) + +let ora = require('ora')({ text: 'Initializing...', spinner: 'dots12' }).start() + +Promise.join( + tasks.stopAndDeleteInstances(), + tasks.checkRequirements() +).then(() => { + return tasks.installFromLocal().then(succeeded => { + return (!succeeded) ? tasks.installFromRemote() : true }) +}).then(() => { + return tasks.installDependencies() +}).then(() => { + return tasks.ensureConfigFile() +}).then(() => { + return tasks.startConfigurationWizard() }).catch(err => { ora.fail(err) -}).finally(() => { - pm2.disconnect() }) diff --git a/views/pages/view.pug b/views/pages/view.pug index e9670c37..f5fdd342 100644 --- a/views/pages/view.pug +++ b/views/pages/view.pug @@ -29,7 +29,7 @@ block rootNavRight block content #page-type-view(data-entrypath=pageData.meta.path) - .container.is-fluid + .container.is-fluid.has-mkcontent .columns.is-gapless .column.is-narrow.is-hidden-touch.sidebar