This commit is contained in:
Nolan Lawson 2018-03-03 14:51:48 -08:00
parent 66801fbc96
commit 3dc46791e9
17 changed files with 81 additions and 103 deletions

View file

@ -17,13 +17,11 @@ export async function updateCustomEmojiForInstance (instanceName) {
) )
} }
export function insertEmoji (emoji) { export function insertEmoji (realm, emoji) {
let idx = store.get('composeSelectionStart') || 0 let idx = store.get('composeSelectionStart') || 0
let oldText = store.get('rawComposeText') || '' let oldText = store.getComposeData(realm, 'text')
let pre = oldText ? substring(oldText, 0, idx) : '' let pre = oldText ? substring(oldText, 0, idx) : ''
let post = oldText ? substring(oldText, idx) : '' let post = oldText ? substring(oldText, idx) : ''
let newText = `${pre}:${emoji.shortcode}: ${post}` let newText = `${pre}:${emoji.shortcode}: ${post}`
store.set({ store.setComposeData(realm, {text: newText})
rawComposeText: newText
})
} }

View file

@ -22,8 +22,7 @@ export function switchToInstance (instanceName) {
store.set({ store.set({
currentInstance: instanceName, currentInstance: instanceName,
searchResults: null, searchResults: null,
queryInSearch: '', queryInSearch: ''
rawComposeText: ''
}) })
store.save() store.save()
switchToTheme(instanceThemes[instanceName]) switchToTheme(instanceThemes[instanceName])
@ -49,7 +48,6 @@ export async function logOutOfInstance (instanceName) {
currentInstance: newInstance, currentInstance: newInstance,
searchResults: null, searchResults: null,
queryInSearch: '', queryInSearch: '',
rawComposeText: '',
composeData: composeData composeData: composeData
}) })
store.save() store.save()

View file

@ -14,10 +14,12 @@ export async function doMediaUpload (realm, file) {
data: response, data: response,
file: { name: file.name } file: { name: file.name }
}) })
let rawComposeText = store.get('rawComposeText') || '' let composeText = store.getComposeData(realm, 'text') || ''
rawComposeText += ' ' + response.text_url composeText += ' ' + response.text_url
store.setComposeData(realm, 'media', composeMedia) store.setComposeData(realm, {
store.set({rawComposeText}) media: composeMedia,
text: composeText
})
scheduleIdleTask(() => store.save()) scheduleIdleTask(() => store.save())
} catch (e) { } catch (e) {
console.error(e) console.error(e)
@ -31,11 +33,12 @@ export function deleteMedia (realm, i) {
let composeMedia = store.getComposeData(realm, 'media') let composeMedia = store.getComposeData(realm, 'media')
let deletedMedia = composeMedia.splice(i, 1)[0] let deletedMedia = composeMedia.splice(i, 1)[0]
let rawComposeText = store.get('rawComposeText') || '' let composeText = store.getComposeData(realm, 'text') || ''
composeText = composeText.replace(' ' + deletedMedia.data.text_url, '')
rawComposeText = rawComposeText.replace(' ' + deletedMedia.data.text_url, '') store.setComposeData(realm, {
media: composeMedia,
store.setComposeData(realm, 'media', composeMedia) text: composeText
store.set({rawComposeText}) })
scheduleIdleTask(() => store.save()) scheduleIdleTask(() => store.save())
} }

View file

@ -2,5 +2,5 @@
import { store } from '../_store/store' import { store } from '../_store/store'
export function setPostPrivacy (realm, postPrivacyKey) { export function setPostPrivacy (realm, postPrivacyKey) {
store.setComposeData(realm, 'postPrivacy', postPrivacyKey) store.setComposeData(realm, {postPrivacy: postPrivacyKey})
} }

View file

@ -1,11 +1,11 @@
<div class="compose-box {{overLimit ? 'over-char-limit' : ''}}"> <div class="compose-box {{overLimit ? 'over-char-limit' : ''}}">
<ComposeAuthor /> <ComposeAuthor />
<ComposeInput :realm /> <ComposeInput :realm :text />
<ComposeLengthGauge /> <ComposeLengthGauge :textLength :textOverLimit />
<ComposeToolbar :realm /> <ComposeToolbar :realm :postPrivacy :media />
<ComposeLengthIndicator /> <ComposeLengthIndicator :textLength :textOverLimit />
<ComposeMedia :realm /> <ComposeMedia :realm :media />
<ComposeButton /> <ComposeButton :textLength :textOverLimit />
</div> </div>
<style> <style>
.compose-box { .compose-box {
@ -42,6 +42,9 @@
import ComposeInput from './ComposeInput.html' import ComposeInput from './ComposeInput.html'
import ComposeButton from './ComposeButton.html' import ComposeButton from './ComposeButton.html'
import ComposeMedia from './ComposeMedia.html' import ComposeMedia from './ComposeMedia.html'
import { measureText } from '../../_utils/measureText'
import { CHAR_LIMIT, POST_PRIVACY_OPTIONS } from '../../_static/statuses'
import { store } from '../../_store/store'
export default { export default {
components: { components: {
@ -52,6 +55,17 @@
ComposeInput, ComposeInput,
ComposeButton, ComposeButton,
ComposeMedia ComposeMedia
},
store: () => store,
computed: {
composeData: ($currentComposeData, realm) => $currentComposeData[realm] || {},
text: (composeData) => composeData.text || '',
media: (composeData) => composeData.media || [],
postPrivacy: (postPrivacyKey) => POST_PRIVACY_OPTIONS.find(_ => _.key === postPrivacyKey),
defaultPostPrivacyKey: ($currentVerifyCredentials) => $currentVerifyCredentials.source.privacy,
postPrivacyKey: (composeData, defaultPostPrivacyKey) => composeData.postPrivacy || defaultPostPrivacyKey,
textLength: (text) => measureText(text),
textOverLimit: (textLength) => textLength > CHAR_LIMIT
} }
} }
</script> </script>

View file

@ -16,8 +16,8 @@
export default { export default {
store: () => store, store: () => store,
computed: { computed: {
disabled: ($rawComposeTextOverLimit, $rawComposeTextLength) => { disabled: (textOverLimit, textLength) => {
return $rawComposeTextOverLimit || $rawComposeTextLength === 0 return textOverLimit || textLength === 0
} }
} }
} }

View file

@ -2,7 +2,7 @@
class="compose-box-input" class="compose-box-input"
placeholder="What's on your mind?" placeholder="What's on your mind?"
ref:textarea ref:textarea
bind:value=$rawComposeText bind:value=rawText
on:blur="onBlur()" on:blur="onBlur()"
></textarea> ></textarea>
<style> <style>
@ -41,17 +41,16 @@
}, },
methods: { methods: {
setupSyncFromStore() { setupSyncFromStore() {
let composeText = this.get('composeText') this.observe('text', text => {
this.store.set({ this.set({rawText: text})
rawComposeText: composeText
}) })
}, },
setupSyncToStore() { setupSyncToStore() {
const saveText = debounce(() => scheduleIdleTask(() => this.store.save()), 1000) const saveText = debounce(() => scheduleIdleTask(() => this.store.save()), 1000)
this.observe('rawComposeText', rawComposeText => { this.observe('rawText', rawText => {
let realm = this.get('realm') let realm = this.get('realm')
this.store.setComposeData(realm, 'text', rawComposeText) this.store.setComposeData(realm, {text: rawText})
saveText() saveText()
}, {init: false}) }, {init: false})
}, },
@ -72,10 +71,8 @@
} }
}, },
store: () => store, store: () => store,
computed: { data: () => ({
rawComposeText: ($rawComposeText) => $rawComposeText, rawText: ''
composeData: ($currentComposeData, realm) => $currentComposeData[realm] || {}, })
composeText: (composeData) => composeData.text || ''
}
} }
</script> </script>

View file

@ -1,4 +1,4 @@
<div class="compose-box-length-gauge {{shouldAnimate ? 'should-animate' : ''}} {{$rawComposeTextOverLimit ? 'over-char-limit' : ''}}" <div class="compose-box-length-gauge {{shouldAnimate ? 'should-animate' : ''}} {{textOverLimit ? 'over-char-limit' : ''}}"
style="transform: scaleX({{inputLengthAsFractionRoundedAfterRaf || 0}});" style="transform: scaleX({{inputLengthAsFractionRoundedAfterRaf || 0}});"
aria-hidden="true" aria-hidden="true"
></div> ></div>
@ -36,12 +36,12 @@
}, },
store: () => store, store: () => store,
computed: { computed: {
inputLengthAsFraction: ($rawComposeTextLength) => { lengthAsFraction: (textLength) => {
return Math.min(CHAR_LIMIT, $rawComposeTextLength) / CHAR_LIMIT return Math.min(CHAR_LIMIT, textLength) / CHAR_LIMIT
}, },
inputLengthAsFractionRounded: (inputLengthAsFraction) => { lengthAsFractionRounded: (lengthAsFraction) => {
// We don't need to update the gauge for every decimal point, so round it to the nearest 0.02 // We don't need to update the gauge for every decimal point, so round it to the nearest 0.02
let int = Math.round(inputLengthAsFraction * 100) let int = Math.round(lengthAsFraction * 100)
int -= (int % 2) int -= (int % 2)
return int / 100 return int / 100
} }

View file

@ -1,6 +1,6 @@
<span class="compose-box-length {{$rawComposeTextOverLimit ? 'over-char-limit' : ''}}" <span class="compose-box-length {{textOverLimit ? 'over-char-limit' : ''}}"
aria-label="{{inputLengthLabel}}"> aria-label="{{lengthLabel}}">
{{inputLengthToDisplayAfterRaf || '0'}} {{lengthToDisplayAfterRaf || '0'}}
</span> </span>
<style> <style>
.compose-box-length { .compose-box-length {
@ -23,24 +23,24 @@
export default { export default {
oncreate() { oncreate() {
// perf improvement for keyboard input latency // perf improvement for keyboard input latency
this.observe('inputLengthToDisplay', inputLengthToDisplay => { this.observe('lengthToDisplay', lengthToDisplay => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
mark('set inputLengthToDisplayAfterRaf') mark('set lengthToDisplayAfterRaf')
this.set({inputLengthToDisplayAfterRaf: inputLengthToDisplay}) this.set({lengthToDisplayAfterRaf: lengthToDisplay})
stop('set inputLengthToDisplayAfterRaf') stop('set lengthToDisplayAfterRaf')
}) })
}) })
}, },
store: () => store, store: () => store,
computed: { computed: {
inputLengthToDisplay: ($rawComposeTextLength) => { lengthToDisplay: (textLength) => {
return CHAR_LIMIT - $rawComposeTextLength return CHAR_LIMIT - textLength
}, },
inputLengthLabel: ($rawComposeTextOverLimit, inputLengthToDisplay) => { lengthLabel: (textOverLimit, lengthToDisplay) => {
if ($rawComposeTextOverLimit) { if (textOverLimit) {
return `${inputLengthToDisplay} characters over limit` return `${lengthToDisplay} characters over limit`
} else { } else {
return `${inputLengthToDisplay} characters remaining` return `${lengthToDisplay} characters remaining`
} }
} }
} }

View file

@ -1,20 +1,19 @@
{{#if composeMedia.length}} {{#if media.length}}
<div class="compose-media-container" style="grid-template-columns: repeat({{composeMedia.length}}, 1fr);"> <div class="compose-media-container" style="grid-template-columns: repeat({{media.length}}, 1fr);">
{{#each composeMedia as media, i}} {{#each media as mediaItem, i}}
<div class="compose-media"> <div class="compose-media">
<img src="{{media.data.preview_url}}" alt="{{media.file.name}}"/> <img src="{{mediaItem.data.preview_url}}" alt="{{mediaItem.file.name}}"/>
<div class="compose-media-delete"> <div class="compose-media-delete">
<button class="compose-media-delete-button" <button class="compose-media-delete-button"
data-a11y-dialog-hide aria-label="Delete {{media.file.name}}" data-a11y-dialog-hide aria-label="Delete {{mediaItem.file.name}}"
on:click="onDeleteMedia(i)" on:click="onDeleteMedia(i)" >
>
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="compose-media-alt"> <div class="compose-media-alt">
<input type="text" <input type="text"
placeholder="Description" placeholder="Description"
aria-label="Describe {{media.file.name}} for the visually impaired"> aria-label="Describe {{mediaItem.file.name}} for the visually impaired">
</div> </div>
</div> </div>
{{/each}} {{/each}}
@ -98,10 +97,6 @@
onDeleteMedia(i) { onDeleteMedia(i) {
deleteMedia(this.get('realm'), i) deleteMedia(this.get('realm'), i)
} }
},
computed: {
composeData: ($currentComposeData, realm) => $currentComposeData[realm] || {},
composeMedia: (composeData) => composeData.media || []
} }
} }
</script> </script>

View file

@ -9,7 +9,7 @@
label="Add media" label="Add media"
href="{{$uploadingMedia ? '#fa-spinner' : '#fa-camera'}}" href="{{$uploadingMedia ? '#fa-spinner' : '#fa-camera'}}"
on:click="onMediaClick()" on:click="onMediaClick()"
disabled="{{$uploadingMedia || (composeMedia.length === 4)}}" disabled="{{$uploadingMedia || (media.length === 4)}}"
/> />
<IconButton <IconButton
label="Adjust privacy (currently {{postPrivacy.label}})" label="Adjust privacy (currently {{postPrivacy.label}})"
@ -62,7 +62,7 @@
async onEmojiClick() { async onEmojiClick() {
/* no await */ updateCustomEmojiForInstance(this.store.get('currentInstance')) /* no await */ updateCustomEmojiForInstance(this.store.get('currentInstance'))
let dialogs = await importDialogs() let dialogs = await importDialogs()
dialogs.showEmojiDialog() dialogs.showEmojiDialog(this.get('realm'))
}, },
onMediaClick() { onMediaClick() {
this.refs.input.click() this.refs.input.click()
@ -76,16 +76,6 @@
let dialogs = await importDialogs() let dialogs = await importDialogs()
dialogs.showPostPrivacyDialog(this.get('realm')) dialogs.showPostPrivacyDialog(this.get('realm'))
} }
},
computed: {
composeData: ($currentComposeData, realm) => $currentComposeData[realm] || {},
composeMedia: (composeData) => composeData.media || [],
postPrivacy: (postPrivacyKey) => {
return POST_PRIVACY_OPTIONS.find(_ => _.key === postPrivacyKey)
},
postPrivacyKey: (composeData, $currentVerifyCredentials) => {
return composeData.postPrivacy || $currentVerifyCredentials.source.privacy
}
} }
} }
</script> </script>

View file

@ -74,7 +74,7 @@
this.set({shown: true}) this.set({shown: true})
}, },
onClickEmoji(emoji) { onClickEmoji(emoji) {
insertEmoji(emoji) insertEmoji(this.get('realm'), emoji)
this.set({closed: true}) this.set({closed: true})
} }
} }

View file

@ -1,11 +1,12 @@
import EmojiDialog from './EmojiDialog.html' import EmojiDialog from './EmojiDialog.html'
export function showEmojiDialog () { export function showEmojiDialog (realm) {
let emojiDialog = new EmojiDialog({ let emojiDialog = new EmojiDialog({
target: document.getElementById('modal-dialog'), target: document.getElementById('modal-dialog'),
data: { data: {
label: 'Emoji dialog', label: 'Emoji dialog',
title: 'Custom emoji' title: 'Custom emoji',
realm
} }
}) })
emojiDialog.show() emojiDialog.show()

View file

@ -1,9 +1,7 @@
import { instanceComputations } from './instanceComputations' import { instanceComputations } from './instanceComputations'
import { timelineComputations } from './timelineComputations' import { timelineComputations } from './timelineComputations'
import { statusComputations } from './statusComputations'
export function computations (store) { export function computations (store) {
instanceComputations(store) instanceComputations(store)
timelineComputations(store) timelineComputations(store)
statusComputations(store)
} }

View file

@ -1,14 +0,0 @@
import { CHAR_LIMIT } from '../../_static/statuses'
import { measureText } from '../../_utils/measureText'
export function statusComputations (store) {
store.compute('rawComposeTextLength',
['rawComposeText'],
(rawComposeText) => measureText(rawComposeText)
)
store.compute('rawComposeTextOverLimit',
['rawComposeTextLength'],
(rawComposeTextLength) => rawComposeTextLength > CHAR_LIMIT
)
}

View file

@ -1,10 +1,9 @@
export function instanceMixins (Store) { export function instanceMixins (Store) {
Store.prototype.setComposeData = function (realm, key, value) { Store.prototype.setComposeData = function (realm, obj) {
let composeData = this.get('composeData') let composeData = this.get('composeData')
let instanceName = this.get('currentInstance') let instanceName = this.get('currentInstance')
composeData[instanceName] = composeData[instanceName] || {} let instanceNameData = composeData[instanceName] = composeData[instanceName] || {}
composeData[instanceName][realm] = composeData[instanceName][realm] || {} instanceNameData[realm] = Object.assign(instanceNameData[realm] || {}, obj)
composeData[instanceName][realm][key] = value
this.set({composeData}) this.set({composeData})
} }

View file

@ -39,7 +39,6 @@ export const store = new PinaforeStore({
pinnedStatuses: {}, pinnedStatuses: {},
instanceInfos: {}, instanceInfos: {},
statusModifications: {}, statusModifications: {},
rawComposeText: '',
customEmoji: {}, customEmoji: {},
composeData: {}, composeData: {},
verifyCredentials: {} verifyCredentials: {}