Add buttons and icons toolbar/menu option in RCE

closes MAT-160
closes MAT-161

Test plan:
- Navigate to a page with RCE
- Verify there is a new toolbar option for buttons and icons
- Verify it is behind the new feature flag
- Verify it is available only in course context

Change-Id: Id0d24a95612572c056e5c84e78c0ee331aa1ed42
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/265607
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Ed Schiebel <eschiebel@instructure.com>
QA-Review: Ed Schiebel <eschiebel@instructure.com>
Product-Review: Guilherme Baron <gbaron@instructure.com>
This commit is contained in:
Guilherme Baron 2021-05-24 16:38:34 -03:00
parent 3cb22ed0af
commit 5c83dc26e2
6 changed files with 136 additions and 5 deletions

View File

@ -62,6 +62,7 @@ const baseProps = {
// }, // },
highContrastCSS: [], highContrastCSS: [],
use_rce_pretty_html_editor: true, use_rce_pretty_html_editor: true,
use_rce_buttons_and_icons: true,
editorOptions: {...defaultTinymceConfig} editorOptions: {...defaultTinymceConfig}
} }

View File

@ -220,7 +220,8 @@ class RCEWrapper extends React.Component {
plugins: PropTypes.arrayOf(PropTypes.string), plugins: PropTypes.arrayOf(PropTypes.string),
instRecordDisabled: PropTypes.bool, instRecordDisabled: PropTypes.bool,
highContrastCSS: PropTypes.arrayOf(PropTypes.string), highContrastCSS: PropTypes.arrayOf(PropTypes.string),
use_rce_pretty_html_editor: PropTypes.bool use_rce_pretty_html_editor: PropTypes.bool,
use_rce_buttons_and_icons: PropTypes.bool
} }
static defaultProps = { static defaultProps = {
@ -1263,6 +1264,14 @@ class RCEWrapper extends React.Component {
canvasPlugins.splice(2, 0, 'instructure_record') canvasPlugins.splice(2, 0, 'instructure_record')
} }
if (
rcsExists &&
this.props.use_rce_buttons_and_icons &&
this.props.trayProps?.contextType === 'course'
) {
canvasPlugins.push('instructure_buttons')
}
const wrappedOpts = { const wrappedOpts = {
...options, ...options,
@ -1315,7 +1324,7 @@ class RCEWrapper extends React.Component {
insert: { insert: {
title: formatMessage('Insert'), title: formatMessage('Insert'),
items: items:
'instructure_links instructure_image instructure_media instructure_document | instructure_equation inserttable instructure_media_embed | hr' 'instructure_links instructure_image instructure_media instructure_document instructure_buttons | instructure_equation inserttable instructure_media_embed | hr'
}, },
tools: {title: formatMessage('Tools'), items: 'wordcount'}, tools: {title: formatMessage('Tools'), items: 'wordcount'},
view: {title: formatMessage('View'), items: 'fullscreen instructure_html_view'} view: {title: formatMessage('View'), items: 'fullscreen instructure_html_view'}
@ -1347,7 +1356,8 @@ class RCEWrapper extends React.Component {
'instructure_links', 'instructure_links',
'instructure_image', 'instructure_image',
'instructure_record', 'instructure_record',
'instructure_documents' 'instructure_documents',
'instructure_buttons'
] ]
}, },
{ {

View File

@ -47,7 +47,7 @@ import {
IconUnderlineLine IconUnderlineLine
} from '@instructure/ui-icons/es/svg' } from '@instructure/ui-icons/es/svg'
tinymce.PluginManager.add('instructure-ui-icons', function(editor) { tinymce.PluginManager.add('instructure-ui-icons', function (editor) {
// the keys here are what tinymce calls it. the values are the svgs from instUI // the keys here are what tinymce calls it. the values are the svgs from instUI
// there are few things here that are commented out that are things that we // there are few things here that are commented out that are things that we
@ -131,6 +131,17 @@ tinymce.PluginManager.add('instructure-ui-icons', function(editor) {
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.7647 5.21417C13.6694 5.21417 13.5773 5.23988 13.482 5.24631C12.8329 3.36281 11.0795 2 9 2C6.53506 2 4.52435 3.91029 4.28294 6.34234C4.09341 6.31127 3.90176 6.28556 3.70588 6.28556C1.66235 6.28556 0 7.96764 0 10.0354C0 12.1032 1.66235 13.7853 3.70588 13.7853L5 13.7853V12.7139L3.70588 12.7139C2.24682 12.7139 1.05882 11.5129 1.05882 10.0354C1.05882 8.55798 2.24682 7.35695 3.70588 7.35695C4.40259 7.35695 5.06012 7.62908 5.55882 8.12192L6.29894 7.35588C6.00565 7.0666 5.66788 6.84483 5.30894 6.66912C5.38941 4.67419 7.00835 3.07139 9 3.07139C11.0435 3.07139 12.7059 4.75347 12.7059 6.82126C12.7059 7.1491 12.6635 7.47266 12.582 7.78658L13.6059 8.06085C13.7107 7.65801 13.7647 7.24124 13.7647 6.82126C13.7647 6.64019 13.7308 6.4677 13.7118 6.29199C13.7298 6.29199 13.7467 6.28556 13.7647 6.28556C15.516 6.28556 16.9412 7.72765 16.9412 9.49973C16.9412 11.2718 15.516 12.7139 13.7647 12.7139L13 12.7139V13.7853L13.7647 13.7853C16.1005 13.7853 18 11.8632 18 9.49973C18 7.13624 16.1005 5.21417 13.7647 5.21417Z" fill="#2B3B46"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M13.7647 5.21417C13.6694 5.21417 13.5773 5.23988 13.482 5.24631C12.8329 3.36281 11.0795 2 9 2C6.53506 2 4.52435 3.91029 4.28294 6.34234C4.09341 6.31127 3.90176 6.28556 3.70588 6.28556C1.66235 6.28556 0 7.96764 0 10.0354C0 12.1032 1.66235 13.7853 3.70588 13.7853L5 13.7853V12.7139L3.70588 12.7139C2.24682 12.7139 1.05882 11.5129 1.05882 10.0354C1.05882 8.55798 2.24682 7.35695 3.70588 7.35695C4.40259 7.35695 5.06012 7.62908 5.55882 8.12192L6.29894 7.35588C6.00565 7.0666 5.66788 6.84483 5.30894 6.66912C5.38941 4.67419 7.00835 3.07139 9 3.07139C11.0435 3.07139 12.7059 4.75347 12.7059 6.82126C12.7059 7.1491 12.6635 7.47266 12.582 7.78658L13.6059 8.06085C13.7107 7.65801 13.7647 7.24124 13.7647 6.82126C13.7647 6.64019 13.7308 6.4677 13.7118 6.29199C13.7298 6.29199 13.7467 6.28556 13.7647 6.28556C15.516 6.28556 16.9412 7.72765 16.9412 9.49973C16.9412 11.2718 15.516 12.7139 13.7647 12.7139L13 12.7139V13.7853L13.7647 13.7853C16.1005 13.7853 18 11.8632 18 9.49973C18 7.13624 16.1005 5.21417 13.7647 5.21417Z" fill="#2B3B46"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.72039 10.6479L8.3603 11.1813L6.75882 13.1025L8.36029 15.0239L7.72038 15.5573L5.6748 13.1024L7.72039 10.6479ZM10.2802 10.6479L12.3258 13.1024L10.2802 15.5573L9.64031 15.0239L11.2418 13.1025L9.6403 11.1813L10.2802 10.6479Z" fill="#2D3B45"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M7.72039 10.6479L8.3603 11.1813L6.75882 13.1025L8.36029 15.0239L7.72038 15.5573L5.6748 13.1024L7.72039 10.6479ZM10.2802 10.6479L12.3258 13.1024L10.2802 15.5573L9.64031 15.0239L11.2418 13.1025L9.6403 11.1813L10.2802 10.6479Z" fill="#2D3B45"/>
</svg>` </svg>`
},
buttons: {
src: `
<svg width="18" height="18" viewBox="0 0 1920 1920" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="53.4444" y="53.4444" width="796.64" height="796.64" stroke="#2D3B45" stroke-width="106.889" fill="none"/>
<circle cx="1468.24" cy="451.765" r="398.32" stroke="#2D3B45" stroke-width="106.889" fill="none"/>
<path d="M817.067 1069.91L451.777 1800.49L86.4873 1069.91L817.067 1069.91Z" stroke="#2D3B45" stroke-width="106.889" fill="none"/>
<path d="M1471.29 1861.78L1069.92 1661.09V1275.38L1468.24 1076.22L1866.56 1275.38V1713.55L1471.29 1861.78Z" stroke="#2D3B45" stroke-width="106.889" fill="none"/>
</svg>
`
} }
} }
Object.keys(icons).forEach(key => { Object.keys(icons).forEach(key => {

View File

@ -0,0 +1,107 @@
/*
* Copyright (C) 2021 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import formatMessage from '../../../format-message'
import {isOKToLink} from '../../contentInsertionUtils'
const CREATE_BUTTON = 'create_button'
const LIST_BUTTON = 'list_buttons'
function getMenuItems() {
return [
{
text: formatMessage('Create Button and Icon'),
value: 'instructure_create_button'
},
{
text: formatMessage('Saved Buttons and Icons'),
value: 'instructure_list_buttons'
}
]
}
function handleOptionSelected(ed, value) {
switch (value) {
case 'instructure_create_button':
ed.focus(true)
ed.execCommand('instructureTrayForButtonsPlugin', false, CREATE_BUTTON)
break
case 'instructure_list_buttons':
ed.focus(true)
ed.execCommand('instructureTrayForButtonsPlugin', false, LIST_BUTTON)
break
}
}
tinymce.create('tinymce.plugins.InstructureButtonsPlugin', {
init(ed) {
// Register tray control command
ed.addCommand('instructureTrayForButtonsPlugin', (ui, action) => {
console.log(`show tray for ${action}`)
})
// Register menu items
ed.ui.registry.addNestedMenuItem('instructure_buttons', {
text: formatMessage('Buttons and Icons'),
icon: 'buttons',
getSubmenuItems: () =>
getMenuItems().map(item => ({
type: 'menuitem',
text: item.text,
onAction: () => handleOptionSelected(ed, item.value),
onSetup: api => {
api.setDisabled(!isOKToLink(ed.selection.getContent()))
return () => {}
}
}))
})
// Register button
ed.ui.registry.addSplitButton('instructure_buttons', {
tooltip: formatMessage('Buttons and Icons'),
icon: 'buttons',
fetch(callback) {
const items = getMenuItems().map(item => ({
type: 'choiceitem',
text: item.text,
value: item.value
}))
callback(items)
},
onAction(api) {
if (!api.isDisabled()) {
handleOptionSelected(ed, 'instructure_create_button')
}
},
onItemAction: (_splitButtonApi, value) => handleOptionSelected(ed, value),
onSetup(api) {
function handleNodeChange(_e) {
api.setDisabled(!isOKToLink(ed.selection.getContent()))
}
setTimeout(handleNodeChange)
ed.on('NodeChange', handleNodeChange)
return () => {
ed.off('NodeChange', handleNodeChange)
}
}
})
}
})
// Register plugin
tinymce.PluginManager.add('instructure_buttons', tinymce.plugins.InstructureButtonsPlugin)

View File

@ -50,6 +50,7 @@ import './plugins/instructure_links/plugin'
import './plugins/instructure_documents/plugin' import './plugins/instructure_documents/plugin'
import './plugins/instructure_html_view/plugin' import './plugins/instructure_html_view/plugin'
import './plugins/instructure_media_embed/plugin' import './plugins/instructure_media_embed/plugin'
import './plugins/instructure_buttons/plugin'
import 'tinymce-a11y-checker' import 'tinymce-a11y-checker'

View File

@ -243,7 +243,8 @@ const RCELoader = {
autosave, autosave,
instRecordDisabled: ENV.RICH_CONTENT_INST_RECORD_TAB_DISABLED, instRecordDisabled: ENV.RICH_CONTENT_INST_RECORD_TAB_DISABLED,
highContrastCSS: window.ENV?.url_for_high_contrast_tinymce_editor_css, highContrastCSS: window.ENV?.url_for_high_contrast_tinymce_editor_css,
use_rce_pretty_html_editor: !!window.ENV?.FEATURES?.rce_pretty_html_editor use_rce_pretty_html_editor: !!window.ENV?.FEATURES?.rce_pretty_html_editor,
use_rce_buttons_and_icons: !!window.ENV?.FEATURES?.rce_buttons_and_icons
} }
} }
} }