初始提交:姜十三论坛 Jiang13 Forum

轻量自用论坛,Go 单二进制 + React SPA 内嵌 + SQLite。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
freefire
2026-06-15 21:08:52 +08:00
commit e1c1708715
140 changed files with 16115 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
/* 姜十三论坛全局样式 */
:root {
--primary: #2c5530;
--primary-light: #3d7a44;
--accent: #c9a227;
}
body {
min-height: 100vh;
display: flex;
flex-direction: column;
background-color: #f8f9fa;
}
.navbar-brand {
font-weight: 700;
letter-spacing: 0.05em;
}
.site-footer {
margin-top: auto;
background: #212529;
color: #adb5bd;
padding: 1.5rem 0;
font-size: 0.875rem;
}
.post-content {
line-height: 1.8;
word-break: break-word;
}
.post-content img {
max-width: 100%;
height: auto;
}
.comment-floor {
border-left: 3px solid var(--primary);
padding-left: 1rem;
}
.avatar-sm {
width: 36px;
height: 36px;
object-fit: cover;
border-radius: 50%;
}
.avatar-md {
width: 64px;
height: 64px;
object-fit: cover;
border-radius: 50%;
}
.badge-pin {
background: var(--accent);
}
.admin-sidebar .nav-link.active {
background: var(--primary);
color: #fff;
}
.admin-sidebar .nav-link {
color: #333;
border-radius: 0.375rem;
margin-bottom: 0.25rem;
}
.login-slogan {
font-style: italic;
color: #6c757d;
letter-spacing: 0.1em;
}
@media (max-width: 576px) {
.navbar-brand span.en { display: none; }
}

View File

@@ -0,0 +1,28 @@
// 姜十三论坛前端通用脚本
function apiPost(url, data, isForm) {
const opts = { method: 'POST', credentials: 'same-origin' };
if (isForm) {
opts.body = new FormData(data instanceof HTMLFormElement ? data : undefined);
if (!(data instanceof HTMLFormElement)) {
const fd = new FormData();
for (const k in data) fd.append(k, data[k]);
opts.body = fd;
}
} else {
opts.headers = { 'Content-Type': 'application/json' };
opts.body = JSON.stringify(data);
}
return fetch(url, opts).then(r => r.json());
}
function showToast(msg, type) {
const el = document.getElementById('toast');
if (!el) { alert(msg); return; }
el.className = 'toast align-items-center text-bg-' + (type || 'success') + ' border-0 show';
el.querySelector('.toast-body').textContent = msg;
new bootstrap.Toast(el).show();
}
function confirmAction(msg, callback) {
if (confirm(msg)) callback();
}

View File

@@ -0,0 +1,499 @@
/* 姜十三论坛 - 前台 + 管理后台样式 */
:root {
--primary: #1a7f4b;
--primary-dark: #156b3f;
--primary-light: #e8f5ee;
--accent: #c9a227;
--admin-sidebar-w: 220px;
}
body {
min-height: 100vh;
background-color: #f4f6f8;
color: #1d2129;
}
/* ===== 管理后台 ===== */
.admin-body {
display: flex;
flex-direction: column;
min-height: 100vh;
background: #f2f3f5;
}
.admin-topbar {
height: 56px;
background: var(--primary);
color: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 2px 8px rgba(0,0,0,.1);
}
.admin-topbar-brand {
display: flex;
align-items: center;
gap: 12px;
}
.admin-topbar-mark {
width: 34px;
height: 34px;
border-radius: 8px;
background: rgba(255,255,255,.2);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 15px;
flex-shrink: 0;
}
.admin-topbar-title {
display: block;
font-weight: 600;
font-size: 15px;
line-height: 1.3;
}
.admin-topbar-sub {
display: block;
font-size: 12px;
opacity: .75;
font-weight: 400;
}
.admin-topbar-actions {
display: flex;
align-items: center;
gap: 12px;
}
.admin-topbar-user {
font-size: 13px;
opacity: .9;
padding-right: 4px;
border-right: 1px solid rgba(255,255,255,.25);
margin-right: 4px;
padding-left: 0;
}
.admin-shell {
display: flex;
flex: 1;
min-height: 0;
}
.admin-sidebar {
width: var(--admin-sidebar-w);
flex-shrink: 0;
background: #fff;
border-right: 1px solid #e5e6eb;
padding: 20px 14px;
overflow-y: auto;
}
.admin-sidebar-section {
font-size: 11px;
font-weight: 600;
color: #86909c;
padding: 12px 12px 6px;
letter-spacing: .06em;
}
.admin-sidebar-section:first-child {
padding-top: 4px;
}
.admin-sidebar .nav-link {
display: block;
color: #4e5969;
border-radius: 8px;
padding: 10px 12px;
margin-bottom: 2px;
font-size: 14px;
text-decoration: none;
transition: background .15s, color .15s;
border-left: 3px solid transparent;
}
.admin-sidebar .nav-link:hover {
background: #f7f8fa;
color: #1d2129;
}
.admin-sidebar .nav-link.active {
background: var(--primary-light);
color: var(--primary);
font-weight: 600;
border-left-color: var(--primary);
}
.admin-main {
flex: 1;
min-width: 0;
padding: 28px 32px;
overflow-y: auto;
max-width: 1280px;
}
.admin-page-head {
display: flex;
justify-content: space-between;
align-items: flex-end;
gap: 16px;
margin-bottom: 24px;
flex-wrap: wrap;
}
.admin-page-head h1 {
font-size: 24px;
font-weight: 600;
margin: 0 0 6px;
color: #1d2129;
}
.admin-page-head p {
margin: 0;
color: #86909c;
font-size: 14px;
}
.admin-search-bar {
display: flex;
gap: 8px;
align-items: center;
}
.admin-search-bar .form-control {
width: 240px;
border-color: #e5e6eb;
font-size: 13px;
}
.admin-stat-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 16px;
margin-bottom: 24px;
}
.admin-stat-card {
background: #fff;
border: 1px solid #e5e6eb;
border-radius: 12px;
padding: 18px 20px;
display: flex;
align-items: center;
gap: 14px;
transition: box-shadow .2s;
}
.admin-stat-card:hover {
box-shadow: 0 4px 12px rgba(0,0,0,.06);
}
.admin-stat-icon {
width: 44px;
height: 44px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 15px;
font-weight: 700;
flex-shrink: 0;
}
.admin-stat-users .admin-stat-icon { background: #e8f3ff; color: #3491fa; }
.admin-stat-posts .admin-stat-icon { background: #e8f5ee; color: var(--primary); }
.admin-stat-boards .admin-stat-icon { background: #fff7e8; color: #ff7d00; }
.admin-stat-comments .admin-stat-icon { background: #f5e8ff; color: #722ed1; }
.admin-stat-online .admin-stat-icon { background: #e8ffea; color: #00b42a; }
.admin-stat-users .value { color: #3491fa; }
.admin-stat-posts .value { color: var(--primary); }
.admin-stat-boards .value { color: #ff7d00; }
.admin-stat-comments .value { color: #722ed1; }
.admin-stat-online .value { color: #00b42a; }
.admin-stat-card .value {
font-size: 26px;
font-weight: 700;
line-height: 1.2;
}
.admin-stat-card .label {
font-size: 13px;
color: #86909c;
margin-top: 2px;
}
.admin-card {
background: #fff;
border: 1px solid #e5e6eb;
border-radius: 12px;
margin-bottom: 20px;
overflow: hidden;
box-shadow: 0 1px 2px rgba(0,0,0,.04);
}
.admin-card-head {
padding: 16px 20px;
border-bottom: 1px solid #f2f3f5;
font-weight: 600;
font-size: 15px;
display: flex;
justify-content: space-between;
align-items: center;
color: #1d2129;
}
.admin-card-link {
font-size: 13px;
font-weight: 500;
color: var(--primary);
text-decoration: none;
transition: opacity .15s;
}
.admin-card-link:hover {
opacity: .75;
color: var(--primary-dark);
}
.admin-card-body {
padding: 20px;
}
.admin-table {
margin: 0;
font-size: 13px;
--bs-table-hover-bg: #f7f8fa;
}
.admin-table th {
background: #fafbfc;
font-weight: 600;
white-space: nowrap;
color: #4e5969;
font-size: 12px;
padding: 12px 16px;
border-bottom: 1px solid #e5e6eb;
}
.admin-table td {
vertical-align: middle;
padding: 12px 16px;
border-bottom: 1px solid #f2f3f5;
color: #1d2129;
}
.admin-table tbody tr:last-child td {
border-bottom: none;
}
.admin-table tbody tr:hover td {
background: #fafbfc;
}
.admin-table .btn-link {
color: var(--primary);
text-decoration: none;
font-weight: 500;
}
.admin-table .btn-link:hover {
text-decoration: underline;
}
.text-truncate-cell {
max-width: 360px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.badge-pin {
background: #ff7d00;
color: #fff;
font-weight: 500;
font-size: 11px;
}
.badge-admin {
background: var(--primary-light);
color: var(--primary);
font-weight: 500;
}
.badge-banned {
background: #ffece8;
color: #f53f3f;
font-weight: 500;
}
.admin-empty {
text-align: center;
padding: 48px 20px;
color: #86909c;
font-size: 14px;
}
.admin-pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
padding: 16px 20px;
border-top: 1px solid #f2f3f5;
}
.admin-pagination-info {
font-size: 13px;
color: #86909c;
padding: 0 8px;
}
.admin-body .btn-success {
background: var(--primary);
border-color: var(--primary);
}
.admin-body .btn-success:hover {
background: var(--primary-dark);
border-color: var(--primary-dark);
}
.admin-body .btn-outline-success {
color: var(--primary);
border-color: var(--primary);
}
.admin-body .btn-outline-success:hover {
background: var(--primary);
border-color: var(--primary);
}
.admin-body .form-control:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(26,127,75,.12);
}
.admin-body .form-label {
color: #4e5969;
margin-bottom: 4px;
}
.admin-login-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(160deg, #f2f3f5 0%, #e8f5ee 60%, #d4edda 100%);
padding: 24px;
}
.admin-login-card {
width: 100%;
max-width: 400px;
background: #fff;
border: 1px solid #e5e6eb;
border-radius: 16px;
padding: 36px 32px;
box-shadow: 0 12px 40px rgba(0,0,0,.08);
}
.admin-login-mark {
width: 52px;
height: 52px;
border-radius: 14px;
background: var(--primary);
color: #fff;
font-size: 24px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 18px;
box-shadow: 0 4px 12px rgba(26,127,75,.25);
}
.login-slogan {
color: #86909c;
font-size: 13px;
}
.info-row {
display: flex;
gap: 12px;
padding: 12px 0;
border-bottom: 1px solid #f2f3f5;
font-size: 13px;
}
.info-row:last-child { border-bottom: none; }
.info-row .info-label { width: 100px; color: #86909c; flex-shrink: 0; }
.info-row .info-value { flex: 1; word-break: break-all; color: #1d2129; }
#adminToast.toast {
background: #1d2129;
color: #fff;
border: none;
border-radius: 10px;
box-shadow: 0 8px 24px rgba(0,0,0,.15);
}
@media (max-width: 1100px) {
.admin-stat-grid {
grid-template-columns: repeat(3, 1fr);
}
}
@media (max-width: 768px) {
.admin-shell { flex-direction: column; }
.admin-sidebar {
width: 100%;
border-right: none;
border-bottom: 1px solid #e5e6eb;
display: flex;
flex-wrap: wrap;
gap: 4px;
padding: 10px 12px;
}
.admin-sidebar-section { display: none; }
.admin-sidebar .nav-link {
padding: 7px 12px;
font-size: 13px;
border-left: none;
}
.admin-sidebar .nav-link.active {
border-left: none;
}
.admin-main { padding: 16px; max-width: none; }
.admin-stat-grid {
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.admin-stat-card { padding: 14px 16px; }
.admin-stat-card .value { font-size: 22px; }
.admin-topbar-user { display: none; }
.admin-search-bar .form-control { width: 160px; }
}
@media (max-width: 480px) {
.admin-stat-grid {
grid-template-columns: 1fr;
}
}
/* ===== 旧版前台兼容 ===== */
.navbar-brand { font-weight: 700; letter-spacing: 0.05em; }
.post-content { line-height: 1.8; word-break: break-word; }
.post-content img { max-width: 100%; height: auto; }
.avatar-sm { width: 36px; height: 36px; object-fit: cover; border-radius: 50%; }

View File

@@ -0,0 +1,79 @@
// 姜十三论坛 - 管理后台通用脚本
async function adminFetch(url, opts) {
opts = opts || {};
var res = await fetch(url, Object.assign({ credentials: 'same-origin' }, opts));
var data;
try {
data = await res.json();
} catch (e) {
throw new Error('服务器响应异常,请重新登录后再试');
}
if (!res.ok) {
throw new Error(data.error || '请求失败');
}
return data;
}
function showToast(msg, type) {
var el = document.getElementById('adminToast');
if (!el) {
alert(msg);
return;
}
el.className = 'toast align-items-center text-bg-' + (type || 'success') + ' border-0 show';
el.querySelector('.toast-body').textContent = msg;
bootstrap.Toast.getOrCreateInstance(el, { delay: 2800 }).show();
}
function adminConfirm(msg, fn) {
if (confirm(msg)) fn();
}
function setBtnLoading(btn, loading) {
if (!btn) return;
btn.disabled = loading;
if (loading) {
btn.dataset.originalHtml = btn.innerHTML;
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
} else if (btn.dataset.originalHtml) {
btn.innerHTML = btn.dataset.originalHtml;
}
}
function adminLogout() {
adminFetch('/admin/api/logout', { method: 'POST' })
.then(function () { location.href = '/admin/login'; })
.catch(function (e) { showToast(e.message, 'danger'); });
}
function postForm(url, method, fields) {
var fd = new FormData();
Object.keys(fields).forEach(function (k) { fd.append(k, fields[k]); });
return adminFetch(url, { method: method, body: fd });
}
function reloadSoon() {
setTimeout(function () { location.reload(); }, 600);
}
// 兼容旧模板
function apiPost(url, data, isForm) {
var opts = { method: 'POST', credentials: 'same-origin' };
if (isForm) {
opts.body = new FormData(data instanceof HTMLFormElement ? data : undefined);
if (!(data instanceof HTMLFormElement)) {
var fd = new FormData();
for (var k in data) fd.append(k, data[k]);
opts.body = fd;
}
} else {
opts.headers = { 'Content-Type': 'application/json' };
opts.body = JSON.stringify(data);
}
return fetch(url, opts).then(function (r) { return r.json(); });
}
function confirmAction(msg, callback) {
adminConfirm(msg, callback);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{e as j,r as n,j as s}from"./react-vendor-CgzNJrV9.js";import{u as f,a as g,B as p}from"./index-474mTIgd.js";import{S as N}from"./spinner-DH1syOsQ.js";import{n as v}from"./notify-Zesh5czd.js";import{f as y}from"./content-CKr4Ur51.js";import{A as k}from"./ui-vendor-CdH1UOKW.js";function E(){const t=j(),{user:a,loading:r}=f(),[i,h]=n.useState([]),[u,x]=n.useState(!0);return n.useEffect(()=>{if(!r){if(!a){t("/login");return}g.favorites().then(e=>h(Array.isArray(e.favorites)?e.favorites:[])).catch(e=>v.error(e.message)).finally(()=>x(!1))}},[a,r,t]),r||u?s.jsx("div",{className:"flex justify-center py-16",children:s.jsx(N,{size:"lg"})}):a?s.jsx("div",{className:"page-wrap",children:s.jsxs("div",{className:"page-inner-wide",children:[s.jsxs(p,{variant:"ghost",className:"mb-3",onClick:()=>t("/"),children:[s.jsx(k,{}),"返回"]}),s.jsx("h1",{className:"page-title",children:"我的收藏"}),s.jsxs("p",{className:"page-desc",children:["共 ",i.length," 篇收藏帖子"]}),i.length===0?s.jsxs("div",{className:"empty-state",children:[s.jsx("p",{children:"还没有收藏任何帖子"}),s.jsx(p,{onClick:()=>t("/"),children:"去逛逛"})]}):s.jsx("div",{className:"content-surface",children:i.map(e=>{var o,l,c,d,m;return s.jsx("div",{className:"post-row",onClick:()=>t(`/post/${e.post_id}`),children:s.jsxs("div",{className:"post-body",children:[s.jsx("div",{className:"post-title",children:((o=e.post)==null?void 0:o.title)||"帖子已删除"}),s.jsxs("div",{className:"post-meta",children:[((c=(l=e.post)==null?void 0:l.board)==null?void 0:c.name)&&s.jsx("span",{children:e.post.board.name}),((m=(d=e.post)==null?void 0:d.user)==null?void 0:m.nickname)&&s.jsx("span",{children:e.post.user.nickname}),s.jsxs("span",{children:["收藏于 ",y(e.created_at)]})]})]})},e.id)})})]})}):null}export{E as default};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{e as f,r as g,j as s,L as b}from"./react-vendor-CgzNJrV9.js";import{u as w,a as F,F as y,b as t,c as n,d as l,e as c,I as i,f as m,o as N,s as d}from"./form-BzHbu9bJ.js";import{u as v,B as L,a as S}from"./index-474mTIgd.js";import{n as u}from"./notify-Zesh5czd.js";import"./ui-vendor-CdH1UOKW.js";const C=N({username:d().min(1,"请输入用户名"),password:d().min(1,"请输入密码")});function z(){const x=f(),{refresh:p}=v(),[h,a]=g.useState(!1),r=w({resolver:F(C),defaultValues:{username:"",password:""}}),j=async e=>{a(!0);try{await S.login(e.username,e.password),await p(),u.success("登录成功"),x("/",{replace:!0})}catch(o){u.error(o instanceof Error?o.message:"登录失败")}finally{a(!1)}};return s.jsx("div",{className:"auth-page",children:s.jsxs("div",{className:"auth-box",children:[s.jsx("div",{className:"logo-mark",children:"姜"}),s.jsx("h1",{children:"登录姜十三论坛"}),s.jsx("p",{className:"subtitle",children:"拾三一隅,自在交流"}),s.jsx(y,{...r,children:s.jsxs("form",{onSubmit:r.handleSubmit(j),className:"space-y-4",children:[s.jsx(t,{control:r.control,name:"username",render:({field:e})=>s.jsxs(n,{children:[s.jsx(l,{children:"用户名"}),s.jsx(c,{children:s.jsx(i,{placeholder:"用户名",autoComplete:"username",...e})}),s.jsx(m,{})]})}),s.jsx(t,{control:r.control,name:"password",render:({field:e})=>s.jsxs(n,{children:[s.jsx(l,{children:"密码"}),s.jsx(c,{children:s.jsx(i,{type:"password",placeholder:"密码",autoComplete:"current-password",...e})}),s.jsx(m,{})]})}),s.jsx(L,{type:"submit",className:"w-full",loading:h,children:"登录"})]})}),s.jsxs("p",{style:{textAlign:"center",marginTop:16,fontSize:13,color:"var(--color-text-3)"},children:["没有账号?",s.jsx(b,{to:"/register",children:"注册"})]})]})})}export{z as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{e as f,r as g,j as s,L as w}from"./react-vendor-CgzNJrV9.js";import{u as b,a as F,F as y,b as a,c as n,d as o,e as t,I as c,f as l,o as k,s as i}from"./form-BzHbu9bJ.js";import{u as N,B as S,a as v}from"./index-474mTIgd.js";import{n as x}from"./notify-Zesh5czd.js";import"./ui-vendor-CdH1UOKW.js";const C=k({username:i().min(1,"请输入用户名"),nickname:i().optional(),password:i().min(6,"密码至少 6 位")});function R(){const u=f(),{refresh:j}=N(),[p,m]=g.useState(!1),r=b({resolver:F(C),defaultValues:{username:"",nickname:"",password:""}}),h=async e=>{m(!0);try{await v.register(e.username,e.password,e.nickname||e.username),await j(),x.success("注册成功"),u("/",{replace:!0})}catch(d){x.error(d instanceof Error?d.message:"注册失败")}finally{m(!1)}};return s.jsx("div",{className:"auth-page",children:s.jsxs("div",{className:"auth-box",children:[s.jsx("div",{className:"logo-mark",children:"姜"}),s.jsx("h1",{children:"注册账号"}),s.jsx("p",{className:"subtitle",children:"首个注册用户自动成为管理员"}),s.jsx(y,{...r,children:s.jsxs("form",{onSubmit:r.handleSubmit(h),className:"space-y-4",children:[s.jsx(a,{control:r.control,name:"username",render:({field:e})=>s.jsxs(n,{children:[s.jsx(o,{children:"用户名"}),s.jsx(t,{children:s.jsx(c,{placeholder:"3-32 位字母数字下划线",autoComplete:"username",...e})}),s.jsx(l,{})]})}),s.jsx(a,{control:r.control,name:"nickname",render:({field:e})=>s.jsxs(n,{children:[s.jsx(o,{children:"昵称"}),s.jsx(t,{children:s.jsx(c,{placeholder:"显示名称(可选)",autoComplete:"nickname",...e})}),s.jsx(l,{})]})}),s.jsx(a,{control:r.control,name:"password",render:({field:e})=>s.jsxs(n,{children:[s.jsx(o,{children:"密码"}),s.jsx(t,{children:s.jsx(c,{type:"password",placeholder:"至少 6 位",autoComplete:"new-password",...e})}),s.jsx(l,{})]})}),s.jsx(S,{type:"submit",className:"w-full",loading:p,children:"注册"})]})}),s.jsxs("p",{style:{textAlign:"center",marginTop:16,fontSize:13,color:"var(--color-text-3)"},children:["已有账号?",s.jsx(w,{to:"/login",children:"登录"})]})]})})}export{R as default};

View File

@@ -0,0 +1 @@
import{j as n}from"./react-vendor-CgzNJrV9.js";import{c as a,d as o}from"./index-474mTIgd.js";const s=o("inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",{variants:{variant:{default:"border-transparent bg-primary text-primary-foreground shadow",secondary:"border-transparent bg-secondary text-secondary-foreground",destructive:"border-transparent bg-destructive text-destructive-foreground shadow",outline:"text-foreground",green:"border-transparent bg-[var(--j13-green-bg)] text-[var(--j13-green)]",orange:"border-transparent bg-orange-100 text-orange-700 dark:bg-orange-950 dark:text-orange-300"}},defaultVariants:{variant:"default"}});function g({className:r,variant:e,...t}){return n.jsx("div",{className:a(s({variant:e}),r),...t})}export{g as B};

View File

@@ -0,0 +1 @@
function a(n,t){return n.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/@([\w\u4e00-\u9fa5_-]+)/g,'<span class="mention">@$1</span>')}function o(n){const t=new Date(n),e=(new Date().getTime()-t.getTime())/1e3;return e<60?"刚刚":e<3600?`${Math.floor(e/60)}分钟前`:e<86400?`${Math.floor(e/3600)}小时前`:`${t.getMonth()+1}-${t.getDate()} ${String(t.getHours()).padStart(2,"0")}:${String(t.getMinutes()).padStart(2,"0")}`}export{o as f,a as h};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{t as s}from"./index-474mTIgd.js";const n={success:r=>s.success(r),error:r=>s.error(r),warning:r=>s.warning(r)};export{n};

View File

@@ -0,0 +1,23 @@
import{p as m}from"./markdown-vendor-DxR1h-Bq.js";const d={ADD_TAGS:["members-only"],ADD_ATTR:["data-locked","data-length"]},c=`
<div class="post-members-only__badge">
<span class="post-members-only__badge-icon" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
</span>
<span>登录可见</span>
</div>`,a='<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>';function p(s){const r=s>0?Math.min(6,Math.max(3,Math.ceil(s/42))):4,n=Array.from({length:r},(l,i)=>{const o=i%3;return`<div class="post-members-only__preview-line${o===1?" post-members-only__preview-line--medium":o===2?" post-members-only__preview-line--short":""}"></div>`}).join(""),e=s>0?`${s} 字的`:"一段";return`
<div class="post-members-only__locked-wrap">
<div class="post-members-only__badge post-members-only__badge--locked">
<span class="post-members-only__badge-icon" aria-hidden="true">${a}</span>
<span>登录可见</span>
</div>
<div class="post-members-only__preview" aria-hidden="true">
${n}
</div>
<div class="post-members-only__gate">
<div class="post-members-only__gate-icon" aria-hidden="true">${a}</div>
<p class="post-members-only__gate-title">此处有${e}专属内容</p>
<p class="post-members-only__gate-desc">作者已将这部分内容设为仅登录用户可见,登录后即可阅读全文。</p>
<button type="button" class="post-members-only__gate-btn" data-members-login>登录查看</button>
<span class="post-members-only__gate-alt">还没有账号?<button type="button" class="post-members-only__gate-link" data-members-register>免费注册</button></span>
</div>
</div>`}function b(s,r){if(!s.trim())return"";const n=new DOMParser().parseFromString(m.sanitize(s,d),"text/html");return n.querySelectorAll("members-only").forEach(e=>{var o;if(e.getAttribute("data-locked")==="true"||!r){const t=parseInt(e.getAttribute("data-length")||"0",10)||0;e.setAttribute("data-locked","true"),e.className="post-members-only post-members-only--locked",e.innerHTML=p(t);return}const i=((o=e.querySelector(".post-members-only__body"))==null?void 0:o.innerHTML)??Array.from(e.childNodes).filter(t=>!(t instanceof Element&&t.classList.contains("post-members-only__badge"))).map(t=>t instanceof Element?t.outerHTML:t.textContent??"").join("");e.className="post-members-only post-members-only--visible",e.innerHTML=`${c}<div class="post-members-only__body">${i}</div>`}),n.body.innerHTML}export{d as P,b as r};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{j as e}from"./react-vendor-CgzNJrV9.js";import{c as s}from"./index-474mTIgd.js";import{p as m}from"./ui-vendor-CdH1UOKW.js";const t={sm:"h-4 w-4",md:"h-5 w-5",lg:"h-8 w-8"};function p({className:r,size:a="md"}){return e.jsx(m,{className:s("animate-spin text-[var(--j13-green)]",t[a],r),"aria-label":"加载中"})}export{p as S};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>姜十三论坛 Jiang13 Forum</title>
<style>
/* 关键布局样式:在 JS/CSS 包加载前即固定三栏结构,避免刷新时组件错位 */
html { scrollbar-gutter: stable; }
html, body, #root { height: 100%; margin: 0; }
body { overflow: hidden; font-size: 14px; line-height: 1.5; }
.app-shell { height: 100%; max-height: 100dvh; display: flex; flex-direction: column; overflow: hidden; }
.app-frame { flex: 1; min-height: 0; height: 100%; max-width: 1400px; width: 100%; margin: 0 auto; display: flex; flex-direction: column; overflow: hidden; }
.app-header { height: 56px; flex-shrink: 0; }
.app-body { flex: 1; display: flex; min-height: 0; width: 100%; overflow: hidden; }
.content-workspace { flex: 1; display: flex; min-width: 0; min-height: 0; overflow: hidden; }
.sidebar { width: 210px; flex-shrink: 0; }
.main-content { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
.aside-panel { width: 280px; flex-shrink: 0; }
@media (max-width: 1100px) { .aside-panel { display: none; } }
@media (max-width: 768px) { .sidebar { display: none; } }
</style>
<script>
(function () {
var theme = localStorage.getItem('j13-theme') || 'light';
document.documentElement.classList.toggle('dark', theme === 'dark');
document.documentElement.style.colorScheme = theme;
})();
</script>
<script type="module" crossorigin src="/assets/index-474mTIgd.js"></script>
<link rel="modulepreload" crossorigin href="/assets/react-vendor-CgzNJrV9.js">
<link rel="modulepreload" crossorigin href="/assets/ui-vendor-CdH1UOKW.js">
<link rel="stylesheet" crossorigin href="/assets/index-JNN-o0YU.css">
</head>
<body>
<div id="root"></div>
</body>
</html>