Compare commits
No commits in common. "gh-pages" and "main" have entirely different histories.
10
.env.local.example
Normal file
10
.env.local.example
Normal file
@ -0,0 +1,10 @@
|
||||
MINIO_ENDPOINT=
|
||||
MINIO_ACCESS_KEY=
|
||||
MINIO_SECRET_KEY=
|
||||
MINIO_BUCKET=
|
||||
|
||||
PEERTUBE_FEED_URL=
|
||||
|
||||
UMAMI_ENABLED=
|
||||
UMAMI_SCRIPT_URL=
|
||||
UMAMI_WEBSITE_ID=
|
30
.gitea/workflows/deploy.yaml
Normal file
30
.gitea/workflows/deploy.yaml
Normal file
@ -0,0 +1,30 @@
|
||||
name: deploy
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "20.x"
|
||||
cache: "npm"
|
||||
- run: npm ci
|
||||
- run: echo "COMMIT_SHORT_SHA=`git rev-parse --short HEAD`" >> $GITHUB_ENV
|
||||
- run: npm run build
|
||||
env:
|
||||
MINIO_ENDPOINT: ${{ vars.MINIO_ENDPOINT }}
|
||||
MINIO_ACCESS_KEY: ${{ vars.MINIO_ACCESS_KEY }}
|
||||
MINIO_SECRET_KEY: ${{ vars.MINIO_SECRET_KEY }}
|
||||
MINIO_BUCKET: ${{ vars.MINIO_BUCKET }}
|
||||
PEERTUBE_FEED_URL: ${{ vars.PEERTUBE_FEED_URL }}
|
||||
UMAMI_ENABLED: ${{ vars.UMAMI_ENABLED }}
|
||||
UMAMI_SCRIPT_URL: ${{ vars.UMAMI_SCRIPT_URL }}
|
||||
UMAMI_WEBSITE_ID: ${{ vars.UMAMI_WEBSITE_ID }}
|
||||
- run: npx wrangler pages deploy out --project-name=$CF_PROJECT_NAME --branch=$GITHUB_REF_NAME
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ vars.CLOUDFLARE_API_TOKEN }}
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
|
||||
CF_PROJECT_NAME: ${{ vars.CF_PROJECT_NAME }}
|
30
.github/workflows/jobs.yaml
vendored
Normal file
30
.github/workflows/jobs.yaml
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
name: deploy
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- run: echo "COMMIT_SHORT_SHA=`git rev-parse --short HEAD`" >> $GITHUB_ENV
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 21
|
||||
cache: "npm"
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
env:
|
||||
MINIO_ENDPOINT: ${{ secrets.MINIO_ENDPOINT }}
|
||||
MINIO_ACCESS_KEY: ${{ secrets.MINIO_ACCESS_KEY }}
|
||||
MINIO_SECRET_KEY: ${{ secrets.MINIO_SECRET_KEY }}
|
||||
MINIO_BUCKET: ${{ secrets.MINIO_BUCKET }}
|
||||
PEERTUBE_FEED_URL: ${{ secrets.PEERTUBE_FEED_URL }}
|
||||
UMAMI_ENABLED: ${{ vars.UMAMI_ENABLED }}
|
||||
UMAMI_SCRIPT_URL: ${{ vars.UMAMI_SCRIPT_URL }}
|
||||
UMAMI_WEBSITE_ID: ${{ vars.UMAMI_WEBSITE_ID }}
|
||||
- uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./out
|
||||
cname: ig.rizaldy.club
|
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
.next
|
||||
node_modules
|
||||
out
|
||||
.devbox
|
||||
.env*.local
|
||||
remoteImagesForOptimization
|
||||
public/nextImageExportOptimizer
|
||||
public/images/next-image-export-optimizer-hashes.json
|
1
404.html
1
404.html
@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><title>404: This page could not be found</title><meta name="next-head-count" content="3"/><link rel="preload" href="/_next/static/css/25b36cfcabf50304.css" as="style" crossorigin=""/><link rel="stylesheet" href="/_next/static/css/25b36cfcabf50304.css" crossorigin="" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" crossorigin="" nomodule="" src="/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="https://u.rizaldy.club/umami.js" data-website-id="8dd38b8f-90e1-4df9-91ff-622d7882f05c" defer="" data-nscript="beforeInteractive" crossorigin=""></script><script src="/_next/static/chunks/webpack-ee7e63bc15b31913.js" defer="" crossorigin=""></script><script src="/_next/static/chunks/framework-1e817f2a1c5c711b.js" defer="" crossorigin=""></script><script src="/_next/static/chunks/main-72cd581c1e9bd837.js" defer="" crossorigin=""></script><script src="/_next/static/chunks/pages/_app-4f9a8d5bb2887a9b.js" defer="" crossorigin=""></script><script src="/_next/static/chunks/pages/_error-b6491f42fb2263bb.js" defer="" crossorigin=""></script><script src="/_next/static/yOMHMNqsMpN-23tJUJ2XT/_buildManifest.js" defer="" crossorigin=""></script><script src="/_next/static/yOMHMNqsMpN-23tJUJ2XT/_ssgManifest.js" defer="" crossorigin=""></script></head><body><div id="__next"><div class="min-h-screen bg-white dark:bg-black dark:text-neutral-200"><nav class="flex py-2 border-b dark:border-neutral-800 hover:opacity-70"><div class="w-3/12 md:w-full lg:w-7/12 md:mx-11 lg:mx-auto"><div class="md:w-2/12"><a href="/"><img alt="not instagram™" src="https://s3.rizaldy.club/0x0/d43840c5ee4ec66a3bee3c6e2.png" class="w-full"/></a></div></div></nav><div class="flex items-center justify-between w-full my-5"><div class="md:w-11/12 lg:w-7/12 mx-auto"><div style="font-family:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">This page could not be found<!-- -->.</h2></div></div></div></div></div><footer class="flex py-4 text-center md:mt-10 mt-5"><div class="md:w-7/12 mx-auto font-semibold"><div class="md:text-sm mb-10"><a class="md:mx-4 mb-1 md:inline block hover:opacity-70" href="/static/about">About</a><a class="md:mx-4 mb-1 md:inline block hover:opacity-70" href="https://rizaldy.club">Blog</a><a class="md:mx-4 mb-1 md:inline block hover:opacity-70" href="https://edgy.social/@rizaldy">Mastodon</a><a class="md:mx-4 mb-1 md:inline block hover:opacity-70" href="https://bsky.app/profile/rizaldy.club">Bluesky</a><a class="mx-4 hover:opacity-70" href="https://github.com/faultables/ig.rizaldy.club">Source Code</a></div><div class="text-sm font-normal text-neutral-400"><p>© <!-- -->MMXXIV<!-- --> <a class="text-neutral-600 dark:text-neutral-200 hover:opacity-70" target="_blank" rel="noopener noreferer" href="https://github.com/faultables">faultables</a> <!-- -->• All media is licensed under<!-- --> <a class="underline hover:opacity-70" href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a> <!-- -->unless stated otherwise •<!-- --> <a class="text-neutral-600 dark:text-neutral-200 hover:opacity-70" target="_blank" rel="noopener noreferer" href="https://github.com/faultables/ig.rizaldy.club/commit/1102852">1102852</a></p></div></div></footer></div></div><script id="__NEXT_DATA__" type="application/json" crossorigin="">{"props":{"pageProps":{"statusCode":404}},"page":"/_error","query":{},"buildId":"yOMHMNqsMpN-23tJUJ2XT","nextExport":true,"isFallback":false,"gip":true,"scriptLoader":[]}</script></body></html>
|
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
@ -1 +0,0 @@
|
||||
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[820],{1981:function(n,_,u){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_error",function(){return u(8435)}])}},function(n){n.O(0,[774,888,179],function(){return n(n.s=1981)}),_N_E=n.O()}]);
|
@ -1 +0,0 @@
|
||||
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[405],{5557:function(e,l,t){(window.__NEXT_P=window.__NEXT_P||[]).push(["/",function(){return t(5171)}])},5171:function(e,l,t){"use strict";t.r(l),t.d(l,{__N_SSG:function(){return p},default:function(){return f}});var r=t(5893),s=t(7294),a=t(7205);let n=(e,l)=>e===l?"border-t dark:border-neutral-400":"";var o=e=>{let{activeTab:l,setActiveTab:t}=e;return(0,r.jsxs)("div",{className:"flex items-center justify-center text-center gap-5 border-t mb-4 dark:border-neutral-800",children:[(0,r.jsx)("button",{onClick:()=>t("photos"),className:"uppercase tracking-tight font-semibold text-sm border-black pt-3 px-3 ".concat(n("photos",l)),children:"Photos"}),(0,r.jsx)("button",{onClick:()=>t("videos"),className:"uppercase tracking-tight font-semibold text-sm pt-3 px-3 border-black ".concat(n("videos",l)),children:"Videos"})]})},d=e=>{let{media:l,type:t,setOverlayContent:s,currentIndex:a,limitPerPage:n}=e;return(0,r.jsx)("main",{className:"grid grid-cols-3 gap-1",children:l.slice(a,n).map(e=>{let{id:l,url:a,previewPath:n,embedPath:o=a}=e;return(0,r.jsx)("a",{href:a,target:"_blank",rel:"noopener noreferer",className:"hover:opacity-70",onClick:e=>{e.preventDefault(),s({url:o,type:t})},children:(0,r.jsx)("img",{alt:a,loading:"lazy",src:n,className:"aspect-".concat(t," bg-neutral-100 dark:bg-neutral-900")})},l)})})},i=e=>{let{profile:l,totalPosts:t}=e;return(0,r.jsxs)("div",{className:"flex flex-warp",children:[(0,r.jsx)("div",{className:"lg:w-4/12 w-3/12 md:py-10 ml-3 md:ml-0 lg:px-20 md:px-10",children:(0,r.jsx)("img",{loading:"lazy",alt:l.display_name,src:l.avatar,className:"w-full rounded-full mx-auto border bg-neutral-100 border-gray-200 p-1 dark:border-neutral-800 dark:bg-black"})}),(0,r.jsxs)("div",{className:"lg:w-9/12 md:w-10/12 mb-5 md:p-5 ml-5",children:[(0,r.jsxs)("div",{className:"md:flex items-center",children:[(0,r.jsx)("h2",{className:"text-2xl font-semibold",children:l.username}),(0,r.jsxs)("div",{className:"md:ml-4 my-4",children:[(0,r.jsx)("a",{className:"rounded-md bg-gray-100 px-5 font-semibold py-2 md:ml-2 text-sm leading-relaxed dark:bg-neutral-800 hover:opacity-70",href:l.follow_url,children:"Follow"}),(0,r.jsx)("a",{className:"rounded-md bg-gray-100 px-5 font-semibold py-2 ml-2 text-sm leading-relaxed dark:bg-neutral-800 hover:opacity-70",href:l.message_url,children:"Message"})]})]}),(0,r.jsx)("div",{className:"md:my-5 my-3 font-bold text-sm",children:(0,r.jsxs)("p",{children:[t," posts"]})}),(0,r.jsx)("p",{className:"font-bold mb-1",children:l.display_name}),(0,r.jsx)("p",{children:l.about}),(0,r.jsx)("p",{className:"leading-loose font-semibold text-blue-900 dark:text-blue-200",children:(0,r.jsx)("a",{target:"_blank",className:"hover:underline",rel:"noopener noreferer",href:"https://".concat(l.link),children:l.link})})]})]})};let c=e=>{let{overlayContent:l}=e;return(null==l?void 0:l.type)===a.oZ.PHOTOS?(0,r.jsx)("img",{alt:null==l?void 0:l.url,src:null==l?void 0:l.url,className:"z-20 cursor-default"}):(null==l?void 0:l.type)===a.oZ.VIDEOS?(0,r.jsx)("iframe",{className:"md:w-6/12 w-full h-1/2",allow:"fullscreen",sandbox:"allow-same-origin allow-scripts allow-popups",src:null==l?void 0:l.url}):void 0};var m=e=>{let{overlayContent:l,closeOverlay:t}=e,s=(null==l?void 0:l.type)!==void 0;return(0,r.jsxs)("div",{onClick:t,className:"".concat(s?"bg-neutral-800/95 fixed w-full h-full left-0 top-0 cursor-pointer z-30":""),children:[s?(0,r.jsx)("p",{onClick:t,className:"fixed right-0 bottom-0 md:mx-5 my-5 text-white rounded text-sm text-center w-full z-30",children:'Click anywhere or press "Escape" to close'}):null,(0,r.jsx)("div",{className:"flex justify-center items-center ".concat(s?"h-full w-full":"h-0 w-0"),children:(0,r.jsx)(c,{overlayContent:l})})]})},u=t(356);let x=e=>{let{activeTab:l,photos:t,videos:s,...n}=e;return l===a.oZ.PHOTOS?(0,r.jsx)(d,{media:t,type:l,...n}):l===a.oZ.VIDEOS?(0,r.jsx)(d,{media:s,type:l,...n}):(0,r.jsx)("div",{className:"text-center pt-10 font-bold text-2xl",children:"nice try"})};var p=!0,f=e=>{let{photos:l,videos:t,totalPosts:n}=e,[d,c]=(0,s.useState)(a.oZ.PHOTOS),[p,f]=(0,s.useState)(0),[h,b]=(0,s.useState)(null),v=()=>b(null),g=e=>{let{keyCode:l}=e;l===a.O_&&v()};return(0,s.useEffect)(()=>(window.addEventListener("keydown",g),()=>{window.removeEventListener("keydown",g)}),[g]),(0,r.jsxs)(s.Fragment,{children:[(0,r.jsx)(i,{profile:u.N5,totalPosts:n}),(0,r.jsx)(o,{activeTab:d,setActiveTab:c}),(0,r.jsx)(m,{overlayContent:h,closeOverlay:v}),(0,r.jsx)(x,{currentIndex:p,setCurrentIndex:f,limitPerPage:a.Zv,activeTab:d,setOverlayContent:b,photos:l,videos:t})]})}}},function(e){e.O(0,[774,888,179],function(){return e(e.s=5557)}),_N_E=e.O()}]);
|
@ -1 +0,0 @@
|
||||
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[664],{1556:function(a,e,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/static/about",function(){return n(3414)}])},3414:function(a,e,n){"use strict";n.r(e);var i=n(5893);e.default=()=>(0,i.jsx)("div",{className:"p-2",children:(0,i.jsxs)("div",{className:"md:border p-5 md:w-8/12 md:shadow md:rotate-[-0.5deg] mx-auto text-neutral-600 dark:text-neutral-200 dark:border-neutral-800",children:[(0,i.jsx)("h2",{className:"font-bold text-3xl leading-loose mb-2 text-black dark:text-neutral-200",children:"Notes from @faultables"}),(0,i.jsxs)("p",{className:"mb-6",children:["Sebelumnya saya menjalankan ",(0,i.jsx)("em",{children:"instance"})," ",(0,i.jsx)("a",{href:"https://pixelfed.org",className:"underline hover:opacity-70",target:"_blank",rel:"noopener noreferer",children:"Pixelfed"})," ",'selama bertahun-tahun sebagai alternatif dari sosial media mainstream yang dijalankan oleh "big co". Pixelfed masih dalam tahap pengembangan, sehingga, adanya bug dan masalah acak lainnya adalah hal yang wajar.']}),(0,i.jsxs)("p",{className:"mb-6",children:["Disamping itu, Pixelfed sangat menjanjikan: menggunakan protokol"," ",(0,i.jsx)("a",{href:"https://www.w3.org/TR/activitypub/",className:"underline hover:opacity-70",target:"_blank",rel:"noopener noreferer",children:"ActivityPub"})," ",'sehingga bisa "berfederasi" dengan ',(0,i.jsx)("em",{children:"instance"})," lain, dan yang paling penting adalah"," ",(0,i.jsx)("a",{href:"https://github.com/pixelfed/pixelfed",classname:"underline hover:opacity-70",target:"_blank",rel:"noopener noreferer",children:"bersumber kode terbuka"})," ",(0,i.jsx)("strong",{children:"dan"}),' dikembangkan murni oleh komunitas. Pixelfed mendukung fitur standar untuk bersosial media seperti memperbaharui status, mengikuti pengguna, mengirim komentar, menyukai, intinya fitur bersosial apapun yang sudah menjadi mainstream. Meskipun saya sudah memiliki akun sosial media lainnya di "universe"'," ",(0,i.jsx)("a",{href:"https://joinmastodon.org",className:"underline hover:opacity-70",target:"_blank",rel:"noopener noreferer",children:"Mastodon"}),", saya memilih Pixelfed murni hanya untuk berbagi media dalam bentuk foto saja."]}),(0,i.jsx)("p",{className:"mb-6",children:"Tapi, ya, saya tidak menggunakan Pixelfed sesering itu. Saya melakukan optimasi gambar secara manual berikut menghapus metadata exif dan memotong gambar ke 1024px tanpa menggunakan fitur built-in (crop) karena terkadang fiturnya tidak berjalan sesuatu dengan yang harapkan."}),(0,i.jsx)("p",{className:"mb-6",children:'Secara teknis, menjalankan 2 aplikasi (Laravel & Horizon) plus MySQL bukanlah hal yang sulit dan mahal, namun bagaimanapun, saya tidak menggunakan "fitur sosial" yang ada di Pixelfed karena itulah yang saya inginkan sehingga terkesan seperti berlebihan.'}),(0,i.jsx)("p",{className:"mb-6",children:"Jika kamu seorang fotografer, kamu bisa mencoba Pixelfed. Kamu juga bisa berinteraksi dengan komunitas di jaringan yang sama—selama menggunakan protokol ActivityPub—karena Pixelfed adalah jaringan federasi!"}),(0,i.jsx)("p",{className:"mb-6",children:"Bagaimanapun, pilihan ini bukanlah pendekatan yang terbaik. Tapi setidaknya, ini tidak berlebihan, khususnya untuk saat ini."}),(0,i.jsx)("br",{}),(0,i.jsx)("p",{className:"mb-6",children:(0,i.jsx)("a",{href:"https://github.com/faultables",className:"hover:opacity-70",target:"_blank",rel:"noopener noreferer",children:"— faultables"})})]})})}},function(a){a.O(0,[774,888,179],function(){return a(a.s=1556)}),_N_E=a.O()}]);
|
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
!function(){"use strict";var e,n,r,t,o={},u={};function i(e){var n=u[e];if(void 0!==n)return n.exports;var r=u[e]={exports:{}},t=!0;try{o[e](r,r.exports,i),t=!1}finally{t&&delete u[e]}return r.exports}i.m=o,e=[],i.O=function(n,r,t,o){if(r){o=o||0;for(var u=e.length;u>0&&e[u-1][2]>o;u--)e[u]=e[u-1];e[u]=[r,t,o];return}for(var f=1/0,u=0;u<e.length;u++){for(var r=e[u][0],t=e[u][1],o=e[u][2],c=!0,l=0;l<r.length;l++)f>=o&&Object.keys(i.O).every(function(e){return i.O[e](r[l])})?r.splice(l--,1):(c=!1,o<f&&(f=o));if(c){e.splice(u--,1);var a=t();void 0!==a&&(n=a)}}return n},i.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(n,{a:n}),n},i.d=function(e,n){for(var r in n)i.o(n,r)&&!i.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:n[r]})},i.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),i.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.p="/_next/",n={272:0},i.O.j=function(e){return 0===n[e]},r=function(e,r){var t,o,u=r[0],f=r[1],c=r[2],l=0;if(u.some(function(e){return 0!==n[e]})){for(t in f)i.o(f,t)&&(i.m[t]=f[t]);if(c)var a=c(i)}for(e&&e(r);l<u.length;l++)o=u[l],i.o(n,o)&&n[o]&&n[o][0](),n[o]=0;return i.O(a)},(t=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))}();
|
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
self.__BUILD_MANIFEST={__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":["static/chunks/pages/index-c91f1d8faf4d92e1.js"],"/_error":["static/chunks/pages/_error-b6491f42fb2263bb.js"],"/static/about":["static/chunks/pages/static/about-30f1ad42665a60b3.js"],sortedPages:["/","/_app","/_error","/static/about"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
|
@ -1 +0,0 @@
|
||||
self.__SSG_MANIFEST=new Set(["\u002F"]);self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()
|
42
components/Feed.js
Normal file
42
components/Feed.js
Normal file
@ -0,0 +1,42 @@
|
||||
import ExportedImage from "next-image-export-optimizer";
|
||||
|
||||
const Feed = ({
|
||||
media,
|
||||
type,
|
||||
setOverlayContent,
|
||||
currentIndex,
|
||||
limitPerPage,
|
||||
}) => (
|
||||
<main className="grid grid-cols-3 gap-1">
|
||||
{media
|
||||
.slice(currentIndex, limitPerPage)
|
||||
.map(({ id, url, previewPath, embedPath = url }) => (
|
||||
<a
|
||||
key={id}
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferer"
|
||||
className="hover:opacity-70"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
setOverlayContent({
|
||||
url: embedPath,
|
||||
type,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<ExportedImage
|
||||
alt={url}
|
||||
loading="lazy"
|
||||
src={previewPath}
|
||||
width="512"
|
||||
height="512"
|
||||
className={`aspect-${type} bg-neutral-100 dark:bg-neutral-900`}
|
||||
/>
|
||||
</a>
|
||||
))}
|
||||
</main>
|
||||
);
|
||||
|
||||
export default Feed;
|
53
components/Footer.js
Normal file
53
components/Footer.js
Normal file
@ -0,0 +1,53 @@
|
||||
import Link from "next/link";
|
||||
|
||||
import { YEAR_TO_BUMP } from "../constants";
|
||||
|
||||
const Footer = ({ license, links, repo, commitID }) => (
|
||||
<footer className="flex py-4 text-center md:mt-10 mt-5">
|
||||
<div className="md:w-7/12 mx-auto font-semibold">
|
||||
<div className="md:text-sm mb-10">
|
||||
{links.map(({ url, label }) => (
|
||||
<Link
|
||||
key={url + label}
|
||||
href={url}
|
||||
className="md:mx-4 mb-1 md:inline block hover:opacity-70"
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
))}
|
||||
<Link href={repo} className="mx-4 hover:opacity-70">
|
||||
Source Code
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="text-sm font-normal text-neutral-400">
|
||||
<p>
|
||||
© {YEAR_TO_BUMP}{" "}
|
||||
<Link
|
||||
className="text-neutral-600 dark:text-neutral-200 hover:opacity-70"
|
||||
target="_blank"
|
||||
rel="noopener noreferer"
|
||||
href="https://faultables.net"
|
||||
>
|
||||
faultables
|
||||
</Link>{" "}
|
||||
• All media is licensed under{" "}
|
||||
<Link className="underline hover:opacity-70" href={license.url}>
|
||||
{license.name}
|
||||
</Link>{" "}
|
||||
unless stated otherwise •{" "}
|
||||
<Link
|
||||
className="text-neutral-600 dark:text-neutral-200 hover:opacity-70"
|
||||
target="_blank"
|
||||
rel="noopener noreferer"
|
||||
href={`${repo}/commit/${commitID}`}
|
||||
>
|
||||
{commitID}
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
|
||||
export default Footer;
|
28
components/Navbar.js
Normal file
28
components/Navbar.js
Normal file
@ -0,0 +1,28 @@
|
||||
import Link from "next/link";
|
||||
import ExportedImage from "next-image-export-optimizer";
|
||||
|
||||
const Navbar = ({ name, logo }) => (
|
||||
<nav className="flex py-2 border-b dark:border-neutral-800 hover:opacity-70">
|
||||
<div className="w-3/12 md:w-full lg:w-7/12 md:mx-11 lg:mx-auto">
|
||||
<div className="md:w-2/12">
|
||||
<Link href="/">
|
||||
{logo ? (
|
||||
<ExportedImage
|
||||
alt={name}
|
||||
src={logo}
|
||||
width={224}
|
||||
height={56}
|
||||
className="w-full"
|
||||
/>
|
||||
) : (
|
||||
<h1 className="font-bold leading-relaxed tracking-tight text-2xl text-neutral-800">
|
||||
{name}
|
||||
</h1>
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
|
||||
export default Navbar;
|
66
components/Overlay.js
Normal file
66
components/Overlay.js
Normal file
@ -0,0 +1,66 @@
|
||||
import { MEDIA_TYPE } from "../constants";
|
||||
|
||||
const OverlayContent = ({ overlayContent }) => {
|
||||
if (overlayContent?.type === MEDIA_TYPE.PHOTOS) {
|
||||
return (
|
||||
<img
|
||||
alt={overlayContent?.url}
|
||||
src={overlayContent?.url}
|
||||
className="z-20 cursor-default"
|
||||
/>
|
||||
);
|
||||
} else if (overlayContent?.type === MEDIA_TYPE.VIDEOS) {
|
||||
return (
|
||||
<iframe
|
||||
className="md:w-6/12 w-full h-1/2"
|
||||
allow="fullscreen"
|
||||
sandbox="allow-same-origin allow-scripts allow-popups"
|
||||
src={overlayContent?.url}
|
||||
></iframe>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const Overlay = ({ overlayContent, closeOverlay }) => {
|
||||
const isOverlayOpen = overlayContent?.type !== undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={closeOverlay}
|
||||
className={`${
|
||||
isOverlayOpen
|
||||
? "bg-neutral-800/95 fixed w-full h-full left-0 top-0 cursor-pointer z-30"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{isOverlayOpen ? (
|
||||
<p
|
||||
onClick={closeOverlay}
|
||||
className="fixed right-0 bottom-0 md:mx-5 my-5 text-white rounded text-sm text-center w-full z-30 md:bottom-10 px-2 leading-relaxed"
|
||||
>
|
||||
Click anywhere or press "Escape" to close
|
||||
<span className="px-2">|</span>
|
||||
<a
|
||||
className="underline"
|
||||
target="_blank"
|
||||
rel="noreferer noopener"
|
||||
href={overlayContent?.url}
|
||||
>
|
||||
Click here
|
||||
</a>{" "}
|
||||
to see the raw media
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<div
|
||||
className={`flex justify-center items-center ${
|
||||
isOverlayOpen ? "h-full w-full" : "h-0 w-0"
|
||||
}`}
|
||||
>
|
||||
<OverlayContent overlayContent={overlayContent} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Overlay;
|
52
components/Profile.js
Normal file
52
components/Profile.js
Normal file
@ -0,0 +1,52 @@
|
||||
import ExportedImage from "next-image-export-optimizer";
|
||||
|
||||
const Profile = ({ profile, totalPosts }) => (
|
||||
<div className="flex flex-warp">
|
||||
<div className="lg:w-4/12 w-3/12 md:py-10 ml-3 md:ml-0 lg:px-20 md:px-10">
|
||||
<ExportedImage
|
||||
loading="lazy"
|
||||
alt={profile.display_name}
|
||||
src={profile.avatar}
|
||||
width={250}
|
||||
height={250}
|
||||
className="w-full rounded-full mx-auto border bg-neutral-100 border-gray-200 p-1 dark:border-neutral-800 dark:bg-black"
|
||||
/>
|
||||
</div>
|
||||
<div className="lg:w-9/12 md:w-10/12 mb-5 md:p-5 ml-5">
|
||||
<div className="md:flex items-center">
|
||||
<h2 className="text-2xl font-semibold">{profile.username}</h2>
|
||||
<div className="md:ml-4 my-4">
|
||||
<a
|
||||
className="rounded-md bg-gray-100 px-5 font-semibold py-2 md:ml-2 text-sm leading-relaxed dark:bg-neutral-800 hover:opacity-70"
|
||||
href={profile.follow_url}
|
||||
>
|
||||
Follow
|
||||
</a>
|
||||
<a
|
||||
className="rounded-md bg-gray-100 px-5 font-semibold py-2 ml-2 text-sm leading-relaxed dark:bg-neutral-800 hover:opacity-70"
|
||||
href={profile.message_url}
|
||||
>
|
||||
Message
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="md:my-5 my-3 font-bold text-sm">
|
||||
<p>{totalPosts} posts</p>
|
||||
</div>
|
||||
<p className="font-bold mb-1">{profile.display_name}</p>
|
||||
<p>{profile.about}</p>
|
||||
<p className="leading-loose font-semibold text-blue-900 dark:text-blue-200">
|
||||
<a
|
||||
target="_blank"
|
||||
className="hover:underline"
|
||||
rel="noopener noreferer"
|
||||
href={`https://${profile.link}`}
|
||||
>
|
||||
{profile.link}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Profile;
|
27
components/Tab.js
Normal file
27
components/Tab.js
Normal file
@ -0,0 +1,27 @@
|
||||
const isActiveTab = (currentTab, activeTab) =>
|
||||
currentTab === activeTab ? "border-t dark:border-neutral-400" : "";
|
||||
|
||||
const Tab = ({ activeTab, setActiveTab }) => (
|
||||
<div className="flex items-center justify-center text-center gap-5 border-t mb-4 dark:border-neutral-800">
|
||||
<button
|
||||
onClick={() => setActiveTab("photos")}
|
||||
className={`uppercase tracking-tight font-semibold text-sm border-black pt-3 px-3 ${isActiveTab(
|
||||
"photos",
|
||||
activeTab
|
||||
)}`}
|
||||
>
|
||||
Photos
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab("videos")}
|
||||
className={`uppercase tracking-tight font-semibold text-sm pt-3 px-3 border-black ${isActiveTab(
|
||||
"videos",
|
||||
activeTab
|
||||
)}`}
|
||||
>
|
||||
Videos
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Tab;
|
36
config.json
Normal file
36
config.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"navbar": {
|
||||
"logotype": "not instagram™",
|
||||
"logo": "https://s3.rizaldy.club/0x0/d43840c5ee4ec66a3bee3c6e2.png"
|
||||
},
|
||||
"profile": {
|
||||
"avatar": "https://s3.rizaldy.club/0x0/IMG_6279.JPG",
|
||||
"username":"faultables",
|
||||
"follow_url": "https://edgy.social/@rizaldy",
|
||||
"message_url": "mailto:rizaldy@duck.com",
|
||||
"display_name": "rizaldy",
|
||||
"about": "SRE, DevOps, and everything in between",
|
||||
"link": "rizaldy.club"
|
||||
},
|
||||
"footer": {
|
||||
"repo": "https://forge.edgy.social/rizaldy/ig.rizaldy.club",
|
||||
"links": [
|
||||
{
|
||||
"label": "About",
|
||||
"url": "/static/about"
|
||||
},
|
||||
{
|
||||
"label": "Mastodon",
|
||||
"url": "https://edgy.social/@rizaldy"
|
||||
},
|
||||
{
|
||||
"label": "Bluesky",
|
||||
"url": "https://bsky.app/profile/rizaldy.club"
|
||||
}
|
||||
],
|
||||
"license": {
|
||||
"name": "CC BY-NC-SA 4.0",
|
||||
"url": "https://creativecommons.org/licenses/by-nc-sa/4.0/"
|
||||
}
|
||||
}
|
||||
}
|
8
constants.js
Normal file
8
constants.js
Normal file
@ -0,0 +1,8 @@
|
||||
export const MEDIA_PER_PAGE = 40;
|
||||
export const MEDIA_TYPE = {
|
||||
PHOTOS: "photos",
|
||||
VIDEOS: "videos",
|
||||
};
|
||||
|
||||
export const YEAR_TO_BUMP = "MMXXIV";
|
||||
export const ESCAPE_KEY = 27;
|
3
devbox.json
Normal file
3
devbox.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"packages": ["nodejs@latest"]
|
||||
}
|
25
devbox.lock
Normal file
25
devbox.lock
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"lockfile_version": "1",
|
||||
"packages": {
|
||||
"nodejs@latest": {
|
||||
"last_modified": "2024-01-14T03:55:27Z",
|
||||
"resolved": "github:NixOS/nixpkgs/dd5621df6dcb90122b50da5ec31c411a0de3e538#nodejs_21",
|
||||
"source": "devbox-search",
|
||||
"version": "21.5.0",
|
||||
"systems": {
|
||||
"aarch64-darwin": {
|
||||
"store_path": "/nix/store/ybpqk26vz7k9grapsgx0sd900s0sp4sa-nodejs-21.5.0"
|
||||
},
|
||||
"aarch64-linux": {
|
||||
"store_path": "/nix/store/brnzb5xxgdx6bbicygz83ybi5inqp09v-nodejs-21.5.0"
|
||||
},
|
||||
"x86_64-darwin": {
|
||||
"store_path": "/nix/store/yvgnx3lj8am9mqn30yr09sb4ia7qy3w8-nodejs-21.5.0"
|
||||
},
|
||||
"x86_64-linux": {
|
||||
"store_path": "/nix/store/nxfirpvaycr7wqzwl6wqifpdrqn7is7x-nodejs-21.5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
19
next.config.js
Normal file
19
next.config.js
Normal file
@ -0,0 +1,19 @@
|
||||
module.exports = {
|
||||
output: "export",
|
||||
images: {
|
||||
loader: "custom",
|
||||
deviceSizes: [1080, 1200, 1920],
|
||||
imageSizes: [128, 256, 384],
|
||||
},
|
||||
transpilePackages: ["next-image-export-optimizer"],
|
||||
env: {
|
||||
COMMIT_SHORT_SHA: process.env.COMMIT_SHORT_SHA || "HEAD",
|
||||
nextImageExportOptimizer_imageFolderPath: "public/images",
|
||||
nextImageExportOptimizer_exportFolderPath: "out",
|
||||
nextImageExportOptimizer_quality: "90",
|
||||
nextImageExportOptimizer_storePicturesInWEBP: "true",
|
||||
nextImageExportOptimizer_exportFolderName: "nextImageExportOptimizer",
|
||||
nextImageExportOptimizer_generateAndUseBlurImages: "true",
|
||||
nextImageExportOptimizer_remoteImageCacheTTL: "31536000",
|
||||
},
|
||||
};
|
3092
package-lock.json
generated
Normal file
3092
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
package.json
Normal file
21
package.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build && next-image-export-optimizer",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"minio": "^7.1.3",
|
||||
"next": "^14.0.4",
|
||||
"next-image-export-optimizer": "^1.12.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"xml2json": "^0.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^10.4.17",
|
||||
"postcss": "^8.4.33",
|
||||
"tailwindcss": "^3.4.1"
|
||||
}
|
||||
}
|
38
pages/_app.js
Normal file
38
pages/_app.js
Normal file
@ -0,0 +1,38 @@
|
||||
import "../styles/globals.css";
|
||||
|
||||
import Script from "next/script";
|
||||
|
||||
import Navbar from "../components/Navbar";
|
||||
import Footer from "../components/Footer";
|
||||
|
||||
import config from "../config.json";
|
||||
|
||||
const Content = ({ children }) => (
|
||||
<div className="flex items-center justify-between w-full my-5">
|
||||
<div className="md:w-11/12 lg:w-7/12 mx-auto">{children}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const App = ({ Component, pageProps }) => (
|
||||
<div className='min-h-screen bg-white dark:bg-black dark:text-neutral-200'>
|
||||
<Navbar name={config.navbar.logotype} logo={config.navbar.logo} />
|
||||
<Content>
|
||||
<Component {...pageProps} />
|
||||
</Content>
|
||||
<Footer
|
||||
repo={config.footer.repo}
|
||||
links={config.footer.links}
|
||||
license={config.footer.license}
|
||||
commitID={process.env.COMMIT_SHORT_SHA || "HEAD"}
|
||||
/>
|
||||
{process.env.UMAMI_ENABLED ? (
|
||||
<Script
|
||||
strategy="beforeInteractive"
|
||||
src={process.env.UMAMI_SCRIPT_URL}
|
||||
data-website-id={process.env.UMAMI_WEBSITE_ID}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default App;
|
15
pages/_document.js
Normal file
15
pages/_document.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { Html, Head, Main, NextScript } from "next/document";
|
||||
|
||||
const Document = () => {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head />
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
export default Document;
|
114
pages/index.js
Normal file
114
pages/index.js
Normal file
@ -0,0 +1,114 @@
|
||||
import xml2json from "xml2json";
|
||||
|
||||
import { Client } from "minio";
|
||||
import { Fragment, useState, useEffect } from "react";
|
||||
|
||||
import { ESCAPE_KEY, MEDIA_PER_PAGE, MEDIA_TYPE } from "../constants";
|
||||
|
||||
import Tab from "../components/Tab";
|
||||
import Feed from "../components/Feed";
|
||||
import Profile from "../components/Profile";
|
||||
import Overlay from "../components/Overlay";
|
||||
|
||||
import config from "../config.json";
|
||||
|
||||
const Content = ({ activeTab, photos, videos, ...props }) => {
|
||||
if (activeTab === MEDIA_TYPE.PHOTOS) {
|
||||
return <Feed media={photos} type={activeTab} {...props} />;
|
||||
} else if (activeTab === MEDIA_TYPE.VIDEOS) {
|
||||
return <Feed media={videos} type={activeTab} {...props} />;
|
||||
} else {
|
||||
return <div className="text-center pt-10 font-bold text-2xl">nice try</div>;
|
||||
}
|
||||
};
|
||||
|
||||
const IndexPage = ({ photos, videos, totalPosts }) => {
|
||||
const [activeTab, setActiveTab] = useState(MEDIA_TYPE.PHOTOS);
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const [overlayContent, setOverlayContent] = useState(null);
|
||||
|
||||
const closeOverlay = () => setOverlayContent(null);
|
||||
|
||||
const handleClose = ({ keyCode }) => {
|
||||
if (keyCode === ESCAPE_KEY) {
|
||||
closeOverlay();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("keydown", handleClose);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", handleClose);
|
||||
};
|
||||
}, [handleClose]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Profile profile={config.profile} totalPosts={totalPosts} />
|
||||
<Tab activeTab={activeTab} setActiveTab={setActiveTab} />
|
||||
<Overlay overlayContent={overlayContent} closeOverlay={closeOverlay} />
|
||||
<Content
|
||||
currentIndex={currentIndex}
|
||||
setCurrentIndex={setCurrentIndex}
|
||||
limitPerPage={MEDIA_PER_PAGE}
|
||||
activeTab={activeTab}
|
||||
setOverlayContent={setOverlayContent}
|
||||
photos={photos}
|
||||
videos={videos}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export async function getStaticProps() {
|
||||
const minioEndpoint = process.env.MINIO_ENDPOINT;
|
||||
const minioBucket = process.env.MINIO_BUCKET;
|
||||
|
||||
const mc = new Client({
|
||||
endPoint: minioEndpoint,
|
||||
accessKey: process.env.MINIO_ACCESS_KEY,
|
||||
secretKey: process.env.MINIO_SECRET_KEY,
|
||||
});
|
||||
|
||||
const minioObjects = await new Promise((resolve, reject) => {
|
||||
const data = [];
|
||||
|
||||
const stream = mc.listObjectsV2(minioBucket, "photos", true, "");
|
||||
|
||||
stream.on("data", (obj) => data.push(obj.name));
|
||||
stream.on("error", reject);
|
||||
stream.on("end", () => {
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
|
||||
const photos = minioObjects.map((id) => ({
|
||||
id,
|
||||
url: `https://${minioEndpoint}/${minioBucket}/${id}`,
|
||||
previewPath: `https://${minioEndpoint}/${minioBucket}/${id}`,
|
||||
})).sort().reverse();
|
||||
|
||||
const getPeertubeFeeds = await fetch(process.env.PEERTUBE_FEED_URL);
|
||||
const peertubeFeeds = await getPeertubeFeeds.text();
|
||||
|
||||
const peertubeFeedsJSON = xml2json.toJson(peertubeFeeds, { object: true });
|
||||
const peertubeItems = peertubeFeedsJSON.rss.channel.item;
|
||||
|
||||
const videos = peertubeItems.map((item) => ({
|
||||
id: item.guid,
|
||||
url: item["media:embed"].url,
|
||||
previewPath: item["media:thumbnail"][0].url,
|
||||
}));
|
||||
|
||||
return {
|
||||
props: {
|
||||
config,
|
||||
photos,
|
||||
videos,
|
||||
totalPosts: photos.length + videos.length,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default IndexPage;
|
93
pages/static/about.js
Normal file
93
pages/static/about.js
Normal file
@ -0,0 +1,93 @@
|
||||
const About = () => (
|
||||
<div className="p-2">
|
||||
<div className="md:border p-5 md:w-8/12 md:shadow md:rotate-[-0.5deg] mx-auto text-neutral-600 dark:text-neutral-200 dark:border-neutral-800">
|
||||
<h2 className="font-bold text-3xl leading-loose mb-2 text-black dark:text-neutral-200">
|
||||
Notes from @faultables
|
||||
</h2>
|
||||
<p className="mb-6">
|
||||
Sebelumnya saya menjalankan <em>instance</em>{" "}
|
||||
<a
|
||||
href="https://pixelfed.org"
|
||||
className="underline hover:opacity-70"
|
||||
target="_blank"
|
||||
rel="noopener noreferer"
|
||||
>
|
||||
Pixelfed
|
||||
</a>{" "}
|
||||
selama bertahun-tahun sebagai alternatif dari sosial media mainstream
|
||||
yang dijalankan oleh "big co". Pixelfed masih dalam tahap pengembangan,
|
||||
sehingga, adanya bug dan masalah acak lainnya adalah hal yang wajar.
|
||||
</p>
|
||||
<p className="mb-6">
|
||||
Disamping itu, Pixelfed sangat menjanjikan: menggunakan protokol{" "}
|
||||
<a
|
||||
href="https://www.w3.org/TR/activitypub/"
|
||||
className="underline hover:opacity-70"
|
||||
target="_blank"
|
||||
rel="noopener noreferer"
|
||||
>
|
||||
ActivityPub
|
||||
</a>{" "}
|
||||
sehingga bisa "berfederasi" dengan <em>instance</em> lain, dan yang
|
||||
paling penting adalah{" "}
|
||||
<a
|
||||
href="https://github.com/pixelfed/pixelfed"
|
||||
classname="underline hover:opacity-70"
|
||||
target="_blank"
|
||||
rel="noopener noreferer"
|
||||
>
|
||||
bersumber kode terbuka
|
||||
</a>{" "}
|
||||
<strong>dan</strong> dikembangkan murni oleh komunitas. Pixelfed
|
||||
mendukung fitur standar untuk bersosial media seperti memperbaharui
|
||||
status, mengikuti pengguna, mengirim komentar, menyukai, intinya fitur
|
||||
bersosial apapun yang sudah menjadi mainstream. Meskipun saya sudah
|
||||
memiliki akun sosial media lainnya di "universe"{" "}
|
||||
<a
|
||||
href="https://joinmastodon.org"
|
||||
className="underline hover:opacity-70"
|
||||
target="_blank"
|
||||
rel="noopener noreferer"
|
||||
>
|
||||
Mastodon
|
||||
</a>
|
||||
, saya memilih Pixelfed murni hanya untuk berbagi media dalam bentuk
|
||||
foto saja.
|
||||
</p>
|
||||
<p className="mb-6">
|
||||
Tapi, ya, saya tidak menggunakan Pixelfed sesering itu. Saya melakukan
|
||||
optimasi gambar secara manual berikut menghapus metadata exif dan
|
||||
memotong gambar ke 1024px tanpa menggunakan fitur built-in (crop) karena
|
||||
terkadang fiturnya tidak berjalan sesuatu dengan yang harapkan.
|
||||
</p>
|
||||
<p className="mb-6">
|
||||
Secara teknis, menjalankan 2 aplikasi (Laravel & Horizon) plus MySQL
|
||||
bukanlah hal yang sulit dan mahal, namun bagaimanapun, saya tidak
|
||||
menggunakan "fitur sosial" yang ada di Pixelfed karena itulah yang saya
|
||||
inginkan sehingga terkesan seperti berlebihan.
|
||||
</p>
|
||||
<p className="mb-6">
|
||||
Jika kamu seorang fotografer, kamu bisa mencoba Pixelfed. Kamu juga bisa
|
||||
berinteraksi dengan komunitas di jaringan yang sama—selama menggunakan
|
||||
protokol ActivityPub—karena Pixelfed adalah jaringan federasi!
|
||||
</p>
|
||||
<p className="mb-6">
|
||||
Bagaimanapun, pilihan ini bukanlah pendekatan yang terbaik. Tapi
|
||||
setidaknya, ini tidak berlebihan, khususnya untuk saat ini.
|
||||
</p>
|
||||
<br />
|
||||
<p className="mb-6">
|
||||
<a
|
||||
href="https://rizaldy.club"
|
||||
className="hover:opacity-70"
|
||||
target="_blank"
|
||||
rel="noopener noreferer"
|
||||
>
|
||||
— faultables
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default About;
|
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
42
remoteOptimizedImages.js
Normal file
42
remoteOptimizedImages.js
Normal file
@ -0,0 +1,42 @@
|
||||
const config = require("./config.json");
|
||||
|
||||
const minio = require("minio");
|
||||
const xml2json = require("xml2json");
|
||||
|
||||
const minioEndpoint = process.env.MINIO_ENDPOINT;
|
||||
const minioBucket = process.env.MINIO_BUCKET;
|
||||
|
||||
const mc = new minio.Client({
|
||||
endPoint: minioEndpoint,
|
||||
accessKey: process.env.MINIO_ACCESS_KEY,
|
||||
secretKey: process.env.MINIO_SECRET_KEY,
|
||||
});
|
||||
|
||||
const minioObjects = new Promise((resolve, reject) => {
|
||||
const data = [];
|
||||
|
||||
const stream = mc.listObjectsV2(minioBucket, "photos", true, "");
|
||||
|
||||
stream.on("data", (obj) => data.push(obj.name));
|
||||
stream.on("error", reject);
|
||||
stream.on("end", () => {
|
||||
resolve(data);
|
||||
});
|
||||
}).then((data) => {
|
||||
return data.map((id) => `https://${minioEndpoint}/${minioBucket}/${id}`);
|
||||
});
|
||||
|
||||
const getPeertubeFeeds = fetch(process.env.PEERTUBE_FEED_URL)
|
||||
.then((res) => res.text())
|
||||
.then((payload) => {
|
||||
const peertubeFeedsJSON = xml2json.toJson(payload, { object: true });
|
||||
const peertubeItems = peertubeFeedsJSON.rss.channel.item;
|
||||
|
||||
const videos = peertubeItems.map((item) => item["media:thumbnail"][0].url);
|
||||
|
||||
return videos;
|
||||
});
|
||||
|
||||
module.exports = Promise.all([minioObjects, getPeertubeFeeds]).then((data) => {
|
||||
return [...data[0], ...data[1], config.navbar.logo, config.profile.avatar];
|
||||
});
|
File diff suppressed because one or more lines are too long
22
styles/globals.css
Normal file
22
styles/globals.css
Normal file
@ -0,0 +1,22 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #000000;
|
||||
}
|
||||
}
|
||||
|
||||
/* Somehow tailwind doesn't include this??? */
|
||||
.aspect-photos {
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
|
||||
.aspect-videos {
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
9
tailwind.config.js
Normal file
9
tailwind.config.js
Normal file
@ -0,0 +1,9 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
mode: "jit",
|
||||
content: ["./pages/**/*.js", "./components/**/*.js"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
Loading…
Reference in New Issue
Block a user