diff --git a/package-lock.json b/package-lock.json index dd11a14..39e3277 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,9 @@ "name": "phanpy", "version": "0.1.0", "dependencies": { - "@github/relative-time-element": "~4.1.5", "@github/text-expander-element": "~2.3.0", + "dayjs": "~1.11.7", + "dayjs-twitter": "~0.5.0", "fast-blurhash": "~1.1.2", "history": "~5.3.0", "iconify-icon": "~1.0.2", @@ -2062,11 +2063,6 @@ "resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.1.5.tgz", "integrity": "sha512-dmG1PuppNKHnBBEcfylWDwj9SSxd/E/qd8mC1G/klQC3s7ps5q6JZ034mwkkG0LKfI+Y+UgEua/ROD776N400w==" }, - "node_modules/@github/relative-time-element": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.1.5.tgz", - "integrity": "sha512-WAf1EQV5Sn6jGuAIQur/ztKlEV9R+VHDNwqEbeaOb6s9fiwM5z7+ujlWNZtgFkDp3lF0H8D/f0vdiPlfHz0ZTQ==" - }, "node_modules/@github/text-expander-element": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@github/text-expander-element/-/text-expander-element-2.3.0.tgz", @@ -3001,6 +2997,19 @@ "node": ">=8" } }, + "node_modules/dayjs": { + "version": "1.11.7", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz", + "integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==" + }, + "node_modules/dayjs-twitter": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/dayjs-twitter/-/dayjs-twitter-0.5.0.tgz", + "integrity": "sha512-SZ7qEUISstBLUXdlGAbLrwr6zfRM9kaCfbq4uVTerM/HXzuHiiGzzUqAJVhxt+3tf69E+ocmQdP6YYpOINv05w==", + "dependencies": { + "duration-js": "^4.0.0" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3060,6 +3069,11 @@ "tslib": "^2.0.3" } }, + "node_modules/duration-js": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/duration-js/-/duration-js-4.0.0.tgz", + "integrity": "sha512-qoXjOsH97r+NrOa6sK5V2cwBOouVG/LI9jwgwKvjVkyqGpZ72yilWjjzFJYPqqbvNZDwpRMaLEUFE+PTefvOEA==" + }, "node_modules/ejs": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", @@ -7183,11 +7197,6 @@ "resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.1.5.tgz", "integrity": "sha512-dmG1PuppNKHnBBEcfylWDwj9SSxd/E/qd8mC1G/klQC3s7ps5q6JZ034mwkkG0LKfI+Y+UgEua/ROD776N400w==" }, - "@github/relative-time-element": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.1.5.tgz", - "integrity": "sha512-WAf1EQV5Sn6jGuAIQur/ztKlEV9R+VHDNwqEbeaOb6s9fiwM5z7+ujlWNZtgFkDp3lF0H8D/f0vdiPlfHz0ZTQ==" - }, "@github/text-expander-element": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@github/text-expander-element/-/text-expander-element-2.3.0.tgz", @@ -7925,6 +7934,19 @@ "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", "dev": true }, + "dayjs": { + "version": "1.11.7", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz", + "integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==" + }, + "dayjs-twitter": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/dayjs-twitter/-/dayjs-twitter-0.5.0.tgz", + "integrity": "sha512-SZ7qEUISstBLUXdlGAbLrwr6zfRM9kaCfbq4uVTerM/HXzuHiiGzzUqAJVhxt+3tf69E+ocmQdP6YYpOINv05w==", + "requires": { + "duration-js": "^4.0.0" + } + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -7964,6 +7986,11 @@ "tslib": "^2.0.3" } }, + "duration-js": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/duration-js/-/duration-js-4.0.0.tgz", + "integrity": "sha512-qoXjOsH97r+NrOa6sK5V2cwBOouVG/LI9jwgwKvjVkyqGpZ72yilWjjzFJYPqqbvNZDwpRMaLEUFE+PTefvOEA==" + }, "ejs": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", diff --git a/package.json b/package.json index ec25be1..0451e33 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,9 @@ "source-map-explorer": "npx source-map-explorer dist/assets/*.js" }, "dependencies": { - "@github/relative-time-element": "~4.1.5", "@github/text-expander-element": "~2.3.0", + "dayjs": "~1.11.7", + "dayjs-twitter": "~0.5.0", "fast-blurhash": "~1.1.2", "history": "~5.3.0", "iconify-icon": "~1.0.2", diff --git a/src/components/relative-time.jsx b/src/components/relative-time.jsx new file mode 100644 index 0000000..7d8c7a1 --- /dev/null +++ b/src/components/relative-time.jsx @@ -0,0 +1,57 @@ +// Twitter-style relative time component +// Seconds = 1s +// Minutes = 1m +// Hours = 1h +// Days = 1d +// After 7 days, use DD/MM/YYYY or MM/DD/YYYY +import dayjs from 'dayjs'; +import dayjsTwitter from 'dayjs-twitter'; +import localizedFormat from 'dayjs/plugin/localizedFormat'; +import relativeTime from 'dayjs/plugin/relativeTime'; +import { useEffect, useState } from 'preact/hooks'; + +dayjs.extend(dayjsTwitter); +dayjs.extend(localizedFormat); +dayjs.extend(relativeTime); + +const dtf = new Intl.DateTimeFormat(); + +export default function RelativeTime({ datetime, format }) { + if (!datetime) return null; + const date = dayjs(datetime); + const [dateStr, setDateStr] = useState(''); + + useEffect(() => { + let timer, raf; + const update = () => { + raf = requestAnimationFrame(() => { + let str; + if (format === 'micro') { + // If date <= 7 days + if (date.diff(dayjs(), 'day') >= -7) { + str = date.twitter(); + } else { + // If date > 7 days + str = dtf.format(date.toDate()); + } + } else { + str = date.fromNow(); + } + setDateStr(str); + + timer = setTimeout(update, 30_000); + }); + }; + raf = requestAnimationFrame(update); + return () => { + clearTimeout(timer); + cancelAnimationFrame(raf); + }; + }, [date]); + + return ( + + ); +} diff --git a/src/components/status.jsx b/src/components/status.jsx index ce555c4..a23e086 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -28,6 +28,7 @@ import visibilityIconsMap from '../utils/visibility-icons-map'; import Avatar from './avatar'; import Icon from './icon'; +import RelativeTime from './relative-time'; function fetchAccount(id) { return masto.v1.accounts.fetch(id); @@ -253,14 +254,7 @@ function Status({ alt={visibility} size="s" />{' '} - - {createdAtDate.toLocaleString()} - + ) : ( @@ -269,14 +263,7 @@ function Status({ alt={visibility} size="s" />{' '} - - {createdAtDate.toLocaleString()} - + ))} @@ -1136,9 +1123,7 @@ function Poll({ poll, lang, readOnly, onUpdate = () => {} }) { )}{' '} • {expired ? 'Ended' : 'Ending'}{' '} - {!!expiresAtDate && ( - - )} + {!!expiresAtDate && }

)} diff --git a/src/compose.jsx b/src/compose.jsx index c9dbe4c..757502e 100644 --- a/src/compose.jsx +++ b/src/compose.jsx @@ -2,7 +2,6 @@ import './index.css'; import './app.css'; -import '@github/relative-time-element'; import { login } from 'masto'; import { render } from 'preact'; import { useEffect, useState } from 'preact/hooks'; diff --git a/src/main.jsx b/src/main.jsx index 74bdd11..ad1d5cb 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,6 +1,5 @@ import './index.css'; -import '@github/relative-time-element'; import { render } from 'preact'; import { App } from './app'; diff --git a/src/pages/notifications.jsx b/src/pages/notifications.jsx index 8ea2acd..378aca7 100644 --- a/src/pages/notifications.jsx +++ b/src/pages/notifications.jsx @@ -8,6 +8,7 @@ import Avatar from '../components/avatar'; import Icon from '../components/icon'; import Loader from '../components/loader'; import NameText from '../components/name-text'; +import RelativeTime from '../components/relative-time'; import Status from '../components/status'; import states from '../utils/states'; import store from '../utils/store'; @@ -102,11 +103,9 @@ function Notification({ notification }) { {' '} •{' '} - )} diff --git a/src/pages/settings.jsx b/src/pages/settings.jsx index 48c6e49..fbc6ef3 100644 --- a/src/pages/settings.jsx +++ b/src/pages/settings.jsx @@ -5,6 +5,7 @@ import { useRef, useState } from 'preact/hooks'; import Avatar from '../components/avatar'; import Icon from '../components/icon'; import NameText from '../components/name-text'; +import RelativeTime from '../components/relative-time'; import states from '../utils/states'; import store from '../utils/store'; @@ -196,8 +197,7 @@ function Settings({ onClose }) {

{__BUILD_TIME__ && (

- Last build:{' '} - {' '} + Last build: {' '} {__COMMIT_HASH__ && ( <> ( diff --git a/src/pages/status.jsx b/src/pages/status.jsx index 63aa3dd..ef0b868 100644 --- a/src/pages/status.jsx +++ b/src/pages/status.jsx @@ -17,6 +17,7 @@ import { useSnapshot } from 'valtio'; import Icon from '../components/icon'; import Loader from '../components/loader'; import NameText from '../components/name-text'; +import RelativeTime from '../components/relative-time'; import Status from '../components/status'; import htmlContentLength from '../utils/html-content-length'; import shortenNumber from '../utils/shorten-number'; @@ -325,11 +326,9 @@ function StatusPage({ id }) { {' '} •{' '} -