InputEditor.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. <template>
  2. <div
  3. class='input_editor'
  4. :class='{
  5. "input_editor--focus": focused,
  6. "input_editor--float": float,
  7. "input_editor--hidden": !show
  8. }'
  9. >
  10. <div class='input_editor__reply_username' v-if='replyUsername'>Replying to <strong>{{replyUsername}}</strong></div>
  11. <div class='input_editor__close input_editor__format_button' @click='closeEditor' v-if='!hideClose'>&times;</div>
  12. <tab-view :tabs='["Editor", "Preview"]' v-model='showTab' small-tabs='true'>
  13. <template slot='Editor'>
  14. <div class='input_editor__format_bar'>
  15. <div class='input_editor__format_button' @click='replaceSelectedText("**", "**")'>B</div>
  16. <div class='input_editor__format_button' @click='replaceSelectedText("*", "*")'>I</div>
  17. <div class='input_editor__format_button' @click='setModalState("link", true)'><span class='fa fa-link'></span></div>
  18. <div class='input_editor__format_button' @click='formatCode'><span class='fa fa-code'></span></div>
  19. <div class='input_editor__format_button' @click='setModalState("image", true)'><span class='fa fa-picture-o'></span></div>
  20. </div>
  21. <textarea
  22. class='input_editor__input'
  23. ref='textarea'
  24. :value='value'
  25. @input='setEditor($event.target.value)'
  26. @focus='focusEditor(true)'
  27. @blur='focusEditor(false)'
  28. placeholder='Type here - you can format using Markdown'
  29. >
  30. </textarea>
  31. </template>
  32. <div slot='Preview' class='input_editor__markdownHTML'>
  33. <div v-html='markdownHTML' style='margin-top: -0.5rem;'></div>
  34. <div v-if='!value.trim().length' class='input_editor__markdownHTML--empty'>
  35. Nothing to preview
  36. </div>
  37. </div>
  38. </tab-view>
  39. <div class='input_editor__submit_bar' v-if='float'>
  40. <button class='button' @click='submit'>Submit</button>
  41. </div>
  42. <modal-window v-model='linkModalVisible'>
  43. <div style='padding: 1rem;'>
  44. <p style='margin-top: 0;'>
  45. Enter the web address in the input box below
  46. </p>
  47. <fancy-input placeholder='Text for link' width='100%' v-model='linkText'></fancy-input>
  48. <fancy-input placeholder='Web address for link' width='100%' v-model='linkURL'></fancy-input>
  49. <button class='button' @click='addLink'>
  50. OK
  51. </button>
  52. <button class='button' @click='setModalState("link", false)'>
  53. Cancel
  54. </button>
  55. </div>
  56. </modal-window>
  57. <modal-window v-model='imageModalVisible'></modal-window>
  58. </div>
  59. </template>
  60. <script>
  61. import ModalWindow from './ModalWindow'
  62. import FancyInput from './FancyInput'
  63. import TabView from './TabView'
  64. import Marked from 'marked'
  65. export default {
  66. name: 'InputEditor',
  67. props: ['value', 'float', 'replyUsername', 'hideClose', 'show'],
  68. components: {
  69. ModalWindow,
  70. FancyInput,
  71. TabView
  72. },
  73. data () {
  74. return {
  75. linkText: '',
  76. linkURL: '',
  77. focused: false,
  78. linkModalVisible: false,
  79. imageModalVisible: false,
  80. showTab: 0
  81. }
  82. },
  83. computed: {
  84. markdownHTML () {
  85. return Marked(this.value);
  86. }
  87. },
  88. methods: {
  89. submit () {
  90. if(this.value.trim().length) {
  91. this.$emit('submit');
  92. }
  93. },
  94. focusEditor (val) {
  95. this.focused = val;
  96. },
  97. setModalState (modal, state) {
  98. if(modal === 'link') {
  99. this.linkModalVisible = state
  100. if(state) {
  101. this.linkText = this.getSelectionData().val;
  102. } else {
  103. this.linkText = '';
  104. this.linkURL = '';
  105. }
  106. } else if(modal === 'image') {
  107. this.imageModalVisible = state
  108. }
  109. },
  110. closeEditor () {
  111. this.setEditor('')
  112. this.$emit('close')
  113. },
  114. setEditor (value) {
  115. this.$emit('input', value)
  116. },
  117. getSelectionData () {
  118. var el = this.$refs.textarea,
  119. start = el.selectionStart,
  120. end = el.selectionEnd;
  121. return {
  122. val: el.value.slice(start, end),
  123. start,
  124. end
  125. };
  126. },
  127. replaceSelectedText (before, after) {
  128. var selectionData = this.getSelectionData();
  129. var el = this.$refs.textarea;
  130. this.setEditor(
  131. this.value.slice(0, selectionData.start) +
  132. before + selectionData.val + after +
  133. this.value.slice(selectionData.end)
  134. );
  135. el.focus();
  136. setTimeout(function() {
  137. el.selectionStart = selectionData.start + before.length;
  138. el.selectionEnd = selectionData.end + before.length;
  139. }, 1);
  140. },
  141. addLink () {
  142. var linkTextLength = this.linkText.length;
  143. var selectionData = this.getSelectionData();
  144. var el = this.$refs.textarea;
  145. this.setEditor(
  146. this.value.slice(0, selectionData.start) +
  147. '[' + this.linkText + '](' + this.linkURL + ')' +
  148. this.value.slice(selectionData.end)
  149. );
  150. el.focus();
  151. setTimeout(function() {
  152. el.selectionStart = selectionData.start + 1;
  153. el.selectionEnd = selectionData.start + 1 + linkTextLength;
  154. }, 1);
  155. this.setModalState('link', false);
  156. },
  157. formatCode () {
  158. var selectionData = this.getSelectionData();
  159. if(this.value[selectionData.start-1] === '\n' || selectionData.start === 0) {
  160. this.replaceSelectedText(' ', '');
  161. } else {
  162. this.replaceSelectedText('`', '`');
  163. }
  164. }
  165. }
  166. }
  167. </script>
  168. <style lang='scss' scoped>
  169. @import '../assets/scss/variables.scss';
  170. .input_editor {
  171. width: 35rem;
  172. border: 0.125rem solid $color__gray--darker;
  173. position: relative;
  174. margin-bottom: 0;
  175. pointer-events: all;
  176. opacity: 1;
  177. transition: margin-bottom 0.2s, opacity 0.2s;
  178. @at-root #{&}--focus {
  179. border-color: $color__gray--darkest;
  180. }
  181. @at-root #{&}--float {
  182. position: fixed;
  183. border-bottom: none;
  184. z-index: 2;
  185. bottom: 0;
  186. }
  187. @at-root #{&}--hidden {
  188. pointer-events: none;
  189. opacity: 0;
  190. margin-bottom: -3rem;
  191. transition: margin-bottom 0.2s, opacity 0.2s;
  192. }
  193. @at-root #{&}__close {
  194. position: absolute;
  195. right: 0.3rem;
  196. top: 0.5rem;
  197. }
  198. @at-root #{&}__reply_username {
  199. position: absolute;
  200. width: 100%;
  201. text-align: center;
  202. top: 0.5rem;
  203. }
  204. @at-root #{&}__format_bar {
  205. width: auto;
  206. position: absolute;
  207. height: 2rem;
  208. top: 0.25rem;
  209. right: 0;
  210. background-color: transparent;
  211. display: flex;
  212. align-items: center;
  213. padding: 0 0.125rem;
  214. margin-right: 2.4rem;
  215. }
  216. @at-root #{&}__format_button {
  217. height: 1.5rem;
  218. width: 1.5rem;
  219. text-align: center;
  220. line-height: 1.4rem;
  221. cursor: pointer;
  222. @include user-select(none);
  223. @include text($font--role-default, 1rem, 600);
  224. color: $color__darkgray--primary;
  225. border: thin solid $color__gray--primary;
  226. transition: background-color 0.2s;
  227. margin: 0;
  228. &:hover {
  229. background-color: $color__gray--darker;
  230. }
  231. &:active {
  232. background-color: $color__gray--darkest;
  233. }
  234. }
  235. @at-root #{&}__spacer {
  236. width: 0.6rem;
  237. }
  238. @at-root #{&}__input {
  239. width: 100%;
  240. height: 8rem;
  241. border: 0;
  242. padding: 0.5rem;
  243. @include text;
  244. outline: none;
  245. resize: none;
  246. @include placeholder {
  247. @include text($font--role-emphasis, 1rem);
  248. display: flex;
  249. align-content: center;
  250. @include user-select(none);
  251. cursor: default;
  252. color: $color__gray--darker;
  253. }
  254. }
  255. @at-root #{&}__markdownHTML {
  256. height: 8.2rem;
  257. overflow: auto;
  258. word-break: break-word;
  259. padding: 0.5rem;
  260. padding-top: 0;
  261. @at-root #{&}--empty {
  262. @include text($font--role-emphasis, 1rem);
  263. display: flex;
  264. margin-top: 1rem;
  265. align-content: center;
  266. @include user-select(none);
  267. cursor: default;
  268. color: $color__gray--darker;
  269. }
  270. }
  271. @at-root #{&}__submit_bar {
  272. display: flex;
  273. justify-content: flex-end;
  274. height: 2rem;
  275. align-items: center;
  276. padding-right: 0.3rem;
  277. background-color: $color__gray--primary;
  278. button {
  279. font-size: 0.8rem;
  280. height: 1.5rem;
  281. padding: 0 0.25rem;
  282. border-color: $color__gray--darkest;
  283. }
  284. }
  285. }
  286. </style>