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:
parent
3cb22ed0af
commit
5c83dc26e2
|
@ -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}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
|
@ -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)
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue