From a9e39bc5ce81a9a7f177e9c7ccf25653f28a0980 Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Mon, 22 Jun 2026 17:13:45 +0530 Subject: [PATCH 1/6] OpenConceptLab/ocl_issues#2576 | Source versions tab --- src/components/repos/RepoHome.jsx | 31 +- src/components/repos/SourceVersionsTab.jsx | 652 +++++++++++++++++++++ 2 files changed, 678 insertions(+), 5 deletions(-) create mode 100644 src/components/repos/SourceVersionsTab.jsx diff --git a/src/components/repos/RepoHome.jsx b/src/components/repos/RepoHome.jsx index 502330bc..2ff4e376 100644 --- a/src/components/repos/RepoHome.jsx +++ b/src/components/repos/RepoHome.jsx @@ -26,6 +26,7 @@ import VersionForm from './VersionForm' import ReleaseVersion from './ReleaseVersion' import RepoHeader from './RepoHeader'; import CollectionVersionsTab from './CollectionVersionsTab'; +import SourceVersionsTab from './SourceVersionsTab'; import ReferenceHome from '../references/ReferenceHome' import AddReferencesDialog from '../collections/AddReferencesDialog' @@ -39,8 +40,14 @@ const RepoHome = () => { {key: 'mappings', label: t('mapping.mappings')}, ] const isCollection = params.repoType === 'collections' + const getRepoTabs = React.useCallback(() => { + if(isCollection) + return [...TABS, {key: 'references', label: t('reference.references')}, {key: 'versions', label: t('repo.versions_expansions')}] - const [tabs, setTabs] = React.useState(isCollection ? [...TABS, {key: 'references', label: t('reference.references')}, {key: 'versions', label: t('repo.versions_expansions')}] : [...TABS]) + return [...TABS, {key: 'versions', label: t('repo.versions')}] + }, [isCollection, t]) + + const [tabs, setTabs] = React.useState(getRepoTabs) const [status, setStatus] = React.useState(false) const [repo, setRepo] = React.useState(false) @@ -81,10 +88,7 @@ const RepoHome = () => { setContextRepo(_repo) fetchOwner() fetchRepoSummary() - if(isCollection) - setTabs([...TABS, {key: 'references', label: t('reference.references')}, {key: 'versions', label: t('repo.versions_expansions')}]) - else - setTabs([...TABS]) + setTabs(getRepoTabs()) if(isConceptURL || isMappingURL || isReferenceURL) setShowItem(true) @@ -330,6 +334,23 @@ const RepoHome = () => { }} /> } + { + tab === 'versions' && !isCollection && + setVersionForm({edit: true, version, expansions: []})} + onReleaseVersion={version => setReleaseTarget(version)} + onDeleteVersion={version => setDeleteTarget(version)} + onDataChange={() => { + fetchRepo() + fetchVersions() + }} + /> + } { tab === 'about' && diff --git a/src/components/repos/SourceVersionsTab.jsx b/src/components/repos/SourceVersionsTab.jsx new file mode 100644 index 00000000..5ebd690f --- /dev/null +++ b/src/components/repos/SourceVersionsTab.jsx @@ -0,0 +1,652 @@ +/* eslint-disable spellcheck/spell-checker */ +import React from 'react'; +import { useHistory } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { + Alert, + Box, + Button, + Chip, + CircularProgress, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Divider, + IconButton, + List, + ListItem, + ListItemSecondaryAction, + ListItemText, + Menu, + MenuItem, + Skeleton, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Tooltip, + Toolbar, + Typography +} from '@mui/material'; +import { + AttachFile as ExternalExportIcon, + CheckCircleOutline as ReleasedIcon, + ContentCopy as CopyIcon, + DeleteOutline as DeleteIcon, + Download as ExportIcon, + EditOutlined as EditIcon, + MoreVert as MoreVertIcon, + NewReleases as ReleaseIcon, + Newspaper as ChangelogIcon, + OpenInNew as OpenInNewIcon, + RadioButtonUnchecked as DraftIcon, + Summarize as SummaryIcon, + Upload as UploadIcon, + Visibility as VisibilityIcon +} from '@mui/icons-material'; +import get from 'lodash/get'; +import isEmpty from 'lodash/isEmpty'; +import isNumber from 'lodash/isNumber'; +import map from 'lodash/map'; + +import APIService from '../../services/APIService'; +import { + copyToClipboard, + currentUserHasAccess, + formatDateTime, + headFirst, + isLoggedIn, + toFullAPIURL +} from '../../common/utils'; +import { OperationsContext } from '../app/LayoutContext'; +import AccessIcon from '../common/AccessIcon'; +import ConceptIcon from '../concepts/ConceptIcon'; +import MappingIcon from '../mappings/MappingIcon'; + +const bodyCellSx = { + borderBottom: '1px solid', + borderColor: 'surface.nv80', + verticalAlign: 'middle' +}; + +const headerCellSx = { + backgroundColor: 'white', + borderBottom: '1px solid', + borderColor: 'surface.nv80', + color: 'surface.contrastText', + fontSize: '12px', + fontWeight: 'bold', + lineHeight: '1.2rem', + padding: '3px 16px' +}; + +const isHeadVersion = version => (version?.version || version?.id) === 'HEAD'; +const getVersionLabel = version => version?.version || version?.id || '-'; +const getVersionURL = version => isHeadVersion(version) ? `${version?.version_url || version?.url}HEAD/` : version?.version_url || version?.url; +const getPreviousVersionURL = version => version?.previous_version_url; +const getContentCount = (version, field) => get(version, `summary.${field}`); +const formatCount = value => isNumber(value) ? value.toLocaleString() : '-'; +const formatError = (value, fallback = 'Something went wrong.') => { + if(!value) return fallback; + if(typeof value === 'string') return value; + return value.detail || value.error || value.__all__ || fallback; +}; + +const downloadBlob = (response, fallbackName) => { + const contentType = get(response, 'headers.content-type') || get(response, 'data.type') || 'application/octet-stream'; + const contentDisposition = get(response, 'headers.content-disposition', ''); + const filenameMatch = contentDisposition.match(/filename\*=UTF-8''([^;]+)/i) || contentDisposition.match(/filename="?([^";]+)"?/i); + const filename = filenameMatch?.[1] ? decodeURIComponent(filenameMatch[1].replace(/"/g, '').trim()) : fallbackName; + const blob = response.data instanceof Blob ? response.data : new Blob([response.data], { type: contentType }); + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', filename); + document.body.appendChild(link); + link.click(); + link.remove(); + setTimeout(() => window.URL.revokeObjectURL(url), 1000); +}; + +const renderInlineMarkdown = text => { + const tokens = []; + const pattern = /(\*\*([^*]+)\*\*|\*([^*]+)\*|`([^`]+)`|\[([^\]]+)\]\(([^)]+)\))/g; + let lastIndex = 0; + let match; + + while((match = pattern.exec(text || ''))) { + if(match.index > lastIndex) + tokens.push(text.slice(lastIndex, match.index)); + + if(match[2]) + tokens.push({match[2]}); + else if(match[3]) + tokens.push({match[3]}); + else if(match[4]) + tokens.push({match[4]}); + else if(match[5]) + tokens.push({match[5]}); + + lastIndex = pattern.lastIndex; + } + + if(lastIndex < (text || '').length) + tokens.push(text.slice(lastIndex)); + + return tokens; +}; + +const parseMarkdownTable = lines => { + if(lines.length < 2 || !lines[0].trim().startsWith('|') || !lines[1].includes('---')) + return null; + + const parseRow = line => line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map(cell => cell.trim()); + const headers = parseRow(lines[0]); + const rows = []; + let nextIndex = 2; + + while(nextIndex < lines.length && lines[nextIndex].trim().startsWith('|')) { + rows.push(parseRow(lines[nextIndex])); + nextIndex += 1; + } + + return { headers, rows, nextIndex }; +}; + +const MarkdownContent = ({ markdown }) => { + const lines = (markdown || '').split('\n'); + const elements = []; + let index = 0; + + while(index < lines.length) { + const line = lines[index]; + const trimmed = line.trim(); + + if(!trimmed) { + index += 1; + continue; + } + + if(trimmed === '---') { + elements.push(); + index += 1; + continue; + } + + const table = parseMarkdownTable(lines.slice(index)); + if(table) { + elements.push( + + + + + {table.headers.map(header => {renderInlineMarkdown(header)})} + + + + {table.rows.map((row, rowIndex) => ( + + {row.map((cell, cellIndex) => {renderInlineMarkdown(cell)})} + + ))} + +
+
+ ); + index += table.nextIndex; + continue; + } + + const heading = trimmed.match(/^(#{1,6})\s+(.+)$/); + if(heading) { + const variant = heading[1].length <= 1 ? 'h6' : 'subtitle1'; + elements.push( + + {renderInlineMarkdown(heading[2])} + + ); + index += 1; + continue; + } + + if(trimmed.startsWith('>')) { + elements.push( + + {renderInlineMarkdown(trimmed.replace(/^>\s?/, ''))} + + ); + index += 1; + continue; + } + + elements.push( + + {renderInlineMarkdown(trimmed)} + + ); + index += 1; + } + + return {elements}; +}; + +const VersionExportDialog = ({ version, open, onClose }) => { + const { setAlert } = React.useContext(OperationsContext); + const [loading, setLoading] = React.useState(false); + const [state, setState] = React.useState(null); + const [error, setError] = React.useState(''); + const exportURL = `${getVersionURL(version)}export/`; + + const checkExport = React.useCallback(() => { + if(!open || !version) return; + setLoading(true); + setState(null); + setError(''); + APIService.new().overrideURL(exportURL).request('GET', null, null, { responseType: 'blob' }) + .then(response => { + if(response.status === 200) { + downloadBlob(response, `${version.short_code || version.id}-${getVersionLabel(version)}.zip`); + setState('downloaded'); + } else if(response.status === 204) { + setState('missing'); + } else if(response.status === 208) { + setState('processing'); + } else { + setError(formatError(response, 'Could not check export.')); + } + }) + .catch(err => setError(formatError(get(err, 'response.data') || err, 'Could not check export.'))) + .finally(() => setLoading(false)); + }, [exportURL, open, version]); + + React.useEffect(() => { + checkExport(); + }, [checkExport]); + + const queueExport = () => { + setLoading(true); + APIService.new().overrideURL(exportURL).post(null, null, null, { noRedirect: true }, true).then(response => { + const status = response?.status || response?.response?.status; + if([202, 204, 409].includes(status)) { + setState(status === 204 ? 'exists' : 'queued'); + setAlert({ severity: 'success', message: status === 204 ? 'An export already exists for this version.' : 'Export request queued.' }); + } else { + setError(formatError(response?.data || response, 'Could not queue export.')); + } + }).finally(() => setLoading(false)); + }; + + return ( + + {`Export Source Version: ${version?.short_code || version?.id} / ${getVersionLabel(version)}`} + + {loading && }>Checking export status...} + {!loading && state === 'downloaded' && Downloaded cached export.} + {!loading && state === 'processing' && A cached export is being generated. Check again later.} + {!loading && state === 'queued' && Export request queued. Check again later.} + {!loading && state === 'exists' && An export already exists. Try downloading again.} + {!loading && state === 'missing' && ( + + There is no cached export for this source version. + + + )} + {Boolean(error) && {error}} + + + + + + ); +}; + +const ExternalExportsDialog = ({ version, open, onClose, canEdit, onChange }) => { + const { setAlert } = React.useContext(OperationsContext); + const [exports, setExports] = React.useState(get(version, 'external_exports', [])); + const [name, setName] = React.useState(''); + const [description, setDescription] = React.useState(''); + const [file, setFile] = React.useState(null); + const [busyKey, setBusyKey] = React.useState(''); + const canUpload = Boolean(canEdit && !isHeadVersion(version)); + + React.useEffect(() => { + setExports(get(version, 'external_exports', [])); + }, [version]); + + const updateExports = nextExports => { + setExports(nextExports); + if(onChange) onChange({ ...version, external_exports: nextExports }); + }; + + const download = externalExport => { + const url = externalExport.url || `${version.version_url}export/${externalExport.key}/`; + setBusyKey(externalExport.key); + APIService.new().overrideURL(url).request('GET', null, null, { responseType: 'blob' }) + .then(response => { + if(response.status === 200) downloadBlob(response, externalExport.file_path?.split('/').pop() || externalExport.key); + else setAlert({ severity: 'error', message: 'Could not download external export.' }); + }) + .catch(() => setAlert({ severity: 'error', message: 'Could not download external export.' })) + .finally(() => setBusyKey('')); + }; + + const upload = () => { + const key = (name || '').replace(/\s/g, ''); + if(!key || !file) { + setAlert({ severity: 'error', message: 'External export name and file are required.' }); + return; + } + const data = new FormData(); + data.append('file', file); + if(description) data.append('description', description); + setBusyKey('upload'); + APIService.new().overrideURL(`${version.version_url}export/${key}/`).request('POST', data, null, { headers: { 'Content-Type': 'multipart/form-data' } }) + .then(response => { + updateExports([...exports, response.data]); + setName(''); + setDescription(''); + setFile(null); + setAlert({ severity: 'success', message: 'External export uploaded.' }); + }) + .catch(error => setAlert({ severity: 'error', message: formatError(get(error, 'response.data'), 'Could not upload external export.') })) + .finally(() => setBusyKey('')); + }; + + return ( + + {`External Exports: ${version?.short_code || version?.id} / ${getVersionLabel(version)}`} + + { + isEmpty(exports) ? + No external exports have been uploaded for this version. : + + {map(exports, externalExport => ( + + + + {busyKey === externalExport.key ? : ( + download(externalExport)}> + + + )} + + + ))} + + } + { + canUpload && ( + + + Upload External Export + setName(event.target.value)} sx={{ mb: 1 }} /> + setDescription(event.target.value)} sx={{ mb: 1 }} /> + + + {file ? file.name : 'sql, zip, pdf, csv'} + + + ) + } + + + + {canUpload && } + + + ); +}; + +const ChangelogDialog = ({ version, open, onClose }) => { + const [loading, setLoading] = React.useState(false); + const [markdown, setMarkdown] = React.useState(''); + const [error, setError] = React.useState(''); + const [showLongMessage, setShowLongMessage] = React.useState(false); + const timerRef = React.useRef(null); + const previousVersionURL = getPreviousVersionURL(version); + + React.useEffect(() => { + if(!open || !version || !previousVersionURL) return undefined; + setLoading(true); + setMarkdown(''); + setError(''); + setShowLongMessage(false); + timerRef.current = setTimeout(() => setShowLongMessage(true), 10000); + APIService.new().overrideURL('/sources/$changelog/').post( + { version1: previousVersionURL, version2: version.version_url || version.url, verbosity: 4 }, + null, + null, + { inline: true, output: 'markdown', verbosity: 4 } + ).then(response => { + const nextMarkdown = get(response, 'data.markdown') || get(response, 'markdown'); + if(nextMarkdown) setMarkdown(nextMarkdown); + else setError(formatError(get(response, 'detail') || get(response, 'error'), 'Could not load changelog.')); + }).catch(() => setError('Could not load changelog.')).finally(() => { + if(timerRef.current) clearTimeout(timerRef.current); + setLoading(false); + }); + return () => { + if(timerRef.current) clearTimeout(timerRef.current); + }; + }, [open, previousVersionURL, version]); + + return ( + + {`Changelog: ${previousVersionURL?.split('/').filter(Boolean).pop()} -> ${getVersionLabel(version)}`} + + {loading && ( + + + {showLongMessage && Large sources can take a while to process.} + + )} + {!loading && Boolean(error) && {error}} + {!loading && Boolean(markdown) && ( + + )} + + + + + + ); +}; + +const SourceVersionsTab = ({ + repo, + versions, + count, + loading, + onVersionChange, + onEditVersion, + onReleaseVersion, + onDeleteVersion, + onDataChange +}) => { + const { t } = useTranslation(); + const history = useHistory(); + const { setAlert } = React.useContext(OperationsContext); + const [menuState, setMenuState] = React.useState({ anchorEl: null, version: null }); + const [exportVersion, setExportVersion] = React.useState(null); + const [externalExportsVersion, setExternalExportsVersion] = React.useState(null); + const [changelogVersion, setChangelogVersion] = React.useState(null); + const sortedVersions = React.useMemo(() => headFirst(Array.isArray(versions) ? versions : []), [versions]); + const hasAccess = currentUserHasAccess(); + + const closeMenu = () => setMenuState({ anchorEl: null, version: null }); + const withClose = callback => { + const version = menuState.version; + closeMenu(); + callback(version); + }; + const compareVersion = version => { + const previousVersionURL = getPreviousVersionURL(version); + if(previousVersionURL) + history.push(`${repo.url}compare-versions?version1=${previousVersionURL}&version2=${version.version_url || version.url}`); + }; + const copyVersionURL = version => { + copyToClipboard(toFullAPIURL(version.version_url || version.url)); + setAlert({ severity: 'success', message: 'Copied version URL.' }); + }; + const computeSummary = version => { + APIService.new().overrideURL(version.version_url).appendToUrl('summary/').put().then(response => { + if(response.detail || response.error) + setAlert({ severity: 'error', message: formatError(response.detail || response.error) }); + else if(response.status === 202) + setAlert({ severity: 'success', message: 'Summary request queued.' }); + else + setAlert({ severity: 'warning', message: 'Summary request submitted.' }); + }); + }; + const onExternalExportsChange = updatedVersion => { + if(onDataChange) onDataChange(updatedVersion); + }; + const versionsCount = count || sortedVersions.length; + const countLabel = `${versionsCount.toLocaleString()} source ${versionsCount === 1 ? 'version' : 'versions'}`; + + return ( + + + + {countLabel} + + + + + + + {t('common.id')} + {t('common.content_summary')} + {t('repo.visibility')} + {t('common.status')} + {t('common.created')} + {t('common.actions')} + + + + {loading && [1, 2, 3].map(index => ( + + + + ))} + {!loading && sortedVersions.map(version => { + const isHEAD = isHeadVersion(version); + const isPublic = ['view', 'edit'].includes((version.public_access || '').toLowerCase()); + return ( + + + + {version?.match_algorithms?.includes('llm') && } + {version.description && ( + + {version.description} + + )} + + + + + + {formatCount(getContentCount(version, 'active_concepts'))} + + + + {formatCount(getContentCount(version, 'active_mappings'))} + + + + + + + {isPublic ? t('common.public') : t('common.private')} + + + + {isHEAD && ( + + + {t('common.draft')} + + )} + {!isHEAD && version.released && ( + + + {t('common.released')} + + )} + {!isHEAD && !version.released && ( + Unreleased + )} + {version.retired && {t('common.retired')}} + + + {version.created_on ? formatDateTime(version.created_on) : '-'} + {version.created_by || ''} + + + + setMenuState({ anchorEl: event.currentTarget, version })}> + + + + + + ); + })} + {!loading && sortedVersions.length === 0 && ( + + + No versions found. + + + )} + +
+
+ + withClose(version => onVersionChange(version))}>Explore Version + withClose(version => copyVersionURL(version))}>Copy URL + withClose(version => setExportVersion(version))} disabled={!isLoggedIn()}>Export Version + withClose(version => setExternalExportsVersion(version))} disabled={!isLoggedIn() || isHeadVersion(menuState.version)}>External Exports + withClose(version => setChangelogVersion(version))} disabled={!getPreviousVersionURL(menuState.version)}>Changelog + withClose(compareVersion)} disabled={!getPreviousVersionURL(menuState.version)}>Compare with Previous + {hasAccess && } + {hasAccess && withClose(onEditVersion)} disabled={isHeadVersion(menuState.version)}>{t('common.edit')}} + {hasAccess && withClose(onReleaseVersion)} disabled={isHeadVersion(menuState.version)}>{menuState.version?.released ? 'Unrelease Version' : 'Release Version'}} + {hasAccess && withClose(computeSummary)}>Re-compute Summary} + {hasAccess && withClose(onDeleteVersion)} disabled={menuState.version?.retired}>Delete} + + {Boolean(exportVersion) && setExportVersion(null)} />} + {Boolean(externalExportsVersion) && setExternalExportsVersion(null)} onChange={onExternalExportsChange} />} + {Boolean(changelogVersion) && setChangelogVersion(null)} />} +
+ ); +}; + +export default SourceVersionsTab; From 6bdceeb6272773900dcbd21b5795a265d97ac0e5 Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Mon, 22 Jun 2026 17:21:29 +0530 Subject: [PATCH 2/6] OpenConceptLab/ocl_issues#2576 | extracting common component | using translations --- src/components/common/MarkdownContent.jsx | 136 ++++++++++++++++++ src/components/repos/SourceVersionsTab.jsx | 159 ++++----------------- src/i18n/locales/en/translations.json | 10 ++ src/i18n/locales/zh/translations.json | 14 ++ 4 files changed, 184 insertions(+), 135 deletions(-) create mode 100644 src/components/common/MarkdownContent.jsx diff --git a/src/components/common/MarkdownContent.jsx b/src/components/common/MarkdownContent.jsx new file mode 100644 index 00000000..f2190eb0 --- /dev/null +++ b/src/components/common/MarkdownContent.jsx @@ -0,0 +1,136 @@ +import React from 'react'; +import { + Box, + Divider, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography +} from '@mui/material'; + +const renderInlineMarkdown = text => { + const tokens = []; + const pattern = /(\*\*([^*]+)\*\*|\*([^*]+)\*|`([^`]+)`|\[([^\]]+)\]\(([^)]+)\))/g; + let lastIndex = 0; + let match; + + while((match = pattern.exec(text || ''))) { + if(match.index > lastIndex) + tokens.push(text.slice(lastIndex, match.index)); + + if(match[2]) + tokens.push({match[2]}); + else if(match[3]) + tokens.push({match[3]}); + else if(match[4]) + tokens.push({match[4]}); + else if(match[5]) + tokens.push({match[5]}); + + lastIndex = pattern.lastIndex; + } + + if(lastIndex < (text || '').length) + tokens.push(text.slice(lastIndex)); + + return tokens; +}; + +const parseMarkdownTable = lines => { + if(lines.length < 2 || !lines[0].trim().startsWith('|') || !lines[1].includes('---')) + return null; + + const parseRow = line => line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map(cell => cell.trim()); + const headers = parseRow(lines[0]); + const rows = []; + let nextIndex = 2; + + while(nextIndex < lines.length && lines[nextIndex].trim().startsWith('|')) { + rows.push(parseRow(lines[nextIndex])); + nextIndex += 1; + } + + return { headers, rows, nextIndex }; +}; + +const MarkdownContent = ({ markdown }) => { + const lines = (markdown || '').split('\n'); + const elements = []; + let index = 0; + + while(index < lines.length) { + const line = lines[index]; + const trimmed = line.trim(); + + if(!trimmed) { + index += 1; + continue; + } + + if(trimmed === '---') { + elements.push(); + index += 1; + continue; + } + + const table = parseMarkdownTable(lines.slice(index)); + if(table) { + elements.push( + + + + + {table.headers.map(header => {renderInlineMarkdown(header)})} + + + + {table.rows.map((row, rowIndex) => ( + + {row.map((cell, cellIndex) => {renderInlineMarkdown(cell)})} + + ))} + +
+
+ ); + index += table.nextIndex; + continue; + } + + const heading = trimmed.match(/^(#{1,6})\s+(.+)$/); + if(heading) { + const variant = heading[1].length <= 1 ? 'h6' : 'subtitle1'; + elements.push( + + {renderInlineMarkdown(heading[2])} + + ); + index += 1; + continue; + } + + if(trimmed.startsWith('>')) { + elements.push( + + {renderInlineMarkdown(trimmed.replace(/^>\s?/, ''))} + + ); + index += 1; + continue; + } + + elements.push( + + {renderInlineMarkdown(trimmed)} + + ); + index += 1; + } + + return {elements}; +}; + +export default MarkdownContent; diff --git a/src/components/repos/SourceVersionsTab.jsx b/src/components/repos/SourceVersionsTab.jsx index 5ebd690f..28261555 100644 --- a/src/components/repos/SourceVersionsTab.jsx +++ b/src/components/repos/SourceVersionsTab.jsx @@ -65,6 +65,7 @@ import { } from '../../common/utils'; import { OperationsContext } from '../app/LayoutContext'; import AccessIcon from '../common/AccessIcon'; +import MarkdownContent from '../common/MarkdownContent'; import ConceptIcon from '../concepts/ConceptIcon'; import MappingIcon from '../mappings/MappingIcon'; @@ -113,128 +114,6 @@ const downloadBlob = (response, fallbackName) => { setTimeout(() => window.URL.revokeObjectURL(url), 1000); }; -const renderInlineMarkdown = text => { - const tokens = []; - const pattern = /(\*\*([^*]+)\*\*|\*([^*]+)\*|`([^`]+)`|\[([^\]]+)\]\(([^)]+)\))/g; - let lastIndex = 0; - let match; - - while((match = pattern.exec(text || ''))) { - if(match.index > lastIndex) - tokens.push(text.slice(lastIndex, match.index)); - - if(match[2]) - tokens.push({match[2]}); - else if(match[3]) - tokens.push({match[3]}); - else if(match[4]) - tokens.push({match[4]}); - else if(match[5]) - tokens.push({match[5]}); - - lastIndex = pattern.lastIndex; - } - - if(lastIndex < (text || '').length) - tokens.push(text.slice(lastIndex)); - - return tokens; -}; - -const parseMarkdownTable = lines => { - if(lines.length < 2 || !lines[0].trim().startsWith('|') || !lines[1].includes('---')) - return null; - - const parseRow = line => line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map(cell => cell.trim()); - const headers = parseRow(lines[0]); - const rows = []; - let nextIndex = 2; - - while(nextIndex < lines.length && lines[nextIndex].trim().startsWith('|')) { - rows.push(parseRow(lines[nextIndex])); - nextIndex += 1; - } - - return { headers, rows, nextIndex }; -}; - -const MarkdownContent = ({ markdown }) => { - const lines = (markdown || '').split('\n'); - const elements = []; - let index = 0; - - while(index < lines.length) { - const line = lines[index]; - const trimmed = line.trim(); - - if(!trimmed) { - index += 1; - continue; - } - - if(trimmed === '---') { - elements.push(); - index += 1; - continue; - } - - const table = parseMarkdownTable(lines.slice(index)); - if(table) { - elements.push( - - - - - {table.headers.map(header => {renderInlineMarkdown(header)})} - - - - {table.rows.map((row, rowIndex) => ( - - {row.map((cell, cellIndex) => {renderInlineMarkdown(cell)})} - - ))} - -
-
- ); - index += table.nextIndex; - continue; - } - - const heading = trimmed.match(/^(#{1,6})\s+(.+)$/); - if(heading) { - const variant = heading[1].length <= 1 ? 'h6' : 'subtitle1'; - elements.push( - - {renderInlineMarkdown(heading[2])} - - ); - index += 1; - continue; - } - - if(trimmed.startsWith('>')) { - elements.push( - - {renderInlineMarkdown(trimmed.replace(/^>\s?/, ''))} - - ); - index += 1; - continue; - } - - elements.push( - - {renderInlineMarkdown(trimmed)} - - ); - index += 1; - } - - return {elements}; -}; - const VersionExportDialog = ({ version, open, onClose }) => { const { setAlert } = React.useContext(OperationsContext); const [loading, setLoading] = React.useState(false); @@ -501,9 +380,9 @@ const SourceVersionsTab = ({ if(response.detail || response.error) setAlert({ severity: 'error', message: formatError(response.detail || response.error) }); else if(response.status === 202) - setAlert({ severity: 'success', message: 'Summary request queued.' }); + setAlert({ severity: 'success', message: t('repo.summary_request_queued') }); else - setAlert({ severity: 'warning', message: 'Summary request submitted.' }); + setAlert({ severity: 'warning', message: t('repo.summary_request_submitted') }); }); }; const onExternalExportsChange = updatedVersion => { @@ -601,7 +480,7 @@ const SourceVersionsTab = ({ )} {!isHEAD && !version.released && ( - Unreleased + {t('repo.unreleased')} )} {version.retired && {t('common.retired')}} @@ -622,7 +501,7 @@ const SourceVersionsTab = ({ {!loading && sortedVersions.length === 0 && ( - No versions found. + {t('repo.no_versions_found')} )} @@ -630,17 +509,27 @@ const SourceVersionsTab = ({ - withClose(version => onVersionChange(version))}>Explore Version - withClose(version => copyVersionURL(version))}>Copy URL - withClose(version => setExportVersion(version))} disabled={!isLoggedIn()}>Export Version - withClose(version => setExternalExportsVersion(version))} disabled={!isLoggedIn() || isHeadVersion(menuState.version)}>External Exports - withClose(version => setChangelogVersion(version))} disabled={!getPreviousVersionURL(menuState.version)}>Changelog - withClose(compareVersion)} disabled={!getPreviousVersionURL(menuState.version)}>Compare with Previous + withClose(version => onVersionChange(version))}>{t('repo.explore_version')} + withClose(version => copyVersionURL(version))}>{t('common.copy_api_url')} + withClose(version => setExportVersion(version))} disabled={!isLoggedIn()}>{t('repo.export_version')} + withClose(version => setExternalExportsVersion(version))} disabled={!isLoggedIn() || isHeadVersion(menuState.version)}>{t('repo.external_exports')} + withClose(version => setChangelogVersion(version))} disabled={!getPreviousVersionURL(menuState.version)}>{t('repo.changelog')} + withClose(compareVersion)} disabled={!getPreviousVersionURL(menuState.version)}>{t('repo.compare_with_previous')} {hasAccess && } {hasAccess && withClose(onEditVersion)} disabled={isHeadVersion(menuState.version)}>{t('common.edit')}} - {hasAccess && withClose(onReleaseVersion)} disabled={isHeadVersion(menuState.version)}>{menuState.version?.released ? 'Unrelease Version' : 'Release Version'}} - {hasAccess && withClose(computeSummary)}>Re-compute Summary} - {hasAccess && withClose(onDeleteVersion)} disabled={menuState.version?.retired}>Delete} + {hasAccess && withClose(onReleaseVersion)} disabled={isHeadVersion(menuState.version)}>{menuState.version?.released ? t('repo.unrelease_version') : t('repo.release_version')}} + {hasAccess && withClose(computeSummary)}>{t('repo.recompute_summary')}} + {hasAccess && } + {hasAccess && ( + withClose(onDeleteVersion)} + disabled={menuState.version?.retired} + sx={{ color: 'error.main', '& .MuiSvgIcon-root': { color: 'error.main' } }} + > + + {t('repo.delete_repo_version')} + + )} {Boolean(exportVersion) && setExportVersion(null)} />} {Boolean(externalExportsVersion) && setExternalExportsVersion(null)} onChange={onExternalExportsChange} />} diff --git a/src/i18n/locales/en/translations.json b/src/i18n/locales/en/translations.json index 3ebe610f..d020dbfe 100644 --- a/src/i18n/locales/en/translations.json +++ b/src/i18n/locales/en/translations.json @@ -445,6 +445,16 @@ "error_version_update": "Could not update version", "versions_expansions": "Versions + Expansions", "versions": "Versions", + "unreleased": "Unreleased", + "explore_version": "Explore Version", + "export_version": "Export Version", + "external_exports": "External Exports", + "changelog": "Changelog", + "compare_with_previous": "Compare with Previous", + "recompute_summary": "Re-compute Summary", + "summary_request_queued": "Summary request queued.", + "summary_request_submitted": "Summary request submitted.", + "no_versions_found": "No versions found.", "expansions": "Expansions", "versions_expansions_subtitle": "HEAD, released versions, and their expansions", "autoexpand": "Auto-expand", diff --git a/src/i18n/locales/zh/translations.json b/src/i18n/locales/zh/translations.json index 14e29e26..612e241b 100644 --- a/src/i18n/locales/zh/translations.json +++ b/src/i18n/locales/zh/translations.json @@ -217,6 +217,8 @@ "add_concept": "添加概念", "source": "来源", "release_status": "发行版本状态", + "release_version": "Release Version", + "unrelease_version": "Un-Release Version", "compare_versions": "比较版本", "compare": "比较", "visibility": "可见性", @@ -250,6 +252,18 @@ "summary_concepts_references": "{{count}} 条概念引用关系", "summary_mappings_references": "{{count}} 条映射关系引用关系", "refresh_summary": "刷新摘要", + "versions": "版本", + "unreleased": "Unreleased", + "explore_version": "Explore Version", + "export_version": "Export Version", + "external_exports": "External Exports", + "changelog": "Changelog", + "compare_with_previous": "Compare with Previous", + "recompute_summary": "Re-compute Summary", + "summary_request_queued": "Summary request queued.", + "summary_request_submitted": "Summary request submitted.", + "no_versions_found": "No versions found.", + "delete_repo_version": "Delete Repo Version", "repo_version_summary": "存储库版本摘要", "repo_summary_is_calculating": "正在计算处理存储库摘要数据,请请稍后再回来查看。", "no_overview": "所有者尚未为该存储库创建概述页面" From 00c094f0a25b618fd2727b061cfd8317081b9d52 Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Mon, 22 Jun 2026 17:24:50 +0530 Subject: [PATCH 3/6] OpenConceptLab/ocl_issues#2576 | HEAD delete not possible --- src/components/repos/SourceVersionsTab.jsx | 25 ++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/components/repos/SourceVersionsTab.jsx b/src/components/repos/SourceVersionsTab.jsx index 28261555..ff3df76f 100644 --- a/src/components/repos/SourceVersionsTab.jsx +++ b/src/components/repos/SourceVersionsTab.jsx @@ -519,17 +519,20 @@ const SourceVersionsTab = ({ {hasAccess && withClose(onEditVersion)} disabled={isHeadVersion(menuState.version)}>{t('common.edit')}} {hasAccess && withClose(onReleaseVersion)} disabled={isHeadVersion(menuState.version)}>{menuState.version?.released ? t('repo.unrelease_version') : t('repo.release_version')}} {hasAccess && withClose(computeSummary)}>{t('repo.recompute_summary')}} - {hasAccess && } - {hasAccess && ( - withClose(onDeleteVersion)} - disabled={menuState.version?.retired} - sx={{ color: 'error.main', '& .MuiSvgIcon-root': { color: 'error.main' } }} - > - - {t('repo.delete_repo_version')} - - )} + { + hasAccess && !isHeadVersion(menuState.version) && + <> + + withClose(onDeleteVersion)} + disabled={menuState.version?.retired} + sx={{ color: 'error.main', '& .MuiSvgIcon-root': { color: 'error.main' } }} + > + + {t('repo.delete_repo_version')} + + + } {Boolean(exportVersion) && setExportVersion(null)} />} {Boolean(externalExportsVersion) && setExternalExportsVersion(null)} onChange={onExternalExportsChange} />} From 04ca4ceb00274f371c448ff7290fd653f31f52e9 Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Tue, 23 Jun 2026 08:17:19 +0530 Subject: [PATCH 4/6] OpenConceptLab/ocl_issues#2576 | using more translations | updated external export --- src/components/repos/SourceVersionsTab.jsx | 85 ++++++++++++---------- src/i18n/locales/en/translations.json | 28 +++++++ src/i18n/locales/es/translations.json | 57 ++++++++++++++- src/i18n/locales/zh/translations.json | 32 ++++++++ 4 files changed, 161 insertions(+), 41 deletions(-) diff --git a/src/components/repos/SourceVersionsTab.jsx b/src/components/repos/SourceVersionsTab.jsx index ff3df76f..17934770 100644 --- a/src/components/repos/SourceVersionsTab.jsx +++ b/src/components/repos/SourceVersionsTab.jsx @@ -92,7 +92,7 @@ const getVersionURL = version => isHeadVersion(version) ? `${version?.version_ur const getPreviousVersionURL = version => version?.previous_version_url; const getContentCount = (version, field) => get(version, `summary.${field}`); const formatCount = value => isNumber(value) ? value.toLocaleString() : '-'; -const formatError = (value, fallback = 'Something went wrong.') => { +const formatError = (value, fallback) => { if(!value) return fallback; if(typeof value === 'string') return value; return value.detail || value.error || value.__all__ || fallback; @@ -115,6 +115,7 @@ const downloadBlob = (response, fallbackName) => { }; const VersionExportDialog = ({ version, open, onClose }) => { + const { t } = useTranslation(); const { setAlert } = React.useContext(OperationsContext); const [loading, setLoading] = React.useState(false); const [state, setState] = React.useState(null); @@ -136,12 +137,12 @@ const VersionExportDialog = ({ version, open, onClose }) => { } else if(response.status === 208) { setState('processing'); } else { - setError(formatError(response, 'Could not check export.')); + setError(formatError(response, t('repo.could_not_check_export'))); } }) - .catch(err => setError(formatError(get(err, 'response.data') || err, 'Could not check export.'))) + .catch(err => setError(formatError(get(err, 'response.data') || err, t('repo.could_not_check_export')))) .finally(() => setLoading(false)); - }, [exportURL, open, version]); + }, [exportURL, open, t, version]); React.useEffect(() => { checkExport(); @@ -153,38 +154,39 @@ const VersionExportDialog = ({ version, open, onClose }) => { const status = response?.status || response?.response?.status; if([202, 204, 409].includes(status)) { setState(status === 204 ? 'exists' : 'queued'); - setAlert({ severity: 'success', message: status === 204 ? 'An export already exists for this version.' : 'Export request queued.' }); + setAlert({ severity: 'success', message: status === 204 ? t('repo.export_already_exists') : t('repo.export_request_queued') }); } else { - setError(formatError(response?.data || response, 'Could not queue export.')); + setError(formatError(response?.data || response, t('repo.could_not_queue_export'))); } }).finally(() => setLoading(false)); }; return ( - {`Export Source Version: ${version?.short_code || version?.id} / ${getVersionLabel(version)}`} + {t('repo.export_source_version_title', { repo: version?.short_code || version?.id, version: getVersionLabel(version) })} - {loading && }>Checking export status...} - {!loading && state === 'downloaded' && Downloaded cached export.} - {!loading && state === 'processing' && A cached export is being generated. Check again later.} - {!loading && state === 'queued' && Export request queued. Check again later.} - {!loading && state === 'exists' && An export already exists. Try downloading again.} + {loading && }>{t('repo.checking_export_status')}} + {!loading && state === 'downloaded' && {t('repo.downloaded_cached_export')}} + {!loading && state === 'processing' && {t('repo.cached_export_generating')}} + {!loading && state === 'queued' && {t('repo.export_request_queued_check_later')}} + {!loading && state === 'exists' && {t('repo.export_already_exists_try_again')}} {!loading && state === 'missing' && ( - There is no cached export for this source version. - + {t('repo.no_cached_export')} + )} {Boolean(error) && {error}} - + ); }; const ExternalExportsDialog = ({ version, open, onClose, canEdit, onChange }) => { + const { t } = useTranslation(); const { setAlert } = React.useContext(OperationsContext); const [exports, setExports] = React.useState(get(version, 'external_exports', [])); const [name, setName] = React.useState(''); @@ -207,17 +209,17 @@ const ExternalExportsDialog = ({ version, open, onClose, canEdit, onChange }) => setBusyKey(externalExport.key); APIService.new().overrideURL(url).request('GET', null, null, { responseType: 'blob' }) .then(response => { - if(response.status === 200) downloadBlob(response, externalExport.file_path?.split('/').pop() || externalExport.key); - else setAlert({ severity: 'error', message: 'Could not download external export.' }); + if(response.status === 200) downloadBlob(response, externalExport.filename || externalExport.key); + else setAlert({ severity: 'error', message: t('repo.could_not_download_external_export') }); }) - .catch(() => setAlert({ severity: 'error', message: 'Could not download external export.' })) + .catch(() => setAlert({ severity: 'error', message: t('repo.could_not_download_external_export') })) .finally(() => setBusyKey('')); }; const upload = () => { const key = (name || '').replace(/\s/g, ''); if(!key || !file) { - setAlert({ severity: 'error', message: 'External export name and file are required.' }); + setAlert({ severity: 'error', message: t('repo.external_export_required') }); return; } const data = new FormData(); @@ -230,23 +232,23 @@ const ExternalExportsDialog = ({ version, open, onClose, canEdit, onChange }) => setName(''); setDescription(''); setFile(null); - setAlert({ severity: 'success', message: 'External export uploaded.' }); + setAlert({ severity: 'success', message: t('repo.external_export_uploaded') }); }) - .catch(error => setAlert({ severity: 'error', message: formatError(get(error, 'response.data'), 'Could not upload external export.') })) + .catch(error => setAlert({ severity: 'error', message: formatError(get(error, 'response.data'), t('repo.could_not_upload_external_export')) })) .finally(() => setBusyKey('')); }; return ( - {`External Exports: ${version?.short_code || version?.id} / ${getVersionLabel(version)}`} + {t('repo.external_exports_title', { repo: version?.short_code || version?.id, version: getVersionLabel(version) })} { isEmpty(exports) ? - No external exports have been uploaded for this version. : + {t('repo.no_external_exports')} : {map(exports, externalExport => ( - + {busyKey === externalExport.key ? : ( download(externalExport)}> @@ -262,29 +264,30 @@ const ExternalExportsDialog = ({ version, open, onClose, canEdit, onChange }) => canUpload && ( - Upload External Export - setName(event.target.value)} sx={{ mb: 1 }} /> - setDescription(event.target.value)} sx={{ mb: 1 }} /> + {t('repo.upload_external_export')} + setName(event.target.value)} sx={{ mb: 1 }} /> + setDescription(event.target.value)} sx={{ mb: 1 }} /> - {file ? file.name : 'sql, zip, pdf, csv'} + {file ? file.name : t('repo.external_export_file_types')} ) } - - {canUpload && } + + {canUpload && } ); }; const ChangelogDialog = ({ version, open, onClose }) => { + const { t } = useTranslation(); const [loading, setLoading] = React.useState(false); const [markdown, setMarkdown] = React.useState(''); const [error, setError] = React.useState(''); @@ -307,15 +310,15 @@ const ChangelogDialog = ({ version, open, onClose }) => { ).then(response => { const nextMarkdown = get(response, 'data.markdown') || get(response, 'markdown'); if(nextMarkdown) setMarkdown(nextMarkdown); - else setError(formatError(get(response, 'detail') || get(response, 'error'), 'Could not load changelog.')); - }).catch(() => setError('Could not load changelog.')).finally(() => { + else setError(formatError(get(response, 'detail') || get(response, 'error'), t('repo.could_not_load_changelog'))); + }).catch(() => setError(t('repo.could_not_load_changelog'))).finally(() => { if(timerRef.current) clearTimeout(timerRef.current); setLoading(false); }); return () => { if(timerRef.current) clearTimeout(timerRef.current); }; - }, [open, previousVersionURL, version]); + }, [open, previousVersionURL, t, version]); return ( @@ -324,7 +327,7 @@ const ChangelogDialog = ({ version, open, onClose }) => { {loading && ( - {showLongMessage && Large sources can take a while to process.} + {showLongMessage && {t('repo.large_sources_take_a_while')}} )} {!loading && Boolean(error) && {error}} @@ -333,7 +336,7 @@ const ChangelogDialog = ({ version, open, onClose }) => { )} - + ); @@ -373,12 +376,12 @@ const SourceVersionsTab = ({ }; const copyVersionURL = version => { copyToClipboard(toFullAPIURL(version.version_url || version.url)); - setAlert({ severity: 'success', message: 'Copied version URL.' }); + setAlert({ severity: 'success', message: t('repo.copied_version_url') }); }; const computeSummary = version => { APIService.new().overrideURL(version.version_url).appendToUrl('summary/').put().then(response => { if(response.detail || response.error) - setAlert({ severity: 'error', message: formatError(response.detail || response.error) }); + setAlert({ severity: 'error', message: formatError(response.detail || response.error, t('common.generic_error')) }); else if(response.status === 202) setAlert({ severity: 'success', message: t('repo.summary_request_queued') }); else @@ -389,7 +392,9 @@ const SourceVersionsTab = ({ if(onDataChange) onDataChange(updatedVersion); }; const versionsCount = count || sortedVersions.length; - const countLabel = `${versionsCount.toLocaleString()} source ${versionsCount === 1 ? 'version' : 'versions'}`; + const countLabel = versionsCount === 1 + ? t('repo.source_version_count', { count: versionsCount.toLocaleString() }) + : t('repo.source_versions_count', { count: versionsCount.toLocaleString() }); return ( @@ -441,7 +446,7 @@ const SourceVersionsTab = ({ > {getVersionLabel(version)} - {version?.match_algorithms?.includes('llm') && } + {version?.match_algorithms?.includes('llm') && } {version.description && ( {version.description} diff --git a/src/i18n/locales/en/translations.json b/src/i18n/locales/en/translations.json index d020dbfe..82f7297e 100644 --- a/src/i18n/locales/en/translations.json +++ b/src/i18n/locales/en/translations.json @@ -455,6 +455,34 @@ "summary_request_queued": "Summary request queued.", "summary_request_submitted": "Summary request submitted.", "no_versions_found": "No versions found.", + "source_version_count": "{{count}} source version", + "source_versions_count": "{{count}} source versions", + "mapper": "Mapper", + "export_source_version_title": "Export Source Version: {{repo}} / {{version}}", + "could_not_check_export": "Could not check export.", + "could_not_queue_export": "Could not queue export.", + "export_already_exists": "An export already exists for this version.", + "export_request_queued": "Export request queued.", + "checking_export_status": "Checking export status...", + "downloaded_cached_export": "Downloaded cached export.", + "cached_export_generating": "A cached export is being generated. Check again later.", + "export_request_queued_check_later": "Export request queued. Check again later.", + "export_already_exists_try_again": "An export already exists. Try downloading again.", + "no_cached_export": "There is no cached export for this source version.", + "queue_export": "Queue Export", + "could_not_download_external_export": "Could not download external export.", + "external_export_required": "External export name and file are required.", + "external_export_uploaded": "External export uploaded.", + "could_not_upload_external_export": "Could not upload external export.", + "external_exports_title": "External Exports: {{repo}} / {{version}}", + "no_external_exports": "No external exports have been uploaded for this version.", + "upload_external_export": "Upload External Export", + "choose_file": "Choose File", + "external_export_file_types": "sql, zip, pdf, csv", + "uploading": "Uploading...", + "could_not_load_changelog": "Could not load changelog.", + "large_sources_take_a_while": "Large sources can take a while to process.", + "copied_version_url": "Copied version URL.", "expansions": "Expansions", "versions_expansions_subtitle": "HEAD, released versions, and their expansions", "autoexpand": "Auto-expand", diff --git a/src/i18n/locales/es/translations.json b/src/i18n/locales/es/translations.json index 3ff4c8a6..a71fcb21 100644 --- a/src/i18n/locales/es/translations.json +++ b/src/i18n/locales/es/translations.json @@ -17,6 +17,8 @@ "retired": "Retirado", "id": "ID", "name": "Nombre", + "description": "Descripción", + "created": "Creado", "created_on": "Creado en", "version": "Versión", "versions": "Versiones", @@ -28,6 +30,7 @@ "eligible": "elegibles", "skipped": "omitidos", "status": "Estado", + "content_summary": "Resumen de contenido", "metadata": "Metadatos", "copied_to_clipboard": "Copiado al portapapeles", "navigate": "Navegar", @@ -37,6 +40,14 @@ "none": "Ninguno", "results": "Resultados", "custom": "Personalizado", + "upload": "Subir", + "public": "Público", + "private": "Privado", + "close": "Cerrar", + "draft": "Borrador", + "released": "Publicado", + "edit": "Editar", + "copy_api_url": "Copiar URL de API", "load_more": "Cargar más", "generate_with_ai": "Generar con IA", "generic_error": "Algo salió mal." @@ -125,7 +136,51 @@ "repos": "Repositorios", "repo": "Repositorio", "repo_type": "Tipo de Repositorio", - "manage": "Gestionar Repositorio" + "manage": "Gestionar Repositorio", + "visibility": "Visibilidad", + "release_version": "Publicar versión", + "unrelease_version": "Despublicar versión", + "versions": "Versiones", + "unreleased": "Sin publicar", + "explore_version": "Explorar versión", + "export_version": "Exportar versión", + "external_exports": "Exportaciones externas", + "changelog": "Registro de cambios", + "compare_with_previous": "Comparar con la anterior", + "recompute_summary": "Recalcular resumen", + "summary_request_queued": "Solicitud de resumen en cola.", + "summary_request_submitted": "Solicitud de resumen enviada.", + "no_versions_found": "No se encontraron versiones.", + "delete_repo_version": "Eliminar versión del repositorio", + "source_version_count": "{{count}} versión de fuente", + "source_versions_count": "{{count}} versiones de fuente", + "mapper": "Mapper", + "export_source_version_title": "Exportar versión de fuente: {{repo}} / {{version}}", + "could_not_check_export": "No se pudo verificar la exportación.", + "could_not_queue_export": "No se pudo poner en cola la exportación.", + "export_already_exists": "Ya existe una exportación para esta versión.", + "export_request_queued": "Solicitud de exportación en cola.", + "checking_export_status": "Verificando estado de exportación...", + "downloaded_cached_export": "Exportación en caché descargada.", + "cached_export_generating": "Se está generando una exportación en caché. Vuelve a intentarlo más tarde.", + "export_request_queued_check_later": "Solicitud de exportación en cola. Vuelve a intentarlo más tarde.", + "export_already_exists_try_again": "Ya existe una exportación. Intenta descargarla nuevamente.", + "no_cached_export": "No hay una exportación en caché para esta versión de fuente.", + "queue_export": "Poner exportación en cola", + "could_not_download_external_export": "No se pudo descargar la exportación externa.", + "external_export_required": "El nombre y el archivo de la exportación externa son obligatorios.", + "external_export_uploaded": "Exportación externa cargada.", + "could_not_upload_external_export": "No se pudo cargar la exportación externa.", + "external_exports_title": "Exportaciones externas: {{repo}} / {{version}}", + "no_external_exports": "No se han cargado exportaciones externas para esta versión.", + "no_description": "Sin descripción", + "upload_external_export": "Cargar exportación externa", + "choose_file": "Elegir archivo", + "external_export_file_types": "sql, zip, pdf, csv", + "uploading": "Cargando...", + "could_not_load_changelog": "No se pudo cargar el registro de cambios.", + "large_sources_take_a_while": "Las fuentes grandes pueden tardar un poco en procesarse.", + "copied_version_url": "URL de versión copiada." }, "org": { "orgs": "Organizaciones", diff --git a/src/i18n/locales/zh/translations.json b/src/i18n/locales/zh/translations.json index 612e241b..dad8404e 100644 --- a/src/i18n/locales/zh/translations.json +++ b/src/i18n/locales/zh/translations.json @@ -82,6 +82,9 @@ "with": "with", "json": "JSON", "draft": "草案", + "released": "Released", + "edit": "编辑", + "copy_api_url": "复制 API URL", "highlights": "亮点", "click_to_copy": "单击复制", "submit": "提交", @@ -263,6 +266,35 @@ "summary_request_queued": "Summary request queued.", "summary_request_submitted": "Summary request submitted.", "no_versions_found": "No versions found.", + "source_version_count": "{{count}} source version", + "source_versions_count": "{{count}} source versions", + "mapper": "Mapper", + "export_source_version_title": "Export Source Version: {{repo}} / {{version}}", + "could_not_check_export": "Could not check export.", + "could_not_queue_export": "Could not queue export.", + "export_already_exists": "An export already exists for this version.", + "export_request_queued": "Export request queued.", + "checking_export_status": "Checking export status...", + "downloaded_cached_export": "Downloaded cached export.", + "cached_export_generating": "A cached export is being generated. Check again later.", + "export_request_queued_check_later": "Export request queued. Check again later.", + "export_already_exists_try_again": "An export already exists. Try downloading again.", + "no_cached_export": "There is no cached export for this source version.", + "queue_export": "Queue Export", + "could_not_download_external_export": "Could not download external export.", + "external_export_required": "External export name and file are required.", + "external_export_uploaded": "External export uploaded.", + "could_not_upload_external_export": "Could not upload external export.", + "external_exports_title": "External Exports: {{repo}} / {{version}}", + "no_external_exports": "No external exports have been uploaded for this version.", + "no_description": "No description", + "upload_external_export": "Upload External Export", + "choose_file": "Choose File", + "external_export_file_types": "sql, zip, pdf, csv", + "uploading": "Uploading...", + "could_not_load_changelog": "Could not load changelog.", + "large_sources_take_a_while": "Large sources can take a while to process.", + "copied_version_url": "Copied version URL.", "delete_repo_version": "Delete Repo Version", "repo_version_summary": "存储库版本摘要", "repo_summary_is_calculating": "正在计算处理存储库摘要数据,请请稍后再回来查看。", From 8c24414325aeb914fcfda00ab6c02d1d0514f4cd Mon Sep 17 00:00:00 2001 From: Joseph Amlung Date: Tue, 23 Jun 2026 11:40:47 -0400 Subject: [PATCH 5/6] Quick fixes --- src/components/repos/RepoHome.jsx | 9 ++++++--- src/components/repos/SourceVersionsTab.jsx | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/components/repos/RepoHome.jsx b/src/components/repos/RepoHome.jsx index 2ff4e376..ce999cbf 100644 --- a/src/components/repos/RepoHome.jsx +++ b/src/components/repos/RepoHome.jsx @@ -145,12 +145,15 @@ const RepoHome = () => { const onVersionChange = (version, reload=true) => { - if(reload) - setLoading(true) let url = version.version_url if(reload && version?.version === 'HEAD') url += 'HEAD/' - history.push(url + (tab || 'concepts')) + const nextPath = url + (tab || 'concepts') + if(nextPath === location.pathname) + return + if(reload) + setLoading(true) + history.push(nextPath) } const onTabChange = (event, newTab) => { diff --git a/src/components/repos/SourceVersionsTab.jsx b/src/components/repos/SourceVersionsTab.jsx index 17934770..ade6c87d 100644 --- a/src/components/repos/SourceVersionsTab.jsx +++ b/src/components/repos/SourceVersionsTab.jsx @@ -58,7 +58,7 @@ import APIService from '../../services/APIService'; import { copyToClipboard, currentUserHasAccess, - formatDateTime, + formatDate, headFirst, isLoggedIn, toFullAPIURL @@ -490,7 +490,7 @@ const SourceVersionsTab = ({ {version.retired && {t('common.retired')}} - {version.created_on ? formatDateTime(version.created_on) : '-'} + {version.created_on ? formatDate(version.created_on) : '-'} {version.created_by || ''} From 155d71aaeb8e9abd90ff6d70bffe07b76a258835 Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Wed, 24 Jun 2026 09:26:18 +0530 Subject: [PATCH 6/6] OpenConceptLab/ocl_issues#2576 | review feedbacks --- package-lock.json | 907 ++++++++++++++++++++- package.json | 2 + src/components/common/MarkdownContent.jsx | 224 +++-- src/components/repos/RepoHome.jsx | 22 +- src/components/repos/SourceVersionsTab.jsx | 76 +- 5 files changed, 1080 insertions(+), 151 deletions(-) diff --git a/package-lock.json b/package-lock.json index d8295806..318add4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3625,6 +3625,14 @@ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==" }, + "@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "requires": { + "@types/ms": "*" + } + }, "@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -3648,8 +3656,15 @@ "@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" + }, + "@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "requires": { + "@types/estree": "*" + } }, "@types/glob": { "version": "7.2.0", @@ -3661,6 +3676,14 @@ "@types/node": "*" } }, + "@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "requires": { + "@types/unist": "*" + } + }, "@types/hoist-non-react-statics": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", @@ -3688,12 +3711,25 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "requires": { + "@types/unist": "*" + } + }, "@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", "dev": true }, + "@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" + }, "@types/node": { "version": "15.3.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-15.3.0.tgz", @@ -3857,11 +3893,15 @@ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==" }, + "@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, "@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==" }, "@webassemblyjs/ast": { "version": "1.14.1", @@ -6454,6 +6494,11 @@ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" }, + "bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==" + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -6921,6 +6966,11 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, + "ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==" + }, "cfb": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", @@ -6940,6 +6990,26 @@ "supports-color": "^5.3.0" } }, + "character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==" + }, + "character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==" + }, + "character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==" + }, + "character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==" + }, "chokidar": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", @@ -7222,6 +7292,11 @@ "delayed-stream": "~1.0.0" } }, + "comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" + }, "commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", @@ -8086,6 +8161,14 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, + "decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "requires": { + "character-entities": "^2.0.0" + } + }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -8256,6 +8339,11 @@ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", "dev": true }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" + }, "destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", @@ -8274,6 +8362,14 @@ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true }, + "devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "requires": { + "dequal": "^2.0.0" + } + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -10958,6 +11054,11 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, + "estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==" + }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -11275,7 +11376,7 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, "fast-uri": { @@ -12417,6 +12518,36 @@ } } }, + "hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "requires": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + } + }, + "hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "requires": { + "@types/hast": "^3.0.0" + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -12511,6 +12642,11 @@ "void-elements": "3.1.0" } }, + "html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==" + }, "html-webpack-plugin": { "version": "5.6.7", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.7.tgz", @@ -12807,6 +12943,11 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==" + }, "internal-ip": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", @@ -12883,6 +13024,20 @@ } } }, + "is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==" + }, + "is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "requires": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + } + }, "is-arguments": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", @@ -13055,6 +13210,11 @@ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==" }, + "is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==" + }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -13159,6 +13319,11 @@ "is-extglob": "^2.1.1" } }, + "is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==" + }, "is-in-browser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", @@ -13630,7 +13795,7 @@ "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, "json-stringify-safe": { @@ -14111,6 +14276,11 @@ "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==", "dev": true }, + "longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==" + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -14178,6 +14348,11 @@ "object-visit": "^1.0.0" } }, + "markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==" + }, "math-expression-evaluator": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.3.7.tgz", @@ -14189,6 +14364,208 @@ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" }, + "mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "requires": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==" + } + } + }, + "mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "requires": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + } + }, + "mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "requires": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + } + }, + "mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "requires": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + } + }, + "mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "requires": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + } + }, + "mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "requires": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + } + }, + "mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "requires": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + } + }, + "mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "requires": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + } + }, + "mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "requires": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + } + }, + "mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "requires": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + } + }, + "mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "requires": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + } + }, + "mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "requires": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + } + }, + "mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "requires": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + } + }, + "mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "requires": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + } + }, + "mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "requires": { + "@types/mdast": "^4.0.0" + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -14336,6 +14713,303 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", "dev": true }, + "micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "requires": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "requires": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "requires": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "requires": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "requires": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "requires": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "requires": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "requires": { + "micromark-util-types": "^2.0.0" + } + }, + "micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "requires": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "requires": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "requires": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "requires": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "requires": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "requires": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "requires": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "requires": { + "micromark-util-symbol": "^2.0.0" + } + }, + "micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "requires": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "requires": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "requires": { + "micromark-util-symbol": "^2.0.0" + } + }, + "micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "requires": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==" + }, + "micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==" + }, + "micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "requires": { + "micromark-util-symbol": "^2.0.0" + } + }, + "micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "requires": { + "micromark-util-types": "^2.0.0" + } + }, + "micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "requires": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "requires": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==" + }, + "micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==" + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -14566,7 +15240,7 @@ "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, "negotiator": { @@ -16342,6 +17016,27 @@ "callsites": "^3.0.0" } }, + "parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "requires": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "dependencies": { + "@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + } + } + }, "parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -17045,6 +17740,11 @@ "react-is": "^16.13.1" } }, + "property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==" + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -17316,6 +18016,24 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "requires": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + } + }, "react-material-ui-carousel": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/react-material-ui-carousel/-/react-material-ui-carousel-3.4.2.tgz", @@ -18242,6 +18960,52 @@ "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", "dev": true }, + "remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "requires": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + } + }, + "remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "requires": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + } + }, + "remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "requires": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + } + }, + "remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "requires": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + } + }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -19420,6 +20184,11 @@ "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", "dev": true }, + "space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" + }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -20471,6 +21240,15 @@ "safe-buffer": "~5.1.0" } }, + "stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "requires": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + } + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -20560,6 +21338,22 @@ } } }, + "style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "requires": { + "style-to-object": "1.0.14" + } + }, + "style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "requires": { + "inline-style-parser": "0.2.7" + } + }, "style-value-types": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-4.1.4.tgz", @@ -20742,7 +21536,7 @@ "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, "through2": { @@ -20840,12 +21634,22 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==" + }, "trim-newlines": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", "dev": true }, + "trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==" + }, "true-case-path": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", @@ -21287,6 +22091,27 @@ "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", "dev": true }, + "unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "requires": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "dependencies": { + "is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==" + } + } + }, "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -21329,6 +22154,49 @@ "imurmurhash": "^0.1.4" } }, + "unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "requires": { + "@types/unist": "^3.0.0" + } + }, + "unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "requires": { + "@types/unist": "^3.0.0" + } + }, + "unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "requires": { + "@types/unist": "^3.0.0" + } + }, + "unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + } + }, + "unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + } + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -21519,6 +22387,24 @@ "extsprintf": "^1.2.0" } }, + "vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "requires": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + } + }, + "vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "requires": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + } + }, "void-elements": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", @@ -22489,6 +23375,11 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true + }, + "zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==" } } } diff --git a/package.json b/package.json index 5d040467..06605f58 100644 --- a/package.json +++ b/package.json @@ -30,12 +30,14 @@ "react-hotjar": "^5.5.0", "react-i18next": "^12.1.5", "react-image-crop": "^8.6.12", + "react-markdown": "^10.1.0", "react-material-ui-carousel": "^3.4.2", "react-native-rss-parser": "^1.5.1", "react-quill": "^2.0.0", "react-router-dom": "^5.3.4", "react-virtuoso": "^4.18.7", "react-window": "^1.8.11", + "remark-gfm": "^4.0.1", "stacktrace-js": "^2.0.2", "xlsx": "^0.18.5" }, diff --git a/src/components/common/MarkdownContent.jsx b/src/components/common/MarkdownContent.jsx index f2190eb0..76eb6faf 100644 --- a/src/components/common/MarkdownContent.jsx +++ b/src/components/common/MarkdownContent.jsx @@ -1,136 +1,94 @@ import React from 'react'; -import { - Box, - Divider, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, - Typography -} from '@mui/material'; - -const renderInlineMarkdown = text => { - const tokens = []; - const pattern = /(\*\*([^*]+)\*\*|\*([^*]+)\*|`([^`]+)`|\[([^\]]+)\]\(([^)]+)\))/g; - let lastIndex = 0; - let match; - - while((match = pattern.exec(text || ''))) { - if(match.index > lastIndex) - tokens.push(text.slice(lastIndex, match.index)); - - if(match[2]) - tokens.push({match[2]}); - else if(match[3]) - tokens.push({match[3]}); - else if(match[4]) - tokens.push({match[4]}); - else if(match[5]) - tokens.push({match[5]}); - - lastIndex = pattern.lastIndex; - } - - if(lastIndex < (text || '').length) - tokens.push(text.slice(lastIndex)); - - return tokens; -}; - -const parseMarkdownTable = lines => { - if(lines.length < 2 || !lines[0].trim().startsWith('|') || !lines[1].includes('---')) - return null; - - const parseRow = line => line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map(cell => cell.trim()); - const headers = parseRow(lines[0]); - const rows = []; - let nextIndex = 2; - - while(nextIndex < lines.length && lines[nextIndex].trim().startsWith('|')) { - rows.push(parseRow(lines[nextIndex])); - nextIndex += 1; - } - - return { headers, rows, nextIndex }; -}; - -const MarkdownContent = ({ markdown }) => { - const lines = (markdown || '').split('\n'); - const elements = []; - let index = 0; - - while(index < lines.length) { - const line = lines[index]; - const trimmed = line.trim(); - - if(!trimmed) { - index += 1; - continue; - } - - if(trimmed === '---') { - elements.push(); - index += 1; - continue; - } - - const table = parseMarkdownTable(lines.slice(index)); - if(table) { - elements.push( - - - - - {table.headers.map(header => {renderInlineMarkdown(header)})} - - - - {table.rows.map((row, rowIndex) => ( - - {row.map((cell, cellIndex) => {renderInlineMarkdown(cell)})} - - ))} - -
-
- ); - index += table.nextIndex; - continue; - } - - const heading = trimmed.match(/^(#{1,6})\s+(.+)$/); - if(heading) { - const variant = heading[1].length <= 1 ? 'h6' : 'subtitle1'; - elements.push( - - {renderInlineMarkdown(heading[2])} - - ); - index += 1; - continue; - } - - if(trimmed.startsWith('>')) { - elements.push( - - {renderInlineMarkdown(trimmed.replace(/^>\s?/, ''))} - - ); - index += 1; - continue; - } - - elements.push( - - {renderInlineMarkdown(trimmed)} - - ); - index += 1; - } - - return {elements}; -}; +import { Box, Typography } from '@mui/material'; +import ReactMarkdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; + +const MarkdownContent = ({ markdown }) => ( + + {children}, + h2: ({ children }) => {children}, + h3: ({ children }) => {children}, + p: ({ children }) => {children}, + code: ({ inline, children }) => inline ? ( + + {children} + + ) : ( + + {children} + + ), + a: ({ href, children, ...props }) => { + const isExternal = /^https?:\/\//i.test(href || ''); + return ( + + {children} + + ); + } + }} + > + {markdown || ''} + + +); export default MarkdownContent; diff --git a/src/components/repos/RepoHome.jsx b/src/components/repos/RepoHome.jsx index ce999cbf..dadeea53 100644 --- a/src/components/repos/RepoHome.jsx +++ b/src/components/repos/RepoHome.jsx @@ -30,6 +30,8 @@ import SourceVersionsTab from './SourceVersionsTab'; import ReferenceHome from '../references/ReferenceHome' import AddReferencesDialog from '../collections/AddReferencesDialog' +const DEFAULT_VERSIONS_PAGE_SIZE = 25 + const RepoHome = () => { const { t } = useTranslation() const location = useLocation() @@ -55,6 +57,8 @@ const RepoHome = () => { const [repoSummary, setRepoSummary] = React.useState(false) const [versions, setVersions] = React.useState(false) const [versionsCount, setVersionsCount] = React.useState(false) + const [versionsPage, setVersionsPage] = React.useState(1) + const [versionsPageSize, setVersionsPageSize] = React.useState(DEFAULT_VERSIONS_PAGE_SIZE) const [loading, setLoading] = React.useState(true) const [showItem, setShowItem] = React.useState(false) const [conceptForm, setConceptForm] = React.useState(false) @@ -107,11 +111,11 @@ const RepoHome = () => { }) } - const fetchVersions = () => { - APIService.new().overrideURL(dropVersion(getURL())).appendToUrl('versions/').get(null, null, {verbose:true, includeSummary: true, limit: 100}).then(response => { + const fetchVersions = (page=versionsPage, limit=versionsPageSize) => { + APIService.new().overrideURL(dropVersion(getURL())).appendToUrl('versions/').get(null, null, {verbose:true, includeSummary: true, limit, page}).then(response => { const _versions = Array.isArray(response?.data) ? response.data : [] setVersions(_versions) - setVersionsCount(response?.headers?.['num_found'] || 1) + setVersionsCount(parseInt(response?.headers?.['num_found'] || _versions.length || 0)) if(!repo.version_url && params.repoVersion !== 'HEAD' && !showConceptURL && !showMappingURL) { const releasedVersions = filter(_versions, {released: true}) let version = orderBy(releasedVersions, 'created_on', ['desc'])[0] || orderBy(_versions, 'created_on', ['desc'])[0] @@ -156,6 +160,12 @@ const RepoHome = () => { history.push(nextPath) } + const onVersionsPageChange = (page, pageSize=versionsPageSize) => { + setVersionsPage(page) + setVersionsPageSize(pageSize) + fetchVersions(page, pageSize) + } + const onTabChange = (event, newTab) => { if(newTab) { setTab(newTab) @@ -328,6 +338,9 @@ const RepoHome = () => { repo={repo} versions={versions} count={versionsCount} + page={versionsPage} + pageSize={versionsPageSize} + onPageChange={onVersionsPageChange} onCreateVersion={onCreateVersionClick} onReleaseVersion={version => setReleaseTarget(version)} onDeleteVersion={version => setDeleteTarget(version)} @@ -343,7 +356,10 @@ const RepoHome = () => { repo={repo} versions={versions} count={versionsCount} + page={versionsPage} + pageSize={versionsPageSize} loading={loading} + onPageChange={onVersionsPageChange} onVersionChange={onVersionChange} onEditVersion={version => setVersionForm({edit: true, version, expansions: []})} onReleaseVersion={version => setReleaseTarget(version)} diff --git a/src/components/repos/SourceVersionsTab.jsx b/src/components/repos/SourceVersionsTab.jsx index ade6c87d..993abdf5 100644 --- a/src/components/repos/SourceVersionsTab.jsx +++ b/src/components/repos/SourceVersionsTab.jsx @@ -27,6 +27,7 @@ import { TableCell, TableContainer, TableHead, + TablePagination, TableRow, TextField, Tooltip, @@ -58,6 +59,7 @@ import APIService from '../../services/APIService'; import { copyToClipboard, currentUserHasAccess, + dropVersion, formatDate, headFirst, isLoggedIn, @@ -76,7 +78,7 @@ const bodyCellSx = { }; const headerCellSx = { - backgroundColor: 'white', + backgroundColor: 'background.paper', borderBottom: '1px solid', borderColor: 'surface.nv80', color: 'surface.contrastText', @@ -346,7 +348,10 @@ const SourceVersionsTab = ({ repo, versions, count, + page, + pageSize, loading, + onPageChange, onVersionChange, onEditVersion, onReleaseVersion, @@ -360,7 +365,39 @@ const SourceVersionsTab = ({ const [exportVersion, setExportVersion] = React.useState(null); const [externalExportsVersion, setExternalExportsVersion] = React.useState(null); const [changelogVersion, setChangelogVersion] = React.useState(null); - const sortedVersions = React.useMemo(() => headFirst(Array.isArray(versions) ? versions : []), [versions]); + const [headVersion, setHeadVersion] = React.useState(isHeadVersion(repo) ? repo : null); + const baseRepoURL = dropVersion(repo?.version_url || repo?.url || ''); + React.useEffect(() => { + if(!baseRepoURL) { + setHeadVersion(null); + return; + } + + if(isHeadVersion(repo)) { + setHeadVersion(repo); + return; + } + + APIService.new() + .overrideURL(baseRepoURL) + .get(null, null, { includeSummary: true }, true) + .then(response => { + setHeadVersion(response?.data || response?.response?.data || null); + }); + }, [baseRepoURL, repo]); + const sortedVersions = React.useMemo(() => { + const versionList = Array.isArray(versions) ? versions : []; + const normalizedHeadVersion = headVersion ? { + ...headVersion, + id: 'HEAD', + version: 'HEAD', + version_url: headVersion.url || headVersion.version_url || baseRepoURL + } : null; + const pagedVersions = page === 1 + ? [normalizedHeadVersion, ...versionList].filter(Boolean) + : versionList.filter(version => !isHeadVersion(version)); + return headFirst(pagedVersions); + }, [baseRepoURL, headVersion, page, versions]); const hasAccess = currentUserHasAccess(); const closeMenu = () => setMenuState({ anchorEl: null, version: null }); @@ -395,13 +432,16 @@ const SourceVersionsTab = ({ const countLabel = versionsCount === 1 ? t('repo.source_version_count', { count: versionsCount.toLocaleString() }) : t('repo.source_versions_count', { count: versionsCount.toLocaleString() }); + const handleChangePage = (event, nextPage) => onPageChange?.(nextPage + 1, pageSize); + const handleChangeRowsPerPage = event => onPageChange?.(1, parseInt(event.target.value, 10)); return ( - + {countLabel} - + @@ -513,6 +553,28 @@ const SourceVersionsTab = ({
+ withClose(version => onVersionChange(version))}>{t('repo.explore_version')} withClose(version => copyVersionURL(version))}>{t('common.copy_api_url')}