InputEditorCore.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. <template>
  2. <div
  3. class='input_editor_core'
  4. >
  5. <error-tooltip :error='error'></error-tooltip>
  6. <div>
  7. <div class='input_editor_core__format_bar'>
  8. <div
  9. class='input_editor_core__format_button'
  10. title='Bold (ctrl + b)'
  11. @click='replaceSelectedText("**", "**")'
  12. >
  13. B
  14. </div>
  15. <div
  16. class='input_editor_core__format_button'
  17. title='Italic (ctrl + i)'
  18. @click='replaceSelectedText("*", "*")'
  19. >
  20. I
  21. </div>
  22. <div
  23. class='input_editor_core__format_button'
  24. title='Link (ctrl + l)'
  25. @click='setModalState("link", true)'
  26. >
  27. <span class='fa fa-link'></span>
  28. </div>
  29. <div
  30. class='input_editor_core__format_button'
  31. title='Code (ctrl + k)'
  32. @click='formatCode'
  33. >
  34. <span class='fa fa-code'></span>
  35. </div>
  36. </div>
  37. <textarea
  38. class='input_editor_core__input'
  39. placeholder='Type here - you can format using Markdown'
  40. ref='textarea'
  41. :value='value'
  42. @input='setEditor($event.target.value)'
  43. @focus='$emit("focus")'
  44. @blur='$emit("blur")'
  45. @keydown.ctrl.66='replaceSelectedText("**", "**")'
  46. @keydown.ctrl.73='replaceSelectedText("*", "*")'
  47. @keydown.ctrl.76='setModalState("link", true)'
  48. @keydown.ctrl.75='formatCode'
  49. >
  50. </textarea>
  51. </div>
  52. <modal-window v-model='linkModalVisible'>
  53. <div style='padding: 1rem;'>
  54. <p style='margin-top: 0;'>
  55. Enter the web address in the input box below
  56. </p>
  57. <fancy-input placeholder='Text for link' width='100%' v-model='linkText'></fancy-input>
  58. <fancy-input placeholder='Web address for link' width='100%' v-model='linkURL'></fancy-input>
  59. <button class='button' @click='addLink'>
  60. OK
  61. </button>
  62. <button class='button' @click='setModalState("link", false)'>
  63. Cancel
  64. </button>
  65. </div>
  66. </modal-window>
  67. <modal-window v-model='imageModalVisible'></modal-window>
  68. </div>
  69. </template>
  70. <script>
  71. import ModalWindow from './ModalWindow'
  72. import FancyInput from './FancyInput'
  73. import TabView from './TabView'
  74. import ErrorTooltip from './ErrorTooltip'
  75. export default {
  76. name: 'InputEditorCore',
  77. props: ['value', 'error'],
  78. components: {
  79. ModalWindow,
  80. FancyInput,
  81. ErrorTooltip
  82. },
  83. data () {
  84. return {
  85. linkText: '',
  86. linkURL: '',
  87. linkModalVisible: false,
  88. imageModalVisible: false,
  89. }
  90. },
  91. methods: {
  92. setModalState (modal, state) {
  93. if(modal === 'link') {
  94. this.linkModalVisible = state
  95. if(state) {
  96. this.linkText = this.getSelectionData().val;
  97. } else {
  98. this.linkText = '';
  99. this.linkURL = '';
  100. }
  101. } else if(modal === 'image') {
  102. this.imageModalVisible = state
  103. }
  104. },
  105. setEditor (value) {
  106. this.$emit('input', value)
  107. },
  108. getSelectionData () {
  109. var el = this.$refs.textarea,
  110. start = el.selectionStart,
  111. end = el.selectionEnd;
  112. return {
  113. val: el.value.slice(start, end),
  114. start,
  115. end
  116. };
  117. },
  118. replaceSelectedText (before, after) {
  119. var selectionData = this.getSelectionData();
  120. var el = this.$refs.textarea;
  121. this.setEditor(
  122. this.value.slice(0, selectionData.start) +
  123. before + selectionData.val + after +
  124. this.value.slice(selectionData.end)
  125. );
  126. el.focus();
  127. setTimeout(function() {
  128. el.selectionStart = selectionData.start + before.length;
  129. el.selectionEnd = selectionData.end + before.length;
  130. }, 1);
  131. },
  132. addLink () {
  133. var linkTextLength = this.linkText.length;
  134. var selectionData = this.getSelectionData();
  135. var el = this.$refs.textarea;
  136. this.setEditor(
  137. this.value.slice(0, selectionData.start) +
  138. '[' + this.linkText + '](' + this.linkURL + ')' +
  139. this.value.slice(selectionData.end)
  140. );
  141. el.focus();
  142. setTimeout(function() {
  143. el.selectionStart = selectionData.start + 1;
  144. el.selectionEnd = selectionData.start + 1 + linkTextLength;
  145. }, 1);
  146. this.setModalState('link', false);
  147. },
  148. formatCode () {
  149. var selectionData = this.getSelectionData();
  150. if(this.value[selectionData.start-1] === '\n' || selectionData.start === 0) {
  151. var el = this.$refs.textarea;
  152. var matches = selectionData.val.match(/\n/g || [] ).length
  153. var replacedText = ' ' + selectionData.val.replace(/\n/g, '\n ')
  154. this.setEditor(
  155. this.value.slice(0, selectionData.start) +
  156. replacedText +
  157. this.value.slice(selectionData.end)
  158. );
  159. el.focus();
  160. setTimeout(function() {
  161. el.selectionStart = selectionData.start + 4;
  162. el.selectionEnd = selectionData.end + matches*4;
  163. }, 1);
  164. } else {
  165. this.replaceSelectedText('`', '`');
  166. }
  167. }
  168. }
  169. }
  170. </script>
  171. <style lang='scss' scoped>
  172. @import '../assets/scss/variables.scss';
  173. .input_editor_core {
  174. @at-root #{&}__format_bar {
  175. width: auto;
  176. position: absolute;
  177. height: 2rem;
  178. top: 0.25rem;
  179. right: 0;
  180. background-color: transparent;
  181. display: flex;
  182. align-items: center;
  183. padding: 0 0.125rem;
  184. margin-right: 2.4rem;
  185. }
  186. @at-root #{&}__format_button {
  187. height: 1.5rem;
  188. width: 1.5rem;
  189. text-align: center;
  190. line-height: 1.4rem;
  191. cursor: pointer;
  192. @include user-select(none);
  193. @include text($font--role-default, 1rem, 600);
  194. color: $color__darkgray--primary;
  195. border: thin solid $color__gray--primary;
  196. transition: background-color 0.2s;
  197. margin: 0 0.25rem;
  198. &:hover {
  199. background-color: $color__gray--darker;
  200. }
  201. &:active {
  202. background-color: $color__gray--darkest;
  203. }
  204. }
  205. @at-root #{&}__spacer {
  206. width: 0.6rem;
  207. }
  208. @at-root #{&}__input {
  209. width: 100%;
  210. height: 8rem;
  211. border: 0;
  212. padding: 0.5rem;
  213. @include text;
  214. outline: none;
  215. resize: none;
  216. @include placeholder {
  217. @include text($font--role-emphasis, 1rem);
  218. display: flex;
  219. align-content: center;
  220. @include user-select(none);
  221. cursor: default;
  222. color: $color__gray--darker;
  223. }
  224. }
  225. @at-root #{&}__error {
  226. position: absolute;
  227. background-color: #ffeff1;
  228. border: 0.125rem solid #D32F2F;
  229. font-size: 0.9rem;
  230. padding: 0.1rem 0.25rem;
  231. top: 0.2125rem;
  232. left: calc(100% + 0.25rem);
  233. white-space: nowrap;
  234. &:first-letter{ text-transform: capitalize; }
  235. opacity: 0;
  236. pointer-events: none;
  237. margin-top: -1rem;
  238. transition: opacity 0.2s, margin-top 0.2s;
  239. @at-root #{&}--show {
  240. opacity: 1;
  241. pointer-events: all;
  242. margin-top: 0;
  243. transition: opacity 0.2s, margin-top 0.2s;
  244. }
  245. &::after {
  246. content: '';
  247. position: relative;
  248. width: 0;
  249. height: 0;
  250. display: inline-block;
  251. right: calc(100% + 0.3rem);
  252. border-top: 0.3rem solid transparent;
  253. border-bottom: 0.3rem solid transparent;
  254. border-right: 0.3rem solid #D32F2F;
  255. }
  256. }
  257. }
  258. </style>