Browse Source

Decouple core input editor and input editor preview from floating input editor

sbkwgh 8 years ago
parent
commit
280150aaf8

+ 50 - 262
src/components/InputEditor.vue

@@ -1,106 +1,55 @@
 <template>
 	<div
-		class='input_editor'
+		class='input_editor input_editor--float'
 		:class='{
-			"input_editor--focus": focused,
-			"input_editor--float": float,
-			"input_editor--hidden": !show
+			"input_editor--hidden": !show,
+			"input_editor--focus-input": focusInput
 		}'
 	>
-		<error-tooltip :error='error'></error-tooltip>
 		<div class='input_editor__reply_username' v-if='replyUsername'>Replying to <strong>{{replyUsername}}</strong></div>
-		<div class='input_editor__close input_editor__format_button' @click='closeEditor' v-if='!hideClose'>&times;</div>
-		<tab-view :tabs='["Editor", "Preview"]' v-model='showTab' small-tabs='true'>
+		<div class='input_editor__close input_editor__format_button' @click='closeEditor'>&times;</div>
+		
+		<tab-view :tabs='["Editor", "Preview"]' v-model='showTab' :small-tabs='true'>
 			<template slot='Editor'>
-				<div class='input_editor__format_bar'>
-					<div class='input_editor__format_button' @click='replaceSelectedText("**", "**")'>B</div>
-					<div class='input_editor__format_button' @click='replaceSelectedText("*", "*")'>I</div>
-					<div class='input_editor__format_button' @click='setModalState("link", true)'><span class='fa fa-link'></span></div>
-					<div class='input_editor__format_button' @click='formatCode'><span class='fa fa-code'></span></div>
-					<div class='input_editor__format_button' @click='setModalState("image", true)'><span class='fa fa-picture-o'></span></div>
-				</div>
-				<textarea
-					class='input_editor__input'
-					ref='textarea'
+				<input-editor-core
 					:value='value'
-					@input='setEditor($event.target.value)'
-					@focus='focusEditor(true)'
-					@blur='focusEditor(false)'
-					placeholder='Type here - you can format using Markdown'
-				>
-				</textarea>
-			</template>
+					:error='error'
 
-			<div slot='Preview' class='input_editor__markdownHTML'>
-				<div v-html='markdownHTML' style='margin-top: -0.5rem;'></div>
-				<div v-if='!value.trim().length' class='input_editor__markdownHTML--empty'>
-					Nothing to preview
-				</div>
-			</div>
+					@input='emitInput'
+					@focus='setFocusInput(true)'
+					@blur='setFocusInput(false)'
+				></input-editor-core>
+			</template>
+			<template slot='Preview'>
+				<input-editor-preview :value='value'></input-editor-preview>
+			</template>
 		</tab-view>
-
-		<div class='input_editor__submit_bar' v-if='float'>
+		
+		
+		<div class='input_editor__submit_bar'>
 			<button class='button' @click='submit'>Submit</button>
 		</div>
-
-		<modal-window v-model='linkModalVisible'>
-			<div style='padding: 1rem;'>
-				<p style='margin-top: 0;'>
-					Enter the web address in the input box below
-				</p>
-				<fancy-input placeholder='Text for link' width='100%' v-model='linkText'></fancy-input>
-				<fancy-input placeholder='Web address for link' width='100%' v-model='linkURL'></fancy-input>
-				<button class='button' @click='addLink'>
-					OK
-				</button>
-				<button class='button' @click='setModalState("link", false)'>
-					Cancel
-				</button>
-			</div>
-		</modal-window>
-
-		<modal-window v-model='imageModalVisible'></modal-window>
-
 	</div>
 </template>
 
 <script>
-	import ModalWindow from './ModalWindow'
-	import FancyInput from './FancyInput'
+	import InputEditorCore from './InputEditorCore'
+	import InputEditorPreview from './InputEditorPreview'
 	import TabView from './TabView'
-	import ErrorTooltip from './ErrorTooltip'
 
-	import Marked from 'marked'
-
-	Marked.setOptions({
-		highlight: function (code) {
-			return require('highlight.js').highlightAuto(code).value;
-		},
-		sanitize: true
-	});
 
 	export default {
 		name: 'InputEditor',
-		props: ['value', 'float', 'replyUsername', 'hideClose', 'show', 'error'],
+		props: ['value', 'error', 'replyUsername', 'show'],
 		components: {
-			ModalWindow,
-			FancyInput,
-			TabView,
-			ErrorTooltip
+			InputEditorCore,
+			InputEditorPreview,
+			TabView
 		},
 		data () {
 			return {
-				linkText: '',
-				linkURL: '',
-				focused: false,
-				linkModalVisible: false,
-				imageModalVisible: false,
-				showTab: 0
-			}
-		},
-		computed: {
-			markdownHTML () {
-				return Marked(this.value);
+				showTab: 0,
+				focusInput: false
 			}
 		},
 		methods: {
@@ -109,85 +58,20 @@
 					this.$emit('submit');
 				}
 			},
-			focusEditor (val) {
-				this.focused = val;
-			},
-			setModalState (modal, state) {
-				if(modal === 'link') {
-					this.linkModalVisible = state
-
-					if(state) {
-						this.linkText = this.getSelectionData().val;
-					} else {
-						this.linkText = '';
-						this.linkURL = '';
-					}
-				} else if(modal === 'image') {
-					this.imageModalVisible = state
-				}
-			},
 			closeEditor () {
-				this.setEditor('')
+				this.emitInput('')
 				this.$emit('close')
 			},
-			setEditor (value) {
-				this.$emit('input', value)
-			},
-			getSelectionData () {
-				var el = this.$refs.textarea,
-					start = el.selectionStart,
-					end = el.selectionEnd;
-
-				return {
-					val: el.value.slice(start, end),
-					start,
-					end
-				};
-
+			emitInput (val) {
+				this.$emit('input', val)
 			},
-			replaceSelectedText (before, after) {
-				var selectionData = this.getSelectionData();
-				var el = this.$refs.textarea;
-
-				this.setEditor(
-					this.value.slice(0, selectionData.start) +
-					before + selectionData.val + after +
-					this.value.slice(selectionData.end)
-				);
-				el.focus();
-
-				setTimeout(function() {
-					el.selectionStart = selectionData.start + before.length;
-					el.selectionEnd = selectionData.end + before.length;
-				}, 1);
-			},
-			addLink () {
-				var linkTextLength = this.linkText.length;
-				var selectionData = this.getSelectionData();
-				var el = this.$refs.textarea;
-
-				this.setEditor(
-					this.value.slice(0, selectionData.start) +
-					'[' + this.linkText + '](' + this.linkURL + ')' +
-					this.value.slice(selectionData.end)
-				);
-				el.focus();
-
-				setTimeout(function() {
-					el.selectionStart = selectionData.start + 1;
-					el.selectionEnd = selectionData.start + 1 + linkTextLength;
-				}, 1);
-
-				this.setModalState('link', false);
-			},
-			formatCode () {
-				var selectionData = this.getSelectionData();
-
-				if(this.value[selectionData.start-1] === '\n' || selectionData.start === 0) {
-					this.replaceSelectedText('    ', '');
-				} else {
-					this.replaceSelectedText('`', '`');
-				}
+			setFocusInput (val) {
+				this.focusInput = val
+			}
+		},
+		watch: {
+			show () {
+				this.$el.querySelector('textarea').focus()
 			}
 		}
 	}
@@ -199,23 +83,20 @@
 	.input_editor {
 		width: 35rem;
 		border: 0.125rem solid $color__gray--darker;
-		position: relative;
-
+		border-bottom: none;
 		margin-bottom: 0;
 		pointer-events: all;
-		opacity: 1;
-		transition:  margin-bottom 0.2s, opacity 0.2s;
-
-		@at-root #{&}--focus {
+		transition:  margin-bottom 0.2s, filter 0.2s, border-color 0.2s;
+		outline: none;
+		position: fixed;
+		
+		z-index: 2;
+		bottom: 0;
+
+		@at-root #{&}--focus-input {
 			border-color: $color__gray--darkest;
 		}
 
-		@at-root #{&}--float {
-			position: fixed;
-			border-bottom: none;
-			z-index: 2;
-			bottom: 0;
-		}
 
 		@at-root #{&}--hidden {
 			pointer-events: none;
@@ -229,28 +110,6 @@
 			position: absolute;
 			right: 0.3rem;
 			top: 0.5rem;
-		}
-
-		@at-root #{&}__reply_username {
-			position: absolute;
-			width: 100%;
-			text-align: center;
-			top: 0.5rem;
-		}
-
-		@at-root #{&}__format_bar {
-			width: auto;
-			position: absolute;
-			height: 2rem;
-			top: 0.25rem;
-			right: 0;
-			background-color: transparent;
-			display: flex;
-			align-items: center;
-			padding: 0 0.125rem;
-			margin-right: 2.4rem;
-		}
-		@at-root #{&}__format_button {
 			height: 1.5rem;
 			width: 1.5rem;
 			text-align: center;
@@ -271,44 +130,11 @@
 			}
 		}
 
-		@at-root #{&}__spacer {
-			width: 0.6rem;
-		}
-
-		@at-root #{&}__input {
+		@at-root #{&}__reply_username {
+			position: absolute;
 			width: 100%;
-			height: 8rem;
-			border: 0;
-			padding: 0.5rem;
-			@include text;
-			outline: none;
-			resize: none;
-
-			@include placeholder {
-				@include text($font--role-emphasis, 1rem);
-				display: flex;
-				align-content: center;
-				@include user-select(none);
-				cursor: default;
-				color: $color__gray--darker;
-			}
-		}
-
-		@at-root #{&}__markdownHTML {
-			height: 8.2rem;
-			overflow: auto;
-			word-break: break-word;
-			padding: 0.5rem;
-
-			@at-root #{&}--empty {
-				@include text($font--role-emphasis, 1rem);
-				display: flex;
-				margin-top: 1rem;
-				align-content: center;
-				@include user-select(none);
-				cursor: default;
-				color: $color__gray--darker;
-			}
+			text-align: center;
+			top: 0.5rem;
 		}
 
 		@at-root #{&}__submit_bar {
@@ -326,43 +152,5 @@
 				border-color: $color__gray--darkest;
 			}
 		}
-
-		@at-root #{&}__error {
-			position: absolute;
-			background-color: #ffeff1;
-			border: 0.125rem solid #D32F2F;
-			font-size: 0.9rem;
-			padding: 0.1rem 0.25rem;
-			top: 0.2125rem;
-			left: calc(100% + 0.25rem);
-			white-space: nowrap;
-			
-
-			&:first-letter{ text-transform: capitalize; }
-
-			opacity: 0;
-			pointer-events: none;
-			margin-top: -1rem;
-			transition: opacity 0.2s, margin-top 0.2s;
-
-			@at-root #{&}--show {
-				opacity: 1;
-				pointer-events: all;
-				margin-top: 0;
-				transition: opacity 0.2s, margin-top 0.2s;
-			}
-
-			&::after {
-				content: '';
-				position: relative;
-				width: 0;
-				height: 0;
-				display: inline-block;
-				right: calc(100% + 0.3rem);
-				border-top: 0.3rem solid transparent;
-				border-bottom: 0.3rem solid transparent;
-				border-right: 0.3rem solid #D32F2F;
-			}
-		}
 	}
 </style>

+ 262 - 0
src/components/InputEditorCore.vue

@@ -0,0 +1,262 @@
+<template>
+	<div
+		class='input_editor_core'
+	>
+		<error-tooltip :error='error'></error-tooltip>
+		<div>
+			<div class='input_editor_core__format_bar'>
+				<div class='input_editor_core__format_button' @click='replaceSelectedText("**", "**")'>B</div>
+				<div class='input_editor_core__format_button' @click='replaceSelectedText("*", "*")'>I</div>
+				<div class='input_editor_core__format_button' @click='setModalState("link", true)'><span class='fa fa-link'></span></div>
+				<div class='input_editor_core__format_button' @click='formatCode'><span class='fa fa-code'></span></div>
+				<div class='input_editor_core__format_button' @click='setModalState("image", true)'><span class='fa fa-picture-o'></span></div>
+			</div>
+			<textarea
+				class='input_editor_core__input'
+				ref='textarea'
+				:value='value'
+				@input='setEditor($event.target.value)'
+				@focus='$emit("focus")'
+				@blur='$emit("blur")'
+				placeholder='Type here - you can format using Markdown'
+			>
+			</textarea>
+		</div>
+
+		<modal-window v-model='linkModalVisible'>
+			<div style='padding: 1rem;'>
+				<p style='margin-top: 0;'>
+					Enter the web address in the input box below
+				</p>
+				<fancy-input placeholder='Text for link' width='100%' v-model='linkText'></fancy-input>
+				<fancy-input placeholder='Web address for link' width='100%' v-model='linkURL'></fancy-input>
+				<button class='button' @click='addLink'>
+					OK
+				</button>
+				<button class='button' @click='setModalState("link", false)'>
+					Cancel
+				</button>
+			</div>
+		</modal-window>
+
+		<modal-window v-model='imageModalVisible'></modal-window>
+
+	</div>
+</template>
+
+<script>
+	import ModalWindow from './ModalWindow'
+	import FancyInput from './FancyInput'
+	import TabView from './TabView'
+	import ErrorTooltip from './ErrorTooltip'
+
+	export default {
+		name: 'InputEditorCore',
+		props: ['value', 'error'],
+		components: {
+			ModalWindow,
+			FancyInput,
+			ErrorTooltip
+		},
+		data () {
+			return {
+				linkText: '',
+				linkURL: '',
+				linkModalVisible: false,
+				imageModalVisible: false,
+			}
+		},
+		methods: {
+			setModalState (modal, state) {
+				if(modal === 'link') {
+					this.linkModalVisible = state
+
+					if(state) {
+						this.linkText = this.getSelectionData().val;
+					} else {
+						this.linkText = '';
+						this.linkURL = '';
+					}
+				} else if(modal === 'image') {
+					this.imageModalVisible = state
+				}
+			},
+			setEditor (value) {
+				this.$emit('input', value)
+			},
+			getSelectionData () {
+				var el = this.$refs.textarea,
+					start = el.selectionStart,
+					end = el.selectionEnd;
+
+				return {
+					val: el.value.slice(start, end),
+					start,
+					end
+				};
+
+			},
+			replaceSelectedText (before, after) {
+				var selectionData = this.getSelectionData();
+				var el = this.$refs.textarea;
+
+				this.setEditor(
+					this.value.slice(0, selectionData.start) +
+					before + selectionData.val + after +
+					this.value.slice(selectionData.end)
+				);
+				el.focus();
+
+				setTimeout(function() {
+					el.selectionStart = selectionData.start + before.length;
+					el.selectionEnd = selectionData.end + before.length;
+				}, 1);
+			},
+			addLink () {
+				var linkTextLength = this.linkText.length;
+				var selectionData = this.getSelectionData();
+				var el = this.$refs.textarea;
+
+				this.setEditor(
+					this.value.slice(0, selectionData.start) +
+					'[' + this.linkText + '](' + this.linkURL + ')' +
+					this.value.slice(selectionData.end)
+				);
+				el.focus();
+
+				setTimeout(function() {
+					el.selectionStart = selectionData.start + 1;
+					el.selectionEnd = selectionData.start + 1 + linkTextLength;
+				}, 1);
+
+				this.setModalState('link', false);
+			},
+			formatCode () {
+				var selectionData = this.getSelectionData();
+
+				if(this.value[selectionData.start-1] === '\n' || selectionData.start === 0) {
+					this.replaceSelectedText('    ', '');
+				} else {
+					this.replaceSelectedText('`', '`');
+				}
+			}
+		}
+	}
+</script>
+
+<style lang='scss' scoped>
+	@import '../assets/scss/variables.scss';
+
+	.input_editor_core {
+		@at-root #{&}__format_bar {
+			width: auto;
+			position: absolute;
+			height: 2rem;
+			top: 0.25rem;
+			right: 0;
+			background-color: transparent;
+			display: flex;
+			align-items: center;
+			padding: 0 0.125rem;
+			margin-right: 2.4rem;
+		}
+		@at-root #{&}__format_button {
+			height: 1.5rem;
+			width: 1.5rem;
+			text-align: center;
+			line-height: 1.4rem;
+			cursor: pointer;
+			@include user-select(none);
+			@include text($font--role-default, 1rem, 600);
+			color: $color__darkgray--primary;
+			border: thin solid $color__gray--primary;
+			transition: background-color 0.2s;
+			margin: 0;
+
+			&:hover {
+				background-color: $color__gray--darker;
+			}
+			&:active {
+				background-color: $color__gray--darkest;
+			}
+		}
+
+		@at-root #{&}__spacer {
+			width: 0.6rem;
+		}
+
+		@at-root #{&}__input {
+			width: 100%;
+			height: 8rem;
+			border: 0;
+			padding: 0.5rem;
+			@include text;
+			outline: none;
+			resize: none;
+
+			@include placeholder {
+				@include text($font--role-emphasis, 1rem);
+				display: flex;
+				align-content: center;
+				@include user-select(none);
+				cursor: default;
+				color: $color__gray--darker;
+			}
+		}
+
+		@at-root #{&}__markdownHTML {
+			height: 8.2rem;
+			overflow: auto;
+			word-break: break-word;
+			padding: 0.5rem;
+
+			@at-root #{&}--empty {
+				@include text($font--role-emphasis, 1rem);
+				display: flex;
+				margin-top: 1rem;
+				align-content: center;
+				@include user-select(none);
+				cursor: default;
+				color: $color__gray--darker;
+			}
+		}
+
+		@at-root #{&}__error {
+			position: absolute;
+			background-color: #ffeff1;
+			border: 0.125rem solid #D32F2F;
+			font-size: 0.9rem;
+			padding: 0.1rem 0.25rem;
+			top: 0.2125rem;
+			left: calc(100% + 0.25rem);
+			white-space: nowrap;
+			
+
+			&:first-letter{ text-transform: capitalize; }
+
+			opacity: 0;
+			pointer-events: none;
+			margin-top: -1rem;
+			transition: opacity 0.2s, margin-top 0.2s;
+
+			@at-root #{&}--show {
+				opacity: 1;
+				pointer-events: all;
+				margin-top: 0;
+				transition: opacity 0.2s, margin-top 0.2s;
+			}
+
+			&::after {
+				content: '';
+				position: relative;
+				width: 0;
+				height: 0;
+				display: inline-block;
+				right: calc(100% + 0.3rem);
+				border-top: 0.3rem solid transparent;
+				border-bottom: 0.3rem solid transparent;
+				border-right: 0.3rem solid #D32F2F;
+			}
+		}
+	}
+</style>

+ 52 - 0
src/components/InputEditorPreview.vue

@@ -0,0 +1,52 @@
+<template>
+	<div class='input_editor_preview__markdownHTML'>
+		<div v-html='HTML' style='margin-top: -0.5rem;'></div>
+		<div v-if='!value.trim().length' class='input_editor_preview__markdownHTML--empty'>
+			Nothing to preview
+		</div>
+	</div>
+</template>
+
+<script>
+	import Marked from 'marked'
+
+	Marked.setOptions({
+		highlight: function (code) {
+			return require('highlight.js').highlightAuto(code).value;
+		},
+		sanitize: true
+	});
+
+	export default {
+		name: 'InputEditorPreview',
+		props: ['value'],
+		computed: {
+			HTML () {
+				return Marked(this.value);
+			}
+		}
+	}
+</script>
+
+<style lang='scss' scoped>
+	@import '../assets/scss/variables.scss';
+
+	.input_editor_preview {
+		@at-root #{&}__markdownHTML {
+			height: 8.2rem;
+			overflow: auto;
+			word-break: break-word;
+			padding: 0.5rem;
+
+			@at-root #{&}--empty {
+				@include text($font--role-emphasis, 1rem);
+				display: flex;
+				margin-top: 1rem;
+				align-content: center;
+				@include user-select(none);
+				cursor: default;
+				color: $color__gray--darker;
+			}
+		}
+	}
+</style>

+ 0 - 1
src/components/routes/Thread.vue

@@ -16,7 +16,6 @@
 		</header>
 		<input-editor
 			v-model='editor'
-			:float='true'
 			:show='editorState'
 			:replyUsername='replyUsername'
 			v-on:close='hideEditor'