128 lines
6.4 KiB
Plaintext
128 lines
6.4 KiB
Plaintext
<%- include("_header") %>
|
|
<%
|
|
// 뷰 로컬 헬퍼
|
|
function tagList(s) { return String(s || "").split(",").map(function (t) { return t.trim(); }).filter(Boolean); }
|
|
function fdate(d) { var x = new Date(d); return x.getFullYear() + "." + String(x.getMonth() + 1).padStart(2, "0") + "." + String(x.getDate()).padStart(2, "0"); }
|
|
function searchText(p) { return (p.title + " " + (p.description || "") + " " + (p.tags || "")).toLowerCase(); }
|
|
%>
|
|
|
|
<main class="main">
|
|
<!-- 프로필 히어로 -->
|
|
<section class="section">
|
|
<div class="profile-hero">
|
|
<div class="profile-avatar-wrap">
|
|
<img class="profile-avatar" id="profileAvatar" src="/avatar/<%= profile.sabun %>" alt="<%= profile.name %>" width="96" height="96" />
|
|
<% if (isSelf) { %>
|
|
<button type="button" class="profile-avatar-edit" id="avatarEditBtn" title="프로필 사진 변경" aria-label="프로필 사진 변경"><%- icon("plus", { size: 15, fill: "#fff" }) %></button>
|
|
<% } %>
|
|
</div>
|
|
<div class="profile-info">
|
|
<div class="profile-idline">
|
|
<h1 class="profile-name"><%= profile.name %></h1>
|
|
<span class="profile-sabun"><%= profile.sabun %></span>
|
|
<% if (profile.is_admin) { %><span class="admin-badge-wrap" title="관리자 인증"><%- adminBadge({ size: 16 }) %></span><% } %>
|
|
</div>
|
|
<div class="profile-metaline">
|
|
<span><%= profile.project_count %>개 프로젝트</span>
|
|
<span>· 가입 <%= fdate(profile.created_at) %></span>
|
|
</div>
|
|
|
|
<% if (profile.bio || isSelf) { %>
|
|
<div class="profile-bio-row" id="bioRow">
|
|
<p class="profile-bio<%= profile.bio ? "" : " is-empty" %>" id="bioText"><%= profile.bio || "한줄 소개를 남겨보세요" %></p>
|
|
<% if (isSelf) { %>
|
|
<button type="button" class="profile-bio-edit" id="bioEditBtn" title="한줄 소개 편집" aria-label="한줄 소개 편집"><%- icon("pencil", { size: 13 }) %></button>
|
|
<% } %>
|
|
</div>
|
|
<% if (isSelf) { %>
|
|
<form class="profile-bio-form" id="bioForm" method="post" action="/profile/bio" hidden>
|
|
<input type="text" name="bio" id="bioInput" maxlength="80" value="<%= profile.bio || "" %>" placeholder="한줄 소개 (최대 80자)" autocomplete="off" />
|
|
<button type="submit" class="btn btn-success" id="bioSave">저장</button>
|
|
<button type="button" class="btn" id="bioCancel">취소</button>
|
|
</form>
|
|
<% } %>
|
|
<% } %>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- 프로젝트 목록 -->
|
|
<section class="section">
|
|
<div class="section-head">
|
|
<div class="section-titlewrap">
|
|
<h2 class="section-title"><%= isSelf ? "내 프로젝트" : (profile.name + " 님의 프로젝트") %></h2>
|
|
<span class="section-sub"><%= isSelf ? "공개·비공개 모두 표시" : "공개된 프로젝트만 표시" %></span>
|
|
</div>
|
|
</div>
|
|
|
|
<% if (!projects.length) { %>
|
|
<div class="empty"><%= isSelf ? "아직 등록한 프로젝트가 없습니다." : "아직 공개된 프로젝트가 없습니다." %></div>
|
|
<% } %>
|
|
<div class="empty" id="feedEmpty" hidden></div>
|
|
|
|
<div class="feed-grid">
|
|
<% projects.forEach(function (p) { var tags = tagList(p.tags); %>
|
|
<a class="fcard" href="/projects/<%= p.id %>?from=/u/<%= encodeURIComponent(profile.sabun) %>" data-search="<%= searchText(p) %>">
|
|
<div class="fcard-top">
|
|
<div class="fcard-titlewrap">
|
|
<span class="pcard-repoico"><%- icon("repo", { size: 16 }) %></span>
|
|
<span class="fcard-title"><%= p.title %></span>
|
|
</div>
|
|
<span class="vis-badge <%= p.is_public ? "public" : "private" %>">
|
|
<%- icon(p.is_public ? "globe" : "lock", { size: 11 }) %><%= p.is_public ? "공개" : "비공개" %>
|
|
</span>
|
|
</div>
|
|
<div class="fcard-desc"><%= mdPlain(p.description) || "설명 없음" %></div>
|
|
<% if (tags.length) { %>
|
|
<div class="fcard-tags"><% tags.forEach(function (t) { %><span class="tag"><%= t %></span><% }); %></div>
|
|
<% } %>
|
|
<div class="fcard-foot">
|
|
<div class="fcard-meta">
|
|
<% if (p.lang) { %><span class="stat"><span class="lang-dot" style="background:<%= langColor(p.lang) %>"></span><%= p.lang %></span><% } %>
|
|
<span class="stat"><span class="ico-heart"><%- icon("heartFill", { size: 13 }) %></span><span class="tabnum"><%= p.heart_count %></span></span>
|
|
<span class="stat"><span class="ico-star"><%- icon("starFill", { size: 13 }) %></span><span class="tabnum"><%= p.star_count %></span></span>
|
|
<span class="stat"><span class="ico-comment"><%- icon("comment", { size: 14 }) %></span><span class="tabnum"><%= p.comment_count %></span></span>
|
|
</div>
|
|
<span class="date"><%= fdate(p.created_at) %></span>
|
|
</div>
|
|
</a>
|
|
<% }); %>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<%- include("_footer") %>
|
|
|
|
<% if (isSelf) { %>
|
|
<!-- 프로필 사진 크롭 모달 -->
|
|
<div class="modal-overlay" id="cropModal" hidden>
|
|
<div class="crop-modal">
|
|
<div class="crop-head">
|
|
<h3>프로필 사진</h3>
|
|
<button type="button" class="modal-close" id="cropClose" title="닫기"><%- icon("x", { size: 16 }) %></button>
|
|
</div>
|
|
<div class="crop-body">
|
|
<div class="crop-stage" id="cropStage">
|
|
<img id="cropImg" alt="" draggable="false" />
|
|
<div class="crop-veil"></div>
|
|
</div>
|
|
<label class="crop-zoom">
|
|
<span><%- icon("search", { size: 13 }) %></span>
|
|
<input type="range" id="cropZoom" min="1" max="4" step="0.01" value="1" />
|
|
</label>
|
|
<p class="crop-hint">사진을 드래그해 위치를 맞추고 슬라이더로 확대하세요. 정사각형으로 저장됩니다.<br>변경한 사진이 모든 화면에 반영되기까지 최대 몇 분 걸릴 수 있어요.</p>
|
|
</div>
|
|
<div class="modal-foot-actions">
|
|
<input type="file" id="avatarFile" accept="image/png,image/jpeg,image/webp" hidden />
|
|
<button type="button" class="btn" id="cropPick">다른 사진 선택</button>
|
|
<button type="button" class="btn btn-success" id="cropSave">저장</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<% } %>
|
|
|
|
<script type="module" src="/public/js/main.js"></script>
|
|
<% if (isSelf) { %><script type="module" src="/public/js/profile.js"></script><% } %>
|
|
</body>
|
|
</html>
|