forked from Archives/Athou_commafeed
add sharing buttons
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
55
commafeed-client/src/components/content/ShareButtons.tsx
Normal file
55
commafeed-client/src/components/content/ShareButtons.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user