Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix bug 1556771 - Show a simple text editor for simple Fluent message…
…s. (#1325) This commit refactors the whole `editor` module into 3 smaller modules: - `modules/genericeditor` has code for the generic editor, the one we show for most formats; - `modules/fluenteditor` has code specific for the Fluent format, including a source editor and all variants of the rich Fluent editor; - `core/editor` has code that is shared by both modules, including a `connectedEditor` higher-order component that has all the data and methods Editor implementations need. It also adds basic support for the Fluent Rich Editor. It allows having multiple translation forms for a single string, and switching between the "rich" mode and the "source" mode. It adds a "simple" rich editor for all strings that are, well, "simple".
- Loading branch information
Showing
67 changed files
with
1,903 additions
and
993 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
.editor-menu { | ||
color: #fff; | ||
padding: 10px; | ||
position: relative; | ||
} | ||
|
||
.editor-menu .actions { | ||
float: right; | ||
} | ||
|
||
.editor-menu .actions button { | ||
background: transparent; | ||
border: none; | ||
color: #EBEBEB; | ||
height: 40px; | ||
margin: 0 2px; | ||
padding: 10px 3px; | ||
text-transform: uppercase; | ||
} | ||
|
||
.editor-menu .actions button:hover { | ||
color: #7BC876; | ||
} | ||
|
||
.editor-menu .actions .action-save, | ||
.editor-menu .actions .action-suggest { | ||
background: #7BC876; | ||
border-radius: 3px; | ||
color: #272A2F; | ||
font-weight: 600; | ||
margin-left: 10px; | ||
padding: 10px; | ||
} | ||
|
||
.editor-menu .actions .action-suggest { | ||
background: #4FC4F6; | ||
} | ||
|
||
.editor-menu .actions .action-save:hover, | ||
.editor-menu .actions .action-suggest:hover { | ||
color: #EBEBEB; | ||
} | ||
|
||
.editor-menu .banner { | ||
font-style: italic; | ||
line-height: 40px; | ||
} | ||
|
||
.editor-menu .banner a { | ||
color: #7BC876; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
/* @flow */ | ||
|
||
import * as React from 'react'; | ||
import { Localized } from 'fluent-react'; | ||
|
||
import './EditorMenu.css'; | ||
|
||
import * as editor from 'core/editor'; | ||
import * as user from 'core/user'; | ||
import * as unsavedchanges from 'modules/unsavedchanges'; | ||
|
||
import type { EditorProps } from 'core/editor'; | ||
|
||
|
||
type Props = { | ||
...EditorProps, | ||
firstItemHook?: React.Node, | ||
}; | ||
|
||
/** | ||
* Render the options to control an Editor. | ||
*/ | ||
export default class EditorMenu extends React.Component<Props> { | ||
render() { | ||
const props = this.props; | ||
|
||
return <menu className='editor-menu'> | ||
{ props.firstItemHook } | ||
<editor.FailedChecks | ||
source={ props.editor.source } | ||
user={ props.user } | ||
errors={ props.editor.errors } | ||
warnings={ props.editor.warnings } | ||
resetFailedChecks={ props.resetFailedChecks } | ||
sendTranslation={ props.sendTranslation } | ||
updateTranslationStatus={ props.updateTranslationStatus } | ||
/> | ||
<unsavedchanges.UnsavedChanges /> | ||
{ !props.user.isAuthenticated ? | ||
<Localized | ||
id="editor-editor-sign-in-to-translate" | ||
a={ | ||
<user.SignInLink url={ props.user.signInURL }></user.SignInLink> | ||
} | ||
> | ||
<p className='banner'> | ||
{ '<a>Sign in</a> to translate.' } | ||
</p> | ||
</Localized> | ||
: (props.entity && props.entity.readonly) ? | ||
<Localized | ||
id="editor-editor-read-only-localization" | ||
> | ||
<p className='banner'>This is a read-only localization.</p> | ||
</Localized> | ||
: | ||
<React.Fragment> | ||
<editor.EditorSettings | ||
settings={ props.user.settings } | ||
updateSetting={ props.updateSetting } | ||
/> | ||
<editor.KeyboardShortcuts /> | ||
<editor.TranslationLength | ||
entity={ props.entity } | ||
pluralForm={ props.pluralForm } | ||
translation={ props.editor.translation } | ||
/> | ||
<div className="actions"> | ||
<Localized id="editor-editor-button-copy"> | ||
<button | ||
className="action-copy" | ||
onClick={ props.copyOriginalIntoEditor } | ||
> | ||
Copy | ||
</button> | ||
</Localized> | ||
<Localized id="editor-editor-button-clear"> | ||
<button | ||
className="action-clear" | ||
onClick={ props.clearEditor } | ||
> | ||
Clear | ||
</button> | ||
</Localized> | ||
{ props.user.settings.forceSuggestions ? | ||
// Suggest button, will send an unreviewed translation. | ||
<Localized id="editor-editor-button-suggest"> | ||
<button | ||
className="action-suggest" | ||
onClick={ props.sendTranslation } | ||
> | ||
Suggest | ||
</button> | ||
</Localized> | ||
: | ||
// Save button, will send an approved translation. | ||
<Localized id="editor-editor-button-save"> | ||
<button | ||
className="action-save" | ||
onClick={ props.sendTranslation } | ||
> | ||
Save | ||
</button> | ||
</Localized> | ||
} | ||
</div> | ||
</React.Fragment> | ||
} | ||
</menu>; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import React from 'react'; | ||
import { shallow } from 'enzyme'; | ||
|
||
import EditorMenu from './EditorMenu'; | ||
import EditorSettings from './EditorSettings'; | ||
import KeyboardShortcuts from './KeyboardShortcuts'; | ||
import TranslationLength from './TranslationLength'; | ||
|
||
|
||
const LOCALE = { | ||
code: 'kg', | ||
} | ||
|
||
const SELECTED_ENTITY = { | ||
pk: 42, | ||
original: 'le test', | ||
original_plural: 'les tests', | ||
translation: [ | ||
{ string: 'test' }, | ||
{ string: 'test plural' }, | ||
], | ||
}; | ||
|
||
|
||
function createEditorMenu({ | ||
forceSuggestions = true, | ||
isAuthenticated = true, | ||
entity = SELECTED_ENTITY, | ||
firstItemHook = null, | ||
} = {}) { | ||
return shallow(<EditorMenu | ||
editor={ | ||
{ translation: 'initial' } | ||
} | ||
locale={ LOCALE } | ||
parameters={ | ||
{ resource: 'resource' } | ||
} | ||
entity={ entity } | ||
user={ { | ||
isAuthenticated, | ||
username: 'Sarevok', | ||
settings: { | ||
forceSuggestions, | ||
}, | ||
} } | ||
firstItemHook={ firstItemHook } | ||
/>); | ||
} | ||
|
||
|
||
function expectHiddenSettingsAndActions(wrapper) { | ||
expect(wrapper.find('button')).toHaveLength(0); | ||
expect(wrapper.find(EditorSettings)).toHaveLength(0); | ||
expect(wrapper.find(KeyboardShortcuts)).toHaveLength(0); | ||
expect(wrapper.find(TranslationLength)).toHaveLength(0); | ||
expect(wrapper.find('#editor-editor-button-copy')).toHaveLength(0); | ||
} | ||
|
||
|
||
describe('<EditorMenu>', () => { | ||
it('renders correctly', () => { | ||
const wrapper = createEditorMenu(); | ||
|
||
// 3 buttons to control the editor. | ||
expect(wrapper.find('button')).toHaveLength(3); | ||
}); | ||
|
||
it('shows the Save button when forceSuggestions is off', () => { | ||
const wrapper = createEditorMenu({ forceSuggestions: false }); | ||
|
||
expect(wrapper.find('.action-save').exists()).toBeTruthy(); | ||
}); | ||
|
||
it('hides the settings and actions when the user is logged out', () => { | ||
const wrapper = createEditorMenu({ isAuthenticated: false }); | ||
|
||
expectHiddenSettingsAndActions(wrapper); | ||
|
||
expect(wrapper.find('#editor-editor-sign-in-to-translate')).toHaveLength(1); | ||
}); | ||
|
||
it('hides the settings and actions when the entity is read-only', () => { | ||
const entity = { | ||
...SELECTED_ENTITY, | ||
readonly: true, | ||
} | ||
const wrapper = createEditorMenu({ entity }); | ||
|
||
expectHiddenSettingsAndActions(wrapper); | ||
|
||
expect(wrapper.find('#editor-editor-read-only-localization')).toHaveLength(1); | ||
}); | ||
|
||
it('accepts a firstItemHook and shows it as its first child', () => { | ||
const firstItemHook = <p>Hello</p>; | ||
const wrapper = createEditorMenu({ firstItemHook }); | ||
|
||
expect(wrapper.find('menu').children().first().text()).toEqual('Hello'); | ||
}); | ||
}); |
File renamed without changes.
File renamed without changes.
Oops, something went wrong.