add sharing buttons

This commit is contained in:
Athou
2022-08-19 12:41:33 +02:00
parent 973fe56cc8
commit 7e605e5cda
10 changed files with 245 additions and 50 deletions

View File

@@ -1,6 +1,9 @@
import { t } from "@lingui/macro"
import { DEFAULT_THEME } from "@mantine/core"
import { Category } from "./types"
import { IconType } from "react-icons"
import { FaAt } from "react-icons/fa"
import { SiBuffer, SiFacebook, SiGmail, SiInstapaper, SiPocket, SiTumblr, SiTwitter } from "react-icons/si"
import { Category, SharingSettings } from "./types"
const categories: { [key: string]: Category } = {
all: {
@@ -20,8 +23,68 @@ const categories: { [key: string]: Category } = {
position: 1,
},
}
const sharing: {
[key in keyof SharingSettings]: {
label: string
icon: IconType
color: `#${string}`
url: (url: string, description: string) => string
}
} = {
email: {
label: "Email",
icon: FaAt,
color: "#000000",
url: (url, desc) => `mailto:?subject=${desc}&body=${url}`,
},
gmail: {
label: "Gmail",
icon: SiGmail,
color: "#EA4335",
url: (url, desc) => `https://mail.google.com/mail/?view=cm&fs=1&tf=1&source=mailto&su=${desc}&body=${url}`,
},
facebook: {
label: "Facebook",
icon: SiFacebook,
color: "#1B74E4",
url: url => `https://www.facebook.com/sharer/sharer.php?u=${url}`,
},
twitter: {
label: "Twitter",
icon: SiTwitter,
color: "#1D9BF0",
url: (url, desc) => `http://twitter.com/share?text=${desc}&url=${url}`,
},
tumblr: {
label: "Tumblr",
icon: SiTumblr,
color: "#375672",
url: (url, desc) => `http://www.tumblr.com/share/link?url=${url}&name=${desc}`,
},
pocket: {
label: "Pocket",
icon: SiPocket,
color: "#EF4154",
url: (url, desc) => `https://getpocket.com/save?url=${url}&title=${desc}`,
},
instapaper: {
label: "Instapaper",
icon: SiInstapaper,
color: "#010101",
url: (url, desc) => `https://www.instapaper.com/hello2?url=${url}&title=${desc}`,
},
buffer: {
label: "Buffer",
icon: SiBuffer,
color: "#000000",
url: (url, desc) => `https://bufferapp.com/add?url=${url}&text=${desc}`,
},
}
export const Constants = {
categories,
sharing,
layout: {
mobileBreakpoint: DEFAULT_THEME.breakpoints.md,
headerHeight: 60,

View File

@@ -3,7 +3,7 @@ import { showNotification } from "@mantine/notifications"
import { createAsyncThunk, createSlice, isAnyOf } from "@reduxjs/toolkit"
import { client } from "app/client"
import { RootState } from "app/store"
import { ReadingMode, ReadingOrder, Settings, UserModel } from "app/types"
import { ReadingMode, ReadingOrder, Settings, SharingSettings, UserModel } from "app/types"
import { reloadEntries } from "./entries"
interface UserState {
@@ -43,6 +43,20 @@ export const changeScrollSpeed = createAsyncThunk<void, boolean, { state: RootSt
if (!settings) return
client.user.saveSettings({ ...settings, scrollSpeed: speed ? 400 : 0 })
})
export const changeSharingSetting = createAsyncThunk<void, { site: keyof SharingSettings; value: boolean }, { state: RootState }>(
"settings/sharingSetting",
(sharingSetting, thunkApi) => {
const { settings } = thunkApi.getState().user
if (!settings) return
client.user.saveSettings({
...settings,
sharingSettings: {
...settings.sharingSettings,
[sharingSetting.site]: sharingSetting.value,
},
})
}
)
export const userSlice = createSlice({
name: "user",
@@ -71,7 +85,11 @@ export const userSlice = createSlice({
if (!state.settings) return
state.settings.scrollSpeed = action.meta.arg ? 400 : 0
})
builder.addMatcher(isAnyOf(changeLanguage.fulfilled, changeScrollSpeed.fulfilled), () => {
builder.addCase(changeSharingSetting.pending, (state, action) => {
if (!state.settings) return
state.settings.sharingSettings[action.meta.arg.site] = action.meta.arg.value
})
builder.addMatcher(isAnyOf(changeLanguage.fulfilled, changeScrollSpeed.fulfilled, changeSharingSetting.fulfilled), () => {
showNotification({
message: t`Settings saved.`,
color: "green",

View File

@@ -233,16 +233,18 @@ export interface Settings {
theme?: string
customCss?: string
scrollSpeed: number
sharingSettings: SharingSettings
}
export interface SharingSettings {
email: boolean
gmail: boolean
facebook: boolean
twitter: boolean
googleplus: boolean
tumblr: boolean
pocket: boolean
instapaper: boolean
buffer: boolean
readability: boolean
}
export interface StarRequest {

View File

@@ -1,17 +1,23 @@
import { t } from "@lingui/macro"
import { Checkbox, Group } from "@mantine/core"
import { Checkbox, Group, Popover } from "@mantine/core"
import { markEntry, starEntry } from "app/slices/entries"
import { useAppDispatch } from "app/store"
import { useAppDispatch, useAppSelector } from "app/store"
import { Entry } from "app/types"
import { ActionButton } from "components/ActionButtton"
import { TbExternalLink, TbStar, TbStarOff } from "react-icons/tb"
import { TbExternalLink, TbShare, TbStar, TbStarOff } from "react-icons/tb"
import { ShareButtons } from "./ShareButtons"
interface FeedEntryFooterProps {
entry: Entry
}
export function FeedEntryFooter(props: FeedEntryFooterProps) {
const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings)
const dispatch = useAppDispatch()
const showSharingButtons =
sharingSettings && (Object.values(sharingSettings) as Array<typeof sharingSettings[keyof typeof sharingSettings]>).some(v => v)
const readStatusCheckboxClicked = () => dispatch(markEntry({ entry: props.entry, read: !props.entry.read }))
return (
@@ -32,6 +38,18 @@ export function FeedEntryFooter(props: FeedEntryFooterProps) {
label={props.entry.starred ? t`Unstar` : t`Star`}
onClick={() => dispatch(starEntry({ entry: props.entry, starred: !props.entry.starred }))}
/>
{showSharingButtons && (
<Popover withArrow withinPortal shadow="md">
<Popover.Target>
<ActionButton icon={<TbShare size={18} />} label={t`Share`} />
</Popover.Target>
<Popover.Dropdown>
<ShareButtons url={props.entry.url} description={props.entry.title} />
</Popover.Dropdown>
</Popover>
)}
<a href={props.entry.url} target="_blank" rel="noreferrer">
<ActionButton icon={<TbExternalLink size={18} />} label={t`Open link`} />
</a>

View File

@@ -0,0 +1,55 @@
import { ActionIcon, Box, createStyles, SimpleGrid } from "@mantine/core"
import { Constants } from "app/constants"
import { useAppSelector } from "app/store"
import { SharingSettings } from "app/types"
import { IconType } from "react-icons"
type Color = `#${string}`
const useStyles = createStyles((theme, props: { color: Color }) => ({
socialIcon: {
color: props.color,
backgroundColor: theme.colorScheme === "dark" ? theme.colors.gray[2] : "white",
borderRadius: "50%",
},
}))
function ShareButton({ url, icon, color }: { url: string; icon: IconType; color: Color }) {
const { classes } = useStyles({ color })
const onClick = (e: React.MouseEvent) => {
e.preventDefault()
window.open(url, "", "menubar=no,toolbar=no,resizable=yes,scrollbars=yes,width=800,height=600")
}
return (
<ActionIcon>
<a href={url} target="_blank" rel="noreferrer" onClick={onClick}>
<Box p={8} className={classes.socialIcon}>
{icon({ size: 18 })}
</Box>
</a>
</ActionIcon>
)
}
export function ShareButtons(props: { url: string; description: string }) {
const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings)
const url = encodeURIComponent(props.url)
const desc = encodeURIComponent(props.description)
return (
<SimpleGrid cols={4}>
{(Object.keys(Constants.sharing) as Array<keyof SharingSettings>)
.filter(site => sharingSettings && sharingSettings[site])
.map(site => (
<ShareButton
key={site}
icon={Constants.sharing[site].icon}
color={Constants.sharing[site].color}
url={Constants.sharing[site].url(url, desc)}
/>
))}
</SimpleGrid>
)
}

View File

@@ -1,12 +1,15 @@
import { t } from "@lingui/macro"
import { Select, Stack, Switch } from "@mantine/core"
import { changeLanguage, changeScrollSpeed } from "app/slices/user"
import { Divider, Select, SimpleGrid, Stack, Switch } from "@mantine/core"
import { Constants } from "app/constants"
import { changeLanguage, changeScrollSpeed, changeSharingSetting } from "app/slices/user"
import { useAppDispatch, useAppSelector } from "app/store"
import { SharingSettings } from "app/types"
import { locales } from "i18n"
export function DisplaySettings() {
const language = useAppSelector(state => state.user.settings?.language)
const scrollSpeed = useAppSelector(state => state.user.settings?.scrollSpeed)
const sharingSettings = useAppSelector(state => state.user.settings?.sharingSettings)
const dispatch = useAppDispatch()
return (
@@ -26,6 +29,19 @@ export function DisplaySettings() {
checked={scrollSpeed ? scrollSpeed > 0 : false}
onChange={e => dispatch(changeScrollSpeed(e.currentTarget.checked))}
/>
<Divider label={t`Sharing sites`} labelPosition="center" />
<SimpleGrid cols={2}>
{(Object.keys(Constants.sharing) as Array<keyof SharingSettings>).map(site => (
<Switch
key={site}
label={Constants.sharing[site].label}
checked={sharingSettings && sharingSettings[site]}
onChange={e => dispatch(changeSharingSetting({ site, value: e.currentTarget.checked }))}
/>
))}
</SimpleGrid>
</Stack>
)
}

View File

@@ -564,6 +564,14 @@ msgstr "Settings"
msgid "Settings saved."
msgstr "Settings saved."
#: src/components/content/FeedEntryFooter.tsx
msgid "Share"
msgstr "Share"
#: src/components/settings/DisplaySettings.tsx
msgid "Sharing sites"
msgstr "Sharing sites"
#: src/components/KeyboardShortcutsHelp.tsx
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Shift"

View File

@@ -564,6 +564,14 @@ msgstr "Réglages"
msgid "Settings saved."
msgstr "Réglages enregistrés."
#: src/components/content/FeedEntryFooter.tsx
msgid "Share"
msgstr "Partager"
#: src/components/settings/DisplaySettings.tsx
msgid "Sharing sites"
msgstr "Sites de partage"
#: src/components/KeyboardShortcutsHelp.tsx
#: src/components/KeyboardShortcutsHelp.tsx
msgid "Shift"

View File

@@ -38,27 +38,34 @@ public class Settings implements Serializable {
@ApiModelProperty(value = "user's preferred scroll speed when navigating between entries", required = true)
private int scrollSpeed;
@ApiModelProperty(required = true)
private boolean email;
@ApiModelProperty(value = "sharing settings", required = true)
private SharingSettings sharingSettings = new SharingSettings();
@ApiModelProperty(required = true)
private boolean gmail;
@ApiModel(description = "User sharing settings")
@Data
public static class SharingSettings implements Serializable {
@ApiModelProperty(required = true)
private boolean email;
@ApiModelProperty(required = true)
private boolean facebook;
@ApiModelProperty(required = true)
private boolean gmail;
@ApiModelProperty(required = true)
private boolean twitter;
@ApiModelProperty(required = true)
private boolean facebook;
@ApiModelProperty(required = true)
private boolean tumblr;
@ApiModelProperty(required = true)
private boolean twitter;
@ApiModelProperty(required = true)
private boolean pocket;
@ApiModelProperty(required = true)
private boolean tumblr;
@ApiModelProperty(required = true)
private boolean instapaper;
@ApiModelProperty(required = true)
private boolean pocket;
@ApiModelProperty(required = true)
private boolean buffer;
@ApiModelProperty(required = true)
private boolean instapaper;
@ApiModelProperty(required = true)
private boolean buffer;
}
}

View File

@@ -91,14 +91,14 @@ public class UserREST {
s.setViewMode(settings.getViewMode().name());
s.setShowRead(settings.isShowRead());
s.setEmail(settings.isEmail());
s.setGmail(settings.isGmail());
s.setFacebook(settings.isFacebook());
s.setTwitter(settings.isTwitter());
s.setTumblr(settings.isTumblr());
s.setPocket(settings.isPocket());
s.setInstapaper(settings.isInstapaper());
s.setBuffer(settings.isBuffer());
s.getSharingSettings().setEmail(settings.isEmail());
s.getSharingSettings().setGmail(settings.isGmail());
s.getSharingSettings().setFacebook(settings.isFacebook());
s.getSharingSettings().setTwitter(settings.isTwitter());
s.getSharingSettings().setTumblr(settings.isTumblr());
s.getSharingSettings().setPocket(settings.isPocket());
s.getSharingSettings().setInstapaper(settings.isInstapaper());
s.getSharingSettings().setBuffer(settings.isBuffer());
s.setScrollMarks(settings.isScrollMarks());
s.setTheme(settings.getTheme());
@@ -112,14 +112,14 @@ public class UserREST {
s.setShowRead(true);
s.setTheme("default");
s.setEmail(true);
s.setGmail(true);
s.setFacebook(true);
s.setTwitter(true);
s.setTumblr(true);
s.setPocket(true);
s.setInstapaper(true);
s.setBuffer(true);
s.getSharingSettings().setEmail(true);
s.getSharingSettings().setGmail(true);
s.getSharingSettings().setFacebook(true);
s.getSharingSettings().setTwitter(true);
s.getSharingSettings().setTumblr(true);
s.getSharingSettings().setPocket(true);
s.getSharingSettings().setInstapaper(true);
s.getSharingSettings().setBuffer(true);
s.setScrollMarks(true);
s.setLanguage("en");
@@ -151,14 +151,14 @@ public class UserREST {
s.setLanguage(settings.getLanguage());
s.setScrollSpeed(settings.getScrollSpeed());
s.setEmail(settings.isEmail());
s.setGmail(settings.isGmail());
s.setFacebook(settings.isFacebook());
s.setTwitter(settings.isTwitter());
s.setTumblr(settings.isTumblr());
s.setPocket(settings.isPocket());
s.setInstapaper(settings.isInstapaper());
s.setBuffer(settings.isBuffer());
s.setEmail(settings.getSharingSettings().isEmail());
s.setGmail(settings.getSharingSettings().isGmail());
s.setFacebook(settings.getSharingSettings().isFacebook());
s.setTwitter(settings.getSharingSettings().isTwitter());
s.setTumblr(settings.getSharingSettings().isTumblr());
s.setPocket(settings.getSharingSettings().isPocket());
s.setInstapaper(settings.getSharingSettings().isInstapaper());
s.setBuffer(settings.getSharingSettings().isBuffer());
userSettingsDAO.saveOrUpdate(s);
return Response.ok().build();