Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Sensitive content filter #2153

Closed
wants to merge 48 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
f8988bf
created VSafeBrowsing component and some basic template
anton202 Jan 23, 2023
8bef1c1
created VToggleSwitch component
anton202 Jan 23, 2023
e427fa7
changed some css values to tw class
anton202 Jan 23, 2023
1bd7278
css fix
anton202 Jan 23, 2023
bb6ef9a
add text to en.json
anton202 Jan 23, 2023
5d8193f
removed filtering of mature filter from appliedFilterCount
anton202 Jan 27, 2023
54cfe40
add isMatureFilterChecked computed property
anton202 Jan 27, 2023
92e74af
add prop checked and emited enevt to VSafeBrowsingFilter component
anton202 Jan 27, 2023
db310d3
add props and emited event to VToggleSwitch component
anton202 Jan 27, 2023
12cfa17
update en.json
anton202 Jan 27, 2023
13b05dc
add some styling
anton202 Jan 27, 2023
8a879d9
changed filterToString to check for enable instead of mature
anton202 Jan 30, 2023
f35ea1c
add localized key-value for safe browsing filter category
anton202 Jan 30, 2023
e2b7a79
add 'enable' filter to mature category
anton202 Jan 30, 2023
e73a7ea
removed filtering mature category from searchFilters
anton202 Jan 30, 2023
154aad3
some chnages for VToggleSwitch
anton202 Jan 30, 2023
13c8ecf
encapsulated VFilterCheckList with tamplate element to include the se…
anton202 Jan 30, 2023
6d4a645
add description and toggle switch for safe browsing filter
anton202 Jan 30, 2023
4eac882
removed VSafeBrowsingFilter
anton202 Jan 30, 2023
3118a27
update story
anton202 Jan 30, 2023
6fc36b0
changed back to mature filter instead of enable
anton202 Jan 30, 2023
2362380
Merge branch 'main' into safe-browsing-filter
anton202 Feb 6, 2023
2082eda
put sensetive content filter under toggle_sensetive_content feature flag
anton202 Feb 6, 2023
e5f0cc7
add check if toggle_sensetive_content is enabled
anton202 Feb 7, 2023
3f8cbd6
Create unit test for VToggleSwitch component
anton202 Feb 7, 2023
e3bbd2e
add string lable to toggle switch story book
anton202 Feb 7, 2023
eb378fc
Add visual regression test for VToggleSwitch
anton202 Feb 7, 2023
ec1293f
add + 1 to the amount of filters for each search type
anton202 Feb 7, 2023
71c170b
Merge branch 'main' into safe-browsing-filter
anton202 Feb 7, 2023
c00633c
removed a deleted component from tsconfig.json
anton202 Feb 8, 2023
845ff7f
Update feat/feature-flags.json
zackkrida Feb 8, 2023
845dd6a
Fix typo
zackkrida Feb 8, 2023
67e1110
Fix remaining sensitive titles
zackkrida Feb 8, 2023
44e8263
Update src/components/VFilters/VSearchGridFilter.vue
anton202 Feb 12, 2023
3ac28ff
Update feat/feature-flags.json
anton202 Feb 12, 2023
0956a80
Merge branch 'main' into safe-browsing-filter
anton202 Feb 12, 2023
265ff99
Add focus state
anton202 Feb 12, 2023
907ed20
removed unused css
anton202 Feb 12, 2023
f78bbf7
focusing toggle switch when focused with the Tab key
anton202 Feb 12, 2023
c8afe35
Css fix
anton202 Feb 12, 2023
5f18435
Update visual regression test
anton202 Feb 12, 2023
84b6f80
Revrted changes in filters.spec.ts
anton202 Feb 15, 2023
df55dee
fix
anton202 Feb 15, 2023
57bb29d
Merge branch 'main' into safe-browsing-filter
anton202 Feb 15, 2023
e9ef4d3
Merge branch 'main' into safe-browsing-filter
anton202 Feb 21, 2023
e4779ed
divider line fix
anton202 Feb 21, 2023
23c199f
fixed conflict that was resolved uncorectly
anton202 Feb 21, 2023
33a4500
css fix
anton202 Feb 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions feat/feature-flags.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
"status": "switchable",
"description": "Can be switched between on and off",
"defaultState": "on"
},
"toggle_sensitive_content": {
"status": {
"staging": "switchable",
"production": "disabled"
},
"description": "Toggle sensitive content",
"defaultState": "off"
}
}
}
32 changes: 26 additions & 6 deletions src/components/VFilters/VFilterChecklist.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
<template>
<fieldset class="mb-8">
<legend class="label-bold">{{ title }}</legend>
<fieldset>
<legend class="label-bold" :class="{ 'pt-10': isMatureCategory }">
{{ title }}
</legend>
<p
v-if="isMatureCategory"
class="mb-4 mt-2 max-w-xs text-sm font-normal leading-4"
>
{{ $t("filters.mature.description") }}
</p>
<div
v-for="(item, index) in options"
:key="index"
class="mt-4 flex items-center justify-between"
>
<VToggleSwitch
v-if="isMatureCategory && item.code === 'mature'"
:checked="item.checked"
:name="filterType"
@change="onValueChange"
>
<span class="self-center">{{ itemLabel(item) }}</span>
</VToggleSwitch>
<VCheckbox
v-else
:id="item.code"
:key="index"
:checked="item.checked"
Expand Down Expand Up @@ -55,11 +72,11 @@
</template>

<script lang="ts">
import { defineComponent, PropType } from "@nuxtjs/composition-api"
import { defineComponent, PropType, computed } from "@nuxtjs/composition-api"

import { useSearchStore } from "~/stores/search"
import { useI18n } from "~/composables/use-i18n"
import type { NonMatureFilterCategory, FilterItem } from "~/constants/filters"
import type { FilterCategory, FilterItem } from "~/constants/filters"
import { defineEvent } from "~/types/emits"
import { getElements } from "~/utils/license"

Expand All @@ -75,7 +92,7 @@ import helpIcon from "~/assets/icons/help.svg"
import closeSmallIcon from "~/assets/icons/close-small.svg"

type toggleFilterPayload = {
filterType: NonMatureFilterCategory
filterType: FilterCategory
code: string
}

Expand All @@ -99,7 +116,7 @@ export default defineComponent({
type: String,
},
filterType: {
type: String as PropType<NonMatureFilterCategory>,
type: String as PropType<FilterCategory>,
required: true,
},
disabled: {
Expand All @@ -113,6 +130,8 @@ export default defineComponent({
setup(props, { emit }) {
const i18n = useI18n()

const isMatureCategory = computed(() => props.filterType === "mature")

const itemLabel = (item: FilterItem) =>
["audioProviders", "imageProviders"].indexOf(props.filterType) > -1
? item.name
Expand Down Expand Up @@ -141,6 +160,7 @@ export default defineComponent({
const icons = { help: helpIcon, closeSmall: closeSmallIcon }

return {
isMatureCategory,
icons,
isDisabled,
itemLabel,
Expand Down
51 changes: 41 additions & 10 deletions src/components/VFilters/VSearchGridFilter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,24 @@
</VButton>
</header>
<form ref="filtersFormRef" class="filters-form">
<VFilterChecklist
v-for="filterType in filterTypes"
:key="filterType"
:options="filters[filterType]"
:title="filterTypeTitle(filterType)"
:filter-type="filterType"
@toggle-filter="toggleFilter"
/>
<template v-for="(filterType, index) in filterTypes">
<!-- Divider above the sensitive content filter -->
<div :key="index" class="relative">
<div
v-if="filterType === 'mature' && isSensitiveContentEnabled"
:class="[isMobile ? 'mobileDividerLine' : 'desktopDividerLine']"
class="absolute h-px bg-dark-charcoal-20"
/>
</div>
<VFilterChecklist
:key="filterType"
:options="filters[filterType]"
:title="filterTypeTitle(filterType)"
:filter-type="filterType"
:class="[index === filterTypes.length - 2 ? 'mb-10' : 'mb-8']"
@toggle-filter="toggleFilter"
/>
</template>
</form>
<footer
v-if="showFilterHeader && isAnyFilterApplied"
Expand All @@ -51,8 +61,10 @@ import { kebab } from "case"
import { watchDebounced } from "@vueuse/core"

import { useSearchStore } from "~/stores/search"
import { useUiStore } from "~/stores/ui"
import { useFeatureFlagStore } from "~/stores/feature-flag"
import { areQueriesEqual, ApiQueryParams } from "~/utils/search-query-transform"
import type { NonMatureFilterCategory } from "~/constants/filters"
import type { FilterCategory } from "~/constants/filters"
import { defineEvent } from "~/types/emits"

import VFilterChecklist from "~/components/VFilters/VFilterChecklist.vue"
Expand Down Expand Up @@ -88,16 +100,22 @@ export default defineComponent({
},
setup() {
const searchStore = useSearchStore()
const featureFlagStore = useFeatureFlagStore()
const uiStore = useUiStore()

const { i18n } = useContext()
const router = useRouter()

const isMobile = computed(() => !uiStore.isDesktopLayout)
const filtersFormRef = ref<HTMLFormElement>(null)

const isSensitiveContentEnabled = computed(() =>
featureFlagStore.isOn("toggle_sensitive_content")
)
const isAnyFilterApplied = computed(() => searchStore.isAnyFilterApplied)
const filters = computed(() => searchStore.searchFilters)
const filterTypes = computed(
() => Object.keys(filters.value) as NonMatureFilterCategory[]
() => Object.keys(filters.value) as FilterCategory[]
)
const filterTypeTitle = (filterType: string) =>
i18n.t(`filters.${kebab(filterType)}.title`)
Expand All @@ -117,6 +135,8 @@ export default defineComponent({
)

return {
isMobile,
isSensitiveContentEnabled,
filtersFormRef,
isAnyFilterApplied,
filters,
Expand All @@ -128,3 +148,14 @@ export default defineComponent({
},
})
</script>

<style scoped>
.mobileDividerLine {
width: calc(100% + 3rem);
left: -1.5rem;
}
.desktopDividerLine {
width: calc(100% + 5rem);
left: -2.5rem;
}
</style>
128 changes: 128 additions & 0 deletions src/components/VToggleSwitch/VToggleSwitch.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<template>
<div class="toggle-switch-container text-sm">
<div class="button mr-4">
<label for="toggle-switch" />
<input
id="toggle-switch"
type="checkbox"
class="checkbox"
:name="name"
:checked="checked"
@change="onChange"
/>
<div class="knob" />
<div class="layer" />
</div>
<slot />
</div>
</template>

<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api"

export default defineComponent({
name: "VToggleSwitch",
props: {
/**
* Whether the checkbox is checked or not.
*
* @default false
*/
checked: {
type: Boolean,
required: false,
default: false,
},
name: {
type: String,
required: false,
},
},
emit: ["change"],
setup(props, { emit }) {
const onChange = () => {
emit("change", { value: props.name })
}

return {
onChange,
}
},
})
</script>

<style scoped>
.toggle-switch-container {
display: flex;
}
.button {
position: relative;
width: 36px;
height: 18px;
}

.button,
.button .layer {
border-radius: 100px;
}

.knob {
z-index: 2;
}

.layer {
width: 100%;
background-color: #ffffff;
border: 1px solid #1e1e1e;
z-index: 1;
@apply ease-linear;
}

.knob,
.layer {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}

.checkbox {
position: relative;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
opacity: 0;
cursor: pointer;
z-index: 3;
}

.checkbox:focus-visible ~ .layer {
outline: 1.5px solid #c52b9b;
outline-offset: 1px;
}

.button .knob:before {
content: "";
position: absolute;
top: 3px;
left: 4px;
width: 12px;
height: 12px;
background-color: #1e1e1e;
border-radius: 50%;
@apply ease-linear;
}

.button .checkbox:checked + .knob:before {
content: "";
left: 21px;
@apply bg-white;
}

.button .checkbox:checked ~ .layer {
@apply bg-trans-blue;
border: none;
}
</style>
22 changes: 22 additions & 0 deletions src/components/VToggleSwitch/meta/VToggleSwitch.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Canvas, Meta, Story } from "@storybook/addon-docs"

import VToggleSwitch from "~/components/VToggleSwitch/VToggleSwitch.vue"
import { WithScreenshotArea } from "~~/.storybook/decorators/with-screenshot-area"

<Meta
title="Components/VToggleSwitch"
component={VToggleSwitch}
decorators={[WithScreenshotArea]}
/>

export const Template = (args) => ({
template: `<VToggleSwitch v-bind="args">Toggle switch</VToggleSwitch>`,
components: { VToggleSwitch },
setup() {
return { args }
},
})

<Canvas>
<Story name="Default">{Template.bind({})}</Story>
</Canvas>
5 changes: 3 additions & 2 deletions src/locales/scripts/en.json5
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,9 @@
large: "Large",
},
mature: {
title: "Search Settings",
enable: "Enable Mature Content",
title: "Safe browsing",
description: "By default, content reported as sensitive is not included in the results.",
mature: "Show Sensitive Content",
},
lengths: {
title: "Duration",
Expand Down
15 changes: 10 additions & 5 deletions src/stores/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,20 @@ export const useSearchStore = defineStore("search", {
},

/**
* Returns the object with filters for selected search type,
* with codes, names for i18n labels, and checked status.
*
* Excludes `searchBy` and `mature` filters that we don't display.
* Returns the object with filters for selected search type, with codes, names for i18n labels, and checked status.
* If `mature` filter is not eanbled, it is not included in the result.
* also excludes `searchBy` filter.
*/
searchFilters(state) {
return mediaFilterKeys[state.searchType]
.filter((filterKey) => !["searchBy", "mature"].includes(filterKey))
.filter((filterKey) => !["searchBy"].includes(filterKey))
.reduce((obj, filterKey) => {
if (
filterKey === "mature" &&
!useFeatureFlagStore().isOn("toggle_sensitive_content")
) {
return obj
}
obj[filterKey] = this.filters[filterKey]
return obj
}, {} as Filters)
Expand Down
5 changes: 3 additions & 2 deletions src/utils/search-query-transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ const getMediaFilterTypes = (searchType: SearchType) => {
/**
* Joins all the filters which have the checked property `true`
* to a string separated by commas for the API request URL, e.g.: "by,nd-nc,nc-sa".
* Mature is a special case, and is converted to `true`.
* 'enable' means, to enable mature content. for now there is no filters
* under the Mature category (its named Safe browsing). if there is going to
* be more filters this function will need to be changed
*/
const filterToString = (filterItem: FilterItem[]) => {
const filterString = filterItem
Expand All @@ -91,7 +93,6 @@ export const filtersToQueryData = (
hideEmpty = true
): ApiQueryFilters => {
const mediaFilterTypes = getMediaFilterTypes(searchType)

return mediaFilterTypes.reduce((query, filterCategory) => {
const queryKey = filterPropertyMappings[filterCategory]
const queryValue = filterToString(filters[filterCategory])
Expand Down
Loading