aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/post_status_form/post_status_form.js29
-rw-r--r--src/components/post_status_form/post_status_form.vue9
-rw-r--r--src/services/completion/completion.js70
3 files changed, 105 insertions, 3 deletions
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index df4c7baf..881a9d1c 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -1,8 +1,10 @@
import statusPoster from '../../services/status_poster/status_poster.service.js'
import MediaUpload from '../media_upload/media_upload.vue'
import fileTypeService from '../../services/file_type/file_type.service.js'
+import Completion from '../../services/completion/completion.js'
+
+import { take, filter, reject, map, uniqBy } from 'lodash'
-import { reject, map, uniqBy } from 'lodash'
const buildMentionsString = ({user, attentions}, currentUser) => {
let allAttentions = [...attentions]
@@ -42,15 +44,38 @@ const PostStatusForm = {
newStatus: {
status: statusText,
files: []
- }
+ },
+ caret: 0
}
},
computed: {
+ candidates () {
+ if (this.textAtCaret.charAt(0) === '@') {
+ const matchedUsers = filter(this.users, (user) => (user.name + user.screen_name).match(this.textAtCaret.slice(1)))
+ // eslint-disable-next-line camelcase
+ return map(take(matchedUsers, 5), ({screen_name, name}) => screen_name)
+ } else {
+ return ['nothing']
+ }
+ },
+ textAtCaret () {
+ return (this.wordAtCaret || {}).word || ''
+ },
+ wordAtCaret () {
+ const word = Completion.wordAtPosition(this.newStatus.status, this.caret - 1) || {}
+ return word
+ },
users () {
return this.$store.state.users.users
}
},
methods: {
+ replace (replacement) {
+ this.newStatus.status = Completion.replaceWord(this.newStatus.status, this.wordAtCaret, replacement)
+ },
+ setCaret ({target: {selectionStart}}) {
+ this.caret = selectionStart
+ },
postStatus (newStatus) {
statusPoster.postStatus({
status: newStatus.status,
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index 46beb506..4f6d4565 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -2,7 +2,14 @@
<div class="post-status-form">
<form @submit.prevent="postStatus(newStatus)">
<div class="form-group base03-border" >
- <textarea v-model="newStatus.status" placeholder="Just landed in L.A." rows="1" class="form-control" @keydown.meta.enter="postStatus(newStatus)" @keyup.ctrl.enter="postStatus(newStatus)" @drop="fileDrop" @dragover.prevent="fileDrag" @input="resize"></textarea>
+ <textarea @click="setCaret" @keyup="setCaret" v-model="newStatus.status" placeholder="Just landed in L.A." rows="1" class="form-control" @keydown.meta.enter="postStatus(newStatus)" @keyup.ctrl.enter="postStatus(newStatus)" @drop="fileDrop" @dragover.prevent="fileDrag" @input="resize"></textarea>
+ </div>
+ <div>
+ <h1>Word</h1>
+ <h2>{{textAtCaret}}</h2>
+ <h1>Candidates</h1>
+
+ <h3 v-for="candidate in candidates" @click="replace('@' + candidate)">{{candidate}}</h3>
</div>
<div class='form-bottom'>
<media-upload @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="enableSubmit" :drop-files="dropFiles"></media-upload>
diff --git a/src/services/completion/completion.js b/src/services/completion/completion.js
new file mode 100644
index 00000000..8788d837
--- /dev/null
+++ b/src/services/completion/completion.js
@@ -0,0 +1,70 @@
+import { reduce, find } from 'lodash'
+
+export const replaceWord = (str, toReplace, replacement) => {
+ return str.slice(0, toReplace.start) + replacement + str.slice(toReplace.end)
+}
+
+export const wordAtPosition = (str, pos) => {
+ const words = splitIntoWords(str)
+ const wordsWithPosition = addPositionToWords(words)
+
+ return find(wordsWithPosition, ({start, end}) => start <= pos && end > pos)
+}
+
+export const addPositionToWords = (words) => {
+ return reduce(words, (result, word) => {
+ const data = {
+ word,
+ start: 0,
+ end: word.length
+ }
+
+ if (result.length > 0) {
+ const previous = result.pop()
+
+ data.start += previous.end
+ data.end += previous.end
+
+ result.push(previous)
+ }
+
+ result.push(data)
+
+ return result
+ }, [])
+}
+
+export const splitIntoWords = (str) => {
+ // Split at word boundaries
+ const regex = /\b/
+ const triggers = /[@#]+$/
+
+ let split = str.split(regex)
+
+ // Add trailing @ and # to the following word.
+ const words = reduce(split, (result, word) => {
+ if (result.length > 0) {
+ let previous = result.pop()
+ const matches = previous.match(triggers)
+ if (matches) {
+ previous = previous.replace(triggers, '')
+ word = matches[0] + word
+ }
+ result.push(previous)
+ }
+ result.push(word)
+
+ return result
+ }, [])
+
+ return words
+}
+
+const completion = {
+ wordAtPosition,
+ addPositionToWords,
+ splitIntoWords,
+ replaceWord
+}
+
+export default completion