diff --git a/assets/css/routes/forum.css b/assets/css/routes/forum.css index f9396270..27c8a174 100644 --- a/assets/css/routes/forum.css +++ b/assets/css/routes/forum.css @@ -1,66 +1,241 @@ +/* Forum surface: home tabs, plate panels, post cards, listing, replies. */ #forum{--forum-ink:var(--key-color,#24282c);--forum-muted:var(--muted-color,#737b82);--forum-line:var(--muted-4-color,#e6e9eb);--forum-accent:var(--focus-color,#2869d1);color:var(--forum-ink)} + +/* ── 通用壳(版块 / 分类 / 搜索列表沿用) ───────────────────────── */ #forum .jio-forum__wrap{width:min(1200px,calc(100% - 32px));margin:22px auto 70px} #forum .jio-forum__breadcrumbs{display:flex;gap:9px;align-items:center;color:var(--forum-muted);font-size:13px;margin-bottom:18px} #forum .jio-forum__breadcrumbs a{color:inherit} #forum .jio-forum__layout{display:grid;grid-template-columns:minmax(0,1fr) 286px;gap:24px;align-items:start} #forum .jio-forum__main{min-width:0} -#forum .jio-forum__hero,#forum .jio-forum__page-head{padding:20px 0;border-bottom:1px solid var(--forum-line)} +#forum .jio-forum__page-head{padding:20px 0;border-bottom:1px solid var(--forum-line)} #forum h1{font-size:27px;line-height:1.25;margin:0 0 10px} -#forum .jio-forum__hero p,#forum .jio-forum__page-head p{margin:0;color:var(--forum-muted)} +#forum .jio-forum__page-head p{margin:0;color:var(--forum-muted)} +#forum .jio-forum__aside{border-left:1px solid var(--forum-line);padding-left:20px} +#forum .jio-forum__new,#forum .jio-forum__primary{display:inline-flex;align-items:center;justify-content:center;background:var(--forum-accent);color:#fff;border:0;border-radius:4px;min-height:40px;padding:8px 16px;cursor:pointer} +#forum .jio-forum__new{width:100%} +#forum .jio-forum__aside-section{padding:22px 0;border-bottom:1px solid var(--forum-line)} +#forum .jio-forum__aside-section h2{font-size:16px;margin:0 0 12px} +#forum .jio-forum__aside-section a{display:block;padding:6px 0;color:var(--forum-ink)} +#forum .jio-forum__widget-link{display:flex;justify-content:space-between;gap:10px;padding:7px 0;color:var(--forum-ink)} +#forum .jio-forum__widget-link span{color:var(--forum-muted)} + +/* 版块 / 分类列表页的固定栏目条 */ #forum .jio-forum__tabs{display:flex;gap:26px;overflow:auto;white-space:nowrap;border-bottom:1px solid var(--forum-line);margin-bottom:16px} #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__chips{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0} -#forum .jio-forum__chips a,#forum .jio-forum__badge,#forum .jio-forum__badges a,#forum .jio-forum__badges span{padding:4px 8px;background:#f1f5f7;color:#48717c;border-radius:4px;font-size:12px} -#forum .jio-forum__post{display:flex;gap:13px;padding:20px 2px;border-bottom:1px solid var(--forum-line)} -#forum .jio-forum__avatar{flex:0 0 44px}#forum .jio-forum__avatar img{width:44px;height:44px;border-radius:50%} -#forum .jio-forum__post-body{min-width:0;flex:1}#forum .jio-forum__post-top{display:flex;gap:12px;color:var(--forum-muted);font-size:12px} -#forum .jio-forum__post h2{font-size:17px;line-height:1.45;margin:7px 0}#forum .jio-forum__post h2 a{color:var(--forum-ink)} -#forum .jio-forum__post p{color:var(--forum-muted);font-size:14px;line-height:1.6;margin:0 0 10px} -#forum .jio-forum__post-meta{display:flex;align-items:center;gap:10px;color:var(--forum-muted);font-size:12px;flex-wrap:wrap} -#forum .jio-forum__post-meta a{color:var(--forum-accent)} -#forum .jio-forum__aside{border-left:1px solid var(--forum-line);padding-left:20px} -#forum .jio-forum__new,#forum .jio-forum__primary{display:inline-flex;align-items:center;justify-content:center;background:var(--forum-accent);color:#fff;border:0;border-radius:4px;min-height:40px;padding:8px 16px;cursor:pointer} -#forum .jio-forum__new{width:100%}#forum .jio-forum__aside-section{padding:22px 0;border-bottom:1px solid var(--forum-line)} -#forum .jio-forum__aside-section h2{font-size:16px;margin:0 0 12px}#forum .jio-forum__aside-section a{display:block;padding:6px 0;color:var(--forum-ink)} -#forum .jio-forum__plates{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px} -#forum .jio-forum__plate{display:flex;gap:13px;padding:14px;border:1px solid var(--forum-line);border-radius:6px} -#forum .jio-forum__plate-cover{width:80px;height:80px;flex:none;background:#edf2f5;border-radius:5px;overflow:hidden} -#forum .jio-forum__plate-cover img{width:100%;height:100%;object-fit:cover}#forum .jio-forum__plate h2{font-size:16px;margin:3px 0 8px} -#forum .jio-forum__plate p{font-size:13px;color:var(--forum-muted);margin:0} -#forum .jio-forum__single{padding:20px 0}#forum .jio-forum__single-meta{display:flex;align-items:center;gap:10px;color:var(--forum-muted);font-size:13px} -#forum .jio-forum__single-meta img{border-radius:50%}#forum .jio-forum__single-head h1{margin:18px 0} + +/* ── 论坛首页:栏目 TAB ─────────────────────────────────────────── */ +#forum.forum-home{padding-bottom:40px} +.forum-home .forum-home__main{min-width:0} +.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 li{padding:0 2px} +.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.active > a{color:var(--focus-color);font-weight:600} +.forum-home-tab .tab-content > .tab-pane{display:none} +.forum-home-tab .tab-content > .tab-pane.active{display:block} +.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} +/* 首页栏目是横向 TAB,覆盖通用 .fixed-wrap 的竖排侧栏导航形态 */ +@media (min-width:768px){ + .forum-home-tab{--fixed-width:auto} + .forum-home-tab > .fixed-wrap-nav{position:static;width:auto} + .forum-home-tab > .fixed-wrap-nav + .fixed-wrap-content{margin-left:0} + .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 .title-theme{margin-bottom:10px} +.forum-home .jio-widget-box{clear:both;background-color:var(--main-bg-color);padding:15px;box-shadow:0 0 10px var(--main-shadow);border-radius:var(--main-radius);margin-bottom:20px} +.forum-home .jio-widget-box .btn-block{display:block;width:100%;text-align:center} +.forum-home .jio-widget-box .title-theme{font-size:15px;font-weight:600} +.quick-posts-form .form-control{width:100%} +.quick-posts-form select.form-control{width:auto;min-width:140px} +.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} + +/* ── 版块面板 / 版块卡片 ────────────────────────────────────────── */ +.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-plate .title-theme{height:24px;display:flex;align-items:center;font-size:15px;font-weight:600} +.panel-plate .panel-title{padding:10px 0;margin:0} +.panel-plate .panel-title, +.panel-plate .panel-title > a{color:var(--key-color)} +.panel-plate .panel-title > a{text-decoration:none} +.panel-plate .plate-lists{padding:0 0 10px} +.panel-plate .fa{transition:.2s} +.panel-plate .collapsed .fa-angle-up{transform:scaleY(-100%)} +.panel-plate .follow-plate{display:inline-block;max-width:100%} +.plate-add{display:inline-flex;align-items:center;font-size:12px} +.plate-card{display:inline-block;text-align:center} +.plate-card .title{color:var(--main-color);font-size:13px;font-weight:500} +.scroll-plate .plate-card{padding:15px 10px 8px;width:115px;border-radius:var(--main-radius);box-shadow:0 0 10px var(--main-shadow);background:var(--main-bg-color)} +.scroll-plate .plate-card.mini{padding:15px 5px;width:85px} +.scroll-plate .plate-card .plate-thumb{width:50px!important;height:50px!important} +.follow-plate .plate-card{width:90px} +.plate-item{display:inline-flex;align-items:flex-start} +.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-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 .forum-thumbnail{transition:all .2s;object-fit:cover;width:100%;height:100%} +.plate-item .item-info{display:flex;justify-content:space-between;flex-direction:column;flex:1;min-width:0} +.plate-item .count-mates,.plate-item .excerpt{margin-top:6px;color:var(--muted-color);min-height:1em} +.plate-item .count-mates{color:var(--muted-2-color);font-size:12px} +.plate-item .excerpt:hover,.forum-title a:hover{color:var(--key-color)} +.count-mates item{display:inline-block} +.count-mates icon,.count-mates text{margin-right:2px} +.plate-thumb{flex-shrink:0;display:inline-block;width:60px;height:60px;position:relative} +.forum-title{font-size:16px;padding:0;margin:0;line-height:1.5} +.btn-follow,.btn-follow:visited{display:inline-flex;align-items:center;justify-content:center;overflow:hidden;position:relative;cursor:pointer} +.active.jb-pink{--this-bg:rgba(27,145,255,.1);--this-color:#3097f8} + +/* ── 帖子卡片 ──────────────────────────────────────────────────── */ +.forum-posts{--this-padding:15px 20px;padding:var(--this-padding);display:flex;transition:.3s;position:relative} +.forum-posts.mini .avatar-img{--this-size:50px} +.forum-posts.manage .badge-status{right:3em} +.forum-posts + .forum-posts{border-top:1px solid var(--main-border-color)} +.forum-posts + .post_ajax_loader > .mb20,.forum-posts + .ajax-pag{padding-top:30px;border-top:1px solid var(--main-border-color)} +.forum-posts + .theme-pagination{margin-top:0} +.forum-posts .entry-info{flex-grow:1;min-width:0} +.forum-posts.mini .entry-info{max-width:calc(100% - 70px)} +.forum-posts .forum-title{font-weight:500} +.forum-posts .forum-title badge{font-weight:normal} +.forum-posts .forum-title > a,.forum-posts .content{line-height:22px} +.forum-posts .forum-title > a.text-ellipsis{line-height:2.5em;display:block;margin-top:-.5em} +.forum-posts .item-meta{color:var(--muted-color);opacity:.6;transition:opacity .15s} +.forum-posts:hover .item-meta{opacity:1} +.forum-posts .meta-right item{margin-left:15px} +.forum-posts.detail{display:block} +.forum-posts.detail .user-info + .badge-status{top:46px} +.forum-posts.detail .content{overflow:hidden} +.forum-posts.detail .content .excerpt{display:block} +.forum-posts.detail .but-plate .icon{color:var(--focus-color)} +.forum-posts.detail .action-meta{margin-right:-7px} +.forum-posts.detail .but-plate{margin:3px 0} +.forum-posts.detail .action-meta .item{color:var(--muted-color);margin-left:5px;padding:2px 8px;font-size:20px;transition:.15s;border-radius:4px;display:inline-flex;align-items:center;border:0;background:none;cursor:pointer} +.forum-posts.detail .action-meta .btn-score{font-size:20px;display:inline-flex;align-items:center;color:var(--muted-color);padding:2px;border-radius:4px} +.forum-posts.detail .action-meta > .item.active{color:var(--focus-color)} +.forum-posts.detail .action-meta text{font-size:12px;margin-left:2px;min-width:14px;color:var(--muted-color);text-align:center} +.forum-posts.detail .plate-null + .action-meta{flex:auto;justify-content:flex-end} +.forum-posts .hot-comment .avatar-img,.forum-posts.detail .hot-comment .avatar-img{--this-size:22px;margin-right:6px} +.forum-posts .hot-comment{background:var(--muted-border-color);border-radius:var(--main-radius);padding:8px 10px;font-size:13px;color:var(--muted-color)} +.forum-posts .hot-comment .comment-content img:not(.smilie-icon){max-width:200px;max-height:200px;display:block;width:auto;height:auto} +.forum-posts.alone{border:none!important;background:var(--main-bg-color);border-radius:var(--main-radius);margin-bottom:10px;--this-padding:20px 26px} +.forum-posts.alone + .post_ajax_loader > .mb20,.forum-posts.alone + .ajax-pag{border-top:none;padding:20px 0 30px;margin:0} +.forum-posts .hide-post{margin-top:6px;padding:10px 12px;background:var(--muted-border-color);border-radius:var(--main-radius);color:var(--muted-color);font-size:13px} +.forum-posts .lists-imgs{margin-top:6px} +.lists-imgs:not(.count-1){display:flex;flex-wrap:wrap;--img-count:4;max-width:650px} +.lists-imgs:not(.count-1) > span{flex:0 0 calc(100% / var(--img-count) - 4px);padding-bottom:calc(100% / var(--img-count) - 4px);margin:2px;position:relative;border-radius:4px;overflow:hidden} +.lists-imgs.count-2{--img-count:3} +.lists-imgs:not(.count-1) > span > img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover} +.lists-imgs.count-1 img{max-width:300px;max-height:300px;width:auto;height:auto;border-radius:4px} +.lists-imgs-more{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.45);color:#fff;font-size:18px;font-weight:600} +.forum-posts .badge-status{position:absolute;right:0;top:0} +#forum .badg.but-plate,#forum .but.but-plate{font-size:12px;border-radius:100px;padding:3px 10px;vertical-align:.1em;display:inline-flex;align-items:center;text-decoration:none} +.badge-question{border-radius:.45em 2px;padding:0 .25em;line-height:1.4em} +.badge-question > text{font-size:.8em;margin-left:2px;vertical-align:.05em} + +/* ── 首页 / 列表响应式 ─────────────────────────────────────────── */ +@media (min-width:641px){ + .forum-posts.mini:hover{background:var(--muted-border-color)} + .forum-posts.detail .avatar-img{--this-size:55px;margin-right:5px} + .forum-posts.detail .user-info + *,.forum-posts.detail .user-info .user-right{margin-top:-12px} + .forum-posts.detail .user-info ~ *{margin-left:70px} + .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)} +} +@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 .content-layout{margin-right:0!important;margin-left:0!important} +} +@media (max-width: 767px){ + .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 .tab-nav-theme li > a{font-size:15px;padding:8px 10px} +} +@media (max-width: 640px){ + .plate-lists .plate-item{width:calc(100% - 6px);padding:15px 12px;align-items:center} + .plate-item .plate-thumb{width:40px;height:40px;margin-right:10px} + .plate-item .excerpt{display:none} + .plate-item .forum-title{font-size:13px} + .follow-plate .plate-card{width:60px} + .follow-plate .plate-card .title{font-size:12px} + .forum-posts,.forum-posts.alone{--this-padding:12px 0} + .forum-posts.mini{flex-direction:column} + .forum-posts.mini .forum-user{display:flex;align-items:center;margin-bottom:8px} + .forum-posts.mini .entry-info{max-width:100%} + .forum-posts.mini .item-meta{margin-top:6px} + .forum-posts.mini .item-meta item{margin-left:8px} + .forum-posts.mini .item-meta .meta-left{margin-left:-5px} + .forum-posts .forum-title > a.text-ellipsis{line-height:22px;font-size:15px;display:block;overflow:unset;text-overflow:unset;white-space:unset;margin:0} + .forum-posts.mini .avatar-img{--this-size:22px;margin-top:-3px} + .forum-posts.detail .action-meta > .item{margin:0} + .forum-posts.detail .user-info ~ *{margin-left:0} + .forum-posts.detail .avatar-img{--this-size:38px} + .lists-imgs.count-3{--img-count:3} + .lists-imgs.count-2,.lists-imgs.count-4{--img-count:2} +@media(max-width:480px){ + #forum .jio-forum__wrap{width:calc(100% - 24px)} + #forum h1{font-size:23px} + #forum .jio-forum__tabs{gap:18px} + .forum-home-tab .tab-nav-theme li > a{font-size:14px;padding:8px} + .forum-posts .action-meta text{display:none} + .quick-posts-form select.form-control{width:100%} +} + +/* ── 帖子详情 / 回复 / 编辑器 ──────────────────────────────────── */ +#forum .jio-forum__single{padding:20px 0} +#forum .jio-forum__single-meta{display:flex;align-items:center;gap:10px;color:var(--forum-muted);font-size:13px} +#forum .jio-forum__single-meta img{border-radius:50%} +#forum .jio-forum__single-head h1{margin:18px 0} #forum .jio-forum__badges,#forum .jio-forum__actions{display:flex;gap:8px;flex-wrap:wrap} #forum .jio-forum__single-content{font-size:15px;line-height:1.8;padding:22px 0;overflow-wrap:anywhere} #forum .jio-forum__actions{border-top:1px solid var(--forum-line);padding:16px 0} #forum .jio-forum__action{border:1px solid var(--forum-line);border-radius:4px;background:transparent;color:var(--forum-muted);padding:7px 12px;cursor:pointer} #forum .jio-forum__action:hover{color:var(--forum-accent);border-color:var(--forum-accent)} -#forum .jio-forum__form{display:grid;gap:15px;margin:20px 0}#forum .jio-forum__form label{display:grid;gap:6px;font-size:14px;font-weight:600} +#forum .jio-forum__form{display:grid;gap:15px;margin:20px 0} +#forum .jio-forum__form label{display:grid;gap:6px;font-size:14px;font-weight:600} #forum .jio-forum__form input,#forum .jio-forum__form select,#forum .jio-forum__form textarea{width:100%;border:1px solid #d6dce1;border-radius:4px;padding:10px 12px;background:var(--body-bg,#fff);color:inherit;font:inherit} -#forum .jio-forum__form-extra[hidden]{display:none}#forum .jio-forum__message{color:#b64343;min-height:20px} -#forum .jio-forum__vote{border-top:1px solid var(--forum-line);padding:20px 0}#forum .jio-forum__vote h2{font-size:17px} +#forum .jio-forum__form-extra[hidden]{display:none} +#forum .jio-forum__message{color:#b64343;min-height:20px} +#forum .jio-forum__vote{border-top:1px solid var(--forum-line);padding:20px 0} +#forum .jio-forum__vote h2{font-size:17px} #forum .jio-forum__vote label{display:flex;gap:12px;padding:12px;border:1px solid var(--forum-line);margin-bottom:8px;border-radius:4px} -#forum .jio-forum__vote-count{margin-left:auto}#forum .jio-forum__locked{padding:30px;text-align:center;background:#f7f9fa} +#forum .jio-forum__vote-count{margin-left:auto} +#forum .jio-forum__locked{padding:30px;text-align:center;background:#f7f9fa} #forum .jio-forum__pagination ul{list-style:none;display:flex;gap:7px;padding:0;margin:24px 0} #forum .jio-forum__pagination a,#forum .jio-forum__pagination span{display:block;padding:6px 10px;border:1px solid var(--forum-line);border-radius:4px} #forum .jio-forum__empty{padding:36px;text-align:center;color:var(--forum-muted)} -@media(max-width:800px){#forum .jio-forum__layout{grid-template-columns:1fr}#forum .jio-forum__aside{border-left:0;padding-left:0}#forum .jio-forum__plates{grid-template-columns:1fr}} -@media(max-width:480px){#forum .jio-forum__wrap{width:calc(100% - 24px)}#forum h1{font-size:23px}#forum .jio-forum__post{padding:16px 0}#forum .jio-forum__tabs{gap:18px}} -#forum .jio-forum__create-plate{width:100%;margin-top:10px} +#forum .jio-forum__badge,#forum .jio-forum__badges a,#forum .jio-forum__badges span{padding:4px 8px;background:#f1f5f7;color:#48717c;border-radius:4px;font-size:12px} #forum .jio-forum__section-title{font-size:18px;margin:28px 0 0} #forum .jio-forum__reply{display:flex;gap:12px;padding:17px 0;border-bottom:1px solid var(--forum-line)} #forum .jio-forum__reply-avatar img{border-radius:50%} #forum .jio-forum__reply-main{flex:1;min-width:0} #forum .jio-forum__reply-head{display:flex;gap:12px;align-items:center;font-size:13px} -#forum .jio-forum__reply-head time{color:var(--forum-muted)}#forum .jio-forum__reply-head strong{color:#147e70} +#forum .jio-forum__reply-head time{color:var(--forum-muted)} +#forum .jio-forum__reply-head strong{color:#147e70} #forum .jio-forum__reply-text{padding:10px 0;line-height:1.7;overflow-wrap:anywhere} #forum .jio-forum__reply-actions button{border:0;background:none;color:var(--forum-accent);padding:4px 10px 4px 0;cursor:pointer} #forum .jio-forum__reply-form{display:grid;gap:12px;margin:24px 0} #forum .jio-forum__reply-form h3{font-size:17px;margin:0} #forum .jio-forum__reply-form textarea{width:100%;border:1px solid var(--forum-line);padding:12px;border-radius:4px;background:var(--body-bg,#fff);color:inherit} #forum .jio-forum__reply-form button{justify-self:start} -#forum .jio-forum__widget-link{display:flex;justify-content:space-between;gap:10px;padding:7px 0;color:var(--forum-ink)} -#forum .jio-forum__widget-link span{color:var(--forum-muted)} +#forum .jio-forum__reply-parent{color:var(--forum-muted);font-size:12px;margin:8px 0 0} +#forum .jio-forum__editor-tools{display:flex;gap:8px;flex-wrap:wrap} +#forum .wp-editor-wrap{border:1px solid var(--forum-line)} +#forum .jio-forum__applications{margin:18px 0;padding:14px 0;border-top:1px solid var(--forum-line)} +#forum .jio-forum__applications h2{font-size:16px;margin:0 0 8px} +#forum .jio-forum__application{display:flex;align-items:center;gap:10px;padding:6px 0} +#forum .jio-forum__application button{border:1px solid var(--forum-line);border-radius:4px;background:transparent;padding:4px 9px;cursor:pointer} + +/* ── 弹窗 / 用户面板 ──────────────────────────────────────────── */ .jio-forum__dialog{width:min(440px,calc(100% - 28px));max-height:90vh;border:1px solid #dce2e5;border-radius:6px;padding:22px;background:var(--body-bg,#fff);color:inherit;box-shadow:0 20px 60px #0003} .jio-forum__dialog::backdrop{background:#10182088} .jio-forum__dialog h2{font-size:20px;margin:0 0 18px;overflow-wrap:anywhere} @@ -72,29 +247,12 @@ .jio-forum__qr{width:180px;height:180px;margin:auto} .jio-forum__user-panel h2{font-size:18px;margin:0 0 14px} .jio-forum__user-row{display:flex;justify-content:space-between;gap:12px;padding:12px 0;border-bottom:1px solid #e6e9eb} -#forum .jio-forum__applications{margin:18px 0;padding:14px 0;border-top:1px solid var(--forum-line)} -#forum .jio-forum__applications h2{font-size:16px;margin:0 0 8px} -#forum .jio-forum__application{display:flex;align-items:center;gap:10px;padding:6px 0} -#forum .jio-forum__application button{border:1px solid var(--forum-line);border-radius:4px;background:transparent;padding:4px 9px;cursor:pointer} -#forum .jio-forum__reply-parent{color:var(--forum-muted);font-size:12px;margin:8px 0 0} -#forum .jio-forum__hot-reply{margin:0 0 10px 58px;padding:9px 12px;background:#f5f8f9;color:var(--forum-muted);font-size:13px;border-radius:4px} -#forum .jio-forum__pending{padding:16px 0;border-bottom:1px solid var(--forum-line)} -#forum .jio-forum__pending h2{font-size:16px;margin:0 0 10px} -#forum .jio-forum__pending>div{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:6px 0} -#forum .jio-forum__pending button{border:1px solid var(--forum-line);background:transparent;border-radius:4px;padding:5px 9px;cursor:pointer} -#forum .jio-forum__editor-tools{display:flex;gap:8px;flex-wrap:wrap} -#forum .wp-editor-wrap{border:1px solid var(--forum-line)} -body.dark-theme #forum .jio-forum__chips a,body.dark-theme #forum .jio-forum__badge,body.dark-theme #forum .jio-forum__badges a,body.dark-theme #forum .jio-forum__badges span{background:var(--muted-bg-2-color);color:var(--forum-accent)} -body.dark-theme #forum .jio-forum__plate-cover{background:var(--muted-bg-2-color)} -body.dark-theme #forum .jio-forum__locked,body.dark-theme #forum .jio-forum__hot-reply{background:var(--muted-bg-2-color)} + +/* ── 深色主题 ─────────────────────────────────────────────────── */ +body.dark-theme #forum .jio-forum__badge,body.dark-theme #forum .jio-forum__badges a,body.dark-theme #forum .jio-forum__badges span{background:var(--muted-bg-2-color);color:var(--forum-accent)} +body.dark-theme #forum .jio-forum__locked,body.dark-theme #forum .forum-posts .hot-comment,body.dark-theme #forum .forum-posts .hide-post{background:var(--muted-bg-2-color)} body.dark-theme #forum .jio-forum__form input,body.dark-theme #forum .jio-forum__form select,body.dark-theme #forum .jio-forum__form textarea,body.dark-theme #forum .jio-forum__reply-form textarea{background:var(--main-bg-color);border-color:var(--forum-line);color:var(--forum-ink)} body.dark-theme .jio-forum__dialog{background:var(--main-bg-color);color:var(--key-color);border-color:var(--muted-4-color)} body.dark-theme .jio-forum__dialog input,body.dark-theme .jio-forum__dialog select,body.dark-theme .jio-forum__dialog textarea{background:var(--main-bg-color);color:var(--key-color);border-color:var(--muted-4-color)} body.dark-theme #forum .jio-forum__action{color:var(--forum-muted)} -#forum .jio-forum__term-management{margin:16px 0;padding:16px 0;border-bottom:1px solid var(--forum-line)} -#forum .jio-forum__term-management h2{font-size:16px} -#forum .jio-forum__term-management form{display:flex;gap:8px;align-items:end;flex-wrap:wrap} -#forum .jio-forum__term-management label{display:grid;gap:5px;font-size:13px} -#forum .jio-forum__term-management input,#forum .jio-forum__term-management select{min-height:34px;padding:5px 8px;border:1px solid var(--forum-line);border-radius:4px;background:var(--main-bg-color);color:var(--forum-ink)} -#forum .jio-forum__term-row{display:flex;gap:8px;align-items:center;padding:5px 0} -#forum .jio-forum__term-row button{border:1px solid var(--forum-line);border-radius:4px;background:transparent;padding:4px 7px;cursor:pointer} +body.dark-theme .panel.panel-plate,body.dark-theme .plate-lists .plate-item,body.dark-theme .forum-posts.alone,body.dark-theme .scroll-plate .plate-card{background:var(--main-bg-color)} diff --git a/assets/js/forum-runtime.js b/assets/js/forum-runtime.js index d88c5e4b..df947eff 100644 --- a/assets/js/forum-runtime.js +++ b/assets/js/forum-runtime.js @@ -23,8 +23,7 @@ else if (value) window.alert(value) } - const modal = (title, fields, submitText = '确认') => new Promise((resolve) => { - const dialog = document.createElement('dialog') + const modal = (title, fields, submitText = '确认') => new Promise((resolve) => { const dialog = document.createElement('dialog') dialog.className = 'jio-forum__dialog' const form = document.createElement('form') form.innerHTML = '

' @@ -172,7 +171,13 @@ return } const open = event.target.closest('[data-forum-open]') - if (open) { root.querySelector(`[data-forum-dialog="${open.dataset.forumOpen}"]`)?.showModal(); return } + if (open) { + const dialog = root.querySelector(`[data-forum-dialog="${open.dataset.forumOpen}"]`) + const category = dialog?.querySelector('select[name="category_id"]') + if (category && open.dataset.category) category.value = open.dataset.category + dialog?.showModal() + return + } const close = event.target.closest('[data-forum-close]') if (close) { close.closest('dialog')?.close(); return } const reply = event.target.closest('[data-forum-reply-to]') @@ -193,7 +198,7 @@ if (action === 'plate-unlock') { await checkout('plate', id); return } if (action === 'plate-audit') { result = await send(`plates/${id}/audit`, 'POST', { status: 'publish' }) - button.closest('.jio-forum__pending > div')?.remove() + button.closest('.forum-pending-plate')?.remove() } else if (action === 'plate-delete') { const data = await modal('删除版块', [ { name: 'posts_action', label: '关联帖子', type: 'select', options: [{ value: 'trash', label: '一并移入回收站' }, { value: 'move', label: '移动到其他版块' }] }, @@ -213,16 +218,6 @@ if (!window.confirm('确定删除此回复?')) return result = await send(`replies/${id}/delete`, 'POST') button.closest('.jio-forum__reply')?.remove() - } else if (action === 'term-edit') { - const name = button.closest('.jio-forum__term-row')?.querySelector('span')?.textContent || '' - const data = await modal('编辑分类', [{ name: 'name', label: '名称', value: name }], '保存') - if (!data) return - result = await send(`terms/${id}`, 'PATCH', data) - button.closest('.jio-forum__term-row').querySelector('span').textContent = data.name - } else if (action === 'term-delete') { - if (!window.confirm('确定删除这个分类?')) return - result = await send(`terms/${id}`, 'DELETE') - button.closest('.jio-forum__term-row')?.remove() } else if (action === 'follow') { try { result = await send(`plates/${id}/follow`, 'POST') } catch (error) { if (error.message.includes('付费')) { await checkout('plate', id); return } throw error } @@ -254,7 +249,17 @@ button.dataset.forumAction = 'restore' button.textContent = '撤销删除' } else if (action === 'adopt' || action === 'audit') window.location.reload() - else button.textContent = result.data?.label || result.message || button.textContent + else if (action === 'follow') { + const following = result.data?.following === true + button.classList.toggle('active', following) + const label = button.querySelector('text') + if (label) label.textContent = following ? '已关注' : '关注' + else button.textContent = result.data?.label || button.textContent + } else if (button.querySelector('text')) { + button.querySelector('text').textContent = result.data?.label || result.message || button.querySelector('text').textContent + } else { + button.textContent = result.data?.label || result.message || button.textContent + } message(button, result.message || '') } catch (error) { message(button, error.message) @@ -263,4 +268,86 @@ button.disabled = false } }) + + // 论坛首页栏目:点击切换 + 懒加载该栏目的 ajaxpager 内容 + const refresh = (pane) => { + if (typeof window.auto_fun === 'function') window.auto_fun() + if (typeof window.jioInitForumMedia === 'function') window.jioInitForumMedia(pane) + } + + const loadTab = async (pane, href) => { + if (!pane || pane.dataset.loaded === '1' || pane.dataset.loading === '1') return + pane.dataset.loading = '1' + pane.querySelector('.post_ajax_loader')?.classList.add('is-loading') + try { + const response = await fetch(href, { credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } }) + if (!response.ok) throw new Error('加载失败') + const doc = new DOMParser().parseFromString(await response.text(), 'text/html') + const source = doc.getElementById(pane.id) + if (!source) throw new Error('内容不存在') + pane.innerHTML = source.innerHTML + pane.dataset.loaded = '1' + refresh(pane) + } catch (error) { + pane.querySelector('.post_ajax_loader')?.classList.remove('is-loading') + message(pane, error.message) + } finally { + pane.dataset.loading = '' + } + } + + const loadMore = async (link, pane) => { + if (pane.dataset.loading === '1') return + pane.dataset.loading = '1' + const more = link.closest('.forum-more') + const loader = [...pane.children].find((item) => item.classList.contains('post_ajax_loader')) + more?.classList.add('is-loading') + loader?.classList.add('is-loading') + try { + const response = await fetch(link.getAttribute('href'), { credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest' } }) + if (!response.ok) throw new Error('加载失败') + const doc = new DOMParser().parseFromString(await response.text(), 'text/html') + const source = doc.getElementById(pane.id) + if (!source) throw new Error('内容不存在') + 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')) + incoming.forEach((item) => pane.insertBefore(item, anchor)) + const next = source.querySelector('[data-forum-more]') + if (next && anchor) anchor.querySelector('[data-forum-more]')?.setAttribute('href', next.getAttribute('href')) + else anchor?.remove() + refresh(pane) + } catch (error) { + message(pane, error.message) + } finally { + pane.dataset.loading = '' + more?.classList.remove('is-loading') + loader?.classList.remove('is-loading') + } + } + + root.addEventListener('click', (event) => { + const tab = event.target.closest('[data-forum-tab]') + if (tab) { + event.preventDefault() + const target = tab.getAttribute('data-forum-tab') + const pane = root.querySelector(target) + root.querySelectorAll('[data-forum-tab]').forEach((link) => link.closest('li')?.classList.toggle('active', link === tab)) + root.querySelectorAll('.forum-tab-pane').forEach((item) => { + const current = `#${item.id}` === target + item.classList.toggle('active', current) + item.classList.toggle('in', current) + }) + if (pane) { + loadTab(pane, pane.dataset.href || tab.getAttribute('href')) + if (pane.dataset.href && window.history?.replaceState) window.history.replaceState(null, document.title, pane.dataset.href) + } + return + } + const more = event.target.closest('[data-forum-more]') + if (more) { + event.preventDefault() + const pane = more.closest('.forum-tab-pane') + if (pane) loadMore(more, pane) + } + }) })() diff --git a/search.php b/search.php index 90cc2f30..40ae48d3 100644 --- a/search.php +++ b/search.php @@ -132,12 +132,12 @@ if (is_active_sidebar('all_top_fluid') || is_active_sidebar('search_top_fluid')) } ?> have_posts()) : ?> -
- have_posts()) : $wp_query->the_post(); \Jio\Forum\View::post_card(get_post()); endwhile; wp_reset_postdata(); ?> + have_posts()) : ?> -
+ diff --git a/src/Admin/Schema/SchemaForum.php b/src/Admin/Schema/SchemaForum.php index f687ffb4..476ab0da 100644 --- a/src/Admin/Schema/SchemaForum.php +++ b/src/Admin/Schema/SchemaForum.php @@ -17,7 +17,41 @@ final class SchemaForum { ]), 'home' => self::section('home', '首页与列表', [ self::field('jio_forum_page_size', '每页帖子数', 'number', 20, ['min' => 1, 'max' => 50]), - self::field('jio_forum_home_tabs', '首页栏目', 'checkbox', ['latest', 'popular', 'essence', 'question', 'vote', 'plates', 'follow'], ['options' => self::options(['latest' => '最新', 'popular' => '热门', 'essence' => '精华', 'question' => '问答', 'vote' => '投票', 'plates' => '版块', 'follow' => '已关注'])]), + self::field('jio_forum_home_tab_items', '首页栏目', 'repeater', \Jio\Forum\HomeTabs::defaults(), [ + 'addText' => '添加栏目', + 'description' => '选择并排序论坛首页的栏目,访客可通过 index 参数直接打开某个栏目', + 'fields' => [ + self::field('title', '栏目标题', 'input', ''), + self::field('type', '栏目类型', 'select', 'posts', ['options' => self::options([ + 'follow' => '关注', + 'synthesis' => '综合', + 'plate' => '版块', + 'posts' => '帖子列表', + ])]), + self::field('orderby', '排序方式', 'select', 'date', ['options' => self::options([ + 'date' => '发布时间', + 'modified' => '修改时间', + 'last_reply' => '最新回复', + 'popular' => '热度', + 'views' => '阅读量', + 'score' => '评分', + ])]), + self::field('filter', '内容筛选', 'select', '', ['options' => self::options([ + '' => '全部', + 'essence' => '精华', + 'question' => '问答', + 'vote' => '投票', + ])]), + self::field('style', '列表样式', 'radio', 'detail', ['options' => self::options(['detail' => '详细内容', 'mini' => '简约风格'])]), + self::field('show', '显示端', 'checkbox', ['pc_s', 'm_s'], ['options' => self::options(['pc_s' => 'PC 端', 'm_s' => '移动端'])]), + ], + ]), + self::field('jio_forum_home_tab_active', '默认栏目', 'number', 2, ['min' => 1, 'max' => 20, 'description' => '打开首页时默认显示第几个栏目']), + self::field('jio_forum_home_quick_post', '快速发布', 'switcher', true), + self::field('jio_forum_home_quick_post_tab', '快速发布所在栏目', 'number', 2, ['min' => 1, 'max' => 20]), + self::field('jio_forum_home_hot_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_show_plate_cover', '显示版块封面', 'switcher', true), self::field('jio_forum_search_enabled', '搜索包含帖子', 'switcher', true), ]), @@ -65,6 +99,8 @@ final class SchemaForum { return ['key' => $key, 'title' => $title, 'version' => '1.0.0', 'option_key' => 'jio_settings', 'groups' => [['key' => $key, 'fields' => $fields]]]; } + /** Initial tab rows for the forum home; mirrors the built-in home layout. */ + private static function field(string $key, string $label, string $type, $default, array $extra = []): array { return array_merge(['key' => $key, 'label' => $label, 'type' => $type, 'default' => $default], $extra); } diff --git a/src/Core/Templates.php b/src/Core/Templates.php index 2d0b294e..a2dae321 100644 --- a/src/Core/Templates.php +++ b/src/Core/Templates.php @@ -45,6 +45,7 @@ class Templates { 'templates/pages/page-submit.php' => __('Jio - 写文章/投稿', 'jio'), 'templates/pages/page-posts-navs.php' => __('Jio - 文章导航', 'jio'), 'templates/pages/page-friends.php' => __('Jio - 友情链接', 'jio'), + 'templates/pages/page-forums.php' => __('Jio - 论坛首页', 'jio'), ]; } diff --git a/src/Forum/Cards.php b/src/Forum/Cards.php new file mode 100644 index 00000000..75cd6021 --- /dev/null +++ b/src/Forum/Cards.php @@ -0,0 +1,215 @@ + */ + private static array $hot = []; + + /** Prime authors, plates and hot replies for a page of posts in batch queries. */ + public static function prime(array $posts): void { + $ids = $authors = $plates = []; + foreach ($posts as $post) { + if (!$post instanceof \WP_Post) continue; + $ids[] = $post->ID; + $authors[] = (int) $post->post_author; + $plates[] = (int) get_post_meta($post->ID, '_jio_forum_plate_id', true); + } + if (!$ids) return; + cache_users(array_values(array_unique(array_filter($authors)))); + _prime_post_caches(array_values(array_unique(array_filter($plates))), true, true); + $wanted = array_flip($ids); + $comments = get_comments(['status' => 'approve', 'meta_key' => '_jio_forum_hot', 'meta_value' => '1', 'number' => 200, 'orderby' => 'comment_date_gmt', 'order' => 'DESC']); + foreach ($comments as $comment) { + $post_id = (int) $comment->comment_post_ID; + if (isset($wanted[$post_id]) && !isset(self::$hot[$post_id])) self::$hot[$post_id] = $comment; + } + } + + public static function post_detail(\WP_Post $post, string $class = 'alone ajax-item', bool $show_plate = true, bool $show_topping = false): string { + $id = $post->ID; + $can_view = Access::can_view(get_current_user_id(), $id); + $plate_id = (int) get_post_meta($id, '_jio_forum_plate_id', true); + $plate = $show_plate ? self::plate_button($plate_id) : ''; + $html = '
'; + $html .= self::status_badge($post); + $html .= self::author_box($post); + $html .= $can_view ? self::cover($post) : ''; + $html .= '
' . self::title($post, 'forum-title flex ac', '', $show_topping) . '
'; + $html .= self::content($post, $can_view); + $html .= '
' . ($plate !== '' ? $plate : '') . self::action_meta($post) . '
'; + return $html . '
'; + } + + public static function post_mini(\WP_Post $post, string $class = 'alone ajax-item', bool $show_topping = false): string { + $author = (int) $post->post_author; + $name = self::author_link($author); + $html = '
'; + $html .= self::status_badge($post); + $html .= '
' . self::avatar($author, 'avatar-img forum-avatar') . '' . $name . '
'; + $html .= ''; + return $html . '
'; + } + + public static function title(\WP_Post $post, string $class, string $link_class = '', bool $show_topping = false): string { + $id = $post->ID; + $badges = ''; + if ($show_topping && get_post_meta($id, '_jio_forum_pinned', true)) $badges .= '置顶'; + if (get_post_meta($id, '_jio_forum_essence', true)) $badges .= '精华'; + $type = (string) get_post_meta($id, '_jio_forum_type', true); + if ($type === 'question') { + $solved = (bool) get_post_meta($id, '_jio_forum_adopted_comment', true); + $badges .= '问' . ($solved ? '已解决' : '待解决') . ''; + } elseif ($type === 'vote') { + $badges .= '投票'; + } + $url = $post->post_status === 'publish' ? get_permalink($post) : Core::edit_url($id); + $link_class = $link_class !== '' ? ' class="' . esc_attr($link_class) . '"' : ''; + return '

' . $badges . esc_html($post->post_title) . '

'; + } + + public static function plate_item(\WP_Post $plate): string { + $url = get_permalink($plate); + $counts = PlateStats::get($plate->ID); + $today = $counts['today'] > 0 ? '' . esc_html(sprintf('今日:%s', jio_cut_count($counts['today']))) . '' : ''; + $excerpt = wp_trim_words(wp_strip_all_tags($plate->post_excerpt ?: $plate->post_content), 30); + $html = '
'; + if (jio_get_setting('jio_forum_show_plate_cover', true)) { + $html .= '' . self::thumbnail($plate) . ''; + } + $html .= '
'; + $html .= '' . esc_html($excerpt) . ''; + $html .= '
帖子' . esc_html(jio_cut_count($counts['posts'])) . '关注' . esc_html(jio_cut_count((int) get_post_meta($plate->ID, '_jio_forum_follow_count', true))) . '
'; + return $html . '
'; + } + + public static function plate_card(\WP_Post $plate, string $class = '', bool $follow = true, bool $today = false): string { + $url = get_permalink($plate); + $counts = PlateStats::get($plate->ID); + $spot = $today && $counts['today'] > 0 ? '' : ''; + $html = ''; + } + + /** @param \WP_Post[] $plates */ + public static function plate_slide(array $plates, string $class = '', bool $follow = true, bool $today = false): string { + if (!$plates) return ''; + $slides = ''; + foreach ($plates as $plate) { + $slides .= '
' . self::plate_card($plate, $class, $follow, $today) . '
'; + } + return '
' . $slides . '
'; + } + + public static function follow_button(int $plate_id, string $class): string { + if (!is_user_logged_in()) { + return '关注'; + } + $followed = Access::follows(get_current_user_id(), $plate_id); + return ''; + } + + public static function plate_button(int $plate_id): string { + if ($plate_id <= 0 || get_queried_object_id() === $plate_id || get_post_status($plate_id) !== 'publish') return ''; + return '' . esc_html(get_the_title($plate_id)) . ''; + } + + public static function avatar(int $user_id, string $class): string { + return function_exists('jio_get_avatar_box') ? jio_get_avatar_box($user_id, $class) : '' . get_avatar($user_id, 80) . ''; + } + + public static function ago(string $date): string { + return function_exists('jio_time_ago') ? jio_time_ago($date) : human_time_diff((int) strtotime($date), (int) current_time('timestamp')) . '前'; + } + + private static function author_link(int $user_id): string { + return '' . esc_html((string) get_the_author_meta('display_name', $user_id)) . ''; + } + + private static function author_box(\WP_Post $post): string { + $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); + $views = jio_cut_count((int) get_post_meta($post->ID, '_jio_forum_views', true)); + $desc = '' . esc_html(self::ago($post->post_date)) . '' . esc_html(sprintf('%s次阅读', $views)) . ''; + return ''; + } + + private static function status_badge(\WP_Post $post): string { + $labels = ['pending' => '待审核', 'draft' => '草稿', 'trash' => '已删除']; + return isset($labels[$post->post_status]) ? '' . esc_html($labels[$post->post_status]) . '' : ''; + } + + private static function cover(\WP_Post $post): string { + if (!has_post_thumbnail($post)) return ''; + return '
' . get_the_post_thumbnail($post, 'large', ['class' => 'fit-cover']) . '
'; + } + private static function content(\WP_Post $post, bool $can_view): string { + if (!$can_view) { + return '
此处内容已隐藏
'; + } + $url = $post->post_status === 'publish' ? get_permalink($post) : Core::edit_url($post->ID); + $images = self::images($post); + $text = trim((string) preg_replace('/\s+/u', ' ', wp_strip_all_tags(strip_shortcodes($post->post_excerpt ?: $post->post_content)))); + $text = jio_str_cut($text, 0, $images !== '' ? 60 : 120); + $html = '
' . ($text !== '' ? '' . esc_html($text) . '' : '') . '
'; + $html .= $images; + $hot = self::$hot[$post->ID] ?? null; + if ($hot instanceof \WP_Comment) { + $html .= '
' . self::avatar((int) $hot->user_id, 'avatar-img') . '' . esc_html(get_comment_author($hot)) . '热门回复
'; + $html .= '
' . esc_html(wp_trim_words(wp_strip_all_tags($hot->comment_content), 40)) . '
'; + } + return $html . '
'; + } + + private static function images(\WP_Post $post): string { + if (!preg_match_all('/]+src=["\']([^"\']+)["\']/i', $post->post_content, $matches)) return ''; + $urls = array_values(array_unique(array_filter(array_map('esc_url_raw', $matches[1])))); + $total = count($urls); + if ($total === 0) return ''; + $shown = min(4, $total); + $html = '
'; + foreach (array_slice($urls, 0, $shown) as $index => $url) { + $more = $index === $shown - 1 && $total > $shown ? '+' . ($total - $shown) . '' : ''; + $html .= '' . $more . ''; + } + return $html . '
'; + } + + private static function action_meta(\WP_Post $post): string { + $id = $post->ID; + $score = (int) get_post_meta($id, '_jio_forum_score', true); + $reply = (int) $post->comment_count; + $html = '
'; + if (jio_get_setting('jio_forum_score_enabled', true)) { + $html .= is_user_logged_in() + ? '' + : '' . esc_html($score ? jio_cut_count($score) : '加分') . ''; + } + $html .= '' . esc_html($reply ? jio_cut_count($reply) : '回复') . ''; + if ($post->post_status === 'publish' && function_exists('jio_get_post_share_btn')) { + $html .= jio_get_post_share_btn($post, 'item btn-share', true, false); + } + return $html . '
'; + } + + private static function icon_metas(\WP_Post $post): string { + $id = $post->ID; + return '' . esc_html(jio_cut_count((int) get_post_meta($id, '_jio_forum_views', true))) . '' + . '' . esc_html(jio_cut_count((int) $post->comment_count)) . '' + . '' . esc_html(jio_cut_count((int) get_post_meta($id, '_jio_forum_score', true))) . ''; + } + + private static function thumbnail(\WP_Post $plate): string { + $src = get_the_post_thumbnail_url($plate, 'thumbnail') ?: get_template_directory_uri() . '/assets/img/thumbnail.svg'; + return ''; + } +} diff --git a/src/Forum/Core.php b/src/Forum/Core.php index 4bf9fd89..f17e6273 100644 --- a/src/Forum/Core.php +++ b/src/Forum/Core.php @@ -28,6 +28,14 @@ final class Core { add_action('trashed_post', [self::class, 'cascade_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('jio_is_show_sidebar', [self::class, 'show_home_sidebar']); + } + + /** The forum home renders its own sidebar column, so keep the layout slot open. */ + public static function show_home_sidebar(bool $show): bool { + if ($show || !self::enabled() || !is_page_template('templates/pages/page-forums.php')) return $show; + $cfg = function_exists('jio_get_sidebar_config_safe') ? jio_get_sidebar_config_safe() : []; + return !empty($cfg['enabled']); } public static function enabled(): bool { diff --git a/src/Forum/HomeContent.php b/src/Forum/HomeContent.php new file mode 100644 index 00000000..bf2e7a52 --- /dev/null +++ b/src/Forum/HomeContent.php @@ -0,0 +1,196 @@ + self::plate_tab(), + 'follow' => self::follow_tab($tab, $index, $page), + 'pending' => self::pending_tab($tab, $index, $page), + default => self::posts_tab($tab, $index, $page), + }; + if ($page === 1 && $tab['type'] !== 'plate' && jio_get_setting('jio_forum_home_quick_post', true) && $index === (int) jio_get_setting('jio_forum_home_quick_post_tab', 2)) { + $html = QuickPost::render() . $html; + } + return $html; + } + + public static function page(): int { + $raw = $_GET['fpage'] ?? 1; + return max(1, is_scalar($raw) ? absint($raw) : 1); + } + + private static function posts_tab(array $tab, int $index, int $page): string { + $args = self::query_args($tab, $page); + $html = self::post_list($args, $tab, $index, $page, $tab['type'] === 'synthesis'); + if ($html !== '') return $html; + $html = jio_get_ajax_null('内容空空如也'); + if ($page === 1) { + $html .= '
' . self::create_plate_button('ml10 mr10 mt10 but hollow c-blue padding-lg') . self::publish_button('ml10 mr10 mt10 but hollow c-green padding-lg') . '
'; + } + return $html; + } + + private static function follow_tab(array $tab, int $index, int $page): string { + $user_id = get_current_user_id(); + if (!$user_id) { + return '
' . jio_get_null('登录后继续查看', 20, 'null-user.svg', '', 0, 220) + . '

登录

'; + } + $followed = View::user_marked_ids($user_id, '_jio_forum_follow_'); + $html = ''; + if ($page === 1 && jio_get_setting('jio_forum_home_recommend_plate', true)) { + $plates = self::plates(['posts_per_page' => 8, 'post__not_in' => $followed]); + $slide = Cards::plate_slide($plates); + if ($slide !== '') { + $html .= '
热门推荐
' . $slide . '
'; + } + } + $list = ''; + if ($followed) { + $args = self::query_args($tab, $page); + $args['meta_query'][] = ['key' => '_jio_forum_plate_id', 'value' => $followed, 'compare' => 'IN']; + $list = self::post_list($args, $tab, $index, $page); + } + return $html . ($list !== '' ? $list : jio_get_ajax_null('暂无内容', 60, 'null.svg')); + } + + private static function pending_tab(array $tab, int $index, int $page): string { + $html = ''; + if ($page === 1) { + foreach (get_posts(['post_type' => Core::PLATE, 'post_status' => 'pending', 'posts_per_page' => 20]) as $plate) { + $html .= '
待审核版块' . esc_html($plate->post_title) . '' + . '
'; + } + } + $list = self::post_list(['post_type' => Core::POST, 'post_status' => 'pending', 'posts_per_page' => self::per_page(), 'paged' => $page], $tab, $index, $page); + $html .= $list; + return $html !== '' ? $html : jio_get_ajax_null('暂无待审核内容'); + } + + private static function plate_tab(): string { + $html = ''; + $user_id = get_current_user_id(); + if ($user_id && jio_get_setting('jio_forum_home_follow_plate', true)) { + $followed = View::user_marked_ids($user_id, '_jio_forum_follow_'); + $slide = $followed ? Cards::plate_slide(self::plates(['post__in' => $followed, 'posts_per_page' => 20]), 'mini', false, true) : ''; + if ($slide !== '') $html .= self::panel('我关注的版块', $slide); + } + if (jio_get_setting('jio_forum_home_hot_plate', true)) { + $slide = Cards::plate_slide(self::plates(['posts_per_page' => 8, 'meta_key' => '_jio_forum_follow_count', 'orderby' => 'meta_value_num'])); + if ($slide !== '') $html .= self::panel('热门推荐', $slide); + } + $all = self::plates(['posts_per_page' => -1, 'orderby' => 'menu_order title', 'order' => 'ASC']); + $groups = []; + $loose = []; + $terms = wp_get_object_terms(wp_list_pluck($all, 'ID'), Core::CATEGORY, ['fields' => 'all_with_object_id']); + $by_plate = []; + foreach (is_array($terms) ? $terms : [] as $term) { + $by_plate[(int) $term->object_id][] = $term; + } + foreach ($all as $plate) { + if (empty($by_plate[$plate->ID])) { + $loose[] = $plate; + continue; + } + foreach ($by_plate[$plate->ID] as $term) { + $groups[$term->term_id]['term'] = $term; + $groups[$term->term_id]['plates'][] = $plate; + } + } + uasort($groups, static fn(array $a, array $b): int => count($b['plates']) <=> count($a['plates'])); + $plate_name = (string) jio_get_setting('jio_forum_plate_name', '版块') ?: '版块'; + $single_group = count($groups) === 0 && $loose; + if ($loose) $groups[0] = ['term' => null, 'plates' => $loose]; + $i = 1; + foreach ($groups as $term_id => $group) { + $name = $group['term'] instanceof \WP_Term ? $group['term']->name : ($single_group ? '全部' . $plate_name : '其他' . $plate_name); + $html .= self::plate_group('home-plate-panel-' . $i++, $name, $group['plates'], (int) $term_id); + } + if ($html === '') { + $html = jio_get_null('暂时没有版块', 60, 'null.svg', '', 0, 170) . '
' . self::create_plate_button('but hollow c-blue padding-lg') . '
'; + } + return '
' . $html . '
'; + } + + /** @param \WP_Post[] $plates */ + private static function plate_group(string $id, string $name, array $plates, int $term_id): string { + $add = self::create_plate_button('plate-add ml20 but hollow c-blue p2-10 px12 flex0', $term_id); + $items = ''; + foreach ($plates as $plate) { + $items .= Cards::plate_item($plate); + } + $title = '' . esc_html($name) . ''; + $top = wp_is_mobile() + ? '

' . $title . $add . '

' + : '

' . $title . '' . $add . '

'; + return '
' . $top . '
' . $items . '
'; + } + + private static function panel(string $title, string $body): string { + return '

' . esc_html($title) . '

' . $body . '
'; + } + + /** @return \WP_Post[] */ + private static function plates(array $args): array { + $plates = get_posts(array_merge(['post_type' => Core::PLATE, 'post_status' => 'publish', 'orderby' => 'modified', 'order' => 'DESC', 'no_found_rows' => true], $args)); + PlateStats::prime(wp_list_pluck($plates, 'ID')); + return $plates; + } + + private static function query_args(array $tab, int $page): array { + $args = [ + 'post_type' => Core::POST, + 'post_status' => 'publish', + 'posts_per_page' => self::per_page(), + 'paged' => $page, + 'jio_forum_list' => $tab['orderby'], + 'meta_query' => [], + ]; + if ($tab['filter'] === 'essence') { + $args['meta_query'][] = ['key' => '_jio_forum_essence', 'value' => 1]; + } elseif (in_array($tab['filter'], ['question', 'vote'], true)) { + $args['meta_query'][] = ['key' => '_jio_forum_type', 'value' => $tab['filter']]; + } + return $args; + } + + private static function post_list(array $args, array $tab, int $index, int $page, bool $show_topping = false): string { + $query = new \WP_Query($args); + if (!$query->have_posts()) return ''; + Cards::prime($query->posts); + $html = ''; + foreach ($query->posts as $post) { + $html .= $tab['style'] === 'mini' || $tab['type'] === 'pending' + ? Cards::post_mini($post, 'alone ajax-item', $show_topping) + : Cards::post_detail($post, 'alone ajax-item', true, $show_topping); + } + if ($page < (int) $query->max_num_pages) { + $next = add_query_arg(['index' => $index, 'fpage' => $page + 1], remove_query_arg(['index', 'fpage'])); + $html .= ''; + } + $html .= '
' . HomeTabs::placeholder('posts', 2) . '
'; + return $html; + } + + private static function per_page(): int { + return max(1, min(50, (int) jio_get_setting('jio_forum_page_size', 20))); + } + + public static function create_plate_button(string $class, int $term_id = 0): string { + $user_id = get_current_user_id(); + if (!$user_id || (!Access::is_admin($user_id) && !jio_get_setting('jio_forum_user_plates', true))) return ''; + $name = (string) jio_get_setting('jio_forum_plate_name', '版块') ?: '版块'; + return ''; + } + + public static function publish_button(string $class): string { + $name = (string) jio_get_setting('jio_forum_post_name', '帖子') ?: '帖子'; + $url = is_user_logged_in() ? Core::edit_url() : jio_get_login_url(Core::edit_url()); + return '发布' . esc_html($name) . ''; + } +} diff --git a/src/Forum/HomeTabs.php b/src/Forum/HomeTabs.php new file mode 100644 index 00000000..7e2f5e8c --- /dev/null +++ b/src/Forum/HomeTabs.php @@ -0,0 +1,98 @@ +> */ + public static function defaults(): array { + $row = static fn(string $type, string $title, string $orderby = 'date', string $filter = ''): array => [ + 'title' => $title, 'type' => $type, 'orderby' => $orderby, 'filter' => $filter, 'style' => 'detail', 'show' => ['pc_s', 'm_s'], + ]; + return [ + $row('follow', '关注'), + $row('synthesis', '综合'), + $row('plate', '版块'), + $row('posts', '热门', 'views'), + $row('posts', '精华', 'modified', 'essence'), + $row('posts', '问答', 'modified', 'question'), + $row('posts', '投票', 'modified', 'vote'), + $row('posts', '最新回复', 'last_reply'), + $row('posts', '最高评分', 'score'), + ]; + } + + /** Tabs visible on the current device, numbered from 1 like the `index` query argument. */ + public static function visible(): array { + $rows = jio_get_setting('jio_forum_home_tab_items', self::defaults()); + $device = wp_is_mobile() ? 'm_s' : 'pc_s'; + $tabs = []; + foreach (is_array($rows) ? $rows : [] as $row) { + if (!is_array($row) || !in_array($device, (array) ($row['show'] ?? []), true)) continue; + $type = in_array($row['type'] ?? '', self::TYPES, true) ? $row['type'] : 'posts'; + $tabs[] = [ + 'title' => sanitize_text_field((string) ($row['title'] ?? '')) ?: '栏目', + 'type' => $type, + 'orderby' => in_array($row['orderby'] ?? '', self::ORDERS, true) ? $row['orderby'] : 'date', + 'filter' => in_array($row['filter'] ?? '', self::FILTERS, true) ? $row['filter'] : '', + 'style' => ($row['style'] ?? '') === 'mini' ? 'mini' : 'detail', + ]; + } + if (Access::is_admin(get_current_user_id())) { + $tabs[] = ['title' => '待审核', 'type' => 'pending', 'orderby' => 'date', 'filter' => '', 'style' => 'mini']; + } + return $tabs ? array_combine(range(1, count($tabs)), $tabs) : []; + } + + public static function active_index(int $count): int { + $raw = $_GET['index'] ?? 0; + $index = (is_scalar($raw) ? absint($raw) : 0) ?: (int) jio_get_setting('jio_forum_home_tab_active', 2); + return $index >= 1 && $index <= $count ? $index : 1; + } + + public static function render(): void { + $tabs = self::visible(); + if (!$tabs) return; + $active = self::active_index(count($tabs)); + $base = remove_query_arg(['index', 'fpage']); + $nav = $panes = ''; + foreach ($tabs as $index => $tab) { + $id = 'home-tab-' . $index; + $href = add_query_arg('index', $index, $base); + $label = esc_html($tab['title']); + $nav .= '' . $label . ''; + $panes .= '
'; + if ($index === $active) { + $panes .= HomeContent::render($tab, $index); + } else { + $panes .= '
' . self::placeholder($tab['type'] === 'plate' ? 'plate' : 'posts', $tab['type'] === 'plate' ? 2 : 4) . '
'; + } + $panes .= '
'; + } + + echo '
'; + echo '
    ' . $nav . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo '
'; + \Jio\WidgetSystem\WidgetAreas::render('forum_home_top_content'); + echo '
' . $panes . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + \Jio\WidgetSystem\WidgetAreas::render('forum_home_bottom_content'); + echo '
'; + } + + public static function placeholder(string $type, int $count): string { + if ($type === 'plate') { + $card = '
'; + $item = '
'; + $block = '
' . str_repeat($card, 4) . '
' + . '
' . str_repeat($item, 4) . '
'; + return str_repeat($block, max(1, $count)); + } + $post = '

'; + return str_repeat($post, max(1, $count)); + } +} diff --git a/src/Forum/HomeView.php b/src/Forum/HomeView.php new file mode 100644 index 00000000..c732ea69 --- /dev/null +++ b/src/Forum/HomeView.php @@ -0,0 +1,67 @@ +'; + \Jio\WidgetSystem\WidgetAreas::render('forum_home_top_fluid', 'fluid-widget-wrap'); + echo '
'; + echo '
'; + HomeTabs::render(); + echo '
'; + self::sidebar(); + echo '
'; + \Jio\WidgetSystem\WidgetAreas::render('forum_home_bottom_fluid', 'fluid-widget-wrap'); + echo ''; + get_footer(); + } + + private static function sidebar(): void { + $plate_name = (string) jio_get_setting('jio_forum_plate_name', '版块') ?: '版块'; + echo ''; + } + + private static function plate_dialog(): void { + echo '

创建版块

' + . '' + . '' + . '

'; + } +} diff --git a/src/Forum/Integration.php b/src/Forum/Integration.php index 8a1fffd1..aa7cf942 100644 --- a/src/Forum/Integration.php +++ b/src/Forum/Integration.php @@ -60,7 +60,7 @@ final class Integration { public static function sort_forum_posts(array $clauses, \WP_Query $query): array { $mode = sanitize_key((string) $query->get('jio_forum_list')); - if (!in_array($mode, ['latest', 'popular', 'question', 'vote', 'essence', 'follow'], true)) return $clauses; + if (!in_array($mode, ['latest', 'date', 'modified', 'views', 'score', 'popular', 'last_reply', 'question', 'vote', 'essence', 'follow'], true)) return $clauses; global $wpdb; $clauses['join'] .= " LEFT JOIN {$wpdb->postmeta} AS jio_forum_pin ON ({$wpdb->posts}.ID = jio_forum_pin.post_id AND jio_forum_pin.meta_key = '_jio_forum_pinned')"; $pin = 'CAST(COALESCE(jio_forum_pin.meta_value, 0) AS SIGNED) DESC'; @@ -71,6 +71,16 @@ final class Integration { $clauses['join'] .= " LEFT JOIN {$wpdb->postmeta} AS jio_forum_views ON ({$wpdb->posts}.ID = jio_forum_views.post_id AND jio_forum_views.meta_key = '_jio_forum_views')"; $rank = "({$wpdb->posts}.comment_count * 2 + CAST(COALESCE(jio_forum_up.meta_value, 0) AS SIGNED) * 10 - CAST(COALESCE(jio_forum_down.meta_value, 0) AS SIGNED) * 15 + CAST(COALESCE(jio_forum_fav.meta_value, 0) AS SIGNED) * 10 + CAST(COALESCE(jio_forum_views.meta_value, 0) AS SIGNED))"; $clauses['orderby'] = $pin . ', ' . $rank . ' DESC, ' . $wpdb->posts . '.post_date DESC'; + } elseif ($mode === 'views' || $mode === 'score') { + $key = $mode === 'views' ? '_jio_forum_views' : '_jio_forum_score'; + $alias = 'jio_forum_sort_' . $mode; + $clauses['join'] .= " LEFT JOIN {$wpdb->postmeta} AS $alias ON ({$wpdb->posts}.ID = $alias.post_id AND $alias.meta_key = '$key')"; + $clauses['orderby'] = $pin . ', CAST(COALESCE(' . $alias . '.meta_value, 0) AS SIGNED) DESC, ' . $wpdb->posts . '.post_date DESC'; + } elseif ($mode === 'modified') { + $clauses['orderby'] = $pin . ', ' . $wpdb->posts . '.post_modified DESC'; + } elseif ($mode === 'last_reply') { + $clauses['join'] .= " LEFT JOIN (SELECT comment_post_ID AS jio_reply_post_id, MAX(comment_date) AS jio_reply_last FROM {$wpdb->comments} WHERE comment_approved = '1' GROUP BY comment_post_ID) AS jio_forum_last ON ({$wpdb->posts}.ID = jio_forum_last.jio_reply_post_id)"; + $clauses['orderby'] = $pin . ', COALESCE(jio_forum_last.jio_reply_last, ' . $wpdb->posts . '.post_date) DESC'; } else { $clauses['orderby'] = $pin . ', ' . $wpdb->posts . '.post_date DESC'; } diff --git a/src/Forum/PlateStats.php b/src/Forum/PlateStats.php new file mode 100644 index 00000000..53945fe8 --- /dev/null +++ b/src/Forum/PlateStats.php @@ -0,0 +1,37 @@ + */ + private static array $stats = []; + + /** Load post totals and today's posts for many plates with one grouped query. */ + public static function prime(array $plate_ids): void { + $ids = array_values(array_diff(array_unique(array_filter(array_map('absint', $plate_ids))), array_keys(self::$stats))); + if (!$ids) return; + foreach ($ids as $id) { + self::$stats[$id] = ['posts' => 0, 'today' => 0]; + } + global $wpdb; + $placeholders = implode(',', array_fill(0, count($ids), '%d')); + $today = wp_date('Y-m-d 00:00:00'); + $sql = "SELECT CAST(pm.meta_value AS UNSIGNED) AS plate_id, COUNT(*) AS total, SUM(p.post_date >= %s) AS today + FROM {$wpdb->posts} p + INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = '_jio_forum_plate_id' + WHERE p.post_type = %s AND p.post_status = 'publish' AND CAST(pm.meta_value AS UNSIGNED) IN ($placeholders) + GROUP BY plate_id"; + $rows = $wpdb->get_results($wpdb->prepare($sql, array_merge([$today, Core::POST], $ids))); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + foreach ((array) $rows as $row) { + self::$stats[(int) $row->plate_id] = ['posts' => (int) $row->total, 'today' => (int) $row->today]; + } + } + + /** @return array{posts:int, today:int} */ + public static function get(int $plate_id): array { + if (!isset(self::$stats[$plate_id])) self::prime([$plate_id]); + return self::$stats[$plate_id] ?? ['posts' => 0, 'today' => 0]; + } +} diff --git a/src/Forum/QuickPost.php b/src/Forum/QuickPost.php new file mode 100644 index 00000000..9a5cdbfc --- /dev/null +++ b/src/Forum/QuickPost.php @@ -0,0 +1,38 @@ +

登录后即可快速发布' . esc_html(self::post_name()) . '

登录
'; + } + $plates = get_posts(['post_type' => Core::PLATE, 'post_status' => 'publish', 'posts_per_page' => 100, 'orderby' => 'title', 'order' => 'ASC']); + $options = ''; + foreach ($plates as $plate) { + $options .= ''; + } + if ($options === '') return ''; + + return '
' + . '

快速发布' . esc_html(self::post_name()) . '

' + . '' + . '' + . '
' + . '' + . '' + . '

'; + } + + private static function post_name(): string { + return (string) jio_get_setting('jio_forum_post_name', '帖子') ?: '帖子'; + } + + private static function plate_name(): string { + return (string) jio_get_setting('jio_forum_plate_name', '版块') ?: '版块'; + } +} diff --git a/src/Forum/View.php b/src/Forum/View.php index ca6a71c7..e769ad80 100644 --- a/src/Forum/View.php +++ b/src/Forum/View.php @@ -24,10 +24,6 @@ final class View { echo '