UNPKG

@instructure/canvas-rce

Version:

A component wrapping Canvas's usage of Tinymce

146 lines (134 loc) 5.23 kB
/* * Copyright (C) 2019 - 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 { typedKeyDict } from '../../../../util/TypedDict'; import { listStyleForSelectionOfEditor } from '../core/ListUtils'; /** * Supported list style types by the RCE */ /** * RCE-specific information about a list style type */ /** * List of list styles supported by the RCE. These will ultimately appear in the toolbar as buttons under the main * list button. */ const listStyleInfos = typedKeyDict({ disc: { listType: 'UL', icon: 'list-bull-default', getTooltipText: () => formatMessage('default bulleted unordered list') }, circle: { listType: 'UL', icon: 'list-bull-circle', getTooltipText: () => formatMessage('circle unordered list') }, square: { listType: 'UL', icon: 'list-bull-square', getTooltipText: () => formatMessage('square unordered list') }, decimal: { listType: 'OL', icon: 'list-num-default', getTooltipText: () => formatMessage('default numerical ordered list') }, 'upper-alpha': { listType: 'OL', icon: 'list-num-upper-alpha', getTooltipText: () => formatMessage('uppercase alphabetic ordered list') }, 'upper-roman': { listType: 'OL', icon: 'list-num-upper-roman', getTooltipText: () => formatMessage('uppercase Roman numeral ordered list') } }, 'listStyleType'); /** * Defines the default `list-style-type` infos for the different list element types. */ const defaultStyleInfoForListType = { UL: listStyleInfos.byKey.disc, OL: listStyleInfos.byKey.decimal }; /** * Determines the inner-most list type for the selection in the given Editor. */ function listStyleInfoForSelectionOfEditor(editor) { const selectedStyleType = listStyleForSelectionOfEditor(editor); if (!selectedStyleType) return null; // Prefer to use `list-style-type`-based type infos. if (selectedStyleType.listStyleType) { // @ts-expect-error const fromStyleType = listStyleInfos.byKey[selectedStyleType.listStyleType]; if (fromStyleType) return fromStyleType; } // Default to style based on the list element type return defaultStyleInfoForListType[selectedStyleType.listType]; } /** * Get the button label. * * Note: Needs to be a function because translation files aren't loaded when top-level code runs. */ const getButtonLabel = () => formatMessage('Ordered and Unordered Lists'); export default function register(editor) { editor.ui.registry.addSplitButton('bullist', { tooltip: getButtonLabel(), icon: 'unordered-list', presets: 'listpreview', columns: 3, fetch: callback => callback(listStyleInfos.values.map(listType => ({ type: 'choiceitem', value: listType.listStyleType, icon: listType.icon, text: listType.getTooltipText() }))), onAction: () => editor.execCommand(listStyleInfoForSelectionOfEditor(editor)?.listType === 'OL' ? 'InsertOrderedList' : 'InsertUnorderedList'), onItemAction: (splitButtonApi, value) => { // @ts-expect-error const desiredListStyleInfo = listStyleInfos.byKey[value] || defaultStyleInfoForListType.UL; const currentListStyleInfo = listStyleInfoForSelectionOfEditor(editor); // When the user clicks the list button that matches the current list style in the selection, // we want to interpret that as un-list-ifying the selection if (currentListStyleInfo?.listStyleType !== desiredListStyleInfo.listStyleType) { editor.execCommand(desiredListStyleInfo.listType === 'UL' ? 'InsertUnorderedList' : 'InsertOrderedList', false, { 'list-style-type': desiredListStyleInfo.listStyleType }); } else { editor.execCommand('RemoveList'); } }, select: value => listStyleInfoForSelectionOfEditor(editor)?.listStyleType === value, onSetup: api => { // Handle updating the icon on the toolbar based on // the type of list the user's selection is currently within const $iconSvgContainer = editor.$(`.tox-split-button[aria-label="${getButtonLabel()}"] .tox-icon`, document); const allIcons = editor.ui.registry.getAll().icons; const nodeChangeHandler = () => { const activeListType = listStyleInfoForSelectionOfEditor(editor); api.setActive(!!activeListType); $iconSvgContainer.html(allIcons[activeListType?.icon || 'list-bull-default']); }; nodeChangeHandler(); editor.on('NodeChange', nodeChangeHandler); return () => editor.off('NodeChange', nodeChangeHandler); } }); }