-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9cdd3d0
commit db38184
Showing
38 changed files
with
3,230 additions
and
775 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"name": "@shared/id-generator", | ||
"version": "1.0.0", | ||
"dependencies": { | ||
"uuid": "^8.3.0", | ||
"xxhashjs": "^0.2.2" | ||
}, | ||
"main": "src/index.js", | ||
"module": "src/index.js", | ||
"types": "src/index.d.ts", | ||
"private": true, | ||
"scripts": {}, | ||
"devDependencies": { | ||
"@types/uuid": "^8.3.0", | ||
"@types/xxhashjs": "^0.2.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export as namespace idGenerator | ||
|
||
export type generatedId = { | ||
cdtn_id: string | ||
initial_id: string | ||
}; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { v4 as uuidv4 } from "uuid"; | ||
import * as XXH from "xxhashjs"; | ||
|
||
const H = XXH.h64(0x1e7f); | ||
|
||
|
||
export const MAX_ID_LENGTH = 10; | ||
|
||
// use xxhash to hash source + newly generated UUID | ||
/** | ||
* | ||
* @param {string} content | ||
* @param {number} maxIdLength | ||
* @returns {string} | ||
*/ | ||
export const generateCdtnId = (content, maxIdLength = MAX_ID_LENGTH) => | ||
// save 64bits hash as Hexa string up to maxIdLength chars (can be changed later in case of collision) | ||
// as the xxhash function ensure distribution property | ||
H.update(content).digest().toString(16).slice(0, maxIdLength); | ||
|
||
export const generateInitialId = uuidv4; | ||
|
||
// Beware, you might be generating an already existing cdtn_id | ||
/** | ||
* @param {string} source | ||
* @param {number} maxIdLength | ||
* @returns {idGenerator.generatedId} | ||
*/ | ||
export const generateIds = (source, maxIdLength = MAX_ID_LENGTH) => { | ||
const uuid = uuidv4(); | ||
return { | ||
cdtn_id: generateCdtnId(source + uuid, maxIdLength), | ||
initial_id: generateInitialId(), | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"compilerOptions": { | ||
"allowJs": true, | ||
"checkJs": true, | ||
"downlevelIteration": true, | ||
"esModuleInterop": true, | ||
"forceConsistentCasingInFileNames": true, | ||
"noEmit": true, | ||
"resolveJsonModule": true, | ||
"skipLibCheck": true, | ||
"strict": true, | ||
"lib": ["es2019"], | ||
"baseUrl": ".", | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
201 changes: 201 additions & 0 deletions
201
targets/frontend/src/components/forms/ContentPicker/ContentSearch.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
/** @jsx jsx */ | ||
|
||
import { getLabelBySource, SOURCES } from "@socialgouv/cdtn-sources"; | ||
import PropTypes from "prop-types"; | ||
import { useEffect, useState } from "react"; | ||
import Autosuggest from "react-autosuggest"; | ||
import { useDebouncedState } from "src/hooks/index"; | ||
import { Input, jsx } from "theme-ui"; | ||
import { useQuery } from "urql"; | ||
|
||
const sources = [ | ||
SOURCES.SHEET_MT_PAGE, | ||
SOURCES.SHEET_SP, | ||
SOURCES.LETTERS, | ||
SOURCES.TOOLS, | ||
SOURCES.CONTRIBUTIONS, | ||
SOURCES.EXTERNALS, | ||
SOURCES.THEMATIC_FILES, | ||
SOURCES.EDITORIAL_CONTENT, | ||
SOURCES.CDT, | ||
SOURCES.THEMES, | ||
]; | ||
|
||
const AUTOSUGGEST_MAX_RESULTS = 15; | ||
|
||
const searchDocumentsQuery = ` | ||
query searchDocuments($sources: [String!]! = "", $search: String = "") { | ||
documents(where: {title: {_ilike: $search}, source: {_in: $sources}}, limit: ${AUTOSUGGEST_MAX_RESULTS}) { | ||
source | ||
title | ||
cdtnId: cdtn_id | ||
} | ||
} | ||
`; | ||
|
||
export const ContentSearch = ({ contents = [], onChange }) => { | ||
const [suggestions, setSuggestions] = useState([]); | ||
const [inputSearchValue, setInputSearchValue] = useState(""); | ||
const [searchValue, , setDebouncedSearchValue] = useDebouncedState("", 500); | ||
|
||
const [results] = useQuery({ | ||
pause: searchValue.length < 3, | ||
query: searchDocumentsQuery, | ||
variables: { | ||
search: `%${searchValue}%`, | ||
sources, | ||
}, | ||
}); | ||
|
||
useEffect(() => { | ||
const allDocuments = results.data?.documents || []; | ||
const documents = allDocuments.filter( | ||
(document) => | ||
document.source !== SOURCES.THEMES && document.source !== SOURCES.CDT | ||
); | ||
documents.forEach((document) => { | ||
document.category = "document"; | ||
}); | ||
const themes = allDocuments.filter( | ||
(document) => document.source === SOURCES.THEMES | ||
); | ||
const articles = allDocuments.filter( | ||
(document) => document.source === SOURCES.CDT | ||
); | ||
setSuggestions([ | ||
{ | ||
suggestions: documents, | ||
title: "Documents", | ||
}, | ||
{ suggestions: articles, title: "Articles" }, | ||
{ suggestions: themes, title: "Thèmes" }, | ||
]); | ||
}, [results.data]); | ||
|
||
const onSearchValueChange = (event, { newValue }) => { | ||
setInputSearchValue(newValue); | ||
setDebouncedSearchValue(newValue); | ||
}; | ||
const onSuggestionSelected = ( | ||
event, | ||
{ suggestion: { cdtnId, source, title = null } } | ||
) => { | ||
if (contents.find((content) => content.cdtnId === cdtnId)) { | ||
return; | ||
} | ||
onChange(contents.concat([{ cdtnId, source, title }])); | ||
setInputSearchValue(""); | ||
setSuggestions([]); | ||
}; | ||
|
||
const onSuggestionsFetchRequested = async ({ value }) => { | ||
setInputSearchValue(value); | ||
setDebouncedSearchValue(value); | ||
}; | ||
|
||
const onSuggestionsClearRequested = () => { | ||
setSuggestions([]); | ||
}; | ||
|
||
const inputProps = { | ||
onChange: onSearchValueChange, | ||
placeholder: "Rechercher et ajouter un contenu", | ||
value: inputSearchValue, | ||
}; | ||
|
||
return ( | ||
<Autosuggest | ||
suggestions={suggestions} | ||
onSuggestionsFetchRequested={onSuggestionsFetchRequested} | ||
onSuggestionsClearRequested={onSuggestionsClearRequested} | ||
onSuggestionSelected={onSuggestionSelected} | ||
getSuggestionValue={getSuggestionValue} | ||
getSectionSuggestions={getSectionSuggestions} | ||
multiSection={true} | ||
shouldRenderSuggestions={shouldRenderSuggestions} | ||
renderInputComponent={renderInputComponent} | ||
renderSuggestion={renderSuggestion} | ||
renderSuggestionsContainer={renderSuggestionsContainer} | ||
renderSectionTitle={renderSectionTitle} | ||
inputProps={inputProps} | ||
/> | ||
); | ||
}; | ||
|
||
ContentSearch.propTypes = { | ||
contents: PropTypes.array, | ||
onChange: PropTypes.func.isRequired, | ||
}; | ||
|
||
const renderInputComponent = (inputProps) => ( | ||
<Input {...inputProps} ref={inputProps.ref} /> | ||
); | ||
|
||
function shouldRenderSuggestions(value) { | ||
return value.trim().length > 2; | ||
} | ||
function renderSectionTitle(section) { | ||
return section.suggestions.length ? ( | ||
<div | ||
sx={{ | ||
bg: "neutral", | ||
fontWeight: "bold", | ||
p: "xxsmall", | ||
}} | ||
> | ||
{section.title} | ||
</div> | ||
) : null; | ||
} | ||
|
||
function getSectionSuggestions(section) { | ||
return section.suggestions; | ||
} | ||
|
||
const getSuggestionValue = (content) => content.title; | ||
|
||
const renderSuggestion = (content) => ( | ||
<div> | ||
{content.title} | ||
{content.category === "document" && ( | ||
<strong> | {getLabelBySource(content.source)}</strong> | ||
)} | ||
</div> | ||
); | ||
|
||
const renderSuggestionsContainer = ({ containerProps, children }) => ( | ||
<div | ||
sx={{ | ||
'&[class*="container--open"]': { | ||
border: "1px solid #ddd", | ||
borderRadius: "4px", | ||
maxHeight: "300px", | ||
overflow: "scroll", | ||
position: "relative", | ||
top: "4px", | ||
}, | ||
li: { | ||
'&[role="option"]:hover': { | ||
bg: "#dde", | ||
}, | ||
":nth-of-type(2n + 1)": { | ||
bg: "highlight", | ||
}, | ||
bg: "white", | ||
cursor: "pointer", | ||
m: "0", | ||
p: "xxsmall", | ||
zIndex: 2, | ||
}, | ||
ul: { | ||
listStyleType: "none", | ||
m: "0", | ||
p: "0", | ||
width: "100%", | ||
}, | ||
}} | ||
{...containerProps} | ||
> | ||
{children} | ||
</div> | ||
); |
Oops, something went wrong.