feat: feature discussion as separate detail page, not inline
This commit is contained in:
@@ -72,6 +72,7 @@
|
|||||||
<script src="js/pages/polls.js"></script>
|
<script src="js/pages/polls.js"></script>
|
||||||
<script src="js/pages/servers.js"></script>
|
<script src="js/pages/servers.js"></script>
|
||||||
<script src="js/pages/features.js"></script>
|
<script src="js/pages/features.js"></script>
|
||||||
|
<script src="js/pages/feature-detail.js"></script>
|
||||||
<script src="js/app.js"></script>
|
<script src="js/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ const App = {
|
|||||||
case 'export': this.renderMain('数据导出', ExportPage, param); break;
|
case 'export': this.renderMain('数据导出', ExportPage, param); break;
|
||||||
case 'polls': this.renderMain('投票', PollsPage, param); break;
|
case 'polls': this.renderMain('投票', PollsPage, param); break;
|
||||||
case 'features': this.renderMain('更新内容', FeaturesPage, param); break;
|
case 'features': this.renderMain('更新内容', FeaturesPage, param); break;
|
||||||
|
case 'feature': this.renderMain('更新详情', FeatureDetail, param); break;
|
||||||
case 'servers': this.renderMain('服务器管理', ServersPage, param); break;
|
case 'servers': this.renderMain('服务器管理', ServersPage, param); break;
|
||||||
default: this.renderMain('控制台', Dashboard, param);
|
default: this.renderMain('控制台', Dashboard, param);
|
||||||
}
|
}
|
||||||
@@ -176,6 +177,7 @@ window.SettingsPage = SettingsPage;
|
|||||||
window.ExportPage = ExportPage;
|
window.ExportPage = ExportPage;
|
||||||
window.PollsPage = PollsPage;
|
window.PollsPage = PollsPage;
|
||||||
window.FeaturesPage = FeaturesPage;
|
window.FeaturesPage = FeaturesPage;
|
||||||
|
window.FeatureDetail = FeatureDetail;
|
||||||
window.ServersPage = ServersPage;
|
window.ServersPage = ServersPage;
|
||||||
window.LoginPage = LoginPage;
|
window.LoginPage = LoginPage;
|
||||||
window.RegisterPage = RegisterPage;
|
window.RegisterPage = RegisterPage;
|
||||||
|
|||||||
55
public/js/pages/feature-detail.js
Normal file
55
public/js/pages/feature-detail.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
const FeatureDetail = {
|
||||||
|
id: null,
|
||||||
|
|
||||||
|
async render(id) { this.id = id; return '<div class="loading"><i class="fas fa-spinner"></i></div>'; },
|
||||||
|
|
||||||
|
async mount() {
|
||||||
|
const id = this.id;
|
||||||
|
document.getElementById('page-title').textContent = `更新 #${id}`;
|
||||||
|
document.getElementById('page-actions').innerHTML = '<button class="btn btn-o btn-sm" data-action="App:navigate:features"><i class="fas fa-arrow-left"></i> 返回</button>';
|
||||||
|
try {
|
||||||
|
const item = await API.get('/features/'+id);
|
||||||
|
this.renderDetail(item);
|
||||||
|
} catch(e) { document.getElementById('page-content').innerHTML = `<div class="alert alert-e">${e.message}</div>`; }
|
||||||
|
},
|
||||||
|
|
||||||
|
renderDetail(i) {
|
||||||
|
const sc = i.status==='done'?'var(--s)':i.status==='planned'?'var(--p)':'var(--g5)';
|
||||||
|
const sl = i.status==='done'?'已完成':i.status==='planned'?'计划中':'待定';
|
||||||
|
const ct = document.getElementById('page-content');
|
||||||
|
ct.innerHTML = `<div class="card"><div class="card-h">
|
||||||
|
<span><span style="color:${sc};font-size:18px">${i.status==='done'?'\u2713':i.status==='planned'?'\u25C9':'\u25CB'}</span> ${U.esc(i.title)}</span>
|
||||||
|
<span>${sl} · ${i.votes_count||0} 票</span>
|
||||||
|
</div><div class="card-b">
|
||||||
|
${i.description?`<p style="white-space:pre-wrap;margin-bottom:16px">${U.esc(i.description)}</p>`:''}
|
||||||
|
${i.group_name?`<span class="badge bg-player">${U.esc(i.group_name)}/${U.esc(i.server_name)}</span>`:''}
|
||||||
|
<span class="ts tm" style="margin-left:8px">${U.date(i.created_at)}</span>
|
||||||
|
<div style="margin-top:12px">
|
||||||
|
<button class="btn btn-${i.my_vote?'s':'o'} btn-sm" id="feat-detail-vote"><i class="fas fa-thumbs-up"></i> ${i.my_vote?'已投票':'投票'} (${i.votes_count||0})</button>
|
||||||
|
</div>
|
||||||
|
</div></div>
|
||||||
|
|
||||||
|
<div class="card"><div class="card-h">讨论 (${(i.comments||[]).length})</div><div class="card-b">
|
||||||
|
<div id="feat-comments">${(i.comments||[]).length===0?'<p class="tm">暂无讨论</p>':i.comments.map(c=>`
|
||||||
|
<div style="padding:8px 0;border-bottom:1px solid var(--g2)">
|
||||||
|
<span class="fb">${U.esc(c.username||'用户')}</span> ${c.role ? U.badge(c.role,'role') : ''}
|
||||||
|
<span class="ts tm">${U.date(c.created_at)}</span>
|
||||||
|
<div style="margin-top:4px;white-space:pre-wrap">${U.esc(c.content)}</div>
|
||||||
|
</div>`).join('')}
|
||||||
|
</div>
|
||||||
|
<form id="feat-comment-form" class="flex g2" style="margin-top:12px">
|
||||||
|
<input id="feat-comment-input" placeholder="添加评论..." style="flex:1;padding:8px 12px;border:1px solid var(--g200);border-radius:var(--r);font-family:inherit">
|
||||||
|
<button type="submit" class="btn btn-p btn-sm"><i class="fas fa-reply"></i></button>
|
||||||
|
</form>
|
||||||
|
</div></div>`;
|
||||||
|
|
||||||
|
document.getElementById('feat-detail-vote').onclick = async () => {
|
||||||
|
try { await API.post(`/features/${id}/vote`); this.mount(); } catch(ex){alert(ex.message);}
|
||||||
|
};
|
||||||
|
document.getElementById('feat-comment-form').onsubmit = async e => {
|
||||||
|
e.preventDefault();
|
||||||
|
const inp = document.getElementById('feat-comment-input');
|
||||||
|
try { await API.post(`/features/${id}/comment`, {content:inp.value}); inp.value=''; this.mount(); } catch(ex){alert(ex.message);}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -65,10 +65,7 @@ const FeaturesPage = {
|
|||||||
const items = await API.get('/features?' + params);
|
const items = await API.get('/features?' + params);
|
||||||
ct.innerHTML = items.length === 0 ? '<div class="empty"><i class="fas fa-inbox"></i><p>暂无内容</p></div>'
|
ct.innerHTML = items.length === 0 ? '<div class="empty"><i class="fas fa-inbox"></i><p>暂无内容</p></div>'
|
||||||
: items.map(i => this.renderItem(i)).join('');
|
: items.map(i => this.renderItem(i)).join('');
|
||||||
for (const i of items) {
|
for (const i of items) { if (Auth.logged()) this.bindVote(i); }
|
||||||
if (Auth.logged()) this.bindVote(i);
|
|
||||||
this.bindComment(i);
|
|
||||||
}
|
|
||||||
} catch (e) { ct.innerHTML = `<div class="alert alert-e">${e.message}</div>`; }
|
} catch (e) { ct.innerHTML = `<div class="alert alert-e">${e.message}</div>`; }
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -77,7 +74,7 @@ const FeaturesPage = {
|
|||||||
const statusLabel = i.status==='done'?'已完成':i.status==='planned'?'计划中':'待定';
|
const statusLabel = i.status==='done'?'已完成':i.status==='planned'?'计划中':'待定';
|
||||||
return `<div class="card feat-item" id="feat-${i.id}">
|
return `<div class="card feat-item" id="feat-${i.id}">
|
||||||
<div class="card-h">
|
<div class="card-h">
|
||||||
<span><span class="status-dot" style="color:${sc}">${i.status==='done'?'\u2713':i.status==='planned'?'\u25C9':'\u25CB'}</span> ${U.esc(i.title)}</span>
|
<span><a href="#/feature/${i.id}" style="color:inherit;text-decoration:none"><span class="status-dot" style="color:${sc}">${i.status==='done'?'\u2713':i.status==='planned'?'\u25C9':'\u25CB'}</span> ${U.esc(i.title)}</a></span>
|
||||||
<div style="display:flex;gap:8px;align-items:center">
|
<div style="display:flex;gap:8px;align-items:center">
|
||||||
${i.group_name ? `<span class="badge bg-player">${U.esc(i.group_name)}</span>` : ''}
|
${i.group_name ? `<span class="badge bg-player">${U.esc(i.group_name)}</span>` : ''}
|
||||||
${Auth.isOwner() ? `<button class="btn btn-o btn-sm" data-action="FeaturesPage:showEdit:${i.id}:${U.escJs(i.title)}:${U.escJs(i.description||'')}:${i.status}:${U.escJs(i.group_name||'')}:${U.escJs(i.server_name||'')}"><i class="fas fa-edit"></i></button>` : ''}
|
${Auth.isOwner() ? `<button class="btn btn-o btn-sm" data-action="FeaturesPage:showEdit:${i.id}:${U.escJs(i.title)}:${U.escJs(i.description||'')}:${i.status}:${U.escJs(i.group_name||'')}:${U.escJs(i.server_name||'')}"><i class="fas fa-edit"></i></button>` : ''}
|
||||||
@@ -87,14 +84,9 @@ const FeaturesPage = {
|
|||||||
${i.description ? `<p style="white-space:pre-wrap;margin-bottom:12px">${U.esc(i.description)}</p>` : ''}
|
${i.description ? `<p style="white-space:pre-wrap;margin-bottom:12px">${U.esc(i.description)}</p>` : ''}
|
||||||
<div class="feat-bar">
|
<div class="feat-bar">
|
||||||
<button class="btn btn-${i.my_vote?'s':'o'} btn-sm feat-vote-btn" data-id="${i.id}"><i class="fas fa-thumbs-up"></i> ${i.votes_count||0}</button>
|
<button class="btn btn-${i.my_vote?'s':'o'} btn-sm feat-vote-btn" data-id="${i.id}"><i class="fas fa-thumbs-up"></i> ${i.votes_count||0}</button>
|
||||||
<span class="ts tm"><i class="fas fa-comment"></i> ${i.comments_count||0} 讨论</span>
|
<span class="ts tm"><i class="fas fa-comment"></i> <a href="#/feature/${i.id}" style="color:inherit;text-decoration:none">${i.comments_count||0} 讨论</a></span>
|
||||||
<span class="ts tm">${U.date(i.created_at)}</span>
|
<span class="ts tm">${U.date(i.created_at)}</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-o btn-sm feat-discuss-btn" data-action="FeaturesPage:toggleDiscuss:${i.id}"><i class="fas fa-comments"></i> 展开讨论</button>
|
|
||||||
<div id="discuss-${i.id}" class="feat-comments" style="display:none">
|
|
||||||
<div id="comments-${i.id}" class="mb-4"></div>
|
|
||||||
<form id="comment-form-${i.id}" class="flex g2"><input id="comment-input-${i.id}" class="feat-comment-input" placeholder="添加评论..."><button type="submit" class="btn btn-p btn-sm"><i class="fas fa-reply"></i></button></form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user