fix(forum): align forum pages with the reference layout

- cards: restore the shared card shell, add author follow/message actions,
  score group, last-reply meta and reference plate header actions
- home: desktop left tab rail, widget-only sidebar, reference load-more markup
- plate: 1-based tabs, reference defaults (全部/推荐/最新回复/热门/精华)
- post: fixed action rail, score footer, author card, h1 article title
- quick publish: plate selector moved into the submit row like the reference
This commit is contained in:
kate522 committed 2026-11-11 18:06:33 +08:00
1 parent 7a59c65252
commit ec5dfcd82f
15 files changed
+396 -166

No files matched your search

+105 -45
View File
@@ -24,44 +24,44 @@
#forum .jio-forum__tabs a{padding:14px 0;color:var(--forum-muted);border-bottom:2px solid transparent} #forum .jio-forum__tabs a{padding:14px 0;color:var(--forum-muted);border-bottom:2px solid transparent}
#forum .jio-forum__tabs .is-active{color:var(--forum-accent);border-color:var(--forum-accent);font-weight:600} #forum .jio-forum__tabs .is-active{color:var(--forum-accent);border-color:var(--forum-accent);font-weight:600}
/* ── 论坛首页:栏目 TAB ─────────────────────────────────────────── */ /* ── 论坛首页:栏目 TAB(桌面端为左侧竖排导航,移动端横排滑动) ───────── */
#forum.forum-home{padding-bottom:40px} #forum.forum-home{padding-bottom:40px}
.forum-home .forum-home__main{min-width:0} .forum-home .content-layout{min-width:0}
.forum-home-tab .fixed-wrap-nav{margin-bottom:10px} .forum-home-tab .fixed-wrap-nav{margin-bottom:10px}
.forum-home-tab .tab-nav-theme{border-bottom:none;padding:0;margin:0} .forum-home-tab .tab-nav-theme{border-bottom:none;padding:0;margin:0}
.forum-home-tab .tab-nav-theme li{padding:0 2px} .forum-home-tab .tab-nav-theme li{padding:0}
.forum-home-tab .tab-nav-theme li > a{display:block;padding:10px 12px;font-size:16px;color:var(--muted-2-color)} .forum-home-tab .tab-nav-theme li > a{display:block;padding:6px;margin:1px 0;font-size:16px;color:var(--muted-2-color);overflow:hidden}
.forum-home-tab .tab-nav-theme li.active > a{color:var(--focus-color);font-weight:600} .forum-home-tab .tab-nav-theme li > a:hover{color:var(--main-color)}
.forum-home-tab .tab-nav-theme li.active > a{color:var(--focus-color);font-weight:500;background:var(--focus-color-opacity05);border-radius:var(--mini-radius)}
.forum-home-tab .tab-nav-theme li::before{display:none}
.forum-home-tab .tab-content > .tab-pane{display:none} .forum-home-tab .tab-content > .tab-pane{display:none}
.forum-home-tab .tab-content > .tab-pane.active{display:block} .forum-home-tab .tab-content > .tab-pane.active{display:block}
.forum-main-tab-content{min-height:120px} .forum-main-tab-content{min-height:120px}
.forum-more{padding:20px 0;text-align:center}
.forum-more.is-loading{display:none}
.forum-tab-pane > .ajax-item:last-of-type{margin-bottom:0} .forum-tab-pane > .ajax-item:last-of-type{margin-bottom:0}
/* 首页栏目是横向 TAB,覆盖通用 .fixed-wrap 的竖排侧栏导航形态 */ @media (max-width:767px){
@media (min-width:768px){ .forum-home-tab .fixed-wrap-nav:not(.affix):not(.affix-bottom){margin-bottom:10px;padding:0 5px;background:transparent;box-shadow:none}
.forum-home-tab{--fixed-width:auto} .forum-home-tab .tab-nav-theme{display:flex;flex-direction:row;overflow-x:auto;overflow-y:hidden;text-align:left;white-space:nowrap;border-bottom:none;margin:0;padding:0}
.forum-home-tab > .fixed-wrap-nav{position:static;width:auto} .forum-home-tab .tab-nav-theme > li{display:inline-block;padding:0;margin:0;flex:0 0 auto}
.forum-home-tab > .fixed-wrap-nav + .fixed-wrap-content{margin-left:0} .forum-home-tab .tab-nav-theme > li > a{padding:8px 10px;margin:0;font-size:15px;background:none!important}
.forum-home-tab .fixed-wrap-nav.affix{position:sticky;left:auto;right:auto;width:auto;z-index:11;background:var(--main-bg-color);box-shadow:0 0 10px var(--main-shadow)}
.forum-home-tab .tab-nav-theme.mini-scrollbar,
.forum-home-tab .tab-nav-theme.swiper-scroll .swiper-wrapper{flex-direction:row;overflow-x:auto;overflow-y:hidden;text-align:left;white-space:nowrap;border-bottom:none;margin:0;padding:0}
.forum-home-tab .tab-nav-theme.mini-scrollbar > li,
.forum-home-tab .tab-nav-theme.swiper-scroll .swiper-wrapper > li{display:inline-block;padding:0;margin:0}
} }
.quick-posts-box{border-radius:var(--main-radius);padding:15px} .quick-posts-box{border-radius:var(--main-radius);padding:15px}
.quick-posts-box .title-theme{margin-bottom:10px} .quick-posts-box .title-theme{margin-bottom:10px}
.quick-input-group{padding:15px;border:1px solid var(--main-border-color);border-radius:var(--main-radius)} .quick-input-group{padding:15px;border:1px solid var(--main-border-color);border-radius:var(--main-radius)}
.quick-posts-box .edit-posts-title{position:relative} .quick-posts-box .edit-posts-title{position:relative}
.quick-posts-box .edit-posts-title .line-form-line{display:none} .quick-posts-box .edit-posts-title .line-form-line{display:none}
.quick-posts-box .edit-posts-title textarea{width:100%;color:var(--main-color);padding:0;margin-bottom:10px;font-weight:500;font-size:18px;border:0;border-bottom:1px solid var(--main-border-color);background:transparent;resize:none;outline:0} .quick-posts-box .edit-posts-title textarea{width:100%;color:var(--main-color);opacity:1;padding:0;margin-bottom:10px;font-weight:500;font-size:18px;border:0;border-bottom:1px solid var(--main-border-color);background:transparent;resize:none;outline:0}
.quick-posts-box .edit-posts-content textarea{outline:0!important;border:none;display:block;width:100%;padding:0;background:transparent;color:var(--main-color);resize:vertical;min-height:88px} .quick-posts-box .edit-posts-content textarea{outline:0!important;border:none;display:block;width:100%;padding:0;margin-bottom:10px;background:var(--main-bg-color);opacity:.9;color:var(--main-color);resize:vertical;min-height:88px}
.quick-posts-box .preview .add,.quick-posts-box .preview .preview-item{width:90px;height:90px;border:1px dashed var(--main-border-color);background:none;margin:2px;border-radius:var(--main-radius);position:relative} .quick-posts-box .preview .add,.quick-posts-box .preview .preview-item{width:90px;height:90px;border:1px dashed var(--main-border-color);background:none;margin:2px;border-radius:var(--main-radius);position:relative}
.quick-posts-box .preview .add:before{content:"+";position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:28px;color:var(--muted-3-color)} .quick-posts-box .preview .add:before{content:"+";position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:28px;color:var(--muted-3-color)}
.quick-posts-box .drop-select .dropdown-menu{min-width:320px} .quick-posts-box .drop-select .dropdown-menu{min-width:320px}
.quick-more-btns{color:var(--muted-color);position:relative;flex-wrap:wrap;gap:6px} .quick-posts-box .dropdown-menu.fluid::before{left:20px}
.quick-more-btns .drop-btn{padding:6px 4px;cursor:pointer} .quick-posts-box .but-plate{font-size:14px;padding:5px 9px 6px 5px}
.quick-more-btns{color:var(--muted-color);position:relative;flex-wrap:wrap}
.quick-more-btns .drop-btn{padding:6px 4px;cursor:pointer;white-space:nowrap}
.quick-more-btns .drop-select{position:relative} .quick-more-btns .drop-select{position:relative}
.quick-selected-tags > *{display:inline-block}
.quick-selected-tags > *:empty{display:none}
.quick-selected-tags [name="topics"]{color:var(--focus-color);margin-top:6px;font-size:13px}
.quick-posts-submit .but{padding:6px 5px;min-width:85px} .quick-posts-submit .but{padding:6px 5px;min-width:85px}
.quick-posts-submit .quick-captcha{margin-bottom:8px} .quick-posts-submit .quick-captcha{margin-bottom:8px}
.hide-type-drop .select-drop-box .badg.active{--this-bg:var(--focus-color);--this-color:#fff} .hide-type-drop .select-drop-box .badg.active{--this-bg:var(--focus-color);--this-color:#fff}
@@ -75,6 +75,15 @@
.quick-posts-form .jio-forum__message:empty{display:none} .quick-posts-form .jio-forum__message:empty{display:none}
.forum-pending-plate{background:var(--main-bg-color);box-shadow:0 0 10px var(--main-shadow);border-radius:var(--main-radius);padding:12px 15px;margin-bottom:10px} .forum-pending-plate{background:var(--main-bg-color);box-shadow:0 0 10px var(--main-shadow);border-radius:var(--main-radius);padding:12px 15px;margin-bottom:10px}
/* ── 论坛首页 / 版块页的加载更多(与主题 AJAX 翻页按钮同构) ───────── */
.forum-tab-pane .theme-pagination,.plate-tab .theme-pagination{margin:0}
.forum-tab-pane .theme-pagination.ajax-pag,.plate-tab .theme-pagination.ajax-pag{padding:15px 5px 10px}
.forum-tab-pane .theme-pagination .ajax-next a,.plate-tab .theme-pagination .ajax-next a{color:var(--muted-color);opacity:.6;border-radius:30px;padding:5px 15px;display:inline-block}
.forum-tab-pane .theme-pagination .ajax-next a:hover,.plate-tab .theme-pagination .ajax-next a:hover{background:var(--main-border-color);opacity:1}
.forum-tab-pane .theme-pagination .ajax-next .fa,.plate-tab .theme-pagination .ajax-next .fa{margin-right:15px;transition:.3s}
.forum-tab-pane .theme-pagination .ajax-next a:hover .fa,.plate-tab .theme-pagination .ajax-next a:hover .fa{margin-right:8px}
.forum-tab-pane.is-loading .theme-pagination,.plate-tab .forum-tab-pane.is-loading .theme-pagination{display:none}
/* ── 版块面板 / 版块卡片 ────────────────────────────────────────── */ /* ── 版块面板 / 版块卡片 ────────────────────────────────────────── */
.panel.panel-plate{border-color:transparent;margin-bottom:10px;background:var(--main-bg-color);border-radius:var(--main-radius);box-shadow:0 0 10px var(--main-shadow);padding:0 15px} .panel.panel-plate{border-color:transparent;margin-bottom:10px;background:var(--main-bg-color);border-radius:var(--main-radius);box-shadow:0 0 10px var(--main-shadow);padding:0 15px}
.panel.panel-plate:first-of-type .panel-title{padding-top:10px} .panel.panel-plate:first-of-type .panel-title{padding-top:10px}
@@ -94,9 +103,10 @@
.scroll-plate .plate-card.mini{padding:15px 5px;width:85px} .scroll-plate .plate-card.mini{padding:15px 5px;width:85px}
.scroll-plate .plate-card .plate-thumb{width:50px!important;height:50px!important} .scroll-plate .plate-card .plate-thumb{width:50px!important;height:50px!important}
.follow-plate .plate-card{width:90px} .follow-plate .plate-card{width:90px}
.plate-item{display:inline-flex;align-items:flex-start} .plate-item{display:inline-flex}
.plate-lists{display:flex;align-content:flex-start;flex-flow:row wrap;margin:-3px} .plate-lists{display:flex;align-content:flex-start;flex-flow:row wrap;margin:-3px}
.plate-lists .plate-item{margin:3px;background:var(--main-bg-color);box-shadow:0 0 10px var(--main-shadow);border-color:transparent;border-radius:var(--main-radius);padding:15px 20px;width:calc(50% - 6px);position:relative;box-sizing:border-box} .plate-lists .plate-item{margin:3px;background:var(--main-bg-color);box-shadow:0 0 10px var(--main-shadow);border-color:transparent;border-radius:var(--main-radius);padding:15px 20px;width:calc(50% - 6px);position:relative;box-sizing:border-box}
.sidebar .plate-lists .plate-item{width:calc(100% - 6px)}
.plate-item .plate-thumb{margin-right:15px;flex-shrink:0;display:inline-block;width:60px;height:60px;position:relative} .plate-item .plate-thumb{margin-right:15px;flex-shrink:0;display:inline-block;width:60px;height:60px;position:relative}
.plate-item .plate-thumb > img,.plate-thumb > img{border-radius:100px;width:100%;height:100%} .plate-item .plate-thumb > img,.plate-thumb > img{border-radius:100px;width:100%;height:100%}
.plate-item .forum-thumbnail{transition:all .2s;object-fit:cover;width:100%;height:100%} .plate-item .forum-thumbnail{transition:all .2s;object-fit:cover;width:100%;height:100%}
@@ -165,10 +175,10 @@
.forum-posts.detail .user-info ~ *{margin-left:70px} .forum-posts.detail .user-info ~ *{margin-left:70px}
.forum-posts.detail .user-info .display-name{font-size:16px} .forum-posts.detail .user-info .display-name{font-size:16px}
.forum-posts.detail .action-meta .btn-score:hover,.forum-posts.detail .action-meta a.item:hover{background:var(--main-border-color)} .forum-posts.detail .action-meta .btn-score:hover,.forum-posts.detail .action-meta a.item:hover{background:var(--main-border-color)}
.forum-header .buts .but{min-width:87px}
.blur-header .count-mates{--main-border-color:rgba(255,255,255,.08)}
} }
@media(max-width:800px){ @media(max-width:800px){
#forum .jio-forum__layout{grid-template-columns:1fr}
#forum .jio-forum__aside{border-left:0;padding-left:0}
.forum-home .sidebar{width:100%;margin-left:0;margin-top:20px} .forum-home .sidebar{width:100%;margin-left:0;margin-top:20px}
.forum-home .content-layout{margin-right:0!important;margin-left:0!important} .forum-home .content-layout{margin-right:0!important;margin-left:0!important}
} }
@@ -301,35 +311,53 @@ body.dark-theme .panel.panel-plate,body.dark-theme .plate-lists .plate-item,body
body.dark-theme .forum-header,body.dark-theme .forum-header .header-content{background:var(--main-bg-color)} body.dark-theme .forum-header,body.dark-theme .forum-header .header-content{background:var(--main-bg-color)}
/* ── 版块 / 分类页面 ──────────────────────────────────────────── */ /* ── 版块 / 分类页面 ──────────────────────────────────────────── */
.forum-listing .jio-forum__breadcrumbs{margin:0 0 12px} .forum-listing .content-layout{min-width:0}
.forum-listing .jio-forum__list-main{min-width:0} .term-main{max-width:790px;margin:0 auto 20px}
.plate-tab{padding:0} .plate-tab{padding:0}
.plate-tab .affix-header-sm{padding:0 15px;background:var(--main-bg-color);border-radius:var(--main-radius) var(--main-radius) 0 0} .plate-tab .affix-header-sm{padding:0 15px;background:var(--main-bg-color);border-radius:var(--main-radius) var(--main-radius) 0 0}
.plate-tab .affix-header-sm.affix,.plate-tab .affix-header-sm.affix-bottom{padding:8px 25px} .plate-tab .affix-header-sm.affix,.plate-tab .affix-header-sm.affix-bottom{padding:8px 25px}
.plate-tab .tab-nav-theme{margin:0;border-bottom:1px solid var(--main-border-color)} .plate-tab .tab-nav-theme{margin:0}
.plate-tab .tab-nav-theme li a{font-size:16px;color:var(--muted-2-color);display:block;padding:12px 14px} .plate-tab .tab-nav-theme li a{font-size:16px;color:var(--muted-2-color);display:block;padding:0 5px}
.plate-tab .tab-nav-theme li.active a{font-weight:bold;color:var(--focus-color)} .plate-tab .tab-nav-theme li.active a{font-weight:700;color:var(--focus-color)}
.plate-tab .tab-content{padding:10px 15px} .plate-tab .tab-content{padding:10px 15px}
.plate-tab .tab-content > .tab-pane{display:none} .plate-tab .tab-content > .tab-pane{display:none}
.plate-tab .tab-content > .tab-pane.active{display:block} .plate-tab .tab-content > .tab-pane.active{display:block}
.plate-tab .forum-tab-pane{min-height:120px} .plate-tab .forum-tab-pane{min-height:120px}
.forum-header.blur-header{overflow:hidden;color:#fff} .forum-header.blur-header{overflow:hidden;color:#ddd}
.forum-header.blur-header .header-content{background:transparent;color:#fff;padding:28px 30px 22px} .forum-header.blur-header .header-content{background:none;color:#fff;padding:70px 50px 50px}
.forum-header.blur-header .forum-title,.forum-header.blur-header .forum-title a{color:#fff} .forum-header.blur-header .forum-title,.forum-header.blur-header .forum-title a,.forum-header.blur-header a{color:#fff}
.forum-header.blur-header .desc,.forum-header.blur-header .px12-sm{color:rgba(255,255,255,.86)} .forum-header.blur-header .desc,.forum-header.blur-header .px12-sm{color:rgba(255,255,255,.86)}
.forum-header .abs-blur-bg img{width:100%;height:100%;object-fit:cover} .forum-header .abs-blur-bg img{width:100%;height:100%;object-fit:cover}
.forum-header .forum-mask{position:absolute;inset:0;background:rgba(76,80,87,.6)} .forum-header .forum-mask{position:absolute;inset:0;background:rgba(76,80,87,.6)}
.forum-header.blur-header .plate-thumb{width:90px;height:90px;flex-shrink:0} .forum-header.blur-header .plate-thumb{width:115px;height:115px;flex-shrink:0}
.forum-header.blur-header .plate-thumb > img{border-radius:var(--main-radius)} .forum-header.blur-header .plate-thumb > img{border-radius:4px}
.forum-header .more-btns{position:absolute;right:5px;top:3px;z-index:1;justify-content:flex-end}
.forum-header .more-btns .item{padding:5px 7px;opacity:.8;font-size:1.2em;color:#fff;border:0;background:none;cursor:pointer}
.forum-header .more-btns .item text{display:none}
.forum-header .moderator-btns{display:flex;flex-wrap:wrap;align-items:center;gap:6px} .forum-header .moderator-btns{display:flex;flex-wrap:wrap;align-items:center;gap:6px}
.forum-header .moderator-btns .avatar-mini{--this-size:22px;margin-right:4px} .forum-header .moderator-btns .avatar-mini{--this-size:22px;margin-right:4px;display:inline-block}
.forum-header .moderator-btns .avatar-mini img{border-radius:100px} .forum-header .moderator-btns .avatar-mini img{border-radius:100px;width:100%;height:100%}
.forum-header .moderator-btns .but{font-size:12px;padding:3px 10px} .forum-header .moderator-btns .but{font-size:12px;padding:3px 10px;line-height:21px;min-width:90px}
.forum-header .count-mates{margin-top:10px}
.forum-header .count-mates item{display:inline-block}
.forum-header .count-mates > item + item{border-left:1px solid rgba(255,255,255,.08);padding-left:20px;margin-left:20px}
.blur-header .c-white{--this-bg:rgba(255,255,255,.12);--this-color:#fff}
.forum-header .more-btns .btn-follow{color:#fff;border:0;background:none;cursor:pointer;opacity:.85;padding:5px 7px;font-size:1.2em}
.forum-header .more-btns .btn-follow.active{color:#ff7800;opacity:1}
/* ── 帖子详情页 ───────────────────────────────────────────────── */ /* ── 帖子详情页 ───────────────────────────────────────────────── */
.forum-post .single-wrap{width:100%} .forum-post .single-wrap{width:100%}
.single-fixed-btns{display:none}
@media (min-width:768px){
.forum-post .fixed-wrap-nav.single-fixed-btns{display:block}
.single-fixed-btns .score-btns{display:block;text-align:center}
.single-fixed-btns .score-btns text{display:block;font-size:12px;color:var(--muted-2-color);min-width:30px;margin:5px}
.single-fixed-btns .btn-score{font-size:24px;color:var(--muted-color);display:inline-flex;align-items:center}
.single-fixed-btns .btn-score.extra.active{color:#40a1ff}
.single-fixed-btns .btn-score.deduct.active{color:#ff556d}
}
.forum-single-cover{border-radius:var(--main-radius);overflow:hidden} .forum-single-cover{border-radius:var(--main-radius);overflow:hidden}
.forum-article{padding:20px 26px} .forum-article{padding:0 30px;overflow:hidden}
.forum-article .article-header{margin-bottom:15px;padding-bottom:12px;margin-top:5px;border-bottom:1px solid var(--main-border-color)} .forum-article .article-header{margin-bottom:15px;padding-bottom:12px;margin-top:5px;border-bottom:1px solid var(--main-border-color)}
.forum-article .article-title{font-size:24px;line-height:1.4;margin:0 0 10px;font-weight:600} .forum-article .article-title{font-size:24px;line-height:1.4;margin:0 0 10px;font-weight:600}
.forum-article .article-title a{color:var(--key-color)} .forum-article .article-title a{color:var(--key-color)}
@@ -340,18 +368,32 @@ body.dark-theme .forum-header,body.dark-theme .forum-header .header-content{back
.forum-article .wp-posts-content{font-size:15px;line-height:1.9;overflow-wrap:anywhere} .forum-article .wp-posts-content{font-size:15px;line-height:1.9;overflow-wrap:anywhere}
.forum-article .article-tags{display:flex;flex-wrap:wrap;gap:6px} .forum-article .article-tags{display:flex;flex-wrap:wrap;gap:6px}
.forum-article .article-tags .but{font-size:12px;padding:4px 10px} .forum-article .article-tags .but{font-size:12px;padding:4px 10px}
.forum-article .score-box.single-footer{margin:10px 0 20px}
.forum-article .score-box .score-btns{margin-bottom:10px;border-radius:100px;padding:8px 24px;box-shadow:0 2px 10px 4px var(--main-shadow);display:inline-flex;align-items:center}
.forum-article .score-box .btn-score{font-size:24px;color:var(--muted-color);padding:2px}
.forum-article .score-box .btn-score.extra.active{color:#40a1ff}
.forum-article .score-box .btn-score.deduct.active{color:#ff556d}
.forum-article .score-box .score-btns text{font-size:12px;color:var(--muted-2-color);min-width:30px;margin:5px}
.forum-single-author{background-color:var(--muted-bg-2-color);box-shadow:none}
.forum-single-author .card-content{display:flex;gap:40px;padding:15px 40px}
.forum-single-author .user-content{display:flex;align-items:center;gap:20px}
.forum-single-author .count-info{display:flex;flex-direction:column;gap:6px}
.forum-single-author .user-info .border-bottom{padding-bottom:10px;margin-bottom:10px}
.forum-single-author .user-desc{font-size:13px}
.forum-single-author .more-posts-mini .item{padding:3px 0}
.forum-single-author .more-posts-mini a{display:block;color:var(--muted-2-color);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.footer-actions{margin:18px -10px 10px;padding-top:14px;border-top:1px solid var(--main-border-color)} .footer-actions{margin:18px -10px 10px;padding-top:14px;border-top:1px solid var(--main-border-color)}
.footer-actions .item{display:flex;align-items:center;padding:4px 8px;margin:0 4px;color:var(--muted-color);border:0;background:none;cursor:pointer;border-radius:4px} .footer-actions .item{display:flex;align-items:center;padding:0 4px;margin:0 4px;color:var(--muted-color);border:0;background:none;cursor:pointer}
.footer-actions .item:hover{background:var(--muted-border-color)} .footer-actions .item:hover{color:var(--focus-color)}
.footer-actions .item > i{font-size:18px;margin-right:5px} .footer-actions .item > i,.footer-actions .item > .icon{font-size:20px;margin-right:4px}
.footer-actions .item text{font-size:12px} .footer-actions .item text{font-size:12px}
.single-footer .but{font-size:12px;padding:4px 12px} .single-footer .but{font-size:12px;padding:4px 12px}
.vote-box.forum-vote{padding:20px 0} .vote-box.forum-vote{padding:15px 30px}
.vote-box.forum-vote h2{font-size:17px} .vote-box.forum-vote h2{font-size:17px}
.vote-item{position:relative;overflow:hidden;padding:8px 3.5em 8px 15px;margin-bottom:6px;border-radius:50px;background:var(--muted-border-color);color:var(--muted-color);display:block;cursor:pointer;text-overflow:ellipsis;white-space:nowrap} .vote-item{position:relative;overflow:hidden;padding:5px 3.5em 5px 15px;margin-bottom:6px;border-radius:50px;background:var(--main-bg-color);color:var(--muted-color);display:block;cursor:pointer;text-overflow:ellipsis;white-space:nowrap}
.vote-item input{margin-right:8px} .vote-item input{margin-right:8px}
.vote-progress{position:absolute;left:0;top:0;bottom:0;background:rgba(130,130,130,.15);transition:width .6s,background .2s;border-radius:50px;width:0;z-index:-1} .vote-progress{position:absolute;left:0;top:0;bottom:0;background:rgba(130,130,130,.15);transition:width 1s,background .2s;border-radius:50px;width:0;z-index:-1}
.vote-item .vote-count{position:absolute;right:14px;font-size:.9em;opacity:.8} .vote-item .vote-count{position:absolute;right:14px;font-size:.9em;margin-top:.1em;opacity:.8}
.vote-box.forum-vote button{display:block;margin:16px auto 0} .vote-box.forum-vote button{display:block;margin:16px auto 0}
/* ── 发帖 / 编辑页 ────────────────────────────────────────────── */ /* ── 发帖 / 编辑页 ────────────────────────────────────────────── */
@@ -384,3 +426,21 @@ body.dark-theme .forum-header,body.dark-theme .forum-header .header-content{back
.forum-header.blur-header .plate-thumb{width:64px;height:64px} .forum-header.blur-header .plate-thumb{width:64px;height:64px}
} }
body.dark-theme .forum-header.blur-header .forum-mask{background:rgba(20,22,26,.72)} body.dark-theme .forum-header.blur-header .forum-mask{background:rgba(20,22,26,.72)}
/* ── 详情页侧栏版块信息卡(参考 .plate-info-sidebar) ─────────────── */
.plate-info-sidebar .forum-header{box-shadow:0 0 10px var(--main-shadow);border-radius:var(--main-radius)}
.plate-info-sidebar .header-content{padding:55px 20px 25px;background:var(--main-bg-color);border-radius:var(--main-radius)}
.plate-info-sidebar .plate-thumb{width:100px;height:100px;margin:0 auto 10px}
.plate-info-sidebar .plate-thumb > img{border-radius:4px}
.plate-info-sidebar .forum-title{font-size:16px;text-align:center}
.plate-info-sidebar .count-mates{text-align:center;color:var(--muted-2-color)}
.plate-info-sidebar .count-mates > item + item{border-left:1px solid var(--main-border-color);padding-left:12px;margin-left:12px}
.plate-info-sidebar .follow-info{background:var(--main-border-color);box-shadow:none;color:var(--focus-color);padding:6px 12px;margin-top:38px;opacity:.9}
@media (max-width:640px){
.forum-single-author .card-content{flex-direction:column;gap:20px;padding:5px 12px}
.forum-single-author .user-info .border-bottom{padding-bottom:10px;margin-bottom:10px}
.forum-article{padding:0 15px}
.forum-article .article-title{font-size:20px}
.forum-header.blur-header .header-content{padding:60px 15px 30px}
.forum-header.blur-header .plate-thumb{width:105px;height:105px;order:2;margin:0 0 0 20px}
}
+23 -2
View File
@@ -359,6 +359,24 @@
const label = button.querySelector('text') const label = button.querySelector('text')
if (label) label.textContent = following ? '已关注' : '关注' if (label) label.textContent = following ? '已关注' : '关注'
else button.textContent = result.data?.label || button.textContent else button.textContent = result.data?.label || button.textContent
} else if (action === 'favorite') {
const active = result.data?.active === true
button.classList.toggle('active', active)
const label = button.querySelector('text')
if (label) label.textContent = active ? '已收藏' : '收藏'
} else if (action === 'score') {
// Card score group: flip the active arrow and refresh the shared counter.
const group = button.closest('.score-btns')
if (group) {
const isUp = button.dataset.direction !== 'down'
const userScore = Number(result.data?.user_score || 0)
group.querySelectorAll('.btn-score').forEach((item) => {
const own = (item.dataset.direction !== 'down') === isUp
item.classList.toggle('active', own && userScore !== 0)
})
const counter = group.querySelector('text')
if (counter) counter.textContent = Number(result.data?.score || 0) > 0 ? String(result.data.score) : '评分'
}
} else if (button.querySelector('text')) { } else if (button.querySelector('text')) {
button.querySelector('text').textContent = result.data?.label || result.message || button.querySelector('text').textContent button.querySelector('text').textContent = result.data?.label || result.message || button.querySelector('text').textContent
} else { } else {
@@ -403,7 +421,7 @@
const loadMore = async (link, pane) => { const loadMore = async (link, pane) => {
if (pane.dataset.loading === '1') return if (pane.dataset.loading === '1') return
pane.dataset.loading = '1' pane.dataset.loading = '1'
const more = link.closest('.forum-more') const more = link.closest('.theme-pagination') || link.closest('.ajax-pag')
const loader = [...pane.children].find((item) => item.classList.contains('post_ajax_loader')) const loader = [...pane.children].find((item) => item.classList.contains('post_ajax_loader'))
more?.classList.add('is-loading') more?.classList.add('is-loading')
loader?.classList.add('is-loading') loader?.classList.add('is-loading')
@@ -414,7 +432,7 @@
const source = doc.getElementById(pane.id) const source = doc.getElementById(pane.id)
if (!source) throw new Error('内容不存在') if (!source) throw new Error('内容不存在')
const anchor = more || null const anchor = more || null
const incoming = [...source.children].filter((item) => !item.classList.contains('forum-tab-pane') && !item.classList.contains('forum-more') && !item.classList.contains('post_ajax_loader')) const incoming = [...source.children].filter((item) => !item.classList.contains('forum-tab-pane') && !item.classList.contains('theme-pagination') && !item.classList.contains('post_ajax_loader'))
incoming.forEach((item) => pane.insertBefore(item, anchor)) incoming.forEach((item) => pane.insertBefore(item, anchor))
const next = source.querySelector('[data-forum-more]') const next = source.querySelector('[data-forum-more]')
if (next && anchor) anchor.querySelector('[data-forum-more]')?.setAttribute('href', next.getAttribute('href')) if (next && anchor) anchor.querySelector('[data-forum-more]')?.setAttribute('href', next.getAttribute('href'))
@@ -449,7 +467,10 @@
} }
const more = event.target.closest('[data-forum-more]') const more = event.target.closest('[data-forum-more]')
if (more) { if (more) {
// The wrapper also carries .ajax-next for the theme's pagination styles;
// stop the generic handler from firing a second request.
event.preventDefault() event.preventDefault()
event.stopPropagation()
const pane = more.closest('.forum-tab-pane') const pane = more.closest('.forum-tab-pane')
if (pane) loadMore(more, pane) if (pane) loadMore(more, pane)
} }
+6 -4
View File
@@ -53,16 +53,17 @@ final class SchemaForum {
self::field('jio_forum_home_follow_plate', '版块栏目显示已关注版块', 'switcher', true), self::field('jio_forum_home_follow_plate', '版块栏目显示已关注版块', 'switcher', true),
self::field('jio_forum_home_recommend_plate', '关注栏目显示版块推荐', 'switcher', true), self::field('jio_forum_home_recommend_plate', '关注栏目显示版块推荐', 'switcher', true),
self::field('jio_forum_show_plate_cover', '显示版块封面', 'switcher', true), self::field('jio_forum_show_plate_cover', '显示版块封面', 'switcher', true),
self::field('jio_forum_plate_tab_active', '版块页默认栏目', 'number', 1, ['min' => 1, 'max' => 20, 'description' => '打开版块页面时默认显示第几个栏目']), self::field('jio_forum_plate_tab_active', '版块页默认栏目', 'number', 2, ['min' => 1, 'max' => 20, 'description' => '打开版块页面时默认显示第几个栏目']),
self::field('jio_forum_plate_tab_items', '版块页栏目', 'repeater', self::default_plate_tabs(), [ self::field('jio_forum_plate_tab_items', '版块页栏目', 'repeater', self::default_plate_tabs(), [
'addText' => '添加栏目', 'addText' => '添加栏目',
'description' => '版块页顶部的栏目条;留空则使用「最新发布 / 最近回复 / 热门 / 精华」默认栏目', 'description' => '版块页顶部的栏目条;留空则使用「全部 / 推荐 / 最新回复 / 热门 / 精华」默认栏目',
'fields' => [ 'fields' => [
self::field('title', '栏目标题', 'input', ''), self::field('title', '栏目标题', 'input', ''),
self::field('orderby', '排序方式', 'select', 'date', ['options' => self::options([ self::field('orderby', '排序方式', 'select', 'date', ['options' => self::options([
'date' => '发布时间', 'date' => '发布时间',
'modified' => '修改时间', 'modified' => '修改时间',
'last_reply' => '最新回复', 'last_reply' => '最新回复',
'popular' => '推荐指数',
'views' => '阅读量', 'views' => '阅读量',
'score' => '评分', 'score' => '评分',
'comment_count' => '回复数量', 'comment_count' => '回复数量',
@@ -116,8 +117,9 @@ final class SchemaForum {
/** Initial plate-page tab rows; the listing falls back to these when unset. */ /** Initial plate-page tab rows; the listing falls back to these when unset. */
private static function default_plate_tabs(): array { private static function default_plate_tabs(): array {
return [ return [
['title' => '最新发布', 'orderby' => 'date', 'filter' => '', 'style' => 'detail'], ['title' => '全部', 'orderby' => 'modified', 'filter' => '', 'style' => 'mini'],
['title' => '最近回复', 'orderby' => 'last_reply', 'filter' => '', 'style' => 'detail'], ['title' => '推荐', 'orderby' => 'popular', 'filter' => '', 'style' => 'detail'],
['title' => '最新回复', 'orderby' => 'last_reply', 'filter' => '', 'style' => 'detail'],
['title' => '热门', 'orderby' => 'views', 'filter' => '', 'style' => 'detail'], ['title' => '热门', 'orderby' => 'views', 'filter' => '', 'style' => 'detail'],
['title' => '精华', 'orderby' => 'modified', 'filter' => 'essence', 'style' => 'detail'], ['title' => '精华', 'orderby' => 'modified', 'filter' => 'essence', 'style' => 'detail'],
]; ];
+4
View File
@@ -66,6 +66,10 @@ final class Access {
return $user_id > 0 && (bool) get_user_meta($user_id, '_jio_forum_follow_' . $plate_id, true); return $user_id > 0 && (bool) get_user_meta($user_id, '_jio_forum_follow_' . $plate_id, true);
} }
public static function favorites(int $user_id, int $post_id): bool {
return $user_id > 0 && (bool) get_user_meta($user_id, '_jio_forum_favorite_' . $post_id, true);
}
public static function can_view(int $user_id, int $post_id): bool { public static function can_view(int $user_id, int $post_id): bool {
$post = get_post($post_id); $post = get_post($post_id);
if (!$post instanceof \WP_Post || $post->post_type !== Core::POST) { if (!$post instanceof \WP_Post || $post->post_type !== Core::POST) {
+85 -18
View File
@@ -7,8 +7,10 @@ defined('ABSPATH') || exit;
final class Cards { final class Cards {
/** @var array<int, \WP_Comment> */ /** @var array<int, \WP_Comment> */
private static array $hot = []; private static array $hot = [];
/** @var array<int, string> */
private static array $last_reply = [];
/** Prime authors, plates and hot replies for a page of posts in batch queries. */ /** Prime authors, plates, hot replies and last replies for a page of posts in batch queries. */
public static function prime(array $posts): void { public static function prime(array $posts): void {
$ids = $authors = $plates = []; $ids = $authors = $plates = [];
foreach ($posts as $post) { foreach ($posts as $post) {
@@ -26,6 +28,20 @@ final class Cards {
$post_id = (int) $comment->comment_post_ID; $post_id = (int) $comment->comment_post_ID;
if (isset($wanted[$post_id]) && !isset(self::$hot[$post_id])) self::$hot[$post_id] = $comment; if (isset($wanted[$post_id]) && !isset(self::$hot[$post_id])) self::$hot[$post_id] = $comment;
} }
self::prime_last_replies($ids);
}
/** @param int[] $ids */
private static function prime_last_replies(array $ids): void {
$missing = array_values(array_diff(array_unique(array_map('absint', $ids)), array_keys(self::$last_reply)));
if (!$missing) return;
global $wpdb;
$placeholders = implode(',', array_fill(0, count($missing), '%d'));
$sql = "SELECT comment_post_ID AS post_id, MAX(comment_date) AS last_date FROM {$wpdb->comments} WHERE comment_approved = '1' AND comment_post_ID IN ($placeholders) GROUP BY comment_post_ID";
$rows = $wpdb->get_results($wpdb->prepare($sql, $missing)); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
foreach ((array) $rows as $row) {
self::$last_reply[(int) $row->post_id] = (string) $row->last_date;
}
} }
public static function post_detail(\WP_Post $post, string $class = 'alone ajax-item', bool $show_plate = true, bool $show_topping = false): string { public static function post_detail(\WP_Post $post, string $class = 'alone ajax-item', bool $show_plate = true, bool $show_topping = false): string {
@@ -51,12 +67,21 @@ final class Cards {
$html .= '<div class="mr20 forum-user">' . self::avatar($author, 'avatar-img forum-avatar') . '<span class="show-sm ml6 flex ac" style="width: 90%;">' . $name . '</span></div>'; $html .= '<div class="mr20 forum-user">' . self::avatar($author, 'avatar-img forum-avatar') . '<span class="show-sm ml6 flex ac" style="width: 90%;">' . $name . '</span></div>';
$html .= '<div class="entry-info">' . self::title($post, 'forum-title flex ac', 'text-ellipsis', $show_topping); $html .= '<div class="entry-info">' . self::title($post, 'forum-title flex ac', 'text-ellipsis', $show_topping);
$html .= '<div class="flex ac jsb item-meta"><div class="meta-left em09-sm flex"><span class="hide-sm">' . $name . '</span>'; $html .= '<div class="flex ac jsb item-meta"><div class="meta-left em09-sm flex"><span class="hide-sm">' . $name . '</span>';
$html .= '<span class="icon-circle" title="' . esc_attr(sprintf('发布时间:%s', get_the_date('Y-m-d H:i:s', $post))) . '">' . esc_html(self::ago($post->post_date) . '发布') . '</span></div>'; $html .= self::reply_time($post) . '</div>';
$html .= '<div class="meta-right">' . self::icon_metas($post) . '</div></div></div>'; $html .= '<div class="meta-right">' . self::icon_metas($post) . '</div></div></div>';
return $html . '</article>'; return $html . '</article>';
} }
public static function title(\WP_Post $post, string $class, string $link_class = '', bool $show_topping = false): string { /** Reference mini-card meta: "最后回复" when replies exist, otherwise the publish time. */
private static function reply_time(\WP_Post $post): string {
$last = self::$last_reply[$post->ID] ?? '';
if ($last !== '') {
return '<span class="icon-circle" title="' . esc_attr(sprintf('最后回复:%s', $last)) . '">' . esc_html(self::ago($last) . '回复') . '</span>';
}
return '<span class="icon-circle" title="' . esc_attr(sprintf('发布时间:%s', get_the_date('Y-m-d H:i:s', $post))) . '">' . esc_html(self::ago($post->post_date) . '发布') . '</span>';
}
public static function title(\WP_Post $post, string $class, string $link_class = '', bool $show_topping = false, string $tag = 'h2'): string {
$id = $post->ID; $id = $post->ID;
$badges = ''; $badges = '';
if ($show_topping && get_post_meta($id, '_jio_forum_pinned', true)) $badges .= '<span class="badg badg-sm c-blue mr3">置顶</span>'; if ($show_topping && get_post_meta($id, '_jio_forum_pinned', true)) $badges .= '<span class="badg badg-sm c-blue mr3">置顶</span>';
@@ -70,7 +95,8 @@ final class Cards {
} }
$url = $post->post_status === 'publish' ? get_permalink($post) : Core::edit_url($id); $url = $post->post_status === 'publish' ? get_permalink($post) : Core::edit_url($id);
$link_class = $link_class !== '' ? ' class="' . esc_attr($link_class) . '"' : ''; $link_class = $link_class !== '' ? ' class="' . esc_attr($link_class) . '"' : '';
return '<h2 class="' . esc_attr($class) . '"><a' . $link_class . ' href="' . esc_url($url) . '" title="' . esc_attr($post->post_title) . '">' . $badges . esc_html($post->post_title) . '</a></h2>'; $tag = in_array($tag, ['h1', 'h2'], true) ? $tag : 'h2';
return '<' . $tag . ' class="' . esc_attr($class) . '"><a' . $link_class . ' href="' . esc_url($url) . '" title="' . esc_attr($post->post_title) . '">' . $badges . esc_html($post->post_title) . '</a></' . $tag . '>';
} }
/** Plate page header: blurred cover, title, stats and moderators (reference layout). */ /** Plate page header: blurred cover, title, stats and moderators (reference layout). */
@@ -94,6 +120,7 @@ final class Cards {
$excerpt = wp_trim_words(wp_strip_all_tags($plate->post_excerpt ?: $plate->post_content), 40); $excerpt = wp_trim_words(wp_strip_all_tags($plate->post_excerpt ?: $plate->post_content), 40);
$html = '<div class="plate-header-wrap"><div class="forum-header blur-header relative-h mb20">'; $html = '<div class="plate-header-wrap"><div class="forum-header blur-header relative-h mb20">';
$html .= '<div class="abs-blur-bg">' . $thumb . '</div><div class="absolute forum-mask"></div>'; $html .= '<div class="abs-blur-bg">' . $thumb . '</div><div class="absolute forum-mask"></div>';
$html .= self::plate_more_btns($plate);
if (get_post_meta($id, '_jio_forum_hot', true)) { if (get_post_meta($id, '_jio_forum_hot', true)) {
$html .= '<span class="img-badge top jb-red px12-sm">热门</span>'; $html .= '<span class="img-badge top jb-red px12-sm">热门</span>';
} }
@@ -107,18 +134,32 @@ final class Cards {
return $html . '</div></div></div>'; return $html . '</div></div></div>';
} }
/** Header corner actions: follow, search and share (reference `.more-btns`). */
private static function plate_more_btns(\WP_Post $plate): string {
$html = '<div class="flex ac more-btns">' . self::follow_button($plate->ID, '');
if (jio_get_setting('jio_forum_search_enabled', true)) {
$html .= '<a class="main-search-btn item" href="' . esc_url(add_query_arg(['type' => 'plate', 's' => $plate->post_title], home_url('/'))) . '" title="搜索"><i class="fa fas fa-search"></i></a>';
}
if (function_exists('jio_get_post_share_btn')) {
$html .= jio_get_post_share_btn($plate, 'item btn-share', true, true);
}
return $html . '</div>';
}
/** Plate moderator avatars row under the header. */ /** Plate moderator avatars row under the header. */
private static function plate_moderator_btns(\WP_Post $plate): string { private static function plate_moderator_btns(\WP_Post $plate): string {
$ids = array_values(array_unique(array_filter(array_merge( $ids = array_values(array_unique(array_filter(array_merge(
[(int) $plate->post_author], [(int) $plate->post_author],
array_map('absint', (array) get_post_meta($plate->ID, '_jio_forum_moderators', true)) array_map('absint', (array) get_post_meta($plate->ID, '_jio_forum_moderators', true))
)))); ))));
if (!$ids) return '';
$btns = ''; $btns = '';
foreach ($ids as $user_id) { foreach ($ids as $user_id) {
$user = get_userdata($user_id); $user = get_userdata($user_id);
if (!$user) continue; if (!$user) continue;
$btns .= '<a class="but c-white mr6 mt6" href="' . esc_url(get_author_posts_url($user_id)) . '" title="' . esc_attr($user->display_name) . '"><span class="avatar-mini avatar-img">' . get_avatar($user_id, 40) . '</span><span class="ml6">' . esc_html($user->display_name) . '</span></a>'; $btns .= '<a class="but c-white mr6 mt6" href="' . esc_url(get_author_posts_url($user_id)) . '" title="' . esc_attr($user->display_name) . '"><span class="avatar-mini mr6">' . get_avatar($user_id, 40) . '</span>' . esc_html($user->display_name) . '</a>';
}
if (is_user_logged_in() && Access::can_publish(get_current_user_id(), $plate->ID)) {
$btns .= '<a rel="nofollow" class="but c-white mr6 mt6" href="' . esc_url(add_query_arg('plate', $plate->ID, Core::edit_url())) . '"><i class="fa fas fa-plus mr6"></i>发布</a>';
} }
$html = '<div class="mt10 moderator-btns em09">' . $btns; $html = '<div class="mt10 moderator-btns em09">' . $btns;
if (is_user_logged_in() && !Access::is_moderator(get_current_user_id(), $plate->ID) && jio_get_setting('jio_forum_moderator_apply', true)) { if (is_user_logged_in() && !Access::is_moderator(get_current_user_id(), $plate->ID) && jio_get_setting('jio_forum_moderator_apply', true)) {
@@ -127,7 +168,8 @@ final class Cards {
return $html . '</div>'; return $html . '</div>';
} }
public static function plate_item(\WP_Post $plate): string { $url = get_permalink($plate); public static function plate_item(\WP_Post $plate): string {
$url = get_permalink($plate);
$counts = PlateStats::get($plate->ID); $counts = PlateStats::get($plate->ID);
$today = $counts['today'] > 0 ? '<span class="badg but-plate hide-sm flex0" title="' . esc_attr(sprintf('今日新帖 %d', $counts['today'])) . '"><i class="fa fas fa-comment-dots c-red mr3"></i>' . esc_html(sprintf('今日:%s', jio_cut_count($counts['today']))) . '</span>' : ''; $today = $counts['today'] > 0 ? '<span class="badg but-plate hide-sm flex0" title="' . esc_attr(sprintf('今日新帖 %d', $counts['today'])) . '"><i class="fa fas fa-comment-dots c-red mr3"></i>' . esc_html(sprintf('今日:%s', jio_cut_count($counts['today']))) . '</span>' : '';
$excerpt = wp_trim_words(wp_strip_all_tags($plate->post_excerpt ?: $plate->post_content), 30); $excerpt = wp_trim_words(wp_strip_all_tags($plate->post_excerpt ?: $plate->post_content), 30);
@@ -165,10 +207,10 @@ final class Cards {
public static function follow_button(int $plate_id, string $class): string { public static function follow_button(int $plate_id, string $class): string {
if (!is_user_logged_in()) { if (!is_user_logged_in()) {
return '<a class="btn-follow ' . esc_attr($class) . '" href="' . esc_url(jio_get_login_url(get_permalink($plate_id))) . '"><text>关注</text></a>'; return '<a class="btn-follow ' . esc_attr($class) . '" href="' . esc_url(jio_get_login_url(get_permalink($plate_id))) . '"><text><i class="fa far fa-heart mr6 em12"></i></text></a>';
} }
$followed = Access::follows(get_current_user_id(), $plate_id); $followed = Access::follows(get_current_user_id(), $plate_id);
return '<button type="button" class="btn-follow ' . esc_attr($class . ($followed ? ' active' : '')) . '" data-forum-action="follow" data-id="' . (int) $plate_id . '"><text>' . ($followed ? '已关注' : '关注') . '</text></button>'; return '<button type="button" class="btn-follow ' . esc_attr($class . ($followed ? ' active' : '')) . '" data-forum-action="follow" data-id="' . (int) $plate_id . '"><text><i class="fa ' . ($followed ? 'fas' : 'far') . ' fa-heart mr6 em12"></i></text></button>';
} }
public static function plate_button(int $plate_id): string { public static function plate_button(int $plate_id): string {
@@ -185,15 +227,29 @@ final class Cards {
} }
private static function author_link(int $user_id): string { private static function author_link(int $user_id): string {
return '<a class="display-name text-ellipsis" href="' . esc_url(get_author_posts_url($user_id)) . '">' . esc_html((string) get_the_author_meta('display_name', $user_id)) . '</a>'; $url = function_exists('jio_get_n') ? jio_get_n($user_id) : get_author_posts_url($user_id);
return '<a class="display-name text-ellipsis" href="' . esc_url($url) . '">' . esc_html((string) get_the_author_meta('display_name', $user_id)) . '</a>';
} }
/** Reference author row: avatar, name, publish time + views, follow and message actions. */
private static function author_box(\WP_Post $post): string { private static function author_box(\WP_Post $post): string {
$author = (int) $post->post_author; $author = (int) $post->post_author;
$name = function_exists('jio_get_user_name') ? jio_get_user_name(['id' => $author, 'class' => 'flex ac flex1']) : self::author_link($author); $name = function_exists('jio_get_user_name') ? jio_get_user_name(['id' => $author, 'class' => 'flex ac flex1']) : self::author_link($author);
$views = jio_cut_count((int) get_post_meta($post->ID, '_jio_forum_views', true)); $views = jio_cut_count((int) get_post_meta($post->ID, '_jio_forum_views', true));
$desc = '<span title="' . esc_attr(get_the_date('Y-m-d H:i:s', $post)) . '">' . esc_html(self::ago($post->post_date)) . '</span><span class="icon-spot">' . esc_html(sprintf('%s次阅读', $views)) . '</span>'; $desc = '<span title="' . esc_attr(get_the_date('Y-m-d H:i:s', $post)) . '">' . esc_html(self::ago($post->post_date) . '发布') . '</span><span class="icon-spot">' . esc_html(sprintf('%s次阅读', $views)) . '</span>';
return '<div class="user-info flex ac mb10">' . self::avatar($author, 'avatar-img') . '<div class="user-right flex flex1 ac jsb ml10"><div class="flex1 overflow-hidden"><div class="flex ac">' . $name . '</div><div class="px12-sm muted-2-color text-ellipsis mt3">' . $desc . '</div></div></div></div>'; $actions = '';
if (function_exists('jio_get_user_follow')) {
$actions .= jio_get_user_follow('px12-sm ml10 follow but c-red', $author);
}
if (function_exists('jio_get_messages_page_url')) {
$messages = jio_get_messages_page_url();
if ($messages !== '' && get_current_user_id() !== $author) {
$actions .= '<a class="ml6 but c-blue px12-sm" href="' . esc_url($messages) . '"><i class="fa far fa-envelope mr3"></i>私信</a>';
}
}
$html = '<div class="user-info flex ac mb10">' . self::avatar($author, 'avatar-img') . '<div class="user-right flex flex1 ac jsb ml10"><div class="flex1 overflow-hidden"><div class="flex ac">' . $name . '</div><div class="px12-sm muted-2-color text-ellipsis">' . $desc . '</div></div>';
if ($actions !== '') $html .= '<div class="flex0 user-action">' . $actions . '</div>';
return $html . '</div></div>';
} }
private static function status_badge(\WP_Post $post): string { private static function status_badge(\WP_Post $post): string {
@@ -205,6 +261,7 @@ final class Cards {
if (!has_post_thumbnail($post)) return ''; if (!has_post_thumbnail($post)) return '';
return '<div class="forum-lists-cover mb10"><div class="graphic imgbox-container" style="padding-bottom:45%">' . get_the_post_thumbnail($post, 'large', ['class' => 'fit-cover']) . '</div></div>'; return '<div class="forum-lists-cover mb10"><div class="graphic imgbox-container" style="padding-bottom:45%">' . get_the_post_thumbnail($post, 'large', ['class' => 'fit-cover']) . '</div></div>';
} }
private static function content(\WP_Post $post, bool $can_view): string { private static function content(\WP_Post $post, bool $can_view): string {
if (!$can_view) { if (!$can_view) {
return '<div class="hide-post mt6"><i class="fa fas fa-unlock-alt mr6"></i>此处内容已隐藏</div>'; return '<div class="hide-post mt6"><i class="fa fas fa-unlock-alt mr6"></i>此处内容已隐藏</div>';
@@ -237,23 +294,33 @@ final class Cards {
return $html . '</div>'; return $html . '</div>';
} }
/** Reference action row: score up/down group, reply count and share. */
private static function action_meta(\WP_Post $post): string { private static function action_meta(\WP_Post $post): string {
$id = $post->ID; $id = $post->ID;
$score = (int) get_post_meta($id, '_jio_forum_score', true);
$reply = (int) $post->comment_count;
$html = '<div class="action-meta flex0 flex jsb">'; $html = '<div class="action-meta flex0 flex jsb">';
if (jio_get_setting('jio_forum_score_enabled', true)) { if (jio_get_setting('jio_forum_score_enabled', true)) {
$html .= is_user_logged_in() $user_id = get_current_user_id();
? '<button type="button" class="item btn-score" data-forum-action="score" data-direction="up" data-id="' . $id . '"><i class="fa far fa-thumbs-up"></i><text>' . esc_html($score ? jio_cut_count($score) : '加分') . '</text></button>' $user_score = $user_id > 0 ? (int) get_user_meta($user_id, '_jio_forum_score_' . $id, true) : 0;
: '<a class="item btn-score" href="' . esc_url(jio_get_login_url(get_permalink($post))) . '"><i class="fa far fa-thumbs-up"></i><text>' . esc_html($score ? jio_cut_count($score) : '加分') . '</text></a>'; $score = (int) get_post_meta($id, '_jio_forum_score', true);
$html .= '<div class="score-btns">' . self::score_button($post, 'up', 'extra', $user_score > 0) . '<text>' . esc_html($score ? jio_cut_count($score) : '评分') . '</text>' . self::score_button($post, 'down', 'deduct', $user_score < 0) . '</div>';
} }
$reply = (int) $post->comment_count;
$html .= '<a rel="nofollow" href="' . esc_url(get_permalink($post) . '#comments') . '" class="item"><i class="fa far fa-comment"></i><text>' . esc_html($reply ? jio_cut_count($reply) : '回复') . '</text></a>'; $html .= '<a rel="nofollow" href="' . esc_url(get_permalink($post) . '#comments') . '" class="item"><i class="fa far fa-comment"></i><text>' . esc_html($reply ? jio_cut_count($reply) : '回复') . '</text></a>';
if ($post->post_status === 'publish' && function_exists('jio_get_post_share_btn')) { if ($post->post_status === 'publish' && function_exists('jio_get_post_share_btn')) {
$html .= jio_get_post_share_btn($post, 'item btn-share', true, false); $html .= jio_get_post_share_btn($post, 'item btn-share', true, true);
} }
return $html . '</div>'; return $html . '</div>';
} }
private static function score_button(\WP_Post $post, string $direction, string $kind, bool $active): string {
$icon = '<i class="fa fas fa-' . ($direction === 'up' ? 'plus' : 'minus') . '-circle"></i>';
$class = 'btn-score ' . $kind . ($active ? ' active' : '');
if (!is_user_logged_in()) {
return '<a href="' . esc_url(jio_get_login_url(get_permalink($post))) . '" class="' . esc_attr($class . ' signin-loader') . '" data-id="' . (int) $post->ID . '">' . $icon . '</a>';
}
return '<button type="button" class="' . esc_attr($class) . '" data-forum-action="score" data-direction="' . esc_attr($direction) . '" data-id="' . (int) $post->ID . '">' . $icon . '</button>';
}
private static function icon_metas(\WP_Post $post): string { private static function icon_metas(\WP_Post $post): string {
$id = $post->ID; $id = $post->ID;
return '<item><i class="fa far fa-eye mr3"></i>' . esc_html(jio_cut_count((int) get_post_meta($id, '_jio_forum_views', true))) . '</item>' return '<item><i class="fa far fa-eye mr3"></i>' . esc_html(jio_cut_count((int) get_post_meta($id, '_jio_forum_views', true))) . '</item>'
+20 -1
View File
@@ -30,13 +30,32 @@ final class Core {
add_action('untrashed_post', [self::class, 'restore_trashed_plate']); add_action('untrashed_post', [self::class, 'restore_trashed_plate']);
add_filter('wp_untrash_post_status', [self::class, 'restore_status'], 10, 2); add_filter('wp_untrash_post_status', [self::class, 'restore_status'], 10, 2);
add_filter('jio_is_show_sidebar', [self::class, 'show_home_sidebar']); add_filter('jio_is_show_sidebar', [self::class, 'show_home_sidebar']);
add_filter('jio_is_show_sidebar', [self::class, 'show_forum_sidebar']);
} }
/** The forum home renders its own sidebar column, so keep the layout slot open. */ /** The forum home renders its own sidebar column, so keep the layout slot open. */
public static function show_home_sidebar(bool $show): bool { public static function show_home_sidebar(bool $show): bool {
if ($show || !self::enabled() || !is_page_template('templates/pages/page-forums.php')) return $show; if ($show || !self::enabled() || !is_page_template('templates/pages/page-forums.php')) return $show;
return self::sidebar_has_widgets('forum_home');
}
/** Forum routes own their widget areas; a configured forum sidebar must open the column. */
public static function show_forum_sidebar(bool $show): bool {
if ($show || !self::enabled()) return $show;
$context = '';
if (is_singular(self::POST)) $context = 'forum_post';
elseif (is_singular(self::PLATE)) $context = 'forum_plate';
elseif (is_tax([self::CATEGORY, self::TOPIC, self::TAG])) $context = 'forum_term';
return $context !== '' && self::sidebar_has_widgets($context);
}
private static function sidebar_has_widgets(string $context): bool {
$cfg = function_exists('jio_get_sidebar_config_safe') ? jio_get_sidebar_config_safe() : []; $cfg = function_exists('jio_get_sidebar_config_safe') ? jio_get_sidebar_config_safe() : [];
return !empty($cfg['enabled']); if (empty($cfg['enabled'])) return false;
foreach ([$context . '_sidebar', 'all_sidebar_top', 'all_sidebar_bottom'] as $area) {
if (is_active_sidebar($area)) return true;
}
return false;
} }
public static function enabled(): bool { public static function enabled(): bool {
+11 -11
View File
@@ -11,7 +11,7 @@ final class EditView {
$post = $edit_id > 0 ? get_post($edit_id) : null; $post = $edit_id > 0 ? get_post($edit_id) : null;
if ($edit_id > 0 && (!$post instanceof \WP_Post || !Access::can_manage_post(get_current_user_id(), $edit_id))) { if ($edit_id > 0 && (!$post instanceof \WP_Post || !Access::can_manage_post(get_current_user_id(), $edit_id))) {
status_header(403); status_header(403);
echo '<main id="forum" class="jio-forum forum-edit"><div class="container"><div class="zib-widget full-widget-sm text-center" style="min-height:60vh;">' . jio_get_null('抱歉!您暂无编辑此内容的权限', 60, 'null-post.svg') . '</div></div></main>'; echo '<main id="forum" class="jio-forum forum-edit"><div class="container"><div class="jio-panel full-widget-sm text-center" style="min-height:60vh;">' . jio_get_null('抱歉!您暂无编辑此内容的权限', 60, 'null-post.svg') . '</div></div></main>';
get_footer(); get_footer();
return; return;
} }
@@ -25,7 +25,7 @@ final class EditView {
wp_enqueue_editor(); wp_enqueue_editor();
echo '<main id="forum" class="jio-forum forum-edit"><form id="bbs-posts-edit" data-forum-form="post" data-id="' . (int) $edit_id . '"><div class="container"><div class="content-wrap"><div class="content-layout">'; echo '<main id="forum" class="jio-forum forum-edit"><form id="bbs-posts-edit" data-forum-form="post" data-id="' . (int) $edit_id . '"><div class="container"><div class="content-wrap"><div class="content-layout">';
echo '<div class="zib-widget full-widget-sm editor-main-box" style="min-height:60vh">'; echo '<div class="jio-panel full-widget-sm editor-main-box" style="min-height:60vh">';
if ($user_id <= 0) { if ($user_id <= 0) {
echo jio_get_null('请先登录后再发布', 40, 'null-user.svg') . '<p class="text-center"><a class="but jb-blue padding-lg" href="' . esc_url(jio_get_login_url(Core::edit_url($edit_id))) . '"><i class="fa fas fa-sign-in-alt mr6"></i>登录</a></p>'; echo jio_get_null('请先登录后再发布', 40, 'null-user.svg') . '<p class="text-center"><a class="but jb-blue padding-lg" href="' . esc_url(jio_get_login_url(Core::edit_url($edit_id))) . '"><i class="fa fas fa-sign-in-alt mr6"></i>登录</a></p>';
echo '</div>'; echo '</div>';
@@ -73,7 +73,7 @@ final class EditView {
echo self::vote_box($vote_options, $post); echo self::vote_box($vote_options, $post);
echo self::hide_box($post); echo self::hide_box($post);
if (!$post && function_exists('jio_render_captcha') && function_exists('jio_is_captcha_enabled_for') && jio_is_captcha_enabled_for('forum_post')) { if (!$post && function_exists('jio_render_captcha') && function_exists('jio_is_captcha_enabled_for') && jio_is_captcha_enabled_for('forum_post')) {
echo '<div class="zib-widget mb10-sm mb20 jio-forum__captcha">'; echo '<div class="jio-panel mb10-sm mb20 jio-forum__captcha">';
jio_render_captcha('forum_post'); jio_render_captcha('forum_post');
echo '</div>'; echo '</div>';
} }
@@ -90,7 +90,7 @@ final class EditView {
private static function submit_box(?\WP_Post $post): string { private static function submit_box(?\WP_Post $post): string {
$last = $post ? get_the_modified_time('Y-m-d H:i:s', $post->ID) : ''; $last = $post ? get_the_modified_time('Y-m-d H:i:s', $post->ID) : '';
$html = '<div class="zib-widget mb10-sm mb20"><div class="but-average mb20 main-radius">'; $html = '<div class="jio-panel mb10-sm mb20"><div class="but-average mb20 main-radius">';
if (!$post || !in_array($post->post_status, ['publish', 'pending'], true)) { if (!$post || !in_array($post->post_status, ['publish', 'pending'], true)) {
$html .= '<button type="button" class="but jb-green padding-10" data-forum-draft><i class="fa far fa-save mr6"></i>保存草稿</button>'; $html .= '<button type="button" class="but jb-green padding-10" data-forum-draft><i class="fa far fa-save mr6"></i>保存草稿</button>';
} }
@@ -109,7 +109,7 @@ final class EditView {
foreach (['pending' => '待审核', 'draft' => '草稿', 'publish' => '发布'] as $key => $label) { foreach (['pending' => '待审核', 'draft' => '草稿', 'publish' => '发布'] as $key => $label) {
$list .= '<span data-forum-status="' . $key . '" class="badg mr3 p2-10 pointer' . ($key === $status ? ' active' : '') . '">' . $label . '</span>'; $list .= '<span data-forum-status="' . $key . '" class="badg mr3 p2-10 pointer' . ($key === $status ? ' active' : '') . '">' . $label . '</span>';
} }
return '<div class="zib-widget mb10 type-drop"><div class="flex ac jsb"><div class="flex ac c-red"><i class="fa fas fa-thumbtack mr10"></i>设置状态</div><div class="em09">' . $list . '</div></div><input type="hidden" name="post_status" value="' . esc_attr($status) . '"></div>'; return '<div class="jio-panel mb10 type-drop"><div class="flex ac jsb"><div class="flex ac c-red"><i class="fa fas fa-thumbtack mr10"></i>设置状态</div><div class="em09">' . $list . '</div></div><input type="hidden" name="post_status" value="' . esc_attr($status) . '"></div>';
} }
private static function allow_view_box(string $current): string { private static function allow_view_box(string $current): string {
@@ -118,7 +118,7 @@ final class EditView {
foreach ($labels as $key => $label) { foreach ($labels as $key => $label) {
$options .= '<div data-forum-view="' . $key . '" class="pointer but p2-10 mb6' . ($key === $current ? ' active' : '') . '">' . esc_html($label) . '</div>'; $options .= '<div data-forum-view="' . $key . '" class="pointer but p2-10 mb6' . ($key === $current ? ' active' : '') . '">' . esc_html($label) . '</div>';
} }
return '<div class="allow-view-drop drop-select dropdown zib-widget mb10-sm mb20"><div data-toggle-class="open" data-target=".allow-view-drop" class="flex ac jsb drop-btn"><div class="flex ac"><i class="fa far fa-eye mr6"></i>阅读权限</div><div class="flex ac"><span name="view_limit" class="em09 muted-2-color">' . esc_html($labels[$current] ?? '公开') . '</span><i class="ml6 fa fas fa-angle-right em12"></i></div></div><div class="dropdown-menu fluid"><div class="padding-10 flex jsb muted-2-color em09">设置阅读权限<span data-toggle-class="close" data-target=".allow-view-drop" class="pointer">完成</span></div><div class="mini-scrollbar padding-w10 select-drop-box">' . $options . '</div></div></div>'; return '<div class="allow-view-drop drop-select dropdown jio-panel mb10-sm mb20"><div data-toggle-class="open" data-target=".allow-view-drop" class="flex ac jsb drop-btn"><div class="flex ac"><i class="fa far fa-eye mr6"></i>阅读权限</div><div class="flex ac"><span name="view_limit" class="em09 muted-2-color">' . esc_html($labels[$current] ?? '公开') . '</span><i class="ml6 fa fas fa-angle-right em12"></i></div></div><div class="dropdown-menu fluid"><div class="padding-10 flex jsb muted-2-color em09">设置阅读权限<span data-toggle-class="close" data-target=".allow-view-drop" class="pointer">完成</span></div><div class="mini-scrollbar padding-w10 select-drop-box">' . $options . '</div></div></div>';
} }
private static function plate_box(int $current): string { private static function plate_box(int $current): string {
@@ -128,7 +128,7 @@ final class EditView {
$options .= '<div data-forum-plate="' . (int) $plate->ID . '" class="pointer but p2-10 mb6' . ((int) $plate->ID === $current ? ' active' : '') . '">' . esc_html($plate->post_title) . '</div>'; $options .= '<div data-forum-plate="' . (int) $plate->ID . '" class="pointer but p2-10 mb6' . ((int) $plate->ID === $current ? ' active' : '') . '">' . esc_html($plate->post_title) . '</div>';
} }
$name = $current > 0 ? get_the_title($current) : '请选择'; $name = $current > 0 ? get_the_title($current) : '请选择';
return '<div class="plate-drop drop-select dropdown zib-widget mb10-sm mb20"><div data-toggle-class="open" data-target=".plate-drop" class="flex ac jsb drop-btn"><div class="flex ac"><i class="fa fas fa-cube mr6"></i>版块</div><div class="flex ac"><span name="plate_id" class="em09 muted-2-color text-ellipsis">' . esc_html($name) . '</span><i class="ml6 fa fas fa-angle-right em12"></i></div></div><input type="hidden" name="plate_id" value="' . ($current ?: '') . '"><div class="dropdown-menu fluid"><div class="mini-scrollbar padding-w10 select-drop-box">' . $options . '</div></div></div>'; return '<div class="plate-drop drop-select dropdown jio-panel mb10-sm mb20"><div data-toggle-class="open" data-target=".plate-drop" class="flex ac jsb drop-btn"><div class="flex ac"><i class="fa fas fa-cube mr6"></i>版块</div><div class="flex ac"><span name="plate_id" class="em09 muted-2-color text-ellipsis">' . esc_html($name) . '</span><i class="ml6 fa fas fa-angle-right em12"></i></div></div><input type="hidden" name="plate_id" value="' . ($current ?: '') . '"><div class="dropdown-menu fluid"><div class="mini-scrollbar padding-w10 select-drop-box">' . $options . '</div></div></div>';
} }
/** @param string[] $selected */ /** @param string[] $selected */
@@ -140,7 +140,7 @@ final class EditView {
$active = in_array($term->name, $selected, true); $active = in_array($term->name, $selected, true);
$options .= '<label class="but p2-10 mb6 pointer' . ($active ? ' active' : '') . '"><input type="checkbox" data-forum-' . esc_attr($field) . ' value="' . esc_attr($term->name) . '"' . checked($active, true, false) . ' hidden> ' . esc_html($term->name) . '</label>'; $options .= '<label class="but p2-10 mb6 pointer' . ($active ? ' active' : '') . '"><input type="checkbox" data-forum-' . esc_attr($field) . ' value="' . esc_attr($term->name) . '"' . checked($active, true, false) . ' hidden> ' . esc_html($term->name) . '</label>';
} }
return '<div class="' . esc_attr($field) . '-drop drop-select dropdown zib-widget mb10-sm mb20"><div data-toggle-class="open" data-target=".' . esc_attr($field) . '-drop" class="flex ac jsb drop-btn"><div class="flex ac"><i class="fa fas ' . esc_attr($icon) . ' mr6"></i>' . esc_html($label) . '</div><div class="flex ac"><span class="em09 muted-2-color text-ellipsis" data-forum-' . esc_attr($field) . '-label>' . esc_html($selected ? implode('、', array_slice($selected, 0, 2)) : '请选择') . '</span><i class="ml6 fa fas fa-angle-right em12"></i></div></div><input type="hidden" name="' . esc_attr($field) . '" value="' . esc_attr(implode(',', $selected)) . '"><div class="dropdown-menu fluid"><div class="mini-scrollbar padding-w10 select-drop-box">' . $options . '</div></div></div>'; return '<div class="' . esc_attr($field) . '-drop drop-select dropdown jio-panel mb10-sm mb20"><div data-toggle-class="open" data-target=".' . esc_attr($field) . '-drop" class="flex ac jsb drop-btn"><div class="flex ac"><i class="fa fas ' . esc_attr($icon) . ' mr6"></i>' . esc_html($label) . '</div><div class="flex ac"><span class="em09 muted-2-color text-ellipsis" data-forum-' . esc_attr($field) . '-label>' . esc_html($selected ? implode('、', array_slice($selected, 0, 2)) : '请选择') . '</span><i class="ml6 fa fas fa-angle-right em12"></i></div></div><input type="hidden" name="' . esc_attr($field) . '" value="' . esc_attr(implode(',', $selected)) . '"><div class="dropdown-menu fluid"><div class="mini-scrollbar padding-w10 select-drop-box">' . $options . '</div></div></div>';
} }
private static function type_box(string $current): string { private static function type_box(string $current): string {
@@ -153,7 +153,7 @@ final class EditView {
foreach ($options as $key => $label) { foreach ($options as $key => $label) {
$list .= '<span data-forum-type-set="' . $key . '" class="but p2-10 pointer' . ($key === $current ? ' active' : '') . '">' . esc_html($label) . '</span>'; $list .= '<span data-forum-type-set="' . $key . '" class="but p2-10 pointer' . ($key === $current ? ' active' : '') . '">' . esc_html($label) . '</span>';
} }
return '<div class="type-drop zib-widget mb10-sm mb20"><div class="flex ac jsb"><div class="flex ac"><i class="fa fas fa-delicious mr6"></i>发布类型</div><div class="but-average radius em09">' . $list . '</div></div></div>'; return '<div class="type-drop jio-panel mb10-sm mb20"><div class="flex ac jsb"><div class="flex ac"><i class="fa fas fa-delicious mr6"></i>发布类型</div><div class="but-average radius em09">' . $list . '</div></div></div>';
} }
/** Hidden-content composer: the block becomes a site visibility shortcode on save. */ /** Hidden-content composer: the block becomes a site visibility shortcode on save. */
@@ -162,7 +162,7 @@ final class EditView {
foreach (['logged' => '登录可见', 'reply' => '评论可见', 'vip1' => '会员可见', 'payshow' => '付费可见'] as $key => $label) { foreach (['logged' => '登录可见', 'reply' => '评论可见', 'vip1' => '会员可见', 'payshow' => '付费可见'] as $key => $label) {
$types .= '<span data-forum-hide-type="' . $key . '" class="mb6 mr6 badg p2-10 pointer' . ($key === 'logged' ? ' active' : '') . '">' . esc_html($label) . '</span>'; $types .= '<span data-forum-hide-type="' . $key . '" class="mb6 mr6 badg p2-10 pointer' . ($key === 'logged' ? ' active' : '') . '">' . esc_html($label) . '</span>';
} }
return '<div class="hide-type-drop drop-select dropdown zib-widget mb10-sm mb20"><div data-toggle-class="open" data-target=".hide-type-drop" class="flex ac jsb drop-btn"><span class="flex ac"><i class="far fa-eye-slash mr6"></i>隐藏内容</span><i class="ml6 fa fas fa-angle-right em12"></i></div>' return '<div class="hide-type-drop drop-select dropdown jio-panel mb10-sm mb20"><div data-toggle-class="open" data-target=".hide-type-drop" class="flex ac jsb drop-btn"><span class="flex ac"><i class="far fa-eye-slash mr6"></i>隐藏内容</span><i class="ml6 fa fas fa-angle-right em12"></i></div>'
. '<input type="hidden" name="hide_type" value="">' . '<input type="hidden" name="hide_type" value="">'
. '<div class="dropdown-menu fluid"><div class="mini-scrollbar padding-w10 select-drop-box"><div class="em09 muted-3-color mb10">添加额外的隐藏内容</div><div class="flex hh">' . $types . '</div>' . '<div class="dropdown-menu fluid"><div class="mini-scrollbar padding-w10 select-drop-box"><div class="em09 muted-3-color mb10">添加额外的隐藏内容</div><div class="flex hh">' . $types . '</div>'
. '<textarea rows="6" name="hide_content" class="form-control input-textarea" placeholder="在此输入需要隐藏的内容"></textarea></div></div></div>'; . '<textarea rows="6" name="hide_content" class="form-control input-textarea" placeholder="在此输入需要隐藏的内容"></textarea></div></div></div>';
@@ -171,7 +171,7 @@ final class EditView {
/** @param string[] $options */ /** @param string[] $options */
private static function vote_box(array $options, ?\WP_Post $post): string { private static function vote_box(array $options, ?\WP_Post $post): string {
$type = $post ? (string) get_post_meta($post->ID, '_jio_forum_type', true) : 'normal'; $type = $post ? (string) get_post_meta($post->ID, '_jio_forum_type', true) : 'normal';
return '<div class="type-drop zib-widget mb10-sm mb20 forum-type-vote"' . ($type === 'vote' ? '' : ' hidden') . '><div class="mb10"><i class="fa fas fa-poll mr6"></i>投票设置</div>' return '<div class="type-drop jio-panel mb10-sm mb20 forum-type-vote"' . ($type === 'vote' ? '' : ' hidden') . '><div class="mb10"><i class="fa fas fa-poll mr6"></i>投票设置</div>'
. '<input class="form-control mb6" name="vote_title" maxlength="60" placeholder="投票标题" value="' . esc_attr($post ? (string) get_post_meta($post->ID, '_jio_forum_vote_title', true) : '') . '">' . '<input class="form-control mb6" name="vote_title" maxlength="60" placeholder="投票标题" value="' . esc_attr($post ? (string) get_post_meta($post->ID, '_jio_forum_vote_title', true) : '') . '">'
. '<select class="form-control mb6" name="vote_type"><option value="single">单选</option><option value="multiple">多选</option><option value="pk">双选 PK</option></select>' . '<select class="form-control mb6" name="vote_type"><option value="single">单选</option><option value="multiple">多选</option><option value="pk">双选 PK</option></select>'
. '<textarea class="form-control mb6" name="vote_options" rows="4" placeholder="每行一个选项">' . esc_textarea(implode("\n", $options)) . '</textarea>' . '<textarea class="form-control mb6" name="vote_options" rows="4" placeholder="每行一个选项">' . esc_textarea(implode("\n", $options)) . '</textarea>'
+2 -2
View File
@@ -114,7 +114,7 @@ final class HomeContent {
if ($html === '') { if ($html === '') {
$html = jio_get_null('暂时没有版块', 60, 'null.svg', '', 0, 170) . '<div class="text-center mt20">' . self::create_plate_button('but hollow c-blue padding-lg') . '</div>'; $html = jio_get_null('暂时没有版块', 60, 'null.svg', '', 0, 170) . '<div class="text-center mt20">' . self::create_plate_button('but hollow c-blue padding-lg') . '</div>';
} }
return '<div class="ajax-item">' . $html . '</div>'; return '<div class="ajax-item">' . $html . '</div><div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
} }
/** @param \WP_Post[] $plates */ /** @param \WP_Post[] $plates */
@@ -171,7 +171,7 @@ final class HomeContent {
} }
if ($page < (int) $query->max_num_pages) { if ($page < (int) $query->max_num_pages) {
$next = add_query_arg(['index' => $index, 'fpage' => $page + 1], remove_query_arg(['index', 'fpage'])); $next = add_query_arg(['index' => $index, 'fpage' => $page + 1], remove_query_arg(['index', 'fpage']));
$html .= '<div class="text-center theme-pagination forum-more"><a class="but hollow c-blue" href="' . esc_url($next) . '" data-forum-more>加载更多</a></div>'; $html .= '<div class="text-center theme-pagination ajax-pag"><div class="next-page ajax-next"><a href="' . esc_url($next) . '" data-forum-more><i class="fas fa-angle-right"></i>加载更多</a></div></div>';
} }
$html .= '<div class="post_ajax_loader">' . HomeTabs::placeholder('posts', 2) . '</div>'; $html .= '<div class="post_ajax_loader">' . HomeTabs::placeholder('posts', 2) . '</div>';
return $html; return $html;
+4 -4
View File
@@ -44,7 +44,7 @@ final class HomeTabs {
]; ];
} }
if (Access::is_admin(get_current_user_id())) { if (Access::is_admin(get_current_user_id())) {
$tabs[] = ['title' => '待审核', 'type' => 'pending', 'orderby' => 'date', 'filter' => '', 'style' => 'mini']; $tabs[] = ['title' => '<i class="fa fas fa-check-circle mr3"></i>待审核', 'type' => 'pending', 'orderby' => 'date', 'filter' => '', 'style' => 'mini'];
} }
return $tabs ? array_combine(range(1, count($tabs)), $tabs) : []; return $tabs ? array_combine(range(1, count($tabs)), $tabs) : [];
} }
@@ -64,12 +64,12 @@ final class HomeTabs {
foreach ($tabs as $index => $tab) { foreach ($tabs as $index => $tab) {
$id = 'home-tab-' . $index; $id = 'home-tab-' . $index;
$href = add_query_arg('index', $index, $base); $href = add_query_arg('index', $index, $base);
$label = esc_html($tab['title']); $nav .= '<li' . ($index === $active ? ' class="active"' : '') . '><a href="' . esc_url($href) . '" data-forum-tab="#' . $id . '">' . $tab['title'] . '</a></li>';
$nav .= '<li' . ($index === $active ? ' class="active"' : '') . '><a href="' . esc_url($href) . '" data-forum-tab="#' . $id . '">' . $label . '</a></li>'; $panes .= '<div class="forum-tab-pane ajaxpager tab-pane fade' . ($index === $active ? ' in active' : '') . '" id="' . $id . '" data-href="' . esc_url($href) . '">';
$panes .= '<div class="forum-tab-pane tab-pane fade' . ($index === $active ? ' in active' : '') . '" id="' . $id . '" data-href="' . esc_url($href) . '">';
if ($index === $active) { if ($index === $active) {
$panes .= HomeContent::render($tab, $index); $panes .= HomeContent::render($tab, $index);
} else { } else {
$panes .= '<span class="post_ajax_trigger hide"><a href="' . esc_url($href) . '" class="ajax_load ajax-next ajax-open"></a></span>';
$panes .= '<div class="post_ajax_loader">' . self::placeholder($tab['type'] === 'plate' ? 'plate' : 'posts', $tab['type'] === 'plate' ? 2 : 4) . '</div>'; $panes .= '<div class="post_ajax_loader">' . self::placeholder($tab['type'] === 'plate' ? 'plate' : 'posts', $tab['type'] === 'plate' ? 2 : 4) . '</div>';
} }
$panes .= '</div>'; $panes .= '</div>';
+11 -25
View File
@@ -16,43 +16,29 @@ final class HomeView {
echo '<main id="forum" class="jio-forum forum-home">'; echo '<main id="forum" class="jio-forum forum-home">';
\Jio\WidgetSystem\WidgetAreas::render('forum_home_top_fluid', 'fluid-widget-wrap'); \Jio\WidgetSystem\WidgetAreas::render('forum_home_top_fluid', 'fluid-widget-wrap');
echo '<div class="container"><div class="content-wrap"><div class="content-layout">'; echo '<div class="container"><div class="content-wrap"><div class="content-layout">';
echo '<div class="forum-home__main">';
HomeTabs::render(); HomeTabs::render();
echo '</div></div></div>';
self::sidebar();
echo '</div>'; echo '</div>';
self::sidebar();
echo '</div></div>';
\Jio\WidgetSystem\WidgetAreas::render('forum_home_bottom_fluid', 'fluid-widget-wrap'); \Jio\WidgetSystem\WidgetAreas::render('forum_home_bottom_fluid', 'fluid-widget-wrap');
if (get_current_user_id() > 0 && (Access::is_admin(get_current_user_id()) || jio_get_setting('jio_forum_user_plates', true))) {
self::plate_dialog();
}
echo '</main>'; echo '</main>';
get_footer(); get_footer();
} }
/** Sidebar is widget-only, exactly like the reference home column. */
private static function sidebar(): void { private static function sidebar(): void {
$plate_name = (string) jio_get_setting('jio_forum_plate_name', '版块') ?: '版块'; if (!jio_is_show_sidebar()) return;
echo '<div class="sidebar forum-home__sidebar">'; echo '<div class="sidebar">';
if (is_user_logged_in()) { \Jio\WidgetSystem\WidgetAreas::render('all_sidebar_top');
echo '<div class="jio-widget-box"><a class="but jb-blue btn-block" href="' . esc_url(Core::edit_url()) . '"><i class="fa fas fa-edit mr6"></i>发布' . esc_html((string) jio_get_setting('jio_forum_post_name', '帖子')) . '</a>';
if (jio_get_setting('jio_forum_user_plates', true)) {
echo '<button type="button" class="but hollow c-blue btn-block mt10" data-forum-open="plate">创建' . esc_html($plate_name) . '</button>';
self::plate_dialog();
}
echo '</div>';
} else {
echo '<div class="jio-widget-box"><a class="but jb-blue btn-block" href="' . esc_url(jio_get_login_url(Core::edit_url())) . '"><i class="fa fas fa-sign-in-alt mr6"></i>登录后发帖</a></div>';
}
\Jio\WidgetSystem\WidgetAreas::render('forum_home_sidebar'); \Jio\WidgetSystem\WidgetAreas::render('forum_home_sidebar');
$plates = get_posts(['post_type' => Core::PLATE, 'post_status' => 'publish', 'posts_per_page' => 8, 'orderby' => 'modified']); \Jio\WidgetSystem\WidgetAreas::render('all_sidebar_bottom');
if ($plates) {
PlateStats::prime(wp_list_pluck($plates, 'ID'));
echo '<div class="jio-widget-box"><h4 class="title-theme mb10">热门' . esc_html($plate_name) . '</h4>';
foreach ($plates as $plate) {
$stats = PlateStats::get($plate->ID);
echo '<a class="jio-forum__widget-link" href="' . esc_url(get_permalink($plate)) . '"><span class="text-ellipsis">' . esc_html($plate->post_title) . '</span><span>' . esc_html(jio_cut_count($stats['posts'])) . '</span></a>';
}
echo '</div>';
}
echo '</div>'; echo '</div>';
} }
/** Plate creation dialog, triggered by `data-forum-open="plate"` anywhere on the page. */
private static function plate_dialog(): void { private static function plate_dialog(): void {
echo '<dialog class="jio-forum__dialog" data-forum-dialog="plate"><form data-forum-form="plate"><h2>创建版块</h2>' echo '<dialog class="jio-forum__dialog" data-forum-dialog="plate"><form data-forum-form="plate"><h2>创建版块</h2>'
. '<label>名称<input name="title" required maxlength="80"></label>' . '<label>名称<input name="title" required maxlength="80"></label>'
+29 -28
View File
@@ -14,11 +14,9 @@ final class ListingView {
return; return;
} }
self::open($plate->post_title, 'plate', $plate); self::open($plate->post_title, 'plate', $plate);
\Jio\WidgetSystem\WidgetAreas::render('forum_plate_top_fluid'); \Jio\WidgetSystem\WidgetAreas::render('forum_plate_top_fluid', 'fluid-widget-wrap');
echo '<div class="fixed-wrap"><div class="fixed-wrap-content">';
echo Cards::plate_header($plate); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo Cards::plate_header($plate); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo self::tab_bar($plate); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo self::tab_bar($plate); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div></div>';
self::close('plate'); self::close('plate');
} }
@@ -31,43 +29,41 @@ final class ListingView {
return; return;
} }
self::open($term->name, 'term', null, $term); self::open($term->name, 'term', null, $term);
\Jio\WidgetSystem\WidgetAreas::render('forum_term_top_fluid'); \Jio\WidgetSystem\WidgetAreas::render('forum_term_top_fluid', 'fluid-widget-wrap');
echo '<div class="zib-widget">'; echo self::term_header($term); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo self::tab_bar(null, $term); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo self::tab_bar(null, $term); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
self::close('term'); self::close('term');
} }
private static function term_header(\WP_Term $term): string {
return '<div class="term-main text-center mb20"><h1 class="forum-title em14">' . esc_html($term->name) . '</h1>'
. ($term->description !== '' ? '<p class="muted-2-color em09 mt6">' . esc_html($term->description) . '</p>' : '')
. '</div>';
}
private static function open(string $title, string $context, ?\WP_Post $plate, ?\WP_Term $term = null): void { private static function open(string $title, string $context, ?\WP_Post $plate, ?\WP_Term $term = null): void {
echo '<main id="forum" class="jio-forum forum-listing forum-listing--' . esc_attr($context) . '">'; echo '<main id="forum" class="jio-forum forum-listing forum-listing--' . esc_attr($context) . '">';
echo '<div class="container"><div class="content-wrap"><div class="content-layout">'; echo '<div class="container"><div class="content-wrap"><div class="content-layout">';
echo '<div class="jio-forum__list-main">';
if ($plate) {
$plate_name = (string) jio_get_setting('jio_forum_plate_name', '版块') ?: '版块';
echo '<nav class="jio-forum__breadcrumbs" aria-label="路径"><a href="' . esc_url(home_url('/')) . '">首页</a><span>/</span><a href="' . esc_url(Core::home_url()) . '">论坛</a><span>/</span><span>' . esc_html($title !== '' ? $title : $plate_name) . '</span></nav>';
} elseif ($term instanceof \WP_Term) {
echo '<div class="zib-widget"><div class="box-body notop"><div class="title-theme">' . esc_html($term->name) . '</div></div>';
if ($term->description !== '') echo '<p class="muted-2-color em09 padding-w10">' . esc_html($term->description) . '</p>';
echo '</div>';
}
} }
private static function close(string $context): void { private static function close(string $context): void {
echo '</div>'; echo '</div>';
self::sidebar($context); self::sidebar($context);
echo '</div></div>'; echo '</div></div>';
\Jio\WidgetSystem\WidgetAreas::render('forum_' . $context . '_bottom_fluid'); \Jio\WidgetSystem\WidgetAreas::render('forum_' . $context . '_bottom_fluid', 'fluid-widget-wrap');
echo '</main>'; echo '</main>';
} }
private static function sidebar(string $context): void { private static function sidebar(string $context): void {
if (!jio_is_show_sidebar()) return; if (!jio_is_show_sidebar()) return;
echo '<div class="sidebar">'; echo '<div class="sidebar">';
\Jio\WidgetSystem\WidgetAreas::render('all_sidebar_top');
\Jio\WidgetSystem\WidgetAreas::render('forum_' . $context . '_sidebar'); \Jio\WidgetSystem\WidgetAreas::render('forum_' . $context . '_sidebar');
\Jio\WidgetSystem\WidgetAreas::render('all_sidebar_bottom');
echo '</div>'; echo '</div>';
} }
/** Plate tab bar + panes; the first tab renders server-side, the rest load by `?plate_tab=N`. */ /** Plate tab bar + panes; the active tab renders server-side, the rest load by `?plate_tab=N`. */
public static function tab_bar(?\WP_Post $plate = null, ?\WP_Term $term = null): string { public static function tab_bar(?\WP_Post $plate = null, ?\WP_Term $term = null): string {
$tabs = self::tabs($plate); $tabs = self::tabs($plate);
if (!$tabs) return ''; if (!$tabs) return '';
@@ -78,14 +74,17 @@ final class ListingView {
foreach ($tabs as $index => $tab) { foreach ($tabs as $index => $tab) {
$id = 'plate-tab-' . $index; $id = 'plate-tab-' . $index;
$href = add_query_arg('plate_tab', $index, is_string($base) ? $base : Core::home_url()); $href = add_query_arg('plate_tab', $index, is_string($base) ? $base : Core::home_url());
$nav .= '<li' . ($index === $active ? ' class="active"' : '') . '><a href="' . esc_url($href) . '" data-forum-tab="#' . $id . '" data-plate-tab="' . $index . '">' . $tab['title'] . '</a></li>'; $nav .= '<li' . ($index === $active ? ' class="active"' : '') . '><a href="' . esc_url($href) . '" data-forum-tab="#' . $id . '">' . $tab['title'] . '</a></li>';
$panes .= '<div class="forum-tab-pane tab-pane fade' . ($index === $active ? ' in active' : '') . '" id="' . $id . '" data-href="' . esc_url($href) . '">'; $panes .= '<div class="forum-tab-pane ajaxpager tab-pane fade' . ($index === $active ? ' in active' : '') . '" id="' . $id . '" data-href="' . esc_url($href) . '">';
$panes .= $index === $active if ($index === $active) {
? self::pane($tab, $plate, $term, $index) $panes .= self::pane($tab, $plate, $term, $index);
: '<div class="post_ajax_loader">' . HomeTabs::placeholder('posts', 3) . '</div>'; } else {
$panes .= '<span class="post_ajax_trigger hide"><a href="' . esc_url($href) . '" class="ajax_load ajax-next ajax-open"></a></span>';
$panes .= '<div class="post_ajax_loader">' . HomeTabs::placeholder('posts', 3) . '</div>';
}
$panes .= '</div>'; $panes .= '</div>';
} }
return '<div class="plate-tab zib-widget"><div class="affix-header-sm" offset-top="7"><ul class="list-inline scroll-x mini-scrollbar tab-nav-theme" role="tablist">' . $nav . '</ul></div><div class="tab-content">' . $panes . '</div></div>'; return '<div class="plate-tab jio-panel"><div class="affix-header-sm" offset-top="7"><ul class="list-inline scroll-x mini-scrollbar tab-nav-theme" role="tablist">' . $nav . '</ul></div><div class="tab-content bbs-main-tab-content">' . $panes . '</div></div>';
} }
/** @return array<int, array<string, mixed>> */ /** @return array<int, array<string, mixed>> */
@@ -104,8 +103,9 @@ final class ListingView {
} }
} else { } else {
foreach ([ foreach ([
['最新发布', 'date', '', 'detail'], ['全部', 'modified', '', 'mini'],
['最近回复', 'last_reply', '', 'detail'], ['推荐', 'popular', '', 'detail'],
['最新回复', 'last_reply', '', 'detail'],
['热门', 'views', '', 'detail'], ['热门', 'views', '', 'detail'],
['精华', 'modified', 'essence', 'detail'], ['精华', 'modified', 'essence', 'detail'],
] as [$title, $orderby, $filter, $style]) { ] as [$title, $orderby, $filter, $style]) {
@@ -117,12 +117,13 @@ final class ListingView {
$tabs[] = ['title' => '待审核', 'orderby' => 'date', 'filter' => '', 'style' => 'mini', 'status' => 'pending']; $tabs[] = ['title' => '待审核', 'orderby' => 'date', 'filter' => '', 'style' => 'mini', 'status' => 'pending'];
$tabs[] = ['title' => '<i class="fa fas fa-trash-alt mr3"></i>回收站', 'orderby' => 'date', 'filter' => '', 'style' => 'mini', 'status' => 'trash']; $tabs[] = ['title' => '<i class="fa fas fa-trash-alt mr3"></i>回收站', 'orderby' => 'date', 'filter' => '', 'style' => 'mini', 'status' => 'trash'];
} }
return $tabs; // `?plate_tab=N` is 1-based, matching the reference tab numbering.
return $tabs ? array_combine(range(1, count($tabs)), $tabs) : [];
} }
public static function active_index(int $count): int { public static function active_index(int $count): int {
$raw = $_GET['plate_tab'] ?? 0; $raw = $_GET['plate_tab'] ?? 0;
$index = (is_scalar($raw) ? absint($raw) : 0) ?: (int) jio_get_setting('jio_forum_plate_tab_active', 1); $index = (is_scalar($raw) ? absint($raw) : 0) ?: (int) jio_get_setting('jio_forum_plate_tab_active', 2);
return $index >= 1 && $index <= $count ? $index : 1; return $index >= 1 && $index <= $count ? $index : 1;
} }
@@ -166,7 +167,7 @@ final class ListingView {
$base = $plate instanceof \WP_Post ? get_permalink($plate) : ($term instanceof \WP_Term ? get_term_link($term) : Core::home_url()); $base = $plate instanceof \WP_Post ? get_permalink($plate) : ($term instanceof \WP_Term ? get_term_link($term) : Core::home_url());
$next = add_query_arg(['plate_tab' => $index, 'fpage' => $page + 1], is_string($base) ? $base : Core::home_url()); $next = add_query_arg(['plate_tab' => $index, 'fpage' => $page + 1], is_string($base) ? $base : Core::home_url());
if ($page < (int) $query->max_num_pages) { if ($page < (int) $query->max_num_pages) {
$html .= '<div class="text-center theme-pagination forum-more"><a class="but hollow c-blue" href="' . esc_url($next) . '" data-forum-more>加载更多</a></div>'; $html .= '<div class="text-center theme-pagination ajax-pag"><div class="next-page ajax-next"><a href="' . esc_url($next) . '" data-forum-more><i class="fas fa-angle-right"></i>加载更多</a></div></div>';
} }
$html .= '<div class="post_ajax_loader">' . HomeTabs::placeholder('posts', 3) . '</div>'; $html .= '<div class="post_ajax_loader">' . HomeTabs::placeholder('posts', 3) . '</div>';
if ($html !== '' && trim(wp_strip_all_tags($html)) !== '') return $html; if ($html !== '' && trim(wp_strip_all_tags($html)) !== '') return $html;
+80 -7
View File
@@ -18,30 +18,103 @@ final class PostView {
$can_view = Access::can_view($user_id, $id); $can_view = Access::can_view($user_id, $id);
echo '<main id="forum" class="jio-forum forum-post"><div class="container"><div class="content-wrap"><div class="content-layout">'; echo '<main id="forum" class="jio-forum forum-post"><div class="container"><div class="content-wrap"><div class="content-layout">';
echo '<div class="fixed-wrap single-wrap"><div class="fixed-wrap-content">'; \Jio\WidgetSystem\WidgetAreas::render('forum_post_top_fluid', 'fluid-widget-wrap');
\Jio\WidgetSystem\WidgetAreas::render('forum_post_top_fluid'); echo '<div class="fixed-wrap single-wrap">';
echo self::fixed_actions($post); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="fixed-wrap-content">';
do_action('bbs_posts_page_content_top'); do_action('bbs_posts_page_content_top');
if ($can_view) echo self::cover($post); if ($can_view) echo self::cover($post);
echo self::breadcrumbs($post); echo self::breadcrumbs($post);
echo '<article id="article" class="article zib-widget forum-article relative">'; echo '<article id="article" class="article jio-panel forum-article relative">';
echo '<div class="article-header clearfix">' . self::title($post) . self::meta($post) . '</div>'; echo '<div class="article-header clearfix">' . self::title($post) . self::meta($post) . '</div>';
echo self::content($post, $can_view); echo self::content($post, $can_view);
if ($can_view) echo self::score_box($post);
if ($can_view) echo self::author_card($post);
echo self::footer($post); echo self::footer($post);
echo '</article>'; echo '</article>';
if ($can_view) { if ($can_view) {
self::comments($id); self::comments($id);
} }
do_action('bbs_posts_page_content_bottom'); do_action('bbs_posts_page_content_bottom');
\Jio\WidgetSystem\WidgetAreas::render('forum_post_bottom_fluid'); echo '</div></div>';
echo '</div></div></div>'; \Jio\WidgetSystem\WidgetAreas::render('forum_post_bottom_fluid', 'fluid-widget-wrap');
echo '</div>';
if (jio_is_show_sidebar()) { if (jio_is_show_sidebar()) {
echo '<div class="sidebar">'; echo '<div class="sidebar">';
\Jio\WidgetSystem\WidgetAreas::render('all_sidebar_top');
\Jio\WidgetSystem\WidgetAreas::render('forum_post_sidebar'); \Jio\WidgetSystem\WidgetAreas::render('forum_post_sidebar');
\Jio\WidgetSystem\WidgetAreas::render('all_sidebar_bottom');
echo '</div>'; echo '</div>';
} }
echo '</div></div></main>'; echo '</div></div></main>';
} }
/** Desktop left rail: score group, favorite and share (reference `.single-fixed-btns`). */
private static function fixed_actions(\WP_Post $post): string {
$id = (int) $post->ID;
$user_id = get_current_user_id();
$user_score = $user_id > 0 ? (int) get_user_meta($user_id, '_jio_forum_score_' . $id, true) : 0;
$html = '<div class="fixed-wrap-nav single-fixed-btns"><div><div class="score-box"><div class="score-btns">';
if (jio_get_setting('jio_forum_score_enabled', true)) {
$html .= self::score_action($post, 'up', 'extra', $user_score > 0);
$html .= '<text>' . esc_html(((int) get_post_meta($id, '_jio_forum_score', true)) ?: '评分') . '</text>';
$html .= self::score_action($post, 'down', 'deduct', $user_score < 0);
}
$html .= '</div></div>';
$html .= '<button type="button" class="btn-favorite' . (Access::favorites($user_id, $id) ? ' active' : '') . '" data-forum-action="favorite" data-id="' . $id . '"><i class="far fa-bookmark"></i><text></text></button>';
if (function_exists('jio_get_post_share_btn')) $html .= jio_get_post_share_btn($post, 'btn-share', true, true);
return $html . '</div></div>';
}
private static function score_action(\WP_Post $post, string $direction, string $kind, bool $active): string {
$class = 'btn-score ' . $kind . ($active ? ' active' : '');
$icon = '<i class="fa fas fa-' . ($direction === 'up' ? 'plus' : 'minus') . '-circle"></i>';
if (!is_user_logged_in()) {
return '<a href="' . esc_url(jio_get_login_url(get_permalink($post))) . '" class="' . esc_attr($class . ' signin-loader') . '" data-id="' . (int) $post->ID . '">' . $icon . '</a>';
}
return '<button type="button" class="' . esc_attr($class) . '" data-forum-action="score" data-direction="' . esc_attr($direction) . '" data-id="' . (int) $post->ID . '">' . $icon . '</button>';
}
/** Article score footer: pill-shaped group with the shared counter. */
private static function score_box(\WP_Post $post): string {
if (!jio_get_setting('jio_forum_score_enabled', true)) return '';
$id = (int) $post->ID;
$user_score = get_current_user_id() > 0 ? (int) get_user_meta(get_current_user_id(), '_jio_forum_score_' . $id, true) : 0;
$score = (int) get_post_meta($id, '_jio_forum_score', true);
return '<div class="score-box single-footer text-center"><div class="score-btns">'
. self::score_action($post, 'up', 'extra', $user_score > 0)
. '<text>' . esc_html($score ? jio_cut_count($score) : '评分') . '</text>'
. self::score_action($post, 'down', 'deduct', $user_score < 0)
. '</div><div class="score-box-detail"><div class="desc em09 muted-3-color mt6 mb10">' . ($score ? esc_html(sprintf('%d 人已评分', $score)) : '欢迎为Ta评分') . '</div></div></div>';
}
/** Author card under the article body (reference `.forum-single-author`). */
private static function author_card(\WP_Post $post): string {
$author = (int) $post->post_author;
$user = get_userdata($author);
if (!$user) return '';
$url = function_exists('jio_get_n') ? jio_get_n($author) : get_author_posts_url($author);
$count = (int) count_user_posts($author, Core::POST, true);
$followers = (int) get_user_meta($author, '_jio_follow_count', true);
$desc = (string) get_user_meta($author, 'description', true);
$recent = get_posts(['post_type' => Core::POST, 'post_status' => 'publish', 'author' => $author, 'post__not_in' => [$post->ID], 'posts_per_page' => 6, 'fields' => 'ids']);
$links = '';
foreach ($recent as $recent_id) {
$links .= '<div class="item"><a class="icon-circle text-ellipsis" href="' . esc_url(get_permalink($recent_id)) . '">' . esc_html(get_the_title($recent_id)) . '</a></div>';
}
$html = '<div class="user-card jio-panel forum-single-author"><div class="card-content mt10 relative flex"><div class="user-content flex xx ac gap20">';
$html .= '<div class="user-avatar"><a href="' . esc_url($url) . '">' . Cards::avatar($author, 'avatar-img avatar-lg') . '</a></div>';
$html .= '<div class="count-info flex xx gap6"><div class="count-info-item"><a class="muted-color" href="' . esc_url(add_query_arg('tab', 'forum', $url)) . '">' . esc_html(sprintf('%d 篇帖子', $count)) . '</a></div>';
$html .= '<div class="count-info-item"><a class="muted-color" href="' . esc_url(add_query_arg('tab', 'follow', $url)) . '">' . esc_html(sprintf('%d 位粉丝', $followers)) . '</a></div></div></div>';
$html .= '<div class="user-info flex1"><div class="border-bottom flex jsb at"><div class="ellipsis-box"><div class="user-name">' . (function_exists('jio_get_user_name') ? jio_get_user_name(['id' => $author, 'class' => 'flex1 flex ac']) : '<name class="flex1 flex ac"><a class="display-name text-ellipsis" href="' . esc_url($url) . '">' . esc_html($user->display_name) . '</a></name>') . '</div>';
if ($desc !== '') $html .= '<div class="user-desc mt10 muted-2-color">' . esc_html(wp_trim_words($desc, 40)) . '</div>';
$html .= '</div>';
if (function_exists('jio_get_user_follow')) $html .= jio_get_user_follow('ml10 but c-red follow flex0', $author);
$html .= '</div>';
if ($links !== '') $html .= '<div class="user-posts mln6 ellipsis-box"><div class="more-posts-mini">' . $links . '</div></div>';
return $html . '</div></div></div>';
}
private static function breadcrumbs(\WP_Post $post): string { private static function breadcrumbs(\WP_Post $post): string {
$plate_id = (int) get_post_meta($post->ID, '_jio_forum_plate_id', true); $plate_id = (int) get_post_meta($post->ID, '_jio_forum_plate_id', true);
$icon = '<i class="fa fas fa-map-marker-alt"></i> '; $icon = '<i class="fa fas fa-map-marker-alt"></i> ';
@@ -65,7 +138,7 @@ final class PostView {
} }
private static function title(\WP_Post $post): string { private static function title(\WP_Post $post): string {
return Cards::title($post, 'article-title', '', true); return Cards::title($post, 'article-title', '', true, 'h1');
} }
private static function meta(\WP_Post $post): string { private static function meta(\WP_Post $post): string {
@@ -125,7 +198,7 @@ final class PostView {
$expired = $deadline > 0 && current_time('timestamp') > $deadline; $expired = $deadline > 0 && current_time('timestamp') > $deadline;
$display = (string) jio_get_setting('jio_forum_vote_number_type', 'percentage'); $display = (string) jio_get_setting('jio_forum_vote_number_type', 'percentage');
$voted = get_current_user_id() > 0 && get_user_meta(get_current_user_id(), '_jio_forum_vote_' . $id, true) !== ''; $voted = get_current_user_id() > 0 && get_user_meta(get_current_user_id(), '_jio_forum_vote_' . $id, true) !== '';
$html = '<form class="vote-box forum-vote zib-widget" data-forum-form="vote" data-id="' . $id . '"><h2 class="mb20">' . esc_html((string) get_post_meta($id, '_jio_forum_vote_title', true) ?: '参与投票') . '</h2>'; $html = '<form class="vote-box forum-vote jio-panel" data-forum-form="vote" data-id="' . $id . '"><h2 class="mb20">' . esc_html((string) get_post_meta($id, '_jio_forum_vote_title', true) ?: '参与投票') . '</h2>';
foreach ($options as $index => $option) { foreach ($options as $index => $option) {
$count = (int) ($counts[$index] ?? 0); $count = (int) ($counts[$index] ?? 0);
$percent = $total > 0 ? (int) round(100 * $count / $total) : 0; $percent = $total > 0 ? (int) round(100 * $count / $total) : 0;
+14 -17
View File
@@ -8,23 +8,22 @@ final class QuickPost {
/** Inline "publish now" box shown on the configured home tab (reference layout). */ /** Inline "publish now" box shown on the configured home tab (reference layout). */
public static function render(): string { public static function render(): string {
$user_id = get_current_user_id(); $user_id = get_current_user_id();
$post_name = (string) jio_get_setting('jio_forum_post_name', '帖子') ?: '帖子';
$plate_name = (string) jio_get_setting('jio_forum_plate_name', '版块') ?: '版块'; $plate_name = (string) jio_get_setting('jio_forum_plate_name', '版块') ?: '版块';
$topic_name = (string) jio_get_setting('jio_forum_topic_name', '话题') ?: '话题'; $topic_name = (string) jio_get_setting('jio_forum_topic_name', '话题') ?: '话题';
$plates = get_posts(['post_type' => Core::PLATE, 'post_status' => 'publish', 'posts_per_page' => 200, 'orderby' => 'title', 'order' => 'ASC']); $plates = get_posts(['post_type' => Core::PLATE, 'post_status' => 'publish', 'posts_per_page' => 200, 'orderby' => 'title', 'order' => 'ASC']);
if (!$plates) return ''; if (!$plates) return '';
$html = '<form class="quick-posts-box zib-widget mb10-sm mb20" data-forum-form="post"><div class="quick-input-group mb10">'; $html = '<form class="quick-posts-box jio-panel mb10-sm mb20" data-forum-form="post"><div class="quick-input-group mb10">';
$html .= '<div class="relative edit-posts-title"><textarea class="line-form-input input-lg new-title" name="title" rows="1" maxlength="120" tabindex="1" placeholder="' . esc_attr((string) jio_get_setting('jio_forum_title_placeholder', '请输入标题')) . '"></textarea><i class="line-form-line"></i></div>'; $html .= '<div class="relative edit-posts-title"><textarea class="line-form-input input-lg new-title" name="title" rows="1" maxlength="120" tabindex="1" placeholder="' . esc_attr((string) jio_get_setting('jio_forum_title_placeholder', '请输入标题')) . '"></textarea><i class="line-form-line"></i></div>';
$html .= '<div class="relative edit-posts-content"><textarea class="form-control" name="content" tabindex="2" rows="4" placeholder="' . esc_attr((string) jio_get_setting('jio_forum_content_placeholder', '请输入内容')) . '"></textarea></div>'; $html .= '<div class="relative edit-posts-content"><textarea class="form-control" name="content" tabindex="2" rows="4" placeholder="' . esc_attr((string) jio_get_setting('jio_forum_content_placeholder', '请输入内容')) . '"></textarea></div>';
if (jio_get_setting('jio_forum_upload_image', true)) { if (jio_get_setting('jio_forum_upload_image', true)) {
$html .= '<div class="' . ($user_id ? 'quick-upload' : 'signin-loader') . '"><div class="preview"><div class="add"></div></div><input type="file" accept="image/*" data-forum-file data-kind="image" hidden></div>'; $html .= '<div class="' . ($user_id ? 'quick-upload' : 'signin-loader') . '"><div class="preview"><div class="add"></div></div><input type="file" accept="image/*" data-forum-file data-kind="image" hidden></div>';
} }
$html .= '<div class="quick-selected-tags"><span name="topics" class="flex ac flex1"></span></div>';
$html .= '</div>'; $html .= '</div>';
// More options: plate / topic / access / hidden content // More options: topic / access / hidden content (plate sits in the submit row).
$html .= '<div class="flex ac quick-more-btns hh">'; $html .= '<div class="flex ac quick-more-btns hh">';
$html .= self::plate_dropdown($plates, $plate_name, $user_id);
$html .= self::topic_dropdown($topic_name, $user_id); $html .= self::topic_dropdown($topic_name, $user_id);
$html .= self::access_dropdown($user_id); $html .= self::access_dropdown($user_id);
if ($user_id) { if ($user_id) {
@@ -32,8 +31,7 @@ final class QuickPost {
} }
$html .= '</div>'; $html .= '</div>';
// Submit row + captcha $html .= '<div class="flex ac jsb hh quick-posts-submit mt10">' . self::plate_dropdown($plates, $plate_name, $user_id) . '<div class="flex ac">';
$html .= '<div class="flex ac jsb hh quick-posts-submit mt10">';
if (function_exists('jio_render_captcha') && function_exists('jio_is_captcha_enabled_for') && jio_is_captcha_enabled_for('forum_post')) { if (function_exists('jio_render_captcha') && function_exists('jio_is_captcha_enabled_for') && jio_is_captcha_enabled_for('forum_post')) {
$html .= '<div class="quick-captcha">'; $html .= '<div class="quick-captcha">';
ob_start(); ob_start();
@@ -41,7 +39,6 @@ final class QuickPost {
$html .= (string) ob_get_clean(); $html .= (string) ob_get_clean();
$html .= '</div>'; $html .= '</div>';
} }
$html .= '<div class="flex ac">';
if ($user_id) { if ($user_id) {
$html .= '<button type="button" class="but c-green mr6" data-forum-draft tabindex="3">保存</button>'; $html .= '<button type="button" class="but c-green mr6" data-forum-draft tabindex="3">保存</button>';
$html .= '<button type="submit" class="but jb-blue" tabindex="4">发布</button>'; $html .= '<button type="submit" class="but jb-blue" tabindex="4">发布</button>';
@@ -49,45 +46,45 @@ final class QuickPost {
$html .= '<a class="but jb-blue signin-loader" href="' . esc_url(jio_get_login_url(Core::home_url())) . '">登录后发布</a>'; $html .= '<a class="but jb-blue signin-loader" href="' . esc_url(jio_get_login_url(Core::home_url())) . '">登录后发布</a>';
} }
$html .= '</div></div>'; $html .= '</div></div>';
$html .= '<input type="hidden" name="plate_id" value=""><input type="hidden" name="topics" value=""><input type="hidden" name="view_limit" value="all"><input type="hidden" name="hide_type" value="">'; $html .= '<input type="hidden" name="view_limit" value="all">';
$html .= '<p class="jio-forum__message mt6 muted-2-color" role="status"></p>'; $html .= '<p class="jio-forum__message mt6 muted-2-color" role="status"></p>';
return $html . '</form>'; return $html . '</form>';
} }
/** @param \WP_Post[] $plates */ /** @param \WP_Post[] $plates */
private static function plate_dropdown(array $plates, string $plate_name, int $user_id): string { private static function plate_dropdown(array $plates, string $plate_name, int $user_id): string {
if ($user_id <= 0) {
return '<div class="plate-drop drop-select dropdown mr20 mt10 signin-loader"><div class="flex ac jsb drop-btn"><div class="flex ac jsb but-plate but"><span class="flex ac"><i class="fa fas fa-cube mr6"></i><span>请选择</span>' . esc_html($plate_name) . '</span><i class="ml6 fa fas fa-angle-right em12"></i></div></div></div>';
}
$options = ''; $options = '';
foreach ($plates as $plate) { foreach ($plates as $plate) {
$options .= '<div data-forum-plate="' . (int) $plate->ID . '" class="pointer but p2-10 mb6">' . esc_html($plate->post_title) . '</div>'; $options .= '<div data-forum-plate="' . (int) $plate->ID . '" class="pointer but p2-10 mb6">' . esc_html($plate->post_title) . '</div>';
} }
if ($user_id <= 0) { return '<div class="plate-drop drop-select dropdown mr20 mt10"><div data-toggle-class="open" data-target=".plate-drop" class="flex ac jsb drop-btn"><div class="flex ac jsb but-plate but"><span class="flex ac" name="plate_id"><i class="fa fas fa-cube mr6"></i><span>请选择</span>' . esc_html($plate_name) . '</span><i class="ml6 fa fas fa-angle-right em12"></i></div></div><div class="dropdown-menu fluid"><div class="mini-scrollbar padding-w10 select-drop-box">' . $options . '</div></div></div>';
return '<div class="plate-drop signin-loader mr20 mt10"><div class="flex ac jsb drop-btn"><span class="flex ac"><i class="fa fas fa-cube mr6"></i>请选择' . esc_html($plate_name) . '</span><i class="ml6 fa fas fa-angle-right em12"></i></div></div>';
}
return '<div class="plate-drop drop-select dropdown mr20 mt10"><div data-toggle-class="open" data-target=".plate-drop" class="flex ac jsb drop-btn"><span class="flex ac" name="plate_id"><i class="fa fas fa-cube mr6"></i><span>请选择' . esc_html($plate_name) . '</span></span><i class="ml6 fa fas fa-angle-right em12"></i></div><div class="dropdown-menu fluid"><div class="mini-scrollbar padding-w10 select-drop-box">' . $options . '</div></div></div>';
} }
private static function topic_dropdown(string $topic_name, int $user_id): string { private static function topic_dropdown(string $topic_name, int $user_id): string {
if ($user_id <= 0) { if ($user_id <= 0) {
return '<div class="flex ac jsb drop-btn mr20 signin-loader"><i class="fa fas fa-hashtag mr6"></i>' . esc_html($topic_name) . '</div>'; return '<div class="flex ac jsb drop-btn mr20 signin-loader"><i class="fa fas fa-hashtag mr3"></i>' . esc_html($topic_name) . '</div>';
} }
$terms = get_terms(['taxonomy' => Core::TOPIC, 'hide_empty' => false, 'number' => 100, 'orderby' => 'count', 'order' => 'DESC']); $terms = get_terms(['taxonomy' => Core::TOPIC, 'hide_empty' => false, 'number' => 100, 'orderby' => 'count', 'order' => 'DESC']);
$options = '<div data-forum-topic-none class="pointer but p2-10 mb6"><i class="fa fas fa-ban mr6"></i>不添加' . esc_html($topic_name) . '</div>'; $options = '<div data-forum-topic-none class="pointer but p2-10 mb6"><i class="fa fas fa-ban mr6"></i>不添加' . esc_html($topic_name) . '</div>';
foreach (is_array($terms) ? $terms : [] as $term) { foreach (is_array($terms) ? $terms : [] as $term) {
$options .= '<div data-forum-topic="' . esc_attr($term->name) . '" class="pointer but p2-10 mb6">' . esc_html($term->name) . '</div>'; $options .= '<div data-forum-topic="' . esc_attr($term->name) . '" class="pointer but p2-10 mb6">' . esc_html($term->name) . '</div>';
} }
return '<div class="topic-drop drop-select dropdown mr20"><div data-toggle-class="open" data-target=".topic-drop" class="flex ac jsb drop-btn"><span class="flex ac" name="topics"><i class="fa fas fa-hashtag mr6"></i>' . esc_html($topic_name) . '</span><i class="ml6 fa fas fa-angle-right em12"></i></div><div class="dropdown-menu fluid"><div class="mini-scrollbar padding-w10 select-drop-box">' . $options . '</div></div></div>'; return '<div class="topic-drop drop-select dropdown mr20"><div data-toggle-class="open" data-target=".topic-drop" class="flex ac jsb drop-btn"><i class="fa fas fa-hashtag mr3"></i>' . esc_html($topic_name) . '</div><input type="hidden" name="topics" value=""><div class="dropdown-menu fluid"><div class="mini-scrollbar padding-w10 select-drop-box">' . $options . '</div></div></div>';
} }
private static function access_dropdown(int $user_id): string { private static function access_dropdown(int $user_id): string {
$labels = ['all' => '公开', 'login' => '登录后可查看', 'follow' => '关注版块后可查看', 'points' => '支付积分查看', 'pay' => '付费查看']; $labels = ['all' => '公开', 'login' => '登录后可查看', 'follow' => '关注版块后可查看', 'points' => '支付积分查看', 'pay' => '付费查看'];
if ($user_id <= 0) { if ($user_id <= 0) {
return '<div class="flex ac jsb drop-btn mr20 signin-loader"><i class="fa far fa-eye mr6"></i><span name="view_limit">公开</span></div>'; return '<div class="flex ac jsb drop-btn mr20 signin-loader"><i class="far fa-eye mr3"></i><span name="view_limit">公开</span></div>';
} }
$options = ''; $options = '';
foreach ($labels as $key => $label) { foreach ($labels as $key => $label) {
$options .= '<div data-forum-view="' . $key . '" class="pointer but p2-10 mb6' . ($key === 'all' ? ' active' : '') . '">' . esc_html($label) . '</div>'; $options .= '<div data-forum-view="' . $key . '" class="pointer but p2-10 mb6' . ($key === 'all' ? ' active' : '') . '">' . esc_html($label) . '</div>';
} }
return '<div class="allow-view-drop drop-select dropdown mr20"><div data-toggle-class="open" data-target=".allow-view-drop" class="flex ac jsb drop-btn"><span class="flex ac"><i class="far fa-eye mr6"></i><span name="view_limit">公开</span></span><i class="ml6 fa fas fa-angle-right em12"></i></div><div class="dropdown-menu fluid"><div class="mini-scrollbar padding-w10 select-drop-box">' . $options . '</div></div></div>'; return '<div class="allow-view-drop drop-select dropdown mr20"><div data-toggle-class="open" data-target=".allow-view-drop" class="flex ac jsb drop-btn"><i class="far fa-eye mr3"></i><span name="view_limit">公开</span></div><div class="dropdown-menu fluid"><div class="padding-10 flex jsb muted-2-color em09">设置阅读权限<span data-toggle-class="close" data-target=".allow-view-drop" class="pointer">完成</span></div><div class="mini-scrollbar padding-w10 select-drop-box">' . $options . '</div></div></div>';
} }
private static function hide_dropdown(): string { private static function hide_dropdown(): string {
@@ -95,6 +92,6 @@ final class QuickPost {
foreach (['logged' => '登录可见', 'reply' => '评论可见', 'vip1' => '会员可见', 'payshow' => '付费可见'] as $key => $label) { foreach (['logged' => '登录可见', 'reply' => '评论可见', 'vip1' => '会员可见', 'payshow' => '付费可见'] as $key => $label) {
$items .= '<span data-forum-hide-type="' . $key . '" class="mb6 mr6 badg p2-10 pointer' . ($key === 'logged' ? ' active' : '') . '">' . esc_html($label) . '</span>'; $items .= '<span data-forum-hide-type="' . $key . '" class="mb6 mr6 badg p2-10 pointer' . ($key === 'logged' ? ' active' : '') . '">' . esc_html($label) . '</span>';
} }
return '<div class="hide-type-drop drop-select dropdown mr20"><div data-toggle-class="open" data-target=".hide-type-drop" class="flex ac jsb drop-btn"><i class="fa far fa-eye-slash mr6"></i><span>隐藏内容</span><i class="ml6 fa fas fa-angle-right em12"></i></div><div class="dropdown-menu fluid"><div class="mini-scrollbar padding-w10 select-drop-box"><div class="em09 muted-3-color mb10">添加额外的隐藏内容</div><div class="flex hh">' . $items . '</div><textarea rows="6" name="hide_content" class="form-control input-textarea" placeholder="在此输入需要隐藏的内容"></textarea></div></div></div>'; return '<div class="hide-type-drop drop-select dropdown mr20"><div data-toggle-class="open" data-target=".hide-type-drop" class="flex ac jsb drop-btn"><i class="far fa-eye-slash mr3"></i><span>隐藏内容</span></div><div class="dropdown-menu fluid"><div class="mini-scrollbar padding-w10 select-drop-box"><div class="em09 muted-3-color mb10">添加额外的隐藏内容</div><div class="flex hh">' . $items . '</div><input type="hidden" name="hide_type" value="logged"><textarea rows="6" name="hide_content" class="form-control input-textarea" placeholder="在此输入需要隐藏的内容"></textarea></div></div></div>';
} }
} }
+1 -1
View File
@@ -30,7 +30,7 @@ test('post detail pages use the reference article shell', () => {
const post = read('src/Forum/PostView.php') const post = read('src/Forum/PostView.php')
const template = read('templates/pages/page-forum-post.php') const template = read('templates/pages/page-forum-post.php')
assert.match(template, /PostView::render\(\)/) assert.match(template, /PostView::render\(\)/)
for (const token of ['fixed-wrap single-wrap', 'fixed-wrap-content', 'forum-single-cover', 'breadcrumb', 'id="article"', 'article zib-widget forum-article', 'article-header clearfix', 'forum-article-meta', 'wp-posts-content', 'article-tags', 'forum-article-footer', 'footer-actions', 'vote-box forum-vote', 'comments-card']) { for (const token of ['fixed-wrap single-wrap', 'fixed-wrap-content', 'forum-single-cover', 'breadcrumb', 'id="article"', 'article jio-panel forum-article', 'article-header clearfix', 'forum-article-meta', 'wp-posts-content', 'article-tags', 'forum-article-footer', 'footer-actions', 'vote-box forum-vote', 'comments-card']) {
assert.ok(post.includes(token), `post page missing ${token}`) assert.ok(post.includes(token), `post page missing ${token}`)
} }
// Banners, permissions and actions stay server-side. // Banners, permissions and actions stay server-side.
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"generated_at": "2026-11-11T08:37:24+00:00", "generated_at": "2026-11-11T10:06:02+00:00",
"errors": [], "errors": [],
"warnings": [ "warnings": [
"customPage tools-update section 'tools/update' not in PHP schema (ok if pure operational page)", "customPage tools-update section 'tools/update' not in PHP schema (ok if pure operational page)",