import React, { createContext, forwardRef, useEffect, useImperativeHandle, useState, } from 'react'; import Transformer from '../../transformers'; import './index.css'; // import arrow from '../../assets/arrow.svg'; import TextTransformer from '../../textTransformers'; // import Mdast from '@ylzcc/mdast' import last from '../../assets/last.svg'; import next from '../../assets/next.svg'; import Mdast from '../../mdast'; export interface MarkdownOptions { text?: string; type: string;//text,video videoShow?: boolean; activeUrl?: string; wsUrl?: string; modal?: number; mardownKey?: string; xternUrl?: string; ifExam?: boolean mdText?: any; location?: boolean getInstruct?: (type: string, value: string) => void overPop?: () => void verify?: (verifyValue: string) => void;//是否需要下一步娇艳 nextVerify?: (verifyValue: string) => void;//是否需要下一步娇艳 addActive?: (scoreValue: any, activeId: any) => void ifRequest?: () => void//是否存在没完成的 reportData?: (data: any) => void // 数据上报 execDemo?: (data: any) => void // 示例演示执行 // 新增 getActivity?: (id: string) => Promise //获取活动内容 updateActivity?: (activityId: string, topicConfig: string, topicResult: string) => Promise // 用户保存活动接口,如果不希望保存可以不提供 codePlayerConnect?: any // 跟codeplayer的交互 preActive?: (data: any) => boolean | void // 三个活动按钮点击前的交互 如果返回true,将不会处理原先的逻辑,表示你需要自己处理该逻辑 imageOnClick?: (src: string) => void // 图片点击事件 [otherProps: string]: any } export type VideoPlayerType = { push: (time: string) => void next: () => void conent: () => void changeUrl: (url: string) => void getPostion: (lineNumber: string, type: string) => void } export const SharedDataContext = createContext({}) let Markdown: React.FC = (props, ref) => { const { getActivity, updateActivity, codePlayerConnect, preActive, ifExam, location, wsUrl, xternUrl, videoShow, type, modal, mardownKey, mdText, overPop, verify, nextVerify, reportData, execDemo, imageOnClick } = props; const [newXternUrl, setNewXternUrl] = useState(xternUrl) const [newSocket, setNewSocket] = useState(null) const [component, setComponent] = useState(null); const [iconChange, setIconChange] = useState(true); const [markDownText, setMarkDownText] = useState(); const [readerText, setReaderText] = useState([]) // 需要重连 const [reconnect, setReconnect] = useState(false) //当前体面进度 const [readerTextIndex, setReaderTextIndex] = useState(0) // const [timeList] = useState(['00:00:01.333', '00:00:05.466', '00:00:25.066', '00:00:29.233', '00:00:32.133', '00:00:38.333']) // const [index, setImdex] = useState(0) const timer = (str: string) => { try { if (!str) { return undefined } if (str.endsWith('.')) { str = str.substring(0, str.length - 1) } let ms: number = 0 let s: string = str if (str.includes('.')) { ms = Number(str.substring(str.lastIndexOf('.') + 1)) s = str.substring(0, str.lastIndexOf('.')) } const t = s.split(':') let ts: number = 0 for (let i = 0; i < t.length; i++) { ts += Number(t[t.length - 1 - i]) * Math.pow(60, i) } return ts + ms / 1000 } catch (error) { console.error(error) return undefined } }; //视屏字幕标题 const getSubtitle = (time: string) => { const titleList = document.querySelectorAll('.subtitle'); const tagdList = document.querySelectorAll('.tag-d-video-text'); titleList.forEach((item: any, index: number) => { let startTime = timer(item.getAttribute('data-st') ?? '')?.toString() ?? '0' let endtTime = '' if (index < titleList.length) { endtTime = timer(titleList[index + 1]?.getAttribute('data-st') ?? '')?.toString() ?? '0' } titleList[index].classList.remove('test'); if (startTime <= time && (time < endtTime || index === titleList.length - 1)) { const parentHeigth = titleList[index].parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.offsetHeight ?? 0 titleList[index].parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.scrollTo({ top: item.offsetTop - parentHeigth / 2, behavior: 'smooth' }); if (index > 0) { titleList[index - 1].parentElement?.parentElement?.classList.remove('activeBac'); } titleList[index].classList.add('test'); titleList[index].parentElement?.parentElement?.classList.add('activeBac') } }) tagdList.forEach((item: any, index: number) => { let tagStartTime = timer(item.getAttribute('data-st') ?? '')?.toString() ?? '0' let tagEndTime = timer(item.getAttribute('data-et') ?? '')?.toString() ?? '0' if (index < tagdList.length - 1) { if (tagStartTime <= time && time <= tagEndTime) { tagdList[index]?.classList.add('tag-d-back') } else { tagdList[index]?.classList.remove('tag-d-back') } } }) } //给字符标题添加css属性 const getSubtitleParent = () => { const titleList = document.querySelectorAll('.subtitle'); titleList?.forEach((item: any,) => { item.parentElement?.classList.add('subtitleList') }) } // 判断是否是JSON格式字符串 const isJsonString = (str: string) => { try { JSON.parse(str); } catch (e) { return false; } return true; } const base64ToText = (base64: any) => { try { const binary = atob(base64); const uint8Array = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { uint8Array[i] = binary.charCodeAt(i); } const decoder = new TextDecoder(); return decoder.decode(uint8Array); } catch (error) { return '' } } // websocket连接 const handleWS = () => { try { if (!wsUrl) return if (newSocket && newSocket?.readyState === 1) { newSocket?.close() } const ws = new WebSocket(wsUrl); setNewSocket(ws) setReconnect(false) ws.onopen = function () { const text = JSON.stringify({ method: "read" }) ws.send(text) setReconnect(false) } ws.onmessage = function (res: any) { const { data } = res if (isJsonString(data)) { const json = JSON.parse(data) const text = json.data const type = json?.type let lineValue: any if (json?.type === 'cursor' && json?.data?.length > 0) { text.forEach((item: any) => { if (item?.key === mardownKey) { lineValue = item?.cursor?.lineNumber } }) getValue(lineValue) } if (type !== 'read') return const mdText = text ? base64ToText(text) : '' setMarkDownText(mdText) } } ws.onclose = function (res: any) { if (res.code < 3000) { setReconnect(true) } } ws.onerror = function () { } window.addEventListener('message', (event) => { if (event.data.type === 'disconnectWebSocket') { ws.close(); } else if (event.data.type === 'connectWebSocket') { handleWS() } else if (event.data.type === 'postValue' && props?.addActive) { props?.addActive(event?.data?.value?.value ?? 0, event?.data?.value?.activeID ?? '') } }); } catch (error) { } } // -------------- //给复制执行添加点击事件 对 md里面拦截了所有的点击事件 const addGetAttribute = (event: any) => { const target = event?.target as HTMLElement; // 当执行指令点击执行 是肯定成功获得分数的 if (target.classList.contains('extvalue1')) { if (props?.getInstruct) { props?.getInstruct(target.getAttribute('data-type') ?? '', target.getAttribute('data-value') ?? '') } } // 之前是执行校验的按钮会触发这个,现在不用直接通过execDemo进行触发 // if (target.classList.contains('exec-btn')) { // if (props?.getInstruct) { // props?.getInstruct(target.getAttribute('data-type') ?? '', target.getAttribute('data-value') ?? '') // } // } if (target.getAttribute('data-valiate-type') && target.classList.contains('validateValue1')) { let valiateList = document.querySelectorAll('.noValidateValue'); // 校验作为隐藏项来做执行 if (valiateList.length > 0 && verify) { valiateList.forEach((item: any) => { if (item.getAttribute('data-valiate-type') === target.getAttribute('data-valiate-type')) { verify(item.getAttribute('data-value') ?? '') } }) } } else { if (target.classList.contains('validateValue1')) { if (verify) { verify(target.getAttribute('data-value') ?? '') } } } } //改变文档行课程分页 const changeIndex = () => { if (readerTextIndex < readerText.length - 1) { setReaderTextIndex(readerTextIndex + 1) } else { if (overPop) { overPop() } } } //下一步和完成 const ifRequired1 = () => { // 判断选择题 let questionList = document.querySelectorAll('.question-active') if (questionList?.length > 0) { let isCorrect = true; questionList.forEach((item: any) => { if (!isCorrect) return const style = getComputedStyle(item?.parentElement?.parentElement?.parentElement?.parentElement) const displayValue = style.getPropertyValue("display"); if (displayValue !== 'block') return if (item.getAttribute('data-required') !== 'true') return if (item.getAttribute('data-requiredcorrect') === 'true' && item.getAttribute('data-success') !== 'true') { isCorrect = false } if (item.getAttribute('data-requiredcorrect') !== 'true' && item.getAttribute('data-hasclick') !== 'true' ) { isCorrect = false } // if (item.getAttribute('data-required') === 'true' && displayValue === 'block') { // if (item.getAttribute('data-requiredcorrect') === 'true') { // if (item.getAttribute('data-contvalue') !== 'true') { // isCorrect = false; // } // } else { // if (item.getAttribute('data-submit') !== 'true') { // isCorrect = false; // } // } // } }); return isCorrect ? 'true' : 'false' } else { return 'true'; } }; const ifRequired2 = () => { // 判断示例演示和编码测验 let demoList = document.querySelectorAll('.demo-active') ?? [] let codetestList = document.querySelectorAll('.codetest-active') ?? [] let list = Array.from(demoList).concat(Array.from(codetestList)) if (list.length > 0) { let isCorrect = true; list.forEach((item: any) => { if (!isCorrect) return const style = getComputedStyle(item?.parentElement?.parentElement?.parentElement?.parentElement) const displayValue = style.getPropertyValue("display") if (item.getAttribute('data-required') === 'true' && displayValue === 'block') { if (item.getAttribute('data-hasclick') !== 'true') { isCorrect = false } // if (item.getAttribute('data-submit') === 'true') { // if (item.getAttribute('data-requiredcorrect') === 'true') { // if (item.getAttribute('data-contvalue') !== 'true') { // isCorrect = false // } // } else { // if (item.getAttribute('data-nextrequired') === 'true') { // if (item.getAttribute('data-validate') === 'true') { // if (item.getAttribute('data-nextcorrect') === 'true' && window.localStorage.getItem('mark') !== 'true') { // if (item.getAttribute('data-contvalue') !== 'true') { // isCorrect = false // } // } // } else { // isCorrect = false // } // } // } // } else { // isCorrect = false // } } }) return isCorrect ? 'true' : 'false' } else { return 'true' } } //转换视屏课程数结构 const changeVideoText = (mdast: any) => { let subtitleList: any[] = [] mdast?.children?.forEach((item: any, index: number) => { let friseIndex = 0 let endIndex = 0 if (item?.type === 'heading' && mdast?.children[index + 1]?.type !== 'heading' && index !== mdast?.children.length - 1) { friseIndex = index for (let i = index + 1; i < mdast.children.length; i++) { if (mdast.children[i].type === 'heading') { endIndex = i break } else if (i === mdast.children.length - 1) { endIndex = i + 1 break } } let sectionItem = { children: [], type: 'section', value: '', } if (mdast.children[friseIndex]?.depth === 3) { for (let i = friseIndex + 1; i < endIndex; i++) { if (mdast.children[i]?.properties?.st !== undefined && mdast.children[friseIndex].properties) { mdast.children[friseIndex].properties.st = mdast.children[i]?.properties?.st break } } for (let i = endIndex - 1 + 1; i > friseIndex; i--) { if (mdast.children[i]?.properties?.et !== undefined && mdast.children[friseIndex].properties) { mdast.children[friseIndex].properties.et = mdast.children[i]?.properties?.et break } } sectionItem.children = mdast.children.splice(friseIndex + 1, endIndex - 1 - friseIndex) mdast.children.splice(friseIndex + 1, 0, sectionItem) } else { sectionItem.children = mdast.children.splice(friseIndex + 1, endIndex - 1 - friseIndex) mdast.children.splice(friseIndex + 1, 0, sectionItem) } subtitleList.push([friseIndex, endIndex]) } }) if (subtitleList.length > 0) { if (subtitleList[0][0] !== 0) { let sectionItem = { children: [], type: 'section', value: '', } let index for (let i = 0; i < mdast?.children?.length; i++) { if (mdast?.children[i].type === 'heading') { index = i break } } sectionItem.children = mdast.children.splice(0, index) mdast.children.splice(0, 0, sectionItem) } } return mdast } //转换文档型课程数结构 const changeReaderText = (mdast: any) => { let newReadetList: any = [] let indexList: any = [] let customValue: any = 0 if (mdast.children?.length > 0) { if (mdast.children[0]?.type !== 'heading' || mdast.children[0]?.depth !== 1 || mdast.children[0]?.properties?.tag !== 's') { mdast.children?.unshift({ type: 'heading', depth: 1, properties: { tag: 's' } }) } mdast?.children?.forEach((item: any, index: number) => { if (item?.properties?.ext === 'active') { item.properties.url = newXternUrl } if (item?.position?.start?.line && item?.position?.end?.line) { item.position.start.line = item?.position?.start?.line - customValue * 2 item.position.end.line = item?.position?.end?.line - customValue * 2 } if (item?.properties?.ext) { customValue += 1 } let friseIndex = 0 let endIndex = 0 if (item?.type === 'heading' && item?.depth === 1 && item?.properties?.tag === 's') { friseIndex = index for (let i = index + 1; i < mdast.children?.length; i++) { if (mdast?.children[i]?.type === 'heading' && mdast?.children[i]?.depth === 1 && mdast?.children[i]?.properties?.tag === 's') { endIndex = i break } if (i === mdast.children?.length - 1) { endIndex = i + 1 } } indexList.push([friseIndex, endIndex]) } }) } indexList.forEach((item: any, index: any) => { if (item[1] - item[0] > 1) { let newMdast = JSON.parse(JSON.stringify(mdast)) let new_arr = newMdast?.children?.splice(item[0] + 1, item[1] - 1 - item[0]); if (new_arr?.length > 0) { new_arr.forEach((value: any) => { value.page = index }) } newReadetList.push(new_arr) } }) setReaderText(newReadetList) return newReadetList } useEffect(() => { const mdast = Mdast.parse(markDownText); if (type === 'videoText') { let domast = Transformer.stringify(Transformer.runSync(changeVideoText(JSON.parse(JSON.stringify(mdast))))); getSubtitleParent() setComponent(domast); } }, [markDownText]); useEffect(() => { const mdast = Mdast.parse(markDownText); if (mdast?.children?.length > 0) { let newMdast = JSON.parse(JSON.stringify(mdast)) if (type === 'text' && newMdast?.children?.length > 0) { let allList: React.ReactElement>[] = [] let fetchPromises = changeReaderText(JSON.parse(JSON.stringify(newMdast))).map((item: any) => { let newMdastList = JSON.parse(JSON.stringify(mdast)) newMdastList.children = item let domast = TextTransformer.stringify(TextTransformer.runSync(newMdastList)); return allList.push(domast) }) Promise.all(fetchPromises) .then(() => { setComponent(allList); }); } else if (type === 'text' && !newMdast?.children) { let domast = TextTransformer.stringify(TextTransformer.runSync(mdast)); setComponent(domast); } } else { setComponent(null) } }, [markDownText, readerTextIndex, newXternUrl]); useEffect(() => { if (mdText) { setMarkDownText(mdText) } }, [mdText]) useEffect(() => { if (component) { } else { const mdast = Mdast.parse(markDownText);; if (mdast) { let newMdast = JSON.parse(JSON.stringify(mdast)) // console.log(newMdast); // newMdast.children = changeReaderText(JSON.parse(JSON.stringify(newMdast)))[readerTextIndex] if (type === 'text' && newMdast?.children?.length > 0) { let allList: React.ReactElement>[] = [] let fetchPromises = changeReaderText(JSON.parse(JSON.stringify(newMdast))).map((item: any) => { let newMdastList = JSON.parse(JSON.stringify(mdast)) newMdastList.children = item let domast = TextTransformer.stringify(TextTransformer.runSync(newMdastList)); return allList.push(domast) }) // let domast = TextTransformer.stringify(TextTransformer.runSync(newMdast)); // console.log(domast); Promise.all(fetchPromises) .then(() => { setComponent(allList); }); } else if (type === 'text' && !newMdast?.children) { let domast = TextTransformer.stringify(TextTransformer.runSync(mdast)); setComponent(domast); } } } }, [component]) useEffect(() => { window.addEventListener('click', addGetAttribute); return () => { window.removeEventListener('click', addGetAttribute); } }, []) useEffect(() => { setIconChange(videoShow ?? true) }, [videoShow]) useImperativeHandle(ref, (): VideoPlayerType => ({ // 添加标签 push: (time: string) => { getSubtitle(time) }, next: () => { changeIndex() }, conent: () => { setReaderTextIndex(0) setComponent(null) }, changeUrl: (url: string) => { if (url) { setNewXternUrl(url) } }, getPostion: (lineNumber: any, type: string) => { if (lineNumber) { getValue(lineNumber, type) } } })); useEffect(() => { handleWS() setReaderTextIndex(0) scrollTop() }, [wsUrl]) useEffect(() => { if (wsUrl && reconnect) { handleWS() } }, [wsUrl, reconnect]) useEffect(() => { return () => { setReconnect(false) if (newSocket && newSocket?.readyState === 1) { newSocket?.close() } } }, []) //滚动条重新回到顶部 const scrollTop = () => { const element = document.querySelector('.readerTextContain') as HTMLElement; if (element) { element?.scrollTo({ top: 0, behavior: 'smooth' }); } } //博客位置的滚动 const scrollBlogTop = () => { const element = document.querySelector('.readerTextContain') as HTMLElement; if (element) { element?.parentElement?.parentElement?.parentElement?.parentElement?.scrollTo({ top: 0, behavior: 'smooth' }); } } //定位功能实时改变 const getValue = (lineNumber: any, type?: string) => { console.log(lineNumber, type); let experimentList = document.querySelectorAll('.readerTextItem'); let otherExperimentList = document.querySelectorAll('.scrollIcon'); if (experimentList?.length > 0) { experimentList?.forEach((item: any, index1: any) => { if (type === 'blog') { if (item?.childNodes?.length > 0 && item?.childNodes[0]?.childNodes?.length > 0) { item?.childNodes[0].childNodes.forEach((itemValue: any, index: any) => { let position: any let page: any if (itemValue?.attributes) { position = JSON.parse(itemValue.getAttribute('position')) page = itemValue.getAttribute('page') } if (position?.start?.line <= lineNumber && lineNumber <= position?.end?.line) { setReaderTextIndex(Number(page) ?? 0) setTimeout(() => { experimentList[index1].childNodes[0].childNodes[index].parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.scrollTo({ top: itemValue.offsetTop - 100, behavior: 'smooth' }); }, 0); } }) } if (item?.getAttribute('data-start') <= lineNumber && lineNumber <= item?.getAttribute('data-end')) { setReaderTextIndex(Number(item?.getAttribute('data-page')) ?? 0) setTimeout(() => { experimentList[index1].parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.scrollTo({ top: item.offsetTop - 100, behavior: 'smooth' }); }, 0); } } else { if (item?.childNodes?.length > 0 && item?.childNodes[0]?.childNodes?.length > 0) { item?.childNodes[0].childNodes.forEach((itemValue: any, index: any) => { let position: any let page: any if (itemValue?.attributes) { position = JSON.parse(itemValue.getAttribute('position')) page = itemValue.getAttribute('page') } if (position?.start?.line <= lineNumber && lineNumber <= position?.end?.line) { setReaderTextIndex(Number(page) ?? 0) setTimeout(() => { experimentList[index1].childNodes[0].childNodes[index].parentElement?.parentElement?.parentElement?.parentElement?.scrollTo({ top: itemValue.offsetTop - 100, behavior: 'smooth' }); }, 0); } }) } if (item?.getAttribute('data-start') <= lineNumber && lineNumber <= item?.getAttribute('data-end')) { setReaderTextIndex(Number(item?.getAttribute('data-page')) ?? 0) setTimeout(() => { experimentList[index1].parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.scrollTo({ top: item.offsetTop - 100, behavior: 'smooth' }); }, 0); } } }) } if (otherExperimentList?.length > 0) { if (type === 'blog') { otherExperimentList?.forEach((item: any, index1: any) => { if (item?.getAttribute('data-start') <= lineNumber && lineNumber <= item?.getAttribute('data-end')) { setReaderTextIndex(Number(item?.getAttribute('data-page')) ?? 0) setTimeout(() => { otherExperimentList[index1].parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.scrollTo({ top: item.offsetTop - 100, behavior: 'smooth' }); }, 0); } }) } else { otherExperimentList?.forEach((item: any, index1: any) => { if (item?.getAttribute('data-start') <= lineNumber && lineNumber <= item?.getAttribute('data-end')) { setReaderTextIndex(Number(item?.getAttribute('data-page')) ?? 0) setTimeout(() => { otherExperimentList[index1].parentElement?.parentElement?.parentElement?.parentElement?.parentElement?.scrollTo({ top: item.offsetTop - 100, behavior: 'smooth' }); }, 0); } }) } } } // // 事件埋点监听 // useEffect(() => { // const handle = (e: MessageEvent) => { // const { data } = e // const { type, value } = data // switch (type) { // case 'markdown-layout': // execDemo?.(value?.data) // break; // default: // break; // } // } // window.addEventListener('message', handle) // return () => { // window.removeEventListener('message', handle) // } // }, []) return (
{type === 'text' ?
{ component?.map((item: any, index: any) => (
{item}
)) }
{ readerTextIndex === 0 ?
:
{ if (readerTextIndex > 0) { setReaderTextIndex(readerTextIndex - 1) if (location) { scrollBlogTop() } else { scrollTop() } } }}>上一步
} { readerTextIndex < readerText?.length - 1 ?
{ let valiateList = document.querySelectorAll('.noValidateValue'); if (ifRequired1() === 'true' && ifRequired2() === 'true') { if (valiateList.length > 0 && nextVerify) { let ifNext = true valiateList.forEach((item: any) => { const style = getComputedStyle(item?.parentElement?.parentElement?.parentElement?.parentElement?.parentElement); const displayValue = style.getPropertyValue("display"); // console.log(displayValue); if (item.getAttribute('data-valiate-type') === '::next' && displayValue === 'block') { ifNext = false nextVerify(item.getAttribute('data-value') ?? '') } }) if (ifNext) { setReaderTextIndex(readerTextIndex + 1) if (location) { scrollBlogTop() } else { scrollTop() } } } else { setReaderTextIndex(readerTextIndex + 1) if (location) { scrollBlogTop() } else { scrollTop() } } } else { // console.log('未完成'); props?.ifRequest?.() } }}>下一步
: modal === 0 && !ifExam ?
{ let valiateList = document.querySelectorAll('.noValidateValue'); if (ifRequired1() === 'true' && ifRequired2() === 'true') { if (valiateList.length > 0 && nextVerify) { let ifNext = true valiateList.forEach((item: any) => { const style = getComputedStyle(item?.parentElement?.parentElement?.parentElement?.parentElement?.parentElement); const displayValue = style.getPropertyValue("display"); if (item.getAttribute('data-valiate-type') === '::next' && displayValue === 'block') { ifNext = false nextVerify(item.getAttribute('data-value') ?? '') } }) if (ifNext) { if (overPop) { overPop() } } } else { if (overPop) { overPop() } } } else { if (props?.ifRequest) { props?.ifRequest() } } }}>完成
: <> }
:
{iconChange && component}
}
); }; Markdown = forwardRef(Markdown as any) export default Markdown;