diff --git a/front/dockerfile b/front/dockerfile
index a541918d1..f9dcb9dae 100644
--- a/front/dockerfile
+++ b/front/dockerfile
@@ -2,6 +2,7 @@
FROM node:14-alpine as app-builder
WORKDIR /app
COPY gatsby/package*.json ./
+RUN apk add --update-cache git
RUN npm ci --silent
COPY gatsby ./
RUN npm run build
diff --git a/front/gatsby/package.json b/front/gatsby/package.json
index fd4f3e343..992efedb3 100644
--- a/front/gatsby/package.json
+++ b/front/gatsby/package.json
@@ -20,7 +20,7 @@
"dependencies": {
"@rjsf/core": "^2.5.1",
"biblatex-csl-converter": "^1.11.0",
- "codemirror": "^5.61.1",
+ "codemirror": "^5.59.4",
"diff-match-patch": "^1.0.5",
"downshift": "^6.1.3",
"http-link-header": "^1.0.2",
diff --git a/front/gatsby/snowpack.config.cjs b/front/gatsby/snowpack.config.cjs
index d0a6bef2a..48a701a35 100644
--- a/front/gatsby/snowpack.config.cjs
+++ b/front/gatsby/snowpack.config.cjs
@@ -29,6 +29,6 @@ module.exports = {
'@snowpack/plugin-react-refresh',
],
devOptions: {
- port: 3000,
+ port: 3000
},
}
diff --git a/front/gatsby/src/components/Write/Biblio.jsx b/front/gatsby/src/components/Write/Biblio.jsx
index cb7dff049..b77987d62 100644
--- a/front/gatsby/src/components/Write/Biblio.jsx
+++ b/front/gatsby/src/components/Write/Biblio.jsx
@@ -8,7 +8,7 @@ import Bibliographe from './bibliographe/Bibliographe'
import menuStyles from './menu.module.scss'
import Button from '../Button'
-export default (props) => {
+export default function Biblio ({ bib, article, bibTeXEntries, handleBib, readOnly }) {
const [expand, setExpand] = useState(true)
const [modal, setModal] = useState(false)
@@ -19,10 +19,10 @@ export default (props) => {
{expand && (
<>
- {!props.readOnly && (
+ {!readOnly && (
)}
- {props.bibTeXEntries.map((entry, index) => (
+ {bibTeXEntries.map((entry, index) => (
))}
>
@@ -30,10 +30,10 @@ export default (props) => {
{modal && (
setModal(false)}>
setModal(false)}
- article={props.article}
+ article={article}
/>
)}
diff --git a/front/gatsby/src/components/Write/Sommaire.jsx b/front/gatsby/src/components/Write/Sommaire.jsx
index 51b29b631..0a742d88c 100644
--- a/front/gatsby/src/components/Write/Sommaire.jsx
+++ b/front/gatsby/src/components/Write/Sommaire.jsx
@@ -1,28 +1,16 @@
import React, { useState } from 'react'
-import { ChevronDown, ChevronRight, Bookmark } from 'react-feather'
+import { ChevronDown, ChevronRight } from 'react-feather'
import styles from './sommaire.module.scss'
import menuStyles from './menu.module.scss'
+import { connect } from 'react-redux'
-export default function Sommaire (props) {
- const [expand, setExpand] = useState(true)
- // eslint-disable-next-line
- const lines = props.md
- .split('\n')
- .map((l, i) => {
- return { line: i, payload: l }
- })
- .filter((l) => l.payload.match(/^##+\ /))
+const mapStateToProps = ({ articleStructure }) => ({ articleStructure })
- //arrow backspace \u21B3
- // right arrow \u2192
- // nbsp \xa0
- // down arrow \u2223
- // right tack \u22A2
- // bottom left box drawing \u2514
- // left drawing \u2502
- //23B8
+function Sommaire (props) {
+ const [expand, setExpand] = useState(true)
+ const { articleStructure } = props
return (
@@ -30,19 +18,18 @@ export default function Sommaire (props) {
{expand ? : } Table of contents
{expand && (
- {lines.map((l) => (
+ {articleStructure.map((item) => (
- props.setCodeMirrorCursor(l.line)}
+ key={`line-${item.index}-${item.line}`}
+ onClick={() => props.setCodeMirrorCursor(item.index)}
>
- {l.payload
- .replace(/##/, '')
- .replace(/#\s/g, '\u21B3')
- .replace(/#/g, '\u00B7\xa0')}
+ {item.title}
))}
)}
)
}
+
+export default connect(mapStateToProps)(Sommaire)
diff --git a/front/gatsby/src/components/Write/Stats.jsx b/front/gatsby/src/components/Write/Stats.jsx
index dc10b5f6b..8dd36f5bf 100644
--- a/front/gatsby/src/components/Write/Stats.jsx
+++ b/front/gatsby/src/components/Write/Stats.jsx
@@ -3,23 +3,9 @@ import { ChevronDown, ChevronRight } from 'react-feather'
import menuStyles from './menu.module.scss'
-export default (props) => {
+export default ({ stats }) => {
const [expand, setExpand] = useState(true)
- let value = props.md || ''
- let regex = /\s+/gi
- let citation = /\[@[\w-]+/gi
- let noMarkDown = /[#_*]+\s?/gi
- let wordCount = value
- .trim()
- .replace(noMarkDown, '')
- .replace(regex, ' ')
- .split(' ').length
- let charCountNoSpace = value.replace(noMarkDown, '').replace(regex, '').length
- let charCountPlusSpace = value.replace(noMarkDown, '').length
- let citationNb =
- value.replace(regex, '').replace(citation, ' ').split(' ').length - 1
-
return (
setExpand(!expand)}>
@@ -27,10 +13,10 @@ export default (props) => {
{expand && (
<>
- Words : {wordCount}
- Characters : {charCountNoSpace}
- Characters (with spaces) : {charCountPlusSpace}
- Citations : {citationNb}
+ Words : {stats.wordCount}
+ Characters : {stats.charCountNoSpace}
+ Characters (with spaces) : {stats.charCountPlusSpace}
+ Citations : {stats.citationNb}
>
)}
diff --git a/front/gatsby/src/components/Write/Write.jsx b/front/gatsby/src/components/Write/Write.jsx
index a5c1ab246..de083052c 100644
--- a/front/gatsby/src/components/Write/Write.jsx
+++ b/front/gatsby/src/components/Write/Write.jsx
@@ -1,7 +1,8 @@
-import React, { useEffect, useRef, useState } from 'react'
-import { connect } from 'react-redux'
+import React, { useCallback, useEffect, useRef, useState } from 'react'
+import { connect, useDispatch } from 'react-redux'
import 'codemirror/mode/markdown/markdown'
import { Controlled as CodeMirror } from 'react-codemirror2'
+import throttle from 'lodash/throttle'
import askGraphQL from '../../helpers/graphQL'
import styles from './write.module.scss'
@@ -19,10 +20,19 @@ const mapStateToProps = ({ sessionToken, activeUser, applicationConfig }) => {
return { sessionToken, activeUser, applicationConfig }
}
-const ConnectedWrite = (props) => {
- const readOnly = Boolean(props.version)
+function ConnectedWrite(props) {
+ const { version: currentVersion } = props
+ const [readOnly, setReadOnly] = useState(Boolean(currentVersion))
+ const dispatch = useDispatch()
+ const deriveArticleStructureAndStats = useCallback(
+ throttle(({ md }) => {
+ dispatch({ type: 'UPDATE_ARTICLE_STATS', md })
+ dispatch({ type: 'UPDATE_ARTICLE_STRUCTURE', md })
+ }, 250, { leading: false, trailing: true }),
+ []
+ )
- const fullQuery = `query($article:ID!, $readOnly: Boolean!, $version:ID!) {
+ const fullQuery = `query($article:ID!, $hasVersion: Boolean!, $version:ID!) {
article(article:$article) {
_id
title
@@ -42,7 +52,7 @@ const ConnectedWrite = (props) => {
}
}
- live @skip (if: $readOnly) {
+ live @skip (if: $hasVersion) {
md
bib
yaml
@@ -53,7 +63,7 @@ const ConnectedWrite = (props) => {
}
}
- version(version: $version) @include (if: $readOnly) {
+ version(version: $version) @include (if: $hasVersion) {
_id
md
bib
@@ -83,8 +93,8 @@ const ConnectedWrite = (props) => {
const variables = {
user: props.activeUser && props.activeUser._id,
article: props.id,
- version: props.version || '0123456789ab',
- readOnly,
+ version: currentVersion || '0123456789ab',
+ hasVersion: typeof currentVersion === 'string'
}
const [graphqlError, setError] = useState()
@@ -114,7 +124,29 @@ const ConnectedWrite = (props) => {
const sendVersion = async (autosave = true, major = false, message = '') => {
try {
- const query = `mutation($user:ID!,$article:ID!,$md:String!,$bib:String!,$yaml:String!,$autosave:Boolean!,$major:Boolean!,$message:String){saveVersion(version:{article:$article,major:$major,auto:$autosave,md:$md,yaml:$yaml,bib:$bib,message:$message},user:$user){ _id version revision message autosave updatedAt owner{ displayName }} }`
+ const query = `mutation($user: ID!, $article: ID!, $md: String!, $bib: String!, $yaml: String!, $autosave: Boolean!, $major: Boolean!, $message: String) {
+ saveVersion(version: {
+ article: $article,
+ major: $major,
+ auto: $autosave,
+ md: $md,
+ yaml: $yaml,
+ bib: $bib,
+ message: $message
+ },
+ user: $user
+ ) {
+ _id
+ version
+ revision
+ message
+ autosave
+ updatedAt
+ owner {
+ displayName
+ }
+ }
+}`
const response = await askGraphQL(
{
query,
@@ -153,6 +185,8 @@ const ConnectedWrite = (props) => {
}, [debouncedLive])
const handleMDCM = async (___, __, md) => {
+ deriveArticleStructureAndStats({ md })
+
await setLive({ ...live, md: md })
}
const handleYaml = async (yaml) => {
@@ -165,32 +199,40 @@ const ConnectedWrite = (props) => {
//Reload when version switching
useEffect(() => {
setIsLoading(true)
+ setReadOnly(currentVersion)
;(async () => {
const data = await askGraphQL(
{ query: fullQuery, variables },
'fetching Live version',
props.sessionToken,
props.applicationConfig
- )
- .then(({ version, article }) => ({ version, article }))
- .catch((error) => {
- setError(error)
- return {}
- })
+ ).then(({ version, article }) => ({ version, article })
+ ).catch((error) => {
+ setError(error)
+ return {}
+ })
if (data?.article) {
- setLive(props.version ? data.version : data.article.live)
+ const article = data.article
+ const version = currentVersion ? data.version : article.live
+ setLive(version)
setArticleInfos({
- _id: data.article._id,
- title: data.article.title,
- zoteroLink: data.article.zoteroLink,
- owners: data.article.owners.map((o) => o.displayName),
+ _id: article._id,
+ title: article.title,
+ zoteroLink: article.zoteroLink,
+ owners: article.owners.map((o) => o.displayName),
})
- setVersions(data.article.versions)
+
+ setVersions(article.versions)
+
+ const md = version.md
+ dispatch({ type: 'UPDATE_ARTICLE_STATS', md })
+ dispatch({ type: 'UPDATE_ARTICLE_STRUCTURE', md })
}
+
setIsLoading(false)
})()
- }, [props.version])
+ }, [currentVersion])
if (graphqlError) {
return (
@@ -213,7 +255,7 @@ const ConnectedWrite = (props) => {
article={articleInfos}
{...live}
compareTo={props.compareTo}
- selectedVersion={props.version}
+ selectedVersion={currentVersion}
versions={versions}
readOnly={readOnly}
sendVersion={sendVersion}
@@ -230,7 +272,7 @@ const ConnectedWrite = (props) => {
versions={versions}
readOnly={readOnly}
article={articleInfos}
- selectedVersion={props.version}
+ selectedVersion={currentVersion}
/>
)}
diff --git a/front/gatsby/src/components/Write/WriteLeft.jsx b/front/gatsby/src/components/Write/WriteLeft.jsx
index 5e6b17ff3..7bdc2f7c3 100644
--- a/front/gatsby/src/components/Write/WriteLeft.jsx
+++ b/front/gatsby/src/components/Write/WriteLeft.jsx
@@ -1,4 +1,5 @@
-import React, { useMemo, useState } from 'react'
+import React, { useState, useMemo } from 'react'
+import { connect } from 'react-redux'
import styles from './writeLeft.module.scss'
import Stats from './Stats'
@@ -7,9 +8,10 @@ import Sommaire from './Sommaire'
import Versions from './Versions'
import bib2key from './bibliographe/CitationsFilter'
-export default (props) => {
- const bibTeXEntries = useMemo(() => bib2key(props.bib), [props.bib])
+const mapStateToProps = ({ articleStats }) => ({ articleStats })
+function WriteLeft (props) {
+ const bibTeXEntries = useMemo(() => bib2key(props.bib), [props.bib])
const [expanded, setExpanded] = useState(true)
return (
@@ -21,19 +23,19 @@ export default (props) => {
{expanded ? 'close' : 'open'}
{expanded && (
- <>
-
-
- {props.article.title}
- by {props.article.owners.join(', ')}
-
-
-
-
-
-
- >
+
+
+ {props.article.title}
+ by {props.article.owners.join(', ')}
+
+
+
+
+
+
)}
)
}
+
+export default connect(mapStateToProps)(WriteLeft)
diff --git a/front/gatsby/src/components/Write/bibliographe/Bibliographe.jsx b/front/gatsby/src/components/Write/bibliographe/Bibliographe.jsx
index 1abbe48c9..0e19faf32 100644
--- a/front/gatsby/src/components/Write/bibliographe/Bibliographe.jsx
+++ b/front/gatsby/src/components/Write/bibliographe/Bibliographe.jsx
@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { connect } from 'react-redux'
-import { debounce } from 'lodash'
+import debounce from 'lodash/debounce'
import styles from './bibliographe.module.scss'
import etv from '../../../helpers/eventTargetValue'
@@ -31,7 +31,7 @@ const mapDispatchToProps = (dispatch) => ({
),
})
-const ConnectedBibliographe = (props) => {
+function ConnectedBibliographe (props) {
const {backendEndpoint} = props.applicationConfig
const defaultSuccess = (result) => console.log(result)
const { refreshProfile } = props
diff --git a/front/gatsby/src/components/Write/bibliographe/CitationsFilter.js b/front/gatsby/src/components/Write/bibliographe/CitationsFilter.js
index 744eaaaa8..681efe496 100644
--- a/front/gatsby/src/components/Write/bibliographe/CitationsFilter.js
+++ b/front/gatsby/src/components/Write/bibliographe/CitationsFilter.js
@@ -21,6 +21,7 @@ export default (input) => {
const parser = new BibLatexParser(input, {
processUnexpected: true,
processUnknown: true,
+ includeRawText: true,
async: false,
})
diff --git a/front/gatsby/src/components/Write/metadata/isidoreAuthor.jsx b/front/gatsby/src/components/Write/metadata/isidoreAuthor.jsx
index 39481fbd9..09d518e75 100644
--- a/front/gatsby/src/components/Write/metadata/isidoreAuthor.jsx
+++ b/front/gatsby/src/components/Write/metadata/isidoreAuthor.jsx
@@ -1,5 +1,5 @@
import React, { useCallback, useState } from 'react'
-import { throttle } from 'lodash'
+import throttle from 'lodash/throttle'
import { searchAuthor as isidoreAuthorSearch } from '../../../helpers/isidore'
import { useCombobox } from 'downshift'
import Field from '../../Field'
diff --git a/front/gatsby/src/components/Write/metadata/isidoreKeyword.jsx b/front/gatsby/src/components/Write/metadata/isidoreKeyword.jsx
index e79cc82bb..e8b6cce85 100644
--- a/front/gatsby/src/components/Write/metadata/isidoreKeyword.jsx
+++ b/front/gatsby/src/components/Write/metadata/isidoreKeyword.jsx
@@ -1,5 +1,5 @@
import React, { useCallback, useState } from 'react'
-import { throttle } from 'lodash'
+import throttle from 'lodash/throttle'
import { searchKeyword as isidoreKeywordSearch } from '../../../helpers/isidore'
import { useCombobox } from 'downshift'
import Field from '../../Field'
diff --git a/front/gatsby/src/createReduxStore.js b/front/gatsby/src/createReduxStore.js
index 365946aa9..dbb1df6c6 100644
--- a/front/gatsby/src/createReduxStore.js
+++ b/front/gatsby/src/createReduxStore.js
@@ -1,87 +1,175 @@
-import { createStore as reduxCreateStore } from 'redux'
+import { createStore } from 'redux'
-// Définition du store Redux et de l'ensemble des actions
+function createReducer(initialState, handlers) {
+ return function reducer(state = initialState, action) {
+ if (handlers.hasOwnProperty(action.type)) {
+ return handlers[action.type](state, action)
+ } else {
+ return state
+ }
+ }
+}
+// Définition du store Redux et de l'ensemble des actions
const initialState = {
logedIn: false,
users: [],
password: undefined,
sessionToken: undefined,
+ articleStructure: [],
+ articleStats: {
+ wordCount: 0,
+ charCountNoSpace: 0,
+ charCountPlusSpace: 0,
+ citationNb: 0,
+ },
}
-const reducer = (state = initialState, action) => {
- if (action.type === 'APPLICATION_CONFIG') {
- return { ...state, applicationConfig: action.applicationConfig }
- } else if (action.type === 'PROFILE') {
- if (!action.user) {
- return { ...state, hasBooted: true }
- }
+const reducer = createReducer([], {
+ APPLICATION_CONFIG: setApplicationConfig,
+ PROFILE: setProfile,
+ CLEAR_ZOTERO_TOKEN: clearZoteroToken,
+ LOGIN: loginUser,
+ UPDATE_ACTIVE_USER: updateActiveUser,
+ RELOAD_USERS: reloadUsers,
+ SWITCH: switchUser,
+ LOGOUT: logoutUser,
+ REMOVE_MYSELF_ALLOWED_LOGIN: removeMyselfAllowedLogin,
+
+ // article reducers
+ UPDATE_ARTICLE_STATS: updateArticleStats,
+ UPDATE_ARTICLE_STRUCTURE: updateArticleStructure,
+})
+
+
+function setApplicationConfig (state, action) {
+ const applicationConfig = {
+ ...action.applicationConfig
+ }
+
+ return { ...state, applicationConfig }
+}
- const { user: activeUser } = action
+function setProfile (state, action) {
+ if (!action.user) {
+ return { ...state, hasBooted: true }
+ }
+
+ const { user: activeUser } = action
+
+ return Object.assign({}, state, {
+ hasBooted: true,
+ activeUser,
+ logedIn: true,
+ // it will allow password modification if logged with password,
+ // otherwise it means we use an external auth service
+ password:
+ activeUser.passwords.find((p) => p.email === activeUser.email) || {},
+ users: [activeUser._id],
+ })
+}
+
+function clearZoteroToken (state) {
+ state.activeUser.zoteroToken = null
+
+ return state
+}
- return Object.assign({}, state, {
- hasBooted: true,
- activeUser,
+function loginUser (state, {login}) {
+ if (login.password && login.users && login.token) {
+ return {
+ ...state,
logedIn: true,
- // it will allow password modification if logged with password,
- // otherwise it means we use an external auth service
- password:
- activeUser.passwords.find((p) => p.email === activeUser.email) || {},
- users: [activeUser._id],
- })
- } else if (action.type === 'CLEAR_ZOTERO_TOKEN') {
- state.activeUser.zoteroToken = null
- return state
- } else if (action.type === 'LOGIN') {
- const login = action.login
- if (login.password && login.users && login.token) {
- return Object.assign({}, state, {
- logedIn: true,
- users: login.users,
- activeUser: login.users[0],
- password: login.password,
- sessionToken: login.token,
- })
+ users: login.users,
+ activeUser: login.users[0],
+ password: login.password,
+ sessionToken: login.token,
}
- } else if (action.type === 'UPDATE_ACTIVE_USER') {
- return Object.assign(
- {},
- state,
- {
- activeUser: { ...state.activeUser, displayName: action.payload },
- },
- {
- users: [...state.users].map((u) => {
- if (state.activeUser._id === u._id) {
- u.displayName = action.payload
- }
- return u
- }),
+ }
+
+ return state
+}
+
+function updateActiveUser (state, action) {
+ return {
+ ...state,
+ activeUser: { ...state.activeUser, displayName: action.payload },
+ users: [...state.users].map((u) => {
+ if (state.activeUser._id === u._id) {
+ u.displayName = action.payload
}
- )
- } else if (action.type === 'RELOAD_USERS') {
- return Object.assign({}, state, {
- users: action.payload,
- })
- } else if (action.type === 'SWITCH') {
- if (state.users.map((u) => u._id).includes(action.payload._id)) {
- return Object.assign({}, state, { activeUser: action.payload })
- }
- } else if (action.type === 'LOGOUT') {
- return Object.assign({}, state, {
- ...initialState,
- })
- } else if (action.type === 'REMOVE_MYSELF_ALLOWED_LOGIN') {
- const remainingUsers = state.users.filter((u) => u._id !== action.payload)
- return Object.assign({}, state, {
- users: remainingUsers,
- activeUser: remainingUsers[0],
+ return u
})
+
+ }
+}
+
+function reloadUsers (state, {payload: users}) {
+ return { ...state, users }
+}
+
+function switchUser (state, {payload: activeUser}) {
+ if (state.users.map((u) => u._id).includes(activeUser._id)) {
+ return { ...state, activeUser }
+ }
+
+ return state
+}
+
+function logoutUser (state) {
+ return { ...state, ...initialState }
+}
+
+function removeMyselfAllowedLogin (state, {payload: userId}) {
+ const remainingUsers = state.users.filter((u) => u._id !== userId)
+
+ return { ...state,
+ users: remainingUsers,
+ activeUser: remainingUsers[0],
}
+}
+
+const SPACE_RE = /\s+/gi
+const CITATION_RE = /(\[@[\w-]+)/gi
+const REMOVE_MARKDOWN_RE = /[#_*]+\s?/gi
- return initialState
+function updateArticleStats (state, { md }) {
+ const text = (md || '').trim()
+
+ const textWithoutMarkdown = text.replace(REMOVE_MARKDOWN_RE, '')
+ const wordCount = textWithoutMarkdown
+ .replace(SPACE_RE, ' ')
+ .split(' ').length
+
+ const charCountNoSpace = textWithoutMarkdown.replace(SPACE_RE, '').length
+ const charCountPlusSpace = textWithoutMarkdown.length
+ const citationNb = text.match(CITATION_RE)?.length || 0
+
+ return { ...state, articleStats: {
+ wordCount,
+ charCountNoSpace,
+ charCountPlusSpace,
+ citationNb
+ }}
}
-const createStore = () => reduxCreateStore(reducer, initialState)
+function updateArticleStructure(state, { md }) {
+ const text = (md || '').trim()
+ const articleStructure = text
+ .split('\n')
+ .map((line, index) => ({ line, index }))
+ .filter((lineWithIndex) => lineWithIndex.line.match(/^##+\ /))
+ .map((lineWithIndex) => {
+ const title = lineWithIndex.line
+ .replace(/##/, '')
+ //arrow backspace (\u21B3)
+ .replace(/#\s/g, '\u21B3')
+ // middle dot (\u00B7) + non-breaking space (\xa0)
+ .replace(/#/g, '\u00B7\xa0')
+ return {...lineWithIndex, title}
+ })
+
+ return { ...state, articleStructure }
+}
-export default createStore
+export default () => createStore(reducer, initialState, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
diff --git a/front/gatsby/src/helpers/bibtex.js b/front/gatsby/src/helpers/bibtex.js
index 168626049..25359c4fe 100644
--- a/front/gatsby/src/helpers/bibtex.js
+++ b/front/gatsby/src/helpers/bibtex.js
@@ -1,9 +1,10 @@
-import { BibLatexExporter, BibLatexParser } from 'biblatex-csl-converter'
+import { BibLatexParser } from 'biblatex-csl-converter'
export async function parse(bibtex, options = { expectOutput: false }) {
const parser = new BibLatexParser(bibtex, {
processUnexpected: true,
processUnknown: true,
+ includeRawText: true,
async: true,
})
@@ -12,13 +13,7 @@ export async function parse(bibtex, options = { expectOutput: false }) {
}
export function toBibtex(entries) {
- const bibDB = entries.reduce((db, entry, i) => {
- return Object.assign(db, { [String(i)]: entry })
- }, {})
-
- return new BibLatexExporter(bibDB, false, {
- exportUnexpectedFields: true,
- }).parse()
+ return entries.map((e) => e.raw_text).join('\n\n')
}
/**