diff --git a/assets/built/alpine.js b/assets/built/alpine.js new file mode 100644 index 0000000..2fdd6ec --- /dev/null +++ b/assets/built/alpine.js @@ -0,0 +1,5 @@ +(()=>{var nt=!1,it=!1,W=[],ot=-1;function Ut(e){Rn(e)}function Rn(e){W.includes(e)||W.push(e),Mn()}function Wt(e){let t=W.indexOf(e);t!==-1&&t>ot&&W.splice(t,1)}function Mn(){!it&&!nt&&(nt=!0,queueMicrotask(Nn))}function Nn(){nt=!1,it=!0;for(let e=0;ee.effect(t,{scheduler:r=>{st?Ut(r):r()}}),at=e.raw}function ct(e){N=e}function Yt(e){let t=()=>{};return[n=>{let i=N(n);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(o=>o())}),e._x_effects.add(i),t=()=>{i!==void 0&&(e._x_effects.delete(i),$(i))},i},()=>{t()}]}function ve(e,t){let r=!0,n,i=N(()=>{let o=e();JSON.stringify(o),r?n=o:queueMicrotask(()=>{t(o,n),n=o}),r=!1});return()=>$(i)}var Xt=[],Zt=[],Qt=[];function er(e){Qt.push(e)}function te(e,t){typeof t=="function"?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,Zt.push(t))}function Ae(e){Xt.push(e)}function Oe(e,t,r){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(r)}function lt(e,t){e._x_attributeCleanups&&Object.entries(e._x_attributeCleanups).forEach(([r,n])=>{(t===void 0||t.includes(r))&&(n.forEach(i=>i()),delete e._x_attributeCleanups[r])})}function tr(e){for(e._x_effects?.forEach(Wt);e._x_cleanups?.length;)e._x_cleanups.pop()()}var ut=new MutationObserver(mt),ft=!1;function ue(){ut.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),ft=!0}function dt(){kn(),ut.disconnect(),ft=!1}var le=[];function kn(){let e=ut.takeRecords();le.push(()=>e.length>0&&mt(e));let t=le.length;queueMicrotask(()=>{if(le.length===t)for(;le.length>0;)le.shift()()})}function m(e){if(!ft)return e();dt();let t=e();return ue(),t}var pt=!1,Se=[];function rr(){pt=!0}function nr(){pt=!1,mt(Se),Se=[]}function mt(e){if(pt){Se=Se.concat(e);return}let t=[],r=new Set,n=new Map,i=new Map;for(let o=0;o{s.nodeType===1&&s._x_marker&&r.add(s)}),e[o].addedNodes.forEach(s=>{if(s.nodeType===1){if(r.has(s)){r.delete(s);return}s._x_marker||t.push(s)}})),e[o].type==="attributes")){let s=e[o].target,a=e[o].attributeName,c=e[o].oldValue,l=()=>{n.has(s)||n.set(s,[]),n.get(s).push({name:a,value:s.getAttribute(a)})},u=()=>{i.has(s)||i.set(s,[]),i.get(s).push(a)};s.hasAttribute(a)&&c===null?l():s.hasAttribute(a)?(u(),l()):u()}i.forEach((o,s)=>{lt(s,o)}),n.forEach((o,s)=>{Xt.forEach(a=>a(s,o))});for(let o of r)t.some(s=>s.contains(o))||Zt.forEach(s=>s(o));for(let o of t)o.isConnected&&Qt.forEach(s=>s(o));t=null,r=null,n=null,i=null}function Ce(e){return z(B(e))}function k(e,t,r){return e._x_dataStack=[t,...B(r||e)],()=>{e._x_dataStack=e._x_dataStack.filter(n=>n!==t)}}function B(e){return e._x_dataStack?e._x_dataStack:typeof ShadowRoot=="function"&&e instanceof ShadowRoot?B(e.host):e.parentNode?B(e.parentNode):[]}function z(e){return new Proxy({objects:e},Dn)}var Dn={ownKeys({objects:e}){return Array.from(new Set(e.flatMap(t=>Object.keys(t))))},has({objects:e},t){return t==Symbol.unscopables?!1:e.some(r=>Object.prototype.hasOwnProperty.call(r,t)||Reflect.has(r,t))},get({objects:e},t,r){return t=="toJSON"?Pn:Reflect.get(e.find(n=>Reflect.has(n,t))||{},t,r)},set({objects:e},t,r,n){let i=e.find(s=>Object.prototype.hasOwnProperty.call(s,t))||e[e.length-1],o=Object.getOwnPropertyDescriptor(i,t);return o?.set&&o?.get?o.set.call(n,r)||!0:Reflect.set(i,t,r)}};function Pn(){return Reflect.ownKeys(this).reduce((t,r)=>(t[r]=Reflect.get(this,r),t),{})}function Te(e){let t=n=>typeof n=="object"&&!Array.isArray(n)&&n!==null,r=(n,i="")=>{Object.entries(Object.getOwnPropertyDescriptors(n)).forEach(([o,{value:s,enumerable:a}])=>{if(a===!1||s===void 0||typeof s=="object"&&s!==null&&s.__v_skip)return;let c=i===""?o:`${i}.${o}`;typeof s=="object"&&s!==null&&s._x_interceptor?n[o]=s.initialize(e,c,o):t(s)&&s!==n&&!(s instanceof Element)&&r(s,c)})};return r(e)}function Re(e,t=()=>{}){let r={initialValue:void 0,_x_interceptor:!0,initialize(n,i,o){return e(this.initialValue,()=>In(n,i),s=>ht(n,i,s),i,o)}};return t(r),n=>{if(typeof n=="object"&&n!==null&&n._x_interceptor){let i=r.initialize.bind(r);r.initialize=(o,s,a)=>{let c=n.initialize(o,s,a);return r.initialValue=c,i(o,s,a)}}else r.initialValue=n;return r}}function In(e,t){return t.split(".").reduce((r,n)=>r[n],e)}function ht(e,t,r){if(typeof t=="string"&&(t=t.split(".")),t.length===1)e[t[0]]=r;else{if(t.length===0)throw error;return e[t[0]]||(e[t[0]]={}),ht(e[t[0]],t.slice(1),r)}}var ir={};function y(e,t){ir[e]=t}function fe(e,t){let r=Ln(t);return Object.entries(ir).forEach(([n,i])=>{Object.defineProperty(e,`$${n}`,{get(){return i(t,r)},enumerable:!1})}),e}function Ln(e){let[t,r]=_t(e),n={interceptor:Re,...t};return te(e,r),n}function or(e,t,r,...n){try{return r(...n)}catch(i){re(i,e,t)}}function re(e,t,r=void 0){e=Object.assign(e??{message:"No error message given."},{el:t,expression:r}),console.warn(`Alpine Expression Error: ${e.message} + +${r?'Expression: "'+r+`" + +`:""}`,t),setTimeout(()=>{throw e},0)}var Me=!0;function ke(e){let t=Me;Me=!1;let r=e();return Me=t,r}function R(e,t,r={}){let n;return x(e,t)(i=>n=i,r),n}function x(...e){return sr(...e)}var sr=xt;function ar(e){sr=e}function xt(e,t){let r={};fe(r,e);let n=[r,...B(e)],i=typeof t=="function"?$n(n,t):Fn(n,t,e);return or.bind(null,e,t,i)}function $n(e,t){return(r=()=>{},{scope:n={},params:i=[]}={})=>{let o=t.apply(z([n,...e]),i);Ne(r,o)}}var gt={};function jn(e,t){if(gt[e])return gt[e];let r=Object.getPrototypeOf(async function(){}).constructor,n=/^[\n\s]*if.*\(.*\)/.test(e.trim())||/^(let|const)\s/.test(e.trim())?`(async()=>{ ${e} })()`:e,o=(()=>{try{let s=new r(["__self","scope"],`with (scope) { __self.result = ${n} }; __self.finished = true; return __self.result;`);return Object.defineProperty(s,"name",{value:`[Alpine] ${e}`}),s}catch(s){return re(s,t,e),Promise.resolve()}})();return gt[e]=o,o}function Fn(e,t,r){let n=jn(t,r);return(i=()=>{},{scope:o={},params:s=[]}={})=>{n.result=void 0,n.finished=!1;let a=z([o,...e]);if(typeof n=="function"){let c=n(n,a).catch(l=>re(l,r,t));n.finished?(Ne(i,n.result,a,s,r),n.result=void 0):c.then(l=>{Ne(i,l,a,s,r)}).catch(l=>re(l,r,t)).finally(()=>n.result=void 0)}}}function Ne(e,t,r,n,i){if(Me&&typeof t=="function"){let o=t.apply(r,n);o instanceof Promise?o.then(s=>Ne(e,s,r,n)).catch(s=>re(s,i,t)):e(o)}else typeof t=="object"&&t instanceof Promise?t.then(o=>e(o)):e(t)}var wt="x-";function C(e=""){return wt+e}function cr(e){wt=e}var De={};function d(e,t){return De[e]=t,{before(r){if(!De[r]){console.warn(String.raw`Cannot find directive \`${r}\`. \`${e}\` will use the default order of execution`);return}let n=G.indexOf(r);G.splice(n>=0?n:G.indexOf("DEFAULT"),0,e)}}}function lr(e){return Object.keys(De).includes(e)}function pe(e,t,r){if(t=Array.from(t),e._x_virtualDirectives){let o=Object.entries(e._x_virtualDirectives).map(([a,c])=>({name:a,value:c})),s=Et(o);o=o.map(a=>s.find(c=>c.name===a.name)?{name:`x-bind:${a.name}`,value:`"${a.value}"`}:a),t=t.concat(o)}let n={};return t.map(dr((o,s)=>n[o]=s)).filter(mr).map(zn(n,r)).sort(Kn).map(o=>Bn(e,o))}function Et(e){return Array.from(e).map(dr()).filter(t=>!mr(t))}var yt=!1,de=new Map,ur=Symbol();function fr(e){yt=!0;let t=Symbol();ur=t,de.set(t,[]);let r=()=>{for(;de.get(t).length;)de.get(t).shift()();de.delete(t)},n=()=>{yt=!1,r()};e(r),n()}function _t(e){let t=[],r=a=>t.push(a),[n,i]=Yt(e);return t.push(i),[{Alpine:K,effect:n,cleanup:r,evaluateLater:x.bind(x,e),evaluate:R.bind(R,e)},()=>t.forEach(a=>a())]}function Bn(e,t){let r=()=>{},n=De[t.type]||r,[i,o]=_t(e);Oe(e,t.original,o);let s=()=>{e._x_ignore||e._x_ignoreSelf||(n.inline&&n.inline(e,t,i),n=n.bind(n,e,t,i),yt?de.get(ur).push(n):n())};return s.runCleanups=o,s}var Pe=(e,t)=>({name:r,value:n})=>(r.startsWith(e)&&(r=r.replace(e,t)),{name:r,value:n}),Ie=e=>e;function dr(e=()=>{}){return({name:t,value:r})=>{let{name:n,value:i}=pr.reduce((o,s)=>s(o),{name:t,value:r});return n!==t&&e(n,t),{name:n,value:i}}}var pr=[];function ne(e){pr.push(e)}function mr({name:e}){return hr().test(e)}var hr=()=>new RegExp(`^${wt}([^:^.]+)\\b`);function zn(e,t){return({name:r,value:n})=>{let i=r.match(hr()),o=r.match(/:([a-zA-Z0-9\-_:]+)/),s=r.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],a=t||e[r]||r;return{type:i?i[1]:null,value:o?o[1]:null,modifiers:s.map(c=>c.replace(".","")),expression:n,original:a}}}var bt="DEFAULT",G=["ignore","ref","data","id","anchor","bind","init","for","model","modelable","transition","show","if",bt,"teleport"];function Kn(e,t){let r=G.indexOf(e.type)===-1?bt:e.type,n=G.indexOf(t.type)===-1?bt:t.type;return G.indexOf(r)-G.indexOf(n)}function J(e,t,r={}){e.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:!0,composed:!0,cancelable:!0}))}function D(e,t){if(typeof ShadowRoot=="function"&&e instanceof ShadowRoot){Array.from(e.children).forEach(i=>D(i,t));return}let r=!1;if(t(e,()=>r=!0),r)return;let n=e.firstElementChild;for(;n;)D(n,t,!1),n=n.nextElementSibling}function E(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}var _r=!1;function gr(){_r&&E("Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems."),_r=!0,document.body||E("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's ` diff --git a/docs/deploy.md b/docs/deploy.md new file mode 100644 index 0000000..6348d62 --- /dev/null +++ b/docs/deploy.md @@ -0,0 +1,96 @@ +# 배포 가이드 + +## 현재 버전 +- `v0.1.20` + +## Git 기본 설정 +- 저장소 작성자 정보는 아래 값으로 통일한다. +- 이름: `zenn` +- 이메일: `zenn.message@gmail.com` + +```bash +git config user.name "zenn" +git config user.email "zenn.message@gmail.com" +``` + +## 저장소 초기화 +```bash +git init +git branch -M main +git remote add origin https://git.sori.studio/zenn/sori.studio.git +``` + +## 작업 종료 시 업로드 절차 +1. 변경 파일과 문서 반영 상태를 확인한다. +2. 작성자 정보가 올바른지 확인한다. +3. 모든 변경을 스테이징한다. +4. 한국어 커밋 메시지로 커밋한다. +5. `main` 브랜치로 원격 저장소에 푸시한다. +6. 마지막 커밋과 작업 트리가 정상인지 다시 확인한다. + +```bash +git config user.name +git config user.email +git status +git add -A +git commit -m "영역: 작업 내용" +git push origin main +git status +git log -1 --oneline +``` + +## 로컬 실행 +```bash +npm install +npm run dev +``` + +## 로컬 스타일 빌드 +```bash +npm run build:alpine +npm run build:tailwind +``` + +- `npm run dev`, `npm run dev:ghost:start`, `npm run dev:ghost:restart` 실행 시 Alpine.js와 Tailwind 빌드가 먼저 수행된다. +- Alpine 결과물은 `assets/built/alpine.js`에 생성된다. +- Tailwind 결과물은 `assets/built/tailwind.css`에 생성되고, Ghost 테마에서 `screen.css`보다 먼저 로드된다. + +## 로컬 빌드 검증 +```bash +npm run build +``` + +## 저장 기능 메모 +- DB 연결 환경에서는 작성/수정 API가 Prisma를 통해 실제 데이터를 저장한다. +- DB 미연결 환경에서는 샘플 콘텐츠 fallback이 프로세스 메모리에서만 갱신된다. +- 따라서 재시작 이후에도 데이터를 유지하려면 PostgreSQL 연결이 필요하다. + +## 폰트 에셋 +- `Pretendard` 폰트 파일은 `assets/fonts` 경로를 사용한다. +- 전역 CSS 로딩에 `assets/fonts/pretendard.css`가 포함되어 있어야 한다. + +## PostgreSQL 준비 +```bash +cp .env.example .env +npm run db:push +npm run db:seed-admin +``` + +## 데모 로그인 계정 +- 이메일: `zenn.message@gmail.com` +- 비밀번호: `zenn-demo-admin` +- 실제 운영 전에는 `.env`에서 데모 인증 값을 반드시 변경한다. + +## 관리자 계정 시드 +- DB 연결 환경에서는 `npm run db:seed-admin`으로 초기 관리자 계정을 생성하거나 갱신한다. +- 시드 계정 정보는 `.env`의 `DEMO_ADMIN_*` 값을 사용한다. +- 실제 운영 전에는 기본 비밀번호를 반드시 변경한다. + +## 원격 저장소 정보 +- 기본 원격 이름: `origin` +- 기본 저장소 주소: `https://git.sori.studio/zenn/sori.studio.git` + +## 운영 메모 +- 민감 정보가 포함된 파일은 커밋 전에 반드시 확인한다. +- 문서 변경이 발생한 작업은 코드와 함께 같은 커밋에 포함한다. +- 배포 절차가 확정되면 UGREEN NAS Docker 배포 방법을 이 문서에 이어서 추가한다. diff --git a/docs/history.md b/docs/history.md index a3e7d5a..1b59365 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,8 @@ # 의사결정 이력 +## 2026-04-14 v0.1.20 +앞으로 원본 테마 섹션 코드를 더 직접적으로 참고할 수 있도록 Alpine.js를 로컬 자산으로 포함하기로 했다. 외부 CDN 의존 대신 테마 빌드 단계에서 `assets/built/alpine.js`를 생성해 함께 배포하고, 기존 `theme.js`는 즉시 제거하지 않고 공존시키면서 점진적으로 Alpine 문법을 허용하는 방향으로 정리했다. + ## 2026-04-14 v0.1.19 Ghost 업로드 오류에 맞춰 `author.hbs`의 구식 `{{#author}}` 블록 헬퍼를 제거했다. 작성자 아카이브 템플릿은 이미 작성자 컨텍스트에서 렌더링되므로, 별도 블록 헬퍼 없이 현재 컨텍스트 값을 직접 사용하는 방식이 Ghost 최신 검사 기준과도 맞다. diff --git a/docs/map.md b/docs/map.md index 484dd63..813b899 100644 --- a/docs/map.md +++ b/docs/map.md @@ -1,11 +1,13 @@ # 파일-화면 매핑 가이드 ## 현재 버전 -- `v0.1.18` +- `v0.1.20` ## 공통 레이아웃 - [default.hbs](/Users/bicute/Desktop/UGREEN/GHOST%20THEME/default.hbs): 전체 3열 셸과 공통 자산 로드 -- [partials/site/sidebar-left.hbs](/Users/bicute/Desktop/UGREEN/GHOST%20THEME/partials/site/sidebar-left.hbs): 좌측 탐색/카테고리 아코디언/작성자/푸터 +- [partials/site/sidebar-left.hbs](/Users/bicute/Desktop/UGREEN/GHOST%20THEME/partials/site/sidebar-left.hbs): 좌측 탐색/직접 링크형 Tags·Authors 메뉴/카테고리 아코디언/푸터 +- [page-tags.hbs](/Users/bicute/Desktop/UGREEN/GHOST%20THEME/page-tags.hbs): `slug=tags` 페이지용 태그 디렉터리 +- [page-authors.hbs](/Users/bicute/Desktop/UGREEN/GHOST%20THEME/page-authors.hbs): `slug=authors` 페이지용 작성자 디렉터리 - [partials/site/topbar.hbs](/Users/bicute/Desktop/UGREEN/GHOST%20THEME/partials/site/topbar.hbs): 상단 검색/CTA/다크모드 - [partials/site/sidebar-right.hbs](/Users/bicute/Desktop/UGREEN/GHOST%20THEME/partials/site/sidebar-right.hbs): 구독/추천/작성자/푸터 @@ -26,6 +28,7 @@ ## 자산 - [assets/built/screen.css](/Users/bicute/Desktop/UGREEN/GHOST%20THEME/assets/built/screen.css): 전체 스타일 - [assets/built/tailwind.css](/Users/bicute/Desktop/UGREEN/GHOST%20THEME/assets/built/tailwind.css): Tailwind 빌드 결과물 +- [assets/built/alpine.js](/Users/bicute/Desktop/UGREEN/GHOST%20THEME/assets/built/alpine.js): Alpine.js 로컬 배포 파일 - [assets/built/theme.js](/Users/bicute/Desktop/UGREEN/GHOST%20THEME/assets/built/theme.js): 인터랙션 스크립트 - [assets/styles/tailwind.css](/Users/bicute/Desktop/UGREEN/GHOST%20THEME/assets/styles/tailwind.css): Tailwind 입력 파일 - [tailwind.config.js](/Users/bicute/Desktop/UGREEN/GHOST%20THEME/tailwind.config.js): Tailwind 스캔 경로 및 테마 설정 diff --git a/docs/spec.md b/docs/spec.md index d8a303c..00cfc02 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -1,7 +1,7 @@ # 기술 명세 ## 현재 버전 -- `v0.1.19` +- `v0.1.20` ## 테마 개요 - Ghost `v5` 대응 커스텀 테마 @@ -14,8 +14,10 @@ - 검색 오버레이, 탭 전환, 다크모드 토글용 프런트 스크립트 - Ghost `navigation`, `get`, `subscribe_form`, `comments`, `pagination` 헬퍼 사용 - Tailwind CSS 빌드 결과물(`assets/built/tailwind.css`)을 기존 `screen.css`와 함께 로드 +- Alpine.js 로컬 자산(`assets/built/alpine.js`)을 전역 로드 - 좌측 카테고리 영역은 `1024px` 이상에서 기본 열림, 미만에서 기본 닫힘 - `author.hbs`는 페이지 컨텍스트의 작성자 데이터를 직접 사용 +- `page-tags.hbs`, `page-authors.hbs`는 각각 `slug=tags`, `slug=authors` 페이지에 연결 가능 ## 주요 스타일 방향 - 밝은 크림톤 배경 + 오렌지 포인트 diff --git a/docs/update.md b/docs/update.md index cfff9ce..c4a65d6 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,11 @@ # 업데이트 로그 +## v0.1.20 - 2026-04-14 +- `Tags`, `Authors` 좌측 메뉴 직접 링크형으로 변경. +- `page-tags.hbs`, `page-authors.hbs` 추가. +- Tailwind 로드 우선순위 수정. +- Alpine.js 로컬 자산 로드 추가. + ## v0.1.19 - 2026-04-14 - `author.hbs` 구식 `author` 블록 헬퍼 제거. - Ghost 업로드 오류 대응 정리. diff --git a/package.json b/package.json index 1870e02..4d30362 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghost-theme-thred-clone", - "version": "0.1.19", + "version": "0.1.20", "private": true, "description": "A Ghost theme inspired by the Thred reference layout.", "keywords": [ @@ -75,9 +75,10 @@ } }, "scripts": { + "build:alpine": "cp ./node_modules/alpinejs/dist/cdn.min.js ./assets/built/alpine.js", "build:tailwind": "tailwindcss -c ./tailwind.config.js -i ./assets/styles/tailwind.css -o ./assets/built/tailwind.css --minify", "dev": "npm run dev:ghost:start", - "dev:prepare": "npm run build:tailwind && npm run dev:sync", + "dev:prepare": "npm run build:alpine && npm run build:tailwind && npm run dev:sync", "dev:sync": "sh ./scripts/sync-theme.sh", "dev:seed": "node ./scripts/build-sample-content.js", "dev:seed:zip": "npm run dev:seed && cd seed && zip -q -r thred-inspired-sample-content.ghost.zip thred-inspired-sample-content.ghost.json", @@ -87,6 +88,7 @@ "zip": "zip -r theme.zip . -x '*.git*' -x 'node_modules/*' -x 'theme.zip'" }, "devDependencies": { + "alpinejs": "^3.14.9", "tailwindcss": "^3.4.17" } } diff --git a/page-authors.hbs b/page-authors.hbs new file mode 100644 index 0000000..9237da3 --- /dev/null +++ b/page-authors.hbs @@ -0,0 +1,42 @@ +{{!< default}} + +{{#post}} +
+
+
+

{{title}}

+ {{#if custom_excerpt}} +

{{custom_excerpt}}

+ {{else}} +

Browse by author

+ {{/if}} +
+ + {{#get "authors" limit="all" include="count.posts"}} + + {{/get}} +
+
+{{/post}} diff --git a/page-tags.hbs b/page-tags.hbs new file mode 100644 index 0000000..0f0f428 --- /dev/null +++ b/page-tags.hbs @@ -0,0 +1,36 @@ +{{!< default}} + +{{#post}} +
+
+
+

{{title}}

+ {{#if custom_excerpt}} +

{{custom_excerpt}}

+ {{else}} +

Browse by topic

+ {{/if}} +
+ + {{#get "tags" limit="all" include="count.posts" order="count.posts desc"}} + + {{/get}} +
+
+{{/post}} diff --git a/partials/site/sidebar-left.hbs b/partials/site/sidebar-left.hbs index 5742b85..50f2851 100644 --- a/partials/site/sidebar-left.hbs +++ b/partials/site/sidebar-left.hbs @@ -17,48 +17,22 @@ - -