forked from cybrespace/pinafore
refactor
This commit is contained in:
parent
66801fbc96
commit
3dc46791e9
|
@ -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
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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">×</span>
|
<span aria-hidden="true">×</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>
|
|
@ -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>
|
|
@ -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})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,6 @@ export const store = new PinaforeStore({
|
||||||
pinnedStatuses: {},
|
pinnedStatuses: {},
|
||||||
instanceInfos: {},
|
instanceInfos: {},
|
||||||
statusModifications: {},
|
statusModifications: {},
|
||||||
rawComposeText: '',
|
|
||||||
customEmoji: {},
|
customEmoji: {},
|
||||||
composeData: {},
|
composeData: {},
|
||||||
verifyCredentials: {}
|
verifyCredentials: {}
|
||||||
|
|
Loading…
Reference in New Issue