UNPKG

@instructure/canvas-rce

Version:

A component wrapping Canvas's usage of Tinymce

170 lines (167 loc) 5.55 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 React, { Component, useRef } from 'react'; import { bool, func, string } from 'prop-types'; import { linkShape, linksShape, linkType } from './propTypes'; import formatMessage from '../../../../format-message'; import { ScreenReaderContent } from '@instructure/ui-a11y-content'; import { List } from '@instructure/ui-list'; import { View } from '@instructure/ui-view'; import uid from '@instructure/uid'; import { LoadMoreButton, LoadingIndicator, LoadingStatus, useIncrementalLoading } from '../../../../common/incremental-loading'; import Link from './Link'; import RCEGlobals from '../../../RCEGlobals'; import { NoResults } from './NoResults'; /* * This is needed only as long as `LinkSet` is a class component. */ function IncrementalLoader(props) { const { children, collection, fetchInitialPage, fetchNextPage, contextType, searchString } = props; const { hasMore, isLoading, links } = collection; const lastItemRef = useRef(null); const loader = useIncrementalLoading({ hasMore: hasMore && fetchNextPage != null, isLoading, lastItemRef, contextType, sortBy: { sort: 'alphabetical', order: 'asc' }, // not actually used in the query, but a required param searchString, onLoadInitial() { if (fetchInitialPage) { fetchInitialPage(); } }, onLoadMore() { fetchNextPage(); }, records: links }); return children({ loader, lastItemRef }); } class LinkSet extends Component { constructor(props) { super(props); this.describedByID = `rce-LinkSet-describedBy-${uid()}`; this.loadMoreButtonRef = null; } hasLinks(props) { return props.collection.links.length > 0; } isEmpty(props) { return !this.hasLinks(props) && !props.collection.hasMore && !props.collection.isLoading; } compareURLs(url1 = '', url2 = '') { if (url1 === '' || url2 === '') return false; return url1.split('?')[0] === url2.split('?')[0]; } renderLinks(lastItemRef) { function refFor(index, array) { if (!lastItemRef || index !== array.length - 1) { return null; } // Return a compatible callback ref for InstUI return ref => { lastItemRef.current = ref; }; } return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ScreenReaderContent, { id: this.describedByID }, formatMessage('Click to insert a link into the editor.')), /*#__PURE__*/React.createElement(List, { isUnstyled: true, as: "ul", margin: "0" }, this.props.collection.links.map((link, index, array) => /*#__PURE__*/React.createElement(List.Item, { key: link.href, spacing: "none", padding: "0" }, /*#__PURE__*/React.createElement(Link, { link: link, type: this.props.type, onClick: this.props.onLinkClick, describedByID: this.describedByID, elementRef: refFor(index, array), editing: this.props.editing, onEditClick: this.props.onEditClick, isSelected: this.compareURLs(this.props.selectedLink?.href, link.href) }))))); } renderEmptyIndicator() { return /*#__PURE__*/React.createElement(NoResults, { contextType: this.props.contextType, contextId: this.props.contextId, collectionType: this.props.type, isSearchResult: this.props.searchString?.length >= 3 }); } renderLoadingError() { if (this.props.collection.lastError) { return /*#__PURE__*/React.createElement("span", { className: "rcs-LinkSet-LoadFailed", role: "alert" }, formatMessage('Loading failed...')); } return null; } render() { return /*#__PURE__*/React.createElement(IncrementalLoader, this.props, ({ loader, lastItemRef }) => /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", { "data-testid": "instructure_links-LinkSet" }, this.hasLinks(this.props) && this.renderLinks(lastItemRef), this.renderLoadingError(), loader.isLoading && /*#__PURE__*/React.createElement(LoadingIndicator, { loader: loader }), !loader.isLoading && loader.hasMore && /*#__PURE__*/React.createElement(LoadMoreButton, { loader: loader }), this.isEmpty(this.props) && !this.props.suppressRenderEmpty && this.renderEmptyIndicator()), /*#__PURE__*/React.createElement(LoadingStatus, { loader: loader }))); } } LinkSet.propTypes = { type: linkType.isRequired, collection: linksShape.isRequired, onLinkClick: func.isRequired, contextType: string.isRequired, contextId: string.isRequired, fetchInitialPage: func, fetchNextPage: func, suppressRenderEmpty: bool, searchString: string, editing: bool, onEditClick: func, selectedLink: linkShape }; export default LinkSet;