diff --git a/themes/Medium/LayoutBase.js b/themes/Medium/LayoutBase.js
index 1ee367d09a1..5772a5e4db3 100644
--- a/themes/Medium/LayoutBase.js
+++ b/themes/Medium/LayoutBase.js
@@ -13,10 +13,10 @@ import CONFIG_MEDIUM from './config_medium'
* @constructor
*/
const LayoutBase = props => {
- const { children, meta, showInfoCard = true } = props
+ const { children, meta, showInfoCard = true, slotRight } = props
return (
-
+
@@ -28,6 +28,7 @@ const LayoutBase = props => {
{ CONFIG_MEDIUM.WIDGET_REVOLVER_MAPS === 'true' && }
+ { slotRight }
diff --git a/themes/Medium/LayoutSlug.js b/themes/Medium/LayoutSlug.js
index cb6ef8c6bb6..905d8ed89aa 100644
--- a/themes/Medium/LayoutSlug.js
+++ b/themes/Medium/LayoutSlug.js
@@ -16,6 +16,7 @@ import Link from 'next/link'
import mediumZoom from 'medium-zoom'
import { useEffect, useRef } from 'react'
import ArticleAround from './components/ArticleAround'
+import Catalog from './components/Catalog'
const mapPageUrl = id => {
return 'https://www.notion.so/' + id.replace(/-/g, '')
@@ -55,7 +56,7 @@ export const LayoutSlug = (props) => {
}
})
- return
+ return }>
{post?.title}
@@ -86,6 +87,7 @@ export const LayoutSlug = (props) => {
/>
)}
+
{/* 文章内嵌广告 */}
{
+ // 无目录就直接返回空
+ if (!toc || toc.length < 1) {
+ return <>>
+ }
+ // 监听滚动事件
+ React.useEffect(() => {
+ window.addEventListener('scroll', actionSectionScrollSpy)
+ actionSectionScrollSpy()
+ return () => {
+ window.removeEventListener('scroll', actionSectionScrollSpy)
+ }
+ }, [])
+
+ // 同步选中目录事件
+ const [activeSection, setActiveSection] = React.useState(null)
+ const throttleMs = 100
+ const actionSectionScrollSpy = React.useCallback(throttle(() => {
+ const sections = document.getElementsByClassName('notion-h')
+ let prevBBox = null
+ let currentSectionId = activeSection
+ for (let i = 0; i < sections.length; ++i) {
+ const section = sections[i]
+ if (!section || !(section instanceof Element)) continue
+ if (!currentSectionId) {
+ currentSectionId = section.getAttribute('data-id')
+ }
+ const bbox = section.getBoundingClientRect()
+ const prevHeight = prevBBox ? bbox.top - prevBBox.bottom : 0
+ const offset = Math.max(150, prevHeight / 4)
+ // GetBoundingClientRect returns values relative to viewport
+ if (bbox.top - offset < 0) {
+ currentSectionId = section.getAttribute('data-id')
+ prevBBox = bbox
+ continue
+ }
+ // No need to continue loop, if last element has been detected
+ break
+ }
+ setActiveSection(currentSectionId)
+ }, throttleMs))
+
+ return
+
目录
+
+
+
+}
+
+export default Catalog
diff --git a/themes/Medium/components/Progress.js b/themes/Medium/components/Progress.js
new file mode 100644
index 00000000000..4c4adb9f681
--- /dev/null
+++ b/themes/Medium/components/Progress.js
@@ -0,0 +1,43 @@
+import React, { useEffect, useState } from 'react'
+
+/**
+ * 顶部页面阅读进度条
+ * @returns {JSX.Element}
+ * @constructor
+ */
+const Progress = ({ targetRef, showPercent = true }) => {
+ const currentRef = targetRef?.current || targetRef
+ const [percent, changePercent] = useState(0)
+ const scrollListener = () => {
+ const target = currentRef || document.getElementById('container')
+ if (target) {
+ const clientHeight = target.clientHeight
+ const scrollY = window.pageYOffset
+ const fullHeight = clientHeight - window.outerHeight
+ let per = parseFloat(((scrollY / fullHeight) * 100).toFixed(0))
+ if (per > 100) per = 100
+ if (per < 0) per = 0
+ changePercent(per)
+ }
+ }
+
+ useEffect(() => {
+ document.addEventListener('scroll', scrollListener)
+ return () => document.removeEventListener('scroll', scrollListener)
+ }, [percent])
+
+ return (
+
+
+ {showPercent && (
+
{percent}%
+ )}
+
+
+ )
+}
+
+export default Progress