diff --git a/package-lock.json b/package-lock.json index 0414e34..a15e750 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1159,6 +1159,15 @@ "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", "dev": true }, + "@types/emoji-mart": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/@types/emoji-mart/-/emoji-mart-2.8.4.tgz", + "integrity": "sha512-C3J+8nwZxAkgb06+835suFCutyFyz3G4aUFGMlJTvl0ANmE8hj8HUfL92GXNRkJspw+8ciXB6OuwscFFPA56iA==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -5602,6 +5611,15 @@ "minimalistic-crypto-utils": "^1.0.0" } }, + "emoji-mart": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-2.11.0.tgz", + "integrity": "sha512-ol1ABg0xfDwCeKVu0Mmr/iTRHUMvqlyLZJFRQy+e6aWV83/Y+TEbJpBbtk7NxaW3BLAOyJ8r0NWthRQwxTtT1A==", + "dev": true, + "requires": { + "prop-types": "^15.6.0" + } + }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -6786,6 +6804,12 @@ "escape-string-regexp": "^1.0.5" } }, + "file-dialog": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/file-dialog/-/file-dialog-0.0.7.tgz", + "integrity": "sha512-eRo8DZbV7G0ADtaUPvGAb5ZXB+jN1ehVeYanIHDt2wEokjiXTy5kzMvtiPw4nEtt08IxJqqMeeFZNP34XLNjRw==", + "dev": true + }, "file-entry-cache": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", diff --git a/package.json b/package.json index 428ca14..0df107d 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@types/react-dom": "16.8.3", "@types/react-router-dom": "^4.3.1", "@types/react-swipeable-views": "latest", + "@types/emoji-mart": "^2.8.2", "megalodon": "^0.6.0", "moment": "^2.24.0", "react": "^16.8.6", @@ -21,7 +22,9 @@ "typescript": "3.3.4000", "notistack": "^0.5.1", "react-web-share-api": "^0.0.2", - "query-string": "^6.4.2" + "query-string": "^6.4.2", + "file-dialog": "^0.0.7", + "emoji-mart": "^2.8.2" }, "scripts": { "start": "BROWSER='Safari Technology Preview' react-scripts start", diff --git a/src/App.tsx b/src/App.tsx index c9f0a63..b3fc6e1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,6 +14,7 @@ import PublicPage from './pages/Public'; import Conversation from './pages/Conversation'; import NotificationsPage from './pages/Notifications'; import SearchPage from './pages/Search'; +import Composer from './pages/Compose'; import {withSnackbar} from 'notistack'; let theme = setHyperspaceTheme(getUserDefaultTheme()); @@ -53,6 +54,7 @@ class App extends Component { + ); } diff --git a/src/components/AppLayout/AppLayout.styles.tsx b/src/components/AppLayout/AppLayout.styles.tsx index 1287130..b9674c8 100644 --- a/src/components/AppLayout/AppLayout.styles.tsx +++ b/src/components/AppLayout/AppLayout.styles.tsx @@ -142,5 +142,11 @@ export const styles = (theme: Theme) => createStyles({ marginLeft: 250 }, overflowY: 'auto', + }, + composeButton: { + position: "fixed", + bottom: theme.spacing.unit * 2, + right: theme.spacing.unit * 2, + zIndex: 50 } }); \ No newline at end of file diff --git a/src/components/AppLayout/AppLayout.tsx b/src/components/AppLayout/AppLayout.tsx index d67191d..cc7b5ab 100644 --- a/src/components/AppLayout/AppLayout.tsx +++ b/src/components/AppLayout/AppLayout.tsx @@ -1,5 +1,5 @@ import React, { Component } from 'react'; -import { Typography, AppBar, Toolbar, IconButton, InputBase, Avatar, ListItemText, Divider, List, ListItem, ListItemIcon, Hidden, Drawer, ListSubheader, ListItemAvatar, withStyles, Menu, MenuItem, ClickAwayListener, Badge } from '@material-ui/core'; +import { Typography, AppBar, Toolbar, IconButton, InputBase, Avatar, ListItemText, Divider, List, ListItemIcon, Hidden, Drawer, ListSubheader, ListItemAvatar, withStyles, Menu, MenuItem, ClickAwayListener, Badge } 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'; @@ -10,11 +10,12 @@ import PublicIcon from '@material-ui/icons/Public'; import GroupIcon from '@material-ui/icons/Group'; import SettingsIcon from '@material-ui/icons/Settings'; import InfoIcon from '@material-ui/icons/Info'; +import EditIcon from '@material-ui/icons/Edit'; import SupervisedUserCircleIcon from '@material-ui/icons/SupervisedUserCircle'; import ExitToAppIcon from '@material-ui/icons/ExitToApp'; import {styles} from './AppLayout.styles'; import { UAccount } from '../../types/Account'; -import {LinkableListItem, LinkableIconButton} from '../../interfaces/overrides'; +import {LinkableListItem, LinkableIconButton, LinkableFab} from '../../interfaces/overrides'; import Mastodon from 'megalodon'; import { Notification } from '../../types/Notification'; import {sendNotificationRequest} from '../../utilities/notifications'; @@ -287,6 +288,9 @@ export class AppLayout extends Component { + + + ); } diff --git a/src/components/Post/Post.styles.tsx b/src/components/Post/Post.styles.tsx index a27c54a..8f2e6d8 100644 --- a/src/components/Post/Post.styles.tsx +++ b/src/components/Post/Post.styles.tsx @@ -40,7 +40,7 @@ export const styles = (theme: Theme) => createStyles({ } }, postEmoji: { - width: theme.typography.body2.fontSize + height: theme.typography.fontSize }, postMedia: { height: 0, @@ -76,5 +76,9 @@ export const styles = (theme: Theme) => createStyles({ postTags: { paddingTop: theme.spacing.unit, paddingBottom: theme.spacing.unit + }, + postAuthorEmoji: { + height: theme.typography.fontSize, + verticalAlign: "middle" } }); \ No newline at end of file diff --git a/src/components/Post/Post.tsx b/src/components/Post/Post.tsx index f3e32d9..f8126bb 100644 --- a/src/components/Post/Post.tsx +++ b/src/components/Post/Post.tsx @@ -19,12 +19,12 @@ import { Tag } from '../../types/Tag'; import { Mention } from '../../types/Mention'; import { Visibility } from '../../types/Visibility'; import moment from 'moment'; -import { MastodonEmoji } from '../../types/Emojis'; import AttachmentComponent from '../Attachment'; import Mastodon from 'megalodon'; import { LinkableChip, LinkableMenuItem, LinkableIconButton } from '../../interfaces/overrides'; import {withSnackbar} from 'notistack'; import ShareMenu from './PostShareMenu'; +import {emojifyString} from '../../utilities/emojis'; interface IPostProps { post: Status; @@ -61,6 +61,7 @@ export class Post extends React.Component { materializeContent(status: Status) { const { classes } = this.props; + const oldContent = document.createElement('div'); oldContent.innerHTML = status.content; @@ -72,12 +73,8 @@ export class Post extends React.Component { } }); - if (status.emojis !== undefined && status.emojis.length > 0) { - status.emojis.forEach((emoji: MastodonEmoji) => { - let regexp = new RegExp(':' + emoji.shortcode + ':', 'g'); - oldContent.innerHTML = oldContent.innerHTML.replace(regexp, ``) - }) - } + oldContent.innerHTML = emojifyString(oldContent.innerHTML, status.emojis, classes.postEmoji); + return (
@@ -144,12 +141,17 @@ export class Post extends React.Component { } getReblogAuthors(post: Status) { + const { classes } = this.props; if (post.reblog) { let author = post.reblog.account; - return `${author.display_name || author.username} (@${author.acct}) 🔄 ${post.account.display_name || post.account.username}` + let origString = `${author.display_name || author.username} (@${author.acct}) 🔄 ${post.account.display_name || post.account.username}`; + let emojis = author.emojis; + emojis.concat(post.account.emojis); + return emojifyString(origString, emojis, classes.postAuthorEmoji); } else { let author = post.account; - return `${author.display_name || author.username} (@${author.acct})` + let origString = `${author.display_name || author.username} (@${author.acct})`; + return emojifyString(origString, author.emojis, classes.postAuthorEmoji); } } @@ -299,7 +301,10 @@ export class Post extends React.Component { this.togglePostMenu()}> } - title={this.getReblogAuthors(post)} subheader={moment(post.created_at).format("MMMM Do YYYY [at] h:mm A")} /> + title={ + + } + subheader={moment(post.created_at).format("MMMM Do YYYY [at] h:mm A")} /> { post.reblog? this.getReblogOfPost(post.reblog): null } @@ -315,7 +320,7 @@ export class Post extends React.Component { } - + diff --git a/src/index.tsx b/src/index.tsx index 2253a53..ddbb70d 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,7 +6,6 @@ import * as serviceWorker from './serviceWorker'; import {createUserDefaults, getUserDefaultBool} from './utilities/settings'; import {refreshUserAccountData} from './utilities/accounts'; import {SnackbarProvider} from 'notistack'; -import {getNotificationRequestPermission} from './utilities/notifications'; createUserDefaults(); refreshUserAccountData(); @@ -16,7 +15,7 @@ ReactDOM.render( diff --git a/src/interfaces/overrides.tsx b/src/interfaces/overrides.tsx index 27ce8c2..f1b136d 100644 --- a/src/interfaces/overrides.tsx +++ b/src/interfaces/overrides.tsx @@ -6,6 +6,7 @@ import Chip, { ChipProps } from '@material-ui/core/Chip'; import { MenuItemProps } from '@material-ui/core/MenuItem'; import { MenuItem } from '@material-ui/core'; import Button, { ButtonProps } from '@material-ui/core/Button'; +import Fab, { FabProps } from '@material-ui/core/Fab'; export interface ILinkableListItemProps extends ListItemProps { to: string; @@ -32,6 +33,11 @@ export interface ILinkableButtonProps extends ButtonProps { replace?: boolean; } +export interface ILinkableFabProps extends FabProps { + to: string; + replace?: boolean; +} + export const LinkableListItem = (props: ILinkableListItemProps) => ( ) @@ -52,6 +58,10 @@ export const LinkableButton = (props: ILinkableButtonProps) => ( + + + ) + } +} + +export default withStyles(styles)(withSnackbar(Composer)); \ No newline at end of file diff --git a/src/pages/PageLayout.styles.tsx b/src/pages/PageLayout.styles.tsx index ca9f2a4..388e2d6 100644 --- a/src/pages/PageLayout.styles.tsx +++ b/src/pages/PageLayout.styles.tsx @@ -98,6 +98,9 @@ export const styles = (theme: Theme) => createStyles({ marginBottom: theme.spacing.unit, backgroundColor: theme.palette.primary.main }, + pageProfileNameEmoji: { + height: theme.typography.h4.fontSize, + }, pageProfileStatsDiv: { display: 'inline-flex', marginTop: theme.spacing.unit * 2, @@ -153,5 +156,5 @@ export const styles = (theme: Theme) => createStyles({ top: 116, right: theme.spacing.unit * 24, } - } + }, }); \ No newline at end of file diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index 131a16d..50f5a7e 100644 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -8,6 +8,7 @@ import { Relationship } from '../types/Relationship'; import Post from '../components/Post'; import {withSnackbar} from 'notistack'; import { LinkableButton } from '../interfaces/overrides'; +import { emojifyString } from '../utilities/emojis'; interface IProfilePageState { account?: Account; @@ -210,7 +211,7 @@ class ProfilePage extends Component {
- {this.state.account ? this.state.account.display_name: ""} + {this.state.account ? '@' + this.state.account.acct: ""} {this.state.account ? this.state.account.note: ""} diff --git a/src/types/Poll.tsx b/src/types/Poll.tsx index 619fb73..a6537b7 100644 --- a/src/types/Poll.tsx +++ b/src/types/Poll.tsx @@ -17,4 +17,14 @@ export type Poll = { export type PollOption = { title: string; votes_count: number | null; +} + +export type PollWizard = { + expires_at: string; + multiple: boolean; + options: PollWizardOption[]; +} + +export type PollWizardOption = { + title: string; } \ No newline at end of file diff --git a/src/utilities/emojis.tsx b/src/utilities/emojis.tsx new file mode 100644 index 0000000..a523adc --- /dev/null +++ b/src/utilities/emojis.tsx @@ -0,0 +1,26 @@ +import {MastodonEmoji} from '../types/Emojis'; + +// if (status.emojis !== undefined && status.emojis.length > 0) { +// status.emojis.forEach((emoji: MastodonEmoji) => { +// let regexp = new RegExp(':' + emoji.shortcode + ':', 'g'); +// oldContent.innerHTML = oldContent.innerHTML.replace(regexp, ``) +// }) +// } + +/** + * Takes a given string and replaces emoji codes with their respective image tags. + * @param contents The string to replace with emojis + * @param emojis The set of emojis to replace the content with + * @param className The associated class for the string + * @returns String with image tags for emojis + */ +export function emojifyString(contents: string, emojis: [MastodonEmoji], className?: any): string { + let newContents: string = contents; + + emojis.forEach((emoji: MastodonEmoji) => { + let filter = new RegExp(`:${emoji.shortcode}:`, 'g'); + newContents = newContents.replace(filter, ``) + }) + + return newContents; +} \ No newline at end of file