diff options
-rwxr-xr-x | bin/cleanup | 104 | ||||
-rw-r--r-- | docs/url-scheme.md | 34 | ||||
-rw-r--r-- | lib/errors.js | 6 | ||||
-rw-r--r-- | lib/migrations/20200321153000-fix-account-deletion.js | 61 | ||||
-rw-r--r-- | lib/web/note/util.js | 4 | ||||
-rw-r--r-- | locales/ar.json | 32 | ||||
-rw-r--r-- | locales/fr.json | 38 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | public/css/slide.css | 6 | ||||
-rw-r--r-- | yarn.lock | 26 |
10 files changed, 250 insertions, 63 deletions
diff --git a/bin/cleanup b/bin/cleanup new file mode 100755 index 00000000..edf72bf2 --- /dev/null +++ b/bin/cleanup @@ -0,0 +1,104 @@ +#!/usr/bin/env node + +const logger = require('../lib/logger') +const models = require('../lib/models') + +logger.info('Cleaning up notes that should already have been removed. Sorry.') + +async function cleanup () { + await models.Note.findAll({ + include: [{ + model: models.User, + as: 'owner', + attributes: ['id'] + }], + attributes: ['id', 'ownerId'] + }).then(async function (notes) { + for (let i = 0, noteCount = notes.length; i < noteCount; i++) { + const item = notes[i] + if (item.ownerId != null && !item.owner) { + await models.Note.destroy({ + where: { + id: item.id + } + }) + await models.Revision.destroy({ + where: { + noteId: item.id + } + }) + await models.Author.destroy({ + where: { + noteId: item.id + } + }) + logger.info(`Deleted note ${item.id} from user ${item.ownerId}`) + } + } + }) + await models.Author.findAll({ + include: [{ + model: models.User, + as: 'user', + attributes: ['id'] + }], + attributes: ['id', 'userId'] + }).then(async function (authors) { + for (let i = 0, authorCount = authors.length; i < authorCount; i++) { + const item = authors[i] + if (item.userId != null && !item.user) { + await models.Author.destroy({ + where: { + id: item.id + } + }) + logger.info(`Deleted authorship ${item.id} from user ${item.userId}`) + } + } + }) + + await models.Author.findAll({ + include: [{ + model: models.Note, + as: 'note', + attributes: ['id'] + }], + attributes: ['id', 'noteId'] + }).then(async function (authors) { + for (let i = 0, authorCount = authors.length; i < authorCount; i++) { + const item = authors[i] + if (item.noteId != null && !item.note) { + await models.Author.destroy({ + where: { + id: item.id + } + }) + logger.info(`Deleted authorship ${item.id} from note ${item.noteId}`) + } + } + }) + + await models.Revision.findAll({ + include: [{ + model: models.Note, + as: 'note', + attributes: ['id'] + }], + attributes: ['id', 'noteId'] + }).then(async function (revisions) { + for (let i = 0, revisionCount = revisions.length; i < revisionCount; i++) { + const item = revisions[i] + if (item.noteId != null && !item.note) { + await models.Revision.destroy({ + where: { + id: item.id + } + }) + logger.info(`Deleted revision ${item.id} from note ${item.userId}`) + } + } + }) + process.exit(0) +} + +cleanup() diff --git a/docs/url-scheme.md b/docs/url-scheme.md new file mode 100644 index 00000000..88f34855 --- /dev/null +++ b/docs/url-scheme.md @@ -0,0 +1,34 @@ +# URL scheme + +CodiMD has three different modes for viewing a stored note. Each mode has a slightly different URL for accessing it. This document gives an overview about these URLs. +We assume that you replace `pad.example.com` with the domain of your instance. + +## Default (random) + +When you create a new note by clicking the "New note" button, your note is given a long random id and a random short-id. The long id is needed for accessing the editor and the live-update view. The short-id is used for the "published" version of a note that is read-only and does not update in realtime as well as for the presentation mode. + +| example URL | prefix | mode | content updates | +| -------------------------------------- | ------ | ----------------- | --------------- | +| pad.example.com/Ndmv3oCyREKZMjSGR9uhnQ | _none_ | editor | in realtime | +| pad.example.com/s/ByXF7k-YI | s/ | read-only version | on reload | +| pad.example.com/p/ByXF7k-YI | p/ | presentation mode | on reload | + +## FreeURL mode + +If the setting `CMD_ALLOW_FREEURL` is enabled, users may create notes with a custom alias URL by just visiting the editor version of a custom alias. The published version and the presentation mode may also be accessed with the custom alias. + +| example URL | prefix | mode | content updates | +| --------------------------------- | ------ | ----------------- | --------------- | +| pad.example.com/my-awesome-note | _none_ | editor | in realtime | +| pad.example.com/s/my-awesome-note | s/ | read-only version | on reload | +| pad.example.com/p/my-awesome-note | p/ | presentation mode | on reload | + +## Different editor modes + +The editor has three different sub-modes. All of these update the content in realtime. + +| example URL | icon in the navbar | behaviour | +| ------------------------------- | -------------------| ----------------------------------------------- | +| pad.example.com/longnoteid?edit | pencil | Full-screen markdown editor for the content | +| pad.example.com/longnoteid?view | eye | Full-screen view of the note without the editor | +| pad.example.com/longnoteid?both | columns | markdown editor and view mode side-by-side | diff --git a/lib/errors.js b/lib/errors.js index f86e8aa3..950b4cae 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -7,8 +7,10 @@ module.exports = { responseError(res, '403', 'Forbidden', 'oh no.') } else { if (!req.session) req.session = {} - req.session.returnTo = req.originalUrl || config.serverUrl + '/' - req.flash('error', 'You are not allowed to access this page. Maybe try logging in?') + if (req.originalUrl !== '/403') { + req.session.returnTo = config.serverURL + (req.originalUrl || '/') + req.flash('error', 'You are not allowed to access this page. Maybe try logging in?') + } res.redirect(config.serverURL + '/') } }, diff --git a/lib/migrations/20200321153000-fix-account-deletion.js b/lib/migrations/20200321153000-fix-account-deletion.js new file mode 100644 index 00000000..e794e993 --- /dev/null +++ b/lib/migrations/20200321153000-fix-account-deletion.js @@ -0,0 +1,61 @@ +'use strict' +const { spawnSync } = require('child_process') +const path = require('path') +module.exports = { + up: function (queryInterface, Sequelize) { + const cleanup = spawnSync('./bin/cleanup', { cwd: path.resolve(__dirname, '../../') }) + if (cleanup.status !== 0) { + throw new Error('Unable to cleanup') + } + return queryInterface.addConstraint('Notes', ['ownerId'], { + type: 'foreign key', + name: 'Notes_owner_fkey', + references: { + table: 'Users', + field: 'id' + }, + onDelete: 'cascade' + }).then(function () { + return queryInterface.addConstraint('Revisions', ['noteId'], { + type: 'foreign key', + name: 'Revisions_note_fkey', + references: { + table: 'Notes', + field: 'id' + }, + onDelete: 'cascade' + }) + }).then(function () { + return queryInterface.addConstraint('Authors', ['noteId'], { + type: 'foreign key', + name: 'Author_note_fkey', + references: { + table: 'Notes', + field: 'id' + }, + onDelete: 'cascade' + }) + }).then(function () { + return queryInterface.addConstraint('Authors', ['userId'], { + type: 'foreign key', + name: 'Author_user_fkey', + references: { + table: 'Users', + field: 'id' + }, + onDelete: 'cascade' + }) + }) + }, + + down: function (queryInterface, Sequelize) { + return queryInterface.removeConstraint('Notes', 'Notes_owner_fkey') + .then(function () { + return queryInterface.removeConstraint('Revisions', 'Revisions_note_fkey') + }).then(function () { + return queryInterface.removeConstraint('Authors', 'Author_note_fkey') + }).then(function () { + return queryInterface.removeConstraint('Authors', 'Author_user_fkey') + }) + } +} diff --git a/lib/web/note/util.js b/lib/web/note/util.js index eadfb1a3..c5affd05 100644 --- a/lib/web/note/util.js +++ b/lib/web/note/util.js @@ -19,7 +19,7 @@ exports.findNote = function (req, res, callback, include) { include: include || null }).then(function (note) { if (!note) { - return exports.newNote(req, res, null) + return exports.newNote(req, res, '') } if (!exports.checkViewPermission(req, note)) { return errors.errorForbidden(res) @@ -102,7 +102,7 @@ exports.getPublishData = function (req, res, note, callback) { } function isRevealTheme (theme) { - if (fs.existsSync(path.join(__dirname, '..', 'public', 'build', 'reveal.js', 'css', 'theme', theme + '.css'))) { + if (fs.existsSync(path.join(__dirname, '..', '..', '..', 'public', 'build', 'reveal.js', 'css', 'theme', theme + '.css'))) { return theme } return undefined diff --git a/locales/ar.json b/locales/ar.json index 88186f2d..ed726689 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -1,15 +1,15 @@ { "Collaborative markdown notes": "ملاحظات ماركداون تعاونية", - "Realtime collaborative markdown notes on all platforms.": "Collaborate on markdown notes on all platforms in realtime.", - "Best way to write and share your knowledge in markdown.": "The best platform to write and share markdown.", + "Realtime collaborative markdown notes on all platforms.": "تعاون آني في ملاحظات ماركداون على كافة المنصات.", + "Best way to write and share your knowledge in markdown.": "أحسن منصة لكتابة ومشاركة ماركداون.", "Intro": "مُقدِّمة", "History": "التاريخ", - "New guest note": "ملاحظة ضيف جديد", - "Collaborate with URL": "Real time collaboration", + "New guest note": "ملاحظة جديدة كضَيف", + "Collaborate with URL": "تعاون آني", "Support charts and MathJax": "دعم المنحنيات البيانية و MathJax", "Support slide mode": "دعم الشرائح التقديمية", "Sign In": "لِج", - "Below is the history from browser": "Below is history from this browser", + "Below is the history from browser": "تحته سِجِل هذا المتصفّح", "Welcome!": "أهلا بك!", "New note": "ملاحظة جديدة", "or": "أو", @@ -25,14 +25,14 @@ "Import history": "استيراد التاريخ", "Clear history": "مسح التاريخ", "Refresh history": "حدث التاريخ", - "No history": "No history", + "No history": "ليس هناك سِجِل", "Import from browser": "استيراد من المتصفح", "Releases": "إصدارات", "Are you sure?": "هل أنت واثق؟", "Do you really want to delete this note?": "هل تريد حقًا حذف هذه الملاحظة؟", "All users will lose their connection.": "سيفقد جميع المستخدمين اتصالهم.", "Cancel": "إلغاء", - "Yes, do it!": "Yes, do it!", + "Yes, do it!": "نعم ، قم بذلك!", "Choose method": "اختر الطريقة", "Sign in via %s": "لِج عبر %s", "New": "جديد", @@ -52,11 +52,11 @@ "Upload Image": "تحميل صورة", "Menu": "القائمة", "This page need refresh": "هذه الصفحة بحاجة إلى تحديث", - "You have an incompatible client version.": "نسخة العميل غير متوافقة ", + "You have an incompatible client version.": "نسخة عميلك غير متوافقة.", "Refresh to update.": "حدث الصفحة للحصول على التحديث", - "New version available!": "نسخة جديدة متوفرة", - "See releases notes here": "إطلع على ملاحضة الاصدار هنا ", - "Refresh to enjoy new features.": "حدث الصفحة لتستمتع بالمميزات الجديدة", + "New version available!": "نسخة جديدة متوفرة!", + "See releases notes here": "إطلع على ملاحظات الإصدار هنا", + "Refresh to enjoy new features.": "حدث الصفحة لتستمتع بالميزات الجديدة.", "Your user state has changed.": "لقد تغيرت حالة المستخدم الخاصة بك.", "Refresh to load new user state.": "قم بتحديث الصفحة لتحميل حالة المستخدم الجديدة.", "Refresh": "تحديث", @@ -71,7 +71,7 @@ "Cheatsheet": "Cheatsheet", "Example": "مثال", "Syntax": "Syntax", - "Header": "الترويسة", + "Header": "الرأسية", "Unordered List": "قائمة غير مرتبة", "Ordered List": "قائمة مرتبة", "Todo List": "Checklist", @@ -85,17 +85,17 @@ "Image": "صورة", "Code": "الشفرة", "Externals": "Externals", - "This is a alert area.": "This is an alert area.", + "This is a alert area.": "هذه منطقة تنبيه.", "Revert": "تراجع", "Import from clipboard": "استيراد من الحافظة", - "Paste your markdown or webpage here...": "Paste your markdown or webpage here…", + "Paste your markdown or webpage here...": "يرجى وضع ماركداون أو صفحة الويب الخاصة بك هنا…", "Clear": "امسح", "This note is locked": "هذه الملاحظة مقفلة", "Sorry, only owner can edit this note.": "آسف ، يمكن للمالك فقط تعديل هذه الملاحظة.", "OK": "حسنا", "Reach the limit": "Reach the limit", "Sorry, you've reached the max length this note can be.": "عذرًا ، لقد بلغت الحد الأقصى لطول هذه الملاحظة.", - "Please reduce the content or divide it to more notes, thank you!": "الرجاء قصر هذه الملاحظة", + "Please reduce the content or divide it to more notes, thank you!": "يرجى جعل هذه الملاحظة قصيرة.", "Import from Gist": "استيراد من Gist", "Paste your gist url here...": "الصق عنوان url الخاص بك هنا...", "Import from Snippet": "استرداد من سنيبت", @@ -105,7 +105,7 @@ "Export to Snippet": "صدر إلى سنيبت", "Select Visibility Level": "حدد مستوى الرؤية", "Night Theme": "الوضع الليلي", - "Follow us on %s and %s.": "تابِعنا على %s و %s.", + "Follow us on %s and %s.": "تابِعنا على %s و على %s.", "Privacy": "الخصوصية", "Terms of Use": "شروط الاستخدام", "Do you really want to delete your user account?": "هل تريد حقًا حذف حسابك؟", diff --git a/locales/fr.json b/locales/fr.json index 3dc434ae..ebda3c69 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -6,11 +6,11 @@ "History": "Historique", "New guest note": "Nouvelle note invité", "Collaborate with URL": "Collaborez avec l'URL", - "Support charts and MathJax": "Supporte les graphiques et MathJax", - "Support slide mode": "Supporte le mode présentation", + "Support charts and MathJax": "Gère les graphiques et MathJax", + "Support slide mode": "Gère le mode présentation", "Sign In": "Se connecter", "Below is the history from browser": "Ci-dessous, l'historique du navigateur", - "Welcome!": "Bienvenue !", + "Welcome!": "Bienvenue !", "New note": "Nouvelle note", "or": "ou", "Sign Out": "Se déconnecter", @@ -23,16 +23,16 @@ "Time": "Date", "Export history": "Exporter l'historique", "Import history": "Importer l'historique", - "Clear history": "Supprimer l'historique", + "Clear history": "Effacer l'historique", "Refresh history": "Actualiser l'historique", "No history": "Pas d'historique", "Import from browser": "Importer depuis le navigateur", "Releases": "Versions", - "Are you sure?": "Ëtes-vous sûr ?", - "Do you really want to delete this note?": "Voulez-vous vraiment supprimer cette note ?", + "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", - "Yes, do it!": "Oui, je suis sûr !", + "Yes, do it!": "Oui, je suis sûr !", "Choose method": "Choisir la méthode", "Sign in via %s": "Se connecter depuis %s", "New": "Nouvelle", @@ -45,16 +45,16 @@ "Clipboard": "Presse-papier", "Download": "Télécharger", "Raw HTML": "HTML brut", - "Edit": "Éditer", + "Edit": "Modifier", "View": "Voir", "Both": "Les deux", "Help": "Aide", - "Upload Image": "Uploader une image", + "Upload Image": "Téléverser une image", "Menu": "Menu", "This page need refresh": "Cette page doit être rechargée", "You have an incompatible client version.": "Vous avez une version client incompatible.", "Refresh to update.": "Recharger pour mettre à jour.", - "New version available!": "Nouvelle version disponible !", + "New version available!": "Nouvelle version disponible !", "See releases notes here": "Voir les commentaires de version ici", "Refresh to enjoy new features.": "Recharger pour bénéficier des nouvelles fonctionnalités.", "Your user state has changed.": "Votre statut utilisateur a changé.", @@ -63,7 +63,7 @@ "Contacts": "Contacts", "Report an issue": "Signaler un problème", "Meet us on %s": "Rencontrez-nous sur %s", - "Send us email": "Envoyez-nous un mail", + "Send us email": "Envoyez-nous un courriel", "Documents": "Documents", "Features": "Fonctionnalités", "YAML Metadata": "Métadonnées YAML", @@ -71,7 +71,7 @@ "Cheatsheet": "Pense-bête", "Example": "Exemple", "Syntax": "Syntaxe", - "Header": "En-tête", + "Header": "Entête", "Unordered List": "Liste à puce", "Ordered List": "List numérotée", "Todo List": "Liste de tâches", @@ -86,16 +86,16 @@ "Code": "Code", "Externals": "Externes", "This is a alert area.": "Ceci est un texte d'alerte.", - "Revert": "Revenir en arrière", + "Revert": "Annuler", "Import from clipboard": "Importer depuis le presse-papier", "Paste your markdown or webpage here...": "Collez votre markdown ou votre page web ici...", - "Clear": "Vider", + "Clear": "Effacer", "This note is locked": "Cette note est verrouillée", - "Sorry, only owner can edit this note.": "Désolé, seul le propriétaire peut éditer cette note.", + "Sorry, only owner can edit this note.": "Désolé, seul le propriétaire peut modifier cette note.", "OK": "OK", "Reach the limit": "Atteindre la limite", "Sorry, you've reached the max length this note can be.": "Désolé, vous avez atteint la longueur maximale que cette note peut avoir.", - "Please reduce the content or divide it to more notes, thank you!": "Merci de réduire le contenu ou de le diviser en plusieurs notes !", + "Please reduce the content or divide it to more notes, thank you!": "Merci de réduire le contenu ou de le diviser en plusieurs notes !", "Import from Gist": "Importer depuis Gist", "Paste your gist url here...": "Coller l'URL de votre Gist ici...", "Import from Snippet": "Importer depuis Snippet", @@ -108,15 +108,15 @@ "Follow us on %s and %s.": "Suivez-nous sur %s, et %s.", "Privacy": "Confidentialité", "Terms of Use": "Conditions d'utilisation", - "Do you really want to delete your user account?": "Voulez-vous vraiment supprimer votre compte utilisateur ?", + "Do you really want to delete your user account?": "Voulez-vous vraiment supprimer votre compte utilisateur ?", "This will delete your account, all notes that are owned by you and remove all references to your account from other notes.": "Cela supprimera votre compte, toutes les notes dont vous êtes propriétaire et supprimera toute référence à votre compte dans les autres notes.", "Delete user": "Supprimer l'utilisateur", "Export user data": "Exporter les données utilisateur", - "Help us translating on %s": "Aidez nous à traduire sur %s", + "Help us translating on %s": "Aidez-nous à traduire sur %s", "Source Code": "Code source", "Register": "S'enregistrer", "Powered by %s": "Propulsé par %s", - "Help us translating": "Aidez nous à traduire", + "Help us translating": "Aidez-nous à traduire", "Join the community": "Rejoignez la communauté", "Imprint": "Mentions légales" }
\ No newline at end of file diff --git a/package.json b/package.json index 7f78d14f..d5805cc4 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "i18n": "^0.8.3", "imgur": "git+https://github.com/hackmdio/node-imgur.git", "ionicons": "~2.0.1", - "jquery": "^3.4.1", + "jquery": "^3.5.0", "jquery-mousewheel": "^3.1.13", "jquery-ui": "^1.12.1", "js-cookie": "^2.1.3", diff --git a/public/css/slide.css b/public/css/slide.css index f8f9c717..0ba87cbb 100644 --- a/public/css/slide.css +++ b/public/css/slide.css @@ -255,8 +255,10 @@ pre.abc > svg { margin-bottom: -.25em !important; } -.reveal .slides, .reveal .backgrounds, .reveal.overview { - transform-style: preserve-3d; +@media only screen { + .reveal .slides, .reveal .backgrounds, .reveal.overview { + transform-style: preserve-3d; + } } .slides, #meta { @@ -5463,10 +5463,10 @@ jquery-ui@^1.12.1: resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.12.1.tgz#bcb4045c8dd0539c134bc1488cdd3e768a7a9e51" integrity sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE= -jquery@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" - integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== +jquery@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.0.tgz#9980b97d9e4194611c36530e7dc46a58d7340fc9" + integrity sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ== js-beautify@^1.8.8: version "1.10.3" @@ -6793,22 +6793,6 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-pre-gyp@*: - version "0.14.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83" - integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4.4.2" - node-pre-gyp@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054" @@ -9612,7 +9596,7 @@ tar-stream@^1.5.0: to-buffer "^1.1.1" xtend "^4.0.0" -tar@^4, tar@^4.4.2: +tar@^4: version "4.4.13" resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== |