diff --git a/README.md b/README.md index 70a5355a..db514c4b 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Everything is designed and engineered for my own use case, following my taste an - 🪟 Compose window pop-out/in - 🌗 Light/dark/auto theme - 🔔 Grouped notifications +- 🪺 Nested replies view ## Design decisions diff --git a/index.html b/index.html index 8241283a..7d0514d4 100644 --- a/index.html +++ b/index.html @@ -28,385 +28,6 @@ content="#242526" media="(prefers-color-scheme: dark)" /> -
diff --git a/package-lock.json b/package-lock.json index f49c1b20..904ec235 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "preact-router": "~4.1.0", "react-intersection-observer": "~9.4.1", "string-length": "~5.0.1", + "swiped-events": "~1.1.7", "toastify-js": "~1.12.0", "use-resize-observer": "~9.1.0", "valtio": "~1.8.0" @@ -32,6 +33,7 @@ "postcss-dark-theme-class": "~0.7.3", "twitter-text": "~3.1.0", "vite": "~4.0.3", + "vite-plugin-html-config": "~1.0.11", "vite-plugin-html-env": "~1.2.7", "vite-plugin-pwa": "~0.14.0", "workbox-cacheable-response": "~6.5.4", @@ -5040,6 +5042,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swiped-events": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/swiped-events/-/swiped-events-1.1.7.tgz", + "integrity": "sha512-bxEy7djwuLykZpPfoE4IFsbna/ngACEpyPqw9tBOaPQtAshsRK7H5CxoCgSXr0QRQ+7rd2TT3bSKLL3R6xJFwg==" + }, "node_modules/temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", @@ -5407,6 +5414,18 @@ } } }, + "node_modules/vite-plugin-html-config": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/vite-plugin-html-config/-/vite-plugin-html-config-1.0.11.tgz", + "integrity": "sha512-hUybhgI+/LQQ5q6xoMMsTvI4PBuQD/Wv6Z1vtDPVWjanS8weCIexXuLLYNGD/93f0v8W2hpNfXpmxgpZMahJ0g==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "vite": ">=2.0.0" + } + }, "node_modules/vite-plugin-html-env": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/vite-plugin-html-env/-/vite-plugin-html-env-1.2.7.tgz", @@ -9382,6 +9401,11 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "devOptional": true }, + "swiped-events": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/swiped-events/-/swiped-events-1.1.7.tgz", + "integrity": "sha512-bxEy7djwuLykZpPfoE4IFsbna/ngACEpyPqw9tBOaPQtAshsRK7H5CxoCgSXr0QRQ+7rd2TT3bSKLL3R6xJFwg==" + }, "temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", @@ -9610,6 +9634,13 @@ "rollup": "^3.7.0" } }, + "vite-plugin-html-config": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/vite-plugin-html-config/-/vite-plugin-html-config-1.0.11.tgz", + "integrity": "sha512-hUybhgI+/LQQ5q6xoMMsTvI4PBuQD/Wv6Z1vtDPVWjanS8weCIexXuLLYNGD/93f0v8W2hpNfXpmxgpZMahJ0g==", + "dev": true, + "requires": {} + }, "vite-plugin-html-env": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/vite-plugin-html-env/-/vite-plugin-html-env-1.2.7.tgz", diff --git a/package.json b/package.json index 52a59f00..6cbdcb8a 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "preact-router": "~4.1.0", "react-intersection-observer": "~9.4.1", "string-length": "~5.0.1", + "swiped-events": "~1.1.7", "toastify-js": "~1.12.0", "use-resize-observer": "~9.1.0", "valtio": "~1.8.0" @@ -34,6 +35,7 @@ "postcss-dark-theme-class": "~0.7.3", "twitter-text": "~3.1.0", "vite": "~4.0.3", + "vite-plugin-html-config": "~1.0.11", "vite-plugin-html-env": "~1.2.7", "vite-plugin-pwa": "~0.14.0", "workbox-cacheable-response": "~6.5.4", diff --git a/rollbar.js b/rollbar.js new file mode 100644 index 00000000..2449c5f1 --- /dev/null +++ b/rollbar.js @@ -0,0 +1,418 @@ +var isDev = /dev\./.test(window.location.hostname); +var _rollbarConfig = { + accessToken: 'ec3e07829d324a29abf6c83472a9740d', + captureUncaught: true, + captureUnhandledRejections: true, + enabled: isDev, + hostSafeList: ['dev.phanpy.social', 'phanpy.social'], + payload: { + environment: isDev ? 'development' : 'production', + }, +}; + +// Rollbar Snippet +!(function (r) { + var e = {}; + function o(n) { + if (e[n]) return e[n].exports; + var t = (e[n] = { i: n, l: !1, exports: {} }); + return r[n].call(t.exports, t, t.exports, o), (t.l = !0), t.exports; + } + (o.m = r), + (o.c = e), + (o.d = function (r, e, n) { + o.o(r, e) || Object.defineProperty(r, e, { enumerable: !0, get: n }); + }), + (o.r = function (r) { + 'undefined' != typeof Symbol && + Symbol.toStringTag && + Object.defineProperty(r, Symbol.toStringTag, { value: 'Module' }), + Object.defineProperty(r, '__esModule', { value: !0 }); + }), + (o.t = function (r, e) { + if ((1 & e && (r = o(r)), 8 & e)) return r; + if (4 & e && 'object' == typeof r && r && r.__esModule) return r; + var n = Object.create(null); + if ( + (o.r(n), + Object.defineProperty(n, 'default', { enumerable: !0, value: r }), + 2 & e && 'string' != typeof r) + ) + for (var t in r) + o.d( + n, + t, + function (e) { + return r[e]; + }.bind(null, t), + ); + return n; + }), + (o.n = function (r) { + var e = + r && r.__esModule + ? function () { + return r.default; + } + : function () { + return r; + }; + return o.d(e, 'a', e), e; + }), + (o.o = function (r, e) { + return Object.prototype.hasOwnProperty.call(r, e); + }), + (o.p = ''), + o((o.s = 0)); +})([ + function (r, e, o) { + 'use strict'; + var n = o(1), + t = o(5); + (_rollbarConfig = _rollbarConfig || {}), + (_rollbarConfig.rollbarJsUrl = + _rollbarConfig.rollbarJsUrl || + 'https://cdn.rollbar.com/rollbarjs/refs/tags/v2.26.0/rollbar.min.js'), + (_rollbarConfig.async = + void 0 === _rollbarConfig.async || _rollbarConfig.async); + var a = n.setupShim(window, _rollbarConfig), + l = t(_rollbarConfig); + (window.rollbar = n.Rollbar), + a.loadFull(window, document, !_rollbarConfig.async, _rollbarConfig, l); + }, + function (r, e, o) { + 'use strict'; + var n = o(2), + t = o(3); + function a(r) { + return function () { + try { + return r.apply(this, arguments); + } catch (r) { + try { + console.error('[Rollbar]: Internal error', r); + } catch (r) {} + } + }; + } + var l = 0; + function i(r, e) { + (this.options = r), (this._rollbarOldOnError = null); + var o = l++; + (this.shimId = function () { + return o; + }), + 'undefined' != typeof window && + window._rollbarShims && + (window._rollbarShims[o] = { handler: e, messages: [] }); + } + var s = o(4), + d = function (r, e) { + return new i(r, e); + }, + c = function (r) { + return new s(d, r); + }; + function u(r) { + return a(function () { + var e = this, + o = Array.prototype.slice.call(arguments, 0), + n = { shim: e, method: r, args: o, ts: new Date() }; + window._rollbarShims[this.shimId()].messages.push(n); + }); + } + (i.prototype.loadFull = function (r, e, o, n, t) { + var l = !1, + i = e.createElement('script'), + s = e.getElementsByTagName('script')[0], + d = s.parentNode; + (i.crossOrigin = ''), + (i.src = n.rollbarJsUrl), + o || (i.async = !0), + (i.onload = i.onreadystatechange = + a(function () { + if ( + !( + l || + (this.readyState && + 'loaded' !== this.readyState && + 'complete' !== this.readyState) + ) + ) { + i.onload = i.onreadystatechange = null; + try { + d.removeChild(i); + } catch (r) {} + (l = !0), + (function () { + var e; + if (void 0 === r._rollbarDidLoad) { + e = new Error('rollbar.js did not load'); + for (var o, n, a, l, i = 0; (o = r._rollbarShims[i++]); ) + for (o = o.messages || []; (n = o.shift()); ) + for (a = n.args || [], i = 0; i < a.length; ++i) + if ('function' == typeof (l = a[i])) { + l(e); + break; + } + } + 'function' == typeof t && t(e); + })(); + } + })), + d.insertBefore(i, s); + }), + (i.prototype.wrap = function (r, e, o) { + try { + var n; + if ( + ((n = + 'function' == typeof e + ? e + : function () { + return e || {}; + }), + 'function' != typeof r) + ) + return r; + if (r._isWrap) return r; + if ( + !r._rollbar_wrapped && + ((r._rollbar_wrapped = function () { + o && 'function' == typeof o && o.apply(this, arguments); + try { + return r.apply(this, arguments); + } catch (o) { + var e = o; + throw ( + (e && + ('string' == typeof e && (e = new String(e)), + (e._rollbarContext = n() || {}), + (e._rollbarContext._wrappedSource = r.toString()), + (window._rollbarWrappedError = e)), + e) + ); + } + }), + (r._rollbar_wrapped._isWrap = !0), + r.hasOwnProperty) + ) + for (var t in r) + r.hasOwnProperty(t) && (r._rollbar_wrapped[t] = r[t]); + return r._rollbar_wrapped; + } catch (e) { + return r; + } + }); + for ( + var p = + 'log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleAnonymousErrors,handleUnhandledRejection,captureEvent,captureDomContentLoaded,captureLoad'.split( + ',', + ), + f = 0; + f < p.length; + ++f + ) + i.prototype[p[f]] = u(p[f]); + r.exports = { + setupShim: function (r, e) { + if (r) { + var o = e.globalAlias || 'Rollbar'; + if ('object' == typeof r[o]) return r[o]; + (r._rollbarShims = {}), (r._rollbarWrappedError = null); + var l = new c(e); + return a(function () { + e.captureUncaught && + ((l._rollbarOldOnError = r.onerror), + n.captureUncaughtExceptions(r, l, !0), + e.wrapGlobalEventHandlers && t(r, l, !0)), + e.captureUnhandledRejections && + n.captureUnhandledRejections(r, l, !0); + var a = e.autoInstrument; + return ( + !1 !== e.enabled && + (void 0 === a || + !0 === a || + (function (r) { + return !( + 'object' != typeof r || + (void 0 !== r.page && !r.page) + ); + })(a)) && + r.addEventListener && + (r.addEventListener('load', l.captureLoad.bind(l)), + r.addEventListener( + 'DOMContentLoaded', + l.captureDomContentLoaded.bind(l), + )), + (r[o] = l), + l + ); + })(); + } + }, + Rollbar: c, + }; + }, + function (r, e, o) { + 'use strict'; + function n(r, e, o, n) { + r._rollbarWrappedError && + (n[4] || (n[4] = r._rollbarWrappedError), + n[5] || (n[5] = r._rollbarWrappedError._rollbarContext), + (r._rollbarWrappedError = null)); + var t = e.handleUncaughtException.apply(e, n); + o && o.apply(r, n), 'anonymous' === t && (e.anonymousErrorsPending += 1); + } + r.exports = { + captureUncaughtExceptions: function (r, e, o) { + if (r) { + var t; + if ('function' == typeof e._rollbarOldOnError) + t = e._rollbarOldOnError; + else if (r.onerror) { + for (t = r.onerror; t._rollbarOldOnError; ) + t = t._rollbarOldOnError; + e._rollbarOldOnError = t; + } + e.handleAnonymousErrors(); + var a = function () { + var o = Array.prototype.slice.call(arguments, 0); + n(r, e, t, o); + }; + o && (a._rollbarOldOnError = t), (r.onerror = a); + } + }, + captureUnhandledRejections: function (r, e, o) { + if (r) { + 'function' == typeof r._rollbarURH && + r._rollbarURH.belongsToShim && + r.removeEventListener('unhandledrejection', r._rollbarURH); + var n = function (r) { + var o, n, t; + try { + o = r.reason; + } catch (r) { + o = void 0; + } + try { + n = r.promise; + } catch (r) { + n = '[unhandledrejection] error getting `promise` from event'; + } + try { + (t = r.detail), !o && t && ((o = t.reason), (n = t.promise)); + } catch (r) {} + o || (o = '[unhandledrejection] error getting `reason` from event'), + e && + e.handleUnhandledRejection && + e.handleUnhandledRejection(o, n); + }; + (n.belongsToShim = o), + (r._rollbarURH = n), + r.addEventListener('unhandledrejection', n); + } + }, + }; + }, + function (r, e, o) { + 'use strict'; + function n(r, e, o) { + if (e.hasOwnProperty && e.hasOwnProperty('addEventListener')) { + for (var n = e.addEventListener; n._rollbarOldAdd && n.belongsToShim; ) + n = n._rollbarOldAdd; + var t = function (e, o, t) { + n.call(this, e, r.wrap(o), t); + }; + (t._rollbarOldAdd = n), (t.belongsToShim = o), (e.addEventListener = t); + for ( + var a = e.removeEventListener; + a._rollbarOldRemove && a.belongsToShim; + + ) + a = a._rollbarOldRemove; + var l = function (r, e, o) { + a.call(this, r, (e && e._rollbar_wrapped) || e, o); + }; + (l._rollbarOldRemove = a), + (l.belongsToShim = o), + (e.removeEventListener = l); + } + } + r.exports = function (r, e, o) { + if (r) { + var t, + a, + l = + 'EventTarget,Window,Node,ApplicationCache,AudioTrackList,ChannelMergerNode,CryptoOperation,EventSource,FileReader,HTMLUnknownElement,IDBDatabase,IDBRequest,IDBTransaction,KeyOperation,MediaController,MessagePort,ModalWindow,Notification,SVGElementInstance,Screen,TextTrack,TextTrackCue,TextTrackList,WebSocket,WebSocketWorker,Worker,XMLHttpRequest,XMLHttpRequestEventTarget,XMLHttpRequestUpload'.split( + ',', + ); + for (t = 0; t < l.length; ++t) + r[(a = l[t])] && r[a].prototype && n(e, r[a].prototype, o); + } + }; + }, + function (r, e, o) { + 'use strict'; + function n(r, e) { + (this.impl = r(e, this)), + (this.options = e), + (function (r) { + for ( + var e = function (r) { + return function () { + var e = Array.prototype.slice.call(arguments, 0); + if (this.impl[r]) return this.impl[r].apply(this.impl, e); + }; + }, + o = + 'log,debug,info,warn,warning,error,critical,global,configure,handleUncaughtException,handleAnonymousErrors,handleUnhandledRejection,_createItem,wrap,loadFull,shimId,captureEvent,captureDomContentLoaded,captureLoad'.split( + ',', + ), + n = 0; + n < o.length; + n++ + ) + r[o[n]] = e(o[n]); + })(n.prototype); + } + (n.prototype._swapAndProcessMessages = function (r, e) { + var o, n, t; + for (this.impl = r(this.options); (o = e.shift()); ) + (n = o.method), + (t = o.args), + this[n] && + 'function' == typeof this[n] && + ('captureDomContentLoaded' === n || 'captureLoad' === n + ? this[n].apply(this, [t[0], o.ts]) + : this[n].apply(this, t)); + return this; + }), + (r.exports = n); + }, + function (r, e, o) { + 'use strict'; + r.exports = function (r) { + return function (e) { + if (!e && !window._rollbarInitialized) { + for ( + var o, + n, + t = (r = r || {}).globalAlias || 'Rollbar', + a = window.rollbar, + l = function (r) { + return new a(r); + }, + i = 0; + (o = window._rollbarShims[i++]); + + ) + n || (n = o.handler), + o.handler._swapAndProcessMessages(l, o.messages); + (window[t] = n), (window._rollbarInitialized = !0); + } + }; + }; + }, +]); +// End Rollbar Snippet diff --git a/src/app.css b/src/app.css index 759e8892..7013621d 100644 --- a/src/app.css +++ b/src/app.css @@ -79,6 +79,7 @@ a.mention span { position: sticky; top: 0; background-color: var(--bg-blur-color); + background-image: linear-gradient(to bottom, var(--bg-color), transparent); backdrop-filter: saturate(180%) blur(20px); border-bottom: 1px solid var(--divider-color); z-index: 1; @@ -455,6 +456,7 @@ a.mention span { /* scroll-behavior: smooth; */ scrollbar-width: none; overscroll-behavior: contain; + touch-action: pan-x; } .carousel::-webkit-scrollbar { display: none; @@ -476,9 +478,11 @@ a.mention span { height: auto; max-height: 100vh; max-height: 100dvh; + vertical-align: middle; } .carousel > * video { min-height: 80px; + max-height: 80vh; /* prevent other UI elements from obscuring video */ } .carousel-top-controls { @@ -502,6 +506,9 @@ a.mention span { text-align: center; pointer-events: none; } +:is(.carousel-top-controls, .carousel-controls)[hidden] { + opacity: 0; +} button.carousel-button, button.carousel-dot { @@ -546,6 +553,9 @@ button.carousel-dot[disabled].active { transform: translateY(100%); transition: transform 0.2s ease-in-out; } + :is(.carousel-top-controls, .carousel-controls)[hidden] { + opacity: 1; + } .carousel:hover + .carousel-top-controls, .carousel:hover + .carousel-top-controls + .carousel-controls, .carousel-top-controls:hover, @@ -761,6 +771,9 @@ meter.donut:is(.danger, .explode):after { box-shadow: 0 3px 8px -1px var(--bg-faded-blur-color), 0 10px 36px -4px var(--button-bg-blur-color); } +.toastify-bottom { + margin-bottom: env(safe-area-inset-bottom); +} :root .toastify:hover { filter: brightness(1.2); } @@ -792,6 +805,11 @@ meter.donut:is(.danger, .explode):after { min-height: 6em; border-bottom: 0; background-color: var(--bg-faded-blur-color); + background-image: linear-gradient( + to bottom, + var(--bg-faded-color), + transparent 50% + ); border-bottom: 0; mask-image: linear-gradient( rgba(0, 0, 0, 1) 50%, diff --git a/src/app.jsx b/src/app.jsx index cfe88e54..adf5578f 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -384,18 +384,20 @@ export function App() { states.showCompose = false; if (newStatus) { states.reloadStatusPage++; - const toast = Toastify({ - text: 'Status posted. Check it out.', - duration: 10_000, // 10 seconds - gravity: 'bottom', - position: 'center', - // destination: `/#/s/${newStatus.id}`, - onClick: () => { - toast.hideToast(); - route(`/s/${newStatus.id}`); - }, - }); - toast.showToast(); + setTimeout(() => { + const toast = Toastify({ + text: 'Status posted. Check it out.', + duration: 10_000, // 10 seconds + gravity: 'bottom', + position: 'center', + // destination: `/#/s/${newStatus.id}`, + onClick: () => { + toast.hideToast(); + route(`/s/${newStatus.id}`); + }, + }); + toast.showToast(); + }, 1000); } }} /> diff --git a/src/components/account.jsx b/src/components/account.jsx index cd36f994..123d2b6e 100644 --- a/src/components/account.jsx +++ b/src/components/account.jsx @@ -198,7 +198,7 @@ function Account({ account }) {