Skip to content
This repository has been archived by the owner on Sep 7, 2024. It is now read-only.

Commit

Permalink
added support for tag normalization
Browse files Browse the repository at this point in the history
  • Loading branch information
LeXofLeviafan committed Mar 3, 2023
1 parent 467987a commit b871706
Show file tree
Hide file tree
Showing 10 changed files with 83 additions and 13 deletions.
1 change: 1 addition & 0 deletions makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ nuke:
webext:
${MAKE} prepare
yarn && yarn build
rm -f '$(RELEASE_DIR)/webext.zip'
cd dist && zip -r '../$(RELEASE_DIR)/webext' ./*
${MAKE} clean

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"lint": "eslint ./src/ --ext ts,tsx",
"dev": "yarn run prepare && parcel watch ./src/content.html ./src/options.html ./src/backend.ts",
"build": "yarn run prepare && parcel build ./src/content.html ./src/options.html ./src/backend.ts",
"make": "make",
"test": "jest",
"fmt": "prettier --write .",
"fmt-check": "prettier --check ."
Expand Down
29 changes: 21 additions & 8 deletions src/components/bookmark-form.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useState, useEffect, useRef, FormEvent, FC } from "react"
import { useSelector } from "react-redux"
import { pipe } from "fp-ts/lib/pipeable"
import * as O from "fp-ts/lib/Option"
import styled from "~/styles"
Expand Down Expand Up @@ -66,6 +67,7 @@ type KeyofStringValues<T> = {
}[keyof T]

const BookmarkForm: FC<Props> = props => {
const normalizeTags = useSelector(state => state.user.normalizeTags)
const firstInputRef = useRef<HTMLInputElement>(null)

const [bookmarkInput, setBookmarkInput] = useState<BookmarkInput>({
Expand Down Expand Up @@ -103,23 +105,34 @@ const BookmarkForm: FC<Props> = props => {
setInputBookmarkPartial({ [key]: input })
}

const inputNormalizedTags = (changedTags: string[]): void => {
const tags = changedTags.map(s => s.trim().toLowerCase()).filter(Boolean)
setInputBookmarkPartial({ tags: Array.from(new Set(tags)).sort() })
}

const handleTagAddition = (evt: FormEvent<HTMLFormElement>): void => {
evt.preventDefault()
evt.stopPropagation()

const newTag = tagInput.trim()
if (normalizeTags) {
inputNormalizedTags([...bookmarkInput.tags, ...newTag.split(",")])
} else {
// Disallow adding an empty tag or the same tag twice
if (!newTag || bookmarkInput.tags.includes(newTag)) return

// Disallow adding an empty tag or the same tag twice
if (!newTag || bookmarkInput.tags.includes(newTag)) return

setInputBookmarkPartial({ tags: [...bookmarkInput.tags, newTag] })
setInputBookmarkPartial({ tags: [...bookmarkInput.tags, newTag] })
}
setTagInput("")
}

const handleTagRemoval = (tagToRemove: string): void => {
setInputBookmarkPartial({
tags: bookmarkInput.tags.filter(tag => tag !== tagToRemove),
})
const tags = bookmarkInput.tags.filter(tag => tag !== tagToRemove)
if (normalizeTags) {
inputNormalizedTags(tags)
} else {
setInputBookmarkPartial({ tags })
}
}

const handleSubmit = (evt: FormEvent<HTMLFormElement>): void => {
Expand Down Expand Up @@ -179,7 +192,7 @@ const BookmarkForm: FC<Props> = props => {
value={tagInput}
onInput={setTagInput}
form="tags"
label="Tags"
label={"Tags" + (!normalizeTags ? "" : " [normalized]")}
/>

<AddTagButton type="submit" form="tags" tabIndex={-1}>
Expand Down
1 change: 1 addition & 0 deletions src/components/tag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const TagItem = styled.li<{ removable: boolean }>`
font-size: 1.3rem;
font-weight: normal;
color: ${(props): string => props.theme.textColorOffset};
white-space: pre-wrap;
${(props): FlattenSimpleInterpolation | false =>
props.removable &&
Expand Down
17 changes: 16 additions & 1 deletion src/modules/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,15 @@ const badgeDisplayCodec = fromRefinement<BadgeDisplay>(
(x): x is BadgeDisplay => t.string.is(x) && isBadgeDisplayOpt(x),
)

export const isBool: Refinement<unknown, boolean> =
(x): x is boolean => typeof x === 'boolean'

const normalizeTagsCodec = fromRefinement<boolean>("normalizeTags", isBool)

const settingsCodec = t.type({
theme: optionFromNullable(themeCodec),
badgeDisplay: optionFromNullable(badgeDisplayCodec),
normalizeTags: optionFromNullable(normalizeTagsCodec),
})

export type Settings = t.TypeOf<typeof settingsCodec>
Expand All @@ -52,15 +58,17 @@ type SaveableSettings = Partial<UnwrapOptions<Settings>>
const saveableSettings = (x: Settings): SaveableSettings => ({
theme: O.toUndefined(x.theme),
badgeDisplay: O.toUndefined(x.badgeDisplay),
normalizeTags: O.toUndefined(x.normalizeTags),
})

const theme = Lens.fromProp<Settings>()("theme")
const badgeDisplay = Lens.fromProp<Settings>()("badgeDisplay")
const normalizeTags = Lens.fromProp<Settings>()("normalizeTags")

export const saveSettings = flow(saveableSettings, setSyncStorage)

const getSettings: TaskEither<Error, Settings> = pipe(
getSyncStorage(["theme", "badgeDisplay"]),
getSyncStorage(["theme", "badgeDisplay", "normalizeTags"]),
T.map(E.chain(decode(settingsCodec))),
)

Expand All @@ -75,3 +83,10 @@ export const getBadgeDisplayOpt: TaskEither<Error, Option<BadgeDisplay>> = pipe(
E.map(flow(badgeDisplay.get, O.chain(O.fromPredicate(isBadgeDisplayOpt)))),
),
)

export const getNormalizeTags: TaskEither<Error, Option<boolean>> = pipe(
getSettings,
T.map(
E.map(flow(normalizeTags.get, O.chain(O.fromPredicate(isBool)))),
),
)
21 changes: 19 additions & 2 deletions src/pages/options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useState, useEffect, FC, FormEvent } from "react"
import * as O from "fp-ts/lib/Option"
import * as EO from "~/modules/eitherOption"
import { useDispatch, useSelector } from "~/store"
import { setActiveTheme } from "~/store/user/actions"
import { setActiveTheme, setNormalizeTags } from "~/store/user/actions"
import {
saveSettings,
getBadgeDisplayOpt,
Expand All @@ -25,10 +25,12 @@ const Page = styled.main`

const OptionsPage: FC = () => {
const activeTheme = useSelector(state => state.user.activeTheme)
const normalizeTags = useSelector(state => state.user.normalizeTags)
const dispatch = useDispatch()

const [themeOpt, setThemeOpt] = useState(activeTheme)
const [badgeOpt, setBadgeOpt] = useState(BadgeDisplay.WithCount)
const [normTagsOpt, setNormTagsOpt] = useState(normalizeTags)

useEffect(() => {
getBadgeDisplayOpt().then(res => {
Expand All @@ -42,6 +44,10 @@ const OptionsPage: FC = () => {
setThemeOpt(activeTheme)
}, [activeTheme])

useEffect(() => {
setNormTagsOpt(normalizeTags)
}, [normalizeTags])

const handleThemeOptChange = (evt: FormEvent<HTMLSelectElement>): void => {
const themeOpt = evt.currentTarget.value
if (!isTheme(themeOpt)) return
Expand All @@ -56,13 +62,18 @@ const OptionsPage: FC = () => {
setBadgeOpt(badgeOpt)
}

const handleNormTagsOptChange = (evt: FormEvent<HTMLInputElement>): void => {
setNormTagsOpt(evt.currentTarget.checked)
}

const handleSubmit = (evt: FormEvent<HTMLFormElement>): void => {
evt.preventDefault()

dispatch(setActiveTheme(themeOpt))
runTask(sendIsomorphicMessage(IsomorphicMessage.SettingsUpdated))
runTask(
saveSettings({ theme: O.some(themeOpt), badgeDisplay: O.some(badgeOpt) }),
saveSettings({ theme: O.some(themeOpt), badgeDisplay: O.some(badgeOpt),
normalizeTags: O.some(normTagsOpt) }),
)
}

Expand All @@ -84,6 +95,12 @@ const OptionsPage: FC = () => {
</select>
<br />
<br />
<label title="Split by comma, trim spaces, lowercase, sort, remove duplicates">
<input type="checkbox" checked={normTagsOpt} onChange={handleNormTagsOptChange}/>
&nbsp;Normalize tags on edit (reproduce buku behaviour)
</label>
<br />
<br />
<Button type="submit">Save Settings</Button>
</form>
</Page>
Expand Down
15 changes: 13 additions & 2 deletions src/store/epics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ import {
checkBinaryVersionFromNative,
HostVersionCheckResult,
} from "~/modules/comms/native"
import { getActiveTheme, Theme } from "~/modules/settings"
import { getActiveTheme, getNormalizeTags, Theme } from "~/modules/settings"
import { ThunkAC, initAutoStoreSync } from "~/store"
import {
setLimitNumRendered,
setFocusedBookmarkIndex,
} from "~/store/bookmarks/actions"
import { setActiveTheme, hostCheckResult, setPage } from "~/store/user/actions"
import {
setActiveTheme,
setNormalizeTags,
hostCheckResult,
setPage,
} from "~/store/user/actions"
import { setSearchFilter } from "~/store/input/actions"
import { addPermanentError } from "~/store/notices/epics"
import {
Expand Down Expand Up @@ -76,6 +81,12 @@ export const onLoad = (): ThunkAC<Promise<void>> => async dispatch => {
dispatch(setActiveTheme(theme))
})

getNormalizeTags()
.then(EO.getOrElse(constant<boolean>(false)))
.then(normTags => {
dispatch(setNormalizeTags(normTags))
})

const res = await checkBinaryVersionFromNative()
dispatch(hostCheckResult(res))

Expand Down
3 changes: 3 additions & 0 deletions src/store/user/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export const hostCheckResult = (comms: HostVersionCheckResult) =>
export const setActiveTheme = (theme: Theme) =>
action(UserActionTypes.SetActiveTheme, theme)

export const setNormalizeTags = (value: boolean) =>
action(UserActionTypes.SetNormalizeTags, value)

export const setDisplayOpenAllBookmarksConfirmation = (display: boolean) =>
action(UserActionTypes.SetDisplayOpenAllBookmarksConfirmation, display)

Expand Down
5 changes: 5 additions & 0 deletions src/store/user/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Page,
comms,
activeTheme,
normalizeTags,
displayOpenAllBookmarksConfirmation,
page,
} from "./types"
Expand All @@ -19,6 +20,7 @@ export type UserActions = ActionType<typeof userActions>
const initialState: UserState = {
comms: HostVersionCheckResult.Unchecked,
activeTheme: Theme.Light,
normalizeTags: false,
displayOpenAllBookmarksConfirmation: false,
page: Page.Search,
}
Expand All @@ -32,6 +34,9 @@ const userReducer = curryReducer<UserActions, UserState>(a => _s => {
case UserActionTypes.SetActiveTheme:
return activeTheme.set(a.payload)

case UserActionTypes.SetNormalizeTags:
return normalizeTags.set(a.payload)

case UserActionTypes.SetDisplayOpenAllBookmarksConfirmation:
return displayOpenAllBookmarksConfirmation.set(a.payload)

Expand Down
3 changes: 3 additions & 0 deletions src/store/user/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export { Theme }
export interface UserState {
comms: HostVersionCheckResult
activeTheme: Theme
normalizeTags: boolean
displayOpenAllBookmarksConfirmation: boolean
page: Page
}
Expand All @@ -15,6 +16,7 @@ export const userL = Lens.fromProp<AppState>()("user")

export const comms = Lens.fromProp<UserState>()("comms")
export const activeTheme = Lens.fromProp<UserState>()("activeTheme")
export const normalizeTags = Lens.fromProp<UserState>()("normalizeTags")
export const displayOpenAllBookmarksConfirmation = Lens.fromProp<UserState>()(
"displayOpenAllBookmarksConfirmation",
)
Expand All @@ -25,6 +27,7 @@ export const commsL = userL.compose(comms)
export enum UserActionTypes {
HostCheckResult = "HOST_CHECK_RESULT",
SetActiveTheme = "SET_ACTIVE_THEME",
SetNormalizeTags = "SET_NORMALIZE_TAGS",
SetDisplayOpenAllBookmarksConfirmation = "SET_OPEN_ALL_BOOKMARKS_CONFIRMATION",
SetPage = "SET_PAGE",
}
Expand Down

0 comments on commit b871706

Please sign in to comment.