diff --git a/packages/web/components/elements/Label.tsx b/packages/web/components/elements/Label.tsx
new file mode 100644
index 000000000..2a666e0b0
--- /dev/null
+++ b/packages/web/components/elements/Label.tsx
@@ -0,0 +1,34 @@
+import { styled } from './../tokens/stitches.config'
+import { Root, Image, Fallback } from '@radix-ui/react-avatar'
+import { StyledText } from './StyledText'
+
+type LabelProps = {
+ text: string
+ color: string // expected to be a RGB hex color string
+}
+
+export function Label(props: LabelProps): JSX.Element {
+ const hexToRgb = (hex: string) => {
+ var bigint = parseInt(hex.substring(1), 16);
+ var r = (bigint >> 16) & 255;
+ var g = (bigint >> 8) & 255;
+ var b = bigint & 255;
+
+ return [r,g,b];
+ }
+ const color = hexToRgb(props.color)
+ return (
+
+ {props.text}
+
+ )
+}
diff --git a/packages/web/components/templates/article/ArticleContainer.tsx b/packages/web/components/templates/article/ArticleContainer.tsx
index 0611b2c09..4058b32d5 100644
--- a/packages/web/components/templates/article/ArticleContainer.tsx
+++ b/packages/web/components/templates/article/ArticleContainer.tsx
@@ -17,6 +17,7 @@ import { ShareArticleModal } from './ShareArticleModal'
import { userPersonalizationMutation } from '../../../lib/networking/mutations/userPersonalizationMutation'
import { webBaseURL } from '../../../lib/appConfig'
import { updateThemeLocally } from '../../../lib/themeUpdater'
+import { EditLabelsModal } from './EditLabelsModal'
type ArticleContainerProps = {
viewerUsername: string
@@ -32,6 +33,7 @@ type ArticleContainerProps = {
export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
const [showShareModal, setShowShareModal] = useState(false)
+ const [showLabelsModal, setShowLabelsModal] = useState(false)
const [showNotesSidebar, setShowNotesSidebar] = useState(false)
const [showReportIssuesModal, setShowReportIssuesModal] = useState(false)
const [fontSize, setFontSize] = useState(props.fontSize ?? 20)
@@ -56,6 +58,9 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
case 'decrementFontSize':
updateFontSize(Math.max(fontSize - 2, 10))
break
+ case 'editLabels':
+ setShowLabelsModal(true)
+ break
}
})
)
@@ -234,6 +239,11 @@ export function ArticleContainer(props: ArticleContainerProps): JSX.Element {
onOpenChange={(open: boolean) => setShowShareModal(open)}
/>
)}
+ {showLabelsModal && (
+ {
+ setShowLabelsModal(false)
+ }} />
+ )}
>
)
}
diff --git a/packages/web/components/templates/article/EditLabelsModal.tsx b/packages/web/components/templates/article/EditLabelsModal.tsx
new file mode 100644
index 000000000..b266e05c3
--- /dev/null
+++ b/packages/web/components/templates/article/EditLabelsModal.tsx
@@ -0,0 +1,102 @@
+import {
+ ModalRoot,
+ ModalContent,
+ ModalOverlay,
+} from '../../elements/ModalPrimitives'
+import { HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
+import { Button } from '../../elements/Button'
+import { StyledText } from '../../elements/StyledText'
+import { CrossIcon } from '../../elements/images/CrossIcon'
+import { theme } from '../../tokens/stitches.config'
+import { useGetLabelsQuery } from '../../../lib/networking/queries/useGetLabelsQuery'
+import { ChangeEvent, useCallback, useState } from 'react'
+import { Label } from '../../elements/Label'
+import { ArticleAttributes } from '../../../lib/networking/queries/useGetArticleQuery'
+import { setLabelsMutation } from '../../../lib/networking/mutations/setLabelsMutation'
+
+type EditLabelsModalProps = {
+ labels: string[]
+ article: ArticleAttributes
+ onOpenChange: (open: boolean) => void
+}
+
+export function EditLabelsModal(
+ props: EditLabelsModalProps
+): JSX.Element {
+ const [selectedLabels, setSelectedLabels] = useState(props.labels)
+ const { labels, revalidate, isValidating } = useGetLabelsQuery()
+
+ const saveAndExit = useCallback(async () => {
+ if (selectedLabels.length > 0) {
+ const result = await setLabelsMutation(
+ props.article.id,
+ selectedLabels,
+ )
+ console.log('result of setting labels', result)
+ }
+ props.onOpenChange(false)
+ }, [selectedLabels, props.onOpenChange])
+
+ const handleChange = useCallback((event: ChangeEvent) => {
+ const label = event.target.value
+ if (event.target.checked) {
+ setSelectedLabels([...selectedLabels, label])
+ } else {
+ setSelectedLabels(selectedLabels.filter((l) => l !== label))
+ }
+ }, [selectedLabels, setSelectedLabels])
+
+ return (
+
+
+ {
+ event.preventDefault()
+ }}
+ css={{ overflow: 'auto', p: '0' }}
+ >
+
+
+ Edit Labels
+
+
+ {labels && labels.map((label) => (
+ {
+ if (selectedLabels.includes(label.id)) {
+ setSelectedLabels(selectedLabels.filter((id) => id !== label.id))
+ } else {
+ setSelectedLabels([...selectedLabels, label.id])
+ }
+ }}>
+
+
+
+ ))}
+
+
+
+
+
+
+ )
+}
diff --git a/packages/web/lib/keyboardShortcuts/navigationShortcuts.ts b/packages/web/lib/keyboardShortcuts/navigationShortcuts.ts
index 3bf61288f..29c5eff7e 100644
--- a/packages/web/lib/keyboardShortcuts/navigationShortcuts.ts
+++ b/packages/web/lib/keyboardShortcuts/navigationShortcuts.ts
@@ -210,6 +210,7 @@ type ArticleKeyboardAction =
| 'openOriginalArticle'
| 'incrementFontSize'
| 'decrementFontSize'
+ | 'editLabels'
export function articleKeyboardCommands(
actionHandler: (action: ArticleKeyboardAction) => void
@@ -233,5 +234,11 @@ export function articleKeyboardCommands(
shortcutKeyDescription: '-',
callback: () => actionHandler('decrementFontSize'),
},
+ {
+ shortcutKeys: ['l'],
+ actionDescription: 'Edit labels',
+ shortcutKeyDescription: 'l',
+ callback: () => actionHandler('editLabels'),
+ },
]
}
diff --git a/packages/web/lib/networking/mutations/setLabelsMutation.ts b/packages/web/lib/networking/mutations/setLabelsMutation.ts
new file mode 100644
index 000000000..3136daa23
--- /dev/null
+++ b/packages/web/lib/networking/mutations/setLabelsMutation.ts
@@ -0,0 +1,29 @@
+import { gql } from 'graphql-request'
+import { gqlFetcher } from '../networkHelpers'
+
+export async function setLabelsMutation(
+ linkId: string,
+ labelIds: string[],
+): Promise {
+ const mutation = gql`
+ mutation SetLabels($input: SetLabelsInput!) {
+ setLabels(input: $input) {
+ ... on SetLabelsSuccess {
+ labels {
+ id
+ }
+ }
+ ... on SetLabelsError {
+ errorCodes
+ }
+ }
+ }`
+
+ try {
+ const data = await gqlFetcher(mutation, { input: { linkId, labelIds }})
+ return data
+ } catch (error) {
+ console.log('SetLabelsOutput error', error)
+ return undefined
+ }
+}