mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-17 08:34:54 -04:00
Restructure editor user interface (#399)
* Replaced connection indicator in editor top bar with user-menu * Added basic layout of bottom document bar * Fixed margins between elements * Reorganized document-bar * Added dividers into toolbar * Move files from task-bar to document-bar and remove test file * moved connection-indicator components into its own folder * moved document bar to the top * moved connection-indicator once again * Change design Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * New idea for timestamps Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Add css Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Revert "Add css" This reverts commit 6780aa05 Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Revert "New idea for timestamps" This reverts commit bf2891e1 Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * split import / export * Made version input field to a common component * added read-only modal added document-time added placeholder text for permissions * remove flex-nowrap from editor toolbar Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Add codimd permission menu Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Move permission picker to the right Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * add use memo Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Add user-select-none to documenttime component Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * added status-bar * fixed status-bar * Add document info mock Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * changed published to share in i18n * reordered document bar moved share modal in it's own component * changed the divider color in the toolbar * Add details to document info Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Add pin mock button Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Restructure toolbar after rebase and extract EmojiPicker+Button into component Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Correct linue number output Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Add some space into status bar Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Cleanup code to make ESLint happy * Fix Toc button position * Added link to presentation mode button * Cache codemirror props Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Fix code blocks not being completely visible * Improve document info modal - The document info timeline always wrapped the received moment.js-object into a new moment.js object instead of directly using the given one. - The timestamps were configured to be displayed without suffix, but this is necessary to support valid translation grammar. - There was no margin between the icons and the texts. * Highlighted user name in document-info modal * Add avatar icon to document-info modal * Improved english translation of the share-info * Improve performance of copyable-fields by using useCallback * Add translation keys for pin-to-history button * Forwarded note title to editor-menu for deletion modal info * Add placeholders to translations Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * change translation Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Change permission dropdown to permission button Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Fix translations of emoji-picker and preferences * remove unused imports Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Add alt attribute Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Fix share button and i18n files Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Fix use of i18n keys Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Use modal-body Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * useCallback Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Use more specific i18n key Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Add a new entry and move i18n key for usercontribution Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Fix i18nkey für shareLink Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * remove unused i18nkey Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Rename component DocumentInfo to DocumentInfoButton Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Extract revision button code into own component Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * wrap buttons in navbar-nav Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * organize imports Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * organize imports Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Added editor-preferences modal * Added functionality to preferences modal * Activated search and replace feature in CodeMirror * pdf export unavailability notice (#403) * added pdf export unavailability notice with link to FAQ as many users ask all the time why this was removed and when they'll get it back, this seemed like a fine solution in the meantime. Co-authored-by: Erik Michelson <github@erik.michelson.eu> * Refactored editor-preferences to just use one generic select component * Fixed warnings regarding duplicated controlId and missing useCb-deps * Reorganized translation keys * Fixed i18n indentation for POEditor.com * Added translation key for 'avatar of ...' * Remove fragment Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Use user-avatar in document-info-line.tsx Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Revert changes in user-avatar and solve the problem otherwise Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Removed unnecessary import * Removed another unnecessary import * Refactored EditorPreferenceSelect to use enum and automatic type conversions * Remove unused CodeMirror reference * Fix spacing problem Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * Increate size of image Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> * fixed share-link's space around the copyable-field Co-authored-by: Philip Molares <philip.molares@udo.edu> Co-authored-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de> Co-authored-by: Philip Molares <git@molar.es>
This commit is contained in:
parent
62e870828c
commit
8377722e1a
69 changed files with 993 additions and 334 deletions
|
@ -124,10 +124,10 @@
|
||||||
"image": "صورة",
|
"image": "صورة",
|
||||||
"uploadImage": "تحميل صورة"
|
"uploadImage": "تحميل صورة"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "القائمة",
|
"menu": "القائمة",
|
||||||
"new": "جديد",
|
"new": "جديد",
|
||||||
"publish": "انشر",
|
"shareLink": "",
|
||||||
"extra": "إضافي",
|
"extra": "إضافي",
|
||||||
"revision": "مراجعة",
|
"revision": "مراجعة",
|
||||||
"slideMode": "نمط الشرائح التقديمية",
|
"slideMode": "نمط الشرائح التقديمية",
|
||||||
|
|
|
@ -124,10 +124,10 @@
|
||||||
"image": "Imatge",
|
"image": "Imatge",
|
||||||
"uploadImage": "Pujar imatge"
|
"uploadImage": "Pujar imatge"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Menú",
|
"menu": "Menú",
|
||||||
"new": "Nou",
|
"new": "Nou",
|
||||||
"publish": "Publicar",
|
"shareLink": "",
|
||||||
"extra": "Extra",
|
"extra": "Extra",
|
||||||
"revision": "Revisió",
|
"revision": "Revisió",
|
||||||
"slideMode": "Mode presentació",
|
"slideMode": "Mode presentació",
|
||||||
|
|
|
@ -124,10 +124,10 @@
|
||||||
"image": "Obrázek",
|
"image": "Obrázek",
|
||||||
"uploadImage": "Nahrát obrázek"
|
"uploadImage": "Nahrát obrázek"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Menu",
|
"menu": "Menu",
|
||||||
"new": "Nová",
|
"new": "Nová",
|
||||||
"publish": "Publikovat",
|
"shareLink": "",
|
||||||
"extra": "Extra",
|
"extra": "Extra",
|
||||||
"revision": "Revize",
|
"revision": "Revize",
|
||||||
"slideMode": "Režim prezentace",
|
"slideMode": "Režim prezentace",
|
||||||
|
|
|
@ -108,10 +108,10 @@
|
||||||
"image": "Billede",
|
"image": "Billede",
|
||||||
"uploadImage": "Upload billede"
|
"uploadImage": "Upload billede"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Menu",
|
"menu": "Menu",
|
||||||
"new": "Ny",
|
"new": "Ny",
|
||||||
"publish": "Publicér",
|
"shareLink": "",
|
||||||
"extra": "Ekstra",
|
"extra": "Ekstra",
|
||||||
"revision": "Revision",
|
"revision": "Revision",
|
||||||
"slideMode": "Præsentationstilstand",
|
"slideMode": "Præsentationstilstand",
|
||||||
|
@ -166,4 +166,4 @@
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,10 +124,10 @@
|
||||||
"image": "Foto",
|
"image": "Foto",
|
||||||
"uploadImage": "Foto hochladen"
|
"uploadImage": "Foto hochladen"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Menü",
|
"menu": "Menü",
|
||||||
"new": "Neu",
|
"new": "Neu",
|
||||||
"publish": "Veröffentlichen",
|
"shareLink": "Link teilen",
|
||||||
"extra": "Extra",
|
"extra": "Extra",
|
||||||
"revision": "Version",
|
"revision": "Version",
|
||||||
"slideMode": "Präsentationsmodus",
|
"slideMode": "Präsentationsmodus",
|
||||||
|
|
|
@ -108,10 +108,10 @@
|
||||||
"image": "Εικόνα",
|
"image": "Εικόνα",
|
||||||
"uploadImage": "Ανέβασμα φωτογραφίας"
|
"uploadImage": "Ανέβασμα φωτογραφίας"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Μενού",
|
"menu": "Μενού",
|
||||||
"new": "Νέο",
|
"new": "Νέο",
|
||||||
"publish": "Δημοσίευση",
|
"shareLink": "",
|
||||||
"extra": "Επιπλέον",
|
"extra": "Επιπλέον",
|
||||||
"revision": "Αναθεώρηση",
|
"revision": "Αναθεώρηση",
|
||||||
"slideMode": "Λειτουργία με σύρσιμο",
|
"slideMode": "Λειτουργία με σύρσιμο",
|
||||||
|
@ -166,4 +166,4 @@
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,32 +16,32 @@
|
||||||
"history": {
|
"history": {
|
||||||
"error": {
|
"error": {
|
||||||
"getHistory": {
|
"getHistory": {
|
||||||
"title": "Load History Error",
|
"title": "Load History Error",
|
||||||
"text": "While trying to load the history form the server an error occurred"
|
"text": "While trying to load the history form the server an error occurred"
|
||||||
},
|
},
|
||||||
"deleteHistory": {
|
"deleteHistory": {
|
||||||
"title": "Delete History Error",
|
"title": "Delete History Error",
|
||||||
"text": "While trying to delete the history on the server an error occurred"
|
"text": "While trying to delete the history on the server an error occurred"
|
||||||
},
|
},
|
||||||
"setHistory": {
|
"setHistory": {
|
||||||
"title": "Upload History Error",
|
"title": "Upload History Error",
|
||||||
"text": "While trying to upload the history to the server an error occurred"
|
"text": "While trying to upload the history to the server an error occurred"
|
||||||
},
|
},
|
||||||
"deleteNote": {
|
"deleteNote": {
|
||||||
"title": "Delete Note Error",
|
"title": "Delete Note Error",
|
||||||
"text": "While trying to delete a note on the server an error occurred"
|
"text": "While trying to delete a note on the server an error occurred"
|
||||||
},
|
},
|
||||||
"updateEntry": {
|
"updateEntry": {
|
||||||
"title": "Update History Entry Error",
|
"title": "Update History Entry Error",
|
||||||
"text": "While trying to update a history entry on the server an error occurred"
|
"text": "While trying to update a history entry on the server an error occurred"
|
||||||
},
|
},
|
||||||
"deleteEntry": {
|
"deleteEntry": {
|
||||||
"title": "Delete History Entry Error",
|
"title": "Delete History Entry Error",
|
||||||
"text": "While trying to delete a history entry on the server an error occurred"
|
"text": "While trying to delete a history entry on the server an error occurred"
|
||||||
},
|
},
|
||||||
"notFoundEntry": {
|
"notFoundEntry": {
|
||||||
"title": "History Entry not found",
|
"title": "History Entry not found",
|
||||||
"text": "We can't find the history entry you requested."
|
"text": "We can't find the history entry you requested."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"noHistory": "No history",
|
"noHistory": "No history",
|
||||||
|
@ -72,10 +72,10 @@
|
||||||
"tooNewVersion": "The file '{{fileName}}' comes from a newer client and can't be imported."
|
"tooNewVersion": "The file '{{fileName}}' comes from a newer client and can't be imported."
|
||||||
},
|
},
|
||||||
"removeNote": {
|
"removeNote": {
|
||||||
"title": "Remove note from history",
|
"title": "Remove note from history",
|
||||||
"question": "Do you really want to remove this note from your history?",
|
"question": "Do you really want to remove this note from your history?",
|
||||||
"warning": "This just removes the history entry and won't delete the note itself.",
|
"warning": "This just removes the history entry and won't delete the note itself.",
|
||||||
"button": "Remove note from history"
|
"button": "Remove note from history"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tableHeader": {
|
"tableHeader": {
|
||||||
|
@ -85,11 +85,11 @@
|
||||||
"lastVisit": "Last Visit"
|
"lastVisit": "Last Visit"
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"recentNotes": "Recent notes",
|
"recentNotes": "Recent notes",
|
||||||
"entryLocal": "Saved in your browser history",
|
"entryLocal": "Saved in your browser history",
|
||||||
"entryRemote": "Saved in your user history",
|
"entryRemote": "Saved in your user history",
|
||||||
"removeEntry": "Remove from history",
|
"removeEntry": "Remove from history",
|
||||||
"deleteNote": "Delete note"
|
"deleteNote": "Delete note"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"navigation": {
|
"navigation": {
|
||||||
|
@ -219,21 +219,41 @@
|
||||||
"uploadImage": "Upload Image",
|
"uploadImage": "Upload Image",
|
||||||
"table": "Table",
|
"table": "Table",
|
||||||
"line": "Horizontal line",
|
"line": "Horizontal line",
|
||||||
"comment": "Comment"
|
"comment": "Comment",
|
||||||
|
"preferences": "Editor settings",
|
||||||
|
"emoji": "Open emoji picker"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Menu",
|
"menu": "Menu",
|
||||||
|
"import": "Import",
|
||||||
|
"export": "Export",
|
||||||
"new": "New",
|
"new": "New",
|
||||||
"publish": "Publish",
|
"shareLink": "Share link",
|
||||||
"extra": "Extra",
|
"extra": "Extra",
|
||||||
"revision": "Revision",
|
"revision": "Revision",
|
||||||
"slideMode": "Slide Mode",
|
"slideMode": "Slide Mode",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
"help": "Help",
|
"help": "Help",
|
||||||
"deleteNote": "Delete note"
|
"deleteNote": "Delete note",
|
||||||
|
"permissions": "Permissions",
|
||||||
|
"documentInfo": "Document info",
|
||||||
|
"pinNoteToHistory": "Pin note to history",
|
||||||
|
"pinnedToHistory": "Pinned to history"
|
||||||
|
},
|
||||||
|
"statusBar": {
|
||||||
|
"cursor": "Line {{line}}, Columns {{columns}}",
|
||||||
|
"selection": {
|
||||||
|
"column": "Selected {{count}} columns",
|
||||||
|
"line": "Selected {{count}} lines"
|
||||||
|
},
|
||||||
|
"lines": "{{lines}} Lines",
|
||||||
|
"length": "Length {{length}}",
|
||||||
|
"lengthTooltip": "You can write up to 100000 characters in this document."
|
||||||
},
|
},
|
||||||
"export": {
|
"export": {
|
||||||
"rawHtml": "Raw HTML"
|
"rawHtml": "Raw HTML",
|
||||||
|
"pdf": "PDF export is unavailable.",
|
||||||
|
"why": "Why?"
|
||||||
},
|
},
|
||||||
"import": {
|
"import": {
|
||||||
"clipboard": "Clipboard"
|
"clipboard": "Clipboard"
|
||||||
|
@ -244,6 +264,13 @@
|
||||||
"selectProject": "Select From Available Projects",
|
"selectProject": "Select From Available Projects",
|
||||||
"selectSnippet": "Select From Available Snippets"
|
"selectSnippet": "Select From Available Snippets"
|
||||||
},
|
},
|
||||||
|
"documentInfo": {
|
||||||
|
"title": "Document info",
|
||||||
|
"created": "<0></0> created this note <1></1> ago",
|
||||||
|
"edited": "<0></0> was the last editor <1></1> ago",
|
||||||
|
"usersContributed": "<0></0> users contributed to this document",
|
||||||
|
"revisions": "<0></0> revisions are saved"
|
||||||
|
},
|
||||||
"gistImport": {
|
"gistImport": {
|
||||||
"title": "Import from Gist",
|
"title": "Import from Gist",
|
||||||
"insertGistUrl": "Paste your gist url here…"
|
"insertGistUrl": "Paste your gist url here…"
|
||||||
|
@ -265,10 +292,25 @@
|
||||||
"question": "Do you really want to delete this note?",
|
"question": "Do you really want to delete this note?",
|
||||||
"warning": "All users will lose their connection. This process is irreversible.",
|
"warning": "All users will lose their connection. This process is irreversible.",
|
||||||
"button": "Delete note"
|
"button": "Delete note"
|
||||||
|
},
|
||||||
|
"permissions": {
|
||||||
|
"title": "Permissions"
|
||||||
|
},
|
||||||
|
"shareLink": {
|
||||||
|
"title": "Share link",
|
||||||
|
"viewOnlyDescription": "This link points to a read-only version of this note. You can use this e.g. for feedback from friends and colleagues."
|
||||||
|
},
|
||||||
|
"preferences": {
|
||||||
|
"title": "Preferences",
|
||||||
|
"theme": "Editor theme",
|
||||||
|
"keyMap": "Keymap",
|
||||||
|
"indentWithTabs": "Tab character",
|
||||||
|
"indentUnit": "Tab size (when using spaces)",
|
||||||
|
"spellChecker": "Spell checking"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"embeddings": {
|
"embeddings": {
|
||||||
"clickToLoad": "Click to load"
|
"clickToLoad": "Click to load"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
|
@ -280,7 +322,8 @@
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"or": "or",
|
"or": "or",
|
||||||
"and": "and"
|
"and": "and",
|
||||||
|
"avatarOf": "avatar of '{{name}}'"
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"chooseMethod": "Choose method",
|
"chooseMethod": "Choose method",
|
||||||
|
@ -297,15 +340,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"register": {
|
"register": {
|
||||||
"title": "Register",
|
"title": "Register",
|
||||||
"passwordAgain": "Password (again)",
|
"passwordAgain": "Password (again)",
|
||||||
"usernameInfo": "The username is your unique identifier for login.",
|
"usernameInfo": "The username is your unique identifier for login.",
|
||||||
"passwordInfo": "Choose a unique and secure password. It must contain at least 8 characters.",
|
"passwordInfo": "Choose a unique and secure password. It must contain at least 8 characters.",
|
||||||
"infoTermsPrivacy": "With the registration of my user account I agree to the following terms:",
|
"infoTermsPrivacy": "With the registration of my user account I agree to the following terms:",
|
||||||
"error": {
|
"error": {
|
||||||
"usernameExisting": "There is already an account with this username.",
|
"usernameExisting": "There is already an account with this username.",
|
||||||
"other": "There was an error while registering your account. Just try it again."
|
"other": "There was an error while registering your account. Just try it again."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,10 +108,10 @@
|
||||||
"image": "Bildo",
|
"image": "Bildo",
|
||||||
"uploadImage": "Alŝutu bildon"
|
"uploadImage": "Alŝutu bildon"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Menuo",
|
"menu": "Menuo",
|
||||||
"new": "Nova",
|
"new": "Nova",
|
||||||
"publish": "Dissendu",
|
"shareLink": "",
|
||||||
"extra": "Plia",
|
"extra": "Plia",
|
||||||
"revision": "Versio",
|
"revision": "Versio",
|
||||||
"slideMode": "Bildvica modo",
|
"slideMode": "Bildvica modo",
|
||||||
|
@ -166,4 +166,4 @@
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,10 +124,10 @@
|
||||||
"image": "Imagen",
|
"image": "Imagen",
|
||||||
"uploadImage": "Subir imagen"
|
"uploadImage": "Subir imagen"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Menú",
|
"menu": "Menú",
|
||||||
"new": "Nuevo",
|
"new": "Nuevo",
|
||||||
"publish": "Publicar",
|
"shareLink": "",
|
||||||
"extra": "Extra",
|
"extra": "Extra",
|
||||||
"revision": "Revision",
|
"revision": "Revision",
|
||||||
"slideMode": "Modo presentación",
|
"slideMode": "Modo presentación",
|
||||||
|
|
|
@ -124,10 +124,10 @@
|
||||||
"image": "Image",
|
"image": "Image",
|
||||||
"uploadImage": "Téléverser une image"
|
"uploadImage": "Téléverser une image"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Menu",
|
"menu": "Menu",
|
||||||
"new": "Nouvelle",
|
"new": "Nouvelle",
|
||||||
"publish": "Publier",
|
"shareLink": "",
|
||||||
"extra": "Extra",
|
"extra": "Extra",
|
||||||
"revision": "Historique",
|
"revision": "Historique",
|
||||||
"slideMode": "Mode présentation",
|
"slideMode": "Mode présentation",
|
||||||
|
|
|
@ -108,10 +108,10 @@
|
||||||
"image": "तस्वीर",
|
"image": "तस्वीर",
|
||||||
"uploadImage": "तस्वीर डालिये"
|
"uploadImage": "तस्वीर डालिये"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "मेन्यू",
|
"menu": "मेन्यू",
|
||||||
"new": "नया",
|
"new": "नया",
|
||||||
"publish": "प्रकाशित करें",
|
"shareLink": "",
|
||||||
"extra": "अतिरिक्त",
|
"extra": "अतिरिक्त",
|
||||||
"revision": "संशोधन",
|
"revision": "संशोधन",
|
||||||
"slideMode": "स्लाइड मोड",
|
"slideMode": "स्लाइड मोड",
|
||||||
|
@ -166,4 +166,4 @@
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,10 +108,10 @@
|
||||||
"image": "Slika",
|
"image": "Slika",
|
||||||
"uploadImage": "Prenesi sliku"
|
"uploadImage": "Prenesi sliku"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Meni",
|
"menu": "Meni",
|
||||||
"new": "Novo",
|
"new": "Novo",
|
||||||
"publish": "Objavi",
|
"shareLink": "",
|
||||||
"extra": "Dodatno",
|
"extra": "Dodatno",
|
||||||
"revision": "Revizija",
|
"revision": "Revizija",
|
||||||
"slideMode": "Način slajda",
|
"slideMode": "Način slajda",
|
||||||
|
@ -166,4 +166,4 @@
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,10 +124,10 @@
|
||||||
"image": "Gambar",
|
"image": "Gambar",
|
||||||
"uploadImage": "Unggah Gambar"
|
"uploadImage": "Unggah Gambar"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Menu",
|
"menu": "Menu",
|
||||||
"new": "Baru",
|
"new": "Baru",
|
||||||
"publish": "Terbitkan",
|
"shareLink": "",
|
||||||
"extra": "Tambahan",
|
"extra": "Tambahan",
|
||||||
"revision": "Revisi",
|
"revision": "Revisi",
|
||||||
"slideMode": "Mode Slide",
|
"slideMode": "Mode Slide",
|
||||||
|
|
|
@ -124,10 +124,10 @@
|
||||||
"image": "Immagine",
|
"image": "Immagine",
|
||||||
"uploadImage": "Carica Immagine"
|
"uploadImage": "Carica Immagine"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Menu",
|
"menu": "Menu",
|
||||||
"new": "Nuovo",
|
"new": "Nuovo",
|
||||||
"publish": "Pubblica",
|
"shareLink": "",
|
||||||
"extra": "Extra",
|
"extra": "Extra",
|
||||||
"revision": "Revisione",
|
"revision": "Revisione",
|
||||||
"slideMode": "Modalità slide",
|
"slideMode": "Modalità slide",
|
||||||
|
|
|
@ -124,10 +124,10 @@
|
||||||
"image": "画像",
|
"image": "画像",
|
||||||
"uploadImage": "画像をアップロード"
|
"uploadImage": "画像をアップロード"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "メニュー",
|
"menu": "メニュー",
|
||||||
"new": "新規作成",
|
"new": "新規作成",
|
||||||
"publish": "公開する",
|
"shareLink": "",
|
||||||
"extra": "その他",
|
"extra": "その他",
|
||||||
"revision": "編集履歴",
|
"revision": "編集履歴",
|
||||||
"slideMode": "スライドモード",
|
"slideMode": "スライドモード",
|
||||||
|
|
|
@ -120,10 +120,10 @@
|
||||||
"image": "이미지",
|
"image": "이미지",
|
||||||
"uploadImage": "이미지 업로드"
|
"uploadImage": "이미지 업로드"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "메뉴",
|
"menu": "메뉴",
|
||||||
"new": "새",
|
"new": "새",
|
||||||
"publish": "공개하기",
|
"shareLink": "",
|
||||||
"extra": "추가",
|
"extra": "추가",
|
||||||
"revision": "기록",
|
"revision": "기록",
|
||||||
"slideMode": "슬라이드 모드",
|
"slideMode": "슬라이드 모드",
|
||||||
|
|
|
@ -124,10 +124,10 @@
|
||||||
"image": "Afbeelding",
|
"image": "Afbeelding",
|
||||||
"uploadImage": "Afbeelding uploaden"
|
"uploadImage": "Afbeelding uploaden"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Menu",
|
"menu": "Menu",
|
||||||
"new": "Nieuw",
|
"new": "Nieuw",
|
||||||
"publish": "Publiceren",
|
"shareLink": "",
|
||||||
"extra": "Extra",
|
"extra": "Extra",
|
||||||
"revision": "Versie",
|
"revision": "Versie",
|
||||||
"slideMode": "Presentatiemodus",
|
"slideMode": "Presentatiemodus",
|
||||||
|
|
|
@ -124,10 +124,10 @@
|
||||||
"image": "Zdjęcie",
|
"image": "Zdjęcie",
|
||||||
"uploadImage": "Prześlij zdjęcie"
|
"uploadImage": "Prześlij zdjęcie"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Menu",
|
"menu": "Menu",
|
||||||
"new": "Nowy",
|
"new": "Nowy",
|
||||||
"publish": "Publikuj",
|
"shareLink": "",
|
||||||
"extra": "Ekstra",
|
"extra": "Ekstra",
|
||||||
"revision": "Korekta",
|
"revision": "Korekta",
|
||||||
"slideMode": "Tryb slajdów",
|
"slideMode": "Tryb slajdów",
|
||||||
|
|
|
@ -124,10 +124,10 @@
|
||||||
"image": "Imagem",
|
"image": "Imagem",
|
||||||
"uploadImage": "Carregar Imagem"
|
"uploadImage": "Carregar Imagem"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Menu",
|
"menu": "Menu",
|
||||||
"new": "Novo",
|
"new": "Novo",
|
||||||
"publish": "Publicar",
|
"shareLink": "",
|
||||||
"extra": "Extra",
|
"extra": "Extra",
|
||||||
"revision": "Revisão",
|
"revision": "Revisão",
|
||||||
"slideMode": "Modo Apresentação",
|
"slideMode": "Modo Apresentação",
|
||||||
|
|
|
@ -124,10 +124,10 @@
|
||||||
"image": "Изображение",
|
"image": "Изображение",
|
||||||
"uploadImage": "Загрузить изображение"
|
"uploadImage": "Загрузить изображение"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Меню",
|
"menu": "Меню",
|
||||||
"new": "Новая",
|
"new": "Новая",
|
||||||
"publish": "Опубликовать",
|
"shareLink": "",
|
||||||
"extra": "Дополнительно",
|
"extra": "Дополнительно",
|
||||||
"revision": "Изменения",
|
"revision": "Изменения",
|
||||||
"slideMode": "Режим слайдера",
|
"slideMode": "Режим слайдера",
|
||||||
|
|
|
@ -124,10 +124,10 @@
|
||||||
"image": "Obrázok",
|
"image": "Obrázok",
|
||||||
"uploadImage": "Nahrať obrázok"
|
"uploadImage": "Nahrať obrázok"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Menu",
|
"menu": "Menu",
|
||||||
"new": "Nová",
|
"new": "Nová",
|
||||||
"publish": "Publikovať",
|
"shareLink": "",
|
||||||
"extra": "Extra",
|
"extra": "Extra",
|
||||||
"revision": "Revízia",
|
"revision": "Revízia",
|
||||||
"slideMode": "Prezentačný režim",
|
"slideMode": "Prezentačný režim",
|
||||||
|
|
|
@ -123,10 +123,10 @@
|
||||||
"image": "Слика",
|
"image": "Слика",
|
||||||
"uploadImage": "Пошаљи слику"
|
"uploadImage": "Пошаљи слику"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Мени",
|
"menu": "Мени",
|
||||||
"new": "Ново",
|
"new": "Ново",
|
||||||
"publish": "Објави",
|
"shareLink": "",
|
||||||
"extra": "Додатно",
|
"extra": "Додатно",
|
||||||
"revision": "Ревизија",
|
"revision": "Ревизија",
|
||||||
"slideMode": "Презентациони мод",
|
"slideMode": "Презентациони мод",
|
||||||
|
|
|
@ -124,10 +124,10 @@
|
||||||
"image": "Bild",
|
"image": "Bild",
|
||||||
"uploadImage": "Ladda upp bilder"
|
"uploadImage": "Ladda upp bilder"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Meny",
|
"menu": "Meny",
|
||||||
"new": "Ny",
|
"new": "Ny",
|
||||||
"publish": "Publicera",
|
"shareLink": "",
|
||||||
"extra": "Extra",
|
"extra": "Extra",
|
||||||
"revision": "Revision",
|
"revision": "Revision",
|
||||||
"slideMode": "Slide Mode",
|
"slideMode": "Slide Mode",
|
||||||
|
|
|
@ -108,10 +108,10 @@
|
||||||
"image": "Resim",
|
"image": "Resim",
|
||||||
"uploadImage": "Resim Yükle"
|
"uploadImage": "Resim Yükle"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Menü",
|
"menu": "Menü",
|
||||||
"new": "Yeni",
|
"new": "Yeni",
|
||||||
"publish": "Yayınla",
|
"shareLink": "",
|
||||||
"extra": "Ekstra",
|
"extra": "Ekstra",
|
||||||
"revision": "Sürüm",
|
"revision": "Sürüm",
|
||||||
"slideMode": "Slayt Modu",
|
"slideMode": "Slayt Modu",
|
||||||
|
@ -166,4 +166,4 @@
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,10 +108,10 @@
|
||||||
"image": "Зображення",
|
"image": "Зображення",
|
||||||
"uploadImage": "Завантажити зображення"
|
"uploadImage": "Завантажити зображення"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Меню",
|
"menu": "Меню",
|
||||||
"new": "Нова",
|
"new": "Нова",
|
||||||
"publish": "Опублікувати",
|
"shareLink": "",
|
||||||
"extra": "Дотатково",
|
"extra": "Дотатково",
|
||||||
"revision": "Ревізія",
|
"revision": "Ревізія",
|
||||||
"slideMode": "Режим слайдера",
|
"slideMode": "Режим слайдера",
|
||||||
|
@ -166,4 +166,4 @@
|
||||||
"error": {}
|
"error": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,10 +123,10 @@
|
||||||
"image": "Ảnh",
|
"image": "Ảnh",
|
||||||
"uploadImage": "Tải ảnh lên"
|
"uploadImage": "Tải ảnh lên"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "Menu",
|
"menu": "Menu",
|
||||||
"new": "Mới",
|
"new": "Mới",
|
||||||
"publish": "Xuất bản",
|
"shareLink": "",
|
||||||
"extra": "Extra",
|
"extra": "Extra",
|
||||||
"revision": "Sửa đổi",
|
"revision": "Sửa đổi",
|
||||||
"slideMode": "Chế độ slide",
|
"slideMode": "Chế độ slide",
|
||||||
|
|
|
@ -124,10 +124,10 @@
|
||||||
"image": "图片",
|
"image": "图片",
|
||||||
"uploadImage": "上传图片"
|
"uploadImage": "上传图片"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "菜单",
|
"menu": "菜单",
|
||||||
"new": "新建",
|
"new": "新建",
|
||||||
"publish": "发表",
|
"shareLink": "",
|
||||||
"extra": "附加功能",
|
"extra": "附加功能",
|
||||||
"revision": "修订版本",
|
"revision": "修订版本",
|
||||||
"slideMode": "幻灯模式",
|
"slideMode": "幻灯模式",
|
||||||
|
|
|
@ -124,10 +124,10 @@
|
||||||
"image": "圖片",
|
"image": "圖片",
|
||||||
"uploadImage": "上傳圖片"
|
"uploadImage": "上傳圖片"
|
||||||
},
|
},
|
||||||
"menu": {
|
"documentBar": {
|
||||||
"menu": "選單",
|
"menu": "選單",
|
||||||
"new": "新增",
|
"new": "新增",
|
||||||
"publish": "發表",
|
"shareLink": "",
|
||||||
"extra": "增益",
|
"extra": "增益",
|
||||||
"revision": "修訂版本",
|
"revision": "修訂版本",
|
||||||
"slideMode": "簡報模式",
|
"slideMode": "簡報模式",
|
||||||
|
|
|
@ -1,40 +1,48 @@
|
||||||
import React, { Fragment, useRef, useState } from 'react'
|
import React, { Fragment, useCallback, useRef, useState } from 'react'
|
||||||
import { Button, FormControl, InputGroup, Overlay, Tooltip } from 'react-bootstrap'
|
import { Button, FormControl, InputGroup, Overlay, Tooltip } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
|
||||||
|
|
||||||
export interface VersionInputFieldProps {
|
export interface CopyableFieldProps {
|
||||||
version: string
|
content: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VersionInputField: React.FC<VersionInputFieldProps> = ({ version }) => {
|
export const CopyableField: React.FC<CopyableFieldProps> = ({ content }) => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const inputField = useRef<HTMLInputElement>(null)
|
const inputField = useRef<HTMLInputElement>(null)
|
||||||
const [showCopiedTooltip, setShowCopiedTooltip] = useState(false)
|
const [showCopiedTooltip, setShowCopiedTooltip] = useState(false)
|
||||||
|
|
||||||
const copyToClipboard = (content: string) => {
|
const copyToClipboard = useCallback((content: string) => {
|
||||||
navigator.clipboard.writeText(content).then(() => {
|
navigator.clipboard.writeText(content).then(() => {
|
||||||
setShowCopiedTooltip(true)
|
setShowCopiedTooltip(true)
|
||||||
setTimeout(() => { setShowCopiedTooltip(false) }, 2000)
|
setTimeout(() => { setShowCopiedTooltip(false) }, 2000)
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
console.error("couldn't copy")
|
console.error("couldn't copy")
|
||||||
})
|
})
|
||||||
}
|
}, [])
|
||||||
|
|
||||||
|
const selectContent = useCallback(() => {
|
||||||
|
if (!inputField.current) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
inputField.current.focus()
|
||||||
|
inputField.current.setSelectionRange(0, inputField.current.value.length)
|
||||||
|
}, [inputField])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Overlay target={inputField} show={showCopiedTooltip} placement="top">
|
<Overlay target={inputField} show={showCopiedTooltip} placement="top">
|
||||||
{(props) => (
|
{(props) => (
|
||||||
<Tooltip id={'copied_' + version} {...props}>
|
<Tooltip id={'copied_' + content} {...props}>
|
||||||
<Trans i18nKey={'landing.versionInfo.successfullyCopied'}/>
|
<Trans i18nKey={'landing.versionInfo.successfullyCopied'}/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</Overlay>
|
</Overlay>
|
||||||
|
|
||||||
<InputGroup className="mb-3">
|
<InputGroup className="my-3">
|
||||||
<FormControl readOnly={true} ref={inputField} className={'text-center'} value={version} />
|
<FormControl readOnly={true} ref={inputField} className={'text-center'} value={content} onMouseEnter={selectContent} />
|
||||||
<InputGroup.Append>
|
<InputGroup.Append>
|
||||||
<Button variant="outline-secondary" onClick={() => copyToClipboard(version)} title={'Copy'}>
|
<Button variant="outline-secondary" onClick={() => copyToClipboard(content)} title={'Copy'}>
|
||||||
<ForkAwesomeIcon icon='files-o'/>
|
<ForkAwesomeIcon icon='files-o'/>
|
||||||
</Button>
|
</Button>
|
||||||
</InputGroup.Append>
|
</InputGroup.Append>
|
|
@ -2,8 +2,8 @@ import React from 'react'
|
||||||
import './active-indicator.scss'
|
import './active-indicator.scss'
|
||||||
|
|
||||||
export enum ActiveIndicatorStatus {
|
export enum ActiveIndicatorStatus {
|
||||||
ACTIVE ='active',
|
ACTIVE = 'active',
|
||||||
INACTIVE ='inactive'
|
INACTIVE = 'inactive'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ActiveIndicatorProps {
|
export interface ActiveIndicatorProps {
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Dropdown } from 'react-bootstrap'
|
import { Dropdown } from 'react-bootstrap'
|
||||||
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
||||||
import { ActiveIndicatorStatus } from './active-indicator'
|
import { ActiveIndicatorStatus } from './active-indicator'
|
||||||
import './connection-indicator.scss'
|
import './connection-indicator.scss'
|
||||||
import { UserLine } from './user-line'
|
import { UserLine } from './user-line'
|
||||||
|
@ -8,9 +8,9 @@ import { UserLine } from './user-line'
|
||||||
const ConnectionIndicator: React.FC = () => {
|
const ConnectionIndicator: React.FC = () => {
|
||||||
const userOnline = 2
|
const userOnline = 2
|
||||||
return (
|
return (
|
||||||
<Dropdown className="small" alignRight>
|
<Dropdown className="small mx-2" alignRight>
|
||||||
<Dropdown.Toggle id="connection-indicator" size="sm" variant="primary" className="upper-case">
|
<Dropdown.Toggle id="connection-indicator" size="sm" variant="primary" className="upper-case">
|
||||||
<ForkAwesomeIcon icon="users"/> {userOnline} Online
|
<ForkAwesomeIcon icon="users" className={'mr-1'}/> {userOnline} Online
|
||||||
</Dropdown.Toggle>
|
</Dropdown.Toggle>
|
||||||
<Dropdown.Menu>
|
<Dropdown.Menu>
|
||||||
<Dropdown.Item disabled={true} className="d-flex align-items-center p-0">
|
<Dropdown.Item disabled={true} className="d-flex align-items-center p-0">
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { Fragment } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { UserAvatar } from '../../landing/layout/user-avatar/user-avatar'
|
import { UserAvatar } from '../../../landing/layout/user-avatar/user-avatar'
|
||||||
import { ActiveIndicator, ActiveIndicatorStatus } from './active-indicator'
|
import { ActiveIndicator, ActiveIndicatorStatus } from './active-indicator'
|
||||||
import './user-line.scss'
|
import './user-line.scss'
|
||||||
|
|
37
src/components/editor/document-bar/document-bar.tsx
Normal file
37
src/components/editor/document-bar/document-bar.tsx
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { ConnectionIndicator } from './connection-indicator/connection-indicator'
|
||||||
|
import { DocumentInfoButton } from './document-info-button'
|
||||||
|
import { EditorMenu } from './editor-menu'
|
||||||
|
import { ExportMenu } from './export-menu'
|
||||||
|
import { ImportMenu } from './import-menu'
|
||||||
|
import { PermissionButton } from './permission-button'
|
||||||
|
import { PinToHistoryButton } from './pin-to-history-button'
|
||||||
|
import { ShareLinkButton } from './share-link-button'
|
||||||
|
import { RevisionButton } from './revision-button'
|
||||||
|
|
||||||
|
export interface DocumentBarProps {
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DocumentBar: React.FC<DocumentBarProps> = ({ title }) => {
|
||||||
|
useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'navbar navbar-expand navbar-light bg-light'}>
|
||||||
|
<div className="navbar-nav">
|
||||||
|
<ShareLinkButton/>
|
||||||
|
<DocumentInfoButton/>
|
||||||
|
<RevisionButton/>
|
||||||
|
<PinToHistoryButton/>
|
||||||
|
<PermissionButton/>
|
||||||
|
</div>
|
||||||
|
<div className="ml-auto navbar-nav">
|
||||||
|
<ImportMenu/>
|
||||||
|
<ExportMenu/>
|
||||||
|
<EditorMenu noteTitle={title}/>
|
||||||
|
<ConnectionIndicator/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
61
src/components/editor/document-bar/document-info-button.tsx
Normal file
61
src/components/editor/document-bar/document-info-button.tsx
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import moment from 'moment'
|
||||||
|
import React, { Fragment, useState } from 'react'
|
||||||
|
import { Button, ListGroup, Modal } from 'react-bootstrap'
|
||||||
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||||
|
import { CommonModal } from '../../common/modals/common-modal'
|
||||||
|
import { DocumentInfoLine } from './document-info-line'
|
||||||
|
import { DocumentInfoLineWithTimeMode, DocumentInfoTimeLine } from './document-info-time-line'
|
||||||
|
import { UnitalicBoldText } from './document-info-time-line-helper/unitalic-bold-text'
|
||||||
|
|
||||||
|
export const DocumentInfoButton: React.FC = () => {
|
||||||
|
const [showModal, setShowModal] = useState(false)
|
||||||
|
useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<Button variant={'light'} className={'mx-1'} size={'sm'} onClick={() => setShowModal(true)}>
|
||||||
|
<ForkAwesomeIcon icon={'line-chart'} className={'mx-1'}/>
|
||||||
|
<Trans i18nKey={'editor.documentBar.documentInfo'}/>
|
||||||
|
</Button>
|
||||||
|
<CommonModal
|
||||||
|
show={showModal}
|
||||||
|
onHide={() => setShowModal(false)}
|
||||||
|
closeButton={true}
|
||||||
|
titleI18nKey={'editor.modal.documentInfo.title'}>
|
||||||
|
<Modal.Body>
|
||||||
|
<ListGroup>
|
||||||
|
<ListGroup.Item>
|
||||||
|
<DocumentInfoTimeLine
|
||||||
|
mode={DocumentInfoLineWithTimeMode.CREATED}
|
||||||
|
time={ moment().subtract(11, 'days') }
|
||||||
|
userName={'Tilman'}
|
||||||
|
profileImageSrc={'https://1.gravatar.com/avatar/767fc9c115a1b989744c755db47feb60?s=200&r=pg&d=mp'}/>
|
||||||
|
</ListGroup.Item>
|
||||||
|
<ListGroup.Item>
|
||||||
|
<DocumentInfoTimeLine
|
||||||
|
mode={DocumentInfoLineWithTimeMode.EDITED}
|
||||||
|
time={ moment().subtract(3, 'minutes') }
|
||||||
|
userName={'Philip'}
|
||||||
|
profileImageSrc={'https://1.gravatar.com/avatar/767fc9c115a1b989744c755db47feb60?s=200&r=pg&d=mp'}/>
|
||||||
|
</ListGroup.Item>
|
||||||
|
<ListGroup.Item>
|
||||||
|
<DocumentInfoLine icon={'users'}>
|
||||||
|
<Trans i18nKey='editor.modal.documentInfo.usersContributed'>
|
||||||
|
<UnitalicBoldText text={'42'}/>
|
||||||
|
</Trans>
|
||||||
|
</DocumentInfoLine>
|
||||||
|
</ListGroup.Item>
|
||||||
|
<ListGroup.Item>
|
||||||
|
<DocumentInfoLine icon={'history'}>
|
||||||
|
<Trans i18nKey='editor.modal.documentInfo.revisions'>
|
||||||
|
<UnitalicBoldText text={'192'}/>
|
||||||
|
</Trans>
|
||||||
|
</DocumentInfoLine>
|
||||||
|
</ListGroup.Item>
|
||||||
|
</ListGroup>
|
||||||
|
</Modal.Body>
|
||||||
|
</CommonModal>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
17
src/components/editor/document-bar/document-info-line.tsx
Normal file
17
src/components/editor/document-bar/document-info-line.tsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { ForkAwesomeIcon, IconName } from '../../common/fork-awesome/fork-awesome-icon'
|
||||||
|
|
||||||
|
export interface DocumentInfoLineProps {
|
||||||
|
icon: IconName
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DocumentInfoLine: React.FC<DocumentInfoLineProps> = ({ icon, children }) => {
|
||||||
|
return (
|
||||||
|
<span className={'d-flex align-items-center'}>
|
||||||
|
<ForkAwesomeIcon icon={icon} size={'2x'} fixedWidth={true} className={'mx-2'}/>
|
||||||
|
<i className={'d-flex align-items-center'}>
|
||||||
|
{children}
|
||||||
|
</i>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Moment } from 'moment'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export interface ItalicTime {
|
||||||
|
time: Moment
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TimeFromNow: React.FC<ItalicTime> = ({ time }) => {
|
||||||
|
return (
|
||||||
|
<time className={'mx-1'} title={time.format('LLLL')} dateTime={time.format()}>{time.fromNow(true)}</time>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
.font-style-normal {
|
||||||
|
font-style: normal;
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import React from 'react'
|
||||||
|
import './unitalic-bold-text.scss'
|
||||||
|
|
||||||
|
export interface BoldTextProps {
|
||||||
|
text: string ;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UnitalicBoldText: React.FC<BoldTextProps> = ({ text }) => {
|
||||||
|
return <b className={'font-style-normal mr-1'}>{text}</b>
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
.document-info-avatar img {
|
||||||
|
height: 30px;
|
||||||
|
width: 30px;
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { Moment } from 'moment'
|
||||||
|
import React from 'react'
|
||||||
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
import { IconName } from '../../common/fork-awesome/fork-awesome-icon'
|
||||||
|
import { DocumentInfoLine } from './document-info-line'
|
||||||
|
import './document-info-time-line.scss'
|
||||||
|
import { TimeFromNow } from './document-info-time-line-helper/time-from-now'
|
||||||
|
import { UserAvatar } from '../../landing/layout/user-avatar/user-avatar'
|
||||||
|
|
||||||
|
export interface DocumentInfoLineWithTimeProps {
|
||||||
|
time: Moment,
|
||||||
|
mode: DocumentInfoLineWithTimeMode
|
||||||
|
userName: string
|
||||||
|
profileImageSrc: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DocumentInfoLineWithTimeMode {
|
||||||
|
CREATED,
|
||||||
|
EDITED
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DocumentInfoTimeLine: React.FC<DocumentInfoLineWithTimeProps> = ({ time, mode, userName, profileImageSrc }) => {
|
||||||
|
useTranslation()
|
||||||
|
|
||||||
|
const i18nKey = mode === DocumentInfoLineWithTimeMode.CREATED ? 'editor.modal.documentInfo.created' : 'editor.modal.documentInfo.edited'
|
||||||
|
const icon: IconName = mode === DocumentInfoLineWithTimeMode.CREATED ? 'plus' : 'pencil'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DocumentInfoLine icon={icon}>
|
||||||
|
<Trans i18nKey={i18nKey} >
|
||||||
|
<UserAvatar photo={profileImageSrc} additionalClasses={'document-info-avatar font-style-normal bold font-weight-bold'} name={userName}/>
|
||||||
|
<TimeFromNow time={time}/>
|
||||||
|
</Trans>
|
||||||
|
</DocumentInfoLine>
|
||||||
|
)
|
||||||
|
}
|
34
src/components/editor/document-bar/editor-menu.tsx
Normal file
34
src/components/editor/document-bar/editor-menu.tsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { Dropdown } from 'react-bootstrap'
|
||||||
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
import { DropdownItemWithDeletionModal } from '../../landing/pages/history/common/entry-menu/dropdown-item-with-deletion-modal'
|
||||||
|
|
||||||
|
export interface EditorMenuProps {
|
||||||
|
noteTitle: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EditorMenu: React.FC<EditorMenuProps> = ({ noteTitle }) => {
|
||||||
|
useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown className={'small mx-1'} alignRight={true}>
|
||||||
|
<Dropdown.Toggle variant='light' size='sm' id='editor-menu'>
|
||||||
|
<Trans i18nKey={'editor.documentBar.menu'}/>
|
||||||
|
</Dropdown.Toggle>
|
||||||
|
|
||||||
|
<Dropdown.Menu>
|
||||||
|
<DropdownItemWithDeletionModal
|
||||||
|
itemI18nKey={'landing.history.menu.deleteNote'}
|
||||||
|
modalButtonI18nKey={'editor.modal.deleteNote.button'}
|
||||||
|
modalIcon={'trash'}
|
||||||
|
modalQuestionI18nKey={'editor.modal.deleteNote.question'}
|
||||||
|
modalTitleI18nKey={'editor.modal.deleteNote.title'}
|
||||||
|
modalWarningI18nKey={'editor.modal.deleteNote.warning'}
|
||||||
|
noteTitle={noteTitle}
|
||||||
|
className={'small'}
|
||||||
|
onConfirm={() => console.log('deleted')}/>
|
||||||
|
|
||||||
|
</Dropdown.Menu>
|
||||||
|
</Dropdown>
|
||||||
|
)
|
||||||
|
}
|
59
src/components/editor/document-bar/export-menu.tsx
Normal file
59
src/components/editor/document-bar/export-menu.tsx
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { Dropdown } from 'react-bootstrap'
|
||||||
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||||
|
import { TranslatedExternalLink } from '../../common/links/translated-external-link'
|
||||||
|
|
||||||
|
const ExportMenu: React.FC = () => {
|
||||||
|
useTranslation()
|
||||||
|
return (
|
||||||
|
<Dropdown className='small mx-1' alignRight={true}>
|
||||||
|
<Dropdown.Toggle variant='light' size='sm' id='editor-menu-export' className=''>
|
||||||
|
<Trans i18nKey='editor.documentBar.export'/>
|
||||||
|
</Dropdown.Toggle>
|
||||||
|
|
||||||
|
<Dropdown.Menu>
|
||||||
|
<Dropdown.Header>
|
||||||
|
<Trans i18nKey='common.export'/>
|
||||||
|
</Dropdown.Header>
|
||||||
|
<Dropdown.Item className='small'>
|
||||||
|
<ForkAwesomeIcon icon='dropbox' className={'mx-2'}/>
|
||||||
|
Dropbox
|
||||||
|
</Dropdown.Item>
|
||||||
|
<Dropdown.Item className='small'>
|
||||||
|
<ForkAwesomeIcon icon='github' className={'mx-2'}/>
|
||||||
|
Gist
|
||||||
|
</Dropdown.Item>
|
||||||
|
|
||||||
|
<Dropdown.Divider/>
|
||||||
|
|
||||||
|
<Dropdown.Header>
|
||||||
|
<Trans i18nKey='editor.documentBar.download'/>
|
||||||
|
</Dropdown.Header>
|
||||||
|
<Dropdown.Item className='small'>
|
||||||
|
<ForkAwesomeIcon icon='file-text' className={'mx-2'}/>
|
||||||
|
Markdown
|
||||||
|
</Dropdown.Item>
|
||||||
|
<Dropdown.Item className='small'>
|
||||||
|
<ForkAwesomeIcon icon='file-code-o' className={'mx-2'}/>
|
||||||
|
HTML
|
||||||
|
</Dropdown.Item>
|
||||||
|
<Dropdown.Item className='small'>
|
||||||
|
<ForkAwesomeIcon icon='file-code-o' className={'mx-2'}/>
|
||||||
|
<Trans i18nKey='editor.export.rawHtml'/>
|
||||||
|
</Dropdown.Item>
|
||||||
|
|
||||||
|
<Dropdown.Divider/>
|
||||||
|
|
||||||
|
<Dropdown.Item className='small text-muted' dir={'auto'}>
|
||||||
|
<ForkAwesomeIcon icon='file-pdf-o' className={'mx-2'}/>
|
||||||
|
<Trans i18nKey={'editor.export.pdf'}/>
|
||||||
|
|
||||||
|
<TranslatedExternalLink i18nKey={'editor.export.why'} href={'https://community.codimd.org/t/frequently-asked-questions/190'} className={'text-primary'}/>
|
||||||
|
</Dropdown.Item>
|
||||||
|
</Dropdown.Menu>
|
||||||
|
</Dropdown>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ExportMenu }
|
29
src/components/editor/document-bar/import-menu.tsx
Normal file
29
src/components/editor/document-bar/import-menu.tsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { Dropdown } from 'react-bootstrap'
|
||||||
|
import { Trans } from 'react-i18next'
|
||||||
|
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||||
|
|
||||||
|
export const ImportMenu: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Dropdown className='small mx-1' alignRight={true}>
|
||||||
|
<Dropdown.Toggle variant='light' size='sm' id='editor-menu-import' className=''>
|
||||||
|
<Trans i18nKey='editor.documentBar.import'/>
|
||||||
|
</Dropdown.Toggle>
|
||||||
|
|
||||||
|
<Dropdown.Menu>
|
||||||
|
<Dropdown.Item className='small'>
|
||||||
|
<ForkAwesomeIcon icon='dropbox' className={'mx-2'}/>
|
||||||
|
Dropbox
|
||||||
|
</Dropdown.Item>
|
||||||
|
<Dropdown.Item className='small'>
|
||||||
|
<ForkAwesomeIcon icon='github' className={'mx-2'}/>
|
||||||
|
Gist
|
||||||
|
</Dropdown.Item>
|
||||||
|
<Dropdown.Item className='small'>
|
||||||
|
<ForkAwesomeIcon icon='clipboard' className={'mx-2'}/>
|
||||||
|
<Trans i18nKey='editor.import.clipboard'/>
|
||||||
|
</Dropdown.Item>
|
||||||
|
</Dropdown.Menu>
|
||||||
|
</Dropdown>
|
||||||
|
)
|
||||||
|
}
|
27
src/components/editor/document-bar/permission-button.tsx
Normal file
27
src/components/editor/document-bar/permission-button.tsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import React, { Fragment, useState } from 'react'
|
||||||
|
import { Trans } from 'react-i18next'
|
||||||
|
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||||
|
import { Button, Modal } from 'react-bootstrap'
|
||||||
|
import { CommonModal } from '../../common/modals/common-modal'
|
||||||
|
|
||||||
|
export const PermissionButton: React.FC = () => {
|
||||||
|
const [showReadOnly, setShowReadOnly] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<Button variant={'light'} className={'mx-1'} size={'sm'} onClick={() => setShowReadOnly(true)}>
|
||||||
|
<ForkAwesomeIcon icon={'lock'} className={'mx-1'}/>
|
||||||
|
<Trans i18nKey={'editor.documentBar.permissions'}/>
|
||||||
|
</Button>
|
||||||
|
<CommonModal
|
||||||
|
show={showReadOnly}
|
||||||
|
onHide={() => setShowReadOnly(false)}
|
||||||
|
closeButton={true}
|
||||||
|
titleI18nKey={'editor.modal.permissions.title'}>
|
||||||
|
<Modal.Body>
|
||||||
|
<img className={'w-100'} src={'https://thumbs.gfycat.com/ImpassionedDeliriousIndianpalmsquirrel-size_restricted.gif'} alt={'Placeholder'}/>
|
||||||
|
</Modal.Body>
|
||||||
|
</CommonModal>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
18
src/components/editor/document-bar/pin-to-history-button.tsx
Normal file
18
src/components/editor/document-bar/pin-to-history-button.tsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { Button } from 'react-bootstrap'
|
||||||
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||||
|
|
||||||
|
export const PinToHistoryButton: React.FC = () => {
|
||||||
|
useTranslation()
|
||||||
|
|
||||||
|
const isPinned = true
|
||||||
|
const i18nKey = isPinned ? 'editor.documentBar.pinNoteToHistory' : 'editor.documentBar.pinnedToHistory'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button variant={'light'} className={'mx-1'} size={'sm'}>
|
||||||
|
<ForkAwesomeIcon icon={'thumb-tack'} className={'mx-1'}/>
|
||||||
|
<Trans i18nKey={i18nKey}/>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
13
src/components/editor/document-bar/revision-button.tsx
Normal file
13
src/components/editor/document-bar/revision-button.tsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { Button } from 'react-bootstrap'
|
||||||
|
import { Trans } from 'react-i18next'
|
||||||
|
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||||
|
|
||||||
|
export const RevisionButton: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Button variant={'light'} className={'mx-1'} size={'sm'}>
|
||||||
|
<ForkAwesomeIcon icon={'history'} className={'mx-1'}/>
|
||||||
|
<Trans i18nKey={'editor.documentBar.revision'}/>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
29
src/components/editor/document-bar/share-link-button.tsx
Normal file
29
src/components/editor/document-bar/share-link-button.tsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import React, { Fragment, useState } from 'react'
|
||||||
|
import { Button, Modal } from 'react-bootstrap'
|
||||||
|
import { Trans } from 'react-i18next'
|
||||||
|
import { CopyableField } from '../../common/copyable-field/copyable-field'
|
||||||
|
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||||
|
import { CommonModal } from '../../common/modals/common-modal'
|
||||||
|
|
||||||
|
export const ShareLinkButton: React.FC = () => {
|
||||||
|
const [showReadOnly, setShowReadOnly] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<Button variant={'light'} className={'mx-1'} size={'sm'} onClick={() => setShowReadOnly(true)}>
|
||||||
|
<ForkAwesomeIcon icon={'share'} className={'mx-1'}/>
|
||||||
|
<Trans i18nKey={'editor.documentBar.shareLink'}/>
|
||||||
|
</Button>
|
||||||
|
<CommonModal
|
||||||
|
show={showReadOnly}
|
||||||
|
onHide={() => setShowReadOnly(false)}
|
||||||
|
closeButton={true}
|
||||||
|
titleI18nKey={'editor.modal.shareLink.title'}>
|
||||||
|
<Modal.Body>
|
||||||
|
<span className={'my-4'}><Trans i18nKey={'editor.modal.shareLink.viewOnlyDescription'}/></span>
|
||||||
|
<CopyableField content={'https://example.com'}/>
|
||||||
|
</Modal.Body>
|
||||||
|
</CommonModal>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
@import '../../../../node_modules/codemirror/lib/codemirror.css';
|
@import '../../../../node_modules/codemirror/lib/codemirror.css';
|
||||||
@import '../../../../node_modules/codemirror/addon/display/fullscreen.css';
|
@import '../../../../node_modules/codemirror/addon/display/fullscreen.css';
|
||||||
|
@import '../../../../node_modules/codemirror/addon/dialog/dialog.css';
|
||||||
|
@import '../../../../node_modules/codemirror/theme/neat.css';
|
||||||
@import './one-dark.css';
|
@import './one-dark.css';
|
||||||
@import 'hints';
|
@import 'hints';
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Editor, EditorChange } from 'codemirror'
|
import { Editor, EditorChange, EditorConfiguration } from 'codemirror'
|
||||||
import 'codemirror/addon/comment/comment'
|
import 'codemirror/addon/comment/comment'
|
||||||
|
import 'codemirror/addon/dialog/dialog'
|
||||||
import 'codemirror/addon/display/autorefresh'
|
import 'codemirror/addon/display/autorefresh'
|
||||||
import 'codemirror/addon/display/fullscreen'
|
import 'codemirror/addon/display/fullscreen'
|
||||||
import 'codemirror/addon/display/placeholder'
|
import 'codemirror/addon/display/placeholder'
|
||||||
|
@ -11,16 +12,21 @@ import 'codemirror/addon/edit/matchtags'
|
||||||
import 'codemirror/addon/fold/foldcode'
|
import 'codemirror/addon/fold/foldcode'
|
||||||
import 'codemirror/addon/fold/foldgutter'
|
import 'codemirror/addon/fold/foldgutter'
|
||||||
import 'codemirror/addon/hint/show-hint'
|
import 'codemirror/addon/hint/show-hint'
|
||||||
|
import 'codemirror/addon/search/search'
|
||||||
|
import 'codemirror/addon/search/jump-to-line'
|
||||||
import 'codemirror/addon/search/match-highlighter'
|
import 'codemirror/addon/search/match-highlighter'
|
||||||
import 'codemirror/addon/selection/active-line'
|
import 'codemirror/addon/selection/active-line'
|
||||||
import 'codemirror/keymap/sublime.js'
|
import 'codemirror/keymap/sublime'
|
||||||
import 'codemirror/mode/gfm/gfm.js'
|
import 'codemirror/keymap/emacs'
|
||||||
import React, { useCallback, useState } from 'react'
|
import 'codemirror/keymap/vim'
|
||||||
|
import 'codemirror/mode/gfm/gfm'
|
||||||
|
import React, { useCallback, useMemo, useState } from 'react'
|
||||||
import { Controlled as ControlledCodeMirror } from 'react-codemirror2'
|
import { Controlled as ControlledCodeMirror } from 'react-codemirror2'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import './editor-window.scss'
|
import './editor-window.scss'
|
||||||
import { emojiHints, emojiWordRegex, findWordAtCursor } from './hints/emoji'
|
import { emojiHints, emojiWordRegex, findWordAtCursor } from './hints/emoji'
|
||||||
import { defaultKeyMap } from './key-map'
|
import { defaultKeyMap } from './key-map'
|
||||||
|
import { createStatusInfo, defaultState, StatusBar, StatusBarInfo } from './status-bar/status-bar'
|
||||||
import { ToolBar } from './tool-bar/tool-bar'
|
import { ToolBar } from './tool-bar/tool-bar'
|
||||||
|
|
||||||
export interface EditorWindowProps {
|
export interface EditorWindowProps {
|
||||||
|
@ -45,55 +51,71 @@ const onChange = (editor: Editor) => {
|
||||||
export const EditorWindow: React.FC<EditorWindowProps> = ({ onContentChange, content }) => {
|
export const EditorWindow: React.FC<EditorWindowProps> = ({ onContentChange, content }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [editor, setEditor] = useState<Editor>()
|
const [editor, setEditor] = useState<Editor>()
|
||||||
|
const [statusBarInfo, setStatusBarInfo] = useState<StatusBarInfo>(defaultState)
|
||||||
|
const [editorPreferences, setEditorPreferences] = useState<EditorConfiguration>({
|
||||||
|
theme: 'one-dark',
|
||||||
|
keyMap: 'sublime',
|
||||||
|
indentUnit: 4,
|
||||||
|
indentWithTabs: false
|
||||||
|
})
|
||||||
|
|
||||||
const onBeforeChange = useCallback((editor: Editor, data: EditorChange, value: string) => {
|
const onBeforeChange = useCallback((editor: Editor, data: EditorChange, value: string) => {
|
||||||
onContentChange(value)
|
onContentChange(value)
|
||||||
}, [onContentChange])
|
}, [onContentChange])
|
||||||
|
const onEditorDidMount = useCallback(mountedEditor => {
|
||||||
|
setStatusBarInfo(createStatusInfo(mountedEditor))
|
||||||
|
setEditor(mountedEditor)
|
||||||
|
}, [])
|
||||||
|
const onCursorActivity = useCallback((editorWithActivity) => {
|
||||||
|
setStatusBarInfo(createStatusInfo(editorWithActivity))
|
||||||
|
}, [])
|
||||||
|
const codeMirrorOptions: EditorConfiguration = useMemo<EditorConfiguration>(() => ({
|
||||||
|
...editorPreferences,
|
||||||
|
mode: 'gfm',
|
||||||
|
viewportMargin: 20,
|
||||||
|
styleActiveLine: true,
|
||||||
|
lineNumbers: true,
|
||||||
|
lineWrapping: true,
|
||||||
|
showCursorWhenSelecting: true,
|
||||||
|
highlightSelectionMatches: true,
|
||||||
|
inputStyle: 'textarea',
|
||||||
|
matchBrackets: true,
|
||||||
|
autoCloseBrackets: true,
|
||||||
|
matchTags: {
|
||||||
|
bothTags: true
|
||||||
|
},
|
||||||
|
autoCloseTags: true,
|
||||||
|
foldGutter: true,
|
||||||
|
gutters: [
|
||||||
|
'CodeMirror-linenumbers',
|
||||||
|
'authorship-gutters',
|
||||||
|
'CodeMirror-foldgutter'
|
||||||
|
],
|
||||||
|
extraKeys: defaultKeyMap,
|
||||||
|
flattenSpans: true,
|
||||||
|
addModeClass: true,
|
||||||
|
autoRefresh: true,
|
||||||
|
// otherCursors: true,
|
||||||
|
placeholder: t('editor.placeholder')
|
||||||
|
}), [t, editorPreferences])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'d-flex flex-column h-100'}>
|
<div className={'d-flex flex-column h-100'}>
|
||||||
<ToolBar
|
<ToolBar
|
||||||
editor={editor}
|
editor={editor}
|
||||||
|
onPreferencesChange={config => setEditorPreferences(config)}
|
||||||
|
editorPreferences={editorPreferences}
|
||||||
/>
|
/>
|
||||||
<ControlledCodeMirror
|
<ControlledCodeMirror
|
||||||
className="overflow-hidden w-100 flex-fill"
|
className="overflow-hidden w-100 flex-fill"
|
||||||
value={content}
|
value={content}
|
||||||
options={{
|
options={codeMirrorOptions}
|
||||||
mode: 'gfm',
|
|
||||||
theme: 'one-dark',
|
|
||||||
keyMap: 'sublime',
|
|
||||||
viewportMargin: 20,
|
|
||||||
styleActiveLine: true,
|
|
||||||
lineNumbers: true,
|
|
||||||
lineWrapping: true,
|
|
||||||
showCursorWhenSelecting: true,
|
|
||||||
highlightSelectionMatches: true,
|
|
||||||
indentUnit: 4,
|
|
||||||
inputStyle: 'textarea',
|
|
||||||
matchBrackets: true,
|
|
||||||
autoCloseBrackets: true,
|
|
||||||
matchTags: {
|
|
||||||
bothTags: true
|
|
||||||
},
|
|
||||||
autoCloseTags: true,
|
|
||||||
foldGutter: true,
|
|
||||||
gutters: [
|
|
||||||
'CodeMirror-linenumbers',
|
|
||||||
'authorship-gutters',
|
|
||||||
'CodeMirror-foldgutter'
|
|
||||||
],
|
|
||||||
extraKeys: defaultKeyMap,
|
|
||||||
flattenSpans: true,
|
|
||||||
addModeClass: true,
|
|
||||||
autoRefresh: true,
|
|
||||||
// otherCursors: true,
|
|
||||||
placeholder: t('editor.placeholder'),
|
|
||||||
showHint: false,
|
|
||||||
hintOptions: hintOptions
|
|
||||||
}}
|
|
||||||
editorDidMount={mountedEditor => setEditor(mountedEditor)}
|
|
||||||
onBeforeChange={onBeforeChange}
|
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/></div>
|
onCursorActivity={onCursorActivity}
|
||||||
|
editorDidMount={onEditorDidMount}
|
||||||
|
onBeforeChange={onBeforeChange}
|
||||||
|
/>
|
||||||
|
<StatusBar {...statusBarInfo} />
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
.status-bar {
|
||||||
|
background: #1c1c1e;
|
||||||
|
border-top: 1px solid #343434;
|
||||||
|
color: #ccc;
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 25px;
|
||||||
|
height: 26px;
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { Editor, Position } from 'codemirror'
|
||||||
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { ShowIf } from '../../../common/show-if/show-if'
|
||||||
|
import './status-bar.scss'
|
||||||
|
|
||||||
|
export interface StatusBarInfo {
|
||||||
|
position: Position
|
||||||
|
selectedColumns: number
|
||||||
|
selectedLines: number
|
||||||
|
linesInDocument: number
|
||||||
|
charactersInDocument: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultState: StatusBarInfo = {
|
||||||
|
position: { line: 0, ch: 0 },
|
||||||
|
selectedColumns: 0,
|
||||||
|
selectedLines: 0,
|
||||||
|
linesInDocument: 0,
|
||||||
|
charactersInDocument: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createStatusInfo = (editor: Editor): StatusBarInfo => ({
|
||||||
|
position: editor.getCursor(),
|
||||||
|
charactersInDocument: editor.getValue().length,
|
||||||
|
linesInDocument: editor.lineCount(),
|
||||||
|
selectedColumns: editor.getSelection().length,
|
||||||
|
selectedLines: editor.getSelection().split('\n').length
|
||||||
|
})
|
||||||
|
|
||||||
|
export const StatusBar: React.FC<StatusBarInfo> = ({ position, selectedColumns, selectedLines, charactersInDocument, linesInDocument }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="d-flex flex-row status-bar px-2">
|
||||||
|
<div>
|
||||||
|
<span>{t('editor.statusBar.cursor', { line: position.line + 1, columns: position.ch + 1 })}</span>
|
||||||
|
<ShowIf condition={selectedColumns !== 0 && selectedLines !== 0}>
|
||||||
|
<ShowIf condition={selectedLines === 1}>
|
||||||
|
<span> – {t('editor.statusBar.selection.column', { count: selectedColumns })}</span>
|
||||||
|
</ShowIf>
|
||||||
|
<ShowIf condition={selectedLines > 1}>
|
||||||
|
<span> – {t('editor.statusBar.selection.line', { count: selectedLines })}</span>
|
||||||
|
</ShowIf>
|
||||||
|
</ShowIf>
|
||||||
|
</div>
|
||||||
|
<div className="ml-auto">
|
||||||
|
<span>{t('editor.statusBar.lines', { lines: linesInDocument })}</span>
|
||||||
|
<span title={t('editor.statusBar.lengthTooltip')}> – {t('editor.statusBar.length', { length: charactersInDocument })}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { EditorConfiguration } from 'codemirror'
|
||||||
|
import React, { ChangeEvent, useCallback, useState } from 'react'
|
||||||
|
import { Form } from 'react-bootstrap'
|
||||||
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
export enum EditorPreferenceProperty {
|
||||||
|
KEYMAP = 'keyMap',
|
||||||
|
THEME = 'theme',
|
||||||
|
INDENT_WITH_TABS = 'indentWithTabs',
|
||||||
|
INDENT_UNIT = 'indentUnit'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EditorPreferenceSelectProps {
|
||||||
|
onChange: (config: EditorConfiguration) => void
|
||||||
|
preferences: EditorConfiguration
|
||||||
|
property: EditorPreferenceProperty
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EditorPreferenceSelect: React.FC<EditorPreferenceSelectProps> = ({ property, onChange, preferences, children }) => {
|
||||||
|
useTranslation()
|
||||||
|
const [selected, setSelected] = useState(preferences[property])
|
||||||
|
|
||||||
|
const selectItem = useCallback((event: ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
let selectedItem: string | boolean | number = event.target.value
|
||||||
|
if (property === EditorPreferenceProperty.INDENT_UNIT) {
|
||||||
|
selectedItem = parseInt(selectedItem)
|
||||||
|
}
|
||||||
|
setSelected(selectedItem)
|
||||||
|
if (property === EditorPreferenceProperty.INDENT_WITH_TABS) {
|
||||||
|
selectedItem = selectedItem === 'true'
|
||||||
|
}
|
||||||
|
onChange({
|
||||||
|
...preferences,
|
||||||
|
[property]: selectedItem
|
||||||
|
})
|
||||||
|
}, [preferences, property, setSelected, onChange])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form.Group controlId={`editor-pref-${property}`}>
|
||||||
|
<Form.Label>
|
||||||
|
<Trans i18nKey={`editor.modal.preferences.${property}`}/>
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
as={property === EditorPreferenceProperty.INDENT_UNIT ? 'input' : 'select'}
|
||||||
|
size='sm'
|
||||||
|
value={selected as string | number}
|
||||||
|
onChange={selectItem}
|
||||||
|
type={property === EditorPreferenceProperty.INDENT_UNIT ? 'number' : ''}>
|
||||||
|
{ children }
|
||||||
|
</Form.Control>
|
||||||
|
</Form.Group>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { EditorConfiguration } from 'codemirror'
|
||||||
|
import React, { Fragment, useCallback, useState } from 'react'
|
||||||
|
import { Button, Form, ListGroup } from 'react-bootstrap'
|
||||||
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
|
||||||
|
import { CommonModal } from '../../../../common/modals/common-modal'
|
||||||
|
import { EditorPreferenceProperty, EditorPreferenceSelect } from './editor-preference-select'
|
||||||
|
|
||||||
|
export interface EditorSettingsButtonProps {
|
||||||
|
preferences: EditorConfiguration
|
||||||
|
onPreferencesChange: (config: EditorConfiguration) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EditorPreferences: React.FC<EditorSettingsButtonProps> = ({ onPreferencesChange, preferences }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [showModal, setShowModal] = useState(false)
|
||||||
|
|
||||||
|
const sendPreferences = useCallback((newPreferences: EditorConfiguration) => {
|
||||||
|
onPreferencesChange(newPreferences)
|
||||||
|
}, [onPreferencesChange])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<Button variant='light' onClick={() => setShowModal(true)} title={t('editor.editorToolbar.preferences')}>
|
||||||
|
<ForkAwesomeIcon icon="wrench"/>
|
||||||
|
</Button>
|
||||||
|
<CommonModal
|
||||||
|
show={showModal}
|
||||||
|
onHide={() => setShowModal(false)}
|
||||||
|
titleI18nKey={'editor.modal.preferences.title'}
|
||||||
|
closeButton={true}
|
||||||
|
icon={'wrench'}>
|
||||||
|
<Form>
|
||||||
|
<ListGroup>
|
||||||
|
<ListGroup.Item>
|
||||||
|
<EditorPreferenceSelect onChange={sendPreferences} preferences={preferences} property={EditorPreferenceProperty.THEME}>
|
||||||
|
<option value='one-dark'>Dark</option>
|
||||||
|
<option value='neat'>Light</option>
|
||||||
|
</EditorPreferenceSelect>
|
||||||
|
</ListGroup.Item>
|
||||||
|
<ListGroup.Item>
|
||||||
|
<EditorPreferenceSelect onChange={sendPreferences} preferences={preferences} property={EditorPreferenceProperty.KEYMAP}>
|
||||||
|
<option value='sublime'>Sublime</option>
|
||||||
|
<option value='emacs'>Emacs</option>
|
||||||
|
<option value='vim'>Vim</option>
|
||||||
|
</EditorPreferenceSelect>
|
||||||
|
</ListGroup.Item>
|
||||||
|
<ListGroup.Item>
|
||||||
|
<EditorPreferenceSelect onChange={sendPreferences} preferences={preferences} property={EditorPreferenceProperty.INDENT_WITH_TABS}>
|
||||||
|
<option value='false'>Spaces</option>
|
||||||
|
<option value='true'>Tab</option>
|
||||||
|
</EditorPreferenceSelect>
|
||||||
|
</ListGroup.Item>
|
||||||
|
<ListGroup.Item>
|
||||||
|
<EditorPreferenceSelect onChange={sendPreferences} preferences={preferences} property={EditorPreferenceProperty.INDENT_UNIT}/>
|
||||||
|
</ListGroup.Item>
|
||||||
|
<ListGroup.Item>
|
||||||
|
<Form.Group controlId='editorSpellChecker'>
|
||||||
|
<Form.Label>
|
||||||
|
<Trans i18nKey='editor.modal.preferences.spellChecker'/>
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control as='select' size='sm' onChange={() => alert('This feature is not yet implemented.')}>
|
||||||
|
<option value='off'>off</option>
|
||||||
|
<option value='en'>English</option>
|
||||||
|
</Form.Control>
|
||||||
|
</Form.Group>
|
||||||
|
</ListGroup.Item>
|
||||||
|
</ListGroup>
|
||||||
|
</Form>
|
||||||
|
</CommonModal>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
import CodeMirror from 'codemirror'
|
||||||
|
import React, { Fragment, useState } from 'react'
|
||||||
|
import { Button } from 'react-bootstrap'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
||||||
|
import { EmojiPicker } from './emoji-picker/emoji-picker'
|
||||||
|
import { addEmoji } from './utils'
|
||||||
|
|
||||||
|
export interface EmojiPickerButtonProps {
|
||||||
|
editor: CodeMirror.Editor
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EmojiPickerButton: React.FC<EmojiPickerButtonProps> = ({ editor }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [showEmojiPicker, setShowEmojiPicker] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<EmojiPicker show={showEmojiPicker} onEmojiSelected={(emoji) => {
|
||||||
|
setShowEmojiPicker(false)
|
||||||
|
addEmoji(emoji, editor)
|
||||||
|
}} onDismiss={() => setShowEmojiPicker(false)}/>
|
||||||
|
<Button variant='light' onClick={() => setShowEmojiPicker(old => !old)} title={t('editor.editorToolbar.emoji')}>
|
||||||
|
<ForkAwesomeIcon icon="smile-o"/>
|
||||||
|
</Button>
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,3 +1,9 @@
|
||||||
.btn-toolbar {
|
.btn-toolbar {
|
||||||
border: 1px solid #ededed;
|
border: 1px solid #ededed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
background-color: #e2e6ea;
|
||||||
|
width: 2px;
|
||||||
|
padding: 0.25rem 0;
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { Editor } from 'codemirror'
|
import { Editor, EditorConfiguration } from 'codemirror'
|
||||||
import React, { Fragment, useState } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { Button, ButtonToolbar } from 'react-bootstrap'
|
import { Button, ButtonGroup, ButtonToolbar } from 'react-bootstrap'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
||||||
import { EmojiPicker } from './emoji-picker/emoji-picker'
|
import { EditorPreferences } from './editor-preferences/editor-preferences'
|
||||||
|
import { EmojiPickerButton } from './emoji-picker-button'
|
||||||
import './tool-bar.scss'
|
import './tool-bar.scss'
|
||||||
import {
|
import {
|
||||||
addCodeFences,
|
addCodeFences,
|
||||||
addComment,
|
addComment,
|
||||||
addEmoji,
|
|
||||||
addHeaderLevel,
|
addHeaderLevel,
|
||||||
addImage,
|
addImage,
|
||||||
addLine,
|
addLine,
|
||||||
|
@ -28,11 +28,12 @@ import {
|
||||||
|
|
||||||
export interface ToolBarProps {
|
export interface ToolBarProps {
|
||||||
editor: Editor | undefined
|
editor: Editor | undefined
|
||||||
|
onPreferencesChange: (config: EditorConfiguration) => void
|
||||||
|
editorPreferences: EditorConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ToolBar: React.FC<ToolBarProps> = ({ editor }) => {
|
export const ToolBar: React.FC<ToolBarProps> = ({ editor, onPreferencesChange, editorPreferences }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false)
|
|
||||||
|
|
||||||
const notImplemented = () => {
|
const notImplemented = () => {
|
||||||
alert('This feature is not yet implemented')
|
alert('This feature is not yet implemented')
|
||||||
|
@ -44,68 +45,77 @@ export const ToolBar: React.FC<ToolBarProps> = ({ editor }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<ButtonToolbar className='flex-nowrap bg-light'>
|
<ButtonToolbar className='bg-light'>
|
||||||
<Button variant='light' onClick={() => makeSelectionBold(editor)} title={t('editor.editorToolbar.bold')}>
|
<ButtonGroup className={'mx-2 flex-wrap'}>
|
||||||
<ForkAwesomeIcon icon="bold"/>
|
<Button variant='light' onClick={() => makeSelectionBold(editor)} title={t('editor.editorToolbar.bold')}>
|
||||||
</Button>
|
<ForkAwesomeIcon icon="bold"/>
|
||||||
<Button variant='light' onClick={() => makeSelectionItalic(editor)} title={t('editor.editorToolbar.italic')}>
|
</Button>
|
||||||
<ForkAwesomeIcon icon="italic"/>
|
<Button variant='light' onClick={() => makeSelectionItalic(editor)} title={t('editor.editorToolbar.italic')}>
|
||||||
</Button>
|
<ForkAwesomeIcon icon="italic"/>
|
||||||
<Button variant='light' onClick={() => underlineSelection(editor)} title={t('editor.editorToolbar.underline')}>
|
</Button>
|
||||||
<ForkAwesomeIcon icon="underline"/>
|
<Button variant='light' onClick={() => underlineSelection(editor)} title={t('editor.editorToolbar.underline')}>
|
||||||
</Button>
|
<ForkAwesomeIcon icon="underline"/>
|
||||||
<Button variant='light' onClick={() => strikeThroughSelection(editor)} title={t('editor.editorToolbar.strikethrough')}>
|
</Button>
|
||||||
<ForkAwesomeIcon icon="strikethrough"/>
|
<Button variant='light' onClick={() => strikeThroughSelection(editor)} title={t('editor.editorToolbar.strikethrough')}>
|
||||||
</Button>
|
<ForkAwesomeIcon icon="strikethrough"/>
|
||||||
<Button variant='light' onClick={() => subscriptSelection(editor)} title={t('editor.editorToolbar.subscript')}>
|
</Button>
|
||||||
<ForkAwesomeIcon icon="subscript"/>
|
<Button variant='light' onClick={() => subscriptSelection(editor)} title={t('editor.editorToolbar.subscript')}>
|
||||||
</Button>
|
<ForkAwesomeIcon icon="subscript"/>
|
||||||
<Button variant='light' onClick={() => superscriptSelection(editor)} title={t('editor.editorToolbar.superscript')}>
|
</Button>
|
||||||
<ForkAwesomeIcon icon="superscript"/>
|
<Button variant='light' onClick={() => superscriptSelection(editor)} title={t('editor.editorToolbar.superscript')}>
|
||||||
</Button>
|
<ForkAwesomeIcon icon="superscript"/>
|
||||||
<Button variant='light' onClick={() => addHeaderLevel(editor)} title={t('editor.editorToolbar.header')}>
|
</Button>
|
||||||
<ForkAwesomeIcon icon="header"/>
|
</ButtonGroup>
|
||||||
</Button>
|
<span className={'divider'}> </span>
|
||||||
<Button variant='light' onClick={() => addCodeFences(editor)} title={t('editor.editorToolbar.code')}>
|
<ButtonGroup className={'mx-2 flex-wrap'}>
|
||||||
<ForkAwesomeIcon icon="code"/>
|
<Button variant='light' onClick={() => addHeaderLevel(editor)} title={t('editor.editorToolbar.header')}>
|
||||||
</Button>
|
<ForkAwesomeIcon icon="header"/>
|
||||||
<Button variant='light' onClick={() => addQuotes(editor)} title={t('editor.editorToolbar.blockquote')}>
|
</Button>
|
||||||
<ForkAwesomeIcon icon="quote-right"/>
|
<Button variant='light' onClick={() => addCodeFences(editor)} title={t('editor.editorToolbar.code')}>
|
||||||
</Button>
|
<ForkAwesomeIcon icon="code"/>
|
||||||
<Button variant='light' onClick={() => addList(editor)} title={t('editor.editorToolbar.unorderedList')}>
|
</Button>
|
||||||
<ForkAwesomeIcon icon="list"/>
|
<Button variant='light' onClick={() => addQuotes(editor)} title={t('editor.editorToolbar.blockquote')}>
|
||||||
</Button>
|
<ForkAwesomeIcon icon="quote-right"/>
|
||||||
<Button variant='light' onClick={() => addOrderedList(editor)} title={t('editor.editorToolbar.orderedList')}>
|
</Button>
|
||||||
<ForkAwesomeIcon icon="list-ol"/>
|
<Button variant='light' onClick={() => addList(editor)} title={t('editor.editorToolbar.unorderedList')}>
|
||||||
</Button>
|
<ForkAwesomeIcon icon="list"/>
|
||||||
<Button variant='light' onClick={() => addTaskList(editor)} title={t('editor.editorToolbar.checkList')}>
|
</Button>
|
||||||
<ForkAwesomeIcon icon="check-square"/>
|
<Button variant='light' onClick={() => addOrderedList(editor)} title={t('editor.editorToolbar.orderedList')}>
|
||||||
</Button>
|
<ForkAwesomeIcon icon="list-ol"/>
|
||||||
<Button variant='light' onClick={() => addLink(editor)} title={t('editor.editorToolbar.link')}>
|
</Button>
|
||||||
<ForkAwesomeIcon icon="link"/>
|
<Button variant='light' onClick={() => addTaskList(editor)} title={t('editor.editorToolbar.checkList')}>
|
||||||
</Button>
|
<ForkAwesomeIcon icon="check-square"/>
|
||||||
<Button variant='light' onClick={() => addImage(editor)} title={t('editor.editorToolbar.image')}>
|
</Button>
|
||||||
<ForkAwesomeIcon icon="picture-o"/>
|
</ButtonGroup>
|
||||||
</Button>
|
<span className={'divider'}> </span>
|
||||||
<Button variant='light' onClick={notImplemented} title={t('editor.editorToolbar.uploadImage')}>
|
<ButtonGroup className={'mx-2 flex-wrap'}>
|
||||||
<ForkAwesomeIcon icon="upload"/>
|
<Button variant='light' onClick={() => addLink(editor)} title={t('editor.editorToolbar.link')}>
|
||||||
</Button>
|
<ForkAwesomeIcon icon="link"/>
|
||||||
<Button variant='light' onClick={() => addTable(editor)} title={t('editor.editorToolbar.table')}>
|
</Button>
|
||||||
<ForkAwesomeIcon icon="table"/>
|
<Button variant='light' onClick={() => addImage(editor)} title={t('editor.editorToolbar.image')}>
|
||||||
</Button>
|
<ForkAwesomeIcon icon="picture-o"/>
|
||||||
<Button variant='light' onClick={() => addLine(editor)} title={t('editor.editorToolbar.line')}>
|
</Button>
|
||||||
<ForkAwesomeIcon icon="minus"/>
|
<Button variant='light' onClick={notImplemented} title={t('editor.editorToolbar.uploadImage')}>
|
||||||
</Button>
|
<ForkAwesomeIcon icon="upload"/>
|
||||||
<Button variant='light' onClick={() => addComment(editor)} title={t('editor.editorToolbar.comment')}>
|
</Button>
|
||||||
<ForkAwesomeIcon icon="comment"/>
|
</ButtonGroup>
|
||||||
</Button>
|
<span className={'divider'}> </span>
|
||||||
<EmojiPicker show={showEmojiPicker} onEmojiSelected={(emoji) => {
|
<ButtonGroup className={'mx-2 flex-wrap'}>
|
||||||
setShowEmojiPicker(false)
|
<Button variant='light' onClick={() => addTable(editor)} title={t('editor.editorToolbar.table')}>
|
||||||
addEmoji(emoji, editor)
|
<ForkAwesomeIcon icon="table"/>
|
||||||
}} onDismiss={() => setShowEmojiPicker(false)}/>
|
</Button>
|
||||||
<Button variant='light' onClick={() => setShowEmojiPicker(old => !old)} title={''}>
|
<Button variant='light' onClick={() => addLine(editor)} title={t('editor.editorToolbar.line')}>
|
||||||
<ForkAwesomeIcon icon="smile-o"/>
|
<ForkAwesomeIcon icon="minus"/>
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button variant='light' onClick={() => addComment(editor)} title={t('editor.editorToolbar.comment')}>
|
||||||
|
<ForkAwesomeIcon icon="comment"/>
|
||||||
|
</Button>
|
||||||
|
<EmojiPickerButton editor={editor}/>
|
||||||
|
</ButtonGroup>
|
||||||
|
<span className={'divider'}> </span>
|
||||||
|
<ButtonGroup className={'mx-2 flex-wrap'}>
|
||||||
|
<EditorPreferences onPreferencesChange={onPreferencesChange} preferences={editorPreferences}/>
|
||||||
|
</ButtonGroup>
|
||||||
</ButtonToolbar>
|
</ButtonToolbar>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { setEditorModeConfig } from '../../redux/editor/methods'
|
||||||
import { DocumentTitle } from '../common/document-title/document-title'
|
import { DocumentTitle } from '../common/document-title/document-title'
|
||||||
import { Splitter } from '../common/splitter/splitter'
|
import { Splitter } from '../common/splitter/splitter'
|
||||||
import { InfoBanner } from '../landing/layout/info-banner'
|
import { InfoBanner } from '../landing/layout/info-banner'
|
||||||
|
import { DocumentBar } from './document-bar/document-bar'
|
||||||
import { EditorWindow } from './editor-window/editor-window'
|
import { EditorWindow } from './editor-window/editor-window'
|
||||||
import { editorTestContent } from './editorTestContent'
|
import { editorTestContent } from './editorTestContent'
|
||||||
import { MarkdownRenderWindow } from './renderer-window/markdown-render-window'
|
import { MarkdownRenderWindow } from './renderer-window/markdown-render-window'
|
||||||
|
@ -14,6 +15,10 @@ import { EditorMode } from './task-bar/editor-view-mode'
|
||||||
import { TaskBar } from './task-bar/task-bar'
|
import { TaskBar } from './task-bar/task-bar'
|
||||||
import { YAMLMetaData } from './yaml-metadata/yaml-metadata'
|
import { YAMLMetaData } from './yaml-metadata/yaml-metadata'
|
||||||
|
|
||||||
|
export interface EditorPathParams {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
export const Editor: React.FC = () => {
|
export const Editor: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const untitledNote = t('editor.untitledNote')
|
const untitledNote = t('editor.untitledNote')
|
||||||
|
@ -61,11 +66,22 @@ export const Editor: React.FC = () => {
|
||||||
<DocumentTitle title={documentTitle}/>
|
<DocumentTitle title={documentTitle}/>
|
||||||
<div className={'d-flex flex-column vh-100'}>
|
<div className={'d-flex flex-column vh-100'}>
|
||||||
<TaskBar/>
|
<TaskBar/>
|
||||||
|
<DocumentBar title={documentTitle}/>
|
||||||
<Splitter
|
<Splitter
|
||||||
showLeft={editorMode === EditorMode.EDITOR || editorMode === EditorMode.BOTH}
|
showLeft={editorMode === EditorMode.EDITOR || editorMode === EditorMode.BOTH}
|
||||||
left={<EditorWindow onContentChange={content => setMarkdownContent(content)} content={markdownContent}/>}
|
left={
|
||||||
|
<EditorWindow
|
||||||
|
onContentChange={content => setMarkdownContent(content)}
|
||||||
|
content={markdownContent}/>
|
||||||
|
}
|
||||||
showRight={editorMode === EditorMode.PREVIEW || (editorMode === EditorMode.BOTH)}
|
showRight={editorMode === EditorMode.PREVIEW || (editorMode === EditorMode.BOTH)}
|
||||||
right={<MarkdownRenderWindow content={markdownContent} wide={editorMode === EditorMode.PREVIEW} onMetadataChange={onMetadataChange} onFirstHeadingChange={onFirstHeadingChange}/>}
|
right={
|
||||||
|
<MarkdownRenderWindow
|
||||||
|
content={markdownContent}
|
||||||
|
wide={editorMode === EditorMode.PREVIEW}
|
||||||
|
onMetadataChange={onMetadataChange}
|
||||||
|
onFirstHeadingChange={onFirstHeadingChange}/>
|
||||||
|
}
|
||||||
containerClassName={'overflow-hidden'}/>
|
containerClassName={'overflow-hidden'}/>
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
|
@ -50,4 +50,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,11 +52,11 @@
|
||||||
.markdown-toc-sidebar-button {
|
.markdown-toc-sidebar-button {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 40px;
|
right: 40px;
|
||||||
bottom: 20px;
|
bottom: 30px;
|
||||||
|
|
||||||
&>.dropup {
|
&>.dropup {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
right: 0px;
|
right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import { Dropdown } from 'react-bootstrap'
|
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
|
||||||
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
|
||||||
|
|
||||||
const EditorMenu: React.FC = () => {
|
|
||||||
useTranslation()
|
|
||||||
return (
|
|
||||||
<Dropdown className="small" alignRight={true}>
|
|
||||||
<Dropdown.Toggle variant="light" size="sm" id="editor-menu" className="text-secondary">
|
|
||||||
<Trans i18nKey="editor.menu.menu"/>
|
|
||||||
</Dropdown.Toggle>
|
|
||||||
|
|
||||||
<Dropdown.Menu>
|
|
||||||
<Dropdown.Header>
|
|
||||||
<Trans i18nKey="editor.menu.extra"/>
|
|
||||||
</Dropdown.Header>
|
|
||||||
<Dropdown.Item className="small">
|
|
||||||
<ForkAwesomeIcon icon="history"/> <Trans i18nKey="editor.menu.revision"/>
|
|
||||||
</Dropdown.Item>
|
|
||||||
<Dropdown.Item className="small">
|
|
||||||
<ForkAwesomeIcon icon="television"/> <Trans i18nKey="editor.menu.slideMode"/>
|
|
||||||
</Dropdown.Item>
|
|
||||||
<Dropdown.Item className="small">
|
|
||||||
<ForkAwesomeIcon icon="trash"/> <Trans i18nKey="editor.menu.deleteNote"/>
|
|
||||||
</Dropdown.Item>
|
|
||||||
|
|
||||||
<Dropdown.Divider/>
|
|
||||||
|
|
||||||
<Dropdown.Header>
|
|
||||||
<Trans i18nKey="common.export"/>
|
|
||||||
</Dropdown.Header>
|
|
||||||
<Dropdown.Item className="small">
|
|
||||||
<ForkAwesomeIcon icon="dropbox"/> Dropbox
|
|
||||||
</Dropdown.Item>
|
|
||||||
<Dropdown.Item className="small">
|
|
||||||
<ForkAwesomeIcon icon="github"/> Gist
|
|
||||||
</Dropdown.Item>
|
|
||||||
|
|
||||||
<Dropdown.Divider/>
|
|
||||||
|
|
||||||
<Dropdown.Header>
|
|
||||||
<Trans i18nKey="common.import"/>
|
|
||||||
</Dropdown.Header>
|
|
||||||
<Dropdown.Item className="small">
|
|
||||||
<ForkAwesomeIcon icon="dropbox"/> Dropbox
|
|
||||||
</Dropdown.Item>
|
|
||||||
<Dropdown.Item className="small">
|
|
||||||
<ForkAwesomeIcon icon="github"/> Gist
|
|
||||||
</Dropdown.Item>
|
|
||||||
<Dropdown.Item className="small">
|
|
||||||
<ForkAwesomeIcon icon="clipboard"/> <Trans i18nKey="editor.import.clipboard"/>
|
|
||||||
</Dropdown.Item>
|
|
||||||
|
|
||||||
<Dropdown.Divider/>
|
|
||||||
|
|
||||||
<Dropdown.Header>
|
|
||||||
<Trans i18nKey="editor.menu.download"/>
|
|
||||||
</Dropdown.Header>
|
|
||||||
<Dropdown.Item className="small">
|
|
||||||
<ForkAwesomeIcon icon="file-text"/> Markdown
|
|
||||||
</Dropdown.Item>
|
|
||||||
<Dropdown.Item className="small">
|
|
||||||
<ForkAwesomeIcon icon="file-code-o"/> HTML
|
|
||||||
</Dropdown.Item>
|
|
||||||
<Dropdown.Item className="small">
|
|
||||||
<ForkAwesomeIcon icon="file-code-o"/> <Trans i18nKey='editor.export.rawHtml'/>
|
|
||||||
</Dropdown.Item>
|
|
||||||
|
|
||||||
</Dropdown.Menu>
|
|
||||||
</Dropdown>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { EditorMenu }
|
|
|
@ -12,14 +12,14 @@ export const HelpButton: React.FC = () => {
|
||||||
const handleClose = () => setShow(false)
|
const handleClose = () => setShow(false)
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Button title={t('editor.menu.help')} className="ml-2 text-secondary" size="sm" variant="outline-light"
|
<Button title={t('editor.documentBar.help')} className="ml-2 text-secondary" size="sm" variant="outline-light"
|
||||||
onClick={handleShow}>
|
onClick={handleShow}>
|
||||||
<ForkAwesomeIcon icon="question-circle"/>
|
<ForkAwesomeIcon icon="question-circle"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Modal show={show} onHide={handleClose} animation={true} className="text-dark" size='lg'>
|
<Modal show={show} onHide={handleClose} animation={true} className="text-dark" size='lg'>
|
||||||
<Modal.Header closeButton>
|
<Modal.Header closeButton>
|
||||||
<Modal.Title>
|
<Modal.Title>
|
||||||
<ForkAwesomeIcon icon="question-circle"/> <Trans i18nKey={'editor.menu.help'}/>
|
<ForkAwesomeIcon icon="question-circle"/> <Trans i18nKey={'editor.documentBar.help'}/>
|
||||||
</Modal.Title>
|
</Modal.Title>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body className="text-dark">
|
<Modal.Body className="text-dark">
|
||||||
|
@ -67,6 +67,7 @@ export const HelpButton: React.FC = () => {
|
||||||
</Card.Text>
|
</Card.Text>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card>
|
</Card>
|
||||||
|
<br/>
|
||||||
<Card>
|
<Card>
|
||||||
<Card.Header><Trans i18nKey='editor.help.documents.title'/></Card.Header>
|
<Card.Header><Trans i18nKey='editor.help.documents.title'/></Card.Header>
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
|
|
|
@ -1,17 +1,25 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Button, Nav, Navbar } from 'react-bootstrap'
|
import { Button, Nav, Navbar } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
import { useParams } from 'react-router'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
import { ApplicationState } from '../../../redux'
|
||||||
import { Branding } from '../../common/branding/branding'
|
import { Branding } from '../../common/branding/branding'
|
||||||
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||||
import { ConnectionIndicator } from './connection-indicator'
|
import { ShowIf } from '../../common/show-if/show-if'
|
||||||
|
import { SignInButton } from '../../landing/layout/navigation/sign-in-button'
|
||||||
|
import { UserDropdown } from '../../landing/layout/navigation/user-dropdown/user-dropdown'
|
||||||
|
import { EditorPathParams } from '../editor'
|
||||||
import { DarkModeButton } from './dark-mode-button'
|
import { DarkModeButton } from './dark-mode-button'
|
||||||
import { EditorMenu } from './editor-menu'
|
|
||||||
import { EditorViewMode } from './editor-view-mode'
|
import { EditorViewMode } from './editor-view-mode'
|
||||||
import { HelpButton } from './help-button'
|
import { HelpButton } from './help-button'
|
||||||
|
|
||||||
const TaskBar: React.FC = () => {
|
export const TaskBar: React.FC = () => {
|
||||||
useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { id } = useParams<EditorPathParams>()
|
||||||
|
const user = useSelector((state: ApplicationState) => state.user)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Navbar bg={'light'}>
|
<Navbar bg={'light'}>
|
||||||
<Nav className="mr-auto d-flex align-items-center">
|
<Nav className="mr-auto d-flex align-items-center">
|
||||||
|
@ -24,24 +32,24 @@ const TaskBar: React.FC = () => {
|
||||||
</Navbar.Brand>
|
</Navbar.Brand>
|
||||||
<EditorViewMode/>
|
<EditorViewMode/>
|
||||||
<DarkModeButton/>
|
<DarkModeButton/>
|
||||||
|
<Link to={`/p/${id}`} target='_blank'>
|
||||||
|
<Button title={t('editor.documentBar.slideMode')} className="ml-2 text-secondary" size="sm" variant="outline-light">
|
||||||
|
<ForkAwesomeIcon icon="television"/>
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
<HelpButton/>
|
<HelpButton/>
|
||||||
</Nav>
|
</Nav>
|
||||||
<Nav className="d-flex align-items-center text-secondary">
|
<Nav className="d-flex align-items-center text-secondary">
|
||||||
<Button className="ml-2 text-secondary" size="sm" variant="outline-light">
|
<Button className="mx-2" size="sm" variant="primary">
|
||||||
<ForkAwesomeIcon icon="plus"/> <Trans i18nKey="editor.menu.new"/>
|
<ForkAwesomeIcon icon="plus"/> <Trans i18nKey="editor.documentBar.new"/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button className="ml-2 text-secondary" size="sm" variant="outline-light">
|
<ShowIf condition={!user}>
|
||||||
<ForkAwesomeIcon icon="share-square-o"/> <Trans i18nKey="editor.menu.publish"/>
|
<SignInButton size={'sm'} />
|
||||||
</Button>
|
</ShowIf>
|
||||||
<div className="text-secondary">
|
<ShowIf condition={!!user}>
|
||||||
<EditorMenu/>
|
<UserDropdown />
|
||||||
</div>
|
</ShowIf>
|
||||||
<div className="mr-2">
|
|
||||||
<ConnectionIndicator/>
|
|
||||||
</div>
|
|
||||||
</Nav>
|
</Nav>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { TaskBar }
|
|
||||||
|
|
|
@ -1,22 +1,28 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { ShowIf } from '../../../common/show-if/show-if'
|
||||||
import './user-avatar.scss'
|
import './user-avatar.scss'
|
||||||
|
|
||||||
export interface UserAvatarProps {
|
export interface UserAvatarProps {
|
||||||
name: string;
|
name: string;
|
||||||
photo: string;
|
photo: string;
|
||||||
additionalClasses?: string;
|
additionalClasses?: string;
|
||||||
|
showName?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserAvatar: React.FC<UserAvatarProps> = ({ name, photo, additionalClasses = '' }) => {
|
const UserAvatar: React.FC<UserAvatarProps> = ({ name, photo, additionalClasses = '', showName = true }) => {
|
||||||
// ToDo: add Translation Key for Avatar of ${name}
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={'d-inline-flex align-items-center ' + additionalClasses}>
|
<span className={'d-inline-flex align-items-center ' + additionalClasses}>
|
||||||
<img
|
<img
|
||||||
src={photo}
|
src={photo}
|
||||||
className="user-avatar"
|
className="user-avatar rounded"
|
||||||
alt={`Avatar of ${name}`}
|
alt={t('common.avatarOf', { name })}
|
||||||
/>
|
/>
|
||||||
<span className="mx-1 user-name">{name}</span>
|
<ShowIf condition={showName}>
|
||||||
|
<span className="mx-1 user-name">{name}</span>
|
||||||
|
</ShowIf>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { ApplicationState } from '../../../../redux'
|
||||||
import frontendVersion from '../../../../version.json'
|
import frontendVersion from '../../../../version.json'
|
||||||
import { TranslatedExternalLink } from '../../../common/links/translated-external-link'
|
import { TranslatedExternalLink } from '../../../common/links/translated-external-link'
|
||||||
import { ShowIf } from '../../../common/show-if/show-if'
|
import { ShowIf } from '../../../common/show-if/show-if'
|
||||||
import { VersionInputField } from './version-input-field'
|
import { CopyableField } from '../../../common/copyable-field/copyable-field'
|
||||||
|
|
||||||
export const VersionInfo: React.FC = () => {
|
export const VersionInfo: React.FC = () => {
|
||||||
const [show, setShow] = useState(false)
|
const [show, setShow] = useState(false)
|
||||||
|
@ -22,7 +22,7 @@ export const VersionInfo: React.FC = () => {
|
||||||
const column = (title: string, version: string, sourceCodeLink: string, issueTrackerLink: string) => (
|
const column = (title: string, version: string, sourceCodeLink: string, issueTrackerLink: string) => (
|
||||||
<Col md={6} className={'flex-column'}>
|
<Col md={6} className={'flex-column'}>
|
||||||
<h5>{title}</h5>
|
<h5>{title}</h5>
|
||||||
<VersionInputField version={version}/>
|
<CopyableField content={version}/>
|
||||||
<ShowIf condition={!!sourceCodeLink}>
|
<ShowIf condition={!!sourceCodeLink}>
|
||||||
<TranslatedExternalLink i18nKey={'landing.versionInfo.sourceCode'} className={'btn btn-sm btn-primary d-block mb-2'} href={sourceCodeLink}/>
|
<TranslatedExternalLink i18nKey={'landing.versionInfo.sourceCode'} className={'btn btn-sm btn-primary d-block mb-2'} href={sourceCodeLink}/>
|
||||||
</ShowIf>
|
</ShowIf>
|
||||||
|
|
|
@ -13,19 +13,20 @@ export interface DropdownItemWithDeletionModalProps {
|
||||||
modalQuestionI18nKey: string
|
modalQuestionI18nKey: string
|
||||||
modalWarningI18nKey: string
|
modalWarningI18nKey: string
|
||||||
noteTitle: string
|
noteTitle: string
|
||||||
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DropdownItemWithDeletionModal: React.FC<DropdownItemWithDeletionModalProps> = ({
|
export const DropdownItemWithDeletionModal: React.FC<DropdownItemWithDeletionModalProps> = ({
|
||||||
onConfirm, noteTitle,
|
onConfirm, noteTitle,
|
||||||
modalTitleI18nKey, modalButtonI18nKey, itemI18nKey, modalIcon,
|
modalTitleI18nKey, modalButtonI18nKey, itemI18nKey, modalIcon,
|
||||||
modalQuestionI18nKey, modalWarningI18nKey
|
modalQuestionI18nKey, modalWarningI18nKey, className
|
||||||
}) => {
|
}) => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const [showDialog, setShowDialog] = useState(false)
|
const [showDialog, setShowDialog] = useState(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Dropdown.Item onClick={() => setShowDialog(true)}>
|
<Dropdown.Item onClick={() => setShowDialog(true)} className={className}>
|
||||||
<ForkAwesomeIcon icon={modalIcon} fixedWidth={true} className="mx-2"/>
|
<ForkAwesomeIcon icon={modalIcon} fixedWidth={true} className="mx-2"/>
|
||||||
<Trans i18nKey={itemI18nKey}/>
|
<Trans i18nKey={itemI18nKey}/>
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
|
|
|
@ -4186,7 +4186,7 @@ cyclist@^1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
||||||
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
|
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
|
||||||
|
|
||||||
cypress@*, cypress@4.12.1:
|
cypress@4.12.1:
|
||||||
version "4.12.1"
|
version "4.12.1"
|
||||||
resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.12.1.tgz#0ead1b9f4c0917d69d8b57f996b6e01fe693b6ec"
|
resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.12.1.tgz#0ead1b9f4c0917d69d8b57f996b6e01fe693b6ec"
|
||||||
integrity sha512-9SGIPEmqU8vuRA6xst2CMTYd9sCFCxKSzrHt0wr+w2iAQMCIIsXsQ5Gplns1sT6LDbZcmLv6uehabAOl3fhc9Q==
|
integrity sha512-9SGIPEmqU8vuRA6xst2CMTYd9sCFCxKSzrHt0wr+w2iAQMCIIsXsQ5Gplns1sT6LDbZcmLv6uehabAOl3fhc9Q==
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue