Update RCE StatusBar

closes CORE-2965 CORE-2966

1. moves html-rich view toggle to the right
2. hides everything but the toggle when in html view
3. adds tooltips to the status bar buttons via a title attribute
   (so they look like the tooltips the toolbar buttons have)
4. removes the language select

test plan:
  - load up an rce and see that ^those changes are reflected in
    what you see

Change-Id: Iac1dd133a779789addca2e8db9af6f192c394b70
Reviewed-on: https://gerrit.instructure.com/194102
Reviewed-by: Ryan Shaw <ryan@instructure.com>
QA-Review: Ryan Shaw <ryan@instructure.com>
Tested-by: Jenkins
Product-Review: Ed Schiebel <eschiebel@instructure.com>
This commit is contained in:
Ed Schiebel 2019-05-17 10:50:33 -04:00
parent fe01162306
commit 946c34b1c6
6 changed files with 69 additions and 46 deletions

View File

@ -44,7 +44,7 @@ function getProps(textareaId, state) {
};
},
textareaClassName: "exampleClassOne exampleClassTwo",
textareaClassName: "exampleClassOne",
textareaId,
trayProps: {

View File

@ -15,6 +15,7 @@
"test:cafe": "yarn build:cafe && yarn test:cafe:only",
"test:cafe:only": "testcafe chrome testcafe/**/*.test.js",
"test:cafe:all": "yarn build:cafe && testcafe chrome,firefox,safari testcafe/**/*.test.js",
"test:cafe:debug": "testcafe --inspect-brk chrome testcafe/**/*.test.js",
"test:watch": "BABEL_ENV=test-node mocha 'test/**/*.test.js' --require @instructure/ui-themes/lib/canvas --require @babel/register --watch",
"test:coverage": "cross-env BABEL_ENV=test-node nyc -r html -r json node_modules/.bin/mocha 'test/**/*.test.js'",
"debug": "BABEL_ENV=test-node inspect _mocha --no-timeouts --debug-brk 'test/**/*.test.js' --require @instructure/ui-themes/lib/canvas --require @babel/register",

View File

@ -138,7 +138,8 @@ class RCEWrapper extends React.Component {
this.state = {
path: [],
wordCount: 0
wordCount: 0,
isHtmlView: false
}
}
@ -287,12 +288,10 @@ class RCEWrapper extends React.Component {
toggle = () => {
if (this.isHidden()) {
this.setCode(this.textareaValue());
this.getTextarea().setAttribute('aria-hidden', true);
this.setState({isHtmlView: false})
} else {
this.getTextarea().removeAttribute('aria-hidden');
this.setState({isHtmlView: true})
}
this.onTinyMCEInstance("mceToggleEditor");
}
focus() {
@ -453,8 +452,18 @@ class RCEWrapper extends React.Component {
this.registerTextareaChange();
}
componentDidUpdate() {
componentDidUpdate(_prevProps, prevState) {
this.registerTextareaChange();
if(prevState.isHtmlView !== this.state.isHtmlView) {
if (this.state.isHtmlView) {
this.getTextarea().removeAttribute('aria-hidden');
this.mceInstance().hide()
} else {
this.setCode(this.textareaValue());
this.getTextarea().setAttribute('aria-hidden', true);
this.mceInstance().show()
}
}
}
render() {
@ -481,6 +490,7 @@ class RCEWrapper extends React.Component {
onToggleHtml={this.toggle}
path={this.state.path}
wordCount={this.state.wordCount}
isHtmlView={this.state.isHtmlView}
/>
<CanvasContentTray bridge={Bridge} {...trayProps} />
</div>

View File

@ -17,13 +17,12 @@
*/
import React from 'react'
import {arrayOf, func, number, string} from 'prop-types'
import {arrayOf, bool, func, number, string} from 'prop-types'
import {StyleSheet, css} from 'aphrodite'
import { Button } from '@instructure/ui-buttons'
import { Flex, View } from '@instructure/ui-layout'
import { ScreenReaderContent } from '@instructure/ui-a11y'
import { Text } from '@instructure/ui-elements'
import { Select } from '@instructure/ui-forms'
import {SVGIcon} from '@instructure/ui-svg-images'
import { IconA11yLine, IconKeyboardShortcutsLine, IconMiniArrowEndLine } from '@instructure/ui-icons'
import formatMessage from '../format-message'
@ -31,7 +30,8 @@ import formatMessage from '../format-message'
StatusBar.propTypes = {
onToggleHtml: func,
path: arrayOf(string),
wordCount: number
wordCount: number,
isHtmlView: bool
}
function renderPath({path}) {
@ -59,53 +59,60 @@ function emptyTagIcon() {
)
}
export default function StatusBar(props) {
return (
<div>
<Flex margin="x-small">
if (props.isHtmlView) {
const toggleToRich = formatMessage('Switch to rich text view')
return (
<View display="block" margin="x-small" textAlign="end" data-testid="RCEStatusBar">
<Button variant="link" icon={emptyTagIcon()} onClick={props.onToggleHtml} title={toggleToRich}>
<ScreenReaderContent>{toggleToRich}</ScreenReaderContent>
</Button>
</View>
)
} else {
const kbshortcut = formatMessage('View keyboard shortcuts')
const a11y = formatMessage('Accessibility')
const wordCount = formatMessage(`{count, plural,
=0 {0 words}
one {1 word}
other {# words}
}`, {count: props.wordCount})
const toggleToHtml = formatMessage('Switch to raw html view')
return (
<Flex margin="x-small" data-testid="RCEStatusBar">
<Flex.Item grow>
<View>{renderPath(props)}</View>
</Flex.Item>
<Flex.Item>
<View display="inline-block" padding="0 x-small">
<Button variant="link" icon={IconKeyboardShortcutsLine}>
<ScreenReaderContent>{formatMessage('View keyboard shortcuts')}</ScreenReaderContent>
<Button variant="link" icon={IconKeyboardShortcutsLine} title={kbshortcut}>
<ScreenReaderContent>{kbshortcut}</ScreenReaderContent>
</Button>
<Button variant="link" icon={IconA11yLine}>
<ScreenReaderContent>{formatMessage('Accessibility')}</ScreenReaderContent>
<Button variant="link" icon={IconA11yLine} title={a11y}>
<ScreenReaderContent>{a11y}</ScreenReaderContent>
</Button>
</View>
<div className={css(styles.separator)}/>
</Flex.Item>
<Flex.Item>
<View display="inline-block" padding="0 x-small">
<Button variant="link" icon={emptyTagIcon()} onClick={props.onToggleHtml}>
<ScreenReaderContent>{formatMessage('Toggle raw html view')}</ScreenReaderContent>
</Button>
</View>
<div className={css(styles.separator)}/>
</Flex.Item>
<Flex.Item>
<View display="inline-block" padding="0 x-small xxx-small x-small">
<Select
inline
width="12rem"
size="small"
label={<ScreenReaderContent>Select a language</ScreenReaderContent>}
>
<option value="en-us">English (US)</option>
<option value="fr">French</option>
</Select>
<View display="inline-block" padding="0 small xx-small small">
<Text>{wordCount}</Text>
</View>
<div className={css(styles.separator)}/>
</Flex.Item>
<Flex.Item>
<View display="inline-block" padding="0 0 0 small">
<Text>{formatMessage('{count} words', {count: props.wordCount})}</Text>
<Button variant="link" icon={emptyTagIcon()} onClick={props.onToggleHtml} title={toggleToHtml}>
<ScreenReaderContent>{toggleToHtml}</ScreenReaderContent>
</Button>
</View>
</Flex.Item>
</Flex>
</div>
)
)
}
}
const styles = StyleSheet.create({

View File

@ -149,13 +149,6 @@ describe("RCEWrapper", () => {
element = createBasicElement();
});
it("calls toggle on its tinyMCE instance", () => {
element.toggle();
assert(
execCommandSpy.withArgs("mceToggleEditor", false, textareaId).called
);
});
it("syncs content during toggle if coming back from hidden instance", () => {
element = createdMountedElement().getMountedInstance();
editor.hidden = true;

View File

@ -28,19 +28,31 @@ test('toggles between rce and html views', async t => {
const textarea = Selector('#textarea')
const rceContainer = Selector('.tox-tinymce')
const toggleButton = Selector('button').withText('</>')
const wordCount = Selector('span').withText('0 words').nth(-1)
await t.expect(rceContainer.visible).ok('rce should be initially visible')
await t.expect(wordCount.count).eql(1)
await t.expect(textarea.visible).notOk('textarea should be initially invisible')
await t.click(toggleButton)
await t.expect(rceContainer.visible).notOk('rce should be invisible after toggle')
await t.expect(wordCount.count).eql(0)
await t.expect(textarea.visible).ok('textarea should be visible after toggle')
await t.click(toggleButton)
await t.expect(rceContainer.visible).ok('rce should be visible after toggling again')
await t.expect(wordCount.count).eql(1)
await t.expect(textarea.visible).notOk('textarea should be hidden after toggling again')
})
test('counts words', async t => {
// search for the exact text for the selector will wait for it to change to this text
await t.switchToIframe(tinyIframe).typeText('body', 'foo bar baz bing')
await t.expect(Selector('span').withText('0 words')).exists
await t.switchToIframe(tinyIframe).typeText('body', 'foo')
await t
.switchToMainWindow()
.expect(Selector('span').withText('1 word').exists)
.ok()
await t.switchToIframe(tinyIframe).typeText('body', ' bar baz bing')
await t
.switchToMainWindow()
.expect(Selector('span').withText('4 words').exists)