├── .dockerignore
├── .eslintrc
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── .travis.yml
├── 404.html
├── 50x.html
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── MANUAL_E2E_TESTS_CHECKLIST.md
├── README.md
├── __mocks__
├── cslMock.js
├── fileMock.js
├── styleMock.js
└── xmlMock.js
├── android-chrome-96x96.png
├── apple-touch-icon.png
├── browserconfig.xml
├── config.json
├── config
├── custom-environment-variables.json
├── default.json
├── production.json
└── sample.json
├── docker-cmd.sh
├── docker-nginx.conf
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
├── fonio-rs.png
├── index.html
├── index.prod.html.template
├── manifest.json
├── mstile-150x150.png
├── package-lock.json
├── package.json
├── safari-pinned-tab.svg
├── src
├── Application.js
├── Application.scss
├── components
│ ├── AssetPreview
│ │ ├── AssetPreview.js
│ │ ├── AssetPreview.scss
│ │ └── index.js
│ ├── AuthorsManager
│ │ ├── AuthorsManager.js
│ │ └── index.js
│ ├── BibHelpModal
│ │ ├── BibHelpModal.js
│ │ └── index.js
│ ├── BibRefsEditor
│ │ ├── BibRefsEditor.js
│ │ └── index.js
│ ├── BibliographicPreview
│ │ ├── BibliographicPreview.js
│ │ ├── BibliographicPreview.scss
│ │ └── index.js
│ ├── CitationStyleSelector
│ │ ├── CitationStyleSelector.js
│ │ ├── CitationStyleSelector.scss
│ │ └── index.js
│ ├── ConfirmToDeleteModal
│ │ ├── ConfirmToDeleteModal.js
│ │ └── index.js
│ ├── DataUrlProvider
│ │ ├── DataUrlProvider.js
│ │ └── index.js
│ ├── DemoLeaveConfirmModal
│ │ ├── DemoLeaveConfirmModal.js
│ │ └── index.js
│ ├── EmbedHelpModal
│ │ ├── EmbedHelpModal.js
│ │ └── index.js
│ ├── ExplainedLabel
│ │ ├── ExplainedLabel.js
│ │ └── index.js
│ ├── ExportModal
│ │ ├── ExportModal.js
│ │ └── index.js
│ ├── GlossaryModal
│ │ ├── GlossaryModal.js
│ │ └── index.js
│ ├── IconBtn
│ │ ├── IconBtn.js
│ │ └── index.js
│ ├── IdentificationModal
│ │ ├── IdentificationModal.js
│ │ └── index.js
│ ├── InternalLinkModal
│ │ ├── InternalLinkModal.js
│ │ └── index.js
│ ├── LanguageToggler
│ │ ├── LanguageToggler.js
│ │ └── index.js
│ ├── LinkModal
│ │ ├── LinkModal.js
│ │ └── index.js
│ ├── LoadingScreen
│ │ ├── LoadingScreen.js
│ │ └── index.js
│ ├── MetadataForm
│ │ ├── MetadataForm.js
│ │ └── index.js
│ ├── MovePad
│ │ ├── MovePad.js
│ │ ├── MovePad.scss
│ │ └── index.js
│ ├── NewSectionForm
│ │ ├── NewSectionForm.js
│ │ └── index.js
│ ├── PageNotFound
│ │ ├── PageNotFound.js
│ │ └── index.js
│ ├── PaginatedList
│ │ ├── PaginatedList.js
│ │ ├── PaginatedList.scss
│ │ └── index.js
│ ├── PasswordInput
│ │ ├── PasswordInput.js
│ │ └── index.js
│ ├── PastingModal
│ │ ├── PastingModal.js
│ │ └── index.js
│ ├── ResourceForm
│ │ ├── ResourceForm.js
│ │ └── index.js
│ ├── SectionEditor
│ │ ├── AssetButton.js
│ │ ├── Bibliography.js
│ │ ├── BlockContextualizationContainer.js
│ │ ├── GlossaryMention.js
│ │ ├── InlineCitation.js
│ │ ├── LinkContextualization.js
│ │ ├── NoteButton.js
│ │ ├── NotePointer.js
│ │ ├── ResourceSearchWidget.js
│ │ ├── SectionEditor.js
│ │ ├── SectionEditor.scss
│ │ ├── SectionEditorWrapper.js
│ │ ├── assets
│ │ │ ├── apa.csl
│ │ │ └── english-locale.xml
│ │ ├── buttons
│ │ │ ├── AssetButton.js
│ │ │ ├── BlockButton.js
│ │ │ ├── BlockQuoteButton.js
│ │ │ ├── BoldButton.js
│ │ │ ├── ButtonStyles.scss
│ │ │ ├── CodeBlockButton.js
│ │ │ ├── GlossaryButton.js
│ │ │ ├── HeaderOneButton.js
│ │ │ ├── HeaderTwoButton.js
│ │ │ ├── InlineButton.js
│ │ │ ├── InternalLinkButton.js
│ │ │ ├── ItalicButton.js
│ │ │ ├── LinkButton.js
│ │ │ ├── NoteButton.js
│ │ │ ├── OrderedListItemButton.js
│ │ │ ├── RemoveFormattingButton.js
│ │ │ ├── Separator.js
│ │ │ ├── UnorderedListItemButton.js
│ │ │ └── defaultButtons.js
│ │ └── index.js
│ └── UploadModal
│ │ ├── UploadModal.js
│ │ └── index.js
├── config.js
├── features
│ ├── AuthManager
│ │ ├── components
│ │ │ ├── AuthManagerContainer.js
│ │ │ ├── AuthManagerLayout.js
│ │ │ └── index.js
│ │ └── duck.js
│ ├── ConnectionsManager
│ │ ├── duck.js
│ │ └── duck.spec.js
│ ├── DesignView
│ │ ├── components
│ │ │ ├── AsideDesignColumn.js
│ │ │ ├── AsideDesignContents.js
│ │ │ ├── DesignViewContainer.js
│ │ │ ├── DesignViewLayout.js
│ │ │ ├── MainDesignColumn.js
│ │ │ ├── StyleEditor.js
│ │ │ └── index.js
│ │ ├── duck.js
│ │ └── utils
│ │ │ └── buildCssHelp.js
│ ├── EditionUiWrapper
│ │ ├── components
│ │ │ ├── EditionUiWrapperContainer.js
│ │ │ ├── EditionUiWrapperLayout.js
│ │ │ └── index.js
│ │ └── duck.js
│ ├── ErrorMessageManager
│ │ ├── components
│ │ │ ├── ErrorMessageContainer.js
│ │ │ └── index.js
│ │ └── duck.js
│ ├── HomeView
│ │ ├── assets
│ │ │ ├── logo-forccast.svg
│ │ │ ├── logo-medialab.svg
│ │ │ └── user-guide-fr.pdf
│ │ ├── components
│ │ │ ├── ChangePasswordModal.js
│ │ │ ├── DeleteStoryModal.js
│ │ │ ├── EnterPasswordModal.js
│ │ │ ├── Footer.js
│ │ │ ├── HomeViewContainer.js
│ │ │ ├── HomeViewLayout.js
│ │ │ ├── NewStoryForm.js
│ │ │ ├── OtherUsersWidget.js
│ │ │ ├── ProfileWidget.js
│ │ │ ├── StoryCard.js
│ │ │ ├── StoryCard.scss
│ │ │ ├── StoryCardWrapper.js
│ │ │ └── index.js
│ │ ├── duck.js
│ │ └── duck.spec.js
│ ├── LibraryView
│ │ ├── components
│ │ │ ├── ConfirmBatchDeleteModal.js
│ │ │ ├── LibraryFiltersBar.js
│ │ │ ├── LibraryViewContainer.js
│ │ │ ├── LibraryViewLayout.js
│ │ │ ├── ResourceCard.js
│ │ │ ├── ResourceCard.scss
│ │ │ └── index.js
│ │ └── duck.js
│ ├── ReadStoryView
│ │ └── components
│ │ │ ├── ReadStoryViewContainer.js
│ │ │ └── index.js
│ ├── SectionView
│ │ ├── components
│ │ │ ├── AsideSectionColumn.js
│ │ │ ├── AsideSectionContents.js
│ │ │ ├── MainSectionAside.js
│ │ │ ├── MainSectionColumn.js
│ │ │ ├── MoveButton.js
│ │ │ ├── ResourceMiniCard.js
│ │ │ ├── ResourceMiniCard.scss
│ │ │ ├── ResourcesList.js
│ │ │ ├── SectionHeader.js
│ │ │ ├── SectionMiniCard.js
│ │ │ ├── SectionViewContainer.js
│ │ │ ├── SectionViewLayout.js
│ │ │ ├── ShortcutsModal.js
│ │ │ ├── SortableMiniSectionsList.js
│ │ │ └── index.js
│ │ └── duck.js
│ ├── SectionsManager
│ │ └── duck.js
│ ├── StoryManager
│ │ ├── duck.js
│ │ └── duck.spec.js
│ ├── SummaryView
│ │ ├── components
│ │ │ ├── AuthorItem.js
│ │ │ ├── SectionCard.js
│ │ │ ├── SortableSectionsList.js
│ │ │ ├── SummaryViewContainer.js
│ │ │ ├── SummaryViewLayout.js
│ │ │ └── index.js
│ │ └── duck.js
│ └── UserInfoManager
│ │ └── duck.js
├── helpers
│ ├── assetsUtils.js
│ ├── assetsUtils.spec.js
│ ├── citationUtils.js
│ ├── clipboardUtils
│ │ ├── __mocks__
│ │ │ ├── README.md
│ │ │ ├── copyTests.json
│ │ │ ├── pasteInsideTests.json
│ │ │ ├── pasteOutsideTests.json
│ │ │ └── services.js
│ │ ├── handleCopy.js
│ │ ├── handleCopy.spec.js
│ │ ├── handlePaste.js
│ │ ├── index.js
│ │ ├── makeReactCitations.js
│ │ ├── parsePastedImage.js
│ │ ├── parsePastedLink.js
│ │ ├── pasteFromInside.js
│ │ ├── pasteFromInside.spec.js
│ │ ├── pasteFromOutside.js
│ │ └── pasteFromOutside.spec.js
│ ├── draftUtils.js
│ ├── editorToStoryUtils.js
│ ├── editorUtils.js
│ ├── fileDownloader.js
│ ├── fileLoader.js
│ ├── fileLoader.spec.js
│ ├── localStorageUtils.js
│ ├── lockUtils.js
│ ├── misc.js
│ ├── postcss.js
│ ├── projectBundler.js
│ ├── reduxUtils.js
│ ├── resourcesUtils.js
│ ├── schemaUtils.js
│ ├── translateUtils.js
│ └── userInfo.js
├── main.js
├── parameters.scss
├── redux
│ ├── configureStore.js
│ ├── payloadValidatorMiddleware.js
│ ├── promiseMiddleware.js
│ ├── rootReducer.js
│ └── socketIoMiddleware.js
├── sharedAssets
│ ├── avatars
│ │ ├── amazed-man.svg
│ │ ├── angry-man.svg
│ │ ├── angry-woman.svg
│ │ ├── baby-crying.svg
│ │ ├── baby-love.svg
│ │ ├── boy-broad-smile.svg
│ │ ├── boy-happy-smile.svg
│ │ ├── boy-smiling.svg
│ │ ├── boy-suffering.svg
│ │ ├── delighted-granny.svg
│ │ ├── disappointed-boy.svg
│ │ ├── embarrased-boy.svg
│ │ ├── embarrased-girl.svg
│ │ ├── embarrased-granny.svg
│ │ ├── emotive-granny.svg
│ │ ├── fat-boy-angry.svg
│ │ ├── fat-boy-shocked.svg
│ │ ├── fat-boy-smiling.svg
│ │ ├── fat-boy-sorry.svg
│ │ ├── fat-boy.svg
│ │ ├── frightened-hipster.svg
│ │ ├── girl-air-kissing.svg
│ │ ├── girl-crying.svg
│ │ ├── girl-embarrased.svg
│ │ ├── girl-laughing-to-tears.svg
│ │ ├── girl-showing-tongue.svg
│ │ ├── girl-smiling.svg
│ │ ├── girl-with-poker-face.svg
│ │ ├── happy-baby.svg
│ │ ├── happy-woman.svg
│ │ ├── hipster-smiling.svg
│ │ ├── hypnotized-hipster.svg
│ │ ├── index.json
│ │ ├── inexpressive-girl.svg
│ │ ├── kissing-girl.svg
│ │ ├── man-with-moustache-smiling.svg
│ │ ├── naughty-girl.svg
│ │ ├── perplexed-man.svg
│ │ ├── philosophizing-boy.svg
│ │ ├── sad-baby.svg
│ │ ├── sad-girl.svg
│ │ ├── sad-hipster.svg
│ │ ├── sad-woman.svg
│ │ ├── satisfied-woman.svg
│ │ ├── shocked-girl.svg
│ │ ├── sleeping-granny.svg
│ │ ├── sleepy-boy.svg
│ │ ├── smiling-baby.svg
│ │ ├── smiling-girl.svg
│ │ ├── surprised-girl.svg
│ │ ├── suspicious-man.svg
│ │ ├── teasing-boy.svg
│ │ ├── upset-girl.svg
│ │ ├── upset-granny.svg
│ │ ├── winking-boy.svg
│ │ └── wondered-hipster.svg
│ ├── bibAssets
│ │ ├── apa.csl
│ │ ├── chicago-author-date-fr.csl
│ │ ├── chicago-author-date.csl
│ │ ├── english-locale.xml
│ │ ├── iso690-author-date-en.csl
│ │ └── iso690-author-date-fr.csl
│ ├── cover_forccast.jpg
│ ├── internal-link.svg
│ ├── logo-quinoa.png
│ └── userNames.json
└── translations
│ ├── index.js
│ └── locales
│ ├── en.json
│ └── fr.json
├── translationScripts
├── addTranslationLanguage.js
├── backfillTranslations.js
├── discoverTranslations.js
├── exportTranslationsToPo.js
├── importTranslationsFromPo.js
└── updateTranslationsToPo.js
├── translations
├── en.po
└── fr.po
├── webpack.config.dev.js
├── webpack.config.docker.js
├── webpack.config.prod.js
└── webpack.config.shared.js
/.dockerignore:
--------------------------------------------------------------------------------
1 | README.md
2 | Dockerfile
3 | node_modules
4 | .git
5 | .gitignore
6 | LICENSE
7 | VERSION
8 | *.md
9 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 |
5 | ---
6 |
7 | **Describe the bug**
8 | A clear and concise description of what the bug is.
9 |
10 | **To Reproduce**
11 | Steps to reproduce the behavior:
12 | 1. Go to '...'
13 | 2. Click on '....'
14 | 3. Scroll down to '....'
15 |
16 | **Observed behavior**
17 | A clear and concise description of what happens when reproducing the steps above.
18 |
19 | **Expected behavior**
20 | A clear and concise description of what you expected to happen.
21 |
22 | **Screenshots**
23 | If applicable, add screenshots to help explain your problem.
24 |
25 | **Desktop (please complete the following information):**
26 | - OS: [e.g. iOS]
27 | - Browser [e.g. chrome, safari]
28 | - Version [e.g. 22]
29 |
30 | **Additional context**
31 | Add any other context about the problem here.
32 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 |
5 | ---
6 |
7 | **Is your feature request related to a problem? Please describe.**
8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
9 |
10 | **Describe the solution you'd like**
11 | A clear and concise description of what you want to happen.
12 |
13 | **Describe alternatives you've considered**
14 | A clear and concise description of any alternative solutions or features you've considered.
15 |
16 | **Additional context**
17 | Add any other context or screenshots about the feature request here.
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | npm-debug.log
4 | config/local.json
5 | build/
6 | data/
7 | index.prod.html
8 | coverage
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "8"
4 | - "9"
5 | - "10"
6 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nginx:1.13-alpine
2 |
3 | ENV NODE_ENV production
4 |
5 | ENV QUINOA_HOST quinoa
6 | ENV QUINOA_PORT 3001
7 | ENV MAX_SECTION_LEVEL 2
8 | ENV MAX_RESOURCE_SIZE 4000000
9 | ENV MAX_FOLDER_SIZE 50000000
10 | ENV MAX_STORY_SIZE 60000000
11 | ENV MAX_BATCH_NUMBER 50
12 | ENV REQUIRE_PUBLICATION_CONSENT false
13 | ENV URL_PREFIX http://localhost:3000
14 | ENV API_URL ${URL_PREFIX}/quinoa
15 | ENV DEMO_MODE false
16 |
17 | ADD . /fonio
18 | WORKDIR /fonio
19 |
20 | RUN apk add --no-cache --virtual .build-deps git nodejs=8.9.3-r1 build-base python \
21 | && npm install --quiet --production false --no-audit \
22 | && npm run build:docker \
23 | && mv ./build/bundle.js ./build/bundle.js.template \
24 | && apk del .build-deps \
25 | && rm -rf ./node_modules /root/.npm /root/.node-gyp /root/.config /usr/lib/node_modules
26 |
27 | RUN rm /etc/nginx/conf.d/default.conf
28 | COPY docker-nginx.conf /etc/nginx/conf.d/docker.template
29 |
30 | CMD /bin/sh docker-cmd.sh
31 |
--------------------------------------------------------------------------------
/__mocks__/fileMock.js:
--------------------------------------------------------------------------------
1 | module.exports = 'test-file-stub';
2 |
--------------------------------------------------------------------------------
/__mocks__/styleMock.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
2 |
--------------------------------------------------------------------------------
/android-chrome-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/medialab/fonio/974f497357f401250d69cad69d05c501f58c5c3d/android-chrome-96x96.png
--------------------------------------------------------------------------------
/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/medialab/fonio/974f497357f401250d69cad69d05c501f58c5c3d/apple-touch-icon.png
--------------------------------------------------------------------------------
/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #da532c
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "maxSectionLevel": 6,
3 | "timers": {
4 | "nano": 10,
5 | "short": 100,
6 | "medium": 300,
7 | "long": 1000,
8 | "veryLong": 2000,
9 | "ultraLong": 5000
10 | },
11 | "savingDelayMs": 2000
12 | }
--------------------------------------------------------------------------------
/config/custom-environment-variables.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiUrl": "API_URL",
3 | "sessionName": "SESSION_NAME",
4 | "urlPrefix": "URL_PREFIX",
5 | "maxSectionLevel": "MAX_SECTION_LEVEL",
6 | "maxResourceSize": "MAX_RESOURCE_SIZE",
7 | "maxFolderSize": "MAX_FOLDER_SIZE",
8 | "maxStorySize": "MAX_STORY_SIZE",
9 | "maxBatchNumber": "MAX_BATCH_NUMBER",
10 | "requirePublicationConsent": {
11 | "__name": "REQUIRE_PUBLICATION_CONSENT",
12 | "__format": "json"
13 | },
14 | "demoMode": {
15 | "__name": "DEMO_MODE",
16 | "__format": "json"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/config/default.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiUrl": "http://localhost:3001",
3 | "maxSectionLevel": 2,
4 | "maxResourceSize": 4000000,
5 | "maxFolderSize": 50000000,
6 | "maxStorySize": 60000000,
7 | "maxBatchNumber": 50,
8 | "sessionName": "Introduction",
9 | "urlPrefix": "",
10 | "requirePublicationConsent": false,
11 | "demoMode": false
12 | }
13 |
--------------------------------------------------------------------------------
/config/production.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiUrl": "http://localhost:3001",
3 | "maxSectionLevel": 2,
4 | "maxResourceSize": 4000000,
5 | "maxFolderSize": 50000000,
6 | "maxStorySize": 60000000,
7 | "maxBatchNumber": 50,
8 | "sessionName": "My super classroom name",
9 | "urlPrefix": "",
10 | "requirePublicationConsent": true,
11 | "demoMode": false
12 | }
13 |
--------------------------------------------------------------------------------
/config/sample.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiUrl": "http://localhost:3001",
3 | "maxSectionLevel": 2,
4 | "maxResourceSize": 4000000,
5 | "maxFolderSize": 50000000,
6 | "maxStorySize": 60000000,
7 | "maxBatchNumber": 50,
8 | "sessionName": "My super classroom name",
9 | "urlPrefix": "",
10 | "requirePublicationConsent": true,
11 | "demoMode": false
12 | }
13 |
--------------------------------------------------------------------------------
/docker-cmd.sh:
--------------------------------------------------------------------------------
1 | # Templating the bundle file
2 | sed "s;@@URL_PREFIX@@;${URL_PREFIX};" /fonio/build/bundle.js.template > /fonio/build/bundle.js
3 |
4 | # Templating the HTML index
5 | envsubst < /fonio/index.prod.html.template > /fonio/index.html
6 |
7 | # Templating the NGINX conf
8 | export NS=$(awk '/^nameserver/{print $2}' /etc/resolv.conf)
9 | envsubst '\$NS \$QUINOA_HOST \$QUINOA_PORT \$MAX_STORY_SIZE' < /etc/nginx/conf.d/docker.template > /etc/nginx/conf.d/default.conf
10 |
11 | # No daemon
12 | nginx -g 'daemon off;'
13 |
--------------------------------------------------------------------------------
/docker-nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 3000;
3 | server_name localhost;
4 |
5 | # TODO: discriminate between MAX_RESOURCE_SIZE & MAX_STORY_SIZE
6 | client_max_body_size ${MAX_STORY_SIZE};
7 |
8 | server_tokens off;
9 | add_header X-Frame-Options SAMEORIGIN;
10 | add_header X-Content-Type-Options nosniff;
11 | add_header X-XSS-Protection "1; mode=block";
12 | add_header Strict-Transport-Security "max-age=31536000;";
13 | add_header X-Robots-Tag "noindex, nofollow";
14 |
15 | resolver ${NS} ipv6=off;
16 | set $api "http://${QUINOA_HOST}:${QUINOA_PORT}";
17 |
18 | ### API
19 | location /quinoa/ {
20 | rewrite ^/quinoa(/.*)$ $1 break;
21 | proxy_pass $api;
22 | proxy_http_version 1.1;
23 | proxy_set_header Upgrade $http_upgrade;
24 | proxy_set_header Connection "Upgrade";
25 | }
26 |
27 | ### Static HTML5/JS
28 | location / {
29 | root /fonio/;
30 | index index.html index.htm;
31 | try_files $uri $uri/ /index.html?$query_string;
32 | }
33 |
34 | location ^~ /quinoa/static {
35 | alias /fonio/stories-data/stories;
36 | }
37 |
38 | error_page 500 502 503 504 /50x.html;
39 | location = /50x.html {
40 | root /usr/share/nginx/html;
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/medialab/fonio/974f497357f401250d69cad69d05c501f58c5c3d/favicon-16x16.png
--------------------------------------------------------------------------------
/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/medialab/fonio/974f497357f401250d69cad69d05c501f58c5c3d/favicon-32x32.png
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/medialab/fonio/974f497357f401250d69cad69d05c501f58c5c3d/favicon.ico
--------------------------------------------------------------------------------
/fonio-rs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/medialab/fonio/974f497357f401250d69cad69d05c501f58c5c3d/fonio-rs.png
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fonio",
3 | "icons": [
4 | {
5 | "src": "/android-chrome-96x96.png",
6 | "sizes": "96x96",
7 | "type": "image/png"
8 | }
9 | ],
10 | "theme_color": "#ffffff",
11 | "background_color": "#ffffff",
12 | "display": "standalone"
13 | }
--------------------------------------------------------------------------------
/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/medialab/fonio/974f497357f401250d69cad69d05c501f58c5c3d/mstile-150x150.png
--------------------------------------------------------------------------------
/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Created by potrace 1.11, written by Peter Selinger 2001-2013
9 |
10 |
12 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/components/AssetPreview/index.js:
--------------------------------------------------------------------------------
1 | import AssetPreview from './AssetPreview';
2 |
3 | export default AssetPreview;
4 |
--------------------------------------------------------------------------------
/src/components/AuthorsManager/index.js:
--------------------------------------------------------------------------------
1 | import Component from './AuthorsManager';
2 |
3 | export default Component;
4 |
--------------------------------------------------------------------------------
/src/components/BibHelpModal/BibHelpModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { translateNameSpacer } from '../../helpers/translateUtils';
4 |
5 | import {
6 | ModalCard,
7 | Button,
8 | Content,
9 | } from 'quinoa-design-library/components/';
10 | const BibHelpModal = ( {
11 | isOpen,
12 | onRequestClose,
13 | onChooseSample
14 | }, context ) => {
15 | const translate = translateNameSpacer( context.t, 'Components.BibHelpModal' );
16 |
17 | const example = `@article{10.2307/1511524,
18 | ISSN = {07479360, 15314790},
19 | author = {Richard Buchanan},
20 | journal = {Design Issues},
21 | number = {1},
22 | pages = {4--22},
23 | publisher = {The MIT Press},
24 | title = {Declaration by Design: Rhetoric, Argument, and Demonstration in Design Practice},
25 | volume = {2},
26 | year = {1985}
27 | }`;
28 | const onTest = () => {
29 | onChooseSample( example );
30 | };
31 | return (
32 |
42 |
43 | {translate( 'In higher education and scientific research, a good practice for managing references and bibliographies is to use specialized software for storing and documenting references in a form which is independent of a specific citation style.' )}
44 |
45 |
46 | {translate( 'For a fast bibliography making tool, you can use zbib which helps building small bibliographies online:' )}
47 |
52 | zbib
53 |
54 |
55 |
56 | {translate( 'For a more substantial bibliography making tool, you may want to install zotero software:' )}
57 |
62 | Zotero
63 |
64 |
65 |
66 | {translate( 'Fonio accepts citation files in the form of bibtex files, which can be exported from all bibliography-related tools and websites.' )}
67 |
68 |
69 | {translate( 'Bibtex is a standard format for exchanging academic references accross tools. You should not have to manipulate them directly, but Bibtex data looks like this: ' )}
70 |
71 |
74 | {translate( 'Try it !' )}
75 |
76 | }
77 | />
78 | );
79 | };
80 |
81 | BibHelpModal.contextTypes = {
82 | t: PropTypes.func,
83 | };
84 |
85 | export default BibHelpModal;
86 |
--------------------------------------------------------------------------------
/src/components/BibHelpModal/index.js:
--------------------------------------------------------------------------------
1 | import BibHelpModal from './BibHelpModal';
2 |
3 | export default BibHelpModal;
4 |
--------------------------------------------------------------------------------
/src/components/BibRefsEditor/BibRefsEditor.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a component allowing to edit raw bibtex data.
3 | * It handles errors and propagates an update only if input data is valid.
4 | * @module fonio/components/AuthorsManager
5 | */
6 | /* eslint react/no-set-state : 0 */
7 | /**
8 | * Imports Libraries
9 | */
10 | import React, { Component } from 'react';
11 | import PropTypes from 'prop-types';
12 | import {
13 | CodeEditor
14 | } from 'quinoa-design-library';
15 | import Cite from 'citation-js';
16 |
17 | class BibRefsEditor extends Component {
18 | constructor( props ) {
19 | super( props );
20 | this.state = {
21 | refsInput: '',
22 | };
23 | }
24 | componentDidMount = () => {
25 | this.updateBibInput( this.props.data );
26 | }
27 | componentWillReceiveProps = ( nextProps ) => {
28 | if ( this.props.data !== nextProps.data ) {
29 | this.updateBibInput( nextProps.data );
30 | }
31 | }
32 |
33 | updateBibInput = ( data ) => {
34 | const resAsBibTeXParser = new Cite( data );
35 | const resAsBibTeX = resAsBibTeXParser.get( { type: 'string', style: 'bibtex' } );
36 | if ( resAsBibTeX !== this.state.refsInput ) {
37 | this.setState( {
38 | refsInput: resAsBibTeX
39 | } );
40 | }
41 | }
42 |
43 | render = () => {
44 |
45 | /**
46 | * Variables definition
47 | */
48 | const {
49 | onChange,
50 | style,
51 | } = this.props;
52 | const { refsInput } = this.state;
53 |
54 | /**
55 | * Callbacks handlers
56 | */
57 | const handleBibTeXInputChange = ( value ) => {
58 | this.setState( {
59 | refsInput: value,
60 | } );
61 | onChange( value );
62 | };
63 |
64 | return (
65 |
68 |
72 |
73 | );
74 | }
75 | }
76 |
77 | BibRefsEditor.contextTypes = {
78 | t: PropTypes.func,
79 | };
80 | export default BibRefsEditor;
81 |
--------------------------------------------------------------------------------
/src/components/BibRefsEditor/index.js:
--------------------------------------------------------------------------------
1 | import BibRefsEditor from './BibRefsEditor';
2 |
3 | export default BibRefsEditor;
4 |
--------------------------------------------------------------------------------
/src/components/BibliographicPreview/BibliographicPreview.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a reusable bibliography preview element component.
3 | * It displays a simple bibliography
4 | * @module fonio/components/BibliographicPreview
5 | */
6 | /**
7 | * Imports Libraries
8 | */
9 | import React from 'react';
10 | import PropTypes from 'prop-types';
11 | import {
12 | Content
13 | } from 'quinoa-design-library/components';
14 | import { Bibliography } from 'react-citeproc';
15 |
16 | /**
17 | * Imports Assets
18 | */
19 | import './BibliographicPreview.scss';
20 | const english = require( 'raw-loader!../../sharedAssets/bibAssets/english-locale.xml' );
21 | const apa = require( 'raw-loader!../../sharedAssets/bibAssets/apa.csl' );
22 |
23 | /**
24 | * Renders the BibliographicPreview component as a pure function
25 | * @param {object} props - used props (see prop types below)
26 | * @todo: load style and locale from currently set style and locale
27 | * @return {ReactElement} component - the resulting component
28 | */
29 | const BibliographicPreview = ( {
30 | items,
31 | style = apa,
32 | locale = english
33 | } ) => (
34 |
35 |
36 |
41 |
42 |
43 | );
44 |
45 | /**
46 | * Component's properties types
47 | */
48 | BibliographicPreview.propTypes = {
49 |
50 | /**
51 | * Map of the bibliographic items to render (keys are ids)
52 | */
53 | items: PropTypes.object,
54 | };
55 |
56 | export default BibliographicPreview;
57 |
--------------------------------------------------------------------------------
/src/components/BibliographicPreview/BibliographicPreview.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * style definitions for the BibliographicPreview component
3 | *
4 | * @module fonio/components/BibliographicPreview
5 | */
6 | @import '../../parameters.scss';
7 |
--------------------------------------------------------------------------------
/src/components/BibliographicPreview/index.js:
--------------------------------------------------------------------------------
1 | import BibliographicPreview from './BibliographicPreview';
2 |
3 | export default BibliographicPreview;
4 |
--------------------------------------------------------------------------------
/src/components/CitationStyleSelector/CitationStyleSelector.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * style definitions for the CitationStyleSelector component
3 | *
4 | * @module fonio/components/CitationStyleSelector
5 | */
6 | @import '../../parameters.scss';
7 |
8 | .citation-style-selector{
9 | .button{
10 | margin: .5rem;
11 | min-width: 2.5rem;
12 | }
13 | .content blockquote{
14 | border: none;
15 | padding: 0;
16 | }
17 | }
--------------------------------------------------------------------------------
/src/components/CitationStyleSelector/index.js:
--------------------------------------------------------------------------------
1 | import CitationStyleSelector from './CitationStyleSelector';
2 |
3 | export default CitationStyleSelector;
4 |
--------------------------------------------------------------------------------
/src/components/ConfirmToDeleteModal/ConfirmToDeleteModal.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a modal for confirming a section deletion
3 | * @module fonio/components/ConfirmToDeleteModal
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import PropTypes from 'prop-types';
10 |
11 | import {
12 | ModalCard,
13 | Button,
14 | } from 'quinoa-design-library/components/';
15 |
16 | /**
17 | * Imports Project utils
18 | */
19 |
20 | import { getResourceTitle } from '../../helpers/resourcesUtils';
21 | import { translateNameSpacer } from '../../helpers/translateUtils';
22 |
23 | const ConfirmToDeleteModal = ( {
24 | onClose,
25 | onDeleteConfirm,
26 | id,
27 | story,
28 | deleteType,
29 | isActive,
30 | isDisabled = false
31 | }, { t } ) => {
32 |
33 | /**
34 | * Local functions
35 | */
36 | const translate = translateNameSpacer( t, 'Components.ConfirmToDeleteModal' );
37 |
38 | /**
39 | * Computed variables
40 | */
41 | let message;
42 | let citedContext;
43 | if ( deleteType === 'section' ) {
44 | message = ( story && story.sections[id] ) ? translate(
45 | 'Are you sure you want to delete the section "{s}" ? All its content will be lost without possible recovery.',
46 | {
47 | s: story.sections[id].metadata.title
48 | }
49 | ) : translate( 'Are you sure you want to delete this section ?' );
50 | }
51 | else {
52 | const { contextualizations } = story;
53 | citedContext = Object.keys( contextualizations )
54 | .map( ( contextId ) => contextualizations[contextId] )
55 | .filter( ( d ) => d.resourceId === id );
56 |
57 | message = ( story && story.resources[id] ) ? translate(
58 | 'Are you sure you want to delete the resource "{s}" ?',
59 | {
60 | s: getResourceTitle( story.resources[id] )
61 | }
62 | ) : translate( 'Are you sure you want to delete this resource ?' );
63 | }
64 |
65 | return (
66 |
72 | {deleteType === 'resource' && citedContext.length > 0 &&
73 |
74 | {translate( [ 'You will destroy one item mention in your content if you delete this item.', 'You will destroy {n} item mentions in your content if your delete this item.', 'n' ],
75 | { n: citedContext.length } )}
76 |
}
77 | {message}
78 |
79 | }
80 | footerContent={ [
81 | {translate( 'Delete' )}
89 | ,
90 | {translate( 'Cancel' )}
96 | ,
97 | ] }
98 | />
99 | );
100 | };
101 |
102 | ConfirmToDeleteModal.contextTypes = {
103 | t: PropTypes.func,
104 | };
105 |
106 | export default ConfirmToDeleteModal;
107 |
108 |
--------------------------------------------------------------------------------
/src/components/ConfirmToDeleteModal/index.js:
--------------------------------------------------------------------------------
1 | import ConfirmToDeleteModal from './ConfirmToDeleteModal';
2 |
3 | export default ConfirmToDeleteModal;
4 |
--------------------------------------------------------------------------------
/src/components/DataUrlProvider/DataUrlProvider.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides wrapper serving a resource data url get function to its children context
3 | * @module fonio/components/DataUrlProvider
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import { Component } from 'react';
9 | import PropTypes from 'prop-types';
10 |
11 | export default class DataUrlProvider extends Component {
12 |
13 | static childContextTypes = {
14 | getResourceDataUrl: PropTypes.func
15 | }
16 |
17 | constructor( props ) {
18 | super( props );
19 | }
20 |
21 | getChildContext = () => ( {
22 | getResourceDataUrl: this.getResourceDataUrl
23 | } )
24 |
25 | getResourceDataUrl = ( data ) => {
26 | const {
27 | serverUrl,
28 | } = this.props;
29 | return `${serverUrl}/static${data.filePath}`;
30 | }
31 |
32 | render = () => {
33 | const { children } = this.props;
34 | return children;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/components/DataUrlProvider/index.js:
--------------------------------------------------------------------------------
1 | import DataUrlProvider from './DataUrlProvider';
2 |
3 | export default DataUrlProvider;
4 |
--------------------------------------------------------------------------------
/src/components/DemoLeaveConfirmModal/DemoLeaveConfirmModal.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a modal for warning user of demo version behaviour when leaving a story
3 | * @module fonio/components/DemoLeaveConfirmModal
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import PropTypes from 'prop-types';
10 | import {
11 | ModalCard,
12 | Notification,
13 | Button,
14 | Title,
15 | } from 'quinoa-design-library/components/';
16 |
17 | /**
18 | * Imports Project utils
19 | */
20 | import { translateNameSpacer } from '../../helpers/translateUtils';
21 |
22 | const DemoLeaveConfirmModal = ( {
23 | onConfirm,
24 | onCancel,
25 | }, { t } ) => {
26 | const translate = translateNameSpacer( t, 'Components.DemoLeaveConfirmModal' );
27 | return (
28 |
34 |
35 | {translate( 'As you are in the demonstration version, this means all the contents of this story will be deleted if you leave it.' )}
36 |
37 |
38 | {translate( 'Before leaving, you can retrieve a publishable version of your by clicking on the export button on the top right corner of the screen (you can also save it as a data file and reimport it later from the home page).' )}
39 |
40 |
41 | {translate( 'So, are you ready to leave this test story and let it be deleted ?' )}
42 |
43 |
44 | }
45 | footerContent={ [
46 | {translate( 'No, let me continue to edit this story' )}
52 | ,
53 | {translate( 'Yes, let this story go !' )}
59 |
60 | ] }
61 | />
62 | );
63 | };
64 |
65 | DemoLeaveConfirmModal.contextTypes = {
66 | t: PropTypes.func.isRequired,
67 | };
68 |
69 | export default DemoLeaveConfirmModal;
70 |
--------------------------------------------------------------------------------
/src/components/DemoLeaveConfirmModal/index.js:
--------------------------------------------------------------------------------
1 | import Component from './DemoLeaveConfirmModal';
2 |
3 | export default Component;
4 |
--------------------------------------------------------------------------------
/src/components/EmbedHelpModal/index.js:
--------------------------------------------------------------------------------
1 | import EmbedHelpModal from './EmbedHelpModal';
2 |
3 | export default EmbedHelpModal;
4 |
--------------------------------------------------------------------------------
/src/components/ExplainedLabel/ExplainedLabel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a reusable label with explanation tooltip
3 | * @module fonio/components/ExplainedLabel
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import {
10 | Label,
11 | HelpPin,
12 | } from 'quinoa-design-library/components/';
13 |
14 | const ExplainedLabel = ( {
15 | title = '',
16 | explanation
17 | } ) => (
18 |
19 | {title}
20 |
21 | {explanation}
22 |
23 |
24 | );
25 |
26 | export default ExplainedLabel;
27 |
--------------------------------------------------------------------------------
/src/components/ExplainedLabel/index.js:
--------------------------------------------------------------------------------
1 | import ExplainedLabel from './ExplainedLabel';
2 |
3 | export default ExplainedLabel;
4 |
--------------------------------------------------------------------------------
/src/components/ExportModal/ExportModal.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a modal for exporting a story
3 | * @module fonio/components/ExportModal
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import PropTypes from 'prop-types';
10 | import {
11 | Column,
12 | ModalCard,
13 | BigSelect,
14 | Notification,
15 | StretchedLayoutContainer,
16 | HelpPin,
17 | StretchedLayoutItem,
18 | } from 'quinoa-design-library/components/';
19 | import icons from 'quinoa-design-library/src/themes/millet/icons';
20 |
21 | /**
22 | * Imports Project utils
23 | */
24 | import { translateNameSpacer } from '../../helpers/translateUtils';
25 |
26 | const ExportModal = ( {
27 | activeOptionId,
28 | onClose,
29 | onChange,
30 | status,
31 | isActive
32 | }, { t } ) => {
33 |
34 | const translate = translateNameSpacer( t, 'Components.ExportModal' );
35 |
36 | return (
37 |
44 |
45 |
46 | {translate( 'Export your story to publish it' )} {translate( 'multiple-html-help' )} ,
55 | iconUrl: activeOptionId === 'html-multi' ? icons.takeAway.white.svg : icons.takeAway.black.svg
56 | },
57 | {
58 | id: 'html-single',
59 | label: {translate( 'Export your story to archive it' )} {translate( 'single-html-help' )} ,
60 | iconUrl: activeOptionId === 'html-single' ? icons.takeAway.white.svg : icons.takeAway.black.svg
61 | },
62 | {
63 | id: 'json',
64 | label: {translate( 'Export your story to backup it' )} {translate( 'json-help' )} ,
65 | iconUrl: activeOptionId === 'json' ? icons.takeAway.white.svg : icons.takeAway.black.svg
66 | }
67 | ] }
68 | />
69 |
70 |
71 | {status === 'success' &&
72 |
73 |
74 | {translate( 'Story was bundled successfully' )}
75 |
76 |
77 | }
78 |
79 | }
80 | />
81 | );
82 | };
83 |
84 | ExportModal.contextTypes = {
85 | t: PropTypes.func,
86 | };
87 |
88 | export default ExportModal;
89 |
90 |
--------------------------------------------------------------------------------
/src/components/ExportModal/index.js:
--------------------------------------------------------------------------------
1 | import ExportModal from './ExportModal';
2 |
3 | export default ExportModal;
4 |
--------------------------------------------------------------------------------
/src/components/GlossaryModal/index.js:
--------------------------------------------------------------------------------
1 | import GlossaryModal from './GlossaryModal';
2 |
3 | export default GlossaryModal;
4 |
--------------------------------------------------------------------------------
/src/components/IconBtn/IconBtn.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a reusable button displaying a single pictographic icon
3 | * @module fonio/components/IconBtn
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React, { Component } from 'react';
9 | import {
10 | Button,
11 | Image,
12 | } from 'quinoa-design-library/';
13 |
14 | export default class IconBtn extends Component {
15 | constructor( props ) {
16 | super( props );
17 | this.state = {};
18 | }
19 | render = () => {
20 | const {
21 | isColor,
22 | onClick,
23 | src,
24 | dataTip,
25 | ...otherProps
26 | } = this.props;
27 |
28 | const bindRef = ( element ) => {
29 | this.element = element;
30 | };
31 |
32 | return (
33 |
36 |
47 | o
48 |
59 |
60 |
61 | );
62 |
63 | }
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/src/components/IconBtn/index.js:
--------------------------------------------------------------------------------
1 | import IconBtn from './IconBtn';
2 |
3 | export default IconBtn;
4 |
--------------------------------------------------------------------------------
/src/components/IdentificationModal/index.js:
--------------------------------------------------------------------------------
1 | import Component from './IdentificationModal';
2 |
3 | export default Component;
4 |
--------------------------------------------------------------------------------
/src/components/InternalLinkModal/index.js:
--------------------------------------------------------------------------------
1 | import InternalLinkModal from './InternalLinkModal';
2 |
3 | export default InternalLinkModal;
4 |
--------------------------------------------------------------------------------
/src/components/LanguageToggler/LanguageToggler.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a language toggling button
3 | * @module fonio/components/LanguageToggler
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React, { Component } from 'react';
9 | import PropTypes from 'prop-types';
10 | import { bindActionCreators } from 'redux';
11 | import { connect } from 'react-redux';
12 | import { setLanguage } from 'redux-i18n';
13 |
14 | import {
15 | Button,
16 | } from 'quinoa-design-library/components/';
17 |
18 | @connect(
19 | ( state ) => ( {
20 | lang: state.i18nState.lang,
21 | } ),
22 | ( dispatch ) => ( {
23 | actions: bindActionCreators( {
24 | setLanguage,
25 | }, dispatch )
26 | } )
27 | )
28 | class LanguageToggler extends Component {
29 |
30 | /**
31 | * Context data used by the component
32 | */
33 | static contextTypes = {
34 |
35 | /**
36 | * Un-namespaced translate function
37 | */
38 | t: PropTypes.func.isRequired,
39 |
40 | /**
41 | * Redux store
42 | */
43 | store: PropTypes.object.isRequired
44 | }
45 |
46 | /**
47 | * constructor
48 | * @param {object} props - properties given to instance at instanciation
49 | */
50 | constructor( props ) {
51 | super( props );
52 | }
53 |
54 | /**
55 | * Defines whether the component should re-render
56 | * @param {object} nextProps - the props to come
57 | * @param {object} nextState - the state to come
58 | * @return {boolean} shouldUpdate - whether to update or not
59 | */
60 | shouldComponentUpdate() {
61 | // todo: optimize when the feature is stabilized
62 | return true;
63 | }
64 |
65 | /**
66 | * Renders the component
67 | * @return {ReactElement} component - the component
68 | */
69 | render() {
70 |
71 | /**
72 | * Variables definition
73 | */
74 | const {
75 | lang,
76 | actions: {
77 | setLanguage: doSetLanguage
78 | }
79 | } = this.props;
80 |
81 | /**
82 | * Computed variables
83 | */
84 | const otherLang = lang === 'fr' ? 'en' : 'fr';
85 |
86 | /**
87 | * Callbacks handlers
88 | */
89 | const handleClick = () => {
90 | doSetLanguage( otherLang );
91 | };
92 |
93 | return (
94 |
98 | {lang}
99 | /{otherLang}
100 |
101 | );
102 | }
103 | }
104 |
105 | export default LanguageToggler;
106 |
--------------------------------------------------------------------------------
/src/components/LanguageToggler/index.js:
--------------------------------------------------------------------------------
1 | import LanguageToggler from './LanguageToggler';
2 |
3 | export default LanguageToggler;
4 |
--------------------------------------------------------------------------------
/src/components/LinkModal/index.js:
--------------------------------------------------------------------------------
1 | import LinkModal from './LinkModal';
2 |
3 | export default LinkModal;
4 |
--------------------------------------------------------------------------------
/src/components/LoadingScreen/LoadingScreen.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a reusable loading component
3 | * @module fonio/components/LoadingScreen
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import PropTypes from 'prop-types';
10 | import {
11 | AbsoluteContainer,
12 | FlexContainer,
13 | } from 'quinoa-design-library/components';
14 |
15 | /**
16 | * Imports Project utils
17 | */
18 | import { translateNameSpacer } from '../../helpers/translateUtils';
19 |
20 | const LoadingScreen = ( {}, { t } ) => {
21 | const translate = translateNameSpacer( t, 'Components.LoadingScreen' );
22 | return (
23 |
24 |
29 |
34 | {translate( 'loading...' )}
35 |
36 |
37 |
38 | );
39 | };
40 |
41 | LoadingScreen.contextTypes = {
42 | t: PropTypes.func.isRequired
43 | };
44 |
45 | export default LoadingScreen;
46 |
--------------------------------------------------------------------------------
/src/components/LoadingScreen/index.js:
--------------------------------------------------------------------------------
1 | import LoadingScreen from './LoadingScreen';
2 |
3 | export default LoadingScreen;
4 |
--------------------------------------------------------------------------------
/src/components/MetadataForm/index.js:
--------------------------------------------------------------------------------
1 | import Component from './MetadataForm';
2 |
3 | export default Component;
4 |
--------------------------------------------------------------------------------
/src/components/MovePad/MovePad.scss:
--------------------------------------------------------------------------------
1 |
2 |
3 | .move-pad{
4 | $unit : 2.5rem;
5 | position : relative;
6 | .move-item{
7 | position : absolute;
8 |
9 | &.is-disabled{
10 | opacity: .1;
11 | pointer-events: none;
12 | cursor: not-allowed;
13 | }
14 | }
15 |
16 | .chevron-icon-up,
17 | .chevron-icon-right,
18 | .chevron-icon-left,
19 | .chevron-icon-down{
20 | width: $unit;
21 | height: $unit;
22 | display: flex;
23 | flex-flow: row nowrap;
24 | align-items: center;
25 | justify-content: center;
26 | cursor: pointer;
27 | transition: all .3s ease;
28 |
29 | opacity: .5;
30 |
31 | &:not(.is-disabled):hover{
32 | opacity: 1;
33 | }
34 |
35 | .icon
36 | {
37 | transition: all .3s ease;
38 | }
39 | }
40 |
41 | .chevron-icon-up{
42 | .icon{
43 | left: -.1rem;
44 | top: .4rem;
45 | position: relative;
46 | }
47 | &:not(.is-disabled):hover{
48 | .icon{
49 | top: 0rem;
50 | }
51 | }
52 | }
53 |
54 | .chevron-icon-down{
55 | .icon{
56 | left: -.1rem;
57 | position: relative;
58 | top: -.4rem;
59 | }
60 | &:not(.is-disabled):hover{
61 | .icon{
62 | top: 0rem;
63 | }
64 | }
65 | }
66 |
67 | .chevron-icon-left{
68 | .icon{
69 | left: .2rem;
70 | position: relative;
71 | }
72 | &:not(.is-disabled):hover{
73 | .icon{
74 | left: -.2rem;
75 | }
76 | }
77 | }
78 | .chevron-icon-right{
79 | .icon{
80 | left: -.4rem;
81 | position: relative;
82 | }
83 | &:not(.is-disabled):hover{
84 | .icon{
85 | left: 0rem;
86 | }
87 | }
88 | }
89 |
90 | .chevron-icon-left {
91 | top: $unit;
92 | left: 0;
93 | }
94 | .chevron-icon-up,
95 | .move-button,
96 | .chevron-icon-down {
97 | left: $unit;
98 | }
99 | .chevron-icon-right{
100 | left: $unit * 2;
101 | top: $unit;
102 | }
103 |
104 | .chevron-icon-up{
105 | top: 0;
106 | }
107 |
108 | .move-button{
109 | top: $unit;
110 | }
111 |
112 | .chevron-icon-down{
113 | top: $unit * 2;
114 | }
115 | }
--------------------------------------------------------------------------------
/src/components/MovePad/index.js:
--------------------------------------------------------------------------------
1 | import MovePad from './MovePad';
2 |
3 | export default MovePad;
4 |
--------------------------------------------------------------------------------
/src/components/NewSectionForm/index.js:
--------------------------------------------------------------------------------
1 | import NewSectionForm from './NewSectionForm';
2 |
3 | export default NewSectionForm;
4 |
--------------------------------------------------------------------------------
/src/components/PageNotFound/PageNotFound.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a component for 404-like views
3 | * @module fonio/components/AuthorsManager
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import PropTypes from 'prop-types';
10 | import {
11 | Link,
12 | } from 'react-router-dom';
13 | import {
14 | ModalCard
15 | } from 'quinoa-design-library/components';
16 |
17 | /**
18 | * Imports Project utils
19 | */
20 | import { translateNameSpacer } from '../../helpers/translateUtils';
21 |
22 | const PageNotFound = ( {
23 | pathName
24 | }, { t } ) => {
25 | const translate = translateNameSpacer( t, 'Components.PageNotFound' );
26 |
27 | return (
28 |
33 | {pathName ?
34 | translate( 'No match for {u}, go back to ', { u: pathName } )
35 | :
36 | translate( 'The page you are looking does not exist on this fonio instance, go back to ' )
37 | }
38 |
39 | {translate( 'home page' )}
40 |
41 |
42 | }
43 | />
44 | );
45 | };
46 |
47 | PageNotFound.contextTypes = {
48 | t: PropTypes.func
49 | };
50 |
51 | export default PageNotFound;
52 |
--------------------------------------------------------------------------------
/src/components/PageNotFound/index.js:
--------------------------------------------------------------------------------
1 | import PageNotFound from './PageNotFound';
2 |
3 | export default PageNotFound;
4 |
--------------------------------------------------------------------------------
/src/components/PaginatedList/PaginatedList.scss:
--------------------------------------------------------------------------------
1 |
2 |
3 | .fonio-PaginatedList
4 | {
5 | display: flex;
6 | overflow: hidden;
7 |
8 | flex-flow: column nowrap;
9 | justify-content: stretch;
10 | .items-container
11 | {
12 | overflow: auto;
13 |
14 | flex: 1;
15 | // max-height: calc(100% - 2rem);
16 | > div
17 | {
18 | display: flex;
19 | overflow-y: auto;
20 |
21 | flex-flow: row wrap;
22 | }
23 | }
24 | .pagination
25 | {
26 | min-height: 5rem;
27 | }
28 |
29 | .pagination-previous,
30 | .pagination-next{
31 | &.is-disabled{
32 | opacity: .5;
33 | cursor: not-allowed;
34 | pointer-events: none;
35 | }
36 | }
37 |
38 | .pagination-list
39 | {
40 | li
41 | {
42 | &.is-current
43 | {
44 | a
45 | {
46 | color: white!important;
47 | background: brown;
48 | }
49 | }
50 | }
51 | }
52 |
53 |
54 | .my-masonry-grid
55 | {
56 | display: flex;
57 |
58 | width: auto;
59 | margin-left: -1rem; /* gutter size offset */
60 | }
61 | .my-masonry-grid_column
62 | {
63 | padding-left: 1rem; /* gutter size */
64 |
65 | background-clip: padding-box;
66 | }
67 |
68 | .my-masonry-grid_column > div
69 | {
70 | width: 100%;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/components/PaginatedList/index.js:
--------------------------------------------------------------------------------
1 | import PaginatedList from './PaginatedList';
2 |
3 | export default PaginatedList;
4 |
--------------------------------------------------------------------------------
/src/components/PasswordInput/PasswordInput.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a reusable password input component
3 | * @module fonio/components/PasswordInput
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import PropTypes from 'prop-types';
10 | import { Text } from 'react-form';
11 | import {
12 | Control,
13 | StretchedLayoutContainer,
14 | StretchedLayoutItem,
15 | } from 'quinoa-design-library/components/';
16 |
17 | /**
18 | * Imports Project utils
19 | */
20 | import { translateNameSpacer } from '../../helpers/translateUtils';
21 |
22 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
23 | import { faLock } from '@fortawesome/free-solid-svg-icons/faLock';
24 |
25 | const PasswordInput = ( { id = 'password' }, { t } ) => {
26 | const translate = translateNameSpacer( t, 'Components.PasswordInput' );
27 |
28 | return (
29 |
30 |
34 |
35 |
38 |
39 |
43 |
50 |
51 |
52 |
53 | );
54 | };
55 |
56 | PasswordInput.contextTypes = {
57 | t: PropTypes.func
58 | };
59 |
60 | export default PasswordInput;
61 |
--------------------------------------------------------------------------------
/src/components/PasswordInput/index.js:
--------------------------------------------------------------------------------
1 | import PasswordInput from './PasswordInput';
2 |
3 | export default PasswordInput;
4 |
--------------------------------------------------------------------------------
/src/components/PastingModal/PastingModal.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a modal displaying what is happening when pasting contents
3 | * @module fonio/components/PastingModal
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import PropTypes from 'prop-types';
10 | import {
11 | ModalCard,
12 | } from 'quinoa-design-library/components/';
13 |
14 | /**
15 | * Imports Project utils
16 | */
17 | import { translateNameSpacer } from '../../helpers/translateUtils';
18 |
19 | const UploadModal = ( {
20 | editorPastingStatus = {}
21 | }, { t } ) => {
22 |
23 | /**
24 | * Local functions
25 | */
26 | const translate = translateNameSpacer( t, 'Components.PastingModal' );
27 |
28 | /**
29 | * Computed variables
30 | */
31 | const { statusParameters = {} } = editorPastingStatus;
32 | let message;
33 | switch ( editorPastingStatus.status ) {
34 | case 'duplicating-contents':
35 | message = translate( 'Duplicating contents' );
36 | break;
37 | case 'updating-contents':
38 | message = translate( 'Updating contents' );
39 | break;
40 | case 'converting-contents':
41 | message = translate( 'Converting contents' );
42 | break;
43 | case 'duplicating-contextualizers':
44 | message = translate( 'Duplicating {n} contextualizers', { n: statusParameters.length } );
45 | break;
46 | case 'duplicating-notes':
47 | message = translate( 'Duplicating {n} notes', { n: statusParameters.length } );
48 | break;
49 | case 'fetching-images':
50 | if ( statusParameters.iteration ) {
51 | message = translate( 'Creating image {x} of {n}', { x: statusParameters.iteration, n: statusParameters.length } );
52 | }
53 | else {
54 | message = translate( 'Creating {n} images', { n: statusParameters.length } );
55 | }
56 | break;
57 | case 'creating-images':
58 | if ( statusParameters.iteration ) {
59 | message = translate( 'Creating image {x} of {n}', { x: statusParameters.iteration, n: statusParameters.length } );
60 | }
61 | else {
62 | message = translate( 'Creating {n} images', { n: statusParameters.length } );
63 | }
64 | break;
65 | case 'creating-resources':
66 | if ( statusParameters.iteration ) {
67 | message = translate( 'Creating item {x} of {n}', { x: statusParameters.iteration, n: statusParameters.length } );
68 | }
69 | else {
70 | message = translate( 'Creating {n} items', { n: statusParameters.length } );
71 | }
72 | break;
73 | case 'attaching-contextualizers':
74 | if ( statusParameters.iteration ) {
75 | message = translate( 'Attaching contextualizer {x} of {n}', { x: statusParameters.iteration, n: statusParameters.length } );
76 | }
77 | else {
78 | message = translate( 'Attaching {n} contextualizers', { n: statusParameters.length } );
79 | }
80 | break;
81 |
82 | default:
83 | break;
84 | }
85 | return (
86 |
91 | {message &&
92 |
93 | {message}
94 |
95 | }
96 |
97 | }
98 | />
99 | );
100 | };
101 |
102 | UploadModal.contextTypes = {
103 | t: PropTypes.func,
104 | };
105 |
106 | export default UploadModal;
107 |
--------------------------------------------------------------------------------
/src/components/PastingModal/index.js:
--------------------------------------------------------------------------------
1 | import PastingModal from './PastingModal';
2 | export default PastingModal;
3 |
--------------------------------------------------------------------------------
/src/components/ResourceForm/index.js:
--------------------------------------------------------------------------------
1 | import ResourceForm from './ResourceForm';
2 |
3 | export default ResourceForm;
4 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/AssetButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a icon button allowing to add/edit assets
3 | * @module fonio/components/SectionEditor
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React, { Component } from 'react';
9 | import PropTypes from 'prop-types';
10 | import icons from 'quinoa-design-library/src/themes/millet/icons';
11 |
12 | /**
13 | * Imports Project utils
14 | */
15 | import { translateNameSpacer } from '../../helpers/translateUtils';
16 | import { silentEvent } from '../../helpers/misc';
17 |
18 | /**
19 | * Imports Components
20 | */
21 | import IconBtn from '../IconBtn';
22 |
23 | class AssetButton extends Component {
24 | constructor( props ) {
25 | super( props );
26 | this.state = {};
27 | }
28 | render = () => {
29 |
30 | /**
31 | * Variables definition
32 | */
33 | const {
34 | props: {
35 | onClick,
36 | active,
37 | icon
38 | },
39 | context: { t }
40 | } = this;
41 |
42 | /**
43 | * Computed variables
44 | */
45 | /**
46 | * Local functions
47 | */
48 | const translate = translateNameSpacer( t, 'Components.SectionEditor' );
49 |
50 | /**
51 | * Callbacks handlers
52 | */
53 | const handleMouseDown = ( event ) => {
54 | silentEvent( event );
55 | };
56 |
57 | /**
58 | * References bindings
59 | */
60 | const bindRef = ( btn ) => {
61 | if ( btn ) {
62 | this.element = btn.element;
63 | }
64 | };
65 | return (
66 |
74 | );
75 | }
76 | }
77 |
78 | AssetButton.contextTypes = {
79 | t: PropTypes.func
80 | };
81 |
82 | export default AssetButton;
83 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/Bibliography.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a reusable bibliography wrapper for the editor component
3 | * @module fonio/components/SectionEditor
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import PropTypes from 'prop-types';
10 |
11 | /**
12 | * Imports Project utils
13 | */
14 | import { translateNameSpacer } from '../../helpers/translateUtils';
15 |
16 | /**
17 | * Renders the Bib component as a pure function
18 | * @param {object} props - (un)used props (see prop types below)
19 | * @param {object} context - used context data (see context types below)
20 | * @return {ReactElement} component - the resulting component
21 | */
22 | const Bib = ( unusedProps, {
23 | bibliography,
24 | t
25 | } ) => {
26 | const translate = translateNameSpacer( t, 'Components.References' );
27 | return (
28 |
29 | {translate( 'References' )}
30 | {bibliography}
31 |
32 | );
33 | };
34 |
35 | /**
36 | * Component's properties types
37 | */
38 | Bib.propTypes = {};
39 |
40 | /**
41 | * Component's context used properties
42 | */
43 | Bib.contextTypes = {
44 |
45 | /**
46 | * The properly formatted bibliography object to be rendered
47 | */
48 | bibliography: PropTypes.oneOfType( [
49 | PropTypes.object,
50 | PropTypes.array
51 | ] ),
52 |
53 | /**
54 | * The active language
55 | */
56 | lang: PropTypes.string,
57 |
58 | /**
59 | * Un-namespaced translate function
60 | */
61 | t: PropTypes.func.isRequired
62 | };
63 |
64 | export default Bib;
65 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/GlossaryMention.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a reusable inline glossary mention component
3 | * @module fonio/components/SectionEditor
4 | */
5 | /* eslint react/no-set-state: 0 */
6 | /* eslint react/prefer-stateless-function : 0 */
7 | /**
8 | * Imports Libraries
9 | */
10 | import React, { Component } from 'react';
11 | import PropTypes from 'prop-types';
12 |
13 | /**
14 | * GlossaryMention class for building react component instances
15 | */
16 | class GlossaryMention extends Component {
17 |
18 | /**
19 | * Component's context used properties
20 | */
21 | static contextTypes = {
22 | t: PropTypes.func.isRequired,
23 | }
24 |
25 | /**
26 | * constructor
27 | * @param {object} props - properties given to instance at instanciation
28 | */
29 | constructor( props ) {
30 | super( props );
31 | }
32 |
33 | shouldComponentUpdate = ( nextProps ) => {
34 | return this.props.children !== nextProps.children;
35 | }
36 |
37 | /**
38 | * Renders the component
39 | * @return {ReactElement} component - the component
40 | */
41 | render() {
42 | const {
43 | children,
44 | } = this.props;
45 | return {children} ;
46 | }
47 | }
48 |
49 | /**
50 | * Component's properties types
51 | */
52 | GlossaryMention.propTypes = {
53 |
54 | /**
55 | * The asset to consume for displaying the glossary mention
56 | */
57 | asset: PropTypes.object,
58 |
59 | /**
60 | * Children react elements of the component
61 | */
62 | children: PropTypes.array,
63 |
64 | /**
65 | * Callbacks when an asset is blured
66 | */
67 | onAssetBlur: PropTypes.func,
68 |
69 | /**
70 | * Callbacks when an asset is changed
71 | */
72 | onAssetChange: PropTypes.func,
73 |
74 | /**
75 | * Callbacks when an asset is focused
76 | */
77 | onAssetFocus: PropTypes.func,
78 | };
79 |
80 | export default GlossaryMention;
81 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/LinkContextualization.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a reusable inline citation widget component
3 | * @module fonio/components/SectionEditor
4 | */
5 | /* eslint react/no-set-state: 0 */
6 | /* eslint react/prefer-stateless-function : 0 */
7 |
8 | /**
9 | * Imports Libraries
10 | */
11 | import React, { Component } from 'react';
12 | import PropTypes from 'prop-types';
13 |
14 | class LinkContextualization extends Component {
15 |
16 | static contextTypes = {
17 | t: PropTypes.func.isRequired,
18 | }
19 | render = () => {
20 | const {
21 | children,
22 | } = this.props;
23 |
24 | return (
25 |
28 | {children}
29 |
30 | );
31 | }
32 | }
33 |
34 | /**
35 | * Component's properties types
36 | */
37 | LinkContextualization.propTypes = {
38 |
39 | /**
40 | * The asset to consume for displaying the inline citation
41 | */
42 | asset: PropTypes.object,
43 |
44 | /**
45 | * Children react elements of the component
46 | */
47 | children: PropTypes.array,
48 |
49 | /**
50 | * Callbacks when an asset is blured
51 | */
52 | onAssetBlur: PropTypes.func,
53 |
54 | /**
55 | * Callbacks when an asset is changed
56 | */
57 | onAssetChange: PropTypes.func,
58 |
59 | /**
60 | * Callbacks when an asset is focused
61 | */
62 | onAssetFocus: PropTypes.func,
63 | };
64 |
65 | export default LinkContextualization;
66 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/NoteButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a reusable note addition button
3 | * @module fonio/components/SectionEditor
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import PropTypes from 'prop-types';
10 | import icons from 'quinoa-design-library/src/themes/millet/icons';
11 |
12 | /**
13 | * Imports Project utils
14 | */
15 | import { translateNameSpacer } from '../../helpers/translateUtils';
16 | import { silentEvent } from '../../helpers/misc';
17 |
18 | /**
19 | * Imports Components
20 | */
21 | import IconBtn from '../IconBtn';
22 |
23 | const NoteButton = ( {
24 | onClick,
25 | active
26 | }, { t } ) => {
27 | const translate = translateNameSpacer( t, 'Components.SectionEditor' );
28 | const handleMouseDown = ( event ) => {
29 | silentEvent( event );
30 | };
31 | return (
32 |
39 | );
40 | };
41 |
42 | NoteButton.contextTypes = {
43 | t: PropTypes.func
44 | };
45 |
46 | export default NoteButton;
47 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/SectionEditorWrapper.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a wrapper for converting editor received props
3 | * into context data for its children
4 | * @module fonio/components/SectionEditor
5 | */
6 | /**
7 | * Imports Libraries
8 | */
9 | import React, { Component } from 'react';
10 | import PropTypes from 'prop-types';
11 |
12 | /**
13 | * Imports Components
14 | */
15 | import SectionEditor from './SectionEditor';
16 |
17 | export default class SectionEditorWrapper extends Component {
18 |
19 | static childContextTypes = {
20 | startExistingResourceConfiguration: PropTypes.func,
21 | startNewResourceConfiguration: PropTypes.func,
22 | deleteContextualizationFromId: PropTypes.func,
23 | }
24 |
25 | constructor( props ) {
26 | super( props );
27 | }
28 |
29 | getChildContext = () => ( {
30 | startExistingResourceConfiguration: this.props.startExistingResourceConfiguration,
31 | startNewResourceConfiguration: this.props.startNewResourceConfiguration,
32 | deleteContextualizationFromId: this.props.deleteContextualizationFromId
33 | } )
34 |
35 | render = () => {
36 | const {
37 | props
38 | } = this;
39 |
40 | return ;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/buttons/AssetButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a icon button allowing to add/edit assets
3 | * @module fonio/components/SectionEditor
4 | */
5 | /* eslint react/prop-types: 0 */
6 |
7 | import React from 'react';
8 | import PropTypes from 'prop-types';
9 | import ReactTooltip from 'react-tooltip';
10 |
11 | const AssetButton = ( {
12 | onClick,
13 | active,
14 | iconMap,
15 | message,
16 | ...otherProps
17 | } ) => {
18 | const onMouseDown = ( event ) => event.preventDefault();
19 | return (
20 |
28 | {iconMap.asset}
29 |
32 |
33 | );
34 | };
35 |
36 | AssetButton.propTypes = {
37 |
38 | active: PropTypes.bool,
39 |
40 | iconMap: PropTypes.object,
41 |
42 | message: PropTypes.string,
43 |
44 | onClick: PropTypes.func,
45 |
46 | };
47 |
48 | export default AssetButton;
49 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/buttons/BlockButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a toolbar button wrapper for block modifiers
3 | * @module fonio/components/SectionEditor
4 | */
5 | import React, { Component } from 'react';
6 | import { RichUtils } from 'draft-js';
7 | import PropTypes from 'prop-types';
8 |
9 | import {
10 | Button
11 | } from 'quinoa-design-library/components';
12 |
13 | class BlockButton extends Component {
14 |
15 | static propTypes = {
16 |
17 | /**
18 | * The block type this button is responsible for.
19 | */
20 | blockType: PropTypes.string,
21 |
22 | children: PropTypes.oneOfType( [
23 | PropTypes.array,
24 | PropTypes.object
25 | ] ),
26 |
27 | /**
28 | * The current editorState. This gets passed down from the editor.
29 | */
30 | editorState: PropTypes.object,
31 |
32 | /**
33 | * A method that can be called to update the editor's editorState. This
34 | * gets passed down from the editor.
35 | */
36 | updateEditorState: PropTypes.func,
37 | };
38 |
39 | isSelected = ( editorState, blockType ) => {
40 | if ( !editorState || !editorState.getSelection ) {
41 | return;
42 | }
43 | const selection = editorState.getSelection();
44 | const selectedBlock = editorState
45 | .getCurrentContent()
46 | .getBlockForKey( selection.getStartKey() );
47 | if ( !selectedBlock ) return false;
48 | const selectedBlockType = selectedBlock.getType();
49 | return selectedBlockType === blockType;
50 | };
51 |
52 | render = () => {
53 |
54 | const {
55 | editorState,
56 | blockType,
57 | children,
58 | updateEditorState,
59 | tooltip,
60 |
61 | /*
62 | * iconMap,
63 | * ...otherProps
64 | */
65 | } = this.props;
66 |
67 | const selected = this.isSelected( editorState, blockType );
68 | // const className = `scholar-draft-BlockButton${selected ? ' active' : ''}`;
69 |
70 | const onMouseDown = ( event ) => {
71 | event.preventDefault();
72 | updateEditorState( RichUtils.toggleBlockType( editorState, blockType ) );
73 | };
74 |
75 | return (
76 |
82 | {React.Children.map(
83 | children,
84 | ( child ) => React.cloneElement( child, {
85 | selected
86 | } )
87 | )}
88 |
89 | );
90 | }
91 | }
92 |
93 | export default BlockButton;
94 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/buttons/BlockQuoteButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a toolbar button for blockquote modifier
3 | * @module fonio/components/SectionEditor
4 | */
5 | /* eslint react/prop-types: 0 */
6 |
7 | import React from 'react';
8 | import BlockButton from './BlockButton';
9 |
10 | export default ( props ) => (
11 |
15 | {props.iconMap.quoteblock}
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/buttons/BoldButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a toolbar button for bold text modifier
3 | * @module fonio/components/SectionEditor
4 | */
5 | /* eslint react/prop-types: 0 */
6 |
7 | import React from 'react';
8 | import InlineButton from './InlineButton';
9 |
10 | export default ( props ) => (
11 |
15 | {props.iconMap.bold}
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/buttons/ButtonStyles.scss:
--------------------------------------------------------------------------------
1 |
2 |
3 | .scholar-draft-InlineButton,
4 | .scholar-draft-BlockButton,
5 | .scholar-draft-NoteButton,
6 | .scholar-draft-AssetButton
7 | {
8 | display: inline-block;
9 |
10 | width: 24px;
11 | height: 24px;
12 |
13 | cursor: pointer;
14 | transition: all .1s ease;
15 |
16 | background: inherit;
17 |
18 | img
19 | {
20 | transition: all .1s ease;
21 | }
22 |
23 | &:hover
24 | {
25 | background: #ccc;
26 | }
27 | &.active
28 | {
29 | background: #999;
30 | }
31 | }
32 |
33 |
34 | .scholar-draft-NoteButton,
35 | .scholar-draft-AssetButton
36 | {
37 | border-radius: 50%;
38 | }
39 |
40 | .scholar-draft-AssetButton
41 | {
42 | &.active
43 | {
44 | img
45 | {
46 | transform: rotate(45deg);
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/buttons/CodeBlockButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a toolbar button for codeblock modifier
3 | * @module fonio/components/SectionEditor
4 | */
5 | /* eslint react/prop-types: 0 */
6 |
7 | import React from 'react';
8 | import BlockButton from './BlockButton';
9 |
10 | export default ( props ) => (
11 |
15 | {props.iconMap.codeblock}
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/buttons/GlossaryButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a toolbar button for link modifier
3 | * @module fonio/components/SectionEditor
4 | */
5 | /* eslint react/prop-types: 0 */
6 |
7 | import React from 'react';
8 | import PropTypes from 'prop-types';
9 |
10 | import {
11 | Button,
12 | Image,
13 | } from 'quinoa-design-library/components';
14 |
15 | import icons from 'quinoa-design-library/src/themes/millet/icons';
16 |
17 | import { silentEvent } from '../../../helpers/misc';
18 |
19 | const GlossaryButton = ( { tooltip }, {
20 | // startNewResourceConfiguration,
21 | setGlossaryModalFocusData,
22 | editorFocus
23 | } ) => {
24 | const onClick = ( e ) => {
25 | silentEvent( e );
26 | setGlossaryModalFocusData( editorFocus );
27 | };
28 | return (
29 |
34 |
39 |
40 | );
41 | };
42 |
43 | GlossaryButton.contextTypes = {
44 | setGlossaryModalFocusData: PropTypes.func,
45 | editorFocus: PropTypes.string,
46 | // startNewResourceConfiguration: PropTypes.func,
47 | };
48 |
49 | export default GlossaryButton;
50 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/buttons/HeaderOneButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a toolbar button for h1 modifier
3 | * @module fonio/components/SectionEditor
4 | */
5 | /* eslint react/prop-types: 0 */
6 |
7 | import React from 'react';
8 | import BlockButton from './BlockButton';
9 |
10 | export default ( props ) => (
11 |
15 | {props.iconMap.h1}
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/buttons/HeaderTwoButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a toolbar button for h2 modifier
3 | * @module fonio/components/SectionEditor
4 | */
5 | /* eslint react/prop-types: 0 */
6 |
7 | import React from 'react';
8 | import BlockButton from './BlockButton';
9 |
10 | export default ( props ) => (
11 |
15 | {props.iconMap.h2}
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/buttons/InlineButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a generic button for inline modifiers
3 | * @module fonio/components/SectionEditor
4 | */
5 | import React, { Component } from 'react';
6 | import { RichUtils } from 'draft-js';
7 | import PropTypes from 'prop-types';
8 |
9 | import './ButtonStyles.scss';
10 |
11 | import {
12 | Button
13 | } from 'quinoa-design-library/components';
14 |
15 | class InlineButton extends Component {
16 |
17 | static propTypes = {
18 |
19 | children: PropTypes.oneOfType( [ PropTypes.array, PropTypes.object ] ),
20 |
21 | /**
22 | * The current editorState. This gets passed down from the editor.
23 | */
24 | editorState: PropTypes.object,
25 |
26 | iconMap: PropTypes.object,
27 | inlineStyleType: PropTypes.string,
28 |
29 | /**
30 | * The inline style type this button is responsible for.
31 | */
32 | styleType: PropTypes.string,
33 |
34 | /**
35 | * A method that can be called to update the editor's editorState. This
36 | * gets passed down from the editor.
37 | */
38 | updateEditorState: PropTypes.func,
39 | };
40 |
41 | /**
42 | * Checks wether current styling button is selected
43 | * @param {Record} editorState - editorState to check for selection
44 | * @param {string} inlineStyleType - inline style to inspect against the provided editorState
45 | * @return {boolean} isSelected -
46 | */
47 | isSelected = ( editorState, inlineStyleType ) => {
48 | if ( !editorState || !editorState.getSelection ) {
49 | return;
50 | }
51 | // Check the editor is focused
52 | const selection = editorState.getSelection();
53 |
54 | const selectedBlock = editorState
55 | .getCurrentContent()
56 | .getBlockForKey( selection.getStartKey() );
57 | if ( !selectedBlock ) {
58 | return false;
59 | }
60 |
61 | const currentInlineStyle = editorState.getCurrentInlineStyle();
62 | return currentInlineStyle.has( inlineStyleType );
63 | };
64 |
65 | render = () => {
66 |
67 | const {
68 | editorState,
69 | updateEditorState,
70 | inlineStyleType,
71 | tooltip,
72 | iconMap, /* eslint no-unused-vars:0 */
73 | ...otherProps
74 | } = this.props;
75 |
76 | const selected = this.isSelected( editorState, inlineStyleType );
77 | // const className = `scholar-draft-InlineButton${selected ? ' active' : ''} `;
78 |
79 | const onMouseDown = ( event ) => {
80 | event.preventDefault();
81 | updateEditorState( RichUtils.toggleInlineStyle( editorState, inlineStyleType ) );
82 | };
83 |
84 | return (
85 |
93 | {React.Children.map(
94 | this.props.children,
95 | ( child ) => React.cloneElement( child, {
96 | selected
97 | } )
98 | )}
99 |
100 | );
101 | };
102 | }
103 |
104 | export default InlineButton;
105 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/buttons/InternalLinkButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a toolbar button for link modifier
3 | * @module fonio/components/SectionEditor
4 | */
5 | /* eslint react/prop-types: 0 */
6 |
7 | import React from 'react';
8 | import PropTypes from 'prop-types';
9 |
10 | import {
11 | Button,
12 | Image,
13 | } from 'quinoa-design-library/components';
14 |
15 | import icon from '../../../sharedAssets/internal-link.svg';
16 |
17 | import { silentEvent } from '../../../helpers/misc';
18 |
19 | const InternalLinkButton = ( { tooltip }, {
20 | // startNewResourceConfiguration,
21 | setInternalLinkModalFocusData,
22 | editorFocus
23 | } ) => {
24 | const onClick = ( e ) => {
25 | silentEvent( e );
26 | // startNewResourceConfiguration(true, 'webpage');
27 | setInternalLinkModalFocusData( editorFocus );
28 | };
29 | return (
30 |
35 |
40 |
41 | );
42 | };
43 |
44 | InternalLinkButton.contextTypes = {
45 | setInternalLinkModalFocusData: PropTypes.func,
46 | editorFocus: PropTypes.string,
47 | // startNewResourceConfiguration: PropTypes.func,
48 | };
49 |
50 | export default InternalLinkButton;
51 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/buttons/ItalicButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a toolbar button for italic modifier
3 | * @module fonio/components/SectionEditor
4 | */
5 | /* eslint react/prop-types: 0 */
6 |
7 | import React from 'react';
8 | import InlineButton from './InlineButton';
9 |
10 | export default ( props ) => (
11 |
15 | {props.iconMap.italic}
16 |
17 | );
18 |
19 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/buttons/LinkButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a toolbar button for link modifier
3 | * @module fonio/components/SectionEditor
4 | */
5 | /* eslint react/prop-types: 0 */
6 |
7 | import React from 'react';
8 | import PropTypes from 'prop-types';
9 |
10 | import {
11 | Button,
12 | Image,
13 | } from 'quinoa-design-library/components';
14 |
15 | import icons from 'quinoa-design-library/src/themes/millet/icons';
16 |
17 | import { silentEvent } from '../../../helpers/misc';
18 |
19 | const LinkButton = ( { tooltip }, {
20 | // startNewResourceConfiguration,
21 | setLinkModalFocusData,
22 | editorFocus
23 | } ) => {
24 | const onClick = ( e ) => {
25 | silentEvent( e );
26 | // startNewResourceConfiguration(true, 'webpage');
27 | setLinkModalFocusData( editorFocus );
28 | };
29 | return (
30 |
35 |
40 |
41 | );
42 | };
43 |
44 | LinkButton.contextTypes = {
45 | setLinkModalFocusData: PropTypes.func,
46 | editorFocus: PropTypes.string,
47 | // startNewResourceConfiguration: PropTypes.func,
48 | };
49 |
50 | export default LinkButton;
51 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/buttons/NoteButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides an aside button for note adding
3 | * @module fonio/components/SectionEditor
4 | */
5 | import React from 'react';
6 | import PropTypes from 'prop-types';
7 | import ReactTooltip from 'react-tooltip';
8 |
9 | const NoteButton = ( {
10 | onClick,
11 | iconMap,
12 | message,
13 | ...otherProps
14 | } ) => {
15 |
16 | const onMouseDown = ( event ) => event.preventDefault();
17 |
18 | return (
19 |
26 | {iconMap.note}
27 |
30 |
31 | );
32 | };
33 |
34 | NoteButton.propTypes = {
35 | iconMap: PropTypes.object,
36 | message: PropTypes.string,
37 | onClick: PropTypes.func,
38 | };
39 |
40 | export default NoteButton;
41 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/buttons/OrderedListItemButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a toolbar button for ordered list modifier
3 | * @module fonio/components/SectionEditor
4 | */
5 | /* eslint react/prop-types: 0 */
6 |
7 | import React from 'react';
8 | import BlockButton from './BlockButton';
9 |
10 | export default ( props ) => (
11 |
15 | {props.iconMap.orderedlist}
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/buttons/RemoveFormattingButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a toolbar button for remove formatting action
3 | * @module fonio/components/SectionEditor
4 | */
5 | /* eslint react/prop-types: 0 */
6 |
7 | import React from 'react';
8 | import PropTypes from 'prop-types';
9 |
10 | import {
11 | Button,
12 | Image,
13 | } from 'quinoa-design-library/components';
14 |
15 | import icons from 'quinoa-design-library/src/themes/millet/icons';
16 |
17 | import { silentEvent } from '../../../helpers/misc';
18 |
19 | const RemoveFormattingButton = ( props, {
20 | removeFormattingForSelection
21 | } ) => {
22 | const onClick = ( e ) => {
23 | silentEvent( e );
24 | removeFormattingForSelection();
25 | };
26 | return (
27 |
32 |
37 |
38 | );
39 | };
40 |
41 | RemoveFormattingButton.contextTypes = {
42 | removeFormattingForSelection: PropTypes.func,
43 | };
44 |
45 | export default RemoveFormattingButton;
46 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/buttons/Separator.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import {
4 | Button,
5 | } from 'quinoa-design-library/components';
6 |
7 | export default () => (
8 |
11 | );
12 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/buttons/UnorderedListItemButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a toolbar button for unordered modifier
3 | * @module fonio/components/SectionEditor
4 | */
5 | /* eslint react/prop-types: 0 */
6 | import React from 'react';
7 | import BlockButton from './BlockButton';
8 |
9 | export default ( props ) => (
10 |
14 | {props.iconMap.unorderedlist}
15 |
16 | );
17 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/buttons/defaultButtons.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides default toolbar buttons
3 | * @module fonio/components/SectionEditor
4 | */
5 | import React from 'react';
6 | import BoldButton from './BoldButton';
7 | import ItalicButton from './ItalicButton';
8 |
9 | export default [
10 | ,
11 | ,
12 | ];
13 |
--------------------------------------------------------------------------------
/src/components/SectionEditor/index.js:
--------------------------------------------------------------------------------
1 | import SectionEditorWrapper from './SectionEditorWrapper';
2 |
3 | export default SectionEditorWrapper;
4 |
--------------------------------------------------------------------------------
/src/components/UploadModal/UploadModal.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a modal for displaying what is happening when uploading contents
3 | * @module fonio/components/UploadModal
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import PropTypes from 'prop-types';
10 | import {
11 | ModalCard,
12 | Notification,
13 | StretchedLayoutContainer,
14 | StretchedLayoutItem,
15 | } from 'quinoa-design-library/components/';
16 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
17 | import { faExclamationCircle } from '@fortawesome/free-solid-svg-icons/faExclamationCircle';
18 |
19 | /**
20 | * Imports Project utils
21 | */
22 | import { translateNameSpacer } from '../../helpers/translateUtils';
23 |
24 | const UploadModal = ( {
25 | uploadStatus
26 | }, { t } ) => {
27 | const translate = translateNameSpacer( t, 'Components.UploadModal' );
28 | return (
29 |
34 | {uploadStatus &&
35 |
36 | {uploadStatus.status === 'initializing' ?
37 | translate( 'Analyzing contents...' )
38 | : translate( 'Adding "{n}"', { n: uploadStatus.currentFileName } )}
39 |
40 | }
41 | {
42 | uploadStatus && uploadStatus.errors && uploadStatus.errors.length > 0 &&
43 |
44 | {
45 | uploadStatus.errors.map( ( error, errorIndex ) => (
46 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | {
57 | error.reason === 'too big' ?
58 | translate( '"{f}" is too big', { f: error.fileName } )
59 | : null
60 | }
61 |
62 |
63 |
64 |
65 | ) )
66 | }
67 |
68 | }
69 |
70 | }
71 | />
72 | );
73 | };
74 |
75 | UploadModal.contextTypes = {
76 | t: PropTypes.func,
77 | };
78 |
79 | export default UploadModal;
80 |
--------------------------------------------------------------------------------
/src/components/UploadModal/index.js:
--------------------------------------------------------------------------------
1 | import UploadModal from './UploadModal';
2 | export default UploadModal;
3 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Fonio Configuration Module
3 | * ===========================
4 | *
5 | * Module exporting the client app's configuration.
6 | *
7 | * IMPORTANT: the FONIO_CONFIG is a global variable which is:
8 | * `dev`: injected by webpack.DefinePlugin
9 | * `prod`: a global variable templated in a script tag
10 | */
11 | /* eslint no-console: 0 */
12 | const CONFIG = typeof FONIO_CONFIG !== 'undefined' ? FONIO_CONFIG : {};
13 |
14 | if ( !Object.keys( CONFIG ).length )
15 | console.warn( 'WARNING: FONIO_CONFIG is absent.' );
16 |
17 | CONFIG.restUrl = `${CONFIG.apiUrl }/api`;
18 |
19 | export default CONFIG;
20 |
--------------------------------------------------------------------------------
/src/features/AuthManager/components/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides an entrypoint to the authentication manager feature react components
3 | * @module fonio/features/AuthManager
4 | */
5 | import AuthManagerContainer from './AuthManagerContainer';
6 |
7 | export default AuthManagerContainer;
8 |
--------------------------------------------------------------------------------
/src/features/DesignView/components/MainDesignColumn.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a layout component for displaying design view main column layout
3 | * @module fonio/features/DesignView
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import StoryPlayer from 'quinoa-story-player';
10 | import Frame, { FrameContextConsumer } from 'react-frame-component';
11 | import { set } from 'lodash/fp';
12 | import {
13 | Column,
14 | Button,
15 | } from 'quinoa-design-library/components/';
16 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
17 | import { faPrint } from '@fortawesome/free-solid-svg-icons/faPrint';
18 | import { getTemplateName, isNewSchema, getStyles } from 'quinoa-schemas';
19 |
20 | /**
21 | * Imports Project utils
22 | */
23 | import { processCustomCss } from '../../../helpers/postcss';
24 |
25 | const PreviewWrapper = ( props ) => {
26 | const { story, lang } = props;
27 | const renderedStory = set(
28 | isNewSchema( story ) ? [
29 | 'settings',
30 | 'styles',
31 | getTemplateName( story ),
32 | 'css'
33 | ] : [
34 | 'settings',
35 | 'css'
36 | ],
37 | processCustomCss( getStyles( story ).css ),
38 | story
39 | );
40 | return (
41 |
44 | {'@import url(\'https://fonts.googleapis.com/css?family=Merriweather:400,400i,700,700i|Roboto:400,400i,700,700i,900\')'}
45 |
46 | }
47 | name={ 'preview' }
48 | id={ 'preview' }
49 | style={ { width: '100%', height: '100%' } }
50 | allowFullScreen
51 | >
52 |
53 | {( { document, window } ) => (
54 |
60 | )}
61 |
62 |
63 | );
64 | };
65 |
66 | const MainDesignColumn = ( {
67 | story,
68 | lang
69 | } ) => {
70 |
71 | const handleClickOnPrint = () => {
72 | window.frames.preview.focus();
73 | window.frames.preview.print();
74 | };
75 |
76 | return (
77 |
81 | {
82 |
86 | }
87 |
96 |
97 |
98 |
99 | );
100 | };
101 |
102 | export default MainDesignColumn;
103 |
--------------------------------------------------------------------------------
/src/features/DesignView/components/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides an entrypoint to the design view react components
3 | * @module fonio/features/DesignView
4 | */
5 | import DesignViewContainer from './DesignViewContainer';
6 |
7 | export default DesignViewContainer;
8 |
--------------------------------------------------------------------------------
/src/features/DesignView/utils/buildCssHelp.js:
--------------------------------------------------------------------------------
1 | export default ( translate ) => [
2 | {
3 | action: translate( 'Change the paragraphs font size' ),
4 | code: `
5 | .content-p{
6 | font-size: 10px;
7 | }`
8 | },
9 | {
10 | action: translate( 'Change the background color' ),
11 | code: `
12 | .wrapper, .nav{
13 | background: white;
14 | }`
15 | },
16 | {
17 | action: translate( 'Change the titles color' ),
18 | code: `
19 | .content-h1,.content-h2,.section-title
20 | {
21 | color: blue;
22 | }`
23 | }
24 | ];
25 |
--------------------------------------------------------------------------------
/src/features/EditionUiWrapper/components/EditionUiWrapperContainer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a connected component for handling edition ui generals
3 | * @module fonio/features/EditionUi
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React, { Component } from 'react';
9 | import { bindActionCreators } from 'redux';
10 | import { connect } from 'react-redux';
11 | import { withRouter } from 'react-router';
12 |
13 | /**
14 | * Imports Project utils
15 | */
16 | /**
17 | * Imports Ducks
18 | */
19 | import * as duck from '../duck';
20 | import * as userInfoDuck from '../../UserInfoManager/duck';
21 | import * as connectionsDuck from '../../ConnectionsManager/duck';
22 | import * as editedStoryDuck from '../../StoryManager/duck';
23 |
24 | /**
25 | * Imports Components
26 | */
27 | import EditionUiWrapperLayout from './EditionUiWrapperLayout';
28 |
29 | @connect(
30 | ( state ) => ( {
31 | lang: state.i18nState && state.i18nState.lang,
32 | ...connectionsDuck.selector( state.connections ),
33 | ...duck.selector( state.editionUiWrapper ),
34 | ...userInfoDuck.selector( state.userInfo ),
35 | ...editedStoryDuck.selector( state.editedStory ),
36 | } ),
37 | ( dispatch ) => ( {
38 | actions: bindActionCreators( {
39 |
40 | ...duck,
41 | ...userInfoDuck,
42 | ...connectionsDuck,
43 | }, dispatch )
44 | } )
45 | )
46 |
47 | class EditionUiWrapperContainer extends Component {
48 |
49 | constructor( props ) {
50 | super( props );
51 | }
52 |
53 | getNavLocation = ( path ) => {
54 | switch ( path ) {
55 | case '/story/:storyId/library':
56 | return 'library';
57 | case '/story/:storyId/design':
58 | return 'design';
59 | case '/story/:storyId/section/:sectionId':
60 | return 'editor';
61 | case '/story/:storyId/summary':
62 | case '/story/:storyId':
63 | return 'summary';
64 | default:
65 | return undefined;
66 | }
67 | }
68 |
69 | getActiveSectionTitle = ( story, sectionId ) => story.sections[sectionId].metadata.title;
70 |
71 | render() {
72 | const navLocation = this.getNavLocation( this.props.match.path );
73 | let activeSectionTitle;
74 | if ( this.props.match.params.sectionId && this.props.editedStory ) {
75 | activeSectionTitle = this.getActiveSectionTitle( this.props.editedStory, this.props.match.params.sectionId );
76 | }
77 | return (
78 |
84 | );
85 | }
86 | }
87 |
88 | export default withRouter( EditionUiWrapperContainer );
89 |
--------------------------------------------------------------------------------
/src/features/EditionUiWrapper/components/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides an entrypoint to the edition ui generic wrapper's react components
3 | * @module fonio/features/AuthManager
4 | */
5 | import EditionUiWrapperContainer from './EditionUiWrapperContainer';
6 |
7 | export default EditionUiWrapperContainer;
8 |
--------------------------------------------------------------------------------
/src/features/ErrorMessageManager/components/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides an entrypoint to error-related react components
3 | * @module fonio/features/ErrorMessage
4 | */
5 | import ErrorMessageContainer from './ErrorMessageContainer';
6 |
7 | export default ErrorMessageContainer;
8 |
--------------------------------------------------------------------------------
/src/features/HomeView/assets/user-guide-fr.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/medialab/fonio/974f497357f401250d69cad69d05c501f58c5c3d/src/features/HomeView/assets/user-guide-fr.pdf
--------------------------------------------------------------------------------
/src/features/HomeView/components/Footer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides the footer of the home view
3 | * @module fonio/features/HomeView
4 | */
5 | /* eslint react/no-danger : 0 */
6 | /**
7 | * Imports Libraries
8 | */
9 | import React from 'react';
10 | import {
11 | Footer,
12 | Container,
13 | Columns,
14 | Column,
15 | Content,
16 | } from 'quinoa-design-library/components/';
17 |
18 | import medialabLogo from '../assets/logo-medialab.svg';
19 | import forccastLogo from '../assets/logo-forccast.svg';
20 |
21 | const logoStyle = {
22 | maxWidth: '150px',
23 | paddingBottom: '.5rem',
24 | paddingTop: '.5rem',
25 | boxSizing: 'content-box',
26 | paddingLeft: '2rem',
27 | display: 'block'
28 | };
29 |
30 | const FooterComponent = ( {
31 | id,
32 | translate
33 | } ) => (
34 |
35 |
36 |
37 |
38 |
58 |
59 |
60 |
64 | FORCCAST program, fostering pedagogical innovations in controversy mapping.' )
67 | } }
68 | />
69 |
médialab SciencesPo, a research laboratory that connects social sciences with inventive methods.' )
72 | } }
73 | />
74 |
75 |
76 | {translate( 'Avatar icons courtesy of ' )}
77 |
81 | Freepik
82 | .
83 |
84 |
85 |
90 |
94 | AGPL v3
95 |
96 | {translate( ' and is hosted on ' )}
97 |
101 | Github
102 | .
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | );
111 |
112 | export default FooterComponent;
113 |
--------------------------------------------------------------------------------
/src/features/HomeView/components/HomeViewContainer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a connected component for handling the home view
3 | * @module fonio/features/HomeView
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React, { Component } from 'react';
9 | import PropTypes from 'prop-types';
10 | import { bindActionCreators } from 'redux';
11 | import { connect } from 'react-redux';
12 | import { setLanguage } from 'redux-i18n';
13 | import { withRouter } from 'react-router';
14 |
15 | /**
16 | * Imports Project utils
17 | */
18 | import { getEditionHistoryMap } from '../../../helpers/localStorageUtils';
19 |
20 | /**
21 | * Imports Ducks
22 | */
23 | import * as duck from '../duck';
24 | import * as userInfoDuck from '../../UserInfoManager/duck';
25 | import * as connectionsDuck from '../../ConnectionsManager/duck';
26 | import * as authDuck from '../../AuthManager/duck';
27 | import * as editionDuck from '../../EditionUiWrapper/duck';
28 | import * as errorMessageDuck from '../../ErrorMessageManager/duck';
29 |
30 | /**
31 | * Imports Components
32 | */
33 | import HomeViewLayout from './HomeViewLayout';
34 |
35 | /**
36 | * Redux-decorated component class rendering the takeaway dialog feature to the app
37 | */
38 | @connect(
39 | ( state ) => ( {
40 | ...editionDuck.selector( state.editionUiWrapper ),
41 | ...duck.selector( state.home ),
42 | lang: state.i18nState.lang,
43 | ...userInfoDuck.selector( state.userInfo ),
44 | ...connectionsDuck.selector( state.connections ),
45 | ...authDuck.selector( state.auth ),
46 | } ),
47 | ( dispatch ) => ( {
48 | actions: bindActionCreators( {
49 | ...editionDuck,
50 | ...userInfoDuck,
51 | ...connectionsDuck,
52 | ...authDuck,
53 | ...errorMessageDuck,
54 | ...duck,
55 | setLanguage,
56 | }, dispatch )
57 | } )
58 | )
59 | class HomeViewContainer extends Component {
60 |
61 | /**
62 | * Context data used by the component
63 | */
64 | static contextTypes = {
65 |
66 | /**
67 | * Un-namespaced translate function
68 | */
69 | t: PropTypes.func.isRequired,
70 |
71 | /**
72 | * Redux store
73 | */
74 | store: PropTypes.object.isRequired
75 | }
76 |
77 | /**
78 | * constructor
79 | * @param {object} props - properties given to instance at instanciation
80 | */
81 | constructor( props ) {
82 | super( props );
83 | }
84 |
85 | componentWillMount() {
86 | this.props.actions.fetchStories();
87 | const editionHistoryMap = getEditionHistoryMap();
88 | this.props.actions.setEditionHistory( editionHistoryMap );
89 | this.props.actions.setStoryLoginId( undefined );
90 | }
91 |
92 | /**
93 | * Defines whether the component should re-render
94 | * @param {object} nextProps - the props to come
95 | * @param {object} nextState - the state to come
96 | * @return {boolean} shouldUpdate - whether to update or not
97 | */
98 | shouldComponentUpdate() {
99 | // todo: optimize when the feature is stabilized
100 | return true;
101 | }
102 |
103 | /**
104 | * Renders the component
105 | * @return {ReactElement} component - the component
106 | */
107 | render() {
108 | return (
109 |
112 | );
113 | }
114 | }
115 |
116 | export default withRouter( HomeViewContainer );
117 |
--------------------------------------------------------------------------------
/src/features/HomeView/components/NewStoryForm.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a form for creating a new story
3 | * @module fonio/features/HomeView
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import {
10 | Column,
11 | Columns,
12 | Container,
13 | Delete,
14 | DropZone,
15 | Help,
16 | Tab,
17 | TabLink,
18 | TabList,
19 | Tabs,
20 | Title,
21 | } from 'quinoa-design-library/components';
22 |
23 | /**
24 | * Imports Components
25 | */
26 | import MetadataForm from '../../../components/MetadataForm';
27 |
28 | const NewStoryForm = ( {
29 | createStoryStatus,
30 | importStoryStatus,
31 | mode,
32 | newStory,
33 | onClose,
34 | onCloseNewStory,
35 | onCreateNewStory,
36 | onDropFiles,
37 | onSetModeFile,
38 | onSetModeForm,
39 | translate,
40 | widthRatio,
41 | } ) => {
42 | return (
43 |
44 | {
45 |
46 |
47 |
48 |
49 | {translate( 'New Story' )}
50 |
51 |
52 |
53 |
54 |
55 |
56 |
60 |
61 |
62 | {translate( 'Create a story' )}
66 |
67 | {translate( 'Import an existing story' )}
71 |
72 |
73 |
74 |
75 | {mode === 'form' ?
76 |
83 | :
84 |
85 |
89 | {translate( 'Drop a fonio file' )}
90 |
91 | {importStoryStatus === 'fail' && {translate( 'Story is not valid' )} }
92 |
93 |
94 | }
95 |
96 | }
97 |
98 |
99 | );
100 | };
101 |
102 | export default NewStoryForm;
103 |
--------------------------------------------------------------------------------
/src/features/HomeView/components/OtherUsersWidget.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a component for representing other users active in the classroom
3 | * @module fonio/features/HomeView
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import {
10 | Title,
11 | StretchedLayoutContainer,
12 | StretchedLayoutItem,
13 | HelpPin,
14 | Image,
15 | Content
16 | } from 'quinoa-design-library/components';
17 |
18 | const OtherUsersWidget = ( {
19 | translate,
20 | users,
21 | userId,
22 | } ) => {
23 | return (
24 |
25 | {
26 | users &&
27 | Object.keys( users )
28 | .filter( ( thatUserId ) => userId !== thatUserId ).length > 0 &&
29 |
33 | {translate( 'Who else is online ?' )}
34 | {translate( 'writers connected to this classroom right now' )}
35 |
36 | }
37 |
38 | {users &&
39 | Object.keys( users )
40 | .filter( ( thatUserId ) => userId !== thatUserId )
41 | .map( ( thatUserId ) => ( { userId, ...users[thatUserId] } ) )
42 | .map( ( user, index ) => {
43 | return (
44 |
49 |
50 |
55 |
56 |
57 |
58 | {user.name}
59 |
60 |
61 |
62 | );
63 | } )
64 | }
65 |
66 |
67 | );
68 | };
69 |
70 | export default OtherUsersWidget;
71 |
--------------------------------------------------------------------------------
/src/features/HomeView/components/ProfileWidget.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a component allowing user to preview and edit its personal identification information
3 | * @module fonio/features/HomeView
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import {
10 | Title,
11 | StretchedLayoutContainer,
12 | StretchedLayoutItem,
13 | Image,
14 | HelpPin,
15 | Button
16 | } from 'quinoa-design-library/components/';
17 |
18 | const ProfileWidget = ( {
19 | translate,
20 | onEdit,
21 | userInfo,
22 | } ) => {
23 | return (
24 |
28 |
32 | {translate( 'Your profile' )}
33 | {translate( 'choose how you will be identified by other writers' )}
34 |
35 | {userInfo.userId !== undefined &&
36 |
37 |
38 |
44 |
45 |
49 | {userInfo.name}
50 |
51 |
52 |
53 | {translate( 'edit' )}
54 |
55 |
56 |
57 | }
58 |
59 | );
60 | };
61 |
62 | export default ProfileWidget;
63 |
--------------------------------------------------------------------------------
/src/features/HomeView/components/StoryCard.scss:
--------------------------------------------------------------------------------
1 |
2 |
3 | .fonio-StoryCard{
4 | .users-container{
5 | max-height: 30rem;
6 | overflow-x: auto;
7 | }
8 | .users-wrapper{
9 | display: flex;
10 | flex-flow: row wrap;
11 | }
12 | .user-container{
13 | margin-right: 1rem;
14 | margin-bottom: 1rem;
15 | }
16 | .authors-container{
17 | padding-bottom: 3rem;
18 | }
19 | .last-update-container{
20 | align-self: flex-end;
21 | position: absolute;
22 | bottom: 2.5rem;
23 | left: 1.5rem;
24 | }
25 | .inline-icon-container{
26 | margin-left: .5rem;
27 | margin-right: 1rem;
28 | }
29 | .title,.subtitle{
30 | color: inherit;
31 | }
32 | &.is-special{
33 | .card{
34 | background: #4a4a4a;
35 | color: #fff;
36 | }
37 | }
38 | @media screen and (max-width:789px) {
39 | .card-content{
40 | padding-bottom: 0;
41 | }
42 | .column.is-two-fifth{
43 | padding-top: 0;
44 |
45 | }
46 | .aside-actions{
47 | padding-top: 0;
48 | padding-bottom: 1.5rem;
49 | }
50 | .authors-container{
51 | padding-bottom: 0;
52 | .content{
53 | margin-bottom: 0;
54 | }
55 | }
56 | .users-container{
57 | padding-bottom: 0;
58 | padding-top: 0;
59 | }
60 | .last-update-container{
61 | padding-top: 1rem;
62 | align-self: unset;
63 | position: relative;
64 | bottom: unset;
65 | left: unset;
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/src/features/HomeView/components/StoryCardWrapper.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a wrapper for story cards as displayed in list within the home view
3 | * @module fonio/features/HomeView
4 | */
5 | /* eslint react/prefer-stateless-function : 0 */
6 | /**
7 | * Imports Libraries
8 | */
9 | import React, { Component } from 'react';
10 | import {
11 | Level,
12 | Column
13 | } from 'quinoa-design-library/components/';
14 |
15 | /**
16 | * Imports Components
17 | */
18 | import StoryCard from './StoryCard';
19 |
20 | export default class StoryCardWrapper extends Component {
21 | render = () => {
22 | const {
23 | story,
24 | users,
25 | onAction: handleAction,
26 | onClick: handleClick,
27 | } = this.props;
28 | return (
29 |
30 |
31 |
37 |
38 |
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/features/HomeView/components/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides an entrypoint to the authentication manager feature react components
3 | * @module fonio/features/HomeView
4 | */
5 | import HomeViewContainer from './HomeViewContainer';
6 |
7 | export default HomeViewContainer;
8 |
--------------------------------------------------------------------------------
/src/features/HomeView/duck.spec.js:
--------------------------------------------------------------------------------
1 | import expect from 'expect';
2 |
3 | import {
4 | ui as uiReducer,
5 | SET_TAB_MODE,
6 | SET_SEARCH_STRING,
7 | SET_SORTING_MODE,
8 | SET_IDENTIFICATION_MODAL_SWITCH,
9 | SET_PREVIEWED_STORY_ID,
10 | SET_STORY_DELETE_ID,
11 | SET_CHANGE_PASSWORD_ID,
12 | SET_OVERRIDE_IMPORT,
13 | SET_OVERRIDE_STORY_MODE,
14 | SET_NEW_STORY_OPEN,
15 | SET_NEW_STORY_TAB_MODE,
16 | SET_PASSWORD_MODAL_OPEN
17 | } from './duck';
18 |
19 | describe( 'HomeView ui reducer test', () => {
20 | let mockState;
21 | let action;
22 |
23 | beforeEach( () => {
24 | mockState = {};
25 | } );
26 |
27 | test.each( [
28 | [ 'stories', SET_TAB_MODE, 'stories', 'tabMode' ],
29 | [ 'searchinput', SET_SEARCH_STRING, 'searchinput', 'searchString' ],
30 | [ 'title', SET_SORTING_MODE, 'title', 'sortingMode' ],
31 | [ false, SET_IDENTIFICATION_MODAL_SWITCH, false, 'identificationModalSwitch' ],
32 | [ 'testStoryId', SET_PREVIEWED_STORY_ID, 'testStoryId', 'previewedStoryId' ],
33 | [ 'testStoryId', SET_STORY_DELETE_ID, 'testStoryId', 'storyDeleteId' ],
34 | [ 'testStoryId', SET_CHANGE_PASSWORD_ID, 'testStoryId', 'changePasswordId' ],
35 | [ false, SET_OVERRIDE_IMPORT, false, 'overrideImport' ],
36 | [ 'create', SET_OVERRIDE_STORY_MODE, 'create', 'overrideStoryMode' ],
37 | [ false, SET_NEW_STORY_OPEN, false, 'newStoryOpen' ],
38 | [ 'form', SET_NEW_STORY_TAB_MODE, 'form', 'newStoryTabMode' ],
39 | [ false, SET_PASSWORD_MODAL_OPEN, false, 'passwordModalOpen' ],
40 | ] )(
41 | 'should return %p when %s with %p',
42 | ( expected, actionName, input, reducerName ) => {
43 | action = {
44 | type: actionName,
45 | payload: input
46 | };
47 | const resultState = uiReducer( mockState, action );
48 | expect( resultState[reducerName] ).toEqual( expected );
49 | },
50 | );
51 | } );
52 |
--------------------------------------------------------------------------------
/src/features/LibraryView/components/ConfirmBatchDeleteModal.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a modal for confirm the deletion of several resources
3 | * @module fonio/features/LibraryView
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import {
10 | ModalCard,
11 | Button
12 | } from 'quinoa-design-library/components';
13 |
14 | const ConfirmBatchDeleteModal = ( {
15 | translate,
16 | isActive,
17 | actualResourcesPromptedToDelete,
18 | resourcesPromptedToDelete,
19 | endangeredContextualizationsLength,
20 | onDelete,
21 | onCancel
22 | } ) => (
23 |
29 | {
30 | actualResourcesPromptedToDelete.length !== resourcesPromptedToDelete.length &&
31 |
32 | {
33 | translate( '{x} of {y} of the resources you selected cannot be deleted now because they are used by another author.', { x: resourcesPromptedToDelete.length - actualResourcesPromptedToDelete.length, y: resourcesPromptedToDelete.length } )
34 | }
35 |
36 | }
37 | {endangeredContextualizationsLength > 0 &&
38 | {
39 | translate( [
40 | 'You will destroy one item mention in your content if you delete these items.',
41 | 'You will destroy {n} item mentions in your content if your delete these items.',
42 | 'n'
43 | ],
44 | { n: endangeredContextualizationsLength } )}
45 |
46 | }
47 |
48 | {translate( [ 'Are you sure you want to delete this item ?', 'Are you sure you want to delete these items ?', 'n' ], { n: resourcesPromptedToDelete.length } )}
49 |
50 |
51 | }
52 | footerContent={ [
53 | {translate( 'Delete' )}
60 | ,
61 | {translate( 'Cancel' )}
67 | ,
68 | ] }
69 | />
70 | );
71 |
72 | export default ConfirmBatchDeleteModal;
73 |
--------------------------------------------------------------------------------
/src/features/LibraryView/components/ResourceCard.scss:
--------------------------------------------------------------------------------
1 | .fonio-ResourceCard
2 | {
3 | .bib-wrapper
4 | {
5 | .Bibliography
6 | {
7 | .csl-entry
8 | {
9 | position: relative;
10 |
11 | overflow: hidden;
12 |
13 | max-height: 9rem;
14 |
15 | word-break: break-all;
16 | &:before
17 | {
18 | position: absolute;
19 | top: 0;
20 | left: 0;
21 |
22 | width: 100%;
23 | height: 100%;
24 |
25 | content: '';
26 | }
27 | }
28 | a
29 | {
30 | pointer-events: none;
31 | }
32 | }
33 | }
34 | .preview-container
35 | {
36 | overflow: hidden;
37 | }
38 |
39 | .ReactTable
40 | {
41 | .-pagination
42 | {
43 | display: none;
44 | }
45 | .rt-tbody{
46 | overflow-y: hidden;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/features/LibraryView/components/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides an entrypoint to the library view react components
3 | * @module fonio/features/LibraryView
4 | */
5 | import LibraryViewContainer from './LibraryViewContainer';
6 |
7 | export default LibraryViewContainer;
8 |
--------------------------------------------------------------------------------
/src/features/ReadStoryView/components/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides an entrypoint to the read story view react components
3 | * @module fonio/features/ReadStoryView
4 | */
5 | import ReadStoryViewContainer from './ReadStoryViewContainer';
6 |
7 | export default ReadStoryViewContainer;
8 |
--------------------------------------------------------------------------------
/src/features/SectionView/components/MoveButton.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a mini component for representing move possibility
3 | * @module fonio/features/SectionView
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import {
10 | Button,
11 | Icon
12 | } from 'quinoa-design-library/components';
13 | import icons from 'quinoa-design-library/src/themes/millet/icons';
14 |
15 | const MoveButton = ( {
16 |
17 | } ) => (
18 |
24 |
25 |
26 |
27 |
28 | );
29 |
30 | export default MoveButton;
31 |
--------------------------------------------------------------------------------
/src/features/SectionView/components/ResourceMiniCard.scss:
--------------------------------------------------------------------------------
1 | .bib-wrapper-mini {
2 | .Bibliography
3 | {
4 | .csl-entry
5 | {
6 | overflow: hidden;
7 | max-height: 4.5rem;
8 | word-break: break-all;
9 | position: relative;
10 | &:before
11 | {
12 | content:'';
13 | width:100%;
14 | height:100%;
15 | position:absolute;
16 | left:0;
17 | top:0;
18 | }
19 | }
20 | a
21 | {
22 | pointer-events: none;
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/src/features/SectionView/components/ShortcutsModal.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a modal displaying shortcuts help
3 | * @module fonio/features/SectionView
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import {
10 | ModalCard
11 | } from 'quinoa-design-library/components';
12 |
13 | const ShortcutsModal = ( {
14 | translate,
15 | isActive,
16 | onClose,
17 | } ) => (
18 |
27 |
28 | {translate( 'All the shortcuts presented below are also accessible through the editor graphical interface (move cursor/select text)' )}
29 |
30 |
31 |
32 |
33 | {translate( 'Shortcut' )}
34 | {translate( 'Where' )}
35 | {translate( 'Effect' )}
36 |
37 |
38 |
39 |
40 | cmd+l
41 | {translate( 'Anywhere' )}
42 | {translate( 'Open item citation widget' )}
43 |
44 |
45 | cmd+m
46 | {translate( 'Anywhere' )}
47 | {translate( 'Add a new note' )}
48 |
49 |
50 | {translate( '"#" then space' )}
51 | {translate( 'Begining of a paragraph' )}
52 | {translate( 'Add a title' )}
53 |
54 |
55 | {translate( '">" then space' )}
56 | {translate( 'Begining of a paragraph' )}
57 | {translate( 'Add a citation block' )}
58 |
59 |
60 | {translate( '"*" then content then "*"' )}
61 | {translate( 'Anywhere' )}
62 | {translate( 'Write italic text' )}
63 |
64 |
65 | {translate( '"**" then content then "**"' )}
66 | {translate( 'Anywhere' )}
67 | {translate( 'Write bold text' )}
68 |
69 |
70 | {translate( '"*" then space' )}
71 | {translate( 'Begining of a paragraph' )}
72 | {translate( 'Begin a list' )}
73 |
74 |
75 |
76 |
77 | }
78 | />
79 | );
80 |
81 | export default ShortcutsModal;
82 |
--------------------------------------------------------------------------------
/src/features/SectionView/components/SortableMiniSectionsList.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a list of sections for the section view
3 | * @module fonio/features/SectionView
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import { SortableContainer, SortableElement } from 'react-sortable-hoc';
10 | import { List, AutoSizer } from 'react-virtualized';
11 | import ReactTooltip from 'react-tooltip';
12 | import {
13 | Level,
14 | Column,
15 | } from 'quinoa-design-library/components/';
16 |
17 | /**
18 | * Imports Components
19 | */
20 | import SectionMiniCard from './SectionMiniCard';
21 |
22 | const SortableItem = SortableElement( ( {
23 | value: section,
24 | onOpenSettings,
25 | onDeleteSection,
26 | setSectionLevel,
27 |
28 | storyId,
29 |
30 | setSectionIndex,
31 | sectionIndex,
32 | maxSectionIndex,
33 | history,
34 |
35 | } ) => {
36 | const handleDelete = ( event ) => {
37 | event.stopPropagation();
38 | onDeleteSection( section.id );
39 | };
40 | const handleSelect = () => {
41 | if ( section.lockStatus === 'open' || ( section.lockData && section.lockData.status === 'idle' ) ) {
42 | history.push( `/story/${storyId}/section/${section.id}` );
43 | }
44 | };
45 | return (
46 |
47 |
51 |
62 |
63 |
64 | );
65 | } );
66 |
67 | const SortableSectionsList = SortableContainer( ( {
68 | items,
69 | ...props
70 | } ) => {
71 | const rowRenderer = ( {
72 | key,
73 | style,
74 | index,
75 | } ) => {
76 | return (
77 |
81 |
87 |
88 | );
89 | };
90 | const handleRowsRendered = () =>
91 | ReactTooltip.rebuild();
92 | return (
93 |
94 | {( { width, height } ) => (
95 |
103 | )}
104 |
105 | );
106 | } );
107 |
108 | export default SortableSectionsList;
109 |
--------------------------------------------------------------------------------
/src/features/SectionView/components/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides an entrypoint to the section view react components
3 | * @module fonio/features/SectionView
4 | */
5 | import SectionViewContainer from './SectionViewContainer';
6 |
7 | export default SectionViewContainer;
8 |
--------------------------------------------------------------------------------
/src/features/SectionsManager/duck.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module exports logic-related elements for specific cross-view sections operations
3 | * This module follows the ducks convention for putting in the same place actions, action types,
4 | * state selectors and reducers about a given feature (see https://github.com/erikras/ducks-modular-redux)
5 | * @module fonio/features/SectionsManager
6 | */
7 |
8 | import { combineReducers } from 'redux';
9 | import { createStructuredSelector } from 'reselect';
10 |
11 | import { getStatePropFromActionSet } from '../../helpers/reduxUtils';
12 |
13 | /**
14 | * ===================================================
15 | * ACTION NAMES
16 | * ===================================================
17 | */
18 | /**
19 | * UI
20 | */
21 | const SET_PROMPTED_TO_DELETE_SECTION_ID = 'SET_PROMPTED_TO_DELETE_SECTION_ID';
22 |
23 | /**
24 | * ===================================================
25 | * ACTION CREATORS
26 | * ===================================================
27 | */
28 |
29 | export const setPromptedToDeleteSectionId = ( payload ) => ( {
30 | type: SET_PROMPTED_TO_DELETE_SECTION_ID,
31 | payload
32 | } );
33 |
34 | /**
35 | * ===================================================
36 | * REDUCERS
37 | * ===================================================
38 | */
39 |
40 | const UI_DEFAULT_STATE = {
41 | promptedToDeleteSectionId: undefined,
42 | };
43 |
44 | /**
45 | * This redux reducer handles the state of the ui
46 | * @param {object} state - the state given to the reducer
47 | * @param {object} action - the action to use to produce new state
48 | * @return {object} newState - the resulting state
49 | */
50 | function ui( state = UI_DEFAULT_STATE, action ) {
51 | const { payload } = action;
52 | switch ( action.type ) {
53 | case SET_PROMPTED_TO_DELETE_SECTION_ID:
54 | const propName = getStatePropFromActionSet( action.type );
55 | return {
56 | ...state,
57 | [propName]: payload
58 | };
59 | default:
60 | return state;
61 | }
62 | }
63 |
64 | /**
65 | * The module exports a reducer connected to pouchdb thanks to redux-pouchdb
66 | */
67 | export default combineReducers( {
68 | ui,
69 | } );
70 |
71 | /**
72 | * ===================================================
73 | * SELECTORS
74 | * ===================================================
75 | */
76 | const promptedToDeleteSectionId = ( state ) => state.ui.promptedToDeleteSectionId;
77 |
78 | /**
79 | * The selector is a set of functions for accessing this feature's state
80 | * @type {object}
81 | */
82 | export const selector = createStructuredSelector( {
83 | promptedToDeleteSectionId,
84 | } );
85 |
--------------------------------------------------------------------------------
/src/features/SummaryView/components/SortableSectionsList.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a sortable sections cards list
3 | * @module fonio/features/SummaryView
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React from 'react';
9 | import { SortableContainer, SortableElement } from 'react-sortable-hoc';
10 | import FlipMove from 'react-flip-move';
11 | import {
12 | Level,
13 | Column,
14 | } from 'quinoa-design-library/components/';
15 |
16 | /**
17 | * Imports Components
18 | */
19 | import SectionCard from './SectionCard';
20 |
21 | const SortableItem = SortableElement( ( {
22 | value: section,
23 | story,
24 | goToSection,
25 | onDelete,
26 | setSectionLevel,
27 | reverseSectionLockMap = {},
28 | isSorting,
29 | sectionIndex,
30 | // sectionIndex,
31 | maxSectionIndex,
32 | setSectionIndex,
33 | } ) => {
34 | return (
35 |
36 |
40 |
52 |
53 |
54 | );
55 | }
56 | );
57 |
58 | const SortableSectionsList = SortableContainer( ( {
59 | items,
60 | ...props
61 | } ) => {
62 | return (
63 |
64 | {items
65 | .map( ( section, index ) => {
66 | return (
67 |
75 | );
76 | }
77 | )}
78 |
79 | );
80 | } );
81 |
82 | export default SortableSectionsList;
83 |
--------------------------------------------------------------------------------
/src/features/SummaryView/components/SummaryViewContainer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a connected component for handling the summary view
3 | * @module fonio/features/SummaryView
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import React, { Component } from 'react';
9 | import { bindActionCreators } from 'redux';
10 | import { connect } from 'react-redux';
11 |
12 | /**
13 | * Imports Ducks
14 | */
15 | import * as duck from '../duck';
16 | import * as editedStoryDuck from '../../StoryManager/duck';
17 | import * as connectionsDuck from '../../ConnectionsManager/duck';
18 | import * as sectionsManagementDuck from '../../SectionsManager/duck';
19 |
20 | /**
21 | * Imports Components
22 | */
23 | import SummaryViewLayout from './SummaryViewLayout';
24 | import EditionUiWrapper from '../../EditionUiWrapper/components';
25 |
26 | @connect(
27 | ( state ) => ( {
28 | ...duck.selector( state.summary ),
29 | ...editedStoryDuck.selector( state.editedStory ),
30 | ...connectionsDuck.selector( state.connections ),
31 | ...sectionsManagementDuck.selector( state.sectionsManagement ),
32 | } ),
33 | ( dispatch ) => ( {
34 | actions: bindActionCreators( {
35 | ...connectionsDuck,
36 | ...editedStoryDuck,
37 | ...sectionsManagementDuck,
38 | ...duck
39 | }, dispatch )
40 | } )
41 | )
42 | class SummaryViewContainer extends Component {
43 |
44 | constructor( props ) {
45 | super( props );
46 | }
47 |
48 | componentWillMount = () => {
49 | const {
50 | match: {
51 | params: {
52 | storyId
53 | }
54 | },
55 | userId
56 | } = this.props;
57 | this.props.actions.enterBlock( {
58 | storyId,
59 | userId,
60 | blockType: 'summary',
61 | blockId: 'summary',
62 | noLock: true
63 | } );
64 | }
65 |
66 | shouldComponentUpdate = () => true;
67 |
68 | componentWillUnmount = () => {
69 |
70 | /**
71 | * Leave metadata if it was locked
72 | */
73 | const {
74 | lockingMap,
75 | userId,
76 | editedStory = {},
77 | actions: {
78 | leaveBlock
79 | }
80 | } = this.props;
81 | const { id: storyId } = editedStory;
82 | const userLockedOnMetadataId = lockingMap[storyId] && lockingMap[storyId].locks &&
83 | Object.keys( lockingMap[storyId].locks )
84 | .find( ( thatUserId ) => lockingMap[storyId].locks[thatUserId].storyMetadata !== undefined );
85 | if ( userLockedOnMetadataId && userLockedOnMetadataId === userId ) {
86 | leaveBlock( {
87 | storyId,
88 | userId,
89 | blockType: 'storyMetadata',
90 | blockId: 'storyMetadata',
91 | } );
92 | }
93 |
94 | this.props.actions.leaveBlock( {
95 | storyId,
96 | userId,
97 | blockType: 'summary',
98 | blockId: 'summary',
99 | noLock: true
100 | } );
101 | }
102 |
103 | goToSection = ( sectionId ) => {
104 | const {
105 | editedStory: {
106 | id
107 | }
108 | } = this.props;
109 | this.props.history.push( `/story/${id}/section/${sectionId}` );
110 | }
111 |
112 | render() {
113 | return this.props.editedStory ?
114 | (
115 |
116 |
120 |
121 | )
122 | : null;
123 | }
124 | }
125 |
126 | export default SummaryViewContainer;
127 |
--------------------------------------------------------------------------------
/src/features/SummaryView/components/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides an entrypoint to the summary view react components
3 | * @module fonio/features/SummaryView
4 | */
5 | import SummaryViewContainer from './SummaryViewContainer';
6 |
7 | export default SummaryViewContainer;
8 |
--------------------------------------------------------------------------------
/src/features/UserInfoManager/duck.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module exports logic-related elements for managing user personal identification
3 | * This module follows the ducks convention for putting in the same place actions, action types,
4 | * state selectors and reducers about a given feature (see https://github.com/erikras/ducks-modular-redux)
5 | * @module fonio/features/UserInfo
6 | */
7 |
8 | import { createStructuredSelector } from 'reselect';
9 | import { saveUserInfo, loadUserInfo } from '../../helpers/localStorageUtils';
10 |
11 | /**
12 | * ===================================================
13 | * ACTION NAMES
14 | * ===================================================
15 | */
16 | export const SET_USER_INFO = 'SET_USER_INFO';
17 | export const SET_USER_INFO_TEMP = 'SET_USER_INFO_TEMP';
18 |
19 | import { SET_IDENTIFICATION_MODAL_SWITCH } from '../HomeView/duck';
20 |
21 | /**
22 | * ===================================================
23 | * ACTION CREATORS
24 | * ===================================================
25 | */
26 | export const setUserInfo = ( payload ) => ( {
27 | type: SET_USER_INFO,
28 | payload
29 | } );
30 |
31 | export const setUserInfoTemp = ( payload ) => ( {
32 | type: SET_USER_INFO_TEMP,
33 | payload
34 | } );
35 |
36 | const DEFAULT_USER_INFO_STATE = {
37 |
38 | /**
39 | * User info
40 | */
41 | userInfo: loadUserInfo(),
42 |
43 | /**
44 | * temp value of user info
45 | */
46 | userInfoTemp: loadUserInfo(),
47 | };
48 |
49 | /**
50 | * Reducer for the user info function
51 | * @param {object} state
52 | * @param {object} action
53 | * @return {object} newState
54 | */
55 | export default function userInfo( state = DEFAULT_USER_INFO_STATE, action ) {
56 | switch ( action.type ) {
57 | case SET_USER_INFO:
58 | saveUserInfo( action.payload );
59 | return {
60 | ...state,
61 | userInfo: action.payload,
62 | userInfoTemp: action.payload,
63 | };
64 | case SET_USER_INFO_TEMP:
65 | return {
66 | ...state,
67 | userInfoTemp: action.payload,
68 | };
69 | case SET_IDENTIFICATION_MODAL_SWITCH:
70 | if ( action.payload === false ) {
71 | return {
72 | ...state,
73 | userInfoTemp: loadUserInfo()
74 | };
75 | }
76 | return state;
77 | default:
78 | return state;
79 | }
80 | }
81 |
82 | export const selector = createStructuredSelector( {
83 | userInfo: ( state ) => state.userInfo,
84 | userInfoTemp: ( state ) => state.userInfoTemp,
85 | } );
86 |
--------------------------------------------------------------------------------
/src/helpers/clipboardUtils/__mocks__/README.md:
--------------------------------------------------------------------------------
1 | # Generate mocked data for copy/paste utils test
2 |
3 | ## copyTests.json (mocked data for editor copied from inside fonio)
4 |
5 | ```
6 | {
7 | name, // name of test case
8 | editorFocus, // focused editor when copy ('main' or 'note')
9 | data: {
10 | sections, // for test purpose, only one section should inside the story, if editorFocus is note, only one note is required
11 | sectionsOrder, // for test purpose, only one section should inside the story
12 | resources,
13 | contextualizations,
14 | contextualizers,
15 | }
16 | }
17 | ```
18 |
19 | ## pasteOutsideTests.json (mocked data for paste from outside fonio)
20 |
21 | ```
22 | {
23 | name, // name of test case
24 | html, // html text copied from outside
25 | resources, // exist resources in story
26 | expectedResourcesToAdd, // number of resources retrieved from html
27 | expectedContextualizationsToAdd, // number of contextualizations should be created from html
28 | expectedContextualizersToAdd, // number of contextualizers created from html
29 | expectedImagesToAdd, // number of images retrieved from html
30 | }
31 | ```
32 | ## pasteInsideTests.json (mocked data for target editor pasted to inside fonio)
33 |
34 | ```
35 | {
36 | name, // name of test case
37 | editorFocus, // focused editor when paste ('main' or 'note')
38 | data: {
39 | sections, // for test purpose, only one section should inside the story, if editorFocus is note, only one note is required
40 | sectionsOrder, // for test purpose, only one section should inside the story
41 | }
42 | }
43 | ```
--------------------------------------------------------------------------------
/src/helpers/clipboardUtils/__mocks__/pasteOutsideTests.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "single-hyperlink",
4 | "html": "Bold text , Italic text Example link ",
5 | "resources": {},
6 | "expectedResourcesToAdd": 1,
7 | "expectedContextualizationsToAdd": 1,
8 | "expectedContextualizersToAdd": 1,
9 | "expectedImagesToAdd": 0
10 | }
11 | ]
--------------------------------------------------------------------------------
/src/helpers/clipboardUtils/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides an entrypoint for methods handling the complex logic
3 | * required to handle copying and pasting content in/from the editor
4 | * @module fonio/components/SectionEditor
5 | */
6 | import copyManager from './handleCopy';
7 | import pasteManager from './handlePaste';
8 |
9 | /**
10 | * Prepares data within component's state for later pasting
11 | * @param {event} e - the copy event
12 | */
13 | export const handleCopy = copyManager;
14 |
15 | /**
16 | * Handles pasting command in the editor
17 | * @param {event} e - the copy event
18 | */
19 | export const handlePaste = pasteManager;
20 |
--------------------------------------------------------------------------------
/src/helpers/clipboardUtils/makeReactCitations.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides a helper for preparing csl-json citations for their display in editor
3 | * @module fonio/components/SectionEditor
4 | */
5 | import { Parser } from 'html-to-react';
6 |
7 | const htmlToReactParser = new Parser();
8 |
9 | const makeReactCitations = ( processor, cits ) => {
10 | return cits.reduce( ( inputCitations, citationData ) => {
11 | const citations = { ...inputCitations };
12 | const citation = citationData[0];
13 | const citationsPre = citationData[1];
14 | const citationsPost = citationData[2];
15 | let citationObjects = processor.processCitationCluster( citation, citationsPre, citationsPost );
16 | citationObjects = citationObjects[1];
17 | citationObjects.forEach( ( cit ) => {
18 | const order = cit[0];
19 | const html = cit[1];
20 | const ThatComponent = htmlToReactParser.parse( cit[1] );
21 | const citationId = cit[2];
22 | citations[citationId] = {
23 | order,
24 | html,
25 | Component: ThatComponent
26 | };
27 | } );
28 | return citations;
29 | }, {} );
30 | };
31 |
32 | export default makeReactCitations;
33 |
--------------------------------------------------------------------------------
/src/helpers/clipboardUtils/parsePastedImage.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides the logic for handling an image pasting
3 | * @module fonio/components/SectionEditor
4 | */
5 | import { v4 as generateId } from 'uuid';
6 |
7 | import { createDefaultResource } from '../schemaUtils';
8 |
9 | import {
10 | constants,
11 | } from 'scholar-draft';
12 |
13 | const {
14 | BLOCK_ASSET,
15 | } = constants;
16 |
17 | export default (
18 | node,
19 | resources = [],
20 | activeSectionId,
21 | ) => {
22 |
23 | let resource;
24 |
25 | const url = node.getAttribute( 'src' );
26 | let title = node.getAttribute( 'title' );
27 | const alt = node.getAttribute( 'alt' );
28 | if ( !title || !alt || alt === 'href' ) {
29 | title = url;
30 | }
31 | if ( !url || url.indexOf( 'http' ) !== 0 ) {
32 | return {};
33 | }
34 |
35 | const existingResource = [ ...resources ]
36 | .find( ( res ) =>
37 | res.metadata.type === 'image'
38 | && res.data.url === url
39 | );
40 | let resourceId;
41 | if ( existingResource ) {
42 | resourceId = existingResource.id;
43 | }
44 | else {
45 | resourceId = generateId();
46 | const ext = url.split( '.' ).pop().split( '?' )[0];
47 | resource = {
48 | ...createDefaultResource(),
49 | id: resourceId,
50 | metadata: {
51 | type: 'image',
52 | createdAt: new Date().getTime(),
53 | lastModifiedAt: new Date().getTime(),
54 | ext,
55 | mimetype: `image/${ext}`,
56 | title,
57 | },
58 | data: {
59 | url,
60 | }
61 | };
62 | }
63 | const contextualizerId = generateId();
64 | const contextualizationId = generateId();
65 | const contextualizer = {
66 | id: contextualizerId,
67 | type: 'image',
68 | insertionType: 'block'
69 | };
70 | const contextualization = {
71 | id: contextualizationId,
72 | resourceId,
73 | contextualizerId,
74 | sectionId: activeSectionId,
75 | type: 'image',
76 | };
77 |
78 | const entity = {
79 | type: BLOCK_ASSET,
80 | mutability: 'IMMUTABLE',
81 | data: {
82 | asset: {
83 | id: contextualizationId
84 | }
85 | }
86 | };
87 |
88 | return {
89 | resource,
90 | contextualizer,
91 | contextualization,
92 | entity,
93 | };
94 | };
95 |
--------------------------------------------------------------------------------
/src/helpers/clipboardUtils/parsePastedLink.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides the logic for handling a link pasting
3 | * @module fonio/components/SectionEditor
4 | */
5 | import { v4 as generateId } from 'uuid';
6 |
7 | import { createDefaultResource } from '../schemaUtils';
8 |
9 | import {
10 | constants,
11 | } from 'scholar-draft';
12 |
13 | const {
14 | INLINE_ASSET,
15 | } = constants;
16 |
17 | export default (
18 | node,
19 | resources = [],
20 | activeSectionId,
21 | ) => {
22 |
23 | let resource;
24 |
25 | const url = node.getAttribute( 'href' );
26 | const alt = node.getAttribute( 'alt' );
27 | let title = node.getAttribute( 'title' );
28 | if ( !title || !alt || alt === 'href' ) {
29 | title = url;
30 | }
31 | if ( !url || url.indexOf( '#' ) === 0 ) {
32 | return {};
33 | }
34 |
35 | const existingResource = [ ...resources ]
36 | .find( ( res ) =>
37 | res.metadata.type === 'webpage'
38 | && res.data.url === url
39 | );
40 | let resourceId;
41 | if ( existingResource ) {
42 | resourceId = existingResource.id;
43 | }
44 | else {
45 | resourceId = generateId();
46 | resource = {
47 | ...createDefaultResource(),
48 | id: resourceId,
49 | metadata: {
50 | type: 'webpage',
51 | createdAt: new Date().getTime(),
52 | lastModifiedAt: new Date().getTime(),
53 | title,
54 | },
55 | data: {
56 | url,
57 | }
58 | };
59 | }
60 | const contextualizerId = generateId();
61 | const contextualizationId = generateId();
62 | const contextualizer = {
63 | id: contextualizerId,
64 | type: 'webpage',
65 | insertionType: 'inline'
66 | };
67 | const contextualization = {
68 | id: contextualizationId,
69 | resourceId,
70 | contextualizerId,
71 | sectionId: activeSectionId,
72 | type: 'webpage',
73 | };
74 |
75 | const entity = {
76 | type: INLINE_ASSET,
77 | mutability: 'MUTABLE',
78 | data: {
79 | asset: {
80 | id: contextualizationId
81 | }
82 | }
83 | };
84 |
85 | return {
86 | resource,
87 | contextualizer,
88 | contextualization,
89 | entity,
90 | };
91 | };
92 |
--------------------------------------------------------------------------------
/src/helpers/clipboardUtils/pasteFromOutside.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides unit tests for the assetsUtils module
3 | * @module fonio/helpers/clipboardUtils/handleCopy
4 | * @funciton computePastedData()
5 | * @param {string} html - mock from handleCopy
6 | * @param {object} activeSection - mock from state
7 | * @param {object} resources - mock from story json
8 | */
9 |
10 | import expect from 'expect';
11 | import {
12 | } from './__mocks__/services.js';
13 | import tests from './__mocks__/pasteOutsideTests';
14 | import {
15 | computePastedData,
16 | } from './pasteFromOutside';
17 |
18 | const testCases = tests.map( ( item ) => {
19 | return [ item.name ];
20 | } );
21 | describe( 'test computePastedData()', () => {
22 | test.each( testCases )( 'test', ( testName ) => {
23 | const {
24 | html,
25 | resources,
26 | expectedResourcesToAdd,
27 | expectedContextualizationsToAdd,
28 | expectedContextualizersToAdd,
29 | expectedImagesToAdd
30 | } = tests.find( ( item ) => item.name === testName );
31 | const computedData = computePastedData( {
32 | html,
33 | resources,
34 | activeSection: {
35 | id: 'testSectionId'
36 | }
37 | } );
38 | const {
39 | copiedContentState,
40 | resourcesToAdd,
41 | contextualizationsToAdd,
42 | contextualizersToAdd,
43 | imagesToAdd
44 | } = computedData;
45 | expect( copiedContentState ).toBeDefined();
46 | expect( resourcesToAdd.length ).toEqual( expectedResourcesToAdd );
47 | expect( contextualizationsToAdd.length ).toEqual( expectedContextualizationsToAdd );
48 | expect( contextualizersToAdd.length ).toEqual( expectedContextualizersToAdd );
49 | expect( imagesToAdd.length ).toEqual( expectedImagesToAdd );
50 | } );
51 | } );
52 |
53 |
--------------------------------------------------------------------------------
/src/helpers/draftUtils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module handles scholar-draft related operations on draft-js states
3 | * @module fonio/utils/draftUtils
4 | */
5 |
6 | import {
7 | utils
8 | } from 'scholar-draft';
9 |
10 | const {
11 | insertInlineAssetInEditor,
12 | insertBlockAssetInEditor,
13 | } = utils;
14 |
15 | /**
16 | * Inserts an inline contextualization entity into the given draft editor state
17 | * @param {EditorState} editorState - the editor state before insertion
18 | * @param {object} contextualization - the contextualization to link the entity to
19 | * @return {EditorState} newEditorState - a new editor state
20 | */
21 | export const insertInlineContextualization = ( editorState, contextualization, mutable = false ) => {
22 | const newEditorState = insertInlineAssetInEditor( editorState, { id: contextualization.id }, editorState.getSelection(), mutable );
23 | return newEditorState ? newEditorState : editorState;
24 | };
25 |
26 | /**
27 | * Inserts a block contextualization entity into the given draft editor state
28 | * @param {EditorState} editorState - the editor state before insertion
29 | * @param {object} contextualization - the contextualization to link the entity to
30 | * @return {EditorState} newEditorState - a new editor state
31 | */
32 | export const insertBlockContextualization = ( editorState, contextualization ) => {
33 | const newEditorState = insertBlockAssetInEditor( editorState, { id: contextualization.id }, editorState.getSelection() );
34 | return newEditorState ? newEditorState : editorState;
35 | };
36 |
37 | /**
38 | * Get current selected text
39 | * @param {Draft.ContentState}
40 | * @param {Draft.SelectionState}
41 | * @param {String}
42 | * @return {String}
43 | */
44 | export const getTextSelection = ( contentState, selection, blockDelimiter ) => {
45 | blockDelimiter = blockDelimiter || '\n';
46 | const startKey = selection.getStartKey();
47 | const endKey = selection.getEndKey();
48 | const blocks = contentState.getBlockMap();
49 |
50 | let lastWasEnd = false;
51 | const selectedBlock = blocks
52 | .skipUntil( function( block ) {
53 | return block.getKey() === startKey;
54 | } )
55 | .takeUntil( function( block ) {
56 | const result = lastWasEnd;
57 |
58 | if ( block.getKey() === endKey ) {
59 | lastWasEnd = true;
60 | }
61 |
62 | return result;
63 | } );
64 |
65 | return selectedBlock
66 | .map( function( block ) {
67 | const key = block.getKey();
68 | let text = block.getText();
69 |
70 | let start = 0;
71 | let end = text.length;
72 |
73 | if ( key === startKey ) {
74 | start = selection.getStartOffset();
75 | }
76 | if ( key === endKey ) {
77 | end = selection.getEndOffset();
78 | }
79 |
80 | text = text.slice( start, end );
81 | return text;
82 | } )
83 | .join( blockDelimiter );
84 | };
85 |
--------------------------------------------------------------------------------
/src/helpers/editorToStoryUtils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides utils for infering story modifications from the editor state
3 | * @module fonio/components/SectionEditor
4 | */
5 | /**
6 | * Imports Libraries
7 | */
8 | import {
9 | utils,
10 | } from 'scholar-draft';
11 |
12 | /**
13 | * Shared variables
14 | */
15 | const {
16 | getUsedAssets,
17 | updateNotesFromEditor,
18 | } = utils;
19 |
20 | /**
21 | * Deletes notes that are not any more linked
22 | * to an entity in the editor
23 | * and update notes numbers if their order has changed.
24 | * @param {object} props - properties to use
25 | */
26 | export const updateNotesFromSectionEditor = ( props ) => {
27 | const {
28 | editorStates,
29 | sectionId,
30 | activeStoryId,
31 | activeSection,
32 | updateSection,
33 | } = props;
34 | const {
35 | // newNotes,
36 | notesOrder
37 | } = updateNotesFromEditor( editorStates[sectionId], { ...activeSection.notes } );
38 | const newSection = activeSection;
39 | // newSection.notes = newNotes;
40 | newSection.notesOrder = notesOrder;
41 | // if (newNotes !== activeSection.notes) {
42 | updateSection( activeStoryId, sectionId, newSection );
43 | // }
44 | };
45 |
46 | /**
47 | * Deletes contextualizations that are not any more linked
48 | * to an entity in the editor.
49 | * @param {object} props - properties to use
50 | */
51 | export const updateContextualizationsFromEditor = ( props ) => {
52 | const {
53 | activeSection,
54 | editorStates,
55 | deleteContextualization,
56 | deleteContextualizer,
57 | // sectionId,
58 | story,
59 | userId
60 | } = props;
61 | const activeStoryId = story.id;
62 | const activeSectionId = activeSection.id;
63 | // regroup all eligible editorStates
64 | const notesEditorStates = activeSection.notesOrder.reduce( ( result, noteId ) => {
65 | return {
66 | ...result,
67 | [noteId]: editorStates[noteId]
68 | };
69 | }, {} );
70 | // regroup all eligible contextualizations
71 | const sectionContextualizations = Object.keys( story.contextualizations )
72 | .filter( ( id ) => {
73 | return story.contextualizations[id].sectionId === activeSectionId;
74 | } )
75 | .reduce( ( final, id ) => ( {
76 | ...final,
77 | [id]: story.contextualizations[id],
78 | } ), {} );
79 |
80 | // look for used contextualizations in main
81 | let used = getUsedAssets( editorStates[activeSectionId], sectionContextualizations );
82 | // look for used contextualizations in notes
83 | Object.keys( notesEditorStates )
84 | .forEach( ( noteId ) => {
85 | const noteEditor = notesEditorStates[noteId];
86 | used = used.concat( getUsedAssets( noteEditor, sectionContextualizations ) );
87 | } );
88 |
89 | /*
90 | * compare list of contextualizations with list of used contextualizations
91 | * to track all unused contextualizations
92 | */
93 | const unusedAssets = Object.keys( sectionContextualizations ).filter( ( id ) => !used.includes( id ) );
94 | // delete contextualizations
95 | unusedAssets.forEach( ( id ) => {
96 | const { contextualizerId } = sectionContextualizations[id];
97 | deleteContextualization( { storyId: activeStoryId, contextualizationId: id, userId } );
98 | deleteContextualizer( { storyId: activeStoryId, contextualizerId, userId } );
99 | } );
100 | };
101 |
--------------------------------------------------------------------------------
/src/helpers/fileDownloader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module handles downloading a file from string content
3 | * @module fonio/utils/fileDownloader
4 | */
5 | import FileSaver from 'file-saver';
6 |
7 | /**
8 | * @param {string} text - the text to put in the file
9 | * @param {string} extension - the extension to append to file name
10 | * @param {string} fileName - the name to attribute to the downloaded file
11 | */
12 | export default function downloadFile( text, extension = 'txt', fileName = 'fonio' ) {
13 | let type;
14 | switch ( extension ) {
15 | case 'zip':
16 | type = 'octet/stream';
17 | break;
18 | case 'html':
19 | type = 'text/html;charset=utf-8';
20 | break;
21 | default:
22 | type = 'text/plain;charset=utf-8';
23 | break;
24 | }
25 | const blob = new Blob( [ text ], { type } );
26 | FileSaver.saveAs( blob, `${fileName }.${ extension}` );
27 | }
28 |
--------------------------------------------------------------------------------
/src/helpers/fileLoader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module helps to load files from app server or user own file system
3 | * @module fonio/utils/fileLoader
4 | */
5 |
6 | /**
7 | * Validates whether the extension of a file is valid against its visualization model
8 | * @param {string} fileName - the name of the file to validate
9 | * @param {object} visualizationModel - the model of the visualization to validate the filename against
10 | * @return {boolean} isValid - whether the filename is valid
11 | */
12 | export function validateFileExtensionForVisType ( fileName = '', visualizationModel ) {
13 | const fileExtension = fileName.split( '.' ).pop();
14 | return visualizationModel.acceptedFileExtensions.find( ( ext ) => ext === fileExtension ) !== undefined;
15 | }
16 |
17 | /**
18 | * Reads the raw string content of a file from user file system
19 | * @param {File} fileToRead - the file to read
20 | * @param {function} callback
21 | */
22 | export function getFileAsText( file ) {
23 | return new Promise( ( resolve, reject ) => {
24 | let reader = new FileReader();
25 | reader.onload = ( event ) => {
26 | resolve( event.target.result );
27 | reader = undefined;
28 | };
29 | reader.onerror = ( event ) => {
30 | reject( event.target.error );
31 | reader = undefined;
32 | };
33 | return reader.readAsText( file );
34 | } );
35 | }
36 |
--------------------------------------------------------------------------------
/src/helpers/fileLoader.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides unit tests for the fileLoader module
3 | * @module fonio/utils/fileLoader
4 | */
5 | import { expect } from 'chai';
6 |
7 | import {
8 | validateFileExtensionForVisType
9 | } from './fileLoader';
10 |
11 | describe( 'fileLoader helpers', () => {
12 | describe( 'validateFileExtensionForVisType', () => {
13 | const visualizationModel = {
14 | acceptedFileExtensions: [ 'csv', 'tsv', 'dsv' ]
15 | };
16 | const validFileNames = [ 'myfile.csv', 'myfile.tsv', 'myfile.dsv', 'myfile.doc.csv' ];
17 | const invalidFileNames = [ '', 'myfile', 'myfile_csv', 'myfile.csv.psd' ];
18 |
19 | it( 'should accept valid extensions', () => {
20 | validFileNames.forEach( ( fileName ) => {
21 | const valid = validateFileExtensionForVisType( fileName, visualizationModel );
22 | return expect( valid ).to.be.true;
23 | } );
24 | } );
25 |
26 | it( 'should not accept invalid extensions', () => {
27 | invalidFileNames.forEach( ( fileName ) => {
28 | const valid = validateFileExtensionForVisType( fileName, visualizationModel );
29 | return expect( valid ).to.be.false;
30 | } );
31 | } );
32 | } );
33 | } );
34 |
--------------------------------------------------------------------------------
/src/helpers/localStorageUtils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module helps to manage local storage data
3 | * @module fonio/utils/localStorageUtils
4 | */
5 | export const loadStoryToken = ( storyId ) => {
6 | return localStorage.getItem( `fonio/storyToken/${storyId}` );
7 | };
8 | export const saveStoryToken = ( storyId, token ) => {
9 | localStorage.setItem( `fonio/storyToken/${storyId}`, token );
10 | };
11 | export const deleteStoryToken = ( storyId ) => {
12 | localStorage.removeItem( `fonio/storyToken/${storyId}` );
13 | };
14 |
15 | export const updateEditionHistoryMap = ( storyId ) => {
16 | const existing = localStorage.getItem( 'fonio/editionStoryMap' );
17 | let previousMap;
18 | try {
19 | if ( existing ) {
20 | previousMap = JSON.parse( existing );
21 | }
22 | else previousMap = {};
23 | }
24 | catch ( e ) {
25 | previousMap = {};
26 | }
27 | const newMap = {
28 | ...previousMap,
29 | [storyId]: new Date().getTime()
30 | };
31 | localStorage.setItem( 'fonio/editionStoryMap', JSON.stringify( newMap ) );
32 | };
33 |
34 | const getJSONFromStorage = ( key ) => {
35 | const existing = localStorage.getItem( key );
36 | let result;
37 | try {
38 | if ( existing ) {
39 | result = JSON.parse( existing );
40 | }
41 | }
42 | catch ( e ) {
43 | result = undefined;
44 | }
45 | return result;
46 | };
47 |
48 | export const getEditionHistoryMap = () => {
49 | return getJSONFromStorage( 'fonio/editionStoryMap' ) || {};
50 | };
51 |
52 | export const saveUserInfo = ( userInfo ) => {
53 | localStorage.setItem( 'fonio/user_info', JSON.stringify( userInfo ) );
54 | };
55 |
56 | export const loadUserInfo = () => {
57 | return getJSONFromStorage( 'fonio/user_info' );
58 | };
59 |
--------------------------------------------------------------------------------
/src/helpers/misc.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provides miscellaneous utils
3 | * @module fonio/utils/misc
4 | */
5 | import trunc from 'unicode-byte-truncate';
6 |
7 | export const abbrevString = ( str = '', maxLength = 10 ) => {
8 | if ( str.length > maxLength ) {
9 | return `${trunc( str, maxLength ) }...`;
10 | }
11 | return str;
12 | };
13 |
14 | export const splitPathnameForSockets = ( url ) => {
15 | const h = url.split( '//' );
16 | const p = h.slice( -1 )[0].split( '/' );
17 |
18 | return [
19 | ( h.length > 1 ? ( `${h[0] }//` ) : '' ) + p[0],
20 | p.slice( 1 ).filter( ( i ) => i )
21 | ];
22 | };
23 |
24 | export const bytesToBase64Length = ( bytes ) => bytes * ( 4 / 3 );
25 | export const base64ToBytesLength = ( bytes ) => bytes / ( 4 / 3 );
26 |
27 | export const getBrowserInfo = () => {
28 | const ua = navigator.userAgent;
29 | let tem, M = ua.match( /(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i ) || [];
30 | if ( /trident/i.test( M[1] ) ) {
31 | tem = /\brv[ :]+(\d+)/g.exec( ua ) || [];
32 | return { name: 'IE ', version: ( tem[1] || '' ) };
33 | }
34 | if ( /edge/gi.test( ua ) ) {
35 | return {
36 | name: 'Edge',
37 | version: 'unknown',
38 | };
39 | }
40 | if ( M[1] === 'Chrome' ) {
41 | tem = ua.match( /\bOPR\/(\d+)/ );
42 | if ( tem != null ) { /* eslint eqeqeq : 0 */
43 | return { name: 'Opera', version: tem[1] };
44 | }
45 | }
46 | M = M[2] ? [ M[1], M[2] ] : [ navigator.appName, navigator.appVersion, '-?' ];
47 | if ( ( tem = ua.match( /version\/(\d+)/i ) ) != null ) { /* eslint eqeqeq : 0 */
48 | M.splice( 1, 1, tem[1] );
49 | }
50 | return {
51 | name: M[0],
52 | version: M[1]
53 | };
54 | };
55 |
56 | export const computeSectionFirstWords = ( section, maxLength = 100 ) => {
57 | if ( section.contents
58 | && section.contents.blocks
59 | && section.contents.blocks[0]
60 | && section.contents.blocks[0].text
61 | ) {
62 | return section.contents.blocks[0].text.length > maxLength ?
63 | `${section.contents.blocks[0].text.substr( 0, maxLength )}...`
64 | :
65 | section.contents.blocks[0].text;
66 | }
67 | return '';
68 | };
69 |
70 | export const silentEvent = ( event ) => {
71 | if ( event ) {
72 | event.stopPropagation();
73 | event.preventDefault();
74 | }
75 | };
76 |
77 | const getContentsRawText = ( contentObj ) => {
78 | return contentObj.blocks ? contentObj.blocks.map( ( b ) => b.text ).join( '\n' ) : '';
79 | };
80 |
81 | const getSectionRawText = ( section ) => {
82 | return section.notesOrder.reduce( ( sum, noteId ) => {
83 | return `${sum} ${getContentsRawText( section.notes[noteId].contents )} `;
84 | }, getContentsRawText( section.contents ) );
85 | };
86 |
87 | export const getStoryStats = ( story ) => {
88 | const rawText = story.sectionsOrder.reduce( ( sum, sectionId ) => {
89 | return `${sum} ${story.sections[sectionId].metadata.title} ${getSectionRawText( story.sections[sectionId] )}`;
90 | }, '' );
91 | const numberOfWords = rawText.match( /\S+/g ) ? rawText.match( /\S+/g ).length : 0;
92 | return {
93 | numberOfCharacters: rawText.length,
94 | numberOfWords,
95 | numberOfPages: Math.ceil( numberOfWords / 300 )
96 | };
97 | };
98 |
--------------------------------------------------------------------------------
/src/helpers/postcss.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module helps to prefix css style
3 | * @module fonio/utils/postcss
4 | */
5 | import postcss from 'postcss';
6 | import prefixer from 'postcss-prefix-selector';
7 |
8 | export const processCustomCss = ( css = '' ) => {
9 | try {
10 | return postcss().use( prefixer( {
11 | prefix: '.quinoa-story-player',
12 | // exclude: ['.c'],
13 |
14 | // Optional transform callback for case-by-case overrides
15 | transform ( prefix, selector, prefixedSelector ) {
16 | if ( selector === 'body' ) {
17 | return `body ${ prefix}`;
18 | }
19 | else {
20 | return prefixedSelector;
21 | }
22 | }
23 | } ) )
24 | .process( css ).css;
25 | }
26 | catch ( e ) {
27 | return undefined;
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/src/helpers/projectBundler.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module helps for processes related to the exports of a story
3 | * @module fonio/utils/projectBundler
4 | */
5 | import {
6 | convertFromRaw
7 | } from 'draft-js';
8 |
9 | import { stateToMarkdown } from 'draft-js-export-markdown';
10 |
11 | /**
12 | * Prepares a story data for a clean version to export
13 | * @param {object} story - the input data to clean
14 | * @return {object} newStory - the cleaned story
15 | */
16 | export const cleanStoryForExport = ( story ) => {
17 | return story;
18 | };
19 |
20 | /**
21 | * Consumes story data to produce a representation in markdown syntax
22 | * @todo: for now this does not handle assets, it should be broadly improved to do that
23 | * @param {object} story - the story to consume
24 | * @return {string} markdown - the markdown representation of the story
25 | */
26 | export const convertStoryToMarkdown = ( story ) => {
27 | const header = `${story.metadata.title}
28 | ====
29 | ${story.metadata.authors.join( ', ' )}
30 | ---
31 | `;
32 | return header + story.sectionsOrder.map( ( id ) => {
33 | const content = convertFromRaw( story.sections[id].contents );
34 | return stateToMarkdown( content );
35 | } ).join( '\n \n' );
36 | };
37 |
38 | /**
39 | * Cleans and serializes a story representation
40 | * @param {object} story - the story to bundle
41 | * @return {string} result - the resulting serialized story
42 | */
43 | export const bundleProjectAsJSON = ( story ) => {
44 | return JSON.stringify( cleanStoryForExport( story ) );
45 | };
46 |
--------------------------------------------------------------------------------
/src/helpers/reduxUtils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module helps to optimize redux-related code
3 | * @module fonio/utils/reduxUtils
4 | */
5 |
6 | export const getStatePropFromActionSet = ( actionName ) => {
7 | return actionName.replace( 'SET_', '' ).toLowerCase().replace( /(_[a-z])/gi, ( a, b ) => b.substr( 1 ).toUpperCase() );
8 | };
9 |
--------------------------------------------------------------------------------
/src/helpers/translateUtils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module provide helpers to handle translations in the app
3 | * @module bulgur/utils/translateUtils
4 | */
5 |
6 | /**
7 | * Automatically functions a translate function
8 | * @param {function} translateFn - the translate function to namespace
9 | * @param {string} nameSpace - the namespace to use
10 | * @return {function} translateFnBis - a new function using the namespace
11 | */
12 | export const translateNameSpacer = ( translateFn, nameSpace ) => {
13 | return function( key, props ) {
14 | if ( Array.isArray( key ) ) {
15 | return translateFn( key.map( ( k ) => {
16 | if ( k.length > 1 ) {
17 | return `${nameSpace }.${ k}`;
18 | }
19 | return k;
20 | } ), props );
21 | }
22 | return translateFn( `${nameSpace }.${ key}`, props );
23 | };
24 | };
25 |
--------------------------------------------------------------------------------
/src/helpers/userInfo.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This module helps to manage users identification data
3 | * @module fonio/utils/userInfo
4 | */
5 | import userNames from '../sharedAssets/userNames';
6 | import avatars from '../sharedAssets/avatars';
7 |
8 | /**
9 | * Generates random values for user name
10 | * @param {string} lang - the lang to use to generate info
11 | */
12 | export default function generateRandomUserInfo ( lang ) {
13 | const { adjectives, names, pattern } = userNames[lang];
14 | const adjective = adjectives[parseInt( Math.random() * adjectives.length, 10 )];
15 | const name = names[parseInt( Math.random() * names.length, 10 )].toLowerCase();
16 | const avatar = avatars[parseInt( Math.random() * avatars.length, 10 )];
17 | return {
18 | name: pattern.replace( 'adjective', adjective ).replace( 'name', name ),
19 | avatar,
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Fonio Application Endpoint
3 | * ======================================
4 | *
5 | * Rendering the application.
6 | * @module fonio
7 | */
8 | import React from 'react';
9 | import { render } from 'react-dom';
10 | import { Provider } from 'react-redux';
11 | import I18n from 'redux-i18n';
12 |
13 | import configureStore from './redux/configureStore';
14 | import Application from './Application';
15 |
16 | import translations from './translations/index.js';
17 |
18 | let CurrentApplication = Application;
19 |
20 | const initialState = {};
21 |
22 | const store = configureStore( initialState );
23 | window.store = store;
24 |
25 | const mountNode = document.getElementById( 'mount' );
26 |
27 | let browserLang = ( navigator.language || navigator.userLanguage ).split( '-' )[0];
28 | if ( browserLang !== 'en' || browserLang !== 'fr' ) {
29 | browserLang = 'en';
30 | }
31 | const initialLang = localStorage.getItem( 'fonio-lang' ) || browserLang;
32 |
33 | /**
34 | * Mounts the application to the given mount node
35 | */
36 | export function renderApplication() {
37 | const group = (
38 |
39 |
43 |
44 |
45 |
46 | );
47 | render( group, mountNode );
48 | }
49 |
50 | renderApplication();
51 |
52 | /**
53 | * Hot-reloading.
54 | */
55 | if ( module.hot ) {
56 | module.hot.accept( './Application', function() {
57 | CurrentApplication = require( './Application' ).default;
58 | renderApplication();
59 | } );
60 | }
61 |
--------------------------------------------------------------------------------
/src/parameters.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * global style parameters for the whole app
3 | *
4 | * @module fonio
5 | */
6 |
7 |
8 | $gutter-small : 1rem;
9 |
--------------------------------------------------------------------------------
/src/redux/configureStore.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Fonio store configuration
3 | * ===================================
4 | * Configuring store with appropriate middlewares
5 | */
6 | import {
7 | applyMiddleware,
8 | createStore,
9 | compose
10 | } from 'redux';
11 | import rootReducer from './rootReducer';
12 | import promiseMiddleware from './promiseMiddleware';
13 | import Validator from './payloadValidatorMiddleware';
14 | import { loadingBarMiddleware } from 'react-redux-loading-bar';
15 |
16 | import { CONNECT_ERROR, RECONNECT } from '../features/ErrorMessageManager/duck';
17 |
18 | import config from '../config';
19 |
20 | import io from 'socket.io-client';
21 | import createSocketIoMiddleware from './socketIoMiddleware';
22 |
23 | import { splitPathnameForSockets } from '../helpers/misc';
24 |
25 | /**
26 | * @todo: fetch that from config
27 | */
28 | const [ apiOrigin, apiPathname ] = splitPathnameForSockets( config.apiUrl );
29 | const path = `/${ apiPathname.concat( 'sockets' ).join( '/' )}`;
30 |
31 | const socket = io( apiOrigin, { path } );
32 |
33 | const socketIoMiddleware = createSocketIoMiddleware( socket );
34 |
35 | /**
36 | * redux action validator middleware
37 | */
38 | const validatorMiddleware = Validator();
39 |
40 | /**
41 | * Configures store with a possible inherited state and appropriate reducers
42 | * @param initialState - the state to use to bootstrap the reducer
43 | * @return {object} store - the configured store
44 | */
45 | export default function configureStore ( initialState = {} ) {
46 | // Compose final middleware with thunk and promises handling
47 | const middleware = applyMiddleware(
48 | validatorMiddleware,
49 | socketIoMiddleware,
50 | promiseMiddleware(),
51 | loadingBarMiddleware( {
52 | promiseTypeSuffixes: [ 'PENDING', 'SUCCESS', 'FAIL' ],
53 | } )
54 | );
55 |
56 | // Create final store and subscribe router in debug env ie. for devtools
57 | const createStoreWithMiddleware = window.__REDUX_DEVTOOLS_EXTENSION__ ? compose(
58 | // related middlewares
59 | middleware,
60 | // connection to redux dev tools
61 | window.__REDUX_DEVTOOLS_EXTENSION__() )( createStore ) : compose( middleware )( createStore );
62 |
63 | const store = createStoreWithMiddleware(
64 | rootReducer,
65 | initialState,
66 | );
67 |
68 | const connectionErrors = [ 'connect_error', 'reconnect_failed', 'reconnect_error', 'disconnect' ];
69 | connectionErrors.forEach( ( message ) => {
70 | socket.on( message, ( error ) => {
71 | store.dispatch( {
72 | type: CONNECT_ERROR,
73 | error
74 | } );
75 | } );
76 | } );
77 |
78 | socket.on( 'reconnect', ( error ) => {
79 | store.dispatch( {
80 | type: RECONNECT,
81 | error
82 | } );
83 | } );
84 |
85 | // live-reloading handling
86 | if ( module.hot ) {
87 | module.hot.accept( './rootReducer', () => {
88 | const nextRootReducer = require( './rootReducer' ).default;
89 | store.replaceReducer( nextRootReducer );
90 | } );
91 | }
92 | return store;
93 | }
94 |
--------------------------------------------------------------------------------
/src/redux/payloadValidatorMiddleware.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Payload validator middleware
3 | * ===================================
4 | * Modified from:
5 | * https://github.com/MaxLi1994/redux-validator
6 | * Author: https://github.com/MaxLi1994
7 | */
8 | const options = {
9 | validatorKey: 'meta',
10 | paramKey: 'payload'
11 | };
12 |
13 | export default () => ( store ) => ( next ) => ( action ) => {
14 | if ( !action[options.validatorKey] || !action[options.validatorKey].validator || action[options.validatorKey].disableValidate ) {
15 | // thunk compatible
16 | if ( action[options.paramKey] && action[options.paramKey].thunk ) {
17 | return next( action[options.paramKey].thunk );
18 | }
19 | else {
20 | return next( action );
21 | }
22 | }
23 |
24 | let flag = true;
25 | let errorParam, errorId, errorMsg;
26 |
27 | const validators = action[options.validatorKey].validator || {};
28 | const runValidator = ( param, func, msg, id, key ) => {
29 | let valid;
30 | if ( func ) {
31 | valid = func( param, store.getState(), action.payload );
32 | }
33 | else {
34 | throw new Error( 'validator func is needed' );
35 | }
36 | if
37 | ( typeof valid !== 'boolean' ) {
38 | throw new Error( 'validator func must return boolean type' );
39 | }
40 | if ( !valid ) {
41 | errorParam = key;
42 | errorId = id;
43 | errorMsg = ( typeof msg === 'function'
44 | ? msg( param, store.getState(), action.payload )
45 | : msg )
46 | || '';
47 | }
48 |
49 | return valid;
50 | };
51 |
52 | const runValidatorContainer = ( validator, param, key ) => {
53 | let valid;
54 | if ( Array.prototype.isPrototypeOf( validator ) ) {
55 | for ( const j in validator ) {
56 | if ( validator.hasOwnProperty( j ) ) {
57 | const item = validator[j];
58 | valid = runValidator( param, item.func, item.msg, j, key );
59 | if ( !valid ) break;
60 | }
61 | }
62 | }
63 | else {
64 | valid = runValidator( param, validator.func, validator.msg, 0, key );
65 | }
66 | return valid;
67 | };
68 |
69 | const params = action[options.paramKey] || {};
70 | for ( const i in validators ) {
71 | if ( validators.hasOwnProperty( i ) ) {
72 | if ( i === options.paramKey || i === 'thunk' ) continue;
73 | const validator = validators[i];
74 |
75 | flag = runValidatorContainer( validator, params[i], i );
76 | if ( !flag ) break;
77 | }
78 | }
79 |
80 | // param object itself
81 | const paramObjValidator = validators[options.paramKey];
82 | if ( paramObjValidator && flag ) {
83 | flag = runValidatorContainer( paramObjValidator, action[options.paramKey], options.paramKey );
84 | }
85 | // -------
86 |
87 | if ( flag ) {
88 | // thunk compatible
89 | if ( action[options.paramKey] && action[options.paramKey].thunk ) {
90 | return next( action[options.paramKey].thunk );
91 | }
92 | else {
93 | return next( action );
94 | }
95 | }
96 | else {
97 | return next( { errors: errorMsg, type: `${action.type}_FAIL`, param: errorParam, id: errorId } );
98 |
99 | /*
100 | * return {
101 | * err: 'validator',
102 | * msg: errorMsg,
103 | * param: errorParam,
104 | * id: errorId
105 | * };
106 | */
107 | }
108 | };
109 |
--------------------------------------------------------------------------------
/src/redux/promiseMiddleware.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Promise middleware
3 | * ===================================
4 | * If a promise is passed in an action,
5 | * this middleware will resolve it and dispatch related actions names
6 | * (ACTION_NAME when started, then ACTION_NAME_SUCCESS or ACTION_NAME_FAIL depending on promise outcome)
7 | */
8 |
9 | import config from '../../config';
10 | const { timers } = config;
11 |
12 | export default () => ( { dispatch, getState } ) => ( next ) => ( action ) => {
13 | // If the action is a function, execute it
14 | if ( typeof action === 'function' ) {
15 | return action( dispatch, getState );
16 | }
17 |
18 | const { promise, type, ...rest } = action;
19 |
20 | // If there is no promise in the action, ignore it
21 | if ( !promise ) {
22 | // pass the action to the next middleware
23 | return next( action );
24 | }
25 | else if ( typeof promise !== 'function' || !Promise.resolve( promise ) ) {
26 | console.warn( 'passed an action with a "promise" prop which is not a promise function, action:', action );/* eslint no-console : 0 */
27 | return next( action );
28 | }
29 | // build constants that will be used to dispatch actions
30 | const REQUEST = `${type }_PENDING`;
31 | const SUCCESS = `${type }_SUCCESS`;
32 | const FAIL = `${type }_FAIL`;
33 | const RESET = `${type }_RESET`;
34 |
35 | /*
36 | * Trigger the action once to dispatch
37 | * the fact promise is starting resolving (for loading indication for instance)
38 | */
39 | next( { ...rest, type: REQUEST } );
40 | // resolve promise
41 | return promise( dispatch, getState ).then(
42 | ( result ) => {
43 | setTimeout( () =>
44 | next( { ...rest, type: RESET } )
45 | , timers.long );
46 | return next( { ...rest, result, type: SUCCESS } );
47 | } ).catch( ( error ) =>
48 | next( { ...rest, error, type: FAIL } )
49 | );
50 |
51 | };
52 |
--------------------------------------------------------------------------------
/src/redux/rootReducer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Fonio Reducers Endpoint
3 | * ===================================
4 | *
5 | * Combining the app's reducers.
6 | */
7 | import { combineReducers } from 'redux';
8 |
9 | import { i18nState } from 'redux-i18n';
10 |
11 | import { loadingBarReducer } from 'react-redux-loading-bar';
12 |
13 | import { reducer as toastrReducer } from 'react-redux-toastr';
14 |
15 | import home from '../features/HomeView/duck';
16 | import summary from '../features/SummaryView/duck';
17 | import section from '../features/SectionView/duck';
18 | import library from '../features/LibraryView/duck';
19 | import design from '../features/DesignView/duck';
20 |
21 | import connections from '../features/ConnectionsManager/duck';
22 | import userInfo from '../features/UserInfoManager/duck';
23 | import auth from '../features/AuthManager/duck';
24 | import editionUiWrapper from '../features/EditionUiWrapper/duck';
25 | import editedStory from '../features/StoryManager/duck';
26 | import sectionsManagement from '../features/SectionsManager/duck';
27 | import errorMessage from '../features/ErrorMessageManager/duck';
28 |
29 | const saveLang = ( state = {}, action ) => {
30 | if ( action.type === 'REDUX_I18N_SET_LANGUAGE' ) {
31 | localStorage.setItem( 'fonio-lang', action.lang );
32 | return state;
33 | }
34 | else return state;
35 | };
36 |
37 | export default combineReducers( {
38 | i18nState,
39 | saveLang,
40 |
41 | loadingBar: loadingBarReducer,
42 | toastr: toastrReducer,
43 |
44 | connections,
45 | userInfo,
46 | auth,
47 | editionUiWrapper,
48 | editedStory,
49 | sectionsManagement,
50 | errorMessage,
51 |
52 | home,
53 | summary,
54 | section,
55 | library,
56 | design,
57 | } );
58 |
--------------------------------------------------------------------------------
/src/redux/socketIoMiddleware.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Socket middleware
3 | * Catch socket-related actions (triggered if they contain a meta property)
4 | * and pass them through the socket
5 | */
6 | import { loadStoryToken } from '../helpers/localStorageUtils';
7 |
8 | export default ( socket ) => {
9 | const eventName = 'action';
10 |
11 | return ( store ) => {
12 | socket.on( eventName, store.dispatch );
13 | return ( next ) => ( action ) => {
14 | if ( action.meta && action.meta.remote ) {
15 |
16 | // passing jwt token if a story content is involved
17 | const { storyId } = action.payload;
18 | let token;
19 | if ( storyId ) {
20 | token = loadStoryToken( storyId );
21 | }
22 |
23 | if ( action.callback && typeof action.callback === 'function' ) {
24 | socket.emit( eventName, { ...action, token }, action.callback );
25 | }
26 | else {
27 | socket.emit( eventName, { ...action, token } );
28 | }
29 | }
30 | return next( action );
31 | };
32 | };
33 | };
34 |
--------------------------------------------------------------------------------
/src/sharedAssets/avatars/disappointed-boy.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
23 |
24 |
25 |
28 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/src/sharedAssets/avatars/embarrased-boy.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
10 |
13 |
16 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/sharedAssets/avatars/fat-boy-smiling.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
22 |
23 |
24 |
27 |
31 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/sharedAssets/avatars/happy-baby.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/sharedAssets/avatars/index.json:
--------------------------------------------------------------------------------
1 | [
2 | "amazed-man.svg",
3 | "happy-baby.svg",
4 | "angry-man.svg",
5 | "happy-woman.svg",
6 | "angry-woman.svg",
7 | "hipster-smiling.svg",
8 | "baby-crying.svg",
9 | "hypnotized-hipster.svg",
10 | "baby-love.svg",
11 | "inexpressive-girl.svg",
12 | "boy-broad-smile.svg",
13 | "kissing-girl.svg",
14 | "boy-happy-smile.svg",
15 | "man-with-moustache-smiling.svg",
16 | "boy-smiling.svg",
17 | "naughty-girl.svg",
18 | "boy-suffering.svg",
19 | "perplexed-man.svg",
20 | "delighted-granny.svg",
21 | "philosophizing-boy.svg",
22 | "disappointed-boy.svg",
23 | "sad-baby.svg",
24 | "embarrased-boy.svg",
25 | "sad-girl.svg",
26 | "embarrased-girl.svg",
27 | "sad-hipster.svg",
28 | "embarrased-granny.svg",
29 | "sad-woman.svg",
30 | "emotive-granny.svg",
31 | "satisfied-woman.svg",
32 | "fat-boy-angry.svg",
33 | "shocked-girl.svg",
34 | "fat-boy-shocked.svg",
35 | "sleeping-granny.svg",
36 | "fat-boy-smiling.svg",
37 | "sleepy-boy.svg",
38 | "fat-boy-sorry.svg",
39 | "smiling-baby.svg",
40 | "fat-boy.svg",
41 | "smiling-girl.svg",
42 | "frightened-hipster.svg",
43 | "surprised-girl.svg",
44 | "girl-air-kissing.svg",
45 | "suspicious-man.svg",
46 | "girl-crying.svg",
47 | "teasing-boy.svg",
48 | "girl-embarrased.svg",
49 | "upset-girl.svg",
50 | "girl-laughing-to-tears.svg",
51 | "upset-granny.svg",
52 | "girl-showing-tongue.svg",
53 | "winking-boy.svg",
54 | "girl-smiling.svg",
55 | "wondered-hipster.svg",
56 | "girl-with-poker-face.svg"
57 | ]
--------------------------------------------------------------------------------
/src/sharedAssets/avatars/sad-baby.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
21 |
24 |
26 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/sharedAssets/avatars/smiling-baby.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/sharedAssets/avatars/winking-boy.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
10 |
26 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/src/sharedAssets/cover_forccast.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/medialab/fonio/974f497357f401250d69cad69d05c501f58c5c3d/src/sharedAssets/cover_forccast.jpg
--------------------------------------------------------------------------------
/src/sharedAssets/internal-link.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
10 |
12 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/sharedAssets/logo-quinoa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/medialab/fonio/974f497357f401250d69cad69d05c501f58c5c3d/src/sharedAssets/logo-quinoa.png
--------------------------------------------------------------------------------
/src/translations/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Translation data map
3 | * Exports available language data maps
4 | */
5 |
6 | import fr from './locales/fr';
7 | import en from './locales/en';
8 |
9 | const translations = {
10 | en,
11 | fr
12 | };
13 |
14 | export default translations;
15 |
--------------------------------------------------------------------------------
/translationScripts/addTranslationLanguage.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Translations language adder
3 | */
4 | var fs = require('fs');
5 | var path = require('path');
6 | var colors = require('colors');
7 | var argv = require('optimist').argv;
8 |
9 | var addTranslationLanguage = function() {
10 | const lang = argv._[0];
11 | const poPath = argv.poPath || './translations';
12 | const translationsPath = path.resolve(__dirname, argv['translations-folder']);
13 | if (lang) {
14 | // copy translation json
15 | const modelPath = fs.readdirSync(translationsPath + '/locales')[0];
16 | const model = fs.readFileSync(translationsPath + '/locales/' + modelPath, 'utf-8');
17 | fs.writeFileSync(translationsPath + '/locales/' + lang + '.json', model);
18 | // update translations index script
19 | const indexContent = fs.readFileSync(translationsPath + '/index.js', 'utf-8');
20 | const defRegex = /translations = {/;
21 | const match = indexContent.match(defRegex);
22 | if (match) {
23 | const newIndexContent = 'import ' + lang + ' from \'./locales/' + lang + '\';\n' + indexContent.substr(0, match.index) + match[0] + '\n ' + lang + ',' + indexContent.substr(match.index + match[0].length);
24 | fs.writeFileSync(translationsPath + '/index.js', newIndexContent);
25 | fs.writeFileSync(poPath + '/' + lang + '.po', '');
26 | console.log(colors.green('done updating code for language ' + lang));
27 | }
28 | } else {
29 | console.log('you must indicate a language code, e.g. "npm run translations:addlanguage it"')
30 | }
31 | }
32 |
33 | addTranslationLanguage();
34 |
35 | module.exports = addTranslationLanguage;
--------------------------------------------------------------------------------
/translationScripts/backfillTranslations.js:
--------------------------------------------------------------------------------
1 | /**
2 | * written by Robin de Mourat
3 | * Backfills untranslated keys with default language.
4 | * Lets the user know about the process
5 | */
6 |
7 | var fs = require('fs');
8 | var path = require('path');
9 | var colors = require('colors');
10 | var argv = require('optimist').argv;
11 |
12 | var backfillTranslations = function() {
13 | console.log(colors.green('Begining to check all translations'));
14 | const localesPath = path.resolve(__dirname, argv.locales);
15 | const localesFiles = fs.readdirSync(localesPath);
16 | const locales = localesFiles.map(fileName => {
17 | return {
18 | fileName,
19 | translations: require(argv.locales + '/' + fileName)
20 | }
21 | });
22 | const msgs = [];
23 |
24 | locales.forEach(locale1 => {
25 | Object.keys(locale1.translations).map(key => {
26 | locales.forEach(locale2 => {
27 | if (locale1.fileName !== locale2.fileName) {
28 | if (locale2.translations[key] === undefined) {
29 | msgs.push({
30 | type: 'untranslated',
31 | key: key,
32 | from: locale1.fileName,
33 | to: locale2.fileName,
34 | backfill: locale1.translations[key]
35 | });
36 | locale2.translations[key] = locale1.translations[key];
37 | }
38 | }
39 | });
40 | });
41 | });
42 |
43 | locales.forEach(locale => {
44 | fs.writeFileSync(localesPath + '/' + locale.fileName, JSON.stringify(locale.translations, null, 2));
45 | });
46 | msgs.forEach(msg => {
47 | if (msg.type === 'untranslated') {
48 | var msgC ='Message with key "' +
49 | msg.key +
50 | '" was not translated in language ' +
51 | msg.to.split('.')[0] +
52 | ', it has been backfilled with ' +
53 | msg.from.split('.')[0] +
54 | ' message "' +
55 | msg.backfill +
56 | '". \nGo to ' +
57 | localesPath +
58 | '/' + msg.to +
59 | ' to translate it properly.\n\n';
60 | console.log(colors.red(msgC));
61 | }
62 | })
63 | console.log(colors.green('Translation maintenance is complete'));
64 | }
65 |
66 | backfillTranslations();
67 |
68 | module.exports = backfillTranslations;
--------------------------------------------------------------------------------
/translationScripts/exportTranslationsToPo.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Translations exporter
3 | */
4 | var fs = require('fs');
5 | var path = require('path');
6 | var colors = require('colors');
7 | var argv = require('optimist').argv;
8 | var json2po = require('json2po');
9 |
10 | var exportTranslationsToPo = function() {
11 | console.log(colors.green('Begining to export all translations to .po translation files'));
12 | const localesPath = path.resolve(__dirname, argv.locales);
13 | const localesFiles = fs.readdirSync(localesPath);
14 | const destPath = path.resolve(__dirname, argv.dest);
15 | localesFiles.forEach(function (fileName) {
16 | const lang = fileName.split('.')[0];
17 | const data = require(localesPath + '/' + fileName);
18 | const po = json2po(
19 | JSON.stringify(data),
20 | {
21 | "Project-Id-Version": "Sample Project",
22 | "PO-Revision-Date": new Date(),
23 | "Language": lang
24 | }
25 | );
26 | const output = destPath + '/' + lang + '.po';
27 | fs.writeFileSync(output, po);
28 | })
29 | console.log(colors.green('Export done for all translations files !'));
30 | }
31 |
32 | exportTranslationsToPo();
33 |
34 | module.exports = exportTranslationsToPo;
--------------------------------------------------------------------------------
/translationScripts/importTranslationsFromPo.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Translations importer
3 | */
4 | var fs = require('fs');
5 | var path = require('path');
6 | var colors = require('colors');
7 | var argv = require('optimist').argv;
8 | var po2json = require('po2json');
9 |
10 | var importTranslationsFromPo = function() {
11 | console.log(colors.green('Begining to import all translations from .po translation files'));
12 | const localesPath = path.resolve(__dirname, argv.locales);
13 | const localesFiles = fs.readdirSync(localesPath);
14 | const srcPath = path.resolve(__dirname, argv.src);
15 | const localesSources = fs.readdirSync(srcPath);
16 | localesSources.forEach(function (fileName) {
17 | const lang = fileName.split('.')[0];
18 | console.log(colors.green('importing locale files for ', lang));
19 | const json = po2json.parseFileSync(srcPath + '/' + fileName);
20 | const clean = Object.keys(json).reduce(function(results, key) {
21 | if (key.length > 0) {
22 | results[key] = json[key][1];
23 | }
24 | return results;
25 | }, {});
26 | if (clean) {
27 | fs.writeFileSync(localesPath + '/' + lang + '.json', JSON.stringify(clean, null, 2));
28 | }
29 | })
30 | console.log(colors.green('Import done for all translations files !'));
31 | }
32 |
33 | importTranslationsFromPo();
34 |
35 | module.exports = importTranslationsFromPo;
--------------------------------------------------------------------------------
/translationScripts/updateTranslationsToPo.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Translations reconcilier
3 | */
4 | var fs = require('fs');
5 | var path = require('path');
6 | var colors = require('colors');
7 | var argv = require('optimist').argv;
8 | var json2po = require('json2po');
9 | var po2json = require('po2json');
10 |
11 | var updateTranslationsToPo = function() {
12 | console.log(colors.green('Begining to update po files with translations to .po translation files'));
13 | const localesPath = path.resolve(__dirname, argv.locales);
14 | const localesFiles = fs.readdirSync(localesPath);
15 | const destPath = path.resolve(__dirname, argv.dest);
16 | const poFiles = fs.readdirSync(destPath);
17 | poFiles.forEach(function (fileName) {
18 | const lang = fileName.split('.')[0];
19 | console.log(colors.green('inspecting locale files for ', lang));
20 | const json = po2json.parseFileSync(destPath + '/' + fileName);
21 | const data = Object.keys(json).reduce(function(results, key) {
22 | if (key.length > 0) {
23 | results[key] = json[key][1];
24 | }
25 | return results;
26 | }, {});
27 | if (data) {
28 | const js = require(localesPath + '/' + lang + '.json');
29 | var toUpdate = false;
30 | Object.keys(js).forEach(function(key) {
31 | if (data[key] === undefined) {
32 | console.log(colors.red(key + ' key is not present in po files for lang ' + lang + ', adding it'));
33 | data[key] = js[key];
34 | toUpdate = true;
35 | }
36 | });
37 |
38 | if (toUpdate) {
39 | console.log(colors.green('updating po file ' + fileName));
40 | const po = json2po(
41 | JSON.stringify(data),
42 | {
43 | "Project-Id-Version": "Sample Project",
44 | "PO-Revision-Date": new Date(),
45 | "Language": lang
46 | }
47 | );
48 | const output = destPath + '/' + lang + '.po';
49 | fs.writeFileSync(output, po);
50 | console.log(colors.green('update done for po file ' + fileName));
51 | }
52 | }
53 | })
54 | console.log(colors.green('Update done for all translations files ! You can now work with the Po files'));
55 | }
56 |
57 | updateTranslationsToPo();
58 |
59 | module.exports = updateTranslationsToPo;
--------------------------------------------------------------------------------
/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Webpack configuration for handling the application's source code
3 | * in development mode (standard)
4 | */
5 | var webpack = require('webpack');
6 | var config = require('config');
7 | var sharedConfig = require('./webpack.config.shared');
8 |
9 | module.exports = {
10 | module: sharedConfig.module,
11 | plugins: sharedConfig.plugins.concat([
12 | new webpack.DefinePlugin({
13 | 'process.env': {
14 | NODE_ENV: JSON.stringify('development')
15 | },
16 | 'FONIO_CONFIG': JSON.stringify(config)
17 | })
18 | ])
19 | };
20 |
--------------------------------------------------------------------------------
/webpack.config.docker.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Webpack configuration for handling the applicatino's source code
3 | * in production mode (standard + minify)
4 | */
5 | var webpack = require('webpack');
6 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
7 |
8 | var sharedConfig = require('./webpack.config.shared');
9 |
10 | module.exports = {
11 |
12 | module: sharedConfig.module,
13 |
14 | plugins: sharedConfig.plugins
15 | .concat(new UglifyJsPlugin())
16 | .concat(new webpack.DefinePlugin({
17 | 'process.env': {
18 | NODE_ENV: JSON.stringify('production')
19 | }
20 | })),
21 |
22 | devtool: 'source-map',
23 |
24 | output: {
25 | path: '/build',
26 | publicPath: '@@URL_PREFIX@@/build/'
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/webpack.config.prod.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Webpack configuration for handling the applicatino's source code
3 | * in production mode (standard + minify)
4 | */
5 | var webpack = require('webpack');
6 | const config = require('config');
7 | const urlPrefix = config.get('urlPrefix');
8 |
9 | var sharedConfig = require('./webpack.config.shared');
10 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
11 | module.exports = {
12 |
13 | module: sharedConfig.module,
14 |
15 | mode: 'production',
16 |
17 | plugins: sharedConfig.plugins
18 | /*.concat(new UglifyJsPlugin())*/
19 | .concat(new webpack.DefinePlugin({
20 | 'process.env': {
21 | NODE_ENV: JSON.stringify('production')
22 | }
23 | }))
24 | .concat(new BundleAnalyzerPlugin({
25 | openAnalyzer: false,
26 | generateStatsFile: true,
27 | analyzerMode: 'disabled'
28 | }))
29 | ,
30 |
31 | // devtool: 'source-map',
32 |
33 | output: {
34 | path: '/build',
35 | publicPath: urlPrefix && urlPrefix.length ? urlPrefix + '/build/' : '/build/'
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/webpack.config.shared.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Webpack configuration base for handling the application's source code
3 | */
4 | var webpack = require('webpack');
5 |
6 | module.exports = {
7 | module: {
8 | rules: [
9 | {
10 | test: /\.(woff|ttf|otf|eot|woff2)$/i,
11 | use: [
12 | {
13 | loader: 'file-loader',
14 | options: {
15 | query: {
16 | name:'assets/[name].[ext]'
17 | }
18 | }
19 | },
20 | ]
21 | },
22 | {
23 | test: /\.(jpe?g|png|gif|svg)$/i,
24 | use: [
25 | 'url-loader?limit=10000',
26 | 'img-loader'
27 | ]
28 | },
29 | /*{
30 | test: /\.scss$/,
31 | use: ['style-loader', 'css-loader', 'sass-loader']
32 | },
33 | {
34 | test: /\.css$/,
35 | use: ['style-loader', 'css-loader']
36 | }*/
37 | ]
38 | },
39 | plugins: []
40 | };
41 |
--------------------------------------------------------------------------------