File size: 15,154 Bytes
1b72d7e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
/**
 *   HEO 主题说明
 *  > 主题设计者 [张洪](https://zhheo.com/)
 *  > 主题开发者 [tangly1024](https://github.com/tangly1024)
 *  1. 开启方式 在blog.config.js 将主题配置为 `HEO`
 *  2. 更多说明参考此[文档](https://docs.tangly1024.com/article/notionnext-heo)
 */

import CONFIG from './config'
import { useEffect, useState } from 'react'
import Footer from './components/Footer'
import SideRight from './components/SideRight'
import NavBar from './components/NavBar'
import { useGlobal } from '@/lib/global'
import BlogPostListPage from './components/BlogPostListPage'
import BlogPostListScroll from './components/BlogPostListScroll'
import Hero from './components/Hero'
import { useRouter } from 'next/router'
import SearchNav from './components/SearchNav'
import BlogPostArchive from './components/BlogPostArchive'
import { ArticleLock } from './components/ArticleLock'
import PostHeader from './components/PostHeader'
import Comment from '@/components/Comment'
import NotionPage from '@/components/NotionPage'
import ArticleAdjacent from './components/ArticleAdjacent'
import ArticleCopyright from './components/ArticleCopyright'
import ArticleRecommend from './components/ArticleRecommend'
import ShareBar from '@/components/ShareBar'
import Link from 'next/link'
import CategoryBar from './components/CategoryBar'
import { Transition } from '@headlessui/react'
import { Style } from './style'
import { NoticeBar } from './components/NoticeBar'
import { HashTag } from '@/components/HeroIcons'
import LatestPostsGroup from './components/LatestPostsGroup'
import FloatTocButton from './components/FloatTocButton'
import replaceSearchResult from '@/components/Mark'
import LazyImage from '@/components/LazyImage'
import WWAds from '@/components/WWAds'
import { AdSlot } from '@/components/GoogleAdsense'
import { siteConfig } from '@/lib/config'

/**
 * 基础布局 采用上中下布局,移动端使用顶部侧边导航栏
 * @param props
 * @returns {JSX.Element}
 * @constructor
 */
const LayoutBase = props => {
  const {
    children,
    slotTop,
    className
  } = props

  // 全屏模式下的最大宽度
  const { fullWidth } = useGlobal()
  const router = useRouter()

  const headerSlot = (
    <header>
      {/* 顶部导航 */}
      <NavBar {...props} />

      {/* 通知横幅 */}
      {router.route === '/'
        ? <>
            <NoticeBar />
            <Hero {...props} />
        </>
        : null}
      {fullWidth ? null : <PostHeader {...props} />}
    </header>
  )

  // 右侧栏 用户信息+标签列表
  const slotRight = (router.route === '/404' || fullWidth) ? null : <SideRight {...props} />

  const maxWidth = fullWidth ? 'max-w-[96rem] mx-auto' : 'max-w-[86rem]' // 普通最大宽度是86rem和顶部菜单栏对齐,留空则与窗口对齐

  const HEO_HERO_BODY_REVERSE = siteConfig('HEO_HERO_BODY_REVERSE', false, CONFIG)

  return (
    <div
      id="theme-heo"
      className="bg-[#f7f9fe] dark:bg-[#18171d] h-full min-h-screen flex flex-col"
    >

      <Style />

      {/* 顶部嵌入 导航栏,首页放hero,文章页放文章详情 */}
      {headerSlot}

      {/* 主区块 */}
      <main
        id="wrapper-outer"
        className={`flex-grow w-full ${maxWidth} mx-auto relative md:px-5`}
      >
        <div
          id="container-inner"
          className={
            `${HEO_HERO_BODY_REVERSE ? 'flex-row-reverse' : ''} w-full mx-auto lg:flex justify-center relative z-10`
          }
        >
          <div className={`w-full h-auto ${className || ''}`}>
            {/* 主区上部嵌入 */}
            {slotTop}
            {children}
          </div>

          <div className='lg:px-2'></div>

          <div className="hidden xl:block">
            {/* 主区快右侧 */}
            {slotRight}
          </div>
        </div>

      </main>

      {/* 页脚 */}
      <Footer title={siteConfig('TITLE')} />
    </div>
  )
}

/**
 * 首页
 * 是一个博客列表,嵌入一个Hero大图
 * @param {*} props
 * @returns
 */
const LayoutIndex = props => {
  return (
      <div id="post-outer-wrapper" className="px-5 md:px-0">
        {/* 文章分类条 */}
        <CategoryBar {...props} />
        {siteConfig('POST_LIST_STYLE') === 'page'
          ? (
            <BlogPostListPage {...props} />
            )
          : (
            <BlogPostListScroll {...props} />
            )}
      </div>
  )
}

/**
 * 博客列表
 * @param {*} props
 * @returns
 */
const LayoutPostList = props => {
  return (
      <div id="post-outer-wrapper" className="px-5  md:px-0">
        {/* 文章分类条 */}
        <CategoryBar {...props} />
        {siteConfig('POST_LIST_STYLE') === 'page'
          ? (
            <BlogPostListPage {...props} />
            )
          : (
            <BlogPostListScroll {...props} />
            )}
      </div>
  )
}

/**
 * 搜索
 * @param {*} props
 * @returns
 */
const LayoutSearch = props => {
  const { keyword } = props
  const router = useRouter()
  const currentSearch = keyword || router?.query?.s

  useEffect(() => {
    // 高亮搜索结果
    if (currentSearch) {
      setTimeout(() => {
        replaceSearchResult({
          doms: document.getElementsByClassName('replace'),
          search: currentSearch,
          target: {
            element: 'span',
            className: 'text-red-500 border-b border-dashed'
          }
        })
      }, 100)
    }
  }, [])
  return (
    <div
      {...props}
      currentSearch={currentSearch}
    >
      <div id="post-outer-wrapper" className="px-5  md:px-0">
        {!currentSearch
          ? (
            <SearchNav {...props} />
            )
          : (
            <div id="posts-wrapper">
              {siteConfig('POST_LIST_STYLE') === 'page'
                ? (
                  <BlogPostListPage {...props} />
                  )
                : (
                  <BlogPostListScroll {...props} />
                  )}
            </div>
            )}
      </div>
    </div>
  )
}

/**
 * 归档
 * @param {*} props
 * @returns
 */
const LayoutArchive = props => {
  const { archivePosts } = props

  // 归档页顶部显示条,如果是默认归档则不显示。分类详情页显示分类列表,标签详情页显示当前标签

  return (
      <div className="p-5 rounded-xl border dark:border-gray-600 max-w-6xl w-full bg-white dark:bg-[#1e1e1e]">
        {/* 文章分类条 */}
        <CategoryBar {...props} border={false} />

        <div className="px-3">
          {Object.keys(archivePosts).map(archiveTitle => (
            <BlogPostArchive
              key={archiveTitle}
              posts={archivePosts[archiveTitle]}
              archiveTitle={archiveTitle}
            />
          ))}
        </div>
      </div>
  )
}

/**
 * 文章详情
 * @param {*} props
 * @returns
 */
const LayoutSlug = props => {
  const { post, lock, validPassword } = props
  const { locale, fullWidth } = useGlobal()

  const [hasCode, setHasCode] = useState(false)

  useEffect(() => {
    const hasCode = document.querySelectorAll('[class^="language-"]').length > 0
    setHasCode(hasCode)
  }, [])

  const commentEnable = siteConfig('COMMENT_TWIKOO_ENV_ID') || siteConfig('COMMENT_WALINE_SERVER_URL') || siteConfig('COMMENT_VALINE_APP_ID') ||
    siteConfig('COMMENT_GISCUS_REPO') || siteConfig('COMMENT_CUSDIS_APP_ID') || siteConfig('COMMENT_UTTERRANCES_REPO') ||
    siteConfig('COMMENT_GITALK_CLIENT_ID') || siteConfig('COMMENT_WEBMENTION_ENABLE')

  return (
    <>
      <div className={`w-full ${fullWidth ? '' : 'xl:max-w-5xl'} ${hasCode ? 'xl:w-[73.15vw]' : ''} lg:hover:shadow lg:border rounded-2xl lg:px-2 lg:py-4 bg-white dark:bg-[#18171d] dark:border-gray-600 article`}>
        {lock && <ArticleLock validPassword={validPassword} />}

        {!lock && (
          <div
            id="article-wrapper"
            className="overflow-x-auto flex-grow mx-auto md:w-full md:px-5 "
          >
            <article
              data-aos="fade-up"
              data-aos-duration="300"
              data-aos-once="false"
              data-aos-anchor-placement="top-bottom"
              itemScope
              itemType="https://schema.org/Movie"
              className="subpixel-antialiased overflow-y-hidden"
            >
              {/* Notion文章主体 */}
              <section className="px-5 justify-center mx-auto">
                <WWAds orientation="horizontal" className="w-full" />
                {post && <NotionPage post={post} />}
                <WWAds orientation="horizontal" className="w-full" />
              </section>

              {/* 分享 */}
              <ShareBar post={post} />
              {post?.type === 'Post' && (
                <div className="px-5">
                  {/* 版权 */}
                  <ArticleCopyright {...props} />
                  {/* 文章推荐 */}
                  <ArticleRecommend {...props} />
                  {/* 上一篇\下一篇文章 */}
                  <ArticleAdjacent {...props} />
                </div>
              )}
            </article>

            {fullWidth
              ? null
              : <div className={`${commentEnable && post ? '' : 'hidden'}`}>
                <hr className="my-4 border-dashed" />
                {/* 评论区上方广告 */}
                <div className="py-2">
                    <AdSlot />
                </div>
                {/* 评论互动 */}
                <div className="duration-200 overflow-x-auto px-5">
                  <div className="text-2xl dark:text-white">
                    <i className="fas fa-comment mr-1" />
                    {locale.COMMON.COMMENTS}
                  </div>
                  <Comment frontMatter={post} className="" />
                </div>
              </div>}
          </div>
        )}
      </div>
      <FloatTocButton {...props} />
    </>
  )
}

/**
 * 404
 * @param {*} props
 * @returns
 */
const Layout404 = props => {
  // const { meta, siteInfo } = props
  const { onLoading, fullWidth } = useGlobal()
  return (
    <>
    {/* 主区块 */}
      <main
        id="wrapper-outer"
        className={`flex-grow ${fullWidth ? '' : 'max-w-4xl'} w-screen mx-auto px-5`}
      >
        <div id="error-wrapper" className={'w-full mx-auto justify-center'}>
          <Transition
            show={!onLoading}
            appear={true}
            enter="transition ease-in-out duration-700 transform order-first"
            enterFrom="opacity-0 translate-y-16"
            enterTo="opacity-100"
            leave="transition ease-in-out duration-300 transform"
            leaveFrom="opacity-100 translate-y-0"
            leaveTo="opacity-0 -translate-y-16"
            unmount={false}
          >
            {/* 404卡牌 */}
            <div className="error-content flex flex-col md:flex-row w-full mt-12 h-[30rem] md:h-96 justify-center items-center bg-white dark:bg-[#1B1C20] border dark:border-gray-800 rounded-3xl">
              {/* 左侧动图 */}
              <LazyImage
                className="error-img h-60 md:h-full p-4"
                src={'https://bu.dusays.com/2023/03/03/6401a7906aa4a.gif'}
              ></LazyImage>

              {/* 右侧文字 */}
              <div className="error-info flex-1 flex flex-col justify-center items-center space-y-4">
                <h1 className="error-title font-extrabold md:text-9xl text-7xl dark:text-white">
                  404
                </h1>
                <div className='dark:text-white'>请尝试站内搜索寻找文章</div>
                <Link href="/">
                  <button className="bg-blue-500 py-2 px-4 text-white shadow rounded-lg hover:bg-blue-600 hover:shadow-md duration-200 transition-all">
                    回到主页
                  </button>
                </Link>
              </div>
            </div>

            {/* 404页面底部显示最新文章 */}
            <div className="mt-12">
              <LatestPostsGroup {...props} />
            </div>
          </Transition>
        </div>
      </main>
    </>
  )
}

/**
 * 分类列表
 * @param {*} props
 * @returns
 */
const LayoutCategoryIndex = props => {
  const { categoryOptions } = props
  const { locale } = useGlobal()

  return (
      <div id="category-outer-wrapper" className="mt-8 px-5 md:px-0">
        <div className="text-4xl font-extrabold dark:text-gray-200 mb-5">
          {locale.COMMON.CATEGORY}
        </div>
        <div
          id="category-list"
          className="duration-200 flex flex-wrap m-10 justify-center"
        >
          {categoryOptions?.map(category => {
            return (
              <Link
                key={category.name}
                href={`/category/${category.name}`}
                passHref
                legacyBehavior
              >
                <div
                  className={
                    'group mr-5 mb-5 flex flex-nowrap items-center border bg-white text-2xl rounded-xl dark:hover:text-white px-4 cursor-pointer py-3 hover:text-white hover:bg-indigo-600 transition-all hover:scale-110 duration-150'
                  }
                >
                  <HashTag className={'w-5 h-5 stroke-gray-500 stroke-2'} />
                  {category.name}
                  <div className="bg-[#f1f3f8] ml-1 px-2 rounded-lg group-hover:text-indigo-600 ">
                    {category.count}
                  </div>
                </div>
              </Link>
            )
          })}
        </div>
      </div>
  )
}

/**
 * 标签列表
 * @param {*} props
 * @returns
 */
const LayoutTagIndex = props => {
  const { tagOptions } = props
  const { locale } = useGlobal()

  return (
      <div id="tag-outer-wrapper" className="px-5 mt-8 md:px-0">
        <div className="text-4xl font-extrabold dark:text-gray-200 mb-5">
          {locale.COMMON.TAGS}
        </div>
        <div
          id="tag-list"
          className="duration-200 flex flex-wrap space-x-5 space-y-5 m-10 justify-center"
        >
          {tagOptions.map(tag => {
            return (
              <Link
                key={tag.name}
                href={`/tag/${tag.name}`}
                passHref
                legacyBehavior
              >
                <div
                  className={
                    'group flex flex-nowrap items-center border bg-white text-2xl rounded-xl dark:hover:text-white px-4 cursor-pointer py-3 hover:text-white hover:bg-indigo-600 transition-all hover:scale-110 duration-150'
                  }
                >
                  <HashTag className={'w-5 h-5 stroke-gray-500 stroke-2'} />
                  {tag.name}
                  <div className="bg-[#f1f3f8] ml-1 px-2 rounded-lg group-hover:text-indigo-600 ">
                    {tag.count}
                  </div>
                </div>
              </Link>
            )
          })}
        </div>
      </div>
  )
}

export {
  CONFIG as THEME_CONFIG,
  LayoutBase,
  LayoutIndex,
  LayoutSearch,
  LayoutArchive,
  LayoutSlug,
  Layout404,
  LayoutCategoryIndex,
  LayoutPostList,
  LayoutTagIndex
}