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": "صورة",
|
||||
"uploadImage": "تحميل صورة"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "القائمة",
|
||||
"new": "جديد",
|
||||
"publish": "انشر",
|
||||
"shareLink": "",
|
||||
"extra": "إضافي",
|
||||
"revision": "مراجعة",
|
||||
"slideMode": "نمط الشرائح التقديمية",
|
||||
|
|
|
@ -124,10 +124,10 @@
|
|||
"image": "Imatge",
|
||||
"uploadImage": "Pujar imatge"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Menú",
|
||||
"new": "Nou",
|
||||
"publish": "Publicar",
|
||||
"shareLink": "",
|
||||
"extra": "Extra",
|
||||
"revision": "Revisió",
|
||||
"slideMode": "Mode presentació",
|
||||
|
|
|
@ -124,10 +124,10 @@
|
|||
"image": "Obrázek",
|
||||
"uploadImage": "Nahrát obrázek"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Menu",
|
||||
"new": "Nová",
|
||||
"publish": "Publikovat",
|
||||
"shareLink": "",
|
||||
"extra": "Extra",
|
||||
"revision": "Revize",
|
||||
"slideMode": "Režim prezentace",
|
||||
|
|
|
@ -108,10 +108,10 @@
|
|||
"image": "Billede",
|
||||
"uploadImage": "Upload billede"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Menu",
|
||||
"new": "Ny",
|
||||
"publish": "Publicér",
|
||||
"shareLink": "",
|
||||
"extra": "Ekstra",
|
||||
"revision": "Revision",
|
||||
"slideMode": "Præsentationstilstand",
|
||||
|
|
|
@ -124,10 +124,10 @@
|
|||
"image": "Foto",
|
||||
"uploadImage": "Foto hochladen"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Menü",
|
||||
"new": "Neu",
|
||||
"publish": "Veröffentlichen",
|
||||
"shareLink": "Link teilen",
|
||||
"extra": "Extra",
|
||||
"revision": "Version",
|
||||
"slideMode": "Präsentationsmodus",
|
||||
|
|
|
@ -108,10 +108,10 @@
|
|||
"image": "Εικόνα",
|
||||
"uploadImage": "Ανέβασμα φωτογραφίας"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Μενού",
|
||||
"new": "Νέο",
|
||||
"publish": "Δημοσίευση",
|
||||
"shareLink": "",
|
||||
"extra": "Επιπλέον",
|
||||
"revision": "Αναθεώρηση",
|
||||
"slideMode": "Λειτουργία με σύρσιμο",
|
||||
|
|
|
@ -219,21 +219,41 @@
|
|||
"uploadImage": "Upload Image",
|
||||
"table": "Table",
|
||||
"line": "Horizontal line",
|
||||
"comment": "Comment"
|
||||
"comment": "Comment",
|
||||
"preferences": "Editor settings",
|
||||
"emoji": "Open emoji picker"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Menu",
|
||||
"import": "Import",
|
||||
"export": "Export",
|
||||
"new": "New",
|
||||
"publish": "Publish",
|
||||
"shareLink": "Share link",
|
||||
"extra": "Extra",
|
||||
"revision": "Revision",
|
||||
"slideMode": "Slide Mode",
|
||||
"download": "Download",
|
||||
"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": {
|
||||
"rawHtml": "Raw HTML"
|
||||
"rawHtml": "Raw HTML",
|
||||
"pdf": "PDF export is unavailable.",
|
||||
"why": "Why?"
|
||||
},
|
||||
"import": {
|
||||
"clipboard": "Clipboard"
|
||||
|
@ -244,6 +264,13 @@
|
|||
"selectProject": "Select From Available Projects",
|
||||
"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": {
|
||||
"title": "Import from Gist",
|
||||
"insertGistUrl": "Paste your gist url here…"
|
||||
|
@ -265,6 +292,21 @@
|
|||
"question": "Do you really want to delete this note?",
|
||||
"warning": "All users will lose their connection. This process is irreversible.",
|
||||
"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": {
|
||||
|
@ -280,7 +322,8 @@
|
|||
"close": "Close",
|
||||
"save": "Save",
|
||||
"or": "or",
|
||||
"and": "and"
|
||||
"and": "and",
|
||||
"avatarOf": "avatar of '{{name}}'"
|
||||
},
|
||||
"login": {
|
||||
"chooseMethod": "Choose method",
|
||||
|
|
|
@ -108,10 +108,10 @@
|
|||
"image": "Bildo",
|
||||
"uploadImage": "Alŝutu bildon"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Menuo",
|
||||
"new": "Nova",
|
||||
"publish": "Dissendu",
|
||||
"shareLink": "",
|
||||
"extra": "Plia",
|
||||
"revision": "Versio",
|
||||
"slideMode": "Bildvica modo",
|
||||
|
|
|
@ -124,10 +124,10 @@
|
|||
"image": "Imagen",
|
||||
"uploadImage": "Subir imagen"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Menú",
|
||||
"new": "Nuevo",
|
||||
"publish": "Publicar",
|
||||
"shareLink": "",
|
||||
"extra": "Extra",
|
||||
"revision": "Revision",
|
||||
"slideMode": "Modo presentación",
|
||||
|
|
|
@ -124,10 +124,10 @@
|
|||
"image": "Image",
|
||||
"uploadImage": "Téléverser une image"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Menu",
|
||||
"new": "Nouvelle",
|
||||
"publish": "Publier",
|
||||
"shareLink": "",
|
||||
"extra": "Extra",
|
||||
"revision": "Historique",
|
||||
"slideMode": "Mode présentation",
|
||||
|
|
|
@ -108,10 +108,10 @@
|
|||
"image": "तस्वीर",
|
||||
"uploadImage": "तस्वीर डालिये"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "मेन्यू",
|
||||
"new": "नया",
|
||||
"publish": "प्रकाशित करें",
|
||||
"shareLink": "",
|
||||
"extra": "अतिरिक्त",
|
||||
"revision": "संशोधन",
|
||||
"slideMode": "स्लाइड मोड",
|
||||
|
|
|
@ -108,10 +108,10 @@
|
|||
"image": "Slika",
|
||||
"uploadImage": "Prenesi sliku"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Meni",
|
||||
"new": "Novo",
|
||||
"publish": "Objavi",
|
||||
"shareLink": "",
|
||||
"extra": "Dodatno",
|
||||
"revision": "Revizija",
|
||||
"slideMode": "Način slajda",
|
||||
|
|
|
@ -124,10 +124,10 @@
|
|||
"image": "Gambar",
|
||||
"uploadImage": "Unggah Gambar"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Menu",
|
||||
"new": "Baru",
|
||||
"publish": "Terbitkan",
|
||||
"shareLink": "",
|
||||
"extra": "Tambahan",
|
||||
"revision": "Revisi",
|
||||
"slideMode": "Mode Slide",
|
||||
|
|
|
@ -124,10 +124,10 @@
|
|||
"image": "Immagine",
|
||||
"uploadImage": "Carica Immagine"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Menu",
|
||||
"new": "Nuovo",
|
||||
"publish": "Pubblica",
|
||||
"shareLink": "",
|
||||
"extra": "Extra",
|
||||
"revision": "Revisione",
|
||||
"slideMode": "Modalità slide",
|
||||
|
|
|
@ -124,10 +124,10 @@
|
|||
"image": "画像",
|
||||
"uploadImage": "画像をアップロード"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "メニュー",
|
||||
"new": "新規作成",
|
||||
"publish": "公開する",
|
||||
"shareLink": "",
|
||||
"extra": "その他",
|
||||
"revision": "編集履歴",
|
||||
"slideMode": "スライドモード",
|
||||
|
|
|
@ -120,10 +120,10 @@
|
|||
"image": "이미지",
|
||||
"uploadImage": "이미지 업로드"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "메뉴",
|
||||
"new": "새",
|
||||
"publish": "공개하기",
|
||||
"shareLink": "",
|
||||
"extra": "추가",
|
||||
"revision": "기록",
|
||||
"slideMode": "슬라이드 모드",
|
||||
|
|
|
@ -124,10 +124,10 @@
|
|||
"image": "Afbeelding",
|
||||
"uploadImage": "Afbeelding uploaden"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Menu",
|
||||
"new": "Nieuw",
|
||||
"publish": "Publiceren",
|
||||
"shareLink": "",
|
||||
"extra": "Extra",
|
||||
"revision": "Versie",
|
||||
"slideMode": "Presentatiemodus",
|
||||
|
|
|
@ -124,10 +124,10 @@
|
|||
"image": "Zdjęcie",
|
||||
"uploadImage": "Prześlij zdjęcie"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Menu",
|
||||
"new": "Nowy",
|
||||
"publish": "Publikuj",
|
||||
"shareLink": "",
|
||||
"extra": "Ekstra",
|
||||
"revision": "Korekta",
|
||||
"slideMode": "Tryb slajdów",
|
||||
|
|
|
@ -124,10 +124,10 @@
|
|||
"image": "Imagem",
|
||||
"uploadImage": "Carregar Imagem"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Menu",
|
||||
"new": "Novo",
|
||||
"publish": "Publicar",
|
||||
"shareLink": "",
|
||||
"extra": "Extra",
|
||||
"revision": "Revisão",
|
||||
"slideMode": "Modo Apresentação",
|
||||
|
|
|
@ -124,10 +124,10 @@
|
|||
"image": "Изображение",
|
||||
"uploadImage": "Загрузить изображение"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Меню",
|
||||
"new": "Новая",
|
||||
"publish": "Опубликовать",
|
||||
"shareLink": "",
|
||||
"extra": "Дополнительно",
|
||||
"revision": "Изменения",
|
||||
"slideMode": "Режим слайдера",
|
||||
|
|
|
@ -124,10 +124,10 @@
|
|||
"image": "Obrázok",
|
||||
"uploadImage": "Nahrať obrázok"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Menu",
|
||||
"new": "Nová",
|
||||
"publish": "Publikovať",
|
||||
"shareLink": "",
|
||||
"extra": "Extra",
|
||||
"revision": "Revízia",
|
||||
"slideMode": "Prezentačný režim",
|
||||
|
|
|
@ -123,10 +123,10 @@
|
|||
"image": "Слика",
|
||||
"uploadImage": "Пошаљи слику"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Мени",
|
||||
"new": "Ново",
|
||||
"publish": "Објави",
|
||||
"shareLink": "",
|
||||
"extra": "Додатно",
|
||||
"revision": "Ревизија",
|
||||
"slideMode": "Презентациони мод",
|
||||
|
|
|
@ -124,10 +124,10 @@
|
|||
"image": "Bild",
|
||||
"uploadImage": "Ladda upp bilder"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Meny",
|
||||
"new": "Ny",
|
||||
"publish": "Publicera",
|
||||
"shareLink": "",
|
||||
"extra": "Extra",
|
||||
"revision": "Revision",
|
||||
"slideMode": "Slide Mode",
|
||||
|
|
|
@ -108,10 +108,10 @@
|
|||
"image": "Resim",
|
||||
"uploadImage": "Resim Yükle"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Menü",
|
||||
"new": "Yeni",
|
||||
"publish": "Yayınla",
|
||||
"shareLink": "",
|
||||
"extra": "Ekstra",
|
||||
"revision": "Sürüm",
|
||||
"slideMode": "Slayt Modu",
|
||||
|
|
|
@ -108,10 +108,10 @@
|
|||
"image": "Зображення",
|
||||
"uploadImage": "Завантажити зображення"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Меню",
|
||||
"new": "Нова",
|
||||
"publish": "Опублікувати",
|
||||
"shareLink": "",
|
||||
"extra": "Дотатково",
|
||||
"revision": "Ревізія",
|
||||
"slideMode": "Режим слайдера",
|
||||
|
|
|
@ -123,10 +123,10 @@
|
|||
"image": "Ảnh",
|
||||
"uploadImage": "Tải ảnh lên"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "Menu",
|
||||
"new": "Mới",
|
||||
"publish": "Xuất bản",
|
||||
"shareLink": "",
|
||||
"extra": "Extra",
|
||||
"revision": "Sửa đổi",
|
||||
"slideMode": "Chế độ slide",
|
||||
|
|
|
@ -124,10 +124,10 @@
|
|||
"image": "图片",
|
||||
"uploadImage": "上传图片"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "菜单",
|
||||
"new": "新建",
|
||||
"publish": "发表",
|
||||
"shareLink": "",
|
||||
"extra": "附加功能",
|
||||
"revision": "修订版本",
|
||||
"slideMode": "幻灯模式",
|
||||
|
|
|
@ -124,10 +124,10 @@
|
|||
"image": "圖片",
|
||||
"uploadImage": "上傳圖片"
|
||||
},
|
||||
"menu": {
|
||||
"documentBar": {
|
||||
"menu": "選單",
|
||||
"new": "新增",
|
||||
"publish": "發表",
|
||||
"shareLink": "",
|
||||
"extra": "增益",
|
||||
"revision": "修訂版本",
|
||||
"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 { 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 {
|
||||
version: string
|
||||
export interface CopyableFieldProps {
|
||||
content: string
|
||||
}
|
||||
|
||||
export const VersionInputField: React.FC<VersionInputFieldProps> = ({ version }) => {
|
||||
export const CopyableField: React.FC<CopyableFieldProps> = ({ content }) => {
|
||||
useTranslation()
|
||||
const inputField = useRef<HTMLInputElement>(null)
|
||||
const [showCopiedTooltip, setShowCopiedTooltip] = useState(false)
|
||||
|
||||
const copyToClipboard = (content: string) => {
|
||||
const copyToClipboard = useCallback((content: string) => {
|
||||
navigator.clipboard.writeText(content).then(() => {
|
||||
setShowCopiedTooltip(true)
|
||||
setTimeout(() => { setShowCopiedTooltip(false) }, 2000)
|
||||
}).catch(() => {
|
||||
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 (
|
||||
<Fragment>
|
||||
<Overlay target={inputField} show={showCopiedTooltip} placement="top">
|
||||
{(props) => (
|
||||
<Tooltip id={'copied_' + version} {...props}>
|
||||
<Tooltip id={'copied_' + content} {...props}>
|
||||
<Trans i18nKey={'landing.versionInfo.successfullyCopied'}/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Overlay>
|
||||
|
||||
<InputGroup className="mb-3">
|
||||
<FormControl readOnly={true} ref={inputField} className={'text-center'} value={version} />
|
||||
<InputGroup className="my-3">
|
||||
<FormControl readOnly={true} ref={inputField} className={'text-center'} value={content} onMouseEnter={selectContent} />
|
||||
<InputGroup.Append>
|
||||
<Button variant="outline-secondary" onClick={() => copyToClipboard(version)} title={'Copy'}>
|
||||
<Button variant="outline-secondary" onClick={() => copyToClipboard(content)} title={'Copy'}>
|
||||
<ForkAwesomeIcon icon='files-o'/>
|
||||
</Button>
|
||||
</InputGroup.Append>
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
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 './connection-indicator.scss'
|
||||
import { UserLine } from './user-line'
|
||||
|
@ -8,9 +8,9 @@ import { UserLine } from './user-line'
|
|||
const ConnectionIndicator: React.FC = () => {
|
||||
const userOnline = 2
|
||||
return (
|
||||
<Dropdown className="small" alignRight>
|
||||
<Dropdown className="small mx-2" alignRight>
|
||||
<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.Menu>
|
||||
<Dropdown.Item disabled={true} className="d-flex align-items-center p-0">
|
|
@ -1,5 +1,5 @@
|
|||
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 './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/addon/display/fullscreen.css';
|
||||
@import '../../../../node_modules/codemirror/addon/dialog/dialog.css';
|
||||
@import '../../../../node_modules/codemirror/theme/neat.css';
|
||||
@import './one-dark.css';
|
||||
@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/dialog/dialog'
|
||||
import 'codemirror/addon/display/autorefresh'
|
||||
import 'codemirror/addon/display/fullscreen'
|
||||
import 'codemirror/addon/display/placeholder'
|
||||
|
@ -11,16 +12,21 @@ import 'codemirror/addon/edit/matchtags'
|
|||
import 'codemirror/addon/fold/foldcode'
|
||||
import 'codemirror/addon/fold/foldgutter'
|
||||
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/selection/active-line'
|
||||
import 'codemirror/keymap/sublime.js'
|
||||
import 'codemirror/mode/gfm/gfm.js'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import 'codemirror/keymap/sublime'
|
||||
import 'codemirror/keymap/emacs'
|
||||
import 'codemirror/keymap/vim'
|
||||
import 'codemirror/mode/gfm/gfm'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { Controlled as ControlledCodeMirror } from 'react-codemirror2'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import './editor-window.scss'
|
||||
import { emojiHints, emojiWordRegex, findWordAtCursor } from './hints/emoji'
|
||||
import { defaultKeyMap } from './key-map'
|
||||
import { createStatusInfo, defaultState, StatusBar, StatusBarInfo } from './status-bar/status-bar'
|
||||
import { ToolBar } from './tool-bar/tool-bar'
|
||||
|
||||
export interface EditorWindowProps {
|
||||
|
@ -45,30 +51,33 @@ const onChange = (editor: Editor) => {
|
|||
export const EditorWindow: React.FC<EditorWindowProps> = ({ onContentChange, content }) => {
|
||||
const { t } = useTranslation()
|
||||
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) => {
|
||||
onContentChange(value)
|
||||
}, [onContentChange])
|
||||
|
||||
return (
|
||||
<div className={'d-flex flex-column h-100'}>
|
||||
<ToolBar
|
||||
editor={editor}
|
||||
/>
|
||||
<ControlledCodeMirror
|
||||
className="overflow-hidden w-100 flex-fill"
|
||||
value={content}
|
||||
options={{
|
||||
const onEditorDidMount = useCallback(mountedEditor => {
|
||||
setStatusBarInfo(createStatusInfo(mountedEditor))
|
||||
setEditor(mountedEditor)
|
||||
}, [])
|
||||
const onCursorActivity = useCallback((editorWithActivity) => {
|
||||
setStatusBarInfo(createStatusInfo(editorWithActivity))
|
||||
}, [])
|
||||
const codeMirrorOptions: EditorConfiguration = useMemo<EditorConfiguration>(() => ({
|
||||
...editorPreferences,
|
||||
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,
|
||||
|
@ -87,13 +96,26 @@ export const EditorWindow: React.FC<EditorWindowProps> = ({ onContentChange, con
|
|||
addModeClass: true,
|
||||
autoRefresh: true,
|
||||
// otherCursors: true,
|
||||
placeholder: t('editor.placeholder'),
|
||||
showHint: false,
|
||||
hintOptions: hintOptions
|
||||
}}
|
||||
editorDidMount={mountedEditor => setEditor(mountedEditor)}
|
||||
onBeforeChange={onBeforeChange}
|
||||
placeholder: t('editor.placeholder')
|
||||
}), [t, editorPreferences])
|
||||
|
||||
return (
|
||||
<div className={'d-flex flex-column h-100'}>
|
||||
<ToolBar
|
||||
editor={editor}
|
||||
onPreferencesChange={config => setEditorPreferences(config)}
|
||||
editorPreferences={editorPreferences}
|
||||
/>
|
||||
<ControlledCodeMirror
|
||||
className="overflow-hidden w-100 flex-fill"
|
||||
value={content}
|
||||
options={codeMirrorOptions}
|
||||
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 {
|
||||
border: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.divider {
|
||||
background-color: #e2e6ea;
|
||||
width: 2px;
|
||||
padding: 0.25rem 0;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { Editor } from 'codemirror'
|
||||
import React, { Fragment, useState } from 'react'
|
||||
import { Button, ButtonToolbar } from 'react-bootstrap'
|
||||
import { Editor, EditorConfiguration } from 'codemirror'
|
||||
import React, { Fragment } from 'react'
|
||||
import { Button, ButtonGroup, ButtonToolbar } 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 { EditorPreferences } from './editor-preferences/editor-preferences'
|
||||
import { EmojiPickerButton } from './emoji-picker-button'
|
||||
import './tool-bar.scss'
|
||||
import {
|
||||
addCodeFences,
|
||||
addComment,
|
||||
addEmoji,
|
||||
addHeaderLevel,
|
||||
addImage,
|
||||
addLine,
|
||||
|
@ -28,11 +28,12 @@ import {
|
|||
|
||||
export interface ToolBarProps {
|
||||
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 [showEmojiPicker, setShowEmojiPicker] = useState(false)
|
||||
|
||||
const notImplemented = () => {
|
||||
alert('This feature is not yet implemented')
|
||||
|
@ -44,7 +45,8 @@ export const ToolBar: React.FC<ToolBarProps> = ({ editor }) => {
|
|||
|
||||
return (
|
||||
<Fragment>
|
||||
<ButtonToolbar className='flex-nowrap bg-light'>
|
||||
<ButtonToolbar className='bg-light'>
|
||||
<ButtonGroup className={'mx-2 flex-wrap'}>
|
||||
<Button variant='light' onClick={() => makeSelectionBold(editor)} title={t('editor.editorToolbar.bold')}>
|
||||
<ForkAwesomeIcon icon="bold"/>
|
||||
</Button>
|
||||
|
@ -63,6 +65,9 @@ export const ToolBar: React.FC<ToolBarProps> = ({ editor }) => {
|
|||
<Button variant='light' onClick={() => superscriptSelection(editor)} title={t('editor.editorToolbar.superscript')}>
|
||||
<ForkAwesomeIcon icon="superscript"/>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<span className={'divider'}> </span>
|
||||
<ButtonGroup className={'mx-2 flex-wrap'}>
|
||||
<Button variant='light' onClick={() => addHeaderLevel(editor)} title={t('editor.editorToolbar.header')}>
|
||||
<ForkAwesomeIcon icon="header"/>
|
||||
</Button>
|
||||
|
@ -81,6 +86,9 @@ export const ToolBar: React.FC<ToolBarProps> = ({ editor }) => {
|
|||
<Button variant='light' onClick={() => addTaskList(editor)} title={t('editor.editorToolbar.checkList')}>
|
||||
<ForkAwesomeIcon icon="check-square"/>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<span className={'divider'}> </span>
|
||||
<ButtonGroup className={'mx-2 flex-wrap'}>
|
||||
<Button variant='light' onClick={() => addLink(editor)} title={t('editor.editorToolbar.link')}>
|
||||
<ForkAwesomeIcon icon="link"/>
|
||||
</Button>
|
||||
|
@ -90,6 +98,9 @@ export const ToolBar: React.FC<ToolBarProps> = ({ editor }) => {
|
|||
<Button variant='light' onClick={notImplemented} title={t('editor.editorToolbar.uploadImage')}>
|
||||
<ForkAwesomeIcon icon="upload"/>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<span className={'divider'}> </span>
|
||||
<ButtonGroup className={'mx-2 flex-wrap'}>
|
||||
<Button variant='light' onClick={() => addTable(editor)} title={t('editor.editorToolbar.table')}>
|
||||
<ForkAwesomeIcon icon="table"/>
|
||||
</Button>
|
||||
|
@ -99,13 +110,12 @@ export const ToolBar: React.FC<ToolBarProps> = ({ editor }) => {
|
|||
<Button variant='light' onClick={() => addComment(editor)} title={t('editor.editorToolbar.comment')}>
|
||||
<ForkAwesomeIcon icon="comment"/>
|
||||
</Button>
|
||||
<EmojiPicker show={showEmojiPicker} onEmojiSelected={(emoji) => {
|
||||
setShowEmojiPicker(false)
|
||||
addEmoji(emoji, editor)
|
||||
}} onDismiss={() => setShowEmojiPicker(false)}/>
|
||||
<Button variant='light' onClick={() => setShowEmojiPicker(old => !old)} title={''}>
|
||||
<ForkAwesomeIcon icon="smile-o"/>
|
||||
</Button>
|
||||
<EmojiPickerButton editor={editor}/>
|
||||
</ButtonGroup>
|
||||
<span className={'divider'}> </span>
|
||||
<ButtonGroup className={'mx-2 flex-wrap'}>
|
||||
<EditorPreferences onPreferencesChange={onPreferencesChange} preferences={editorPreferences}/>
|
||||
</ButtonGroup>
|
||||
</ButtonToolbar>
|
||||
</Fragment>
|
||||
)
|
||||
|
|
|
@ -7,6 +7,7 @@ import { setEditorModeConfig } from '../../redux/editor/methods'
|
|||
import { DocumentTitle } from '../common/document-title/document-title'
|
||||
import { Splitter } from '../common/splitter/splitter'
|
||||
import { InfoBanner } from '../landing/layout/info-banner'
|
||||
import { DocumentBar } from './document-bar/document-bar'
|
||||
import { EditorWindow } from './editor-window/editor-window'
|
||||
import { editorTestContent } from './editorTestContent'
|
||||
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 { YAMLMetaData } from './yaml-metadata/yaml-metadata'
|
||||
|
||||
export interface EditorPathParams {
|
||||
id: string
|
||||
}
|
||||
|
||||
export const Editor: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const untitledNote = t('editor.untitledNote')
|
||||
|
@ -61,11 +66,22 @@ export const Editor: React.FC = () => {
|
|||
<DocumentTitle title={documentTitle}/>
|
||||
<div className={'d-flex flex-column vh-100'}>
|
||||
<TaskBar/>
|
||||
<DocumentBar title={documentTitle}/>
|
||||
<Splitter
|
||||
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)}
|
||||
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'}/>
|
||||
</div>
|
||||
</Fragment>
|
||||
|
|
|
@ -50,4 +50,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -52,11 +52,11 @@
|
|||
.markdown-toc-sidebar-button {
|
||||
position: fixed;
|
||||
right: 40px;
|
||||
bottom: 20px;
|
||||
bottom: 30px;
|
||||
|
||||
&>.dropup {
|
||||
position: sticky;
|
||||
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)
|
||||
return (
|
||||
<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}>
|
||||
<ForkAwesomeIcon icon="question-circle"/>
|
||||
</Button>
|
||||
<Modal show={show} onHide={handleClose} animation={true} className="text-dark" size='lg'>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<ForkAwesomeIcon icon="question-circle"/> <Trans i18nKey={'editor.menu.help'}/>
|
||||
<ForkAwesomeIcon icon="question-circle"/> <Trans i18nKey={'editor.documentBar.help'}/>
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body className="text-dark">
|
||||
|
@ -67,6 +67,7 @@ export const HelpButton: React.FC = () => {
|
|||
</Card.Text>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
<br/>
|
||||
<Card>
|
||||
<Card.Header><Trans i18nKey='editor.help.documents.title'/></Card.Header>
|
||||
<Card.Body>
|
||||
|
|
|
@ -1,17 +1,25 @@
|
|||
import React from 'react'
|
||||
import { Button, Nav, Navbar } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useParams } from 'react-router'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { ApplicationState } from '../../../redux'
|
||||
import { Branding } from '../../common/branding/branding'
|
||||
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 { EditorMenu } from './editor-menu'
|
||||
import { EditorViewMode } from './editor-view-mode'
|
||||
import { HelpButton } from './help-button'
|
||||
|
||||
const TaskBar: React.FC = () => {
|
||||
useTranslation()
|
||||
export const TaskBar: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const { id } = useParams<EditorPathParams>()
|
||||
const user = useSelector((state: ApplicationState) => state.user)
|
||||
|
||||
return (
|
||||
<Navbar bg={'light'}>
|
||||
<Nav className="mr-auto d-flex align-items-center">
|
||||
|
@ -24,24 +32,24 @@ const TaskBar: React.FC = () => {
|
|||
</Navbar.Brand>
|
||||
<EditorViewMode/>
|
||||
<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/>
|
||||
</Nav>
|
||||
<Nav className="d-flex align-items-center text-secondary">
|
||||
<Button className="ml-2 text-secondary" size="sm" variant="outline-light">
|
||||
<ForkAwesomeIcon icon="plus"/> <Trans i18nKey="editor.menu.new"/>
|
||||
<Button className="mx-2" size="sm" variant="primary">
|
||||
<ForkAwesomeIcon icon="plus"/> <Trans i18nKey="editor.documentBar.new"/>
|
||||
</Button>
|
||||
<Button className="ml-2 text-secondary" size="sm" variant="outline-light">
|
||||
<ForkAwesomeIcon icon="share-square-o"/> <Trans i18nKey="editor.menu.publish"/>
|
||||
</Button>
|
||||
<div className="text-secondary">
|
||||
<EditorMenu/>
|
||||
</div>
|
||||
<div className="mr-2">
|
||||
<ConnectionIndicator/>
|
||||
</div>
|
||||
<ShowIf condition={!user}>
|
||||
<SignInButton size={'sm'} />
|
||||
</ShowIf>
|
||||
<ShowIf condition={!!user}>
|
||||
<UserDropdown />
|
||||
</ShowIf>
|
||||
</Nav>
|
||||
</Navbar>
|
||||
)
|
||||
}
|
||||
|
||||
export { TaskBar }
|
||||
|
|
|
@ -1,22 +1,28 @@
|
|||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ShowIf } from '../../../common/show-if/show-if'
|
||||
import './user-avatar.scss'
|
||||
|
||||
export interface UserAvatarProps {
|
||||
name: string;
|
||||
photo: string;
|
||||
additionalClasses?: string;
|
||||
showName?: boolean
|
||||
}
|
||||
|
||||
const UserAvatar: React.FC<UserAvatarProps> = ({ name, photo, additionalClasses = '' }) => {
|
||||
// ToDo: add Translation Key for Avatar of ${name}
|
||||
const UserAvatar: React.FC<UserAvatarProps> = ({ name, photo, additionalClasses = '', showName = true }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<span className={'d-inline-flex align-items-center ' + additionalClasses}>
|
||||
<img
|
||||
src={photo}
|
||||
className="user-avatar"
|
||||
alt={`Avatar of ${name}`}
|
||||
className="user-avatar rounded"
|
||||
alt={t('common.avatarOf', { name })}
|
||||
/>
|
||||
<ShowIf condition={showName}>
|
||||
<span className="mx-1 user-name">{name}</span>
|
||||
</ShowIf>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { ApplicationState } from '../../../../redux'
|
|||
import frontendVersion from '../../../../version.json'
|
||||
import { TranslatedExternalLink } from '../../../common/links/translated-external-link'
|
||||
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 = () => {
|
||||
const [show, setShow] = useState(false)
|
||||
|
@ -22,7 +22,7 @@ export const VersionInfo: React.FC = () => {
|
|||
const column = (title: string, version: string, sourceCodeLink: string, issueTrackerLink: string) => (
|
||||
<Col md={6} className={'flex-column'}>
|
||||
<h5>{title}</h5>
|
||||
<VersionInputField version={version}/>
|
||||
<CopyableField content={version}/>
|
||||
<ShowIf condition={!!sourceCodeLink}>
|
||||
<TranslatedExternalLink i18nKey={'landing.versionInfo.sourceCode'} className={'btn btn-sm btn-primary d-block mb-2'} href={sourceCodeLink}/>
|
||||
</ShowIf>
|
||||
|
|
|
@ -13,19 +13,20 @@ export interface DropdownItemWithDeletionModalProps {
|
|||
modalQuestionI18nKey: string
|
||||
modalWarningI18nKey: string
|
||||
noteTitle: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const DropdownItemWithDeletionModal: React.FC<DropdownItemWithDeletionModalProps> = ({
|
||||
onConfirm, noteTitle,
|
||||
modalTitleI18nKey, modalButtonI18nKey, itemI18nKey, modalIcon,
|
||||
modalQuestionI18nKey, modalWarningI18nKey
|
||||
modalQuestionI18nKey, modalWarningI18nKey, className
|
||||
}) => {
|
||||
useTranslation()
|
||||
const [showDialog, setShowDialog] = useState(false)
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Dropdown.Item onClick={() => setShowDialog(true)}>
|
||||
<Dropdown.Item onClick={() => setShowDialog(true)} className={className}>
|
||||
<ForkAwesomeIcon icon={modalIcon} fixedWidth={true} className="mx-2"/>
|
||||
<Trans i18nKey={itemI18nKey}/>
|
||||
</Dropdown.Item>
|
||||
|
|
|
@ -4186,7 +4186,7 @@ cyclist@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
||||
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
|
||||
|
||||
cypress@*, cypress@4.12.1:
|
||||
cypress@4.12.1:
|
||||
version "4.12.1"
|
||||
resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.12.1.tgz#0ead1b9f4c0917d69d8b57f996b6e01fe693b6ec"
|
||||
integrity sha512-9SGIPEmqU8vuRA6xst2CMTYd9sCFCxKSzrHt0wr+w2iAQMCIIsXsQ5Gplns1sT6LDbZcmLv6uehabAOl3fhc9Q==
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue