Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 2.5.0 #1835

Merged
merged 73 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
0ec949d
Add oauth2 authorization
Nov 20, 2020
b6d5db3
fix: should not clear guest history when guest pin note
a60814billy Jun 12, 2021
0d1cd1d
feat: add organizations whitelist to GitHub OAuth
jakubgs Aug 20, 2021
d627d9f
Documentation - add Music section and move abc abd fretboard to this …
brunetton Sep 15, 2021
c013c22
chore: bump meta-marked to 0.5.0
Yukaii Oct 19, 2021
54e5b93
Update README.md to remove IE from supporting list
jackycute Oct 31, 2021
adb0dd5
Fix fence class array initialization
V1ncNet Jan 5, 2022
07cfb29
🐛 [fix] modify replacement rule for disqus short-name
chenxuanzzy Mar 3, 2022
1a38aaa
s3 support multiple cloud providers: https://github.com/hackmdio/codi…
blademainer May 6, 2022
7b1e4e3
s3 support multiple cloud providers: https://github.com/hackmdio/codi…
blademainer May 6, 2022
e6d2b7a
Upgrade Node.js version
inductor Aug 2, 2022
03f8649
upgrading pg to 8.8.0 to support new scram-sha-256 authentication
phntom Dec 4, 2022
5d72fae
fix: restrict export type and use sandbox to prevent potential attack
galaxian85 Jan 6, 2023
9df1c34
fix: sanitize vimeo video id to prevent XSS
galaxian85 Jan 10, 2023
a7564e7
Merge pull request #1697 from hackmdio/fix/guest-history-pin
jackycute Jan 13, 2023
3ab0b7e
Merge pull request #1762 from blademainer/develop
jackycute Jan 13, 2023
21345ed
Merge pull request #1715 from brunetton/develop
jackycute Jan 13, 2023
a2565bd
Merge pull request #1739 from V1ncNet/hotfix/fence-class
jackycute Jan 13, 2023
8b29d05
Merge pull request #1729 from hackmdio/feature/readme-remove-IE-support
a60814billy Jan 13, 2023
8efc2d0
chore: update formidable to 2.1.1
galaxian85 Jan 7, 2023
1ab2a36
fix: handle error when check is image valid
galaxian85 Jan 7, 2023
30fe18d
Merge pull request #1792 from galaxian85/bugfix/potential-xss-vulnera…
jackycute Jan 13, 2023
19f494d
Merge pull request #1789 from galaxian85/bugfix/invalid-filename-caus…
a60814billy Jan 13, 2023
6a5da1e
Typos + Better translation for "Externals"
eyssette Jan 15, 2023
3a3e892
Merge pull request #1722 from hackmdio/chore/upgrade-meta-marked-to-0…
jackycute Jan 28, 2023
c78bb69
Merge pull request #1793 from eyssette/patch-2
jackycute Jan 28, 2023
ee47c6c
fix(buildpacks): replace custom buildpack with APT buildpack
EtienneM Feb 14, 2023
6ff5a3a
feat: Migrate to gtag and support GA4
assanges Feb 26, 2023
3ea10a6
Update minimum required node.js version to v12
PeterDaveHello Mar 1, 2023
500fe78
Update npm package dependencies
PeterDaveHello Mar 1, 2023
342ff2b
Merge pull request #1797 from EtienneM/develop
jackycute Mar 9, 2023
b09a472
Merge pull request #1790 from galaxian85/bugfix/pandoc-security-issue
jackycute Mar 9, 2023
52bfb9b
Merge pull request #1799 from PeterDaveHello/updateDependenciesBaseline
Yukaii Mar 9, 2023
08e2dcd
Merge pull request #1798 from assanges/feature/support-ga4
jackycute Mar 17, 2023
4399457
reword japanese
AQ-masatoshi-yamaguchi Mar 23, 2023
75d830d
Merge pull request #1802 from AQ-masatoshi-yamaguchi/reword-japanese
jackycute Mar 23, 2023
20e71a3
fix: use encoded note id to update history
bbtfr Apr 10, 2023
298db32
Merge pull request #1804 from bbtfr/fix-note-id
jackycute Jun 5, 2023
8af3638
Merge pull request #1784 from phntom/develop
jackycute Jun 5, 2023
a3a789b
Merge pull request #1710 from status-im/gh-oauth-orgs
jackycute Jun 5, 2023
f1ed885
Merge branch 'develop' into update_node
jackycute Jun 5, 2023
ae38fd3
Merge pull request #1767 from inductor/update_node
jackycute Jun 5, 2023
be1f4cf
Merge pull request #1626 from joachimmathes/oauth2_authorization
jackycute Jun 5, 2023
289db3d
Merge pull request #1750 from chenxuanzzy/bugfix/fix-regex
jackycute Jun 5, 2023
09e7dfe
feat: update package-lock.json
jackycute Jun 5, 2023
03e456f
Revert "feat: update package-lock.json"
jackycute Jun 5, 2023
e2ecaa8
feat: update package-lock.json
jackycute Jun 5, 2023
dd4c236
feat: update package-lock.json
jackycute Jun 5, 2023
5ce1537
fix: js standard code styles
jackycute Jun 5, 2023
c97e224
fix: jsonlint code styles
jackycute Jun 5, 2023
b5b95df
fix: hide exportNoteData form in history page nav
jackycute Jun 5, 2023
c4f607f
Merge pull request #1808 from hackmdio/bugfix/fix-history-nav-ui
jackycute Jun 5, 2023
b6078a6
Update both Traditional and Simplified Chinese locales
PeterDaveHello Jun 18, 2023
192698b
Update npm dependencies with `npm audit fix`, except `markmap-lib`
PeterDaveHello Jun 18, 2023
976bca3
Update npm dependency markmap-lib to ^0.9.3
PeterDaveHello Jun 18, 2023
3f7d202
Update npm dependencies with `npm audit fix` after markmap-lib update
PeterDaveHello Jun 18, 2023
8efa75e
Update node.js version in .nvmrc
PeterDaveHello Jun 18, 2023
b6eba05
Merge pull request #1816 from PeterDaveHello/node.js-14
jackycute Aug 11, 2023
11cd200
fix: sanitize pdf url to prevent xss
EastSun5566 Dec 15, 2023
2a977bc
fix: markmap api changes in 0.9.3
Yukaii Dec 26, 2023
6d95fd1
Merge pull request #1817 from PeterDaveHello/updateDependenciesBaseline
Yukaii Dec 26, 2023
dca7f8c
Merge pull request #1832 from hackmdio/bugfix/sanitize-url-to-prevent…
Yukaii Dec 26, 2023
070808b
Merge pull request #1815 from PeterDaveHello/updateLocales
Yukaii Dec 26, 2023
027a4b4
fix the uploadimage form
hcyuser Aug 9, 2023
96a589d
add newline here to pass the standard lint.
hcyuser Aug 10, 2023
1b1ff89
fix: formidable option API changes
Yukaii Dec 26, 2023
f58c669
Merge pull request #1836 from hackmdio/bugfix/uploadimage-form
stanley2058 Dec 26, 2023
c436af1
fix: logout api should add callback
Yukaii Dec 26, 2023
9790586
Merge pull request #1837 from hackmdio/bugfix/logout-callback
stanley2058 Dec 26, 2023
f8b0301
doc(release-note): release 2.5.0
stanley2058 Dec 26, 2023
9b8a2de
Merge branch 'master' into release/2.5.0
stanley2058 Dec 26, 2023
86bf44f
doc(release-note): update release code name
stanley2058 Dec 26, 2023
afe49f4
doc(release-note): update release note
stanley2058 Dec 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .buildpacks
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
https://github.com/alex88/heroku-buildpack-vips
https://github.com/Scalingo/apt-buildpack
https://github.com/Scalingo/nodejs-buildpack
4 changes: 2 additions & 2 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# [Choice] Node.js version: 16, 14, 12
ARG VARIANT=12-buster
# [Choice] Node.js version: 16, 14
ARG VARIANT=14-buster
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}

# [Optional] Uncomment this section to install additional OS packages.
Expand Down
2 changes: 1 addition & 1 deletion .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ services:
context: ..
dockerfile: .devcontainer/Dockerfile
args:
VARIANT: 12-buster
VARIANT: 14-buster
environment:
- CMD_DB_URL=postgres://codimd:codimd@localhost/codimd
- CMD_USECDN=false
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x]
node-version: [14.x, 16.x]

steps:
- uses: actions/checkout@v2
Expand Down Expand Up @@ -39,9 +39,9 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
name: Use Node.js 12
name: Use Node.js 14
with:
node-version: 12
node-version: 14
check-latest: true
- name: Install doctoc-check
run: |
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v10.20.1
v16.20.2
1 change: 1 addition & 0 deletions Aptfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
libvips-dev
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ CodiMD is a service that runs on Node.js, while users use the service through br
- <img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" /> Chrome >= 47, Chrome for Android >= 47
- <img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" /> Safari >= 9, iOS Safari >= 8.4
- <img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" /> Firefox >= 44
- <img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" /> IE >= 9, Edge >= 12
- <img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" /> Edge >= 12
- <img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" /> Opera >= 34, Opera Mini not supported
- Android Browser >= 4.4

Expand Down
8 changes: 8 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@
"description": "GitHub API client secret",
"required": false
},
"CMD_GITHUB_ORGANIZATIONS": {
"description": "GitHub whitelist of orgs",
"required": false
},
"CMD_GITHUB_SCOPES": {
"description": "GitHub OAuth API scopes",
"required": false
},
"CMD_BITBUCKET_CLIENTID": {
"description": "Bitbucket API client id",
"required": false
Expand Down
4 changes: 3 additions & 1 deletion config.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@
},
"github": {
"clientID": "change this",
"clientSecret": "change this"
"clientSecret": "change this",
"organizations": ["names of github organizations allowed, optional"],
"scopes": ["defaults to 'read:user' scope for auth user"]
},
"gitlab": {
"baseURL": "change this",
Expand Down
37 changes: 35 additions & 2 deletions lib/auth/github/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
'use strict'

const Router = require('express').Router
const request = require('request')
const passport = require('passport')
const GithubStrategy = require('passport-github').Strategy
const { InternalOAuthError } = require('passport-oauth2')
const config = require('../../config')
const response = require('../../response')
const { setReturnToFromReferer, passportGeneralCallback } = require('../utils')
const { URL } = require('url')
const { promisify } = require('util')

const rp = promisify(request)

const githubAuth = module.exports = Router()

Expand All @@ -15,20 +20,48 @@ function githubUrl (path) {
}

passport.use(new GithubStrategy({
scope: (config.github.organizations ? config.github.scopes.concat(['read:org']) : config.github.scope),
clientID: config.github.clientID,
clientSecret: config.github.clientSecret,
callbackURL: config.serverURL + '/auth/github/callback',
authorizationURL: githubUrl('login/oauth/authorize'),
tokenURL: githubUrl('login/oauth/access_token'),
userProfileURL: githubUrl('api/v3/user')
}, passportGeneralCallback))
}, async (accessToken, refreshToken, profile, done) => {
if (!config.github.organizations) {
return passportGeneralCallback(accessToken, refreshToken, profile, done)
}
const { statusCode, body: data } = await rp({
url: `https://api.github.com/user/orgs`,
method: 'GET',
json: true,
timeout: 2000,
headers: {
Authorization: `token ${accessToken}`,
'User-Agent': 'nodejs-http'
}
})
if (statusCode !== 200) {
return done(InternalOAuthError(
`Failed to query organizations for user: ${profile.username}`
))
}
const orgs = data.map(({ login }) => login)
for (const org of orgs) {
if (config.github.organizations.includes(org)) {
return passportGeneralCallback(accessToken, refreshToken, profile, done)
}
}
return done(InternalOAuthError(
`User orgs not whitelisted: ${profile.username} (${orgs.join(',')})`
))
}))

githubAuth.get('/auth/github', function (req, res, next) {
setReturnToFromReferer(req)
passport.authenticate('github')(req, res, next)
})

// github auth callback
githubAuth.get('/auth/github/callback',
passport.authenticate('github', {
successReturnToOrRedirect: config.serverURL + '/',
Expand Down
10 changes: 7 additions & 3 deletions lib/auth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,14 @@ if (config.isEmailEnable) authRouter.use(require('./email'))
if (config.isOpenIDEnable) authRouter.use(require('./openid'))

// logout
authRouter.get('/logout', function (req, res) {
authRouter.get('/logout', function (req, res, next) {
if (config.debug && req.isAuthenticated()) {
logger.debug('user logout: ' + req.user.id)
}
req.logout()
res.redirect(config.serverURL + '/')

req.logout((err) => {
if (err) { return next(err) }

res.redirect(config.serverURL + '/')
})
})
14 changes: 13 additions & 1 deletion lib/auth/oauth2/strategy.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { Strategy, InternalOAuthError } = require('passport-oauth2')
const config = require('../../config')

function parseProfile (data) {
const id = extractProfileAttribute(data, config.oauth2.userProfileIdAttr)
const username = extractProfileAttribute(data, config.oauth2.userProfileUsernameAttr)
const displayName = extractProfileAttribute(data, config.oauth2.userProfileDisplayNameAttr)
const email = extractProfileAttribute(data, config.oauth2.userProfileEmailAttr)
Expand All @@ -14,7 +15,7 @@ function parseProfile (data) {
}

return {
id: username,
id: id || username,
username: username,
displayName: displayName,
email: email,
Expand All @@ -41,6 +42,16 @@ function extractProfileAttribute (data, path) {
return data
}

function checkAuthorization (data, done) {
const roles = extractProfileAttribute(data, config.oauth2.rolesClaim)

if (config.oauth2.accessRole && roles) {
if (!roles.includes(config.oauth2.accessRole)) {
return done('Permission denied', null)
}
}
}

class OAuth2CustomStrategy extends Strategy {
constructor (options, verify) {
options.customHeaders = options.customHeaders || {}
Expand All @@ -59,6 +70,7 @@ class OAuth2CustomStrategy extends Strategy {
let profile, json
try {
json = JSON.parse(body)
checkAuthorization(json, done)
profile = parseProfile(json)
} catch (ex) {
return done(new InternalOAuthError('Failed to parse user profile' + ex.toString()))
Expand Down
4 changes: 3 additions & 1 deletion lib/config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ module.exports = {
github: {
enterpriseURL: undefined, // if you use github.com, not need to specify
clientID: undefined,
clientSecret: undefined
clientSecret: undefined,
organizations: [],
scopes: ['read:user']
},
gitlab: {
baseURL: undefined,
Expand Down
7 changes: 6 additions & 1 deletion lib/config/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ module.exports = {
github: {
enterpriseURL: process.env.CMD_GITHUB_ENTERPRISE_URL,
clientID: process.env.CMD_GITHUB_CLIENTID,
clientSecret: process.env.CMD_GITHUB_CLIENTSECRET
clientSecret: process.env.CMD_GITHUB_CLIENTSECRET,
organizations: toArrayConfig(process.env.CMD_GITHUB_ORGANIZATIONS),
scopes: toArrayConfig(process.env.CMD_GITHUB_SCOPES)
},
bitbucket: {
clientID: process.env.CMD_BITBUCKET_CLIENTID,
Expand All @@ -96,6 +98,9 @@ module.exports = {
userProfileURL: process.env.CMD_OAUTH2_USER_PROFILE_URL,
scope: process.env.CMD_OAUTH2_SCOPE,
state: process.env.CMD_OAUTH2_STATE,
rolesClaim: process.env.CMD_OAUTH2_ROLES_CLAIM,
accessRole: process.env.CMD_OAUTH2_ACCESS_ROLE,
userProfileIdAttr: process.env.CMD_OAUTH2_USER_PROFILE_ID_ATTR,
userProfileUsernameAttr: process.env.CMD_OAUTH2_USER_PROFILE_USERNAME_ATTR,
userProfileDisplayNameAttr: process.env.CMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR,
userProfileEmailAttr: process.env.CMD_OAUTH2_USER_PROFILE_EMAIL_ATTR,
Expand Down
31 changes: 18 additions & 13 deletions lib/imageRouter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,42 @@ const response = require('../response')
const imageRouter = module.exports = Router()

function checkImageValid (filepath) {
const buffer = readChunk.sync(filepath, 0, 12)
/** @type {{ ext: string, mime: string } | null} */
const mimetypeFromBuf = imageType(buffer)
const mimeTypeFromExt = mime.lookup(path.extname(filepath))
try {
const buffer = readChunk.sync(filepath, 0, 12)
/** @type {{ ext: string, mime: string } | null} */
const mimetypeFromBuf = imageType(buffer)
const mimeTypeFromExt = mime.lookup(path.extname(filepath))

return mimetypeFromBuf && config.allowedUploadMimeTypes.includes(mimetypeFromBuf.mime) &&
mimeTypeFromExt && config.allowedUploadMimeTypes.includes(mimeTypeFromExt)
return mimetypeFromBuf && config.allowedUploadMimeTypes.includes(mimetypeFromBuf.mime) &&
mimeTypeFromExt && config.allowedUploadMimeTypes.includes(mimeTypeFromExt)
} catch (err) {
logger.error(err)
return false
}
}

// upload image
imageRouter.post('/uploadimage', function (req, res) {
var form = new formidable.IncomingForm()

form.keepExtensions = true
var form = new formidable.IncomingForm({
keepExtensions: true
})

form.parse(req, function (err, fields, files) {
if (err || !files.image || !files.image.path) {
if (err || !files.image || !files.image.filepath) {
response.errorForbidden(req, res)
} else {
if (config.debug) {
logger.info('SERVER received uploadimage: ' + JSON.stringify(files.image))
}

if (!checkImageValid(files.image.path)) {
if (!checkImageValid(files.image.filepath)) {
return response.errorForbidden(req, res)
}

const uploadProvider = require('./' + config.imageUploadType)
uploadProvider.uploadImage(files.image.path, function (err, url) {
uploadProvider.uploadImage(files.image.filepath, function (err, url) {
// remove temporary upload file, and ignore any error
fs.unlink(files.image.path, () => {})
fs.unlink(files.image.filepath, () => {})
if (err !== null) {
logger.error(err)
return res.status(500).end('upload image error')
Expand Down
11 changes: 7 additions & 4 deletions lib/imageRouter/s3.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,16 @@ exports.uploadImage = function (imagePath, callback) {
const command = new PutObjectCommand(params)

s3.send(command).then(data => {
let s3Endpoint = 's3.amazonaws.com'
// default scheme settings to https
let s3Endpoint = 'https://s3.amazonaws.com'
if (config.s3.region && config.s3.region !== 'us-east-1') {
s3Endpoint = `s3-${config.s3.region}.amazonaws.com`
}
// rewrite endpoint from config
if (config.s3.endpoint) {
s3Endpoint = config.s3.endpoint
} else if (config.s3.region && config.s3.region !== 'us-east-1') {
s3Endpoint = `s3-${config.s3.region}.amazonaws.com`
}
callback(null, `https://${s3Endpoint}/${config.s3bucket}/${params.Key}`)
callback(null, `${s3Endpoint}/${config.s3bucket}/${params.Key}`)
}).catch(err => {
if (err) {
callback(new Error(err), null)
Expand Down
2 changes: 1 addition & 1 deletion lib/note/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ const updateNote = async (req, res) => {
}

if (req.isAuthenticated()) {
updateHistory(req.user.id, noteId, content)
updateHistory(req.user.id, Note.encodeNoteId(noteId), content)
}

Revision.saveNoteRevision(note, (err, revision) => {
Expand Down
9 changes: 6 additions & 3 deletions lib/note/noteActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,17 @@ async function actionPandoc (req, res, note) {
var path = config.tmpPath + '/' + Date.now()
content = content.replace(/\]\(\//g, '](' + url + '/')

// TODO: check export type
const { exportType } = req.query
const contentType = outputFormats[exportType]

try {
// TODO: timeout rejection
if (!contentType) {
return res.sendStatus(400)
}

await pandoc.convertToFile(content, 'markdown', exportType, path, [
'--metadata', `title=${title}`
'--metadata', `title=${title}`, '--sandbox'
])

var stream = fs.createReadStream(path)
Expand All @@ -149,7 +152,7 @@ async function actionPandoc (req, res, note) {
// Ideally this should strip them
res.setHeader('Content-disposition', `attachment; filename="${filename}.${exportType}"`)
res.setHeader('Cache-Control', 'private')
res.setHeader('Content-Type', `${outputFormats[exportType]}; charset=UTF-8`)
res.setHeader('Content-Type', `${contentType}; charset=UTF-8`)
res.setHeader('X-Robots-Tag', 'noindex, nofollow') // prevent crawling
stream.pipe(res)
} catch (err) {
Expand Down
8 changes: 4 additions & 4 deletions locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"No history": "Pas d'historique",
"Import from browser": "Importer depuis le navigateur",
"Releases": "Versions",
"Are you sure?": "Ëtes-vous sûr ?",
"Are you sure?": "Êtes-vous sûr ?",
"Do you really want to delete this note?": "Voulez-vous vraiment supprimer cette note ?",
"All users will lose their connection.": "Tous les utilisateurs perdront leur connexion.",
"Cancel": "Annuler",
Expand Down Expand Up @@ -73,7 +73,7 @@
"Syntax": "Syntaxe",
"Header": "En-tête",
"Unordered List": "Liste à puce",
"Ordered List": "List numérotée",
"Ordered List": "Liste numérotée",
"Todo List": "Liste de tâches",
"Blockquote": "Citation",
"Bold font": "Gras",
Expand All @@ -84,7 +84,7 @@
"Link": "Lien",
"Image": "Image",
"Code": "Code",
"Externals": "Externes",
"Externals": "Contenus externes",
"This is a alert area.": "Ceci est un texte d'alerte.",
"Revert": "Revenir en arrière",
"Import from clipboard": "Importer depuis le presse-papier",
Expand Down Expand Up @@ -116,4 +116,4 @@
"Source Code": "Code source",
"Register": "S'enregistrer",
"Powered by %s": "Propulsé par %s"
}
}
Loading
Loading