Browse Source

Build 342
* Simplification Guide compliance
* Unsaved data warning
* A little UI cleanup

Aleksandr Bashurov 1 year ago
parent
commit
1f556ea9ff
6 changed files with 1201 additions and 644 deletions
  1. 4 3
      package.json
  2. 244 269
      src/App.js
  3. 35 17
      src/components/DropdownHolder.js
  4. 1 9
      src/components/Menubar.js
  5. 1 1
      src/views/TriggerListModal.js
  6. 916 345
      yarn.lock

+ 4 - 3
package.json

@@ -9,12 +9,13 @@
     "noty": "3.2.0-beta",
     "prop-types": "^15.6.0",
     "react": "^16.1.1",
+    "react-beforeunload": "^1.0.4",
     "react-dom": "^16.1.1",
     "react-helmet": "^5.2.0",
-    "react-scripts": "1.1.0",
+    "react-scripts": "1.1.1",
     "react-splitter-layout": "^3.0.0",
-    "semantic-ui-css": "^2.2.12",
-    "semantic-ui-react": "0.77.2",
+    "semantic-ui-css": "2.1.8",
+    "semantic-ui-react": "0.78.3",
     "turndown": "^4.0.0-rc.2"
   },
   "scripts": {

+ 244 - 269
src/App.js

@@ -1,309 +1,284 @@
-import React, { Component } from 'react';
-import { Container } from 'semantic-ui-react';
-import Noty from 'noty';
-import SplitterLayout from 'react-splitter-layout';
-import Menubar from './components/Menubar';
-import TriggerListModal from './views/TriggerListModal';
-import TurndownService from 'turndown';
-import MarkdownIt from 'markdown-it';
-import copy from 'copy-to-clipboard';
+import React, { Component } from 'react'
+import { Container } from 'semantic-ui-react'
+import Noty from 'noty'
+import SplitterLayout from 'react-splitter-layout'
+import Menubar from './components/Menubar'
+import TriggerListModal from './views/TriggerListModal'
+import TurndownService from 'turndown'
+import MarkdownIt from 'markdown-it'
+import copy from 'copy-to-clipboard'
+import Beforeunload from 'react-beforeunload'
 
 const kbHandleProto = (...keys) => ((...v) => keys.reduce((o, k, i) => {o[k] = v[i]; return o} , {}));
 const kbHandle = kbHandleProto('ctrlState', 'shiftState', 'kbCode', 'callback' );
 
 class App extends Component {
-	state = {
-		helpIsOpen: false,
-		text: ''
-	}
+    state = {
+        helpIsOpen: false,
+        text: ''
+    }
 
-	// Snippet list for future drop-in replacements
-	snippetList = [
-		'Jönh Döugh was here',
-		'Connect to the server [via SSH](https://support.plesk.com/hc/en-us/articles/115000172834)',
-		'Connect to the server [via RDP](https://support.plesk.com/hc/en-us/articles/360000471413)',
-		'[Log into Plesk](https://support.plesk.com/hc/en-us/articles/213413369)',
-		'[Create Plesk database backup](https://support.plesk.com/hc/en-us/articles/213904125)',
-		'203.0.113.2',
-		'192.0.2.2',
-		'example.com',
-		'![Placeholder](assets/placeholder.png)',
-		'This feature is not yet implemented in Plesk UI, leave a feature request for it [on our UserVoice](https://plesk.uservoice.com/)'
-	]
+    // Snippet list for future drop-in replacements
+    snippetList = [
+        'This feature is not yet implemented in Plesk UI, leave a feature request for it [on our UserVoice](https://plesk.uservoice.com/)',
+        'Connect to the server via [SSH](https://support.plesk.com/hc/en-us/articles/115000172834)',
+        'Connect to the server via [RDP](https://support.plesk.com/hc/en-us/articles/360000471413)',
+        '[Log into Plesk](https://support.plesk.com/hc/en-us/articles/213413369)',
+        '[Create Plesk database backup](https://support.plesk.com/hc/en-us/articles/213904125)',
+        '[Access Plesk database](https://support.plesk.com/hc/en-us/articles/213928465)',
+        '[Edit file](https://support.plesk.com/hc/en-us/articles/360001084114)',
+        '203.0.113.2',
+        '192.0.2.2',
+        '![Placeholder](assets/placeholder.png)'
+    ]
 
-	// Handler list for simpler modifications; ctrlKey, shiftKey, keyCode, callbackfn()
-	kbHandleList = [
-		//Ctrl + Shift
-		kbHandle(true, true, 49, () => { this.insertPrepend('# ') }), // Ctrl + Shift + 1 -> heading 1
-		kbHandle(true, true, 50, () => { this.insertPrepend('## ') }), // Ctrl + Shift + 2 -> heading 2
-		kbHandle(true, true, 51, () => { this.insertPrepend('### ') }), // Ctrl + Shift + 3 -> heading 3
-		kbHandle(true, true, 52, () => { this.insertPrepend('#### ') }), // Ctrl + Shift + 4 -> heading 4
-		kbHandle(true, true, 53, () => { this.insertPrepend('##### ') }), // Ctrl + Shift + 5 -> heading 5
-		kbHandle(true, true, 54, () => { this.insertPrepend('###### ') }), // Ctrl + Shift + 6 -> heading 6
-		kbHandle(true, true, 69, () => { this.insertPrepend('* ') }), // Ctrl + Shift + E -> Unordered list
-		kbHandle(true, true, 72, () => { this.insertDecorate('[','](https://)') }), // Ctrl + Shift + H -> Insert link / linkify
-		kbHandle(true, true, 74, () => { this.insertSimple('PLESK_INFO: ') }), // Ctrl + Shift + J -> PLESK_INFO
-		kbHandle(true, true, 75, () => { this.insertSimple('MYSQL_WIN: ') }), // Ctrl + Shift + K -> MySQL Windows
-		kbHandle(true, true, 76, () => { this.insertSimple('MYSQL_LIN: ') }), // Ctrl + Shift + L -> MySQL Linux
-	
-		//Ctrl
-		kbHandle(true, false, 48, () => { this.insertSimple(this.snippetList[0]) }), // Ctrl + 0 -> snippet 0
-		kbHandle(true, false, 49, () => { this.insertSimple(this.snippetList[1]) }), // Ctrl + 1 -> snippet 1
-		kbHandle(true, false, 50, () => { this.insertSimple(this.snippetList[2]) }), // Ctrl + 2 -> snippet 2
-		kbHandle(true, false, 51, () => { this.insertSimple(this.snippetList[3]) }), // Ctrl + 3 -> snippet 3
-		kbHandle(true, false, 52, () => { this.insertSimple(this.snippetList[4]) }), // Ctrl + 4 -> snippet 4
-		kbHandle(true, false, 53, () => { this.insertSimple(this.snippetList[5]) }), // Ctrl + 5 -> snippet 5
-		kbHandle(true, false, 54, () => { this.insertSimple(this.snippetList[6]) }), // Ctrl + 6 -> snippet 6
-		kbHandle(true, false, 55, () => { this.insertSimple(this.snippetList[7]) }), // Ctrl + 7 -> snippet 7
-		kbHandle(true, false, 56, () => { this.insertSimple(this.snippetList[8]) }), // Ctrl + 8 -> snippet 8 
-		kbHandle(true, false, 57, () => { this.insertSimple(this.snippetList[9]) }), // Ctrl + 9 -> snippet 9
-		kbHandle(true, false, 66, () => { this.insertDecorate('**', '**') }), // Ctrl + B -> Bold font
-		kbHandle(true, false, 68, () => { this.insertSimple('PLESK_WARN: ') }), // Ctrl + D -> PLESK_WARNING
-		kbHandle(true, false, 69, () => { this.insertPrepend('0. ') }), // Ctrl + E -> Ordered list
-		kbHandle(true, false, 72, () => { this.eventHandleDict["help"]() }), // Ctrl + H -> Help dialog
-		kbHandle(true, false, 73, () => { this.insertDecorate('_', '_') }), // Ctrl + I -> Italic font
-		kbHandle(true, false, 74, () => { this.insertSimple('CONFIG_TEXT: ') }), // Ctrl + J -> CONFIG_TEXT
-		kbHandle(true, false, 75, () => { this.insertSimple('C:\\> ') }), // Ctrl + K -> CMD C:\>
-		kbHandle(true, false, 76, () => { this.insertSimple('\\# ') }), // Ctrl + L -> bash \#
-		kbHandle(true, false, 77, () => { this.insertDecorate('`', '`') }), // Ctrl + M -> Monospace (code)
-		kbHandle(true, false, 79, () => { this.insertSimple('PLESK_ERROR: ') }), // Ctrl + O -> PLESK_ERROR
-		kbHandle(true, false, 80, () => { this.insertSimple('PS ') }), // Ctrl + P -> PowerShell PS
-		kbHandle(true, false, 83, () => { this.exportHandler() }), // Ctrl + S -> Export
-		kbHandle(true, false, 85, () => { this.replaceUppercase() }), // Ctrl + U -> Uppercase selection
-	
-		//Shift
-		kbHandle(false, true, 9,  () => { this.removePrepend('\t') }), // Shift + Tab -> Unindent
-		kbHandle(false, true, 13, () => { this.insertSimple('\n\n') }), // Shift + Enter -> Double \n
-	
-		kbHandle(false, false, 9,  () => { this.insertPrepend('\t') }), //Tab -> Indent
-	]
+    // Handler list for simpler modifications; ctrlKey, shiftKey, keyCode, callbackfn()
+    kbHandleList = [
+        //Ctrl + Shift
+        kbHandle(true, true, 49, () => { this.insertPrepend('# ') }), // Ctrl + Shift + 1 -> heading 1
+        kbHandle(true, true, 50, () => { this.insertPrepend('## ') }), // Ctrl + Shift + 2 -> heading 2
+        kbHandle(true, true, 51, () => { this.insertPrepend('### ') }), // Ctrl + Shift + 3 -> heading 3
+        kbHandle(true, true, 52, () => { this.insertPrepend('#### ') }), // Ctrl + Shift + 4 -> heading 4
+        kbHandle(true, true, 53, () => { this.insertPrepend('##### ') }), // Ctrl + Shift + 5 -> heading 5
+        kbHandle(true, true, 54, () => { this.insertPrepend('###### ') }), // Ctrl + Shift + 6 -> heading 6
+        kbHandle(true, true, 69, () => { this.insertPrepend('* ') }), // Ctrl + Shift + E -> Unordered list
+        kbHandle(true, true, 72, () => { this.insertDecorate('[','](https://)') }), // Ctrl + Shift + H -> Insert link / linkify
+        kbHandle(true, true, 74, () => { this.insertSimple('PLESK_INFO: ') }), // Ctrl + Shift + J -> PLESK_INFO
+        kbHandle(true, true, 75, () => { this.insertSimple('MYSQL_WIN: ') }), // Ctrl + Shift + K -> MySQL Windows
+        kbHandle(true, true, 76, () => { this.insertSimple('MYSQL_LIN: ') }), // Ctrl + Shift + L -> MySQL Linux
+    
+        //Ctrl
+        kbHandle(true, false, 48, () => { this.insertSimple(this.snippetList[0]) }), // Ctrl + 0 -> snippet 0
+        kbHandle(true, false, 49, () => { this.insertSimple(this.snippetList[1]) }), // Ctrl + 1 -> snippet 1
+        kbHandle(true, false, 50, () => { this.insertSimple(this.snippetList[2]) }), // Ctrl + 2 -> snippet 2
+        kbHandle(true, false, 51, () => { this.insertSimple(this.snippetList[3]) }), // Ctrl + 3 -> snippet 3
+        kbHandle(true, false, 52, () => { this.insertSimple(this.snippetList[4]) }), // Ctrl + 4 -> snippet 4
+        kbHandle(true, false, 53, () => { this.insertSimple(this.snippetList[5]) }), // Ctrl + 5 -> snippet 5
+        kbHandle(true, false, 54, () => { this.insertSimple(this.snippetList[6]) }), // Ctrl + 6 -> snippet 6
+        kbHandle(true, false, 55, () => { this.insertSimple(this.snippetList[7]) }), // Ctrl + 7 -> snippet 7
+        kbHandle(true, false, 56, () => { this.insertSimple(this.snippetList[8]) }), // Ctrl + 8 -> snippet 8 
+        kbHandle(true, false, 57, () => { this.insertSimple(this.snippetList[9]) }), // Ctrl + 9 -> snippet 9
+        kbHandle(true, false, 66, () => { this.insertDecorate('**', '**') }), // Ctrl + B -> Bold font
+        kbHandle(true, false, 68, () => { this.insertSimple('PLESK_WARN: ') }), // Ctrl + D -> PLESK_WARNING
+        kbHandle(true, false, 69, () => { this.insertPrepend('0. ') }), // Ctrl + E -> Ordered list
+        kbHandle(true, false, 72, () => { this.eventHandleDict["help"]() }), // Ctrl + H -> Help dialog
+        kbHandle(true, false, 73, () => { this.insertDecorate('_', '_') }), // Ctrl + I -> Italic font
+        kbHandle(true, false, 74, () => { this.insertSimple('CONFIG_TEXT: ') }), // Ctrl + J -> CONFIG_TEXT
+        kbHandle(true, false, 75, () => { this.insertSimple('C:\\> ') }), // Ctrl + K -> CMD C:\>
+        kbHandle(true, false, 76, () => { this.insertSimple('\\# ') }), // Ctrl + L -> bash \#
+        kbHandle(true, false, 77, () => { this.insertDecorate('`', '`') }), // Ctrl + M -> Monospace (code)
+        kbHandle(true, false, 79, () => { this.insertSimple('PLESK_ERROR: ') }), // Ctrl + O -> PLESK_ERROR
+        kbHandle(true, false, 80, () => { this.insertSimple('PS ') }), // Ctrl + P -> PowerShell PS
+        kbHandle(true, false, 83, () => { this.exportHandler() }), // Ctrl + S -> Export
+        kbHandle(true, false, 85, () => { this.replaceUppercase() }), // Ctrl + U -> Uppercase selection
+    
+        //Shift
+        kbHandle(false, true, 9,  () => { this.removePrepend('\t') }), // Shift + Tab -> Unindent
+        kbHandle(false, true, 13, () => { this.insertSimple('\n\n') }), // Shift + Enter -> Double \n
+    
+        kbHandle(false, false, 9,  () => { this.insertPrepend('\t') }), //Tab -> Indent
+    ]
 
-	eventHandleDict = {
-		help : () => { this.setState({ helpIsOpen: !this.state.helpIsOpen }) }
-	}
+    eventHandleDict = {
+        help : () => { this.setState({ helpIsOpen: !this.state.helpIsOpen }) }
+    }
 
 /********************************************************************************
  * Markdown parsing and updating methods
  *****************/
 
-	updateSource = (e) => {
-		this.setState({ text: e.target.value })
-	}
+    updateSource = (e) => {
+        this.setState({ text: e.target.value })
+    }
 
-	getMarkdown = () => {
-		let Parser = new MarkdownIt({
-			highlight: function (str, lang) {
-				return '<pre class="' + lang.toLowerCase() + '"><code class="' + lang.toLowerCase() + '">' + Parser.utils.escapeHtml(str) + '</code></pre>';
-			},
-			//langPrefix: '',
-			xhtmlOut: true,
-			breaks: true,
-		})
-		return Parser.render(this.state.text).replace(/<p>C:&gt;/g, '<p>C:\\&gt;')
-	}
+    getMarkdown = () => {
+        let Parser = new MarkdownIt({
+            highlight: function (str, lang) {
+                return '<pre class="' + lang.toLowerCase() + '"><code class="' + lang.toLowerCase() + '">' + Parser.utils.escapeHtml(str) + '</code></pre>';
+            },
+            //langPrefix: '',
+            xhtmlOut: true,
+            breaks: true,
+        })
+        return Parser.render(this.state.text).replace(/<p>C:&gt;/g, '<p>C:\\&gt;')
+    }
 
-	applyStyles = () => {
-		return this.getMarkdown()
-			.replace(/<p># /g, '<p class="bash"># ')
-			.replace(/<p>C:\\&gt;/g, '<p class="winshell">')
-			.replace(/<p>PS /g, '<p class="powershell">PS ')
-			.replace(/<p>PLESK_ERROR:/g, '<p class="pleskerr">')
-			.replace(/<p>PLESK_WARN:/g, '<p class="pleskwarn">')
-			.replace(/<p>PLESK_INFO:/g, '<p class="pleskinfo">')
-			.replace(/<p>MYSQL_LIN:/g, '<p class="bash">')
-			.replace(/<p>MYSQL_WIN:/g, '<p class="winshell">')
-			.replace(/<p>CONFIG_TEXT:/g, '<p class="configtext">')
-			.replace(/<p>Note: /g, '<p class="note"><strong>Note:</strong> ')
-			.replace(/<p><strong>Note:<\/strong> /g, '<p class="note"><strong>Note:</strong> ')
-			.replace(/<p>Warning: /g, '<p class="warning"><strong>Warning:</strong> ')
-			.replace(/<p><strong>Warning:<\/strong> /g, '<p class="warning"><strong>Warning:</strong> ')
-	}
+    applyStyles = () => {
+        return this.getMarkdown()
+            .replace(/<p># /g, '<p class="bash"># ')
+            .replace(/<p>C:\\&gt;/g, '<p class="winshell">')
+            .replace(/<p>PS /g, '<p class="powershell">PS ')
+            .replace(/<p>PLESK_ERROR:/g, '<p class="pleskerr">')
+            .replace(/<p>PLESK_WARN:/g, '<p class="pleskwarn">')
+            .replace(/<p>PLESK_INFO:/g, '<p class="pleskinfo">')
+            .replace(/<p>MYSQL_LIN:/g, '<p class="bash">')
+            .replace(/<p>MYSQL_WIN:/g, '<p class="winshell">')
+            .replace(/<p>CONFIG_TEXT:/g, '<p class="configtext">')
+            .replace(/<p>Note: /g, '<p class="note"><strong>Note:</strong> ')
+            .replace(/<p><strong>Note:<\/strong> /g, '<p class="note"><strong>Note:</strong> ')
+            .replace(/<p>Warning: /g, '<p class="warning"><strong>Warning:</strong> ')
+            .replace(/<p><strong>Warning:<\/strong> /g, '<p class="warning"><strong>Warning:</strong> ')
+    }
 
-	renderMarkdown = () => {
-		return { __html: this.applyStyles() }
-	}
+    renderMarkdown = () => {
+        return { __html: this.applyStyles() }
+    }
 
 /*******************************************************************************/
 /********************************************************************************
  * Text managing methods
  ******************/
-	insertSimple = (text) => {
-		this.textArea.focus()
-		document.execCommand("insertText", false, text)
-	}
+    insertSimple = (text) => {
+        this.textArea.focus()
+        document.execCommand("insertText", false, text)
+    }
 
-	insertPrepend = (text) => {
-		let start = this.textArea.selectionStart
-		let end = this.textArea.selectionEnd
-		let caret = this.textArea.selectionStart - 1
-		while(this.textArea.value[caret] !== '\n' && caret >= 0)
-			caret--
-		this.textArea.selectionStart = caret + 1
-		this.textArea.selectionEnd = caret + 1
-		this.insertSimple(text)
-		this.textArea.selectionStart = start + text.length
-		this.textArea.selectionEnd = end + text.length
-	}
+    insertPrepend = (text) => {
+        let start = this.textArea.selectionStart
+        let end = this.textArea.selectionEnd
+        let caret = this.textArea.selectionStart - 1
+        while(this.textArea.value[caret] !== '\n' && caret >= 0)
+            caret--
+        this.textArea.selectionStart = caret + 1
+        this.textArea.selectionEnd = caret + 1
+        this.insertSimple(text)
+        this.textArea.selectionStart = start + text.length
+        this.textArea.selectionEnd = end + text.length
+    }
 
-	insertReplace = (text) => {
-		this.textArea.setSelectionRange(0, this.textArea.value.length)
-		this.insertSimple(text)
-	}
+    insertReplace = (text) => {
+        this.textArea.setSelectionRange(0, this.textArea.value.length)
+        this.insertSimple(text)
+    }
 
-	insertDecorate = (prepend, append) => {
-		let start = this.textArea.selectionStart
-		let end = this.textArea.selectionEnd
-		this.textArea.focus()
-		let text = prepend + this.textArea.value.slice(this.textArea.selectionStart, this.textArea.selectionEnd) + append
-		document.execCommand("insertText", false, text)
-		if (start !== end)
-			this.textArea.setSelectionRange(start + text.length, start + text.length)
-		else
-			this.textArea.setSelectionRange(start + prepend.length, start + prepend.length)
-	}
+    insertDecorate = (prepend, append) => {
+        let start = this.textArea.selectionStart
+        let end = this.textArea.selectionEnd
+        this.textArea.focus()
+        let text = prepend + this.textArea.value.slice(this.textArea.selectionStart, this.textArea.selectionEnd) + append
+        document.execCommand("insertText", false, text)
+        if (start !== end)
+            this.textArea.setSelectionRange(start + text.length, start + text.length)
+        else
+            this.textArea.setSelectionRange(start + prepend.length, start + prepend.length)
+    }
 
-	removePrepend = (text) => {
-		let start = this.textArea.selectionStart
-		let end = this.textArea.selectionEnd
-		let caret = this.textArea.selectionStart - 1
-		while(this.textArea.value[caret] !== '\n' && caret >= 0)
-			caret--
-		caret++
-		if (this.textArea.value[caret] === text) {
-			this.textArea.setSelectionRange(caret, caret + 1)
-			document.execCommand("insertText", false, '')
-		}
-		this.textArea.selectionStart = start - 1
-		this.textArea.selectionEnd = end - 1
-	}
+    removePrepend = (text) => {
+        let start = this.textArea.selectionStart
+        let end = this.textArea.selectionEnd
+        let caret = this.textArea.selectionStart - 1
+        while(this.textArea.value[caret] !== '\n' && caret >= 0)
+            caret--
+        caret++
+        if (this.textArea.value[caret] === text) {
+            this.textArea.setSelectionRange(caret, caret + 1)
+            document.execCommand("insertText", false, '')
+        }
+        this.textArea.selectionStart = start - 1
+        this.textArea.selectionEnd = end - 1
+    }
 
-	replaceUppercase = () => {
-		document.execCommand("insertText", false, this.textArea.value.slice(this.textArea.selectionStart, this.textArea.selectionEnd).toUpperCase())
-	}
+    replaceUppercase = () => {
+        document.execCommand("insertText", false, this.textArea.value.slice(this.textArea.selectionStart, this.textArea.selectionEnd).toUpperCase())
+    }
 
 
 /*******************************************************************************/
 
 
-	handleItemClick = (e, { name }) => this.setState({ activeItem: name })
+    handleItemClick = (e, { name }) => this.setState({ activeItem: name })
 
-	debugHandler = (e, {name}) => {
-		console.log(e, name);
-	}
+    debugHandler = (e, {name}) => {
+        console.log(e, name);
+    }
 
-	menuGlobalEventHandler = (e, {name}) => {
-		switch (name){
-			case 'newscr':
-				this.insertReplace('## Symptoms\n\n\n## Cause\n\n\n## Resolution\n\n\n')
-				break
-			case 'newqa':
-				this.insertReplace('## Question\n\n\n## Answer\n\n\n')
-				break
-			case 'exscr':
-				fetch('https://api.kcs.rocks/index.php?type=scr')
-					.then((response) => {
-						response.text().then((text) => {
-							this.insertReplace(atob(text))
-						})
-					}).catch((err) => {
-						console.log(err)
-						this.insertReplace("An error happened during fetch: " + err)
-					})
-				break
-			case 'exqa':
-				fetch('https://api.kcs.rocks/index.php?type=qa')
-					.then((response) => {
-						response.text().then((text) => {
-							this.insertReplace(atob(text))
-						})
-					}).catch((err) => {
-						console.log(err)
-						this.insertReplace("An error happened during fetch: " + err)
-					})
-				break
-			case 'loginssh':
-				this.insertSimple(this.snippetList[1])
-				break
-			case 'loginrdp':
-				this.insertSimple(this.snippetList[2])
-				break
-			case 'loginplesk':
-				this.insertSimple(this.snippetList[3])
-				break
-			case 'dobackup':
-				this.insertSimple(this.snippetList[4])
-				break
-			case 'publicip':
-				this.insertSimple(this.snippetList[5])
-				break
-			case 'privateip':
-				this.insertSimple(this.snippetList[6])
-				break
-			case 'exampledomain':
-				this.insertSimple(this.snippetList[7])
-				break
-			case 'image':
-				this.insertSimple(this.snippetList[8])
-				break
-			case 'uservoice':
-				this.insertSimple(this.snippetList[9])
-				break
-			default:
-				console.log('Unknown event: ' + name)
-				break
-		}
-	}
+    menuGlobalEventHandler = (e, {name}) => {
+        if (name === 'newscr') {
+            this.insertReplace('## Symptoms\n\n\n## Cause\n\n\n## Resolution\n\n\n')
+        } else if (name === 'newqa') {
+            this.insertReplace('## Question\n\n\n## Answer\n\n\n')
+        } else if (name === 'exscr') {
+            fetch('https://api.kcs.rocks/index.php?type=scr')
+                .then((response) => {
+                    response.text().then((text) => {
+                        this.insertReplace(atob(text))
+                    })
+                }).catch((err) => {
+                    console.log(err)
+                    this.insertReplace("An error happened during fetch: " + err)
+                })
+        } else if (name === 'exqa') {
+            fetch('https://api.kcs.rocks/index.php?type=qa')
+                .then((response) => {
+                    response.text().then((text) => {
+                        this.insertReplace(atob(text))
+                    })
+                }).catch((err) => {
+                    console.log(err)
+                    this.insertReplace("An error happened during fetch: " + err)
+                })
+        } else if (name.startsWith('sn')) {
+            this.insertSimple(this.snippetList[parseInt(name[2], 10)])
+        } else {
+            console.log('Unknown event: ' + name)
+        }
+    }
 
-	exportHandler = () => { 
-		copy(this.getMarkdown())
-		new Noty({
-			type: 'success',
-			text: 'Copied HTML to the clipboard. You can use it further in Zendesk',
-			theme: 'semanticui'
-		}).show()
-	}
-	convertHandler = (e) => { 
-		let turndown = new TurndownService()
-		this.insertReplace(turndown.turndown(this.textArea.value))
-	}
-	helpHandler = (e) => { 
-		this.setState({ helpIsOpen: !this.state.helpIsOpen })
-	}
+    exportHandler = () => { 
+        copy(this.getMarkdown()
+            .replace(/<p>Note: /g, '<p><strong>Note:</strong> ')
+            .replace(/<p>Warning: /g, '<p><strong>Warning:</strong> '))
+        new Noty({
+            type: 'success',
+            text: 'Copied HTML to the clipboard. You can use it further in Zendesk',
+            theme: 'semanticui',
+            timeout: 5000
+        }).show()
+    }
+    convertHandler = (e) => { 
+        let turndown = new TurndownService()
+        this.insertReplace(turndown.turndown(this.textArea.value))
+    }
+    helpHandler = (e) => { 
+        this.setState({ helpIsOpen: !this.state.helpIsOpen })
+    }
 
-	eventFindCallback = (e) => {
-		return (l) => {
-			return (e.ctrlKey === l.ctrlState && 
-				e.shiftKey === l.shiftState && 
-				e.keyCode === l.kbCode) }
-	}
+    eventFindCallback = (e) => {
+        return (l) => {
+            return (e.ctrlKey === l.ctrlState && 
+                e.shiftKey === l.shiftState && 
+                e.keyCode === l.kbCode) }
+    }
 
-	shortcutHandler = (e) => {
-		if (e.altKey) return // No shortcuts make use of Alt, so we hard fail here, so not to break Compose and alternative layouts
-		let j = this.kbHandleList.filter(this.eventFindCallback(e));
-		if (j.length) { 
-			e.preventDefault();
-			e.stopPropagation();
-			j[0].callback.call();
-		}
-	}
+    shortcutHandler = (e) => {
+        if (e.altKey) return // No shortcuts make use of Alt, so we hard fail here, so not to break Compose and alternative layouts
+        let j = this.kbHandleList.filter(this.eventFindCallback(e));
+        if (j.length) { 
+            e.preventDefault();
+            e.stopPropagation();
+            j[0].callback.call();
+        }
+    }
 
-	render() {
-		return (
-			<Container fluid>
-				<Menubar 
-					exportHandler={this.exportHandler}
-					convertHandler={this.convertHandler} 
-					helpHandler={this.helpHandler}
-					globalHandler={this.menuGlobalEventHandler}
-				/>
-				<SplitterLayout primaryIndex={1} percentage={true} secondaryInitialSize={33}>
-					<div><textarea className='textarea-main' onKeyDown={this.shortcutHandler.bind()} onChange={this.updateSource} defaultValue={this.state.text} ref={(textarea) => { this.textArea = textarea }} /></div>
-					<div className="preview" dangerouslySetInnerHTML={this.renderMarkdown()} />
-				</SplitterLayout>
-				<TriggerListModal isOpen={this.state.helpIsOpen} closeList={this.helpHandler} />
-			</Container>
-		)
-	}
+    render() {
+        return (
+            <Beforeunload onBeforeunload = {() => this.textArea.value ? '' : false }>
+              <Container fluid>
+                <Menubar 
+                    exportHandler={this.exportHandler}
+                    convertHandler={this.convertHandler} 
+                    helpHandler={this.helpHandler}
+                    globalHandler={this.menuGlobalEventHandler}
+                />
+                <SplitterLayout primaryIndex={1} percentage={true} secondaryInitialSize={33}>
+                    <div><textarea className='textarea-main' onKeyDown={this.shortcutHandler.bind()} onChange={this.updateSource} defaultValue={this.state.text} ref={(textarea) => { this.textArea = textarea }} /></div>
+                    <div className="preview" dangerouslySetInnerHTML={this.renderMarkdown()} />
+                </SplitterLayout>
+                <TriggerListModal isOpen={this.state.helpIsOpen} closeList={this.helpHandler} />
+            </Container>
+          </Beforeunload>
+        )
+    }
 }
 
 export default App;

+ 35 - 17
src/components/DropdownHolder.js

@@ -54,7 +54,7 @@ class DropdownSnippet extends Component {
             <Dropdown.Menu>
                 <Popup 
                     trigger={<Dropdown.Item 
-                        name='loginssh'
+                        name='sn1'
                         onClick={this.props.handler}>
                         Login over SSH</Dropdown.Item>} 
                     content='Link to the article about SSH connection' 
@@ -62,7 +62,7 @@ class DropdownSnippet extends Component {
                 />
                 <Popup 
                     trigger={<Dropdown.Item 
-                        name='loginrdp'
+                        name='sn2'
                         onClick={this.props.handler}>
                         Login over RDP</Dropdown.Item>} 
                     content='Link to the article about RDP connection' 
@@ -70,7 +70,7 @@ class DropdownSnippet extends Component {
                 />
                 <Popup 
                     trigger={<Dropdown.Item 
-                        name='loginplesk'
+                        name='sn3'
                         onClick={this.props.handler}>
                         Log into Plesk</Dropdown.Item>} 
                     content='Link to the article about logging to Plesk' 
@@ -78,7 +78,7 @@ class DropdownSnippet extends Component {
                 />
                 <Popup 
                     trigger={<Dropdown.Item 
-                        name='dobackup'
+                        name='sn4'
                         onClick={this.props.handler}>
                         Create PSA backup</Dropdown.Item>} 
                     content='Link to the article about PSA dump' 
@@ -86,7 +86,23 @@ class DropdownSnippet extends Component {
                 />
                 <Popup 
                     trigger={<Dropdown.Item 
-                        name='publicip'
+                        name='sn5'
+                        onClick={this.props.handler}>
+                        Access PSA database</Dropdown.Item>} 
+                    content='Link to the article about PSA access ways' 
+                    position='right center' 
+                />
+                <Popup
+                    trigger={<Dropdown.Item 
+                        name='sn6'
+                        onClick={this.props.handler}>
+                        Edit file using vi</Dropdown.Item>} 
+                    content='Link to the article about how to use vi' 
+                    position='right center' 
+                />
+                <Popup 
+                    trigger={<Dropdown.Item 
+                        name='sn7'
                         onClick={this.props.handler}>
                         Standard Public IP</Dropdown.Item>} 
                     content='Public IP from Content Standard' 
@@ -94,7 +110,7 @@ class DropdownSnippet extends Component {
                 />
                 <Popup 
                     trigger={<Dropdown.Item 
-                        name='privateip'
+                        name='sn8'
                         onClick={this.props.handler}>
                         Standard Private IP</Dropdown.Item>} 
                     content='Private IP from Content Standard' 
@@ -102,15 +118,7 @@ class DropdownSnippet extends Component {
                 />
                 <Popup 
                     trigger={<Dropdown.Item 
-                        name='exampledomain'
-                        onClick={this.props.handler}>
-                        Standard domain name</Dropdown.Item>} 
-                    content='Domain name from Content Standard' 
-                    position='right center' 
-                />
-                <Popup 
-                    trigger={<Dropdown.Item 
-                        name='image'
+                        name='sn9'
                         onClick={this.props.handler}>
                         Insert image</Dropdown.Item>} 
                     content='Placeholder of an image' 
@@ -118,7 +126,7 @@ class DropdownSnippet extends Component {
                 />
                 <Popup 
                     trigger={<Dropdown.Item 
-                        name='uservoice'
+                        name='sn0'
                         onClick={this.props.handler}>
                         Plesk UserVoice template</Dropdown.Item>} 
                     content='Link to the UserVoice' 
@@ -133,7 +141,7 @@ class DropdownSnippet extends Component {
 class DropdownLinks extends Component {
     render() {
         return (
-            <Dropdown item text='KCS Guides'>
+            <Dropdown item text='Docs'>
                 <Dropdown.Menu>
                     <Dropdown.Item 
                         href='https://docs.plesk.ru/display/SUP/KCS+Style+Guide' target='_blank'
@@ -150,6 +158,16 @@ class DropdownLinks extends Component {
                     >
                         Content Standard
                     </Dropdown.Item>
+                    <Dropdown.Item 
+                        href='https://docs.plesk.ru/display/SUP/Markdown+editor+kcs.rocks' target='_blank'
+                    >
+                        Editor's page
+                    </Dropdown.Item>
+                    <Dropdown.Item 
+                        href='https://git.kcs.rocks/Plesk_Support/Editor' target='_blank'
+                    >
+                        Editor's git
+                    </Dropdown.Item>
                 </Dropdown.Menu>
             </Dropdown>
         )

+ 1 - 9
src/components/Menubar.js

@@ -38,20 +38,12 @@ class Menubar extends Component {
                                 labelPosition='left'
                                 onClick={this.props.helpHandler}
                         />				
-                        <Button 
-                            icon='help circle' 
-                            content='Help'
-                            labelPosition='left'
-                            // onClick={this.props.helpHandler}
-                            href='https://docs.plesk.ru/display/SUP/Markdown+editor+kcs.rocks'
-                            target='_blank'
-                        />
                     </Button.Group>
                 </Menu.Item>
                 <Menu.Item fitted>
                     <DropdownLinks />
                 </Menu.Item>
-                <Menu.Item href='https://git.kcs.rocks/Plesk_Support/Editor' target='_blank' header>Editor v1.2 b328</Menu.Item>
+                <Menu.Item href='https://git.kcs.rocks/Plesk_Support/Editor' target='_blank' header>Editor v1.2 b342</Menu.Item>
             </Menu.Menu>
         </Menu>
         )

+ 1 - 1
src/views/TriggerListModal.js

@@ -5,7 +5,7 @@ export default class TriggerListModal extends Component {
 	render() {
 		return (
 			<Modal
-				dimmer='blurring'
+				dimmer={true}
 				open={this.props.isOpen}
 				onClose={this.props.closeList}
 			>

File diff suppressed because it is too large
+ 916 - 345
yarn.lock