diff --git a/package-lock.json b/package-lock.json index ad6ab43..28bc836 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "hyperspace", - "version": "1.0.4", + "version": "1.1.0-beta1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1055,13 +1055,23 @@ } }, "@material-ui/icons": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-3.0.2.tgz", - "integrity": "sha512-QY/3gJnObZQ3O/e6WjH+0ah2M3MOgLOzCy8HTUoUx9B6dDrS18vP7Ycw3qrDEKlB6q1KNxy6CZHm5FCauWGy2g==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.5.1.tgz", + "integrity": "sha512-YZ/BgJbXX4a0gOuKWb30mBaHaoXRqPanlePam83JQPZ/y4kl+3aW0Wv9tlR70hB5EGAkEJGW5m4ktJwMgxQAeA==", "dev": true, "requires": { - "@babel/runtime": "^7.2.0", - "recompose": "0.28.0 - 0.30.0" + "@babel/runtime": "^7.4.4" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.2.tgz", + "integrity": "sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + } + } } }, "@material-ui/system": { @@ -9451,9 +9461,9 @@ "dev": true }, "handlebars": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.4.0.tgz", - "integrity": "sha512-xkRtOt3/3DzTKMOt3xahj2M/EqNhY988T+imYSlMgs5fVhLN2fmKVVj0LtEGmb+3UUYV5Qmm1052Mm3dIQxOvw==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz", + "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==", "dev": true, "requires": { "neo-async": "^2.6.0", @@ -10438,9 +10448,9 @@ } }, "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, "ip": { @@ -11838,12 +11848,12 @@ "integrity": "sha512-u93kb2fPbIrfzBuLjZE+w+fJbUUMhNDXxNmMfaqNgpfQf1CO5ZSe2LfsnBqVAk7i/2NF48OSoRj+Xe2VT+lE8Q==" }, "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { - "invert-kv": "^1.0.0" + "invert-kv": "^2.0.0" } }, "left-pad": { @@ -12265,12 +12275,22 @@ } }, "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + } } }, "memory-fs": { @@ -13236,14 +13256,40 @@ "dev": true }, "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + }, + "dependencies": { + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } } }, "os-tmpdir": { @@ -19861,9 +19907,9 @@ } }, "typescript": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.1.tgz", - "integrity": "sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz", + "integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==", "dev": true }, "ua-parser-js": { @@ -21425,16 +21471,16 @@ "dev": true }, "yargs": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", - "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.1.tgz", + "integrity": "sha512-PRU7gJrJaXv3q3yQZ/+/X6KBswZiaQ+zOmdprZcouPYtQgvNU35i+68M4b1ZHLZtYFT5QObFLV+ZkmJYcwKdiw==", "dev": true, "requires": { "cliui": "^4.0.0", "decamelize": "^1.1.1", "find-up": "^2.1.0", "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", + "os-locale": "^3.1.0", "require-directory": "^2.1.1", "require-main-filename": "^1.0.1", "set-blocking": "^2.0.0", diff --git a/package.json b/package.json index d195ba7..f1b3b37 100644 --- a/package.json +++ b/package.json @@ -1,114 +1,114 @@ { - "name": "hyperspace", - "productName": "Hyperspace Desktop", - "version": "1.0.4", - "description": "A beautiful, fluffy client for the fediverse", - "author": "Marquis Kurt ", - "repository": "https://github.com/hyperspacedev/hyperspace.git", - "private": true, - "homepage": "./", - "devDependencies": { - "@date-io/moment": "^1.3.11", - "@material-ui/core": "^3.9.3", - "@material-ui/icons": "^3.0.2", - "@types/emoji-mart": "^2.11.0", - "@types/jest": "^24.0.18", - "@types/node": "11.11.6", - "@types/react": "16.8.8", - "@types/react-dom": "16.8.3", - "@types/react-router-dom": "^4.3.5", - "@types/react-swipeable-views": "latest", - "axios": "^0.19.0", - "electron": "^6.0.11", - "electron-builder": "^21.2.0", - "emoji-mart": "^2.11.1", - "file-dialog": "^0.0.7", - "material-ui-pickers": "^2.2.4", - "mdi-material-ui": "^5.18.0", - "megalodon": "^0.6.4", - "moment": "^2.24.0", - "notistack": "^0.5.1", - "prettier": "1.18.2", - "query-string": "^6.8.3", - "react": "^16.10.2", - "react-dom": "^16.10.2", - "react-router-dom": "^5.1.2", - "react-scripts": "^2.1.8", - "react-swipeable-views": "^0.13.3", - "react-web-share-api": "^0.0.2", - "typescript": "3.4.1" - }, - "dependencies": { - "electron-notarize": "^0.1.1", - "electron-updater": "^4.1.2", - "electron-window-state": "^5.0.3" - }, - "main": "public/electron.js", - "scripts": { - "start": "HTTPS=true react-scripts start", - "electrify": "npm run build; electron .", - "electrify-nobuild": "electron .", - "build": "react-scripts build", - "create-mac-icon": "cd desktop; iconutil -c icns app.iconset; cd ..", - "build-desktop": "npm run build; npm run create-mac-icon; electron-builder -p 'never' -mwl deb AppImage snap", - "build-desktop-win": "electron-builder -p 'never' -w", - "build-desktop-darwin": "npm run create-mac-icon; electron-builder -p 'never' -m", - "build-desktop-darwin-nosign": "npm run create-mac-icon; electron-builder -p 'never' -m dmg -c.mac.identity=null -c.afterSign=\"desktop/donothing.js\"", - "build-desktop-linux": "electron-builder -p 'never' -l deb AppImage snap", - "build-desktop-linux-select": "electron-builder -p 'never' -l ", - "check-prettier": "prettier --check src/**/**.tsx", - "test": "react-scripts test", - "eject": "react-scripts eject" - }, - "eslintConfig": { - "extends": "react-app" - }, - "browserslist": [ - ">0.2%", - "not dead", - "not ie <= 11", - "not op_mini all" - ], - "build": { - "appId": "net.marquiskurt.hyperspace", - "afterSign": "desktop/notarize.js", - "directories": { - "buildResources": "desktop" + "name": "hyperspace", + "productName": "Hyperspace Desktop", + "version": "1.1.0-beta1", + "description": "A beautiful, fluffy client for the fediverse", + "author": "Marquis Kurt ", + "repository": "https://github.com/hyperspacedev/hyperspace.git", + "private": true, + "homepage": "./", + "devDependencies": { + "@date-io/moment": "^1.3.11", + "@material-ui/core": "^3.9.3", + "@material-ui/icons": "^4.5.1", + "@types/emoji-mart": "^2.11.0", + "@types/jest": "^24.0.18", + "@types/node": "11.11.6", + "@types/react": "16.8.8", + "@types/react-dom": "16.8.3", + "@types/react-router-dom": "^4.3.5", + "@types/react-swipeable-views": "latest", + "axios": "^0.19.0", + "electron": "^6.0.11", + "electron-builder": "^21.2.0", + "emoji-mart": "^2.11.1", + "file-dialog": "^0.0.7", + "material-ui-pickers": "^2.2.4", + "mdi-material-ui": "^5.18.0", + "megalodon": "^0.6.4", + "moment": "^2.24.0", + "notistack": "^0.5.1", + "prettier": "1.18.2", + "query-string": "^6.8.3", + "react": "^16.10.2", + "react-dom": "^16.10.2", + "react-router-dom": "^5.1.2", + "react-scripts": "^2.1.8", + "react-swipeable-views": "^0.13.3", + "react-web-share-api": "^0.0.2", + "typescript": "^3.7.2" }, - "mac": { - "category": "public.app-category.social-networking", - "icon": "desktop/app.icns", - "target": [ - "dmg", - "mas" - ], - "darkModeSupport": true, - "hardenedRuntime": true + "dependencies": { + "electron-notarize": "^0.1.1", + "electron-updater": "^4.1.2", + "electron-window-state": "^5.0.3" }, - "mas": { - "entitlements": "desktop/entitlements.mas.plist", - "entitlementsInherit": "desktop/entitlements.mas.inherit.plist", - "provisioningProfile": "desktop/embedded.provisionprofile" + "main": "public/electron.js", + "scripts": { + "start": "react-scripts start", + "electrify": "npm run build; electron .", + "electrify-nobuild": "electron .", + "build": "react-scripts build", + "create-mac-icon": "cd desktop; iconutil -c icns app.iconset; cd ..", + "build-desktop": "npm run build; npm run create-mac-icon; electron-builder -p 'never' -mwl deb AppImage snap", + "build-desktop-win": "electron-builder -p 'never' -w", + "build-desktop-darwin": "npm run create-mac-icon; electron-builder -p 'never' -m", + "build-desktop-darwin-nosign": "npm run create-mac-icon; electron-builder -p 'never' -m dmg -c.mac.identity=null -c.afterSign=\"desktop/donothing.js\"", + "build-desktop-linux": "electron-builder -p 'never' -l deb AppImage snap", + "build-desktop-linux-select": "electron-builder -p 'never' -l ", + "check-prettier": "prettier --check src/**/**.tsx", + "test": "react-scripts test", + "eject": "react-scripts eject" }, - "dmg": { - "sign": false + "eslintConfig": { + "extends": "react-app" }, - "win": { - "target": [ - "nsis" - ], - "icon": "desktop/app.ico" - }, - "linux": { - "target": [ - "${@:1}" - ], - "icon": "linux", - "category": "Network" - }, - "snap": { - "confinement": "strict", - "summary": "A beautiful, fluffy client for the fediverse" + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ], + "build": { + "appId": "net.marquiskurt.hyperspace", + "afterSign": "desktop/notarize.js", + "directories": { + "buildResources": "desktop" + }, + "mac": { + "category": "public.app-category.social-networking", + "icon": "desktop/app.icns", + "target": [ + "dmg", + "mas" + ], + "darkModeSupport": true, + "hardenedRuntime": true + }, + "mas": { + "entitlements": "desktop/entitlements.mas.plist", + "entitlementsInherit": "desktop/entitlements.mas.inherit.plist", + "provisioningProfile": "desktop/embedded.provisionprofile" + }, + "dmg": { + "sign": false + }, + "win": { + "target": [ + "nsis" + ], + "icon": "desktop/app.ico" + }, + "linux": { + "target": [ + "${@:1}" + ], + "icon": "linux", + "category": "Network" + }, + "snap": { + "confinement": "strict", + "summary": "A beautiful, fluffy client for the fediverse" + } } - } } diff --git a/public/config.json b/public/config.json index 6877cbb..a7bd8c3 100644 --- a/public/config.json +++ b/public/config.json @@ -1,5 +1,5 @@ { - "version": "1.0.4", + "version": "1.1.0", "location": "https://hyperspaceapp-next.herokuapp.com", "branding": { "name": "Hyperspace", diff --git a/public/electron.js b/public/electron.js index 69ecaec..547afe6 100644 --- a/public/electron.js +++ b/public/electron.js @@ -312,6 +312,14 @@ function createMenubar() { click() { safelyGoTo("hyperspace://hyperspace/app/#/messages") } + }, + { type: 'separator' }, + { + label: 'Activity', + accelerator: 'Alt+CmdOrCtrl+A', + click() { + safelyGoTo("hyperspace://hyperspace/app/#/activity") + } } ] }, @@ -326,7 +334,7 @@ function createMenubar() { } }, { - label: 'Recommendations...', + label: 'Recommendations', accelerator: "Alt+CmdOrCtrl+R", click() { safelyGoTo("hyperspace://hyperspace/app/#/recommended") @@ -340,6 +348,13 @@ function createMenubar() { safelyGoTo("hyperspace://hyperspace/app/#/you") } }, + { + label: 'Follow Requests', + accelerator: "Alt+CmdOrCtrl+E", + click() { + safelyGoTo("hyperspace://hyperspace/app/#/requests") + } + }, { label: 'Blocked Servers', accelerator: "Shift+CmdOrCtrl+B", diff --git a/src/App.tsx b/src/App.tsx index da77bbb..548e87d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,6 +21,8 @@ import RecommendationsPage from "./pages/Recommendations"; import Missingno from "./pages/Missingno"; import Blocked from "./pages/Blocked"; import You from "./pages/You"; +import RequestsPage from "./pages/Requests"; +import ActivityPage from "./pages/Activity"; import { withSnackbar } from "notistack"; import { PrivateRoute } from "./interfaces/overrides"; import { userLoggedIn } from "./utilities/accounts"; @@ -123,6 +125,8 @@ class App extends Component { path="/recommended" component={RecommendationsPage} /> + + ); diff --git a/src/components/AppLayout/AppLayout.tsx b/src/components/AppLayout/AppLayout.tsx index ffba805..a3f89d5 100644 --- a/src/components/AppLayout/AppLayout.tsx +++ b/src/components/AppLayout/AppLayout.tsx @@ -28,6 +28,7 @@ import { ListItem, Tooltip } from "@material-ui/core"; + import MenuIcon from "@material-ui/icons/Menu"; import SearchIcon from "@material-ui/icons/Search"; import NotificationsIcon from "@material-ui/icons/Notifications"; @@ -41,6 +42,9 @@ import InfoIcon from "@material-ui/icons/Info"; import CreateIcon from "@material-ui/icons/Create"; import SupervisedUserCircleIcon from "@material-ui/icons/SupervisedUserCircle"; import ExitToAppIcon from "@material-ui/icons/ExitToApp"; +import TrendingUpIcon from "@material-ui/icons/TrendingUp"; +import BuildIcon from "@material-ui/icons/Build"; + import { styles } from "./AppLayout.styles"; import { MultiAccount, UAccount } from "../../types/Account"; import { @@ -232,6 +236,8 @@ export class AppLayout extends Component { } searchForQuery(what: string) { + what = what.replace(/^#/g, "tag:"); + console.log(what); window.location.href = isDesktopApp() ? "hyperspace://hyperspace/app/index.html#/search?query=" + what : "/#/search?query=" + what; @@ -290,7 +296,11 @@ export class AppLayout extends Component { return (
- 🛠 Careful: you're running in developer mode. + {" "} + Careful: you're running in developer mode.
); @@ -430,7 +440,13 @@ export class AppLayout extends Component { - More + Community + + + + + + { - + + + More @@ -617,6 +635,15 @@ export class AppLayout extends Component { Edit profile + + + Manage follow requests + + + ); + case "audio": + return ; case "gifv": return ( - this.moveForward()} - disabled={ - this.state.currentStep === - this.state.totalSteps - 1 - } - > - Next - - } - backButton={ - - } - /> + {this.state.totalSteps > 1 ? ( + this.moveForward()} + disabled={ + this.state.currentStep === + this.state.totalSteps - 1 + } + > + Next + + } + backButton={ + + } + /> + ) : null} +
{mediaItem.description ? mediaItem.description diff --git a/src/components/AudioPlayer/AudioPlayer.styles.tsx b/src/components/AudioPlayer/AudioPlayer.styles.tsx new file mode 100644 index 0000000..6ac7870 --- /dev/null +++ b/src/components/AudioPlayer/AudioPlayer.styles.tsx @@ -0,0 +1,17 @@ +import { Theme, createStyles } from "@material-ui/core"; + +export const styles = (theme: Theme) => + createStyles({ + root: { + backgroundColor: theme.palette.background.paper, + borderColor: theme.palette.action.disabledBackground, + borderWidth: 1, + borderStyle: "solid" + }, + progressBar: { + width: "100%" + }, + download: { + color: `${theme.palette.action.active} !important` + } + }); diff --git a/src/components/AudioPlayer/AudioPlayer.tsx b/src/components/AudioPlayer/AudioPlayer.tsx new file mode 100644 index 0000000..06ca995 --- /dev/null +++ b/src/components/AudioPlayer/AudioPlayer.tsx @@ -0,0 +1,141 @@ +import React, { Component } from "react"; +import { + Toolbar, + IconButton, + withStyles, + LinearProgress, + Tooltip +} from "@material-ui/core"; +import { LinkableIconButton } from "../../interfaces/overrides"; + +import FastRewindIcon from "@material-ui/icons/FastRewind"; +import FastForwardIcon from "@material-ui/icons/FastForward"; +import PlayArrowIcon from "@material-ui/icons/PlayArrow"; +import PauseIcon from "@material-ui/icons/Pause"; +import CloudDownloadIcon from "@material-ui/icons/CloudDownload"; + +import { styles } from "./AudioPlayer.styles"; + +interface IAudioPlayerProps { + src: string; + id: string; + classes: any; +} + +interface IAudioPlayerState { + src: string; + elementId: string; + playing: boolean; + progress: number; +} + +class AudioPlayer extends Component { + constructor(props: any) { + super(props); + + this.state = { + src: this.props.src, + elementId: "audioplayer-" + this.props.id, + playing: false, + progress: 0 + }; + } + + componentDidMount() { + let audioPlayerElement = this.getAudioPlayer(); + + if (audioPlayerElement) { + audioPlayerElement.ontimeupdate = () => { + let music = audioPlayerElement as HTMLAudioElement; + let progress = 100 * (music.currentTime / music.duration); + this.setState({ progress }); + }; + } + } + + getAudioPlayer(): HTMLAudioElement | null { + return document.getElementById( + this.state.elementId + ) as HTMLAudioElement | null; + } + + toggleAudio() { + let audioPlayerElement = this.getAudioPlayer(); + + if (audioPlayerElement && this.state.playing) { + audioPlayerElement.pause(); + this.setState({ playing: false }); + } else if (audioPlayerElement) { + audioPlayerElement.play(); + this.setState({ playing: true }); + } + } + + fastForward() { + let audioPlayerElement = this.getAudioPlayer(); + + if (audioPlayerElement) { + audioPlayerElement.currentTime += 15.0; + } + } + + rewind() { + let audioPlayerElement = this.getAudioPlayer(); + + if (audioPlayerElement) { + audioPlayerElement.currentTime -= 15.0; + } + } + + render() { + const { classes } = this.props; + return ( +
+
+ ); + } +} + +export default withStyles(styles)(AudioPlayer); diff --git a/src/components/AudioPlayer/index.tsx b/src/components/AudioPlayer/index.tsx new file mode 100644 index 0000000..2be9e12 --- /dev/null +++ b/src/components/AudioPlayer/index.tsx @@ -0,0 +1,3 @@ +import AudioPlayer from "./AudioPlayer"; + +export default AudioPlayer; diff --git a/src/pages/Activity.tsx b/src/pages/Activity.tsx new file mode 100644 index 0000000..ad12078 --- /dev/null +++ b/src/pages/Activity.tsx @@ -0,0 +1,344 @@ +import React, { Component } from "react"; +import { + withStyles, + Typography, + CircularProgress, + ListSubheader, + Link, + Paper, + List, + ListItem, + ListItemText, + ListItemAvatar, + ListItemSecondaryAction, + Tooltip, + IconButton +} from "@material-ui/core"; +import { styles } from "./PageLayout.styles"; +import { UAccount, Account } from "../types/Account"; +import { Tag } from "../types/Tag"; +import Mastodon from "megalodon"; +import { LinkableAvatar, LinkableIconButton } from "../interfaces/overrides"; +import moment from "moment"; + +import FireplaceIcon from "@material-ui/icons/Fireplace"; +import TrendingUpIcon from "@material-ui/icons/TrendingUp"; +import SearchIcon from "@material-ui/icons/Search"; +import OpenInNewIcon from "@material-ui/icons/OpenInNew"; +import AssignmentIndIcon from "@material-ui/icons/AssignmentInd"; + +interface IActivityPageState { + user?: UAccount; + trendingTags?: [Tag]; + activeProfileDirectory?: [Account]; + newProfileDirectory?: [Account]; + viewLoading: boolean; + viewLoaded?: boolean; + viewErrored?: boolean; +} + +class ActivityPage extends Component { + client: Mastodon; + + constructor(props: any) { + super(props); + + this.client = new Mastodon( + localStorage.getItem("access_token") as string, + localStorage.getItem("baseurl") + "/api/v1" + ); + + this.state = { + viewLoading: true + }; + } + + componentDidMount() { + this.getAccountData(); + + this.client + .get("/trends", { limit: 3 }) + .then((resp: any) => { + let trendingTags: [Tag] = resp.data; + this.setState({ trendingTags }); + }) + .catch((err: Error) => { + this.setState({ + viewLoading: false, + viewErrored: true + }); + console.error(err.message); + }); + + this.client + .get("/directory", { local: true, order: "active", limit: 5 }) + .then((resp: any) => { + let profileDirectory: [Account] = resp.data; + this.setState({ + activeProfileDirectory: profileDirectory + }); + }) + .catch((err: Error) => { + this.setState({ + viewLoading: false, + viewErrored: true + }); + console.log(err.message); + }); + + this.client + .get("/directory", { local: true, order: "new", limit: 5 }) + .then((resp: any) => { + let profileDirectory: [Account] = resp.data; + this.setState({ + newProfileDirectory: profileDirectory, + viewLoading: false, + viewLoaded: true + }); + }) + .catch((err: Error) => { + this.setState({ + viewLoading: false, + viewErrored: true + }); + console.log(err.message); + }); + } + + getAccountData() { + this.client + .get("/accounts/verify_credentials") + .then((resp: any) => { + let data: UAccount = resp.data; + this.setState({ user: data }); + }) + .catch((err: Error) => { + this.props.enqueueSnackbar( + "Couldn't find profile info: " + err.name + ); + console.error(err.message); + let acct = localStorage.getItem("account") as string; + this.setState({ user: JSON.parse(acct) }); + }); + } + + render() { + const { classes } = this.props; + return ( +
+
+ + + Hey there,{" "} + {this.state.user + ? this.state.user.display_name || + this.state.user.acct + : "user"} + ! + + + Take a look at what's been happening on your instance. + +
+ {this.state.viewLoaded ? ( +
+ Trending hashtags + {this.state.trendingTags && + this.state.trendingTags.length > 0 ? ( + + + {this.state.trendingTags.map((tag: Tag) => ( + + + + + + + + + + + + + + + + + + + ))} + + + ) : ( + + It looks like there aren't any trending tags on + your instance as of right now. + + )} +
+ Who's been active + {this.state.activeProfileDirectory && + this.state.activeProfileDirectory.length > 0 ? ( + + + {this.state.activeProfileDirectory.map( + (account: Account) => ( + + + + + + + + + + + + + + ) + )} + + + ) : ( + + It looks like there aren't any active people in + the profile directory yet. + + )} +
+ New arrivals + {this.state.newProfileDirectory && + this.state.newProfileDirectory.length > 0 ? ( + + + {this.state.newProfileDirectory.map( + (account: Account) => ( + + + + + + + + + + + + + + ) + )} + + + ) : ( + + It looks like there aren't any new arrivals + listed in the profile directory yet. + + )} +
+ ) : null} + {this.state.viewErrored ? ( + + Bummer. + + Something went wrong when loading instance activity. + + + ) : ( + + )} + {this.state.viewLoading ? ( +
+ +
+ ) : ( + + )} +
+
+ + Trending hashtags and the profile directory may not + appear here if your instance isn't up to date. Check the{" "} + about page to see if your + instance is running the latest version. + +
+
+ ); + } +} + +export default withStyles(styles)(ActivityPage); diff --git a/src/pages/Compose.tsx b/src/pages/Compose.tsx index f9288d9..fa8915f 100644 --- a/src/pages/Compose.tsx +++ b/src/pages/Compose.tsx @@ -23,7 +23,7 @@ import { parse as parseParams, ParsedQuery } from "query-string"; import { styles } from "./Compose.styles"; import { UAccount } from "../types/Account"; import { Visibility } from "../types/Visibility"; -import CameraAltIcon from "@material-ui/icons/CameraAlt"; +import AttachFileIcon from "@material-ui/icons/AttachFile"; import TagFacesIcon from "@material-ui/icons/TagFaces"; import HowToVoteIcon from "@material-ui/icons/HowToVote"; import VisibilityIcon from "@material-ui/icons/Visibility"; @@ -39,7 +39,11 @@ import ComposeMediaAttachment from "../components/ComposeMediaAttachment"; import EmojiPicker from "../components/EmojiPicker"; import { DateTimePicker, MuiPickersUtilsProvider } from "material-ui-pickers"; import MomentUtils from "@date-io/moment"; -import { getUserDefaultVisibility, getConfig } from "../utilities/settings"; +import { + getUserDefaultVisibility, + getConfig, + getUserDefaultBool +} from "../utilities/settings"; interface IComposerState { account: UAccount; @@ -75,7 +79,9 @@ class Composer extends Component { sensitive: false, visibilityMenu: false, text: "", - remainingChars: 500, + remainingChars: getUserDefaultBool("imposeCharacterLimit") + ? 500 + : 9999999999999, showEmojis: false, federated: true }; @@ -95,9 +101,30 @@ class Composer extends Component { acct: state.acct, visibility: state.visibility, text, - remainingChars: 500 - text.length + remainingChars: getUserDefaultBool("imposeCharacterLimit") + ? 500 - text.length + : 99999999 }); }); + + window.addEventListener("paste", (evt: Event) => { + let thePasteEvent = evt as ClipboardEvent; + let fileList: File[] = []; + if (thePasteEvent.clipboardData != null) { + let clipitems = thePasteEvent.clipboardData.items; + if (clipitems != undefined) { + for (let i = 0; i < clipitems.length; i++) { + if (clipitems[i].type.indexOf("image") != -1) { + let clipfile = clipitems[i].getAsFile(); + if (clipfile != null) { + fileList.push(clipfile); + } + } + } + this.actuallyUploadMedia(fileList); + } + } + }); } componentWillReceiveProps(props: any) { @@ -108,7 +135,9 @@ class Composer extends Component { acct: state.acct, visibility: state.visibility, text, - remainingChars: 500 - text.length + remainingChars: getUserDefaultBool("imposeCharacterLimit") + ? 500 - text.length + : 99999999 }); } @@ -145,7 +174,12 @@ class Composer extends Component { } updateTextFromField(text: string) { - this.setState({ text, remainingChars: 500 - text.length }); + this.setState({ + text, + remainingChars: getUserDefaultBool("imposeCharacterLimit") + ? 500 - text.length + : 99999999 + }); } updateWarningFromField(sensitiveText: string) { @@ -159,37 +193,9 @@ class Composer extends Component { uploadMedia() { filedialog({ multiple: false, - accept: "image/*, video/*" + accept: ".jpeg,.jpg,.png,.gif,.webm,.mp4,.mov,.ogg,.wav,.mp3,.flac" }) - .then((media: FileList) => { - let mediaForm = new FormData(); - mediaForm.append("file", media[0]); - this.props.enqueueSnackbar("Uploading media...", { - persist: true, - key: "media-upload" - }); - this.client - .post("/media", mediaForm) - .then((resp: any) => { - let attachment: Attachment = resp.data; - let attachments = this.state.attachments; - if (attachments) { - attachments.push(attachment); - } else { - attachments = [attachment]; - } - this.setState({ attachments }); - this.props.closeSnackbar("media-upload"); - this.props.enqueueSnackbar("Media uploaded."); - }) - .catch((err: Error) => { - this.props.closeSnackbar("media-upload"); - this.props.enqueueSnackbar( - "Couldn't upload media: " + err.name, - { variant: "error" } - ); - }); - }) + .then((media: FileList) => this.actuallyUploadMedia(media)) .catch((err: Error) => { this.props.enqueueSnackbar("Couldn't get media: " + err.name, { variant: "error" @@ -198,6 +204,36 @@ class Composer extends Component { }); } + actuallyUploadMedia(media: FileList | File[]) { + let mediaForm = new FormData(); + mediaForm.append("file", media[0]); + this.props.enqueueSnackbar("Uploading media...", { + persist: true, + key: "media-upload" + }); + this.client + .post("/media", mediaForm) + .then((resp: any) => { + let attachment: Attachment = resp.data; + let attachments = this.state.attachments; + if (attachments) { + attachments.push(attachment); + } else { + attachments = [attachment]; + } + this.setState({ attachments }); + this.props.closeSnackbar("media-upload"); + this.props.enqueueSnackbar("Media uploaded."); + }) + .catch((err: Error) => { + this.props.closeSnackbar("media-upload"); + this.props.enqueueSnackbar( + "Couldn't upload media: " + err.name, + { variant: "error" } + ); + }); + } + getOnlyMediaIds() { let ids: string[] = []; if (this.state.attachments) { @@ -339,7 +375,6 @@ class Composer extends Component { let poll = this.state.poll; if (poll) { let expiry = (newDate.getTime() - currentDate.getTime()) / 1000; - console.log(expiry); if (expiry >= 1800) { poll.expires_at = expiry.toString(); this.setState({ poll, pollExpiresDate: date }); @@ -394,7 +429,7 @@ class Composer extends Component { }) .catch((err: Error) => { this.props.enqueueSnackbar("Couldn't post: " + err.name); - console.log(err.message); + console.error(err.message); }); } @@ -412,7 +447,6 @@ class Composer extends Component { render() { const { classes } = this.props; - console.log(this.state); return ( { }} value={this.state.text} /> - - {`${this.state.remainingChars} character${ - this.state.remainingChars === 1 ? "" : "s" - } remaining`} - + {getUserDefaultBool("imposeCharacterLimit") ? ( + + {`${this.state.remainingChars} character${ + this.state.remainingChars === 1 ? "" : "s" + } remaining`} + + ) : ( + + {" "} + You have the character limit turned off. Make sure + that your post matches your instance's character + limit before posting. + + )} + {this.state.attachments && this.state.attachments.length > 0 ? (
@@ -605,13 +649,13 @@ class Composer extends Component { ) : null} - + this.uploadMedia()} id="compose-media" > - + diff --git a/src/pages/Messages.tsx b/src/pages/Messages.tsx index ca6325e..3d03a20 100644 --- a/src/pages/Messages.tsx +++ b/src/pages/Messages.tsx @@ -10,10 +10,12 @@ import { ListItemAvatar, Avatar, ListItemSecondaryAction, - Tooltip + Tooltip, + Typography } from "@material-ui/core"; import PersonIcon from "@material-ui/icons/Person"; import ForumIcon from "@material-ui/icons/Forum"; +import MailIcon from "@material-ui/icons/Mail"; import { styles } from "./PageLayout.styles"; import Mastodon from "megalodon"; import { Status } from "../types/Status"; @@ -70,67 +72,82 @@ class MessagesPage extends Component { return innerContent; } + renderMessage(message: Status) { + return ( + + + + + + + + + + + + + + + + ); + } + render() { const { classes } = this.props; return (
{this.state.viewDidLoad ? (
- Recent messages - - - {this.state.posts - ? this.state.posts.map( - (message: Status) => { - return ( - - - - - - - - - - - - - - - - ); - } - ) - : null} - - -
+ {this.state.posts && this.state.posts.length > 0 ? ( +
+ Recent messages + + + {this.state.posts + ? this.state.posts.map( + (message: Status) => + this.renderMessage( + message + ) + ) + : null} + + +
+
+ ) : ( +
+
+ + + You don't have any messages. + + + Why not interact with the fediverse a + bit by sending a message? + +
+
+
+ )}
) : null} {this.state.viewIsLoading ? ( diff --git a/src/pages/Notifications.tsx b/src/pages/Notifications.tsx index 85f5c8d..654027b 100644 --- a/src/pages/Notifications.tsx +++ b/src/pages/Notifications.tsx @@ -19,6 +19,7 @@ import { DialogActions, Tooltip } from "@material-ui/core"; + import AssignmentIndIcon from "@material-ui/icons/AssignmentInd"; import PersonIcon from "@material-ui/icons/Person"; import PersonAddIcon from "@material-ui/icons/PersonAdd"; @@ -27,6 +28,8 @@ import { styles } from "./PageLayout.styles"; import { LinkableIconButton, LinkableAvatar } from "../interfaces/overrides"; import ForumIcon from "@material-ui/icons/Forum"; import ReplyIcon from "@material-ui/icons/Reply"; +import NotificationsIcon from "@material-ui/icons/Notifications"; + import Mastodon from "megalodon"; import { Notification } from "../types/Notification"; import { Account } from "../types/Account"; @@ -337,12 +340,20 @@ class NotificationsPage extends Component {
) : ( -
- All clear! +
+ + All clear! It looks like you have no notifications. Why not get the conversation going with a new post? +
) ) : null} diff --git a/src/pages/Recommendations.tsx b/src/pages/Recommendations.tsx index afdc39b..215e7fe 100644 --- a/src/pages/Recommendations.tsx +++ b/src/pages/Recommendations.tsx @@ -6,25 +6,22 @@ import { ListItem, Paper, ListItemText, - Avatar, ListItemSecondaryAction, ListItemAvatar, ListSubheader, CircularProgress, IconButton, - Divider, - Tooltip + Tooltip, + Link } from "@material-ui/core"; import { styles } from "./PageLayout.styles"; import Mastodon from "megalodon"; import { Account } from "../types/Account"; import { LinkableIconButton, LinkableAvatar } from "../interfaces/overrides"; -import AccountCircleIcon from "@material-ui/icons/AccountCircle"; import AssignmentIndIcon from "@material-ui/icons/AssignmentInd"; import PersonAddIcon from "@material-ui/icons/PersonAdd"; -import CheckIcon from "@material-ui/icons/Check"; -import CloseIcon from "@material-ui/icons/Close"; import { withSnackbar, withSnackbarProps } from "notistack"; +import GroupIcon from "@material-ui/icons/Group"; interface IRecommendationsPageProps extends withSnackbarProps { classes: any; @@ -35,7 +32,6 @@ interface IRecommendationsPageState { viewDidLoad?: boolean; viewDidError?: Boolean; viewDidErrorCode?: string; - requestedFollows?: [Account]; followSuggestions?: [Account]; } @@ -57,21 +53,6 @@ class RecommendationsPage extends Component< } componentDidMount() { - this.client - .get("/follow_requests") - .then((resp: any) => { - let requestedFollows: [Account] = resp.data; - this.setState({ requestedFollows }); - }) - .catch((err: Error) => { - this.setState({ - viewIsLoading: false, - viewDidError: true, - viewDidErrorCode: err.name - }); - console.error(err.message); - }); - this.client .get("/suggestions") .then((resp: any) => { @@ -125,110 +106,6 @@ class RecommendationsPage extends Component< }); } - handleFollowRequest(acct: Account, type: "authorize" | "reject") { - this.client - .post(`/follow_requests/${acct.id}/${type}`) - .then((resp: any) => { - let requestedFollows = this.state.requestedFollows; - if (requestedFollows) { - requestedFollows.forEach( - (request: Account, index: number) => { - if (requestedFollows && request.id === acct.id) { - requestedFollows.splice(index, 1); - } - } - ); - } - this.setState({ requestedFollows }); - - let verb: string = type; - verb === "authorize" - ? (verb = "authorized") - : (verb = "rejected"); - this.props.enqueueSnackbar(`You have ${verb} this request.`); - }) - .catch((err: Error) => { - this.props.enqueueSnackbar( - `Couldn't ${type} this request: ${err.name}`, - { variant: "error" } - ); - console.error(err.message); - }); - } - - showFollowRequests() { - const { classes } = this.props; - return ( -
- Follow requests - - - {this.state.requestedFollows - ? this.state.requestedFollows.map( - (request: Account) => { - return ( - - - - - - - - - this.handleFollowRequest( - request, - "authorize" - ) - } - > - - - - - - this.handleFollowRequest( - request, - "reject" - ) - } - > - - - - - - - - - - - ); - } - ) - : null} - - -
-
- ); - } - showFollowSuggestions() { const { classes } = this.props; return ( @@ -295,23 +172,6 @@ class RecommendationsPage extends Component<
{this.state.viewDidLoad ? (
- {this.state.requestedFollows && - this.state.requestedFollows.length > 0 ? ( - this.showFollowRequests() - ) : ( -
- - You don't have any follow requests. - -
-
- )} - -
{this.state.followSuggestions && this.state.followSuggestions.length > 0 ? ( this.showFollowSuggestions() @@ -320,23 +180,35 @@ class RecommendationsPage extends Component< className={ classes.pageLayoutEmptyTextConstraints } + style={{ textAlign: "center" }} > - + + We don't have any suggestions for you. - Why not interact with the fediverse a bit by - creating a new post? + Take a look around the fediverse or check + out the Activity page for more. +
)} +
+ + Looking for follow requests? You can find them in + Settings or in the account menu. You can also{" "} + click here. +
) : null} {this.state.viewDidError ? ( Bummer. - Something went wrong when loading this timeline. + Something went wrong when loading recommendations. {this.state.viewDidErrorCode diff --git a/src/pages/Requests.tsx b/src/pages/Requests.tsx new file mode 100644 index 0000000..58b2445 --- /dev/null +++ b/src/pages/Requests.tsx @@ -0,0 +1,215 @@ +import React, { Component } from "react"; +import { + CircularProgress, + IconButton, + List, + ListItem, + ListItemAvatar, + ListItemSecondaryAction, + ListItemText, + ListSubheader, + Paper, + Tooltip, + Typography, + withStyles +} from "@material-ui/core"; +import { styles } from "./PageLayout.styles"; +import { Account } from "../types/Account"; +import Mastodon from "megalodon"; +import { LinkableAvatar, LinkableIconButton } from "../interfaces/overrides"; +import CheckIcon from "@material-ui/icons/Check"; +import AccountCircleIcon from "@material-ui/icons/AccountCircle"; +import CloseIcon from "@material-ui/icons/Close"; +import CheckCircleIcon from "@material-ui/icons/CheckCircle"; +import { withSnackbar } from "notistack"; + +interface IRequestsPageState { + viewLoading: boolean; + viewLoaded?: boolean; + viewErrored?: boolean; + requestedAccounts?: [Account]; +} + +class RequestsPage extends Component { + client: Mastodon; + + constructor(props: any) { + super(props); + + this.client = new Mastodon( + localStorage.getItem("access_token") as string, + localStorage.getItem("baseurl") + "/api/v1" + ); + this.state = { + viewLoading: true + }; + } + + componentDidMount() { + this.client + .get("/follow_requests") + .then((resp: any) => { + let requestedAccounts: [Account] = resp.data; + this.setState({ + requestedAccounts, + viewLoading: false, + viewLoaded: true + }); + }) + .catch((err: Error) => { + this.setState({ + viewLoading: false, + viewErrored: true + }); + console.error(err.message); + }); + } + + handleFollowRequest(acct: Account, type: "authorize" | "reject") { + this.client + .post(`/follow_requests/${acct.id}/${type}`) + .then((resp: any) => { + let requestedAccounts = this.state.requestedAccounts; + if (requestedAccounts) { + requestedAccounts.forEach( + (request: Account, index: number) => { + if (requestedAccounts && request.id === acct.id) { + requestedAccounts.splice(index, 1); + } + } + ); + } + this.setState({ requestedAccounts }); + + let verb: string = type; + verb === "authorize" + ? (verb = "authorized") + : (verb = "rejected"); + this.props.enqueueSnackbar(`You have ${verb} this request.`); + }) + .catch((err: Error) => { + this.props.enqueueSnackbar( + `Couldn't ${type} this request: ${err.name}`, + { variant: "error" } + ); + console.error(err.message); + }); + } + + showFollowRequests() { + const { classes } = this.props; + return ( +
+ Follow requests + + + {this.state.requestedAccounts + ? this.state.requestedAccounts.map( + (request: Account) => { + return ( + + + + + + + + + this.handleFollowRequest( + request, + "authorize" + ) + } + > + + + + + + this.handleFollowRequest( + request, + "reject" + ) + } + > + + + + + + + + + + + ); + } + ) + : null} + + +
+
+ ); + } + + render() { + const { classes } = this.props; + return ( +
+ {this.state.viewLoaded ? ( +
+ {this.state.requestedAccounts && + this.state.requestedAccounts.length > 0 ? ( + this.showFollowRequests() + ) : ( +
+ + + You don't have any follow requests. + +
+
+ )} +
+ ) : null} + {this.state.viewLoading ? ( +
+ +
+ ) : ( + + )} +
+ ); + } +} + +export default withStyles(styles)(withSnackbar(RequestsPage)); diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index aad0d08..8abe80e 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -62,6 +62,9 @@ import BellAlertIcon from "mdi-material-ui/BellAlert"; import RefreshIcon from "@material-ui/icons/Refresh"; import UndoIcon from "@material-ui/icons/Undo"; import DomainDisabledIcon from "@material-ui/icons/DomainDisabled"; +import AccountSettingsIcon from "mdi-material-ui/AccountSettings"; +import AlphabeticalVariantOffIcon from "mdi-material-ui/AlphabeticalVariantOff"; + import { Config } from "../types/Config"; import { Account } from "../types/Account"; import Mastodon from "megalodon"; @@ -82,6 +85,7 @@ interface ISettingsState { brandName: string; federated: boolean; currentUser?: Account; + imposeCharacterLimit: boolean; } class SettingsPage extends Component { @@ -112,7 +116,8 @@ class SettingsPage extends Component { setHyperspaceTheme(defaultTheme), defaultVisibility: getUserDefaultVisibility() || "public", brandName: "Hyperspace", - federated: true + federated: true, + imposeCharacterLimit: getUserDefaultBool("imposeCharacterLimit") }; this.toggleDarkMode = this.toggleDarkMode.bind(this); @@ -216,6 +221,16 @@ class SettingsPage extends Component { }); } + toggleCharacterLimit() { + this.setState({ + imposeCharacterLimit: !this.state.imposeCharacterLimit + }); + setUserDefaultBool( + "imposeCharacterLimit", + !this.state.imposeCharacterLimit + ); + } + toggleResetDialog() { this.setState({ resetHyperspaceDialog: !this.state.resetHyperspaceDialog @@ -502,7 +517,7 @@ class SettingsPage extends Component {
- + { + + + + + { + + + + + + + + this.toggleCharacterLimit() + } + /> + +
diff --git a/src/types/Account.tsx b/src/types/Account.tsx index a6454a9..5fb1504 100644 --- a/src/types/Account.tsx +++ b/src/types/Account.tsx @@ -14,6 +14,7 @@ export type Account = { followers_count: number; following_count: number; statuses_count: number; + last_status_at: string; note: string; url: string; avatar: string; diff --git a/src/types/Attachment.tsx b/src/types/Attachment.tsx index e8d9427..5041066 100644 --- a/src/types/Attachment.tsx +++ b/src/types/Attachment.tsx @@ -3,7 +3,7 @@ */ export type Attachment = { id: string; - type: "unknown" | "image" | "gifv" | "video"; + type: "unknown" | "image" | "gifv" | "audio" | "video"; url: string; remote_url: string | null; preview_url: string; diff --git a/src/types/History.tsx b/src/types/History.tsx new file mode 100644 index 0000000..22176bd --- /dev/null +++ b/src/types/History.tsx @@ -0,0 +1,5 @@ +export type History = { + day: string; + uses: number; + accounts: number; +}; diff --git a/src/types/Tag.tsx b/src/types/Tag.tsx index f12ccf2..d03b269 100644 --- a/src/types/Tag.tsx +++ b/src/types/Tag.tsx @@ -1,4 +1,7 @@ +import { History } from "./History"; + export type Tag = { name: string; url: string; + history?: [History]; }; diff --git a/src/utilities/settings.tsx b/src/utilities/settings.tsx index 95ad574..43b36ee 100644 --- a/src/utilities/settings.tsx +++ b/src/utilities/settings.tsx @@ -12,6 +12,7 @@ type SettingsTemplate = { clearNotificationsOnRead: boolean; displayAllOnNotificationBadge: boolean; defaultVisibility: string; + imposeCharacterLimit: boolean; }; /** @@ -99,7 +100,8 @@ export function createUserDefaults() { enablePushNotifications: true, clearNotificationsOnRead: false, displayAllOnNotificationBadge: false, - defaultVisibility: "public" + defaultVisibility: "public", + imposeCharacterLimit: true }; let settings = [ @@ -107,7 +109,8 @@ export function createUserDefaults() { "systemDecidesDarkMode", "clearNotificationsOnRead", "displayAllOnNotificationBadge", - "defaultVisibility" + "defaultVisibility", + "imposeCharacterLimit" ]; migrateExistingSettings();