feat: add length gauge for media alt text editor (#1431)

* feat: add length gauge for media alt text editor

* fix test
This commit is contained in:
Nolan Lawson 2019-08-24 21:23:43 -07:00 committed by GitHub
parent 7f9195c2af
commit 59d26f1a09
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 163 additions and 103 deletions

View file

@ -0,0 +1,59 @@
<div class="length-gauge {shouldAnimate ? 'should-animate' : ''} {overLimit ? 'over-char-limit' : ''}"
style={computedStyle}
aria-hidden="true"
></div>
<style>
.length-gauge {
height: 2px;
background: var(--main-theme-color);
transform-origin: left;
}
.length-gauge.should-animate {
transition: transform 0.2s linear;
}
.length-gauge.over-char-limit {
background: var(--warning-color);
}
</style>
<script>
import { mark, stop } from '../_utils/marks'
import { store } from '../_store/store'
import { observe } from 'svelte-extras'
import { throttleTimer } from '../_utils/throttleTimer'
const updateDisplayedLength = process.browser && throttleTimer(requestAnimationFrame)
export default {
oncreate () {
const { lengthAsFraction } = this.get()
this.set({ lengthAsFractionDeferred: lengthAsFraction })
// perf improvement for keyboard input latency
this.observe('lengthAsFraction', () => {
updateDisplayedLength(() => {
mark('set lengthAsFractionDeferred')
const { lengthAsFraction } = this.get()
this.set({ lengthAsFractionDeferred: lengthAsFraction })
stop('set lengthAsFractionDeferred')
requestAnimationFrame(() => this.set({ shouldAnimate: true }))
})
}, { init: false })
},
data: () => ({
shouldAnimate: false,
lengthAsFractionDeferred: 0,
style: ''
}),
store: () => store,
computed: {
lengthAsFraction: ({ length, max }) => {
// We don't need to update the gauge for every decimal point, so round it to the nearest 0.02
const int = Math.round(Math.min(max, length) / max * 100)
return (int - (int % 2)) / 100
},
computedStyle: ({ style, lengthAsFractionDeferred }) => `transform: scaleX(${lengthAsFractionDeferred}); ${style}`
},
methods: {
observe
}
}
</script>

View file

@ -0,0 +1,56 @@
<span class="length-indicator {overLimit ? 'over-char-limit' : ''}"
aria-label={lengthLabel}
{style}
>{lengthToDisplayDeferred}</span>
<style>
.length-indicator {
color: var(--length-indicator-color);
font-size: 1.3em;
}
.length-indicator.over-char-limit {
color: var(--warning-color);
}
</style>
<script>
import { mark, stop } from '../_utils/marks'
import { store } from '../_store/store'
import { observe } from 'svelte-extras'
import { throttleTimer } from '../_utils/throttleTimer'
const updateDisplayedLength = process.browser && throttleTimer(requestAnimationFrame)
export default {
oncreate () {
const { lengthToDisplay } = this.get()
this.set({ lengthToDisplayDeferred: lengthToDisplay })
// perf improvement for keyboard input latency
this.observe('lengthToDisplay', () => {
updateDisplayedLength(() => {
mark('set lengthToDisplayDeferred')
const { lengthToDisplay } = this.get()
this.set({ lengthToDisplayDeferred: lengthToDisplay })
stop('set lengthToDisplayDeferred')
})
}, { init: false })
},
data: () => ({
lengthToDisplayDeferred: 0,
style: ''
}),
store: () => store,
computed: {
lengthToDisplay: ({ length, max }) => max - length,
lengthLabel: ({ overLimit, lengthToDisplayDeferred }) => {
if (overLimit) {
return `${lengthToDisplayDeferred} characters over limit`
} else {
return `${lengthToDisplayDeferred} characters remaining`
}
}
},
methods: {
observe
}
}
</script>

View file

@ -1,57 +1,16 @@
<div class="compose-box-length-gauge {shouldAnimate ? 'should-animate' : ''} {overLimit ? 'over-char-limit' : ''}"
style="transform: scaleX({lengthAsFractionDeferred});"
aria-hidden="true"
></div>
<style>
.compose-box-length-gauge {
grid-area: gauge;
margin: 0 0 5px 5px;
height: 2px;
background: var(--main-theme-color);
transform-origin: left;
}
.compose-box-length-gauge.should-animate {
transition: transform 0.2s linear;
}
.compose-box-length-gauge.over-char-limit {
background: var(--warning-color);
}
</style>
<LengthGauge style="grid-area: gauge; margin: 0 0 5px 5px;"
{length}
{overLimit}
max={$maxStatusChars}
/>
<script>
import { mark, stop } from '../../_utils/marks'
import LengthGauge from '../LengthGauge.html'
import { store } from '../../_store/store'
import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
import { observe } from 'svelte-extras'
export default {
oncreate () {
const { lengthAsFraction } = this.get()
this.set({ lengthAsFractionDeferred: lengthAsFraction })
// perf improvement for keyboard input latency
this.observe('lengthAsFraction', () => {
scheduleIdleTask(() => {
mark('set lengthAsFractionDeferred')
const { lengthAsFraction } = this.get()
this.set({ lengthAsFractionDeferred: lengthAsFraction })
stop('set lengthAsFractionDeferred')
requestAnimationFrame(() => this.set({ shouldAnimate: true }))
})
}, { init: false })
components: {
LengthGauge
},
data: () => ({
shouldAnimate: false,
lengthAsFractionDeferred: 0
}),
store: () => store,
computed: {
lengthAsFraction: ({ length, $maxStatusChars }) => {
// We don't need to update the gauge for every decimal point, so round it to the nearest 0.02
const int = Math.round(Math.min($maxStatusChars, length) / $maxStatusChars * 100)
return (int - (int % 2)) / 100
}
},
methods: {
observe
}
store: () => store
}
</script>

View file

@ -1,56 +1,17 @@
<span class="compose-box-length {overLimit ? 'over-char-limit' : ''}"
aria-label={lengthLabel}>
{lengthToDisplayDeferred}
</span>
<style>
.compose-box-length {
grid-area: length;
justify-self: right;
color: var(--length-indicator-color);
font-size: 1.3em;
align-self: center;
}
.compose-box-length.over-char-limit {
color: var(--warning-color);
}
</style>
<LengthIndicator
{length}
{overLimit}
max={$maxStatusChars}
style="grid-area: length; justify-self: right; align-self: center;"
/>
<script>
import { mark, stop } from '../../_utils/marks'
import { store } from '../../_store/store'
import { scheduleIdleTask } from '../../_utils/scheduleIdleTask'
import { observe } from 'svelte-extras'
import LengthIndicator from '../LengthIndicator.html'
export default {
oncreate () {
const { lengthToDisplay } = this.get()
this.set({ lengthToDisplayDeferred: lengthToDisplay })
// perf improvement for keyboard input latency
this.observe('lengthToDisplay', () => {
scheduleIdleTask(() => {
mark('set lengthToDisplayDeferred')
const { lengthToDisplay } = this.get()
this.set({ lengthToDisplayDeferred: lengthToDisplay })
stop('set lengthToDisplayDeferred')
})
}, { init: false })
},
data: () => ({
lengthToDisplayDeferred: 0
}),
store: () => store,
computed: {
lengthToDisplay: ({ length, $maxStatusChars }) => $maxStatusChars - length,
lengthLabel: ({ overLimit, lengthToDisplayDeferred }) => {
if (overLimit) {
return `${lengthToDisplayDeferred} characters over limit`
} else {
return `${lengthToDisplayDeferred} characters remaining`
}
}
},
methods: {
observe
components: {
LengthIndicator
}
}
</script>

View file

@ -26,7 +26,6 @@
placeholder="Description"
ref:textarea
bind:value=rawText
maxlength="420"
></textarea>
<label for="compose-media-input-{uuid}" class="sr-only">
Describe for the visually impaired (image, video) or auditorily impaired (audio, video)

View file

@ -5,15 +5,26 @@
placeholder="Description"
ref:textarea
bind:value=rawText
maxlength="420"
></textarea>
<label for="the-media-alt-input-{realm}-{index}" class="sr-only">
Describe for the visually impaired
</label>
<LengthGauge
{length}
{overLimit}
max={mediaAltCharLimit}
/>
<LengthIndicator
{length}
{overLimit}
max={mediaAltCharLimit}
style="width: 100%; text-align: right;"
/>
</div>
<style>
.media-alt-editor {
display: flex;
flex-direction: column;
}
.media-alt-input {
padding: 5px;
@ -42,6 +53,10 @@
import { throttleTimer } from '../../../_utils/throttleTimer'
import { get } from '../../../_utils/lodash-lite'
import { store } from '../../../_store/store'
import { MEDIA_ALT_CHAR_LIMIT } from '../../../_static/media'
import LengthGauge from '../../LengthGauge.html'
import LengthIndicator from '../../LengthIndicator.html'
import { length } from 'stringz'
const updateRawTextInStore = throttleTimer(requestPostAnimationFrame)
@ -56,8 +71,13 @@
},
store: () => store,
data: () => ({
rawText: ''
rawText: '',
mediaAltCharLimit: MEDIA_ALT_CHAR_LIMIT
}),
computed: {
length: ({ rawText }) => length(rawText || ''),
overLimit: ({ mediaAltCharLimit, length }) => length > mediaAltCharLimit
},
methods: {
observe,
setupSyncFromStore () {
@ -99,6 +119,10 @@
measure () {
autosize.update(this.refs.textarea)
}
},
components: {
LengthGauge,
LengthIndicator
}
}
</script>

View file

@ -8,3 +8,5 @@ export const mediaAccept = '.jpg,.jpeg,.png,.gif,.webm,.mp4,.m4v,.mov,image/jpeg
'image/gif,video/webm,video/mp4,video/quicktime,' +
// some instances allow audio uploads
'audio/mpeg,audio/mp4,audio/vnd.wav,audio/wav,audio/x-wav,audio/x-wave,audio/ogg'
export const MEDIA_ALT_CHAR_LIMIT = 420

View file

@ -19,7 +19,7 @@ export const formError = $('.form-error-user-error')
export const composeInput = $('.compose-box-input')
export const composeContentWarning = $('.content-warning-input')
export const composeButton = $('.compose-box-button')
export const composeLengthIndicator = $('.compose-box-length')
export const composeLengthIndicator = $('.length-indicator')
export const emojiButton = $('.compose-box-toolbar button:first-child')
export const mediaButton = $('.compose-box-toolbar button:nth-child(2)')
export const pollButton = $('.compose-box-toolbar button:nth-child(3)')