Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
wangsen
MinerU
Commits
791e653c
"examples/avsr/models/conformer_rnnt.py" did not exist on "29deb085f097f584223e0e276050b867577693d7"
Commit
791e653c
authored
Sep 19, 2024
by
dechen lin
Browse files
feat: add web project
parent
9d689790
Changes
381
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
2669 additions
and
0 deletions
+2669
-0
projects/web/src/locale/side/en.ts
projects/web/src/locale/side/en.ts
+44
-0
projects/web/src/locale/side/zh.ts
projects/web/src/locale/side/zh.ts
+43
-0
projects/web/src/locale/zh.json
projects/web/src/locale/zh.json
+55
-0
projects/web/src/main.tsx
projects/web/src/main.tsx
+10
-0
projects/web/src/pages/extract-side/index.module.scss
projects/web/src/pages/extract-side/index.module.scss
+38
-0
projects/web/src/pages/extract-side/index.tsx
projects/web/src/pages/extract-side/index.tsx
+107
-0
projects/web/src/pages/extract/components/extractor-guide/index.module.scss
...ages/extract/components/extractor-guide/index.module.scss
+18
-0
projects/web/src/pages/extract/components/extractor-guide/index.tsx
...eb/src/pages/extract/components/extractor-guide/index.tsx
+78
-0
projects/web/src/pages/extract/components/extractor-lang/index.tsx
...web/src/pages/extract/components/extractor-lang/index.tsx
+29
-0
projects/web/src/pages/extract/components/extractor-queue/index.tsx
...eb/src/pages/extract/components/extractor-queue/index.tsx
+199
-0
projects/web/src/pages/extract/components/extractor-repo/index.module.scss
...pages/extract/components/extractor-repo/index.module.scss
+62
-0
projects/web/src/pages/extract/components/extractor-repo/index.tsx
...web/src/pages/extract/components/extractor-repo/index.tsx
+22
-0
projects/web/src/pages/extract/components/iframe-loading/index.tsx
...web/src/pages/extract/components/iframe-loading/index.tsx
+58
-0
projects/web/src/pages/extract/components/image-layer-viwer/index.tsx
.../src/pages/extract/components/image-layer-viwer/index.tsx
+338
-0
projects/web/src/pages/extract/components/latex-renderer/index.module.scss
...pages/extract/components/latex-renderer/index.module.scss
+15
-0
projects/web/src/pages/extract/components/latex-renderer/index.tsx
...web/src/pages/extract/components/latex-renderer/index.tsx
+30
-0
projects/web/src/pages/extract/components/loading-icon/index.module.scss
...c/pages/extract/components/loading-icon/index.module.scss
+93
-0
projects/web/src/pages/extract/components/loading-icon/index.tsx
...s/web/src/pages/extract/components/loading-icon/index.tsx
+21
-0
projects/web/src/pages/extract/components/md-viewer/index.module.scss
.../src/pages/extract/components/md-viewer/index.module.scss
+1141
-0
projects/web/src/pages/extract/components/md-viewer/index.tsx
...ects/web/src/pages/extract/components/md-viewer/index.tsx
+268
-0
No files found.
projects/web/src/locale/side/en.ts
0 → 100644
View file @
791e653c
import
{
EXTRACTOR_TYPE_LIST
}
from
"
@/types/extract-task-type
"
;
import
odlLogo
from
"
@/assets/pdf/odl-logo.svg
"
;
import
labelLLMLogo
from
"
@/assets/pdf/label-llm.svg
"
;
import
labelULogo
from
"
@/assets/pdf/labelU.svg
"
;
export
default
{
"
extractor.side.tabList
"
:
[
{
label
:
"
PDF Extraction
"
,
type
:
EXTRACTOR_TYPE_LIST
.
pdf
,
},
// {
// label: "Formula Extraction",
// type: EXTRACTOR_TYPE_LIST.formula,
// },
],
"
extractor.side.guide_list
"
:
[
{
type
:
"
odl
"
,
icon
:
odlLogo
,
title
:
"
OpenDataLab
"
,
desc
:
"
Covers a huge amount of high-quality, multimodal datasets
"
,
goToText
:
"
Go Now
"
,
link
:
"
https://opendatalab.com
"
,
},
{
type
:
"
labelU
"
,
icon
:
labelULogo
,
title
:
"
Label U Labeling Tool
"
,
desc
:
"
Lightweight open source annotation tools
"
,
goToText
:
"
github
"
,
link
:
"
https://github.com/opendatalab/labelU
"
,
},
{
type
:
"
labelLLM
"
,
icon
:
labelLLMLogo
,
[
`zh-CN-title`
]:
"
LabelLLM Labeling Tool
"
,
title
:
"
LabelLLM Labeling Tool
"
,
desc
:
"
Specializing in dialogue annotation for large language models
"
,
goToText
:
"
github
"
,
link
:
"
https://github.com/opendatalab/LabelLLM
"
,
},
],
};
projects/web/src/locale/side/zh.ts
0 → 100644
View file @
791e653c
import
{
EXTRACTOR_TYPE_LIST
}
from
"
@/types/extract-task-type
"
;
import
odlLogo
from
"
@/assets/pdf/odl-logo.svg
"
;
import
labelLLMLogo
from
"
@/assets/pdf/label-llm.svg
"
;
import
labelULogo
from
"
@/assets/pdf/labelU.svg
"
;
export
default
{
"
extractor.side.tabList
"
:
[
{
label
:
"
PDF文档提取
"
,
type
:
EXTRACTOR_TYPE_LIST
.
pdf
,
},
// {
// label: "公式检测与识别",
// type: EXTRACTOR_TYPE_LIST.formula,
// },
],
"
extractor.side.guide_list
"
:
[
{
type
:
"
odl
"
,
icon
:
odlLogo
,
title
:
"
OpenDataLab
"
,
desc
:
"
涵盖海量优质、多模态数据集
"
,
goToText
:
"
立即前往
"
,
link
:
"
https://opendatalab.com
"
,
},
{
type
:
"
labelU
"
,
icon
:
labelULogo
,
title
:
"
Label U 标注工具
"
,
desc
:
"
轻量级开源标注工具
"
,
goToText
:
"
github
"
,
link
:
"
https://github.com/opendatalab/labelU
"
,
},
{
type
:
"
labelLLM
"
,
icon
:
labelLLMLogo
,
title
:
"
LabelLLM 标注工具
"
,
desc
:
"
专攻于大模型的对话标注
"
,
goToText
:
"
github
"
,
link
:
"
https://github.com/opendatalab/LabelLLM
"
,
},
],
};
projects/web/src/locale/zh.json
0 → 100644
View file @
791e653c
{
"extractor.common.upload"
:
"点击上传文件"
,
"extractor.common.try"
:
"试一试:"
,
"extractor.home"
:
"首页"
,
"extractor.button.download"
:
"下载"
,
"extractor.button.lineWrap"
:
"换行"
,
"extractor.button.fullScreen"
:
"全屏"
,
"extractor.button.exitFullScreen"
:
"退出全屏"
,
"extractor.button.showLayer"
:
"显示识别结果"
,
"extractor.button.hiddenLayer"
:
"隐藏识别结果"
,
"extractor.button.reUpload"
:
"重新上传"
,
"extractor.error"
:
"提取失败"
,
"extractor.common.loading"
:
"加载中"
,
"extractor.law"
:
"请确保您上传的文件合法合规,我们不承担因文件内容产生的法律责任。《信息保护政策》 《儿童信息保护政策》《服务协议》|© All Rights Reserved.沪ICP备2021009351号-21"
,
"extractor.failed"
:
"不可提取,暂无可展示数据"
,
"extractor.common.extracting"
:
"提取中,请稍等"
,
"extractor.common.extracting.queue"
:
"正在排队提取,当前排在第 {id} 位"
,
"extractor.common.pdf.demo1"
:
"示例1.pdf"
,
"extractor.common.pdf.demo2"
:
"示例2.pdf"
,
"extractor.common.formula.detect.demo1"
:
"公式检测1.jpg"
,
"extractor.common.formula.extract.demo1"
:
"公式识别1.jpg"
,
"extractor.common.login.desc"
:
"登录后可使用完整功能"
,
"extractor.markdown.preview"
:
"预览"
,
"extractor.markdown.code"
:
"代码"
,
"extractor.home.title"
:
"欢迎使用 Miner U"
,
"extractor.home.subTitle"
:
"上传文档,智能提取为 Markdown 格式"
,
"extractor.side.extractTask"
:
"提取任务"
,
"extractor.side.extractTask.title"
:
"请上传 5M 以内的 PDF 文档 ( 10 页以内)或 JPG/PNG 图片"
,
"extractor.pdf.title"
:
"PDF文档提取"
,
"extractor.pdf.subTitle"
:
"支持文本/扫描型 PDF 解析,识别各类版面元素并转换为多模态 Markdown 格式"
,
"extractor.common.pdf.upload.tip"
:
"请上传 PDF 文档"
,
"extractor.pdf.ocr"
:
"OCR 识别模式"
,
"extractor.pdf.ocr.popover"
:
" 默认将自动识别PDF类型(文本型、扫描型),并根据识别结果选择采用文本识别或者OCR识别方式。 如开启,将对所有类型PDF采用OCR识别方式。"
,
"extractor.formula.title"
:
"定位图片中的行内、行间公式,生成边界框"
,
"extractor.formula.title2"
:
"将图片中的数学公式识别为 laTex 格式,支持多行公式、手写公式识别"
,
"extractor.formula.upload.text"
:
"点击上传图片"
,
"extractor.formula.popover.extract"
:
"为获得最佳的公式识别效果,请上传清晰、无水印的包含数学公式的图片,如下图"
,
"extractor.formula.popover.detect"
:
"为获得最佳的公式识别效果,请裁剪图片,聚焦公式部分,上传清晰、无水印的数学公式图片,如下图"
,
"extractor.formula.upload.accept"
:
"请上传 5M 以内的JPG/PNG 图片"
,
"extractor.formula.upload.try"
:
"请上传包含数学公式的图片 示例:"
,
"extractor.guide.title"
:
"欢迎使用更多开源产品 🎉"
,
"extractor.queue"
:
"提取记录"
,
"extractor.queue.delete"
:
"确认删除此文件?"
,
"extractor.queue.extracting"
:
"提取中"
,
"extractor.feedback.title1"
:
"您对整体提取效果是否满意 ?"
,
"extractor.feedback.title3"
:
"期待您的建议,帮助我们更好的优化"
,
"extractor.feedback.up.title"
:
"您期望看到哪些改进?"
,
"extractor.feedback.down.title"
:
"您感到不满意的原因是?"
,
"extractor.feedback.up"
:
"满意"
,
"extractor.feedback.down"
:
"不满意"
,
"extractor.feedback.input.placeholder"
:
"请输入您的改进建议"
,
"extractor.feedback.input.submit"
:
"提交"
,
"extractor.feedback.success"
:
"感谢你的反馈"
,
"extractor.queue.delete.success"
:
"删除成功"
}
\ No newline at end of file
projects/web/src/main.tsx
0 → 100644
View file @
791e653c
import
{
StrictMode
}
from
'
react
'
import
{
createRoot
}
from
'
react-dom/client
'
import
App
from
'
./App.tsx
'
import
'
./index.css
'
createRoot
(
document
.
getElementById
(
'
root
'
)
!
).
render
(
<
StrictMode
>
<
App
/>
</
StrictMode
>,
)
projects/web/src/pages/extract-side/index.module.scss
0 → 100644
View file @
791e653c
@import
'@/styles/variable.scss'
;
.gradientBtn
{
width
:
179px
;
height
:
37px
;
border-radius
:
4px
;
font-size
:
14px
;
color
:
rgba
(
255
,
255
,
255
,
0
.95
);
// background: linear-gradient(110deg, #38A0FF -33.56%, #0D53DE 32.84%, #5246FF 102.05%);
background
:
#3477EB
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
cursor
:
pointer
;
&
:hover
{
background
:
#3477EB
;
}
}
.linearBlue
{
// TIP: 这里为啥用bg呢,因为ui稿给的参数是假的
background
:
url('@/assets/pdf/pdf-upload.png')
;
background-size
:
cover
;
}
.tryText
{
font-size
:
13px
;
line-height
:
20px
;
background
:
linear-gradient
(
107deg
,
#38A0FF
-24
.14%
,
#0D53DE
30
.09%
,
#5246FF
86
.61%
);
background-clip
:
text
;
-webkit-background-clip
:
text
;
-webkit-text-fill-color
:
transparent
;
}
.extractorContainer
{
min-width
:
$page-min-witch
;
}
\ No newline at end of file
projects/web/src/pages/extract-side/index.tsx
0 → 100644
View file @
791e653c
import
DarkLogo
from
"
@/assets/svg/logo.svg
"
;
import
{
useLocation
,
useNavigate
,
useParams
}
from
"
react-router-dom
"
;
import
commonStyles
from
"
./index.module.scss
"
;
import
{
EXTRACTOR_TYPE_LIST
}
from
"
@/types/extract-task-type
"
;
import
extractorPdfIcon
from
"
@/assets/pdf/extractor-pdf.svg
"
;
import
extractorTableIcon
from
"
@/assets/pdf/extractor-table.svg
"
;
import
extractorFormulaIcon
from
"
@/assets/pdf/extractor-formula.svg
"
;
import
{
useIntl
}
from
"
react-intl
"
;
import
cls
from
"
classnames
"
;
import
ExtractorGuide
from
"
@/pages/extract/components/extractor-guide
"
;
import
ExtractorQueue
from
"
@/pages/extract/components/extractor-queue
"
;
import
ExtractorLang
from
"
@/pages/extract/components/extractor-lang
"
;
interface
IExtractorSideProps
{
className
?:
string
;
}
interface
TabItem
{
label
:
string
;
type
:
string
;
}
export
const
ExtractorSide
=
({
className
=
""
}:
IExtractorSideProps
)
=>
{
const
navigate
=
useNavigate
();
const
params
=
useParams
();
const
location
=
useLocation
();
const
{
messages
}
=
useIntl
();
console
.
log
(
"
test-params-jobID
"
,
params
.
jobID
);
const
menuClass
=
"
px-2 py-2.5 mb-1 text-[0.875rem] text-[#121316]/[0.8] font-semibold rounded h-10 flex items-center cursor-pointer hover:bg-[#0d53de]/[0.05]
"
;
const
handleMenuClick
=
(
type
:
string
)
=>
{
navigate
(
`/OpenSourceTools/Extractor/
${
type
}
`
);
};
const
goToOpenSource
=
()
=>
{
navigate
(
"
/OpenSourceTools/Extractor/
"
);
};
const
tabList
=
(
messages
?.[
"
extractor.side.tabList
"
]
as
unknown
[]
as
TabItem
[])
||
[];
const
getIconStyle
=
(
type
:
string
)
=>
{
const
activeClassName
=
"
!bg-[#0d53de]/[0.05] !text-[#0D53DE]
"
;
const
path
=
location
.
pathname
;
const
regex
=
/
\/
Extractor
\/([^/]
+
)(\/
|$
)
/
;
const
match
=
params
?.
jobID
?
""
:
path
.
match
(
regex
)?.[
1
]
||
"
/
"
;
const
getIcon
=
()
=>
{
switch
(
type
)
{
case
EXTRACTOR_TYPE_LIST
.
pdf
:
return
extractorPdfIcon
;
case
EXTRACTOR_TYPE_LIST
.
table
:
return
extractorTableIcon
;
case
EXTRACTOR_TYPE_LIST
.
formula
:
return
extractorFormulaIcon
;
}
};
return
{
icon
:
getIcon
(),
tabClassName
:
match
===
type
?
activeClassName
:
""
,
};
};
return
(
<
div
className
=
{
cls
(
`w-[240px] min-w-[240px] h-full px-4 py-6 flex flex-col justify-start border-r-[1px] border-y-0 border-l-0 border-solid border-[#EBECF0] select-none`
,
commonStyles
.
linearBlue
,
className
)
}
>
<
div
className
=
{
""
}
>
<
div
className
=
"h-[2rem] mb-6 flex justify-between items-center"
>
<
img
className
=
"h-full cursor-pointer"
src
=
{
DarkLogo
}
alt
=
""
onClick
=
{
goToOpenSource
}
/>
<
ExtractorGuide
/>
</
div
>
{
/* tab-list */
}
<
div
className
=
"mb-2"
>
{
tabList
.
map
((
i
)
=>
(
<
div
key
=
{
i
.
type
}
className
=
{
cls
(
menuClass
,
getIconStyle
(
i
.
type
)?.
tabClassName
)
}
onClick
=
{
()
=>
handleMenuClick
(
i
.
type
)
}
>
<
img
src
=
{
getIconStyle
(
i
.
type
).
icon
}
className
=
"mr-2 w-6 h-6"
/>
{
i
.
label
}
</
div
>
))
}
</
div
>
</
div
>
<
div
className
=
"bg-[#0d53de]/[0.08] w-full h-[1px] mt-2 mb-4"
></
div
>
<
ExtractorQueue
className
=
"flex-1 overflow-y-auto mb-6"
/>
<
ExtractorLang
className
=
"absolute bottom-6"
/>
</
div
>
);
};
projects/web/src/pages/extract/components/extractor-guide/index.module.scss
0 → 100644
View file @
791e653c
.extractorGuide
{
:global
{
.ant-popover-content
,
.ant-popover-inner
{
border-radius
:
12px
!
important
;
overflow
:
hidden
;
box-shadow
:
0px
8px
26px
0px
rgba
(
0
,
0
,
0
,
0
.12
);
}
.ant-popover-inner-content
{
padding
:
24px
!
important
;
}
.ant-popover-arrow
{
display
:
none
!
important
;
}
}
}
projects/web/src/pages/extract/components/extractor-guide/index.tsx
0 → 100644
View file @
791e653c
import
{
Popover
}
from
"
antd
"
;
import
guideToolsSvg
from
"
@/assets/pdf/guideTools.svg
"
;
import
style
from
"
./index.module.scss
"
;
import
{
useIntl
}
from
"
react-intl
"
;
import
IconFont
from
"
@/components/icon-font
"
;
import
{
windowOpen
}
from
"
@/utils/windowOpen
"
;
interface
GuideItem
{
type
:
string
;
icon
:
string
;
"
zh-CN-title
"
:
string
;
title
:
string
;
desc
:
string
;
goToText
:
string
;
link
:
string
;
}
const
ExtractorGuide
=
()
=>
{
const
{
formatMessage
,
messages
}
=
useIntl
();
const
EXTRACTOR_GUIDE_ITEM_LIST
=
(
messages
?.[
"
extractor.side.guide_list
"
]
||
[])
as
unknown
as
GuideItem
[];
console
.
log
(
"
test-EXTRACTOR_GUIDE_ITEM_LIST
"
,
EXTRACTOR_GUIDE_ITEM_LIST
);
const
content
=
(
<
div
>
<
div
className
=
"text-[1.25rem] font-semibold mt-3 mb-2 ml-4"
>
{
formatMessage
({
id
:
"
extractor.guide.title
"
,
})
}
</
div
>
<
hgroup
>
{
EXTRACTOR_GUIDE_ITEM_LIST
?.
map
((
i
)
=>
{
return
(
<
div
key
=
{
i
.
type
}
className
=
"flex p-4 items-center cursor-pointer hover:bg-[#F4F5F9] rounded group h-[6.5rem]"
onClick
=
{
()
=>
windowOpen
(
i
.
link
)
}
>
<
img
src
=
{
i
.
icon
}
alt
=
""
className
=
"w-[1.5rem] h-[1.5rem] transition-all mr-[0.75rem]"
/>
<
div
className
=
""
>
<
div
className
=
"font-semibold transition-all text-[1rem]"
>
{
i
.
title
}
</
div
>
<
div
className
=
"text-base text-[13px] text-[#121316]/[0.6] transition-all "
>
{
i
.
desc
}
</
div
>
<
div
className
=
"h-0 mt-2 overflow-hidden !text-[13px] text-[#121316]/[0.8] transition-all group-hover:h-auto"
>
{
i
.
goToText
}
<
IconFont
type
=
"icon-ArrowRightOutlined"
className
=
"ml-1"
/>
</
div
>
</
div
>
</
div
>
);
})
}
</
hgroup
>
</
div
>
);
return
(
<
Popover
overlayClassName
=
{
style
.
extractorGuide
}
content
=
{
content
}
showArrow
=
{
false
}
placement
=
"right"
>
<
img
className
=
"w-[1.32rem] h-[1.32rem] p-0.5 hover:rotate-45 transition-all cursor-pointer rounded"
src
=
{
guideToolsSvg
}
alt
=
"guideToolsSvg"
/>
</
Popover
>
);
};
export
default
ExtractorGuide
;
projects/web/src/pages/extract/components/extractor-lang/index.tsx
0 → 100644
View file @
791e653c
import
LangChangeIcon
from
"
@/assets/pdf/lang-change.svg
"
;
import
{
useLanguageStore
}
from
"
@/store/languageStore
"
;
import
cls
from
"
classnames
"
;
interface
ExtractorLangProps
{
className
?:
string
;
}
const
ExtractorLang
:
React
.
FC
<
ExtractorLangProps
>
=
({
className
})
=>
{
const
{
toggleLanguage
}
=
useLanguageStore
();
const
changeLang
=
()
=>
{
toggleLanguage
?.();
};
return
(
<>
<
img
onClick
=
{
()
=>
changeLang
()
}
src
=
{
LangChangeIcon
}
alt
=
"LangChangeIcon"
className
=
{
cls
(
"
w-[1.5rem] h-[1.5rem] cursor-pointer object-cover hover:bg-[#0D53DE]/[0.1] rounded cursor-pointer
"
,
className
)
}
/>
</>
);
};
export
default
ExtractorLang
;
projects/web/src/pages/extract/components/extractor-queue/index.tsx
0 → 100644
View file @
791e653c
import
IconFont
from
"
@/components/icon-font
"
;
import
{
useIntl
}
from
"
react-intl
"
;
import
extractorQueueSvg
from
"
@/assets/pdf/extractor-queue.svg
"
;
import
{
useNavigate
,
useParams
}
from
"
react-router-dom
"
;
import
{
EXTRACTOR_TYPE_LIST
,
ExtractTaskType
,
}
from
"
@/types/extract-task-type
"
;
import
cls
from
"
classnames
"
;
import
{
useLatest
,
useRequest
}
from
"
ahooks
"
;
import
{
deleteExtractJob
,
getExtractorHistory
}
from
"
@/api/extract
"
;
import
{
message
,
Popconfirm
,
Tooltip
}
from
"
antd
"
;
import
{
useEffect
}
from
"
react
"
;
import
{
ADD_TASK_LIST
,
UPDATE_TASK_LIST
}
from
"
@/constant/event
"
;
import
{
findIndex
}
from
"
lodash
"
;
import
{
TextTooltip
}
from
"
@/components/text-tooltip
"
;
interface
ExtractorQueueProps
{
className
?:
string
;
}
const
ExtractorQueue
:
React
.
FC
<
ExtractorQueueProps
>
=
({
className
})
=>
{
const
{
formatMessage
,
locale
}
=
useIntl
();
const
navigate
=
useNavigate
();
const
params
=
useParams
();
console
.
log
(
"
test-params
"
,
params
);
const
{
data
:
taskList
,
mutate
}
=
useRequest
(()
=>
{
return
getExtractorHistory
({
pageNo
:
1
,
pageSize
:
100
,
}).
then
((
res
)
=>
{
return
res
?.
list
?.
filter
((
i
)
=>
!!
i
.
id
&&
!!
i
.
type
)
||
[];
});
});
let
timeout
:
NodeJS
.
Timeout
|
null
=
null
;
const
activeClassName
=
"
!bg-[#0d53de]/[0.05] !text-[#0D53DE]
"
;
const
handleExtractor
=
(
originType
:
ExtractTaskType
,
id
:
string
)
=>
{
const
type
=
originType
?.
split
(
"
-
"
)[
0
];
const
detailType
=
originType
?.
split
(
"
-
"
)[
1
];
if
(
type
===
EXTRACTOR_TYPE_LIST
.
formula
.
toLowerCase
())
{
navigate
(
`/OpenSourceTools/Extractor/formula/
${
id
}
?type=
${
detailType
}
`
);
}
else
if
(
type
===
EXTRACTOR_TYPE_LIST
.
pdf
.
toLowerCase
())
{
navigate
(
`/OpenSourceTools/Extractor/PDF/
${
id
}
`
);
}
else
if
(
type
===
EXTRACTOR_TYPE_LIST
.
table
.
toLocaleLowerCase
())
{
navigate
(
`/OpenSourceTools/Extractor/table/
${
id
}
`
);
}
return
;
};
const
cancel
=
(
e
?:
React
.
MouseEvent
<
HTMLElement
>
)
=>
{
e
?.
stopPropagation
();
e
?.
preventDefault
();
};
const
confirm
=
(
id
:
string
)
=>
{
const
deleteIndex
=
findIndex
(
taskList
,
(
i
)
=>
i
.
id
===
id
);
const
nextJob
=
taskList
?.[
deleteIndex
+
1
]
?
taskList
?.[
deleteIndex
+
1
]
:
taskList
?.[
deleteIndex
-
1
];
mutate
(
taskList
?.
filter
((
i
)
=>
i
.
id
!==
id
));
console
.
log
(
"
test-next-job
"
,
nextJob
);
deleteExtractJob
(
id
).
then
(()
=>
{
console
.
log
(
"
test-delete-job
"
,
id
);
message
.
success
(
formatMessage
({
id
:
"
extractor.queue.delete.success
"
}));
});
if
(
timeout
)
{
clearTimeout
(
timeout
);
}
timeout
=
setTimeout
(()
=>
{
handleExtractor
(
nextJob
?.
type
,
nextJob
?.
id
);
},
10
);
};
const
taskListRef
=
useLatest
(
taskList
);
const
handleAddList
=
({
detail
}:
CustomEvent
)
=>
{
const
taskData
=
detail
as
any
;
mutate
(
[
{
fileName
:
taskData
?.
fileName
,
id
:
taskData
?.
id
,
type
:
taskData
?.
type
,
state
:
taskData
?.
state
,
// 提取状态
},
].
concat
(
taskListRef
?.
current
)
);
};
useEffect
(()
=>
{
const
handleUpdateList
=
({
detail
}:
CustomEvent
)
=>
{
const
taskData
=
detail
as
any
;
taskListRef
?.
current
?.
forEach
((
i
)
=>
{
if
(
i
.
id
===
taskData
?.
id
)
{
i
.
state
=
taskData
?.
state
||
taskData
?.
state
;
}
});
mutate
(
taskListRef
?.
current
);
};
document
.
addEventListener
(
UPDATE_TASK_LIST
,
handleUpdateList
as
EventListener
);
document
.
addEventListener
(
ADD_TASK_LIST
,
handleAddList
as
EventListener
);
return
()
=>
{
document
.
removeEventListener
(
UPDATE_TASK_LIST
,
handleUpdateList
as
EventListener
);
document
.
removeEventListener
(
ADD_TASK_LIST
,
handleAddList
as
EventListener
);
};
},
[]);
useEffect
(()
=>
{
mutate
(
taskListRef
?.
current
);
},
[
locale
]);
console
.
log
(
"
test-dd
"
,
params
);
return
(
<
div
className
=
{
cls
(
"
w-full flex flex-col mb-3
"
,
className
)
}
>
<
header
className
=
"flex items-center px-2 py-[0.625rem] text-[#121316]/[0.8] text-[0.875rem] font-semibold"
>
<
img
src
=
{
extractorQueueSvg
}
className
=
"w-6 h-6 mr-2 "
alt
=
"extractorQueueSvg"
/>
{
formatMessage
({
id
:
"
extractor.queue
"
,
})
}
</
header
>
<
hgroup
className
=
"overflow-auto flex-1 scrollbar-thin"
>
{
taskList
?.
map
((
i
,
index
)
=>
{
return
(
<
div
className
=
{
cls
(
"
group h-[2.5rem] flex items-center px-4 py-2.5 mb-1 text-[#121316]/[0.8] pl-10 text-sm rounded h-10 flex items-center cursor-pointer hover:bg-[#0d53de]/[0.05]
"
,
params
?.
jobID
===
String
(
i
?.
id
)
&&
activeClassName
)
}
key
=
{
i
?.
fileName
+
index
+
i
?.
id
}
onClick
=
{
()
=>
handleExtractor
(
i
.
type
as
any
,
i
.
id
)
}
>
<
span
className
=
"truncate mr-2 max-w-[calc(100%-2rem)]"
>
<
TextTooltip
trigger
=
"hover"
str
=
{
i
?.
fileName
}
/>
</
span
>
<>
{
i
?.
state
===
"
failed
"
&&
(
<
Tooltip
title
=
{
formatMessage
({
id
:
"
extractor.error
"
,
})
}
>
<
IconFont
type
=
{
"
icon-attentionFilled
"
}
className
=
"text-[#FF8800] mr-1"
/>
</
Tooltip
>
)
}
<
Popconfirm
title
=
{
formatMessage
({
id
:
"
extractor.queue.delete
"
})
}
description
=
{
<
div
className
=
"my-4"
></
div
>
}
onConfirm
=
{
(
e
)
=>
{
e
?.
stopPropagation
();
e
?.
preventDefault
();
confirm
(
i
.
id
);
}
}
onCancel
=
{
cancel
}
okText
=
{
formatMessage
({
id
:
"
common.confirm
"
})
}
cancelText
=
{
formatMessage
({
id
:
"
common.cancel
"
})
}
okButtonProps
=
{
{
style
:
{
backgroundColor
:
"
#F5483B
"
,
},
}
}
>
<
IconFont
onClick
=
{
(
e
)
=>
{
e
.
stopPropagation
();
e
.
preventDefault
();
}
}
type
=
"icon-shanchu"
className
=
"hidden ml-auto text-[1rem] text-[#121316]/[0.8] hover:text-[#0D53DE] group-hover:block"
/>
</
Popconfirm
>
</>
</
div
>
);
})
}
</
hgroup
>
</
div
>
);
};
export
default
ExtractorQueue
;
projects/web/src/pages/extract/components/extractor-repo/index.module.scss
0 → 100644
View file @
791e653c
.githubBtn
{
position
:
relative
;
width
:
100%
;
cursor
:
pointer
;
height
:
2
.5rem
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
margin-bottom
:
1
.25rem
;
overflow
:
hidden
;
border-radius
:
8px
;
cursor
:
pointer
;
filter
:
blur
(
0px
);
z-index
:
0
;
&
::before
{
width
:
100%
;
height
:
100%
;
display
:
block
;
content
:
""
;
position
:
absolute
;
top
:
0
;
left
:
0
;
background
:
linear-gradient
(
to
bottom
,
rgba
(
185
,
214
,
246
,
1
)
-100%
,
rgba
(
244
,
247
,
254
,
)
100%
);
z-index
:
0
;
}
&
>
span
{
border-radius
:
7px
;
display
:inline-flex
;
width
:
calc
(
100%
-
2px
);
height
:
calc
(
100%
-
2px
);
background
:
linear-gradient
(
180deg
,
#5C93FF
1
F
-160
.94%
,
rgba
(
255
,
255
,
255
,
1
)
80%
);
z-index
:
1
;
filter
:
blur
(
0px
);
justify-content
:
center
;
align-items
:
center
;
font-size
:
16px
;
&
:hover
{
background
:
linear-gradient
(
180deg
,
#5C93FF
1
F
-60
.94%
,
rgba
(
255
,
255
,
255
,
1
)
80%
);
filter
:
blur
(
0px
);
}
span
:nth-child
(
3
)
{
color
:
var
(
--
80
-
text-4
,
rgba
(
18
,
19
,
22
,
0
.80
));
-webkit-background-clip
:
text
;
background-clip
:
text
;
}
}
}
.githubText
{
/* 正文/加粗text-1-semibold */
font-family
:
"PingFang SC"
;
font-size
:
14px
;
font-style
:
normal
;
font-weight
:
600
;
line-height
:
21px
;
/* 150% */
color
:
#121316
;
}
projects/web/src/pages/extract/components/extractor-repo/index.tsx
0 → 100644
View file @
791e653c
import
githubSvg
from
"
@/assets/pdf/github.svg
"
;
import
{
windowOpen
}
from
"
@/utils/windowOpen
"
;
import
styles
from
"
./index.module.scss
"
;
import
cls
from
"
classnames
"
;
const
ExtractorRepo
=
()
=>
{
return
(
<
div
className
=
{
cls
(
styles
.
githubBtn
)
}
onClick
=
{
()
=>
windowOpen
(
"
https://github.com/opendatalab/MinerU
"
,
"
_blank
"
)
}
>
<
span
className
=
"text-sm "
>
<
img
src
=
{
githubSvg
}
className
=
"mr-2"
/>
<
span
className
=
"!text-[14px] ml-[0.5rem]"
>
🎉
</
span
>
</
span
>
</
div
>
);
};
export
default
ExtractorRepo
;
projects/web/src/pages/extract/components/iframe-loading/index.tsx
0 → 100644
View file @
791e653c
import
LoadingAnimation
from
"
@/components/loading-animation
"
;
import
{
ExclamationCircleFilled
}
from
"
@ant-design/icons
"
;
import
cls
from
"
classnames
"
;
export
const
IframeLoading
=
({
filename
,
type
,
text
,
errorElement
,
classNameTitle
=
""
,
showHeader
,
}:
{
filename
?:
string
;
type
:
"
loading
"
|
"
error
"
;
text
?:
string
;
errorElement
?:
React
.
ReactElement
;
classNameTitle
?:
string
;
showHeader
?:
boolean
;
})
=>
{
return
(
<
div
className
=
"flex flex-col h-full text-sm text-[#121316]/[0.8] whitespace-nowrap "
>
{
showHeader
&&
(
<
div
className
=
{
cls
(
"
h-[47px] border-0 border-solid border-b-[1px] border-[#EBECF0] w-full pl-[24px]
"
,
classNameTitle
)
}
>
{
filename
}
</
div
>
)
}
<
div
className
=
"flex-1 flex justify-center items-center"
>
{
type
===
"
error
"
?
(
errorElement
?
(
errorElement
)
:
(
<>
<
ExclamationCircleFilled
style
=
{
{
color
:
"
#FF8800
"
}
}
rotate
=
{
180
}
/>
<
span
className
=
"ml-2.5"
>
上传失败,请
</
span
>
<
span
className
=
"text-[#0D53DE] ml-1 cursor-pointer"
>
重新上传
</
span
>
</>
)
)
:
(
<>
<
LoadingAnimation
/>
<
span
className
=
"ml-2.5"
>
{
text
||
"
PDF 上传中,请稍等...
"
}
</
span
>
</>
)
}
</
div
>
</
div
>
);
};
projects/web/src/pages/extract/components/image-layer-viwer/index.tsx
0 → 100644
View file @
791e653c
import
React
,
{
useEffect
,
useRef
,
useState
,
useMemo
,
forwardRef
,
useImperativeHandle
,
useCallback
}
from
'
react
'
;
import
cls
from
'
classnames
'
;
import
{
isObjEqual
}
from
'
@/utils/render
'
;
import
{
useSize
}
from
'
ahooks
'
;
interface
IImageLayersViewerProps
{
imageUrl
:
string
;
imageWidth
:
number
;
imageHeight
:
number
;
layout
:
Array
<
{
category_id
:
number
;
poly
:
number
[];
score
:
number
;
latex
?:
string
;
}
>
;
layerVisible
?:
boolean
;
disableZoom
?:
boolean
;
className
?:
string
;
onChange
?:
(
data
:
{
scale
:
number
})
=>
void
;
}
export
interface
ImageLayerViewerRef
{
containerRef
:
HTMLDivElement
|
null
;
zoomIn
:
()
=>
void
;
zoomOut
:
()
=>
void
;
scale
:
number
;
updateScaleAndPosition
:
()
=>
void
;
}
const
ImageLayerViewer
=
forwardRef
<
ImageLayerViewerRef
,
IImageLayersViewerProps
>
(
({
imageUrl
,
imageHeight
,
imageWidth
,
onChange
,
layout
,
disableZoom
,
className
=
''
,
layerVisible
=
true
},
ref
)
=>
{
const
containerRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
imageCanvasRef
=
useRef
<
HTMLCanvasElement
>
(
null
);
const
overlayCanvasRef
=
useRef
<
HTMLCanvasElement
>
(
null
);
const
rafRef
=
useRef
<
number
|
null
>
(
null
);
const
containerSize
=
useSize
(
containerRef
);
const
[
scale
,
setScale
]
=
useState
(
1
);
const
[
position
,
setPosition
]
=
useState
({
x
:
0
,
y
:
0
});
const
[
padding
,
setPadding
]
=
useState
({
left
:
0
,
top
:
0
});
const
minZoom
=
0.1
;
const
maxZoom
=
3
;
const
zoomSensitivity
=
0.001
;
const
zoomStep
=
0.1
;
const
dpr
=
useMemo
(()
=>
window
.
devicePixelRatio
||
1
,
[]);
const
image
=
useMemo
(()
=>
{
const
img
=
new
Image
();
img
.
src
=
imageUrl
;
return
img
;
},
[
imageUrl
]);
const
calculateInitialScaleAndPosition
=
useCallback
(()
=>
{
if
(
!
containerRef
.
current
)
return
{
initialScale
:
1
,
initialPosition
:
{
x
:
0
,
y
:
0
}
};
const
containerWidth
=
containerRef
.
current
.
clientWidth
;
const
containerHeight
=
containerRef
.
current
.
clientHeight
;
const
scaleX
=
containerWidth
/
imageWidth
;
const
scaleY
=
containerHeight
/
imageHeight
;
const
initialScale
=
Math
.
min
(
scaleX
,
scaleY
,
1
);
// Ensure it doesn't scale up initially
const
scaledWidth
=
imageWidth
*
initialScale
;
const
scaledHeight
=
imageHeight
*
initialScale
;
const
initialPosition
=
{
x
:
(
containerWidth
-
scaledWidth
)
/
2
,
y
:
(
containerHeight
-
scaledHeight
)
/
2
};
return
{
initialScale
,
initialPosition
};
},
[
imageWidth
,
imageHeight
]);
const
updateScaleAndPosition
=
useCallback
(()
=>
{
const
{
initialScale
,
initialPosition
}
=
calculateInitialScaleAndPosition
();
setScale
(
initialScale
);
setPosition
(
initialPosition
);
setPadding
({
left
:
0
,
top
:
0
});
},
[
calculateInitialScaleAndPosition
]);
useEffect
(()
=>
{
updateScaleAndPosition
();
},
[
imageWidth
,
imageHeight
]);
const
drawImage
=
useCallback
(()
=>
{
const
ctx
=
imageCanvasRef
.
current
?.
getContext
(
'
2d
'
);
if
(
!
ctx
||
!
image
.
complete
)
return
;
const
scaledWidth
=
imageWidth
*
scale
;
const
scaledHeight
=
imageHeight
*
scale
;
ctx
.
canvas
.
width
=
scaledWidth
*
dpr
;
ctx
.
canvas
.
height
=
scaledHeight
*
dpr
;
ctx
.
canvas
.
style
.
width
=
`
${
scaledWidth
}
px`
;
ctx
.
canvas
.
style
.
height
=
`
${
scaledHeight
}
px`
;
ctx
.
scale
(
dpr
,
dpr
);
ctx
.
clearRect
(
0
,
0
,
scaledWidth
,
scaledHeight
);
ctx
.
drawImage
(
image
,
0
,
0
,
scaledWidth
,
scaledHeight
);
},
[
image
,
imageWidth
,
imageHeight
,
scale
,
dpr
]);
const
drawLayout
=
useCallback
(()
=>
{
const
ctx
=
overlayCanvasRef
.
current
?.
getContext
(
'
2d
'
);
if
(
!
ctx
)
return
;
const
scaledWidth
=
imageWidth
*
scale
;
const
scaledHeight
=
imageHeight
*
scale
;
ctx
.
canvas
.
width
=
scaledWidth
*
dpr
;
ctx
.
canvas
.
height
=
scaledHeight
*
dpr
;
ctx
.
canvas
.
style
.
width
=
`
${
scaledWidth
}
px`
;
ctx
.
canvas
.
style
.
height
=
`
${
scaledHeight
}
px`
;
ctx
.
scale
(
dpr
,
dpr
);
ctx
.
clearRect
(
0
,
0
,
scaledWidth
,
scaledHeight
);
layout
?.
forEach
((
item
)
=>
{
const
[
x1
,
y1
,
x2
,
y2
,
x3
,
y3
,
x4
,
y4
]
=
item
.
poly
.
map
((
coord
)
=>
coord
*
scale
);
switch
(
item
.
category_id
)
{
case
9
:
ctx
.
fillStyle
=
'
rgba(230, 113, 230, 0.4)
'
;
ctx
.
strokeStyle
=
'
rgba(230, 113, 230, 1)
'
;
break
;
case
8
:
ctx
.
fillStyle
=
'
rgba(240, 240, 124, 0.4)
'
;
ctx
.
strokeStyle
=
'
rgba(240, 240, 124, 1)
'
;
break
;
case
13
:
ctx
.
fillStyle
=
'
rgba(150, 232, 172, 0.4)
'
;
ctx
.
strokeStyle
=
'
rgba(150, 232, 172, 1)
'
;
break
;
case
14
:
ctx
.
fillStyle
=
'
rgba(230, 122, 171, 0.4)
'
;
ctx
.
strokeStyle
=
'
rgba(230, 122, 171, 1)
'
;
break
;
default
:
ctx
.
fillStyle
=
'
transparent
'
;
ctx
.
strokeStyle
=
'
transparent
'
;
}
ctx
.
beginPath
();
ctx
.
moveTo
(
x1
,
y1
);
ctx
.
lineTo
(
x2
,
y2
);
ctx
.
lineTo
(
x3
,
y3
);
ctx
.
lineTo
(
x4
,
y4
);
ctx
.
closePath
();
ctx
.
fill
();
ctx
.
stroke
();
});
},
[
layout
,
scale
,
dpr
]);
const
updateScale
=
useCallback
(
(
newScale
:
number
,
clientX
:
number
,
clientY
:
number
)
=>
{
if
(
containerRef
.
current
)
{
const
rect
=
containerRef
.
current
.
getBoundingClientRect
();
const
containerWidth
=
rect
.
width
;
const
containerHeight
=
rect
.
height
;
const
x
=
clientX
-
rect
.
left
;
const
y
=
clientY
-
rect
.
top
;
const
prevScaledWidth
=
imageWidth
*
scale
;
const
prevScaledHeight
=
imageHeight
*
scale
;
const
newScaledWidth
=
imageWidth
*
newScale
;
const
newScaledHeight
=
imageHeight
*
newScale
;
let
newPosition
=
{
x
:
position
.
x
-
((
x
-
position
.
x
)
*
(
newScaledWidth
-
prevScaledWidth
))
/
prevScaledWidth
,
y
:
position
.
y
-
((
y
-
position
.
y
)
*
(
newScaledHeight
-
prevScaledHeight
))
/
prevScaledHeight
};
// Center the image if it's smaller than the container
if
(
newScaledWidth
<
containerWidth
)
{
newPosition
.
x
=
(
containerWidth
-
newScaledWidth
)
/
2
;
}
if
(
newScaledHeight
<
containerHeight
)
{
newPosition
.
y
=
(
containerHeight
-
newScaledHeight
)
/
2
;
}
setScale
(
newScale
);
setPosition
(
newPosition
);
// Calculate new padding
const
newPadding
=
{
left
:
Math
.
max
(
0
,
-
newPosition
.
x
),
top
:
Math
.
max
(
0
,
-
newPosition
.
y
)
};
setPadding
(
newPadding
);
}
},
[
scale
,
position
,
imageWidth
,
imageHeight
]
);
const
handleZoom
=
useCallback
(
(
delta
:
number
,
clientX
:
number
,
clientY
:
number
)
=>
{
const
newScale
=
scale
*
Math
.
exp
(
-
delta
*
zoomSensitivity
);
const
boundedNewScale
=
Math
.
max
(
minZoom
,
Math
.
min
(
newScale
,
maxZoom
));
if
(
rafRef
.
current
!==
null
)
{
cancelAnimationFrame
(
rafRef
.
current
);
}
rafRef
.
current
=
requestAnimationFrame
(()
=>
{
updateScale
(
boundedNewScale
,
clientX
,
clientY
);
});
},
[
scale
,
updateScale
]
);
const
handleCenterZoom
=
useCallback
(
(
zoomIn
:
boolean
)
=>
{
const
newScale
=
zoomIn
?
scale
*
(
1
+
zoomStep
)
:
scale
/
(
1
+
zoomStep
);
const
boundedNewScale
=
Math
.
max
(
minZoom
,
Math
.
min
(
newScale
,
maxZoom
));
if
(
containerRef
.
current
)
{
const
rect
=
containerRef
.
current
.
getBoundingClientRect
();
const
centerX
=
rect
.
width
/
2
;
const
centerY
=
rect
.
height
/
2
;
updateScale
(
boundedNewScale
,
centerX
,
centerY
);
}
},
[
scale
,
updateScale
]
);
const
zoomIn
=
useCallback
(()
=>
{
handleCenterZoom
(
true
);
},
[
handleCenterZoom
]);
const
zoomOut
=
useCallback
(()
=>
{
handleCenterZoom
(
false
);
},
[
handleCenterZoom
]);
useImperativeHandle
(
ref
,
()
=>
({
containerRef
:
containerRef
.
current
,
zoomIn
,
zoomOut
,
scale
,
updateScaleAndPosition
}),
[
zoomIn
,
zoomOut
,
scale
]
);
useEffect
(()
=>
{
const
container
=
containerRef
.
current
;
if
(
!
container
)
return
;
const
handleWheel
=
(
e
:
WheelEvent
)
=>
{
if
(
e
.
ctrlKey
||
e
.
metaKey
)
{
e
.
preventDefault
();
handleZoom
(
e
.
deltaY
*
4.8
,
e
.
clientX
,
e
.
clientY
);
}
};
container
.
addEventListener
(
'
wheel
'
,
handleWheel
,
{
passive
:
false
});
return
()
=>
{
container
.
removeEventListener
(
'
wheel
'
,
handleWheel
);
};
},
[
handleZoom
]);
useEffect
(()
=>
{
if
(
containerRef
?.
current
)
{
containerRef
.
current
?.
scrollTo
({
left
:
padding
.
left
,
top
:
padding
.
top
});
}
},
[
padding
]);
useEffect
(()
=>
{
const
draw
=
()
=>
{
drawImage
();
drawLayout
();
};
if
(
image
.
complete
)
{
draw
();
}
else
{
image
.
onload
=
draw
;
}
},
[
image
,
drawImage
,
drawLayout
]);
useEffect
(()
=>
{
if
(
overlayCanvasRef
.
current
)
{
overlayCanvasRef
.
current
.
style
.
opacity
=
layerVisible
?
'
1
'
:
'
0
'
;
}
},
[
layerVisible
]);
useEffect
(()
=>
{
onChange
?.({
scale
});
},
[
scale
]);
console
.
log
(
'
test-render
'
);
return
(
<
div
className
=
{
cls
(
className
,
'
w-full h-full overflow-auto scrollbar-thin relative
'
)
}
ref
=
{
containerRef
}
>
<
div
style
=
{
{
paddingLeft
:
`
${
padding
.
left
}
px`
,
paddingTop
:
`
${
padding
.
top
}
px`
}
}
>
<
div
className
=
"absolute"
style
=
{
{
width
:
`
${
imageWidth
*
scale
}
px`
,
height
:
`
${
imageHeight
*
scale
}
px`
,
transform
:
`translate(
${
position
.
x
}
px,
${
position
.
y
}
px)`
}
}
>
<
canvas
ref
=
{
imageCanvasRef
}
style
=
{
{
width
:
`
${
imageWidth
*
scale
}
px`
,
height
:
`
${
imageHeight
*
scale
}
px`
}
}
/>
<
canvas
ref
=
{
overlayCanvasRef
}
className
=
"absolute top-0 left-0"
style
=
{
{
width
:
`
${
imageWidth
*
scale
}
px`
,
height
:
`
${
imageHeight
*
scale
}
px`
}
}
/>
</
div
>
</
div
>
</
div
>
);
}
);
export
default
React
.
memo
(
ImageLayerViewer
,
isObjEqual
);
projects/web/src/pages/extract/components/latex-renderer/index.module.scss
0 → 100644
View file @
791e653c
// @import '../../../../global.scss';
.customStyle
{
padding
:
2rem
;
padding-top
:
0rem
;
&
>
div
{
max-width
:
100%
;
max-height
:
100%
;
// @include scrollBar(red);
}
.katex-display
{
margin-top
:
0px
!
important
;
// @include scrollBar(red);
}
}
projects/web/src/pages/extract/components/latex-renderer/index.tsx
0 → 100644
View file @
791e653c
import
React
from
'
react
'
;
import
'
katex/dist/katex.min.css
'
;
import
{
BlockMath
}
from
'
react-katex
'
;
import
style
from
'
./index.module.scss
'
;
import
classNames
from
'
classnames
'
;
interface
LatexRendererProps
{
formula
:
string
;
className
?:
string
;
'
aria-label
'
?:
string
;
title
?:
string
;
}
function
LatexRenderer
({
formula
,
className
=
''
,
'
aria-label
'
:
ariaLabel
,
title
}:
LatexRendererProps
)
{
try
{
return
(
<
div
className
=
{
`
${
className
}
max-w-[100%] max-h-[100%] scrollbar-thin
${
style
.
customStyle
}
`
}
aria
-
label
=
{
ariaLabel
}
>
<
BlockMath
math
=
{
formula
}
className
=
"scrollbar-thin"
/>
</
div
>
);
}
catch
(
error
)
{
console
.
error
(
'
Error rendering Latex:
'
,
error
);
return
<
div
>
Unable to render Latex formula.
</
div
>;
}
}
export
default
LatexRenderer
;
projects/web/src/pages/extract/components/loading-icon/index.module.scss
0 → 100644
View file @
791e653c
$circle-width
:
16px
;
.container
{
position
:
relative
;
width
:
4
*
$circle-width
;
height
:
20px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
}
.dot-pulse
{
position
:
relative
;
left
:
-9999px
;
width
:
$circle-width
;
height
:
$circle-width
;
border-radius
:
50%
;
background-color
:
var
(
--
color
);
color
:
var
(
--
color
);
box-shadow
:
9999px
0
0
-3px
;
animation
:
dot-pulse
1
.5s
infinite
linear
;
animation-delay
:
0
.25s
;
}
.
dot-pulse
:
:
before
,
.
dot-pulse
::
after
{
content
:
''
;
display
:
inline-block
;
position
:
absolute
;
top
:
0
;
width
:
$circle-width
;
height
:
$circle-width
;
border-radius
:
50%
;
background-color
:
var
(
--
color
);
color
:
var
(
--
color
);
}
.
dot-pulse
:
:
before
{
box-shadow
:
9974px
0
0
-3px
;
animation
:
dot-pulse-before
1
.5s
infinite
linear
;
animation-delay
:
0s
;
}
.
dot-pulse
:
:
after
{
box-shadow
:
10024px
0
0
-3px
;
animation
:
dot-pulse-after
1
.5s
infinite
linear
;
animation-delay
:
0
.5s
;
}
@keyframes
dot-pulse-before
{
0
%
{
box-shadow
:
9974px
0
0
-3px
;
}
30
%
{
box-shadow
:
9974px
0
0
2px
;
}
60
%
,
100
%
{
box-shadow
:
9974px
0
0
-3px
;
}
}
@keyframes
dot-pulse
{
0
%
{
box-shadow
:
9999px
0
0
-3px
;
}
30
%
{
box-shadow
:
9999px
0
0
3px
;
}
60
%
,
100
%
{
box-shadow
:
9999px
0
0
-3px
;
}
}
@keyframes
dot-pulse-after
{
0
%
{
box-shadow
:
10024px
0
0
-3px
;
}
30
%
{
box-shadow
:
10024px
0
0
2px
;
}
60
%
,
100
%
{
box-shadow
:
10024px
0
0
-3px
;
}
}
projects/web/src/pages/extract/components/loading-icon/index.tsx
0 → 100644
View file @
791e653c
import
classNames
from
"
classnames
"
;
import
style
from
"
./index.module.scss
"
;
const
LoadingIcon
=
({
color
,
className
,
}:
{
color
:
string
;
className
?:
string
;
})
=>
{
return
(
<
div
className
=
{
classNames
(
style
.
container
,
className
)
}
>
<
div
className
=
{
style
.
dotPulse
}
style
=
{
{
"
--color
"
:
color
||
"
grey
"
}
as
any
}
></
div
>
</
div
>
);
};
export
default
LoadingIcon
;
projects/web/src/pages/extract/components/md-viewer/index.module.scss
0 → 100644
View file @
791e653c
/*light*/
// 自定义滚动跳
.scrollBar
{
// 火狐
scrollbar-color
:
#EBECF0
transparent
;
scrollbar-width
:
thin
;
// 定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸
&
::-webkit-scrollbar
{
width
:
4px
;
height
:
6px
;
background-color
:
transparent
;
}
// 定义滚动条轨道 内阴影+圆角
&
::-webkit-scrollbar-track
{
background-color
:
#fff
;
border-radius
:
10px
;
box-shadow
:
transparent
;
}
// 定义滑块 内阴影+圆角
&
::-webkit-scrollbar-thumb
{
background
:
#EBECF0
;
border-radius
:
10px
;
box-shadow
:
inset
0
0
6px
rgba
(
0
,
0
,
0
,
0
.6
);
}
}
.mdViewerWrap
{
font-size
:
10px
;
}
.mdViewerWrap
{
-ms-text-size-adjust
:
100%
;
-webkit-text-size-adjust
:
100%
;
margin
:
0
;
color
:
#1f2328
;
background-color
:
#ffffff
;
font-family
:
-
apple-system
,
BlinkMacSystemFont
,
"Segoe UI"
,
"Noto Sans"
,
Helvetica
,
Arial
,
sans-serif
,
"Apple Color Emoji"
,
"Segoe UI Emoji"
;
font-size
:
16px
;
line-height
:
1
.5
;
word-wrap
:
break-word
;
scroll-behavior
:
auto
;
}
.mdViewerWrap
.octicon
{
display
:
inline-block
;
fill
:
currentColor
;
vertical-align
:
text-bottom
;
}
.mdViewerWrap
h1
:hover
.anchor
.octicon-link
:before
,
.mdViewerWrap
h2
:hover
.anchor
.octicon-link
:before
,
.mdViewerWrap
h3
:hover
.anchor
.octicon-link
:before
,
.mdViewerWrap
h4
:hover
.anchor
.octicon-link
:before
,
.mdViewerWrap
h5
:hover
.anchor
.octicon-link
:before
,
.mdViewerWrap
h6
:hover
.anchor
.octicon-link
:before
{
width
:
16px
;
height
:
16px
;
content
:
" "
;
display
:
inline-block
;
background-color
:
currentColor
;
-webkit-mask-image
:
url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg>")
;
mask-image
:
url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg>")
;
}
.mdViewerWrap
details
,
.mdViewerWrap
figcaption
,
.mdViewerWrap
figure
{
display
:
block
;
}
.mdViewerWrap
summary
{
display
:
list-item
;
}
.mdViewerWrap
[
hidden
]
{
display
:
none
!
important
;
}
.mdViewerWrap
a
{
background-color
:
transparent
;
color
:
#0969da
;
text-decoration
:
none
;
}
.mdViewerWrap
abbr
[
title
]
{
border-bottom
:
none
;
-webkit-text-decoration
:
underline
dotted
;
text-decoration
:
underline
dotted
;
}
.mdViewerWrap
b
,
.mdViewerWrap
strong
{
font-weight
:
600
;
}
.mdViewerWrap
dfn
{
font-style
:
italic
;
}
.mdViewerWrap
h1
{
margin
:
0
.67em
0
;
font-weight
:
600
;
padding-bottom
:
0
.3em
;
font-size
:
2em
;
border-bottom
:
1px
solid
#d0d7de
b3
;
}
.mdViewerWrap
mark
{
background-color
:
#fff8c5
;
color
:
#1f2328
;
}
.mdViewerWrap
small
{
font-size
:
90%
;
}
.mdViewerWrap
sub
,
.mdViewerWrap
sup
{
font-size
:
75%
;
line-height
:
0
;
position
:
relative
;
vertical-align
:
baseline
;
}
.mdViewerWrap
sub
{
bottom
:
-0
.25em
;
}
.mdViewerWrap
sup
{
top
:
-0
.5em
;
}
.mdViewerWrap
img
{
border-style
:
none
;
max-width
:
100%
;
box-sizing
:
content-box
;
background-color
:
#ffffff
;
}
.mdViewerWrap
code
,
.mdViewerWrap
kbd
,
.mdViewerWrap
pre
,
.mdViewerWrap
samp
{
font-family
:
monospace
;
font-size
:
1em
;
}
.mdViewerWrap
figure
{
margin
:
1em
40px
;
}
.mdViewerWrap
hr
{
box-sizing
:
content-box
;
overflow
:
hidden
;
background
:
transparent
;
border-bottom
:
1px
solid
#d0d7de
b3
;
height
:
0
.25em
;
padding
:
0
;
margin
:
24px
0
;
background-color
:
#d0d7de
;
border
:
0
;
}
.mdViewerWrap
input
{
font
:
inherit
;
margin
:
0
;
overflow
:
visible
;
font-family
:
inherit
;
font-size
:
inherit
;
line-height
:
inherit
;
}
.mdViewerWrap
[
type
=
"button"
],
.mdViewerWrap
[
type
=
"reset"
],
.mdViewerWrap
[
type
=
"submit"
]
{
-webkit-appearance
:
button
;
appearance
:
button
;
}
.mdViewerWrap
[
type
=
"checkbox"
],
.mdViewerWrap
[
type
=
"radio"
]
{
box-sizing
:
border-box
;
padding
:
0
;
}
.mdViewerWrap
[
type
=
"number"
]
::-webkit-inner-spin-button
,
.mdViewerWrap
[
type
=
"number"
]
::-webkit-outer-spin-button
{
height
:
auto
;
}
.mdViewerWrap
[
type
=
"search"
]
::-webkit-search-cancel-button
,
.mdViewerWrap
[
type
=
"search"
]
::-webkit-search-decoration
{
-webkit-appearance
:
none
;
appearance
:
none
;
}
.
mdViewerWrap
:
:-
webkit-input-placeholder
{
color
:
inherit
;
opacity
:
0
.54
;
}
.
mdViewerWrap
:
:-
webkit-file-upload-button
{
-webkit-appearance
:
button
;
appearance
:
button
;
font
:
inherit
;
}
.mdViewerWrap
a
:hover
{
text-decoration
:
underline
;
}
.
mdViewerWrap
:
:
placeholder
{
color
:
#636c76
;
opacity
:
1
;
}
.
mdViewerWrap
hr
:
:
before
{
display
:
table
;
content
:
""
;
}
.
mdViewerWrap
hr
:
:
after
{
display
:
table
;
clear
:
both
;
content
:
""
;
}
.mdViewerWrap
table
{
border-spacing
:
0
;
border-collapse
:
collapse
;
display
:
block
;
width
:
max-content
;
max-width
:
100%
;
overflow
:
auto
;
}
.mdViewerWrap
td
,
.mdViewerWrap
th
{
padding
:
0
;
}
.mdViewerWrap
details
summary
{
cursor
:
pointer
;
}
.mdViewerWrap
details
:not
([
open
])
>
*
:not
(
summary
)
{
display
:
none
;
}
.mdViewerWrap
a
:focus
,
.mdViewerWrap
[
role
=
"button"
]
:focus
,
.mdViewerWrap
input
[
type
=
"radio"
]
:focus
,
.mdViewerWrap
input
[
type
=
"checkbox"
]
:focus
{
outline
:
2px
solid
#0969da
;
outline-offset
:
-2px
;
box-shadow
:
none
;
}
.mdViewerWrap
a
:focus:not
(
:focus-visible
),
.mdViewerWrap
[
role
=
"button"
]
:focus:not
(
:focus-visible
),
.mdViewerWrap
input
[
type
=
"radio"
]
:focus:not
(
:focus-visible
),
.mdViewerWrap
input
[
type
=
"checkbox"
]
:focus:not
(
:focus-visible
)
{
outline
:
solid
1px
transparent
;
}
.mdViewerWrap
a
:focus-visible
,
.mdViewerWrap
[
role
=
"button"
]
:focus-visible
,
.mdViewerWrap
input
[
type
=
"radio"
]
:focus-visible
,
.mdViewerWrap
input
[
type
=
"checkbox"
]
:focus-visible
{
outline
:
2px
solid
#0969da
;
outline-offset
:
-2px
;
box-shadow
:
none
;
}
.mdViewerWrap
a
:not
([
class
])
:focus
,
.mdViewerWrap
a
:not
([
class
])
:focus-visible
,
.mdViewerWrap
input
[
type
=
"radio"
]
:focus
,
.mdViewerWrap
input
[
type
=
"radio"
]
:focus-visible
,
.mdViewerWrap
input
[
type
=
"checkbox"
]
:focus
,
.mdViewerWrap
input
[
type
=
"checkbox"
]
:focus-visible
{
outline-offset
:
0
;
}
.mdViewerWrap
kbd
{
display
:
inline-block
;
padding
:
3px
5px
;
font
:
11px
ui-monospace
,
SFMono-Regular
,
SF
Mono
,
Menlo
,
Consolas
,
Liberation
Mono
,
monospace
;
line-height
:
10px
;
color
:
#1f2328
;
vertical-align
:
middle
;
background-color
:
#f6f8fa
;
border
:
solid
1px
#afb8c1
33
;
border-bottom-color
:
#afb8c1
33
;
border-radius
:
6px
;
box-shadow
:
inset
0
-1px
0
#afb8c1
33
;
}
.mdViewerWrap
h1
,
.mdViewerWrap
h2
,
.mdViewerWrap
h3
,
.mdViewerWrap
h4
,
.mdViewerWrap
h5
,
.mdViewerWrap
h6
{
margin-top
:
24px
;
margin-bottom
:
16px
;
font-weight
:
600
;
line-height
:
1
.25
;
}
.mdViewerWrap
h2
{
font-weight
:
600
;
padding-bottom
:
0
.3em
;
font-size
:
1
.5em
;
border-bottom
:
1px
solid
#d0d7de
b3
;
}
.mdViewerWrap
h3
{
font-weight
:
600
;
font-size
:
1
.25em
;
}
.mdViewerWrap
h4
{
font-weight
:
600
;
font-size
:
1em
;
}
.mdViewerWrap
h5
{
font-weight
:
600
;
font-size
:
0
.875em
;
}
.mdViewerWrap
h6
{
font-weight
:
600
;
font-size
:
0
.85em
;
color
:
#636c76
;
}
.mdViewerWrap
p
{
margin-top
:
0
;
margin-bottom
:
10px
;
font-size
:
1
.25em
;
}
.mdViewerWrap
blockquote
{
margin
:
0
;
padding
:
0
1em
;
color
:
#636c76
;
border-left
:
0
.25em
solid
#d0d7de
;
}
.mdViewerWrap
ul
,
.mdViewerWrap
ol
{
margin-top
:
0
;
margin-bottom
:
0
;
padding-left
:
2em
;
}
.mdViewerWrap
ol
ol
,
.mdViewerWrap
ul
ol
{
list-style-type
:
lower-roman
;
}
.mdViewerWrap
ul
ul
ol
,
.mdViewerWrap
ul
ol
ol
,
.mdViewerWrap
ol
ul
ol
,
.mdViewerWrap
ol
ol
ol
{
list-style-type
:
lower-alpha
;
}
.mdViewerWrap
dd
{
margin-left
:
0
;
}
.mdViewerWrap
tt
,
.mdViewerWrap
code
,
.mdViewerWrap
samp
{
font-family
:
ui-monospace
,
SFMono-Regular
,
SF
Mono
,
Menlo
,
Consolas
,
Liberation
Mono
,
monospace
;
font-size
:
12px
;
}
.mdViewerWrap
pre
{
margin-top
:
0
;
margin-bottom
:
0
;
font-family
:
ui-monospace
,
SFMono-Regular
,
SF
Mono
,
Menlo
,
Consolas
,
Liberation
Mono
,
monospace
;
font-size
:
12px
;
word-wrap
:
normal
;
}
.mdViewerWrap
.octicon
{
display
:
inline-block
;
overflow
:
visible
!
important
;
vertical-align
:
text-bottom
;
fill
:
currentColor
;
}
.
mdViewerWrap
input
:
:-
webkit-outer-spin-button
,
.
mdViewerWrap
input
::-
webkit-inner-spin-button
{
margin
:
0
;
-webkit-appearance
:
none
;
appearance
:
none
;
}
.mdViewerWrap
.mr-2
{
margin-right
:
0
.5rem
!
important
;
}
.
mdViewerWrap
:
:
before
{
display
:
table
;
content
:
""
;
}
.
mdViewerWrap
:
:
after
{
display
:
table
;
clear
:
both
;
content
:
""
;
}
.mdViewerWrap
>
*
:first-child
{
margin-top
:
0
!
important
;
}
.mdViewerWrap
>
*
:last-child
{
margin-bottom
:
0
!
important
;
}
.mdViewerWrap
a
:not
([
href
])
{
color
:
inherit
;
text-decoration
:
none
;
}
.mdViewerWrap
.absent
{
color
:
#d1242f
;
}
.mdViewerWrap
.anchor
{
float
:
left
;
padding-right
:
4px
;
margin-left
:
-20px
;
line-height
:
1
;
}
.mdViewerWrap
.anchor
:focus
{
outline
:
none
;
}
.mdViewerWrap
p
,
.mdViewerWrap
blockquote
,
.mdViewerWrap
ul
,
.mdViewerWrap
ol
,
.mdViewerWrap
dl
,
.mdViewerWrap
table
,
.mdViewerWrap
pre
,
.mdViewerWrap
details
{
margin-top
:
0
;
margin-bottom
:
16px
;
}
.mdViewerWrap
blockquote
>
:first-child
{
margin-top
:
0
;
}
.mdViewerWrap
blockquote
>
:last-child
{
margin-bottom
:
0
;
}
.mdViewerWrap
h1
.octicon-link
,
.mdViewerWrap
h2
.octicon-link
,
.mdViewerWrap
h3
.octicon-link
,
.mdViewerWrap
h4
.octicon-link
,
.mdViewerWrap
h5
.octicon-link
,
.mdViewerWrap
h6
.octicon-link
{
color
:
#1f2328
;
vertical-align
:
middle
;
visibility
:
hidden
;
}
.mdViewerWrap
h1
:hover
.anchor
,
.mdViewerWrap
h2
:hover
.anchor
,
.mdViewerWrap
h3
:hover
.anchor
,
.mdViewerWrap
h4
:hover
.anchor
,
.mdViewerWrap
h5
:hover
.anchor
,
.mdViewerWrap
h6
:hover
.anchor
{
text-decoration
:
none
;
}
.mdViewerWrap
h1
:hover
.anchor
.octicon-link
,
.mdViewerWrap
h2
:hover
.anchor
.octicon-link
,
.mdViewerWrap
h3
:hover
.anchor
.octicon-link
,
.mdViewerWrap
h4
:hover
.anchor
.octicon-link
,
.mdViewerWrap
h5
:hover
.anchor
.octicon-link
,
.mdViewerWrap
h6
:hover
.anchor
.octicon-link
{
visibility
:
visible
;
}
.mdViewerWrap
h1
tt
,
.mdViewerWrap
h1
code
,
.mdViewerWrap
h2
tt
,
.mdViewerWrap
h2
code
,
.mdViewerWrap
h3
tt
,
.mdViewerWrap
h3
code
,
.mdViewerWrap
h4
tt
,
.mdViewerWrap
h4
code
,
.mdViewerWrap
h5
tt
,
.mdViewerWrap
h5
code
,
.mdViewerWrap
h6
tt
,
.mdViewerWrap
h6
code
{
padding
:
0
0
.2em
;
font-size
:
inherit
;
}
.mdViewerWrap
summary
h1
,
.mdViewerWrap
summary
h2
,
.mdViewerWrap
summary
h3
,
.mdViewerWrap
summary
h4
,
.mdViewerWrap
summary
h5
,
.mdViewerWrap
summary
h6
{
display
:
inline-block
;
}
.mdViewerWrap
summary
h1
.anchor
,
.mdViewerWrap
summary
h2
.anchor
,
.mdViewerWrap
summary
h3
.anchor
,
.mdViewerWrap
summary
h4
.anchor
,
.mdViewerWrap
summary
h5
.anchor
,
.mdViewerWrap
summary
h6
.anchor
{
margin-left
:
-40px
;
}
.mdViewerWrap
summary
h1
,
.mdViewerWrap
summary
h2
{
padding-bottom
:
0
;
border-bottom
:
0
;
}
.mdViewerWrap
ul
.no-list
,
.mdViewerWrap
ol
.no-list
{
padding
:
0
;
list-style-type
:
none
;
}
.mdViewerWrap
ol
[
type
=
"a s"
]
{
list-style-type
:
lower-alpha
;
}
.mdViewerWrap
ol
[
type
=
"A s"
]
{
list-style-type
:
upper-alpha
;
}
.mdViewerWrap
ol
[
type
=
"i s"
]
{
list-style-type
:
lower-roman
;
}
.mdViewerWrap
ol
[
type
=
"I s"
]
{
list-style-type
:
upper-roman
;
}
.mdViewerWrap
ol
[
type
=
"1"
]
{
list-style-type
:
decimal
;
}
.mdViewerWrap
div
>
ol
:not
([
type
])
{
list-style-type
:
decimal
;
}
.mdViewerWrap
ul
ul
,
.mdViewerWrap
ul
ol
,
.mdViewerWrap
ol
ol
,
.mdViewerWrap
ol
ul
{
margin-top
:
0
;
margin-bottom
:
0
;
}
.mdViewerWrap
li
>
p
{
margin-top
:
16px
;
}
.mdViewerWrap
li
+
li
{
margin-top
:
0
.25em
;
}
.mdViewerWrap
dl
{
padding
:
0
;
}
.mdViewerWrap
dl
dt
{
padding
:
0
;
margin-top
:
16px
;
font-size
:
1em
;
font-style
:
italic
;
font-weight
:
600
;
}
.mdViewerWrap
dl
dd
{
padding
:
0
16px
;
margin-bottom
:
16px
;
}
.mdViewerWrap
table
th
{
font-weight
:
600
;
}
.mdViewerWrap
table
th
,
.mdViewerWrap
table
td
{
padding
:
6px
13px
;
border
:
1px
solid
#d0d7de
;
}
.mdViewerWrap
table
td
>
:last-child
{
margin-bottom
:
0
;
}
.mdViewerWrap
table
tr
{
background-color
:
#ffffff
;
border-top
:
1px
solid
#d0d7de
b3
;
}
.mdViewerWrap
table
tr
:nth-child
(
2n
)
{
background-color
:
#f6f8fa
;
}
.mdViewerWrap
table
img
{
background-color
:
transparent
;
}
.mdViewerWrap
img
[
align
=
"right"
]
{
padding-left
:
20px
;
}
.mdViewerWrap
img
[
align
=
"left"
]
{
padding-right
:
20px
;
}
.mdViewerWrap
.emoji
{
max-width
:
none
;
vertical-align
:
text-top
;
background-color
:
transparent
;
}
.mdViewerWrap
span
.frame
{
display
:
block
;
overflow
:
hidden
;
}
.mdViewerWrap
span
.frame
>
span
{
display
:
block
;
float
:
left
;
width
:
auto
;
padding
:
7px
;
margin
:
13px
0
0
;
overflow
:
hidden
;
border
:
1px
solid
#d0d7de
;
}
.mdViewerWrap
span
.frame
span
img
{
display
:
block
;
float
:
left
;
}
.mdViewerWrap
span
.frame
span
span
{
display
:
block
;
padding
:
5px
0
0
;
clear
:
both
;
color
:
#1f2328
;
}
.mdViewerWrap
span
.align-center
{
display
:
block
;
overflow
:
hidden
;
clear
:
both
;
}
.mdViewerWrap
span
.align-center
>
span
{
display
:
block
;
margin
:
13px
auto
0
;
overflow
:
hidden
;
text-align
:
center
;
}
.mdViewerWrap
span
.align-center
span
img
{
margin
:
0
auto
;
text-align
:
center
;
}
.mdViewerWrap
span
.align-right
{
display
:
block
;
overflow
:
hidden
;
clear
:
both
;
}
.mdViewerWrap
span
.align-right
>
span
{
display
:
block
;
margin
:
13px
0
0
;
overflow
:
hidden
;
text-align
:
right
;
}
.mdViewerWrap
span
.align-right
span
img
{
margin
:
0
;
text-align
:
right
;
}
.mdViewerWrap
span
.float-left
{
display
:
block
;
float
:
left
;
margin-right
:
13px
;
overflow
:
hidden
;
}
.mdViewerWrap
span
.float-left
span
{
margin
:
13px
0
0
;
}
.mdViewerWrap
span
.float-right
{
display
:
block
;
float
:
right
;
margin-left
:
13px
;
overflow
:
hidden
;
}
.mdViewerWrap
span
.float-right
>
span
{
display
:
block
;
margin
:
13px
auto
0
;
overflow
:
hidden
;
text-align
:
right
;
}
.mdViewerWrap
code
,
.mdViewerWrap
tt
{
padding
:
0
.2em
0
.4em
;
margin
:
0
;
font-size
:
85%
;
white-space
:
break-spaces
;
background-color
:
#afb8c1
33
;
border-radius
:
6px
;
}
.mdViewerWrap
code
br
,
.mdViewerWrap
tt
br
{
display
:
none
;
}
.mdViewerWrap
del
code
{
text-decoration
:
inherit
;
}
.mdViewerWrap
samp
{
font-size
:
85%
;
}
.mdViewerWrap
pre
code
{
font-size
:
100%
;
}
.mdViewerWrap
pre
>
code
{
padding
:
0
;
margin
:
0
;
word-break
:
normal
;
white-space
:
pre
;
background
:
transparent
;
border
:
0
;
}
.mdViewerWrap
.highlight
{
margin-bottom
:
16px
;
}
.mdViewerWrap
.highlight
pre
{
margin-bottom
:
0
;
word-break
:
normal
;
}
.mdViewerWrap
.highlight
pre
,
.mdViewerWrap
pre
{
padding
:
16px
;
overflow
:
auto
;
font-size
:
85%
;
line-height
:
1
.45
;
color
:
#1f2328
;
background-color
:
#f6f8fa
;
border-radius
:
6px
;
}
.mdViewerWrap
pre
code
,
.mdViewerWrap
pre
tt
{
display
:
inline
;
max-width
:
auto
;
padding
:
0
;
margin
:
0
;
overflow
:
visible
;
line-height
:
inherit
;
word-wrap
:
normal
;
background-color
:
transparent
;
border
:
0
;
}
.mdViewerWrap
.csv-data
td
,
.mdViewerWrap
.csv-data
th
{
padding
:
5px
;
overflow
:
hidden
;
font-size
:
12px
;
line-height
:
1
;
text-align
:
left
;
white-space
:
nowrap
;
}
.mdViewerWrap
.csv-data
.blob-num
{
padding
:
10px
8px
9px
;
text-align
:
right
;
background
:
#ffffff
;
border
:
0
;
}
.mdViewerWrap
.csv-data
tr
{
border-top
:
0
;
}
.mdViewerWrap
.csv-data
th
{
font-weight
:
600
;
background
:
#f6f8fa
;
border-top
:
0
;
}
.mdViewerWrap
[
data-footnote-ref
]
::before
{
content
:
"["
;
}
.mdViewerWrap
[
data-footnote-ref
]
::after
{
content
:
"]"
;
}
.mdViewerWrap
.footnotes
{
font-size
:
12px
;
color
:
#636c76
;
border-top
:
1px
solid
#d0d7de
;
}
.mdViewerWrap
.footnotes
ol
{
padding-left
:
16px
;
}
.mdViewerWrap
.footnotes
ol
ul
{
display
:
inline-block
;
padding-left
:
16px
;
margin-top
:
16px
;
}
.mdViewerWrap
.footnotes
li
{
position
:
relative
;
}
.mdViewerWrap
.footnotes
li
:target::before
{
position
:
absolute
;
top
:
-8px
;
right
:
-8px
;
bottom
:
-8px
;
left
:
-24px
;
pointer-events
:
none
;
content
:
""
;
border
:
2px
solid
#0969da
;
border-radius
:
6px
;
}
.mdViewerWrap
.footnotes
li
:target
{
color
:
#1f2328
;
}
.mdViewerWrap
.footnotes
.data-footnote-backref
g-emoji
{
font-family
:
monospace
;
}
.mdViewerWrap
.pl-c
{
color
:
#57606a
;
}
.mdViewerWrap
.pl-c1
,
.mdViewerWrap
.pl-s
.pl-v
{
color
:
#0550ae
;
}
.mdViewerWrap
.pl-e
,
.mdViewerWrap
.pl-en
{
color
:
#6639ba
;
}
.mdViewerWrap
.pl-smi
,
.mdViewerWrap
.pl-s
.pl-s1
{
color
:
#24292f
;
}
.mdViewerWrap
.pl-ent
{
color
:
#0550ae
;
}
.mdViewerWrap
.pl-k
{
color
:
#cf222e
;
}
.mdViewerWrap
.pl-s
,
.mdViewerWrap
.pl-pds
,
.mdViewerWrap
.pl-s
.pl-pse
.pl-s1
,
.mdViewerWrap
.pl-sr
,
.mdViewerWrap
.pl-sr
.pl-cce
,
.mdViewerWrap
.pl-sr
.pl-sre
,
.mdViewerWrap
.pl-sr
.pl-sra
{
color
:
#0a3069
;
}
.mdViewerWrap
.pl-v
,
.mdViewerWrap
.pl-smw
{
color
:
#953800
;
}
.mdViewerWrap
.pl-bu
{
color
:
#82071e
;
}
.mdViewerWrap
.pl-ii
{
color
:
#f6f8fa
;
background-color
:
#82071e
;
}
.mdViewerWrap
.pl-c2
{
color
:
#f6f8fa
;
background-color
:
#cf222e
;
}
.mdViewerWrap
.pl-sr
.pl-cce
{
font-weight
:
bold
;
color
:
#116329
;
}
.mdViewerWrap
.pl-ml
{
color
:
#3b2300
;
}
.mdViewerWrap
.pl-mh
,
.mdViewerWrap
.pl-mh
.pl-en
,
.mdViewerWrap
.pl-ms
{
font-weight
:
bold
;
color
:
#0550ae
;
}
.mdViewerWrap
.pl-mi
{
font-style
:
italic
;
color
:
#24292f
;
}
.mdViewerWrap
.pl-mb
{
font-weight
:
bold
;
color
:
#24292f
;
}
.mdViewerWrap
.pl-md
{
color
:
#82071e
;
background-color
:
#ffebe9
;
}
.mdViewerWrap
.pl-mi1
{
color
:
#116329
;
background-color
:
#dafbe1
;
}
.mdViewerWrap
.pl-mc
{
color
:
#953800
;
background-color
:
#ffd8b5
;
}
.mdViewerWrap
.pl-mi2
{
color
:
#eaeef2
;
background-color
:
#0550ae
;
}
.mdViewerWrap
.pl-mdr
{
font-weight
:
bold
;
color
:
#8250df
;
}
.mdViewerWrap
.pl-ba
{
color
:
#57606a
;
}
.mdViewerWrap
.pl-sg
{
color
:
#8c959f
;
}
.mdViewerWrap
.pl-corl
{
text-decoration
:
underline
;
color
:
#0a3069
;
}
.mdViewerWrap
[
role
=
"button"
]
:focus:not
(
:focus-visible
),
.mdViewerWrap
[
role
=
"tabpanel"
][
tabindex
=
"0"
]
:focus:not
(
:focus-visible
),
.mdViewerWrap
button
:focus:not
(
:focus-visible
),
.mdViewerWrap
summary
:focus:not
(
:focus-visible
),
.mdViewerWrap
a
:focus:not
(
:focus-visible
)
{
outline
:
none
;
box-shadow
:
none
;
}
.mdViewerWrap
[
tabindex
=
"0"
]
:focus:not
(
:focus-visible
),
.mdViewerWrap
details-dialog
:focus:not
(
:focus-visible
)
{
outline
:
none
;
}
.mdViewerWrap
g-emoji
{
display
:
inline-block
;
min-width
:
1ch
;
font-family
:
"Apple Color Emoji"
,
"Segoe UI Emoji"
,
"Segoe UI Symbol"
;
font-size
:
1em
;
font-style
:
normal
!
important
;
font-weight
:
400
;
line-height
:
1
;
vertical-align
:
-0
.075em
;
}
.mdViewerWrap
g-emoji
img
{
width
:
1em
;
height
:
1em
;
}
.mdViewerWrap
.task-list-item
{
list-style-type
:
none
;
}
.mdViewerWrap
.task-list-item
label
{
font-weight
:
400
;
}
.mdViewerWrap
.task-list-item.enabled
label
{
cursor
:
pointer
;
}
.mdViewerWrap
.task-list-item
+
.task-list-item
{
margin-top
:
0
.25rem
;
}
.mdViewerWrap
.task-list-item
.handle
{
display
:
none
;
}
.mdViewerWrap
.task-list-item-checkbox
{
margin
:
0
0
.2em
0
.25em
-1
.4em
;
vertical-align
:
middle
;
}
.mdViewerWrap
.contains-task-list
:dir
(
rtl
)
.task-list-item-checkbox
{
margin
:
0
-1
.6em
0
.25em
0
.2em
;
}
.mdViewerWrap
.contains-task-list
{
position
:
relative
;
}
.mdViewerWrap
.contains-task-list
:hover
.task-list-item-convert-container
,
.mdViewerWrap
.contains-task-list
:focus-within
.task-list-item-convert-container
{
display
:
block
;
width
:
auto
;
height
:
24px
;
overflow
:
visible
;
clip
:
auto
;
}
.
mdViewerWrap
:
:-
webkit-calendar-picker-indicator
{
filter
:
invert
(
50%
);
}
.mdViewerWrap
.markdown-alert
{
padding
:
0
.5rem
1rem
;
margin-bottom
:
1rem
;
color
:
inherit
;
border-left
:
0
.25em
solid
#d0d7de
;
}
.mdViewerWrap
.markdown-alert
>
:first-child
{
margin-top
:
0
;
}
.mdViewerWrap
.markdown-alert
>
:last-child
{
margin-bottom
:
0
;
}
.mdViewerWrap
.markdown-alert
.markdown-alert-title
{
display
:
flex
;
font-weight
:
500
;
align-items
:
center
;
line-height
:
1
;
}
.mdViewerWrap
.markdown-alert.markdown-alert-note
{
border-left-color
:
#0969da
;
}
.mdViewerWrap
.markdown-alert.markdown-alert-note
.markdown-alert-title
{
color
:
#0969da
;
}
.mdViewerWrap
.markdown-alert.markdown-alert-important
{
border-left-color
:
#8250df
;
}
.mdViewerWrap
.markdown-alert.markdown-alert-important
.markdown-alert-title
{
color
:
#8250df
;
}
.mdViewerWrap
.markdown-alert.markdown-alert-warning
{
border-left-color
:
#bf8700
;
}
.mdViewerWrap
.markdown-alert.markdown-alert-warning
.markdown-alert-title
{
color
:
#9a6700
;
}
.mdViewerWrap
.markdown-alert.markdown-alert-tip
{
border-left-color
:
#1a7f37
;
}
.mdViewerWrap
.markdown-alert.markdown-alert-tip
.markdown-alert-title
{
color
:
#1a7f37
;
}
.mdViewerWrap
.markdown-alert.markdown-alert-caution
{
border-left-color
:
#cf222e
;
}
.mdViewerWrap
.markdown-alert.markdown-alert-caution
.markdown-alert-title
{
color
:
#d1242f
;
}
.mdViewerWrap
>
*
:first-child
>
.heading-element
:first-child
{
margin-top
:
0
!
important
;
}
projects/web/src/pages/extract/components/md-viewer/index.tsx
0 → 100644
View file @
791e653c
import
{
useEffect
,
useRef
,
useState
}
from
"
react
"
;
import
{
Tooltip
}
from
"
antd
"
;
import
cls
from
"
classnames
"
;
import
styles
from
"
./index.module.scss
"
;
import
{
useDeepCompareEffect
,
useHover
}
from
"
ahooks
"
;
import
IconFont
from
"
@/components/icon-font
"
;
import
{
downloadFileUseAScript
}
from
"
@/utils/download
"
;
import
{
MD_DRIVE_PDF
}
from
"
@/constant/event
"
;
import
{
useIntl
}
from
"
react-intl
"
;
import
LazyUrlMarkdown
from
"
../url-markdown
"
;
import
exitFullScreenSvg
from
"
@/assets/pdf/exitFullScreen.svg
"
;
import
fullScreenSvg
from
"
@/assets/pdf/fullScreen.svg
"
;
import
{
MD_PREVIEW_TYPE
}
from
"
@/types/extract-task-type
"
;
import
_
from
"
lodash
"
;
import
{
TaskIdResItem
}
from
"
@/api/extract
"
;
import
useMdStore
from
"
@/store/mdStore
"
;
import
CodeMirror
from
"
@/components/code-mirror
"
;
import
{
useParams
}
from
"
react-router-dom
"
;
interface
IMdViewerProps
{
md
?:
string
;
className
?:
string
;
filename
?:
string
;
url
?:
string
;
taskInfo
:
TaskIdResItem
;
curPage
:
number
;
fullScreen
?:
boolean
;
setFullScreen
?:
(
value
?:
boolean
)
=>
void
;
}
const
MdViewer
:
React
.
FC
<
IMdViewerProps
>
=
({
fullScreen
,
setFullScreen
,
taskInfo
,
className
=
""
,
curPage
,
})
=>
{
const
mdViewerPef
=
useRef
<
HTMLDivElement
>
(
null
);
const
url
=
taskInfo
?.
fullMdLink
||
""
;
const
containerRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
isHovering
=
useHover
(
containerRef
);
const
{
formatMessage
}
=
useIntl
();
const
[
displayType
,
setDisplayType
]
=
useState
(
MD_PREVIEW_TYPE
.
preview
);
const
params
=
useParams
();
const
{
setAllMdContentWithAnchor
,
allMdContentWithAnchor
,
setMdUrlArr
,
mdContents
,
}
=
useMdStore
();
const
[
lineWrap
,
setLineWrap
]
=
useState
(
false
);
const
threshold
=
562
-
427
;
const
menuList
=
[
{
name
:
formatMessage
({
id
:
"
extractor.markdown.preview
"
}),
code
:
MD_PREVIEW_TYPE
.
preview
,
},
{
name
:
formatMessage
({
id
:
"
extractor.markdown.code
"
}),
code
:
MD_PREVIEW_TYPE
.
code
,
},
];
const
getVisibleFromType
=
(
str
:
string
,
type
:
string
)
=>
{
return
str
===
type
?
"
relative w-full h-full
"
:
"
w-0 h-0 overflow-hidden hidden
"
;
};
const
pushMdViewerScroll
=
(
scrollType
?:
"
instant
"
|
"
smooth
"
)
=>
{
const
container
=
document
.
getElementById
(
`md-container`
);
// md渲染的时候用一个元素包括anchor
const
element
=
displayType
===
MD_PREVIEW_TYPE
.
preview
?
document
.
getElementById
(
`md-anchor-
${
curPage
-
1
}
`
)?.
parentElement
:
document
.
getElementById
(
`code-
${
curPage
-
1
}
`
);
if
(
element
&&
container
)
{
container
.
scrollTo
({
top
:
element
.
offsetTop
-
124
,
behavior
:
scrollType
||
"
smooth
"
,
});
}
};
useEffect
(()
=>
{
if
(
isHovering
)
return
;
pushMdViewerScroll
();
},
[
curPage
,
isHovering
]);
useEffect
(()
=>
{
pushMdViewerScroll
(
"
instant
"
);
},
[
displayType
]);
useEffect
(()
=>
{
if
(
!
isHovering
)
return
;
const
handleScroll
=
()
=>
{
if
(
!
containerRef
.
current
)
return
;
taskInfo
?.
markdownUrl
?.
forEach
((
page
,
index
)
=>
{
const
element
=
displayType
===
MD_PREVIEW_TYPE
.
preview
?
document
.
getElementById
(
`md-anchor-
${
index
}
`
)?.
parentElement
:
document
.
getElementById
(
`code-
${
index
}
`
);
if
(
element
)
{
const
rect
=
element
.
getBoundingClientRect
();
if
(
rect
.
top
<=
threshold
)
{
document
.
dispatchEvent
(
new
CustomEvent
(
MD_DRIVE_PDF
,
{
detail
:
index
,
})
);
}
}
});
};
const
container
=
containerRef
.
current
;
if
(
container
)
{
container
.
addEventListener
(
"
scroll
"
,
handleScroll
);
}
return
()
=>
{
if
(
container
)
{
container
?.
removeEventListener
(
"
scroll
"
,
handleScroll
);
}
};
},
[
taskInfo
,
isHovering
,
displayType
]);
useDeepCompareEffect
(()
=>
{
if
(
taskInfo
?.
markdownUrl
)
{
setMdUrlArr
(
taskInfo
?.
markdownUrl
);
}
},
[
taskInfo
?.
markdownUrl
,
params
?.
jobID
]);
const
handleContentChange
=
(
val
:
string
)
=>
{
setAllMdContentWithAnchor
(
val
);
};
return
(
<
div
className
=
{
cls
(
className
)
}
ref
=
{
mdViewerPef
}
>
<
div
className
=
{
cls
(
"
h-[49px] px-6 border-0 border-solid border-b-[1px] border-[#EBECF0] w-full pl-[24px] flex justify-between items-center
"
)
}
>
<
ul
className
=
"p-1 list-none mb-0 inline-block rounded-sm mr-auto bg-[#F4F5F9] select-none"
>
{
menuList
.
map
((
item
)
=>
(
<
li
key
=
{
item
.
code
}
className
=
{
`mx-[0.125rem] px-2 leading-[25px] inline-block rounded-sm text-[14px] cursor-pointer text-color
${
displayType
===
item
.
code
&&
"
bg-white text-primary
"
}
`
}
onClick
=
{
()
=>
setDisplayType
(
item
.
code
)
}
>
{
item
.
name
}
</
li
>
))
}
</
ul
>
{
displayType
===
"
code
"
&&
(
<>
<
Tooltip
title
=
{
fullScreen
?
formatMessage
({
id
:
"
extractor.button.exitFullScreen
"
})
:
formatMessage
({
id
:
"
extractor.button.lineWrap
"
,
})
}
>
<
IconFont
type
=
"icon-line-wrap"
className
=
{
cls
(
"
text-lg text-[#464a53] leading-0 ml-[1rem] cursor-pointer hover:bg-[#F4F5F9] p-1 rounded
"
,
lineWrap
&&
"
!text-[#0D53DE]
"
)
}
onClick
=
{
()
=>
setLineWrap
?.(
!
lineWrap
)
}
/>
</
Tooltip
>
<
span
className
=
"w-[1px] h-[0.75rem] bg-[#D7D8DD] mx-[1rem]"
></
span
>
</>
)
}
<
Tooltip
title
=
{
fullScreen
?
formatMessage
({
id
:
"
extractor.button.exitFullScreen
"
})
:
formatMessage
({
id
:
"
extractor.button.fullScreen
"
,
})
}
>
<
span
className
=
"cursor-pointer w-[1.5rem] user-select-none flex items-center justify-center h-[1.5rem] hover:bg-[#F4F5F9] rounded "
onClick
=
{
()
=>
setFullScreen
?.(
!
fullScreen
)
}
>
{
!
fullScreen
?
(
<
img
className
=
" w-[1.125rem] h-[1.125rem] "
src
=
{
fullScreenSvg
}
/>
)
:
(
<
img
className
=
" w-[1.125rem] h-[1.125rem] "
src
=
{
exitFullScreenSvg
}
/>
)
}
</
span
>
</
Tooltip
>
<
span
className
=
"w-[1px] h-[0.75rem] bg-[#D7D8DD] ml-[1rem]"
></
span
>
<
Tooltip
title
=
{
formatMessage
({
id
:
"
extractor.button.download
"
})
}
>
<
IconFont
type
=
"icon-xiazai"
className
=
"text-lg text-[#464a53] leading-0 ml-[1rem] cursor-pointer hover:bg-[#F4F5F9] p-1 rounded"
onClick
=
{
()
=>
downloadFileUseAScript
(
url
,
`
${
_
(
taskInfo
?.
fileName
).
split
(
"
.
"
).
slice
(
0
,
-
1
).
join
(
"
.
"
)}
.md`
)
}
/>
</
Tooltip
>
</
div
>
<
div
className
=
{
cls
(
"
bg-white !h-[calc(100%-60px)] px-6 py-8 overflow-auto w-full max-w-[100%]
"
,
styles
.
scrollBar
)
}
id
=
"md-container"
ref
=
{
containerRef
}
>
<
div
className
=
{
cls
(
getVisibleFromType
(
displayType
,
MD_PREVIEW_TYPE
.
preview
)
)
}
>
<
LazyUrlMarkdown
markdownClass
=
{
"
relative
"
}
content
=
{
allMdContentWithAnchor
}
/>
</
div
>
<
div
className
=
{
cls
(
getVisibleFromType
(
displayType
,
MD_PREVIEW_TYPE
.
code
))
}
>
{
taskInfo
?.
markdownUrl
?.
map
((
url
:
string
,
index
:
number
)
=>
{
const
md
=
mdContents
[
url
]?.
content
||
""
;
if
(
!
md
)
return
null
;
return
(
<
div
key
=
{
url
}
id
=
{
`code-
${
index
}
`
}
className
=
"opacity-1 z-[-1]"
>
<
CodeMirror
value
=
{
md
}
lineWrapping
=
{
lineWrap
}
onChange
=
{
handleContentChange
}
editable
className
=
"w-full h-full"
/>
</
div
>
);
})
}
</
div
>
</
div
>
</
div
>
);
};
export
default
MdViewer
;
Prev
1
…
13
14
15
16
17
18
19
20
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment