mirror of
https://github.com/status-im/codimd.git
synced 2025-02-28 19:50:35 +00:00
commit
2e468db210
@ -71,3 +71,5 @@ If you set your `user.name` and `user.email` git configs, you can sign your
|
|||||||
commit automatically with `git commit -s`. You can also use git [aliases](https://git-scm.com/book/tr/v2/Git-Basics-Git-Aliases)
|
commit automatically with `git commit -s`. You can also use git [aliases](https://git-scm.com/book/tr/v2/Git-Basics-Git-Aliases)
|
||||||
like `git config --global alias.ci 'commit -s'`. Now you can commit with
|
like `git config --global alias.ci 'commit -s'`. Now you can commit with
|
||||||
`git ci` and the commit will be signed.
|
`git ci` and the commit will be signed.
|
||||||
|
|
||||||
|
[dcofile]: https://github.com/hackmdio/codimd/blob/develop/contribute/developer-certificate-of-origin
|
||||||
|
@ -4,6 +4,7 @@ CodiMD
|
|||||||
[![build status][travis-image]][travis-url]
|
[![build status][travis-image]][travis-url]
|
||||||
[![version][github-version-badge]][github-release-page]
|
[![version][github-version-badge]][github-release-page]
|
||||||
[![Gitter][gitter-image]][gitter-url]
|
[![Gitter][gitter-image]][gitter-url]
|
||||||
|
[![Matrix][matrix-image]][matrix-url]
|
||||||
[![POEditor][poeditor-image]][poeditor-url]
|
[![POEditor][poeditor-image]][poeditor-url]
|
||||||
|
|
||||||
CodiMD lets you collaborate in real-time with markdown.
|
CodiMD lets you collaborate in real-time with markdown.
|
||||||
@ -99,3 +100,5 @@ To stay up to date with your installation it's recommended to subscribe the [rel
|
|||||||
[github-release-feed]: https://github.com/hackmdio/codimd/releases.atom
|
[github-release-feed]: https://github.com/hackmdio/codimd/releases.atom
|
||||||
[poeditor-image]: https://img.shields.io/badge/POEditor-translate-blue.svg
|
[poeditor-image]: https://img.shields.io/badge/POEditor-translate-blue.svg
|
||||||
[poeditor-url]: https://poeditor.com/join/project/q0nuPWyztp
|
[poeditor-url]: https://poeditor.com/join/project/q0nuPWyztp
|
||||||
|
[matrix-image]: https://img.shields.io/matrix/hackmdio_hackmd:gitter.im?color=blue&logo=matrix
|
||||||
|
[matrix-url]: https://matrix.to/#/#hackmdio_hackmd:gitter.im
|
||||||
|
1
app.js
1
app.js
@ -194,6 +194,7 @@ app.set('view engine', 'ejs')
|
|||||||
app.locals.useCDN = config.useCDN
|
app.locals.useCDN = config.useCDN
|
||||||
app.locals.serverURL = config.serverURL
|
app.locals.serverURL = config.serverURL
|
||||||
app.locals.sourceURL = config.sourceURL
|
app.locals.sourceURL = config.sourceURL
|
||||||
|
app.locals.privacyPolicyURL = config.privacyPolicyURL
|
||||||
app.locals.allowAnonymous = config.allowAnonymous
|
app.locals.allowAnonymous = config.allowAnonymous
|
||||||
app.locals.allowAnonymousEdits = config.allowAnonymousEdits
|
app.locals.allowAnonymousEdits = config.allowAnonymousEdits
|
||||||
app.locals.permission = config.permission
|
app.locals.permission = config.permission
|
||||||
|
4
app.json
4
app.json
@ -143,6 +143,10 @@
|
|||||||
"CMD_ALLOW_PDF_EXPORT": {
|
"CMD_ALLOW_PDF_EXPORT": {
|
||||||
"description": "Enable or disable PDF exports",
|
"description": "Enable or disable PDF exports",
|
||||||
"required": false
|
"required": false
|
||||||
|
},
|
||||||
|
"PGSSLMODE": {
|
||||||
|
"description": "Enforce PG SSL mode",
|
||||||
|
"value": "require"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"addons": [
|
"addons": [
|
||||||
|
@ -5,10 +5,23 @@ const config = require('../config')
|
|||||||
const logger = require('../logger')
|
const logger = require('../logger')
|
||||||
|
|
||||||
exports.setReturnToFromReferer = function setReturnToFromReferer (req) {
|
exports.setReturnToFromReferer = function setReturnToFromReferer (req) {
|
||||||
var referer = req.get('referer')
|
|
||||||
if (!req.session) req.session = {}
|
if (!req.session) req.session = {}
|
||||||
|
|
||||||
|
var referer = req.get('referer')
|
||||||
|
var refererSearchParams = new URLSearchParams(new URL(referer).search)
|
||||||
|
var nextURL = refererSearchParams.get('next')
|
||||||
|
|
||||||
|
if (nextURL) {
|
||||||
|
var isRelativeNextURL = nextURL.indexOf('://') === -1 && !nextURL.startsWith('//')
|
||||||
|
if (isRelativeNextURL) {
|
||||||
|
req.session.returnTo = (new URL(nextURL, config.serverURL)).toString()
|
||||||
|
} else {
|
||||||
|
req.session.returnTo = config.serverURL
|
||||||
|
}
|
||||||
|
} else {
|
||||||
req.session.returnTo = referer
|
req.session.returnTo = referer
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
exports.passportGeneralCallback = function callback (accessToken, refreshToken, profile, done) {
|
exports.passportGeneralCallback = function callback (accessToken, refreshToken, profile, done) {
|
||||||
var stringifiedProfile = JSON.stringify(profile)
|
var stringifiedProfile = JSON.stringify(profile)
|
||||||
|
@ -37,6 +37,7 @@ module.exports = {
|
|||||||
defaultPermission: 'editable',
|
defaultPermission: 'editable',
|
||||||
dbURL: '',
|
dbURL: '',
|
||||||
db: {},
|
db: {},
|
||||||
|
privacyPolicyURL: '',
|
||||||
// ssl path
|
// ssl path
|
||||||
sslKeyPath: '',
|
sslKeyPath: '',
|
||||||
sslCertPath: '',
|
sslCertPath: '',
|
||||||
@ -188,5 +189,6 @@ module.exports = {
|
|||||||
// 2nd appearance: "31-good-morning-my-friend---do-you-have-5-1"
|
// 2nd appearance: "31-good-morning-my-friend---do-you-have-5-1"
|
||||||
// 3rd appearance: "31-good-morning-my-friend---do-you-have-5-2"
|
// 3rd appearance: "31-good-morning-my-friend---do-you-have-5-2"
|
||||||
linkifyHeaderStyle: 'keep-case',
|
linkifyHeaderStyle: 'keep-case',
|
||||||
autoVersionCheck: true
|
autoVersionCheck: true,
|
||||||
|
defaultTocDepth: 3
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ module.exports = {
|
|||||||
sessionSecret: process.env.CMD_SESSION_SECRET,
|
sessionSecret: process.env.CMD_SESSION_SECRET,
|
||||||
sessionLife: toIntegerConfig(process.env.CMD_SESSION_LIFE),
|
sessionLife: toIntegerConfig(process.env.CMD_SESSION_LIFE),
|
||||||
responseMaxLag: toIntegerConfig(process.env.CMD_RESPONSE_MAX_LAG),
|
responseMaxLag: toIntegerConfig(process.env.CMD_RESPONSE_MAX_LAG),
|
||||||
|
privacyPolicyURL: process.env.CMD_PRIVACY_POLICY_URL,
|
||||||
imageUploadType: process.env.CMD_IMAGE_UPLOAD_TYPE,
|
imageUploadType: process.env.CMD_IMAGE_UPLOAD_TYPE,
|
||||||
imgur: {
|
imgur: {
|
||||||
clientID: process.env.CMD_IMGUR_CLIENTID
|
clientID: process.env.CMD_IMGUR_CLIENTID
|
||||||
@ -147,5 +148,6 @@ module.exports = {
|
|||||||
openID: toBooleanConfig(process.env.CMD_OPENID),
|
openID: toBooleanConfig(process.env.CMD_OPENID),
|
||||||
defaultUseHardbreak: toBooleanConfig(process.env.CMD_DEFAULT_USE_HARD_BREAK),
|
defaultUseHardbreak: toBooleanConfig(process.env.CMD_DEFAULT_USE_HARD_BREAK),
|
||||||
linkifyHeaderStyle: process.env.CMD_LINKIFY_HEADER_STYLE,
|
linkifyHeaderStyle: process.env.CMD_LINKIFY_HEADER_STYLE,
|
||||||
autoVersionCheck: toBooleanConfig(process.env.CMD_AUTO_VERSION_CHECK)
|
autoVersionCheck: toBooleanConfig(process.env.CMD_AUTO_VERSION_CHECK),
|
||||||
|
defaultTocDepth: toIntegerConfig(process.env.CMD_DEFAULT_TOC_DEPTH)
|
||||||
}
|
}
|
||||||
|
@ -92,20 +92,22 @@ module.exports = function (sequelize, DataTypes) {
|
|||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
// if no content specified then use default note
|
// if no content specified then use default note
|
||||||
if (!note.content) {
|
if (!note.content) {
|
||||||
var body = null
|
let filePath = config.defaultNotePath
|
||||||
let filePath = null
|
|
||||||
if (!note.alias) {
|
if (note.alias) {
|
||||||
filePath = config.defaultNotePath
|
const notePathInDocPath = path.join(config.docsPath, path.basename(note.alias) + '.md')
|
||||||
} else {
|
if (Note.checkFileExist(notePathInDocPath)) {
|
||||||
filePath = path.join(config.docsPath, note.alias + '.md')
|
filePath = notePathInDocPath
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Note.checkFileExist(filePath)) {
|
if (Note.checkFileExist(filePath)) {
|
||||||
var fsCreatedTime = moment(fs.statSync(filePath).ctime)
|
const noteInFS = readFileSystemNote(filePath)
|
||||||
body = fs.readFileSync(filePath, 'utf8')
|
note.title = noteInFS.title
|
||||||
note.title = Note.parseNoteTitle(body)
|
note.content = noteInFS.content
|
||||||
note.content = body
|
|
||||||
if (filePath !== config.defaultNotePath) {
|
if (filePath !== config.defaultNotePath) {
|
||||||
note.createdAt = fsCreatedTime
|
note.createdAt = noteInFS.lastchangeAt
|
||||||
|
note.lastchangeAt = noteInFS.lastchangeAt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -196,6 +198,29 @@ module.exports = function (sequelize, DataTypes) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function syncNote (noteInFS, note) {
|
||||||
|
const contentLength = noteInFS.content.length
|
||||||
|
|
||||||
|
let note2 = await note.update({
|
||||||
|
title: noteInFS.title,
|
||||||
|
content: noteInFS.content,
|
||||||
|
lastchangeAt: noteInFS.lastchangeAt
|
||||||
|
})
|
||||||
|
const revision = await sequelize.models.Revision.saveNoteRevisionAsync(note2)
|
||||||
|
// update authorship on after making revision of docs
|
||||||
|
const patch = dmp.patch_fromText(revision.patch)
|
||||||
|
const operations = Note.transformPatchToOperations(patch, contentLength)
|
||||||
|
let authorship = note2.authorship
|
||||||
|
for (let i = 0; i < operations.length; i++) {
|
||||||
|
authorship = Note.updateAuthorshipByOperation(operations[i], null, authorship)
|
||||||
|
}
|
||||||
|
note2 = await note.update({
|
||||||
|
authorship: authorship
|
||||||
|
})
|
||||||
|
return note2.id
|
||||||
|
}
|
||||||
|
|
||||||
Note.parseNoteId = function (noteId, callback) {
|
Note.parseNoteId = function (noteId, callback) {
|
||||||
async.series({
|
async.series({
|
||||||
parseNoteIdByAlias: function (_callback) {
|
parseNoteIdByAlias: function (_callback) {
|
||||||
@ -204,65 +229,35 @@ module.exports = function (sequelize, DataTypes) {
|
|||||||
where: {
|
where: {
|
||||||
alias: noteId
|
alias: noteId
|
||||||
}
|
}
|
||||||
}).then(function (note) {
|
}).then(async function (note) {
|
||||||
if (note) {
|
const filePath = path.join(config.docsPath, path.basename(noteId) + '.md')
|
||||||
const filePath = path.join(config.docsPath, noteId + '.md')
|
|
||||||
if (Note.checkFileExist(filePath)) {
|
if (Note.checkFileExist(filePath)) {
|
||||||
|
try {
|
||||||
|
if (note) {
|
||||||
// if doc in filesystem have newer modified time than last change time
|
// if doc in filesystem have newer modified time than last change time
|
||||||
// then will update the doc in db
|
// then will update the doc in db
|
||||||
var fsModifiedTime = moment(fs.statSync(filePath).mtime)
|
const noteInFS = readFileSystemNote(filePath)
|
||||||
var dbModifiedTime = moment(note.lastchangeAt || note.createdAt)
|
if (shouldSyncNote(note, noteInFS)) {
|
||||||
var body = fs.readFileSync(filePath, 'utf8')
|
const noteId = await syncNote(noteInFS, note)
|
||||||
var contentLength = body.length
|
return callback(null, noteId)
|
||||||
var title = Note.parseNoteTitle(body)
|
|
||||||
if (fsModifiedTime.isAfter(dbModifiedTime) && note.content !== body) {
|
|
||||||
note.update({
|
|
||||||
title: title,
|
|
||||||
content: body,
|
|
||||||
lastchangeAt: fsModifiedTime
|
|
||||||
}).then(function (note) {
|
|
||||||
sequelize.models.Revision.saveNoteRevision(note, function (err, revision) {
|
|
||||||
if (err) return _callback(err, null)
|
|
||||||
// update authorship on after making revision of docs
|
|
||||||
var patch = dmp.patch_fromText(revision.patch)
|
|
||||||
var operations = Note.transformPatchToOperations(patch, contentLength)
|
|
||||||
var authorship = note.authorship
|
|
||||||
for (let i = 0; i < operations.length; i++) {
|
|
||||||
authorship = Note.updateAuthorshipByOperation(operations[i], null, authorship)
|
|
||||||
}
|
|
||||||
note.update({
|
|
||||||
authorship: authorship
|
|
||||||
}).then(function (note) {
|
|
||||||
return callback(null, note.id)
|
|
||||||
}).catch(function (err) {
|
|
||||||
return _callback(err, null)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}).catch(function (err) {
|
|
||||||
return _callback(err, null)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return callback(null, note.id)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return callback(null, note.id)
|
// create new note with alias, and will sync md file in beforeCreateHook
|
||||||
}
|
const note = await Note.create({
|
||||||
} else {
|
|
||||||
var filePath = path.join(config.docsPath, noteId + '.md')
|
|
||||||
if (Note.checkFileExist(filePath)) {
|
|
||||||
Note.create({
|
|
||||||
alias: noteId,
|
alias: noteId,
|
||||||
owner: null,
|
owner: null,
|
||||||
permission: 'locked'
|
permission: 'locked'
|
||||||
}).then(function (note) {
|
|
||||||
return callback(null, note.id)
|
|
||||||
}).catch(function (err) {
|
|
||||||
return _callback(err, null)
|
|
||||||
})
|
})
|
||||||
} else {
|
return callback(null, note.id)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return _callback(err, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!note) {
|
||||||
return _callback(null, null)
|
return _callback(null, null)
|
||||||
}
|
}
|
||||||
}
|
return callback(null, note.id)
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
return _callback(err, null)
|
return _callback(err, null)
|
||||||
})
|
})
|
||||||
@ -589,5 +584,21 @@ module.exports = function (sequelize, DataTypes) {
|
|||||||
return operations
|
return operations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readFileSystemNote (filePath) {
|
||||||
|
const fsModifiedTime = moment(fs.statSync(filePath).mtime)
|
||||||
|
const content = fs.readFileSync(filePath, 'utf8')
|
||||||
|
|
||||||
|
return {
|
||||||
|
lastchangeAt: fsModifiedTime,
|
||||||
|
title: Note.parseNoteTitle(content),
|
||||||
|
content: content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldSyncNote (note, noteInFS) {
|
||||||
|
const dbModifiedTime = moment(note.lastchangeAt || note.createdAt)
|
||||||
|
return noteInFS.lastchangeAt.isAfter(dbModifiedTime) && note.content !== noteInFS.content
|
||||||
|
}
|
||||||
|
|
||||||
return Note
|
return Note
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ var moment = require('moment')
|
|||||||
var childProcess = require('child_process')
|
var childProcess = require('child_process')
|
||||||
var shortId = require('shortid')
|
var shortId = require('shortid')
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
|
var util = require('util')
|
||||||
|
|
||||||
var Op = Sequelize.Op
|
var Op = Sequelize.Op
|
||||||
|
|
||||||
@ -296,6 +297,7 @@ module.exports = function (sequelize, DataTypes) {
|
|||||||
return callback(err, null)
|
return callback(err, null)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Revision.saveNoteRevisionAsync = util.promisify(Revision.saveNoteRevision)
|
||||||
Revision.finishSaveNoteRevision = function (note, revision, callback) {
|
Revision.finishSaveNoteRevision = function (note, revision, callback) {
|
||||||
note.update({
|
note.update({
|
||||||
savedAt: revision.updatedAt
|
savedAt: revision.updatedAt
|
||||||
|
@ -268,7 +268,7 @@ const deleteNote = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateNote = async (req, res) => {
|
const updateNote = async (req, res) => {
|
||||||
if (req.isAuthenticated()) {
|
if (req.isAuthenticated() || config.allowAnonymousEdits) {
|
||||||
const noteId = await Note.parseNoteIdAsync(req.params.noteId)
|
const noteId = await Note.parseNoteIdAsync(req.params.noteId)
|
||||||
try {
|
try {
|
||||||
const note = await Note.findOne({
|
const note = await Note.findOne({
|
||||||
@ -294,7 +294,7 @@ const updateNote = async (req, res) => {
|
|||||||
lastchangeAt: now,
|
lastchangeAt: now,
|
||||||
authorship: [
|
authorship: [
|
||||||
[
|
[
|
||||||
req.user.id,
|
req.isAuthenticated() ? req.user.id : null,
|
||||||
0,
|
0,
|
||||||
content.length,
|
content.length,
|
||||||
now,
|
now,
|
||||||
@ -308,7 +308,9 @@ const updateNote = async (req, res) => {
|
|||||||
return errorInternalError(req, res)
|
return errorInternalError(req, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateHistory(req.user.id, note.id, content)
|
if (req.isAuthenticated()) {
|
||||||
|
updateHistory(req.user.id, noteId, content)
|
||||||
|
}
|
||||||
|
|
||||||
Revision.saveNoteRevision(note, (err, revision) => {
|
Revision.saveNoteRevision(note, (err, revision) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -321,7 +323,7 @@ const updateNote = async (req, res) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err)
|
logger.error(err.stack)
|
||||||
logger.error('Update note failed: Internal Error.')
|
logger.error('Update note failed: Internal Error.')
|
||||||
return errorInternalError(req, res)
|
return errorInternalError(req, res)
|
||||||
}
|
}
|
||||||
|
@ -32,8 +32,10 @@ function errorForbidden (req, res) {
|
|||||||
if (req.user) {
|
if (req.user) {
|
||||||
responseError(res, '403', 'Forbidden', 'oh no.')
|
responseError(res, '403', 'Forbidden', 'oh no.')
|
||||||
} else {
|
} else {
|
||||||
|
var nextURL = new URL('', config.serverURL)
|
||||||
|
nextURL.search = new URLSearchParams({ next: req.originalUrl })
|
||||||
req.flash('error', 'You are not allowed to access this page. Maybe try logging in?')
|
req.flash('error', 'You are not allowed to access this page. Maybe try logging in?')
|
||||||
res.redirect(config.serverURL + '/')
|
res.redirect(nextURL.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,8 @@ exports.getConfig = (req, res) => {
|
|||||||
allowedUploadMimeTypes: config.allowedUploadMimeTypes,
|
allowedUploadMimeTypes: config.allowedUploadMimeTypes,
|
||||||
defaultUseHardbreak: config.defaultUseHardbreak,
|
defaultUseHardbreak: config.defaultUseHardbreak,
|
||||||
linkifyHeaderStyle: config.linkifyHeaderStyle,
|
linkifyHeaderStyle: config.linkifyHeaderStyle,
|
||||||
useCDN: config.useCDN
|
useCDN: config.useCDN,
|
||||||
|
defaultTocDepth: config.defaultTocDepth
|
||||||
}
|
}
|
||||||
res.set({
|
res.set({
|
||||||
'Cache-Control': 'private', // only cache by client
|
'Cache-Control': 'private', // only cache by client
|
||||||
|
864
package-lock.json
generated
864
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -27,7 +27,7 @@
|
|||||||
"coverage:ci": "nyc mocha --no-color -R dot --require intelli-espower-loader --exit --recursive ./test",
|
"coverage:ci": "nyc mocha --no-color -R dot --require intelli-espower-loader --exit --recursive ./test",
|
||||||
"test": "npm run-script lint && npm run-script jsonlint && npm run-script coverage",
|
"test": "npm run-script lint && npm run-script jsonlint && npm run-script coverage",
|
||||||
"test:ci": "npm run-script lint && npm run-script jsonlint && npm run-script coverage:ci",
|
"test:ci": "npm run-script lint && npm run-script jsonlint && npm run-script coverage:ci",
|
||||||
"postinstall": "bin/heroku"
|
"heroku-postbuild": "npm run build && ./bin/heroku"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3-node": "0.1.0-preview.2",
|
"@aws-sdk/client-s3-node": "0.1.0-preview.2",
|
||||||
@ -105,7 +105,7 @@
|
|||||||
"ws": "~7.1.1"
|
"ws": "~7.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hackmd/codemirror": "~5.49.8",
|
"@hackmd/codemirror": "~5.57.7",
|
||||||
"@hackmd/emojify.js": "^2.1.0",
|
"@hackmd/emojify.js": "^2.1.0",
|
||||||
"@hackmd/idle-js": "~1.0.1",
|
"@hackmd/idle-js": "~1.0.1",
|
||||||
"@hackmd/js-sequence-diagrams": "~0.0.1-alpha.3",
|
"@hackmd/js-sequence-diagrams": "~0.0.1-alpha.3",
|
||||||
@ -163,7 +163,8 @@
|
|||||||
"markdown-it-ruby": "^0.1.1",
|
"markdown-it-ruby": "^0.1.1",
|
||||||
"markdown-it-sub": "~1.0.0",
|
"markdown-it-sub": "~1.0.0",
|
||||||
"markdown-it-sup": "~1.0.0",
|
"markdown-it-sup": "~1.0.0",
|
||||||
"markdownlint": "^0.17.0",
|
"markdownlint": "^0.22.0",
|
||||||
|
"markdownlint-rule-helpers": "^0.13.0",
|
||||||
"markmap-lib": "^0.4.2",
|
"markmap-lib": "^0.4.2",
|
||||||
"mathjax": "~2.7.5",
|
"mathjax": "~2.7.5",
|
||||||
"mermaid": "~8.6.4",
|
"mermaid": "~8.6.4",
|
||||||
@ -173,7 +174,7 @@
|
|||||||
"nyc": "~14.0.0",
|
"nyc": "~14.0.0",
|
||||||
"optimize-css-assets-webpack-plugin": "~5.0.0",
|
"optimize-css-assets-webpack-plugin": "~5.0.0",
|
||||||
"papaparse": "^5.2.0",
|
"papaparse": "^5.2.0",
|
||||||
"pdfobject": "~2.1.1",
|
"pdfobject": "~2.2.4",
|
||||||
"plantuml-encoder": "^1.2.5",
|
"plantuml-encoder": "^1.2.5",
|
||||||
"power-assert": "~1.6.1",
|
"power-assert": "~1.6.1",
|
||||||
"prismjs": "^1.17.1",
|
"prismjs": "^1.17.1",
|
||||||
|
@ -263,6 +263,32 @@
|
|||||||
padding-right: 40px;
|
padding-right: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ui-toc-dropdown .nav .nav>li>ul>li>ul>li>a {
|
||||||
|
padding-top: 1px;
|
||||||
|
padding-bottom: 1px;
|
||||||
|
padding-left: 50px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-toc-dropdown[dir='rtl'] .nav .nav>li>ul>li>ul>li>a {
|
||||||
|
padding-right: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-toc-dropdown .nav .nav>li>ul>li>ul>li>ul>li>a {
|
||||||
|
padding-top: 1px;
|
||||||
|
padding-bottom: 1px;
|
||||||
|
padding-left: 60px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-toc-dropdown[dir='rtl'] .nav .nav>li>ul>li>ul>li>ul>li>a {
|
||||||
|
padding-right: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.ui-toc-dropdown .nav .nav>li>a:focus,.ui-toc-dropdown .nav .nav>li>a:hover {
|
.ui-toc-dropdown .nav .nav>li>a:focus,.ui-toc-dropdown .nav .nav>li>a:hover {
|
||||||
padding-left: 29px;
|
padding-left: 29px;
|
||||||
}
|
}
|
||||||
@ -279,6 +305,22 @@
|
|||||||
padding-right: 39px;
|
padding-right: 39px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ui-toc-dropdown .nav .nav>li>ul>li>ul>li>a:focus,.ui-toc-dropdown .nav .nav>li>ul>li>ul>li>a:hover {
|
||||||
|
padding-left: 49px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-toc-dropdown[dir='rtl'] .nav .nav>li>ul>li>ul>li>a:focus,.ui-toc-dropdown[dir='rtl'] .nav .nav>li>ul>li>ul>li>a:hover {
|
||||||
|
padding-right: 49px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-toc-dropdown .nav .nav>li>ul>li>ul>li>ul>li>a:focus,.ui-toc-dropdown .nav .nav>li>ul>li>ul>li>ul>li>a:hover {
|
||||||
|
padding-left: 59px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-toc-dropdown[dir='rtl'] .nav .nav>li>ul>li>ul>li>ul>li>a:focus,.ui-toc-dropdown[dir='rtl'] .nav .nav>li>ul>li>ul>li>ul>li>a:hover {
|
||||||
|
padding-right: 59px;
|
||||||
|
}
|
||||||
|
|
||||||
.ui-toc-dropdown .nav .nav>.active:focus>a,.ui-toc-dropdown .nav .nav>.active:hover>a,.ui-toc-dropdown .nav .nav>.active>a {
|
.ui-toc-dropdown .nav .nav>.active:focus>a,.ui-toc-dropdown .nav .nav>.active:hover>a,.ui-toc-dropdown .nav .nav>.active>a {
|
||||||
padding-left: 28px;
|
padding-left: 28px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@ -297,6 +339,24 @@
|
|||||||
padding-right: 38px;
|
padding-right: 38px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ui-toc-dropdown .nav .nav>.active>.nav>.active>.nav>.active:focus>a,.ui-toc-dropdown .nav .nav>.active>.nav>.active>.nav>.active:hover>a,.ui-toc-dropdown .nav .nav>.active>.nav>.active>.nav>.active>a {
|
||||||
|
padding-left: 48px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-toc-dropdown[dir='rtl'] .nav .nav>.active>.nav>.active>.nav>.active:focus>a,.ui-toc-dropdown[dir='rtl'] .nav .nav>.active>.nav>.active>.nav>.active:hover>a,.ui-toc-dropdown[dir='rtl'] .nav .nav>.active>.active>.nav>.nav>.active>a {
|
||||||
|
padding-right: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-toc-dropdown .nav .nav>.active>.nav>.active>.nav>.active>.nav>.active:focus>a,.ui-toc-dropdown .nav .nav>.active>.nav>.active>.nav>.active>.nav>.active:hover>a,.ui-toc-dropdown .nav .nav>.active>.nav>.active>.nav>.active>.nav>.active>a {
|
||||||
|
padding-left: 58px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-toc-dropdown[dir='rtl'] .nav .nav>.active>.nav>.active>.nav>.active>.nav>.active:focus>a,.ui-toc-dropdown[dir='rtl'] .nav .nav>.active>.nav>.active>.nav>.active>.nav>.active:hover>a,.ui-toc-dropdown[dir='rtl'] .nav .nav>.active>.active>.nav>.nav>.active>.nav>.active>a {
|
||||||
|
padding-right: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
/* support japanese font */
|
/* support japanese font */
|
||||||
.markdown-body[lang^="ja"] {
|
.markdown-body[lang^="ja"] {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, Arial, "Hiragino Kaku Gothic Pro", "ヒラギノ角ゴ Pro W3", Osaka, Meiryo, "メイリオ", "MS Gothic", "MS ゴシック", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Helvetica, Arial, "Hiragino Kaku Gothic Pro", "ヒラギノ角ゴ Pro W3", Osaka, Meiryo, "メイリオ", "MS Gothic", "MS ゴシック", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
@ -82,7 +82,8 @@ View
|
|||||||
## Table of Contents:
|
## Table of Contents:
|
||||||
You can look at the bottom right section of the view area, there is a _ToC_ button <i class="fa fa-bars"></i>.
|
You can look at the bottom right section of the view area, there is a _ToC_ button <i class="fa fa-bars"></i>.
|
||||||
Pressing that button will show you a current _Table of Contents_, and will highlight which section you're at.
|
Pressing that button will show you a current _Table of Contents_, and will highlight which section you're at.
|
||||||
ToCs support up to **three header levels**.
|
ToCs support up to **five header levels**, the **default** is **set to three**. The maxLevel can be set for each note by using
|
||||||
|
[YAML Metadata](./yaml-metadata)
|
||||||
|
|
||||||
## Permalink
|
## Permalink
|
||||||
Every header will automatically add a permalink on the right side.
|
Every header will automatically add a permalink on the right side.
|
||||||
@ -133,12 +134,19 @@ You can provide advanced note information to set the browser behavior (visit abo
|
|||||||
- GA: set to use Google Analytics
|
- GA: set to use Google Analytics
|
||||||
- disqus: set to use Disqus
|
- disqus: set to use Disqus
|
||||||
- slideOptions: setup slide mode options
|
- slideOptions: setup slide mode options
|
||||||
|
- toc: set options of the Table of Contents.
|
||||||
|
|
||||||
## ToC:
|
## ToC:
|
||||||
Use the syntax `[TOC]` to embed table of content into your note.
|
Use the syntax `[TOC]` to embed table of content into your note. By default, three header levels are displayed. This can also be specified by using [YAML Metadata](./yaml-metadata).
|
||||||
|
|
||||||
[TOC]
|
[TOC]
|
||||||
|
|
||||||
|
You can also specify the number of header levels by specifying the `maxLevel` like this: `[TOC maxLevel=1]`
|
||||||
|
|
||||||
|
[TOC maxLevel=1]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Emoji
|
## Emoji
|
||||||
You can type any emoji like this :smile: :smiley: :cry: :wink:
|
You can type any emoji like this :smile: :smiley: :cry: :wink:
|
||||||
> See full emoji list [here](http://www.emoji-cheat-sheet.com/).
|
> See full emoji list [here](http://www.emoji-cheat-sheet.com/).
|
||||||
|
@ -1,6 +1,44 @@
|
|||||||
Release Notes
|
Release Notes
|
||||||
===
|
===
|
||||||
|
|
||||||
|
<i class="fa fa-tag"></i> 2.4.0 Papilio maraho <i class="fa fa-clock-o"></i> 2021-05-11
|
||||||
|
---
|
||||||
|
|
||||||
|
<div style="text-align: center; margin-bottom: 1em;">
|
||||||
|
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/32/Papilio_maraho_male_ventral_view_20160423.jpg/569px-Papilio_maraho_male_ventral_view_20160423.jpg" width="600">
|
||||||
|
<small style="display: block;">Papilio maraho</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
> Papilio maraho is a species of butterfly in the family Papilionidae. It is endemic to Taiwan.
|
||||||
|
> \- Wikipedia [Papilio maraho](https://en.wikipedia.org/wiki/Papilio_maraho)
|
||||||
|
|
||||||
|
[Check out the complete release note][v2_4_0]. Thank you CodiMD community and all our contributors. ❤️
|
||||||
|
|
||||||
|
[v2_4_0]: https://hackmd.io/@codimd/release-notes/%2F%40codimd%2Fv2_4_0
|
||||||
|
|
||||||
|
## Enhancements
|
||||||
|
|
||||||
|
- Support autofix linter errors
|
||||||
|
- Support anonymous updates via API
|
||||||
|
- Support mediawiki export format in pandoc export
|
||||||
|
- Add some help strings to Prometheus metrics
|
||||||
|
- Allow more syntax highlight modes in editor
|
||||||
|
- Support TOC level customization
|
||||||
|
- Follow Google guidelines to use Google OAuth
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- Vimeo won't show up due to the jsonp callback data unable be parsed with jQuery
|
||||||
|
- Fix slide mode stored XSS
|
||||||
|
- Enforce PG ssl require mode on heroku
|
||||||
|
- Webpack exclude path should support windows path
|
||||||
|
- Free url can read any md in file system
|
||||||
|
- Use encoded noteId when calling updateHistory
|
||||||
|
|
||||||
|
## Docs
|
||||||
|
|
||||||
|
- Add matrix badge and links to README [#1629](https://github.com/hackmdio/codimd/pull/1629) [@a-andreyev](https://github.com/a-andreyev)
|
||||||
|
|
||||||
<i class="fa fa-tag"></i> 2.3.1 Isoetes taiwanensis <i class="fa fa-clock-o"></i> 2021-01-04
|
<i class="fa fa-tag"></i> 2.3.1 Isoetes taiwanensis <i class="fa fa-clock-o"></i> 2021-01-04
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -139,6 +139,22 @@ This option allows you to enable Disqus with your shortname.
|
|||||||
disqus: codimd
|
disqus: codimd
|
||||||
```
|
```
|
||||||
|
|
||||||
|
toc
|
||||||
|
---
|
||||||
|
|
||||||
|
This option allows you to set options regarding the table of contents (toc). Currently, its only option is to set the maxDepth.
|
||||||
|
|
||||||
|
**Notice: always use two spaces as indention in YAML metadata!**
|
||||||
|
|
||||||
|
|
||||||
|
> **maxDepth:**
|
||||||
|
> default: not set (whioch will show everything until level 3 (h1 -- h3))
|
||||||
|
> max: 5 (as defined by md-toc.js)
|
||||||
|
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
---
|
---
|
||||||
This option allows you to switch the document view to the slide preview, to simplify live editing of presentations.
|
This option allows you to switch the document view to the slide preview, to simplify live editing of presentations.
|
||||||
|
@ -260,6 +260,23 @@ if (typeof window.mermaid !== 'undefined' && window.mermaid) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function jsonp (url, callback) {
|
||||||
|
const callbackName = 'jsonp_callback_' + Math.round(1000000000 * Math.random())
|
||||||
|
window[callbackName] = function (data) {
|
||||||
|
delete window[callbackName]
|
||||||
|
document.body.removeChild(script)
|
||||||
|
callback(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
const script = document.createElement('script')
|
||||||
|
script.src = url + (url.indexOf('?') >= 0 ? '&' : '?') + 'callback=' + callbackName
|
||||||
|
document.body.appendChild(script)
|
||||||
|
script.onerror = function (e) {
|
||||||
|
console.error(e)
|
||||||
|
script.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// dynamic event or object binding here
|
// dynamic event or object binding here
|
||||||
export function finishView (view) {
|
export function finishView (view) {
|
||||||
// todo list
|
// todo list
|
||||||
@ -304,17 +321,11 @@ export function finishView (view) {
|
|||||||
imgPlayiframe(this, '//player.vimeo.com/video/')
|
imgPlayiframe(this, '//player.vimeo.com/video/')
|
||||||
})
|
})
|
||||||
.each((key, value) => {
|
.each((key, value) => {
|
||||||
$.ajax({
|
jsonp(`//vimeo.com/api/v2/video/${$(value).attr('data-videoid')}.json`, function (data) {
|
||||||
type: 'GET',
|
|
||||||
url: `//vimeo.com/api/v2/video/${$(value).attr('data-videoid')}.json`,
|
|
||||||
jsonp: 'callback',
|
|
||||||
dataType: 'jsonp',
|
|
||||||
success (data) {
|
|
||||||
const thumbnailSrc = data[0].thumbnail_large
|
const thumbnailSrc = data[0].thumbnail_large
|
||||||
const image = `<img src="${thumbnailSrc}" />`
|
const image = `<img src="${thumbnailSrc}" />`
|
||||||
$(value).prepend(image)
|
$(value).prepend(image)
|
||||||
if (window.viewAjaxCallback) window.viewAjaxCallback()
|
if (window.viewAjaxCallback) window.viewAjaxCallback()
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
// gist
|
// gist
|
||||||
@ -597,9 +608,11 @@ export function finishView (view) {
|
|||||||
const url = $(value).attr('data-pdfurl')
|
const url = $(value).attr('data-pdfurl')
|
||||||
const inner = $('<div></div>')
|
const inner = $('<div></div>')
|
||||||
$(this).append(inner)
|
$(this).append(inner)
|
||||||
|
setTimeout(() => {
|
||||||
PDFObject.embed(url, inner, {
|
PDFObject.embed(url, inner, {
|
||||||
height: '400px'
|
height: '400px'
|
||||||
})
|
})
|
||||||
|
}, 1)
|
||||||
})
|
})
|
||||||
// syntax highlighting
|
// syntax highlighting
|
||||||
view.find('code.raw').removeClass('raw')
|
view.find('code.raw').removeClass('raw')
|
||||||
@ -864,8 +877,12 @@ export function generateToc (id) {
|
|||||||
const target = $(`#${id}`)
|
const target = $(`#${id}`)
|
||||||
target.html('')
|
target.html('')
|
||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
|
|
||||||
|
var tocOptions = md.meta.toc || {}
|
||||||
|
var maxLevel = (typeof tocOptions.maxLevel === 'number' && tocOptions.maxLevel > 0) ? tocOptions.maxLevel : window.defaultTocDepth
|
||||||
|
|
||||||
var toc = new window.Toc('doc', {
|
var toc = new window.Toc('doc', {
|
||||||
level: 3,
|
level: maxLevel,
|
||||||
top: -1,
|
top: -1,
|
||||||
class: 'toc',
|
class: 'toc',
|
||||||
ulClass: 'nav',
|
ulClass: 'nav',
|
||||||
@ -1063,11 +1080,20 @@ export function renderTOC (view) {
|
|||||||
const target = $(`#${id}`)
|
const target = $(`#${id}`)
|
||||||
target.html('')
|
target.html('')
|
||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
|
|
||||||
|
const specificDepth = parseInt(toc.data('toc-depth'))
|
||||||
|
|
||||||
|
var tocOptions = md.meta.toc || {}
|
||||||
|
var yamlMaxDepth = (typeof tocOptions.maxLevel === 'number' && tocOptions.maxLevel > 0) ? tocOptions.maxLevel : window.defaultTocDepth
|
||||||
|
|
||||||
|
var maxLevel = specificDepth || yamlMaxDepth
|
||||||
|
|
||||||
const TOC = new window.Toc('doc', {
|
const TOC = new window.Toc('doc', {
|
||||||
level: 3,
|
level: maxLevel,
|
||||||
top: -1,
|
top: -1,
|
||||||
class: 'toc',
|
class: 'toc',
|
||||||
targetId: id,
|
targetId: id,
|
||||||
|
data: { tocDepth: specificDepth },
|
||||||
process: getHeaderContent
|
process: getHeaderContent
|
||||||
})
|
})
|
||||||
/* eslint-enable no-unused-vars */
|
/* eslint-enable no-unused-vars */
|
||||||
@ -1322,9 +1348,12 @@ const gistPlugin = new Plugin(
|
|||||||
// TOC
|
// TOC
|
||||||
const tocPlugin = new Plugin(
|
const tocPlugin = new Plugin(
|
||||||
// regexp to match
|
// regexp to match
|
||||||
/^\[TOC\]$/i,
|
/^\[TOC(|\s*maxLevel=\d+?)\]$/i,
|
||||||
|
|
||||||
(match, utils) => '<div class="toc"></div>'
|
(match, utils) => {
|
||||||
|
const tocDepth = match[1].split(/[?&=]+/)[1]
|
||||||
|
return `<div class="toc" data-toc-depth="${tocDepth}"></div>`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
// slideshare
|
// slideshare
|
||||||
const slidesharePlugin = new Plugin(
|
const slidesharePlugin = new Plugin(
|
||||||
|
@ -13,3 +13,5 @@ window.linkifyHeaderStyle = '<%- linkifyHeaderStyle %>'
|
|||||||
window.DROPBOX_APP_KEY = '<%- DROPBOX_APP_KEY %>'
|
window.DROPBOX_APP_KEY = '<%- DROPBOX_APP_KEY %>'
|
||||||
|
|
||||||
window.USE_CDN = <%- useCDN %>
|
window.USE_CDN = <%- useCDN %>
|
||||||
|
|
||||||
|
window.defaultTocDepth = <%- defaultTocDepth %>
|
||||||
|
@ -1,9 +1,23 @@
|
|||||||
|
# HELP online_notes Number of currently used notes
|
||||||
|
# TYPE online_notes gauge
|
||||||
online_notes <%- onlineNotes %>
|
online_notes <%- onlineNotes %>
|
||||||
|
# HELP online_users Number of online users
|
||||||
|
# TYPE online_users gauge
|
||||||
online_users <%- onlineUsers %>
|
online_users <%- onlineUsers %>
|
||||||
|
# HELP distinct_online_users Number of distinct online users
|
||||||
|
# TYPE distinct_online_users gauge
|
||||||
distinct_online_users <%- distinctOnlineUsers %>
|
distinct_online_users <%- distinctOnlineUsers %>
|
||||||
|
# HELP notes_count Total count of notes
|
||||||
|
# TYPE notes_count gauge
|
||||||
notes_count <%- notesCount %>
|
notes_count <%- notesCount %>
|
||||||
|
# HELP registered_users Number of registered users
|
||||||
|
# TYPE registered_users gauge
|
||||||
registered_users <%- registeredUsers %>
|
registered_users <%- registeredUsers %>
|
||||||
|
# HELP online_registered_users Number of online registered users
|
||||||
|
# TYPE online_registered_users gauge
|
||||||
online_registered_users <%- onlineRegisteredUsers %>
|
online_registered_users <%- onlineRegisteredUsers %>
|
||||||
|
# HELP distinct_online_registered_users Number of distinct online registered users
|
||||||
|
# TYPE distinct_online_registered_users gauge
|
||||||
distinct_online_registered_users <%- distinctOnlineRegisteredUsers %>
|
distinct_online_registered_users <%- distinctOnlineRegisteredUsers %>
|
||||||
is_connection_busy <%- isConnectionBusy ? 1 : 0 %>
|
is_connection_busy <%- isConnectionBusy ? 1 : 0 %>
|
||||||
connection_socket_queue_length <%- connectionSocketQueueLength %>
|
connection_socket_queue_length <%- connectionSocketQueueLength %>
|
||||||
|
@ -6,7 +6,7 @@ import * as utils from './utils'
|
|||||||
import config from './config'
|
import config from './config'
|
||||||
import statusBarTemplate from './statusbar.html'
|
import statusBarTemplate from './statusbar.html'
|
||||||
import toolBarTemplate from './toolbar.html'
|
import toolBarTemplate from './toolbar.html'
|
||||||
import './markdown-lint'
|
import { linterOptions } from './markdown-lint'
|
||||||
import CodeMirrorSpellChecker, { supportLanguages, supportLanguageCodes } from './spellcheck'
|
import CodeMirrorSpellChecker, { supportLanguages, supportLanguageCodes } from './spellcheck'
|
||||||
import { initTableEditor } from './table-editor'
|
import { initTableEditor } from './table-editor'
|
||||||
import { availableThemes } from './constants'
|
import { availableThemes } from './constants'
|
||||||
@ -140,6 +140,42 @@ export default class Editor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CodeMirror.defineMode('c', function (config, modeConfig) {
|
||||||
|
return CodeMirror.overlayMode(CodeMirror.getMode(config, 'text/x-csrc'), ignoreOverlay)
|
||||||
|
})
|
||||||
|
CodeMirror.defineMode('cpp', function (config, modeConfig) {
|
||||||
|
return CodeMirror.overlayMode(CodeMirror.getMode(config, 'text/x-c++src'), ignoreOverlay)
|
||||||
|
})
|
||||||
|
CodeMirror.defineMode('java', function (config, modeConfig) {
|
||||||
|
return CodeMirror.overlayMode(CodeMirror.getMode(config, 'text/x-java'), ignoreOverlay)
|
||||||
|
})
|
||||||
|
CodeMirror.defineMode('csharp', function (config, modeConfig) {
|
||||||
|
return CodeMirror.overlayMode(CodeMirror.getMode(config, 'text/x-csharp'), ignoreOverlay)
|
||||||
|
})
|
||||||
|
CodeMirror.defineMode('objectivec', function (config, modeConfig) {
|
||||||
|
return CodeMirror.overlayMode(CodeMirror.getMode(config, 'text/x-objectivec'), ignoreOverlay)
|
||||||
|
})
|
||||||
|
CodeMirror.defineMode('scala', function (config, modeConfig) {
|
||||||
|
return CodeMirror.overlayMode(CodeMirror.getMode(config, 'text/x-scala'), ignoreOverlay)
|
||||||
|
})
|
||||||
|
CodeMirror.defineMode('kotlin', function (config, modeConfig) {
|
||||||
|
return CodeMirror.overlayMode(CodeMirror.getMode(config, 'text/x-kotlin'), ignoreOverlay)
|
||||||
|
})
|
||||||
|
CodeMirror.defineMode('json', function (config, modeConfig) {
|
||||||
|
return CodeMirror.overlayMode(CodeMirror.getMode(config, 'application/json'), ignoreOverlay)
|
||||||
|
})
|
||||||
|
CodeMirror.defineMode('jsonld', function (config, modeConfig) {
|
||||||
|
return CodeMirror.overlayMode(CodeMirror.getMode(config, 'application/ld+json'), ignoreOverlay)
|
||||||
|
})
|
||||||
|
CodeMirror.defineMode('bash', function (config, modeConfig) {
|
||||||
|
return CodeMirror.overlayMode(CodeMirror.getMode(config, 'text/x-sh'), ignoreOverlay)
|
||||||
|
})
|
||||||
|
CodeMirror.defineMode('ocaml', function (config, modeConfig) {
|
||||||
|
return CodeMirror.overlayMode(CodeMirror.getMode(config, 'text/x-ocaml'), ignoreOverlay)
|
||||||
|
})
|
||||||
|
CodeMirror.defineMode('csvpreview', function (config, modeConfig) {
|
||||||
|
return CodeMirror.overlayMode(CodeMirror.getMode(config, 'csv'), ignoreOverlay)
|
||||||
|
})
|
||||||
CodeMirror.defineMode('vega', function (config, modeConfig) {
|
CodeMirror.defineMode('vega', function (config, modeConfig) {
|
||||||
return CodeMirror.overlayMode(CodeMirror.getMode(config, 'application/ld+json'), ignoreOverlay)
|
return CodeMirror.overlayMode(CodeMirror.getMode(config, 'application/ld+json'), ignoreOverlay)
|
||||||
})
|
})
|
||||||
@ -674,7 +710,7 @@ export default class Editor {
|
|||||||
this.editor.setOption('gutters', gutters.filter(g => g !== lintGutter))
|
this.editor.setOption('gutters', gutters.filter(g => g !== lintGutter))
|
||||||
Cookies.remove('linter')
|
Cookies.remove('linter')
|
||||||
}
|
}
|
||||||
this.editor.setOption('lint', enable)
|
this.editor.setOption('lint', enable ? linterOptions : false)
|
||||||
}
|
}
|
||||||
|
|
||||||
setLinter () {
|
setLinter () {
|
||||||
@ -685,7 +721,7 @@ export default class Editor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
linterToggle.click(() => {
|
linterToggle.click(() => {
|
||||||
const lintEnable = this.editor.getOption('lint')
|
const lintEnable = !!this.editor.getOption('lint')
|
||||||
this.toggleLinter.bind(this)(!lintEnable)
|
this.toggleLinter.bind(this)(!lintEnable)
|
||||||
updateLinterStatus(!lintEnable)
|
updateLinterStatus(!lintEnable)
|
||||||
})
|
})
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
// load CM lint plugin explicitly
|
// load CM lint plugin explicitly
|
||||||
import '@hackmd/codemirror/addon/lint/lint'
|
import '@hackmd/codemirror/addon/lint/lint'
|
||||||
|
|
||||||
|
import '@hackmd/codemirror/addon/hint/show-hint.css'
|
||||||
|
import helpers from 'markdownlint-rule-helpers'
|
||||||
|
|
||||||
window.markdownit = require('markdown-it')
|
window.markdownit = require('markdown-it')
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
require('script-loader!markdownlint');
|
require('script-loader!markdownlint');
|
||||||
@ -26,10 +29,11 @@ require('script-loader!markdownlint');
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messageHTML: `${ruleNames.join('/')}: ${ruleDescription}`,
|
messageHTML: `${ruleNames.join('/')}: ${ruleDescription} <small>markdownlint(${ruleNames[0]})</small>`,
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
from: CodeMirror.Pos(lineNumber, start),
|
from: CodeMirror.Pos(lineNumber, start),
|
||||||
to: CodeMirror.Pos(lineNumber, end)
|
to: CodeMirror.Pos(lineNumber, end),
|
||||||
|
__error: error
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -37,11 +41,84 @@ require('script-loader!markdownlint');
|
|||||||
CodeMirror.registerHelper('lint', 'markdown', validator)
|
CodeMirror.registerHelper('lint', 'markdown', validator)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const linterOptions = {
|
||||||
|
fixedTooltip: true,
|
||||||
|
contextmenu: annotations => {
|
||||||
|
const singleFixMenus = annotations
|
||||||
|
.map(annotation => {
|
||||||
|
const error = annotation.__error
|
||||||
|
const ruleNameAlias = error.ruleNames.join('/')
|
||||||
|
|
||||||
|
if (annotation.__error.fixInfo) {
|
||||||
|
return {
|
||||||
|
content: `Click to fix this violoation of ${ruleNameAlias}`,
|
||||||
|
onClick () {
|
||||||
|
const doc = window.editor.doc
|
||||||
|
const fixInfo = normalizeFixInfo(error.fixInfo, error.lineNumber)
|
||||||
|
const line = fixInfo.lineNumber - 1
|
||||||
|
const lineContent = doc.getLine(line) || ''
|
||||||
|
const fixedText = helpers.applyFix(lineContent, fixInfo, '\n')
|
||||||
|
|
||||||
|
let from = { line, ch: 0 }
|
||||||
|
let to = { line, ch: lineContent ? lineContent.length : 0 }
|
||||||
|
|
||||||
|
if (typeof fixedText === 'string') {
|
||||||
|
doc.replaceRange(fixedText, from, to)
|
||||||
|
} else {
|
||||||
|
if (fixInfo.lineNumber === 1) {
|
||||||
|
if (doc.lineCount() > 1) {
|
||||||
|
const nextLineStart = doc.indexFromPos({
|
||||||
|
line: to.line + 1,
|
||||||
|
ch: 0
|
||||||
|
})
|
||||||
|
to = doc.posFromIndex(nextLineStart)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const previousLineEnd = doc.indexFromPos(from) - 1
|
||||||
|
from = doc.posFromIndex(previousLineEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.replaceRange('', from, to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
content: `Click for more information about ${ruleNameAlias}`,
|
||||||
|
onClick () {
|
||||||
|
window.open(error.ruleInformation, '_blank')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return singleFixMenus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function lint (content) {
|
function lint (content) {
|
||||||
const { content: errors } = window.markdownlint.sync({
|
const { content: errors } = window.markdownlint.sync({
|
||||||
strings: {
|
strings: {
|
||||||
content
|
content
|
||||||
}
|
},
|
||||||
|
resultVersion: 3
|
||||||
})
|
})
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Taken from https://github.com/DavidAnson/markdownlint/blob/2a9274ece586514ba3e2819cec3eb74312dc1b84/helpers/helpers.js#L611
|
||||||
|
/**
|
||||||
|
* Normalizes the fields of a RuleOnErrorFixInfo instance.
|
||||||
|
*
|
||||||
|
* @param {Object} fixInfo RuleOnErrorFixInfo instance.
|
||||||
|
* @param {number} [lineNumber] Line number.
|
||||||
|
* @returns {Object} Normalized RuleOnErrorFixInfo instance.
|
||||||
|
*/
|
||||||
|
function normalizeFixInfo (fixInfo, lineNumber) {
|
||||||
|
return {
|
||||||
|
lineNumber: fixInfo.lineNumber || lineNumber,
|
||||||
|
editColumn: fixInfo.editColumn || 1,
|
||||||
|
deleteCount: fixInfo.deleteCount || 0,
|
||||||
|
insertText: fixInfo.insertText || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -103,7 +103,7 @@ import { md } from './extra'
|
|||||||
|
|
||||||
// prevent script end tags in the content from interfering
|
// prevent script end tags in the content from interfering
|
||||||
// with parsing
|
// with parsing
|
||||||
content = content.replace(/<\/script>/g, SCRIPT_END_PLACEHOLDER)
|
content = content.replace(/<\/script>/gi, SCRIPT_END_PLACEHOLDER)
|
||||||
|
|
||||||
return '<script type="text/template">' + content + '</script>'
|
return '<script type="text/template">' + content + '</script>'
|
||||||
}
|
}
|
||||||
|
@ -80,6 +80,8 @@ const defaultOptions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var options = meta.slideOptions || {}
|
var options = meta.slideOptions || {}
|
||||||
|
// delete dependencies to avoid import user defined external resources
|
||||||
|
delete options.dependencies
|
||||||
|
|
||||||
if (Object.hasOwnProperty.call(options, 'spotlight')) {
|
if (Object.hasOwnProperty.call(options, 'spotlight')) {
|
||||||
defaultOptions.dependencies.push({
|
defaultOptions.dependencies.push({
|
||||||
|
@ -71,3 +71,11 @@
|
|||||||
background-position: right bottom;
|
background-position: right bottom;
|
||||||
width: 100%; height: 100%;
|
width: 100%; height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.CodeMirror-hints {
|
||||||
|
background: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-hint {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
6
public/vendor/md-toc.js
vendored
6
public/vendor/md-toc.js
vendored
@ -2,6 +2,8 @@
|
|||||||
/**
|
/**
|
||||||
* md-toc.js v1.0.2
|
* md-toc.js v1.0.2
|
||||||
* https://github.com/yijian166/md-toc.js
|
* https://github.com/yijian166/md-toc.js
|
||||||
|
*
|
||||||
|
* Adapted to accept data attributes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function (window) {
|
(function (window) {
|
||||||
@ -15,6 +17,7 @@
|
|||||||
this.tocTop = parseInt(options.top) || 0
|
this.tocTop = parseInt(options.top) || 0
|
||||||
this.elChilds = this.el.children
|
this.elChilds = this.el.children
|
||||||
this.process = options['process']
|
this.process = options['process']
|
||||||
|
this.data = options.data || {}
|
||||||
if (!this.elChilds.length) return
|
if (!this.elChilds.length) return
|
||||||
this._init()
|
this._init()
|
||||||
}
|
}
|
||||||
@ -123,6 +126,9 @@
|
|||||||
this.toc = document.createElement('div')
|
this.toc = document.createElement('div')
|
||||||
this.toc.innerHTML = this.tocContent
|
this.toc.innerHTML = this.tocContent
|
||||||
this.toc.setAttribute('class', this.tocClass)
|
this.toc.setAttribute('class', this.tocClass)
|
||||||
|
if (this.data.tocDepth) {
|
||||||
|
this.toc.dataset.tocDepth = this.data.tocDepth
|
||||||
|
}
|
||||||
if (!this.options.targetId) {
|
if (!this.options.targetId) {
|
||||||
this.el.appendChild(this.toc)
|
this.el.appendChild(this.toc)
|
||||||
} else {
|
} else {
|
||||||
|
@ -77,7 +77,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 inner">
|
<div class="col-md-4 inner">
|
||||||
<a href="<%- serverURL %>/features#Slide-Modee">
|
<a href="<%- serverURL %>/features#Slide-Mode">
|
||||||
<i class="fa fa-tv fa-3x"></i>
|
<i class="fa fa-tv fa-3x"></i>
|
||||||
<h4><%= __('Support slide mode') %></h4>
|
<h4><%= __('Support slide mode') %></h4>
|
||||||
</a>
|
</a>
|
||||||
@ -156,6 +156,7 @@
|
|||||||
<h6 class="social-foot">
|
<h6 class="social-foot">
|
||||||
<%- __('Follow us on %s and %s.', '<a href="https://github.com/hackmdio/CodiMD" target="_blank" rel="noopener"><i class="fa fa-github"></i> GitHub</a>, <a href="https://gitter.im/hackmdio/hackmd" target="_blank" rel="noopener"><i class="fa fa-comments"></i> Gitter</a>', '<a href="https://poeditor.com/join/project/q0nuPWyztp" target="_blank" rel="noopener"><i class="fa fa-globe"></i> POEditor</a>') %>
|
<%- __('Follow us on %s and %s.', '<a href="https://github.com/hackmdio/CodiMD" target="_blank" rel="noopener"><i class="fa fa-github"></i> GitHub</a>, <a href="https://gitter.im/hackmdio/hackmd" target="_blank" rel="noopener"><i class="fa fa-comments"></i> Gitter</a>', '<a href="https://poeditor.com/join/project/q0nuPWyztp" target="_blank" rel="noopener"><i class="fa fa-globe"></i> POEditor</a>') %>
|
||||||
</h6>
|
</h6>
|
||||||
|
<% if(privacyPolicyURL && privacyPolicyURL.length > 0) { %><p><a href="<%- privacyPolicyURL %>"><%= __('Privacy Policy') %></a></p><% } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
<option value="rtf">Rich Text Format (.rtf)</option>
|
<option value="rtf">Rich Text Format (.rtf)</option>
|
||||||
<option value="textile">Textile</option>
|
<option value="textile">Textile</option>
|
||||||
<option value="docx">Word (.docx)</option>
|
<option value="docx">Word (.docx)</option>
|
||||||
|
<option value="mediawiki">Mediawiki</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
@ -44,8 +44,8 @@
|
|||||||
</a>
|
</a>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if (authProviders.google) { %>
|
<% if (authProviders.google) { %>
|
||||||
<a href="<%- serverURL %>/auth/google" class="btn btn-lg btn-block btn-login-method btn-google">
|
<a href="<%- serverURL %>/auth/google" class="btn btn-lg btn-block" style="color: #fff; background-color: #4285F4; vertical-align: middle; font-size: 14px; font-family: Roboto;">
|
||||||
<i class="fa fa-google"></i> <%= __('Sign in via %s', 'Google') %>
|
<svg aria-hidden="true" width="34" height="34" viewBox="-8 -8 34 34" style="vertical-align: middle; margin-right: 16; background-color=#fff;"><path d="M16.51 8H8.98v3h4.3c-.18 1-.74 1.48-1.6 2.04v2.01h2.6a7.8 7.8 0 002.38-5.88c0-.57-.05-.66-.15-1.18z" fill="#4285F4"></path><path d="M8.98 17c2.16 0 3.97-.72 5.3-1.94l-2.6-2a4.8 4.8 0 01-7.18-2.54H1.83v2.07A8 8 0 008.98 17z" fill="#34A853"></path><path d="M4.5 10.52a4.8 4.8 0 010-3.04V5.41H1.83a8 8 0 000 7.18l2.67-2.07z" fill="#FBBC05"></path><path d="M8.98 4.18c1.17 0 2.23.4 3.06 1.2l2.3-2.3A8 8 0 001.83 5.4L4.5 7.49a4.77 4.77 0 014.48-3.3z" fill="#EA4335"></path></svg> <%= __('Sign in via %s', 'Google') %>
|
||||||
</a>
|
</a>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if (authProviders.saml) { %>
|
<% if (authProviders.saml) { %>
|
||||||
|
@ -443,7 +443,10 @@ module.exports = {
|
|||||||
}, {
|
}, {
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
use: [{ loader: 'babel-loader' }],
|
use: [{ loader: 'babel-loader' }],
|
||||||
exclude: [/node_modules/, /public\/vendor/]
|
exclude: [
|
||||||
|
path.resolve(__dirname, 'node_modules'),
|
||||||
|
path.resolve(__dirname, 'public/vendor')
|
||||||
|
]
|
||||||
}, {
|
}, {
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
use: [
|
use: [
|
||||||
@ -522,7 +525,8 @@ module.exports = {
|
|||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
node: {
|
node: {
|
||||||
fs: 'empty'
|
fs: 'empty',
|
||||||
|
os: 'empty'
|
||||||
},
|
},
|
||||||
|
|
||||||
stats: {
|
stats: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user