зеркало из https://github.com/mozilla/pontoon.git
Set `tabWidth: 2` in Prettier config (#2438)
* chore: Update Prettier config, setting tabWidth:2 * style: Apply updated Prettier styles If you need to rebase work past this style change, do as follows: 0. Consider this to be commit `commitA`, replacing that with its id in the following. 1. To make sure mistakes aren't fatal, assign a second branch to your current work. 2. Rebase your branch on the commit immediately before this one, commitA~ 3. Run the following command at the root of the repo: git rebase --strategy-option=theirs \ --exec 'npx prettier --write . && git add -u && git commit --amend --no-edit' \ commitA That will take a short while esp. if you have multiple commits, as it runs Prettier on everything for every commit. If you've deleted files, the rebase may drop down to interactive mode and have you `git rm` as appropriate, then `git rebase --continue`. You should end up with just your changes in your branch, prettily formatted. To validate that, apply the same Prettier config change to your original branch, reformat the files with `npm run prettier`, and then compare the results with the rebased branch. * chore: Clean up lint configs
This commit is contained in:
Родитель
9fc9430cc7
Коммит
9faea69c20
|
@ -1,16 +1,23 @@
|
|||
**/*.bundle.js
|
||||
**/*.js.map
|
||||
**/dist/**
|
||||
**/build/**
|
||||
vendor/**
|
||||
coverage/**
|
||||
static/*
|
||||
**/*.min.js
|
||||
**/js/lib/**/*.js
|
||||
**/app/error_pages/**/*.js
|
||||
**/*blockrain*js
|
||||
assets/*
|
||||
**/node_modules/**
|
||||
docs/
|
||||
.vscode/
|
||||
tag-admin/dist/
|
||||
translate/dist/
|
||||
coverage/
|
||||
docs/_build/
|
||||
docs/venv/
|
||||
package-lock.json
|
||||
specs/
|
||||
|
||||
# Jinja templates
|
||||
pontoon/base/templates/js/pontoon.js
|
||||
translate/public/translate.html
|
||||
**/templates/**/*.html
|
||||
|
||||
# Vendored code
|
||||
error_pages/css/blockrain.css
|
||||
error_pages/js/
|
||||
pontoon/base/static/css/boilerplate.css
|
||||
pontoon/base/static/css/fontawesome-all.css
|
||||
pontoon/base/static/css/jquery-ui.css
|
||||
pontoon/base/static/css/nprogress.css
|
||||
pontoon/base/static/js/lib/
|
||||
pontoon/in_context/static/
|
||||
|
|
36
.eslintrc.js
36
.eslintrc.js
|
@ -1,19 +1,18 @@
|
|||
/* eslint-env node */
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended"
|
||||
],
|
||||
extends: ['eslint:recommended', 'plugin:react/recommended'],
|
||||
env: {
|
||||
es6: true,
|
||||
browser: true,
|
||||
jest: true,
|
||||
},
|
||||
parser: "@babel/eslint-parser",
|
||||
parser: '@babel/eslint-parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2017,
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
experimentalObjectRestSpread: true
|
||||
experimentalObjectRestSpread: true,
|
||||
},
|
||||
sourceType: 'module',
|
||||
babelOptions: {
|
||||
|
@ -56,23 +55,24 @@ module.exports = {
|
|||
traversalShortcutsHandler: true,
|
||||
editorShortcutsHandler: true,
|
||||
},
|
||||
plugins: [
|
||||
'react',
|
||||
],
|
||||
plugins: ['react'],
|
||||
rules: {
|
||||
'react/display-name': 0,
|
||||
'react/prefer-es6-class': 1,
|
||||
'react/prefer-stateless-function': 0,
|
||||
"react/prop-types": 0,
|
||||
"react/jsx-key": 0,
|
||||
"react/jsx-uses-react": 1,
|
||||
'react/prop-types': 0,
|
||||
'react/jsx-key': 0,
|
||||
'react/jsx-uses-react': 1,
|
||||
'react/jsx-uses-vars': 1,
|
||||
"no-unused-vars": ["error", { "vars": "all", "args": "after-used", "ignoreRestSiblings": true }],
|
||||
"no-console": 1,
|
||||
'no-unused-vars': [
|
||||
'error',
|
||||
{ vars: 'all', args: 'after-used', ignoreRestSiblings: true },
|
||||
],
|
||||
'no-console': 1,
|
||||
},
|
||||
settings: {
|
||||
'react': {
|
||||
'version': 'detect'
|
||||
}
|
||||
}
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
# minified code
|
||||
*.min.js
|
||||
*.min.css
|
||||
translate/dist
|
||||
tag-admin/dist
|
||||
.vscode/
|
||||
tag-admin/dist/
|
||||
translate/dist/
|
||||
coverage/
|
||||
docs/_build/
|
||||
docs/venv/
|
||||
package-lock.json
|
||||
specs/
|
||||
|
||||
# libraries
|
||||
**/base/static/js/lib*
|
||||
**/base/static/css/boilerplate.css
|
||||
**/base/static/css/fontawesome-all.css
|
||||
**/base/static/css/jquery-ui.css
|
||||
**/base/static/css/nprogress.css
|
||||
**/base/templates/js/pontoon.js
|
||||
**/in_context/static/css/agency.css
|
||||
**/in_context/static/js/agency.js
|
||||
# Jinja templates
|
||||
pontoon/base/templates/js/pontoon.js
|
||||
translate/public/translate.html
|
||||
**/templates/**/*.html
|
||||
|
||||
# Prevent VSCode to reformat these files if "Format On Save" enabled
|
||||
*.html
|
||||
*.yml
|
||||
**/package.json*
|
||||
# Vendored code
|
||||
error_pages/css/blockrain.css
|
||||
error_pages/js/
|
||||
pontoon/base/static/css/boilerplate.css
|
||||
pontoon/base/static/css/fontawesome-all.css
|
||||
pontoon/base/static/css/jquery-ui.css
|
||||
pontoon/base/static/css/nprogress.css
|
||||
pontoon/base/static/js/lib/
|
||||
pontoon/in_context/static/
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
schedule: "every week on monday"
|
||||
schedule: 'every week on monday'
|
||||
search: False
|
||||
update: insecure
|
||||
requirements:
|
||||
|
|
|
@ -5,6 +5,7 @@ For more details, please read the
|
|||
[Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).
|
||||
|
||||
## How to Report
|
||||
|
||||
For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page.
|
||||
|
||||
<!--
|
||||
|
|
4
Makefile
4
Makefile
|
@ -122,13 +122,13 @@ pyupgrade:
|
|||
"${DC}" run --rm server pyupgrade --exit-zero-even-if-changed --py38-plus *.py `find pontoon -name \*.py`
|
||||
|
||||
check-pyupgrade:
|
||||
"${DC}" run --rm webapp pyupgrade --py38-plus *.py `find pontoon -name \*.py`
|
||||
"${DC}" run --rm server pyupgrade --py38-plus *.py `find pontoon -name \*.py`
|
||||
|
||||
black:
|
||||
"${DC}" run --rm server black pontoon/
|
||||
|
||||
check-black:
|
||||
"${DC}" run --rm webapp black --check pontoon
|
||||
"${DC}" run --rm server black --check pontoon
|
||||
|
||||
dropdb:
|
||||
"${DC}" down --volumes postgresql
|
||||
|
|
23
README.md
23
README.md
|
@ -7,7 +7,6 @@ uses version-control systems for storing translations.
|
|||
|
||||
[📚 **Documentation**](https://mozilla-pontoon.readthedocs.io/)
|
||||
|
||||
|
||||
## Installing Pontoon
|
||||
|
||||
If you are looking to host your own instance of Pontoon, there are several ways to do so.
|
||||
|
@ -27,7 +26,6 @@ testing for example, see our
|
|||
[Developer Setup using Docker](https://mozilla-pontoon.readthedocs.io/en/latest/dev/setup.html).
|
||||
Please note that you should **not** deploy a production instance with Docker.
|
||||
|
||||
|
||||
## Contributing to Pontoon
|
||||
|
||||
Do you want to help us make Pontoon better? We are very glad!
|
||||
|
@ -39,14 +37,14 @@ database, run tests, and send your contribution.
|
|||
|
||||
If you want to go further, you can:
|
||||
|
||||
* Check out development roadmap on the [wiki](https://wiki.mozilla.org/Pontoon)
|
||||
* Report an [issue](https://github.com/mozilla/pontoon/issues/new)
|
||||
* Check [existing issues](https://github.com/mozilla/pontoon/issues)
|
||||
* See Mozilla's Pontoon servers:
|
||||
* [Staging](https://mozilla-pontoon-staging.herokuapp.com/)
|
||||
* [Production](https://pontoon.mozilla.org/)
|
||||
* For discussing Pontoon's development, get in touch with us on [chat.mozilla.org](https://chat.mozilla.org/#/room/#pontoon:mozilla.org)
|
||||
* For feedback, support, and 3rd party deployments, check out [Discourse](https://discourse.mozilla.org/c/pontoon/)
|
||||
- Check out development roadmap on the [wiki](https://wiki.mozilla.org/Pontoon)
|
||||
- Report an [issue](https://github.com/mozilla/pontoon/issues/new)
|
||||
- Check [existing issues](https://github.com/mozilla/pontoon/issues)
|
||||
- See Mozilla's Pontoon servers:
|
||||
- [Staging](https://mozilla-pontoon-staging.herokuapp.com/)
|
||||
- [Production](https://pontoon.mozilla.org/)
|
||||
- For discussing Pontoon's development, get in touch with us on [chat.mozilla.org](https://chat.mozilla.org/#/room/#pontoon:mozilla.org)
|
||||
- For feedback, support, and 3rd party deployments, check out [Discourse](https://discourse.mozilla.org/c/pontoon/)
|
||||
|
||||
## License
|
||||
|
||||
|
@ -54,11 +52,10 @@ This software is licensed under the
|
|||
[New BSD License](https://creativecommons.org/licenses/BSD/). For more
|
||||
information, read [LICENSE](https://github.com/mozilla/pontoon/blob/master/LICENSE).
|
||||
|
||||
|
||||
## Screenshots
|
||||
|
||||
![](docs/img/screenshots/teams-dashboard.png)
|
||||
*Teams dashboard*
|
||||
_Teams dashboard_
|
||||
|
||||
![](docs/img/screenshots/translation-app.png)
|
||||
*Translation app*
|
||||
_Translation app_
|
||||
|
|
9
app.json
9
app.json
|
@ -1,7 +1,14 @@
|
|||
{
|
||||
"name": "pontoon",
|
||||
"description": "In-place localization tool.",
|
||||
"keywords": ["l10n", "localization", "mozilla", "collaboration", "python", "django"],
|
||||
"keywords": [
|
||||
"l10n",
|
||||
"localization",
|
||||
"mozilla",
|
||||
"collaboration",
|
||||
"python",
|
||||
"django"
|
||||
],
|
||||
"website": "https://pontoon.mozilla.org",
|
||||
"logo": "https://pontoon.mozilla.org/static/img/logo.svg",
|
||||
"success_url": "/",
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
{
|
||||
"presets": [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react"
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-transform-runtime"
|
||||
]
|
||||
"presets": ["@babel/preset-env", "@babel/preset-react"],
|
||||
"plugins": ["@babel/plugin-transform-runtime"]
|
||||
}
|
||||
|
|
|
@ -9,10 +9,7 @@
|
|||
"home": "https://wiki.mozilla.org/Webdev/GetInvolved/pontoon.mozilla.org",
|
||||
"docs": "https://mozilla-pontoon.readthedocs.io/",
|
||||
"chat": "https://chat.mozilla.org/#/room/#pontoon:mozilla.org",
|
||||
"chat-contacts": [
|
||||
"mathjazz",
|
||||
"eemeli"
|
||||
],
|
||||
"chat-contacts": ["mathjazz", "eemeli"],
|
||||
"mailing-list": "https://discourse.mozilla.org/c/pontoon"
|
||||
},
|
||||
"bugs": {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# docker-compose for Pontoon development.
|
||||
#
|
||||
# Note: Requires docker-compose 1.10+.
|
||||
version: "2.3"
|
||||
version: '2.3'
|
||||
services:
|
||||
server:
|
||||
build:
|
||||
|
@ -14,7 +14,7 @@ services:
|
|||
depends_on:
|
||||
- postgresql
|
||||
ports:
|
||||
- "8000:8000"
|
||||
- '8000:8000'
|
||||
volumes:
|
||||
- ./pontoon:/app/pontoon
|
||||
- ./requirements:/app/requirements
|
||||
|
|
|
@ -166,27 +166,27 @@ spec:
|
|||
cpu: 0.5
|
||||
env:
|
||||
- name: SECRET_KEY
|
||||
value: "<the pontoon secret key>"
|
||||
value: '<the pontoon secret key>'
|
||||
- name: DJANGO_LOGIN
|
||||
value: "true"
|
||||
value: 'true'
|
||||
- name: DJANGO_DEV
|
||||
value: "false"
|
||||
value: 'false'
|
||||
- name: DJANGO_DEBUG
|
||||
value: "true"
|
||||
value: 'true'
|
||||
- name: CI
|
||||
value: "true"
|
||||
value: 'true'
|
||||
- name: DATABASE_URL
|
||||
value: "<pontoon database URL>"
|
||||
value: '<pontoon database URL>'
|
||||
- name: ALLOWED_HOSTS
|
||||
value: "<comma seperated list of allowd hosts>"
|
||||
value: '<comma seperated list of allowd hosts>'
|
||||
- name: SITE_URL
|
||||
value: "http://127.0.0.10"
|
||||
value: 'http://127.0.0.10'
|
||||
- name: SYNC_INTERVAL
|
||||
value: "30"
|
||||
value: '30'
|
||||
- name: KNOWN_HOSTS
|
||||
value: "<base64 encoded file content of .ssh/known_hosts>"
|
||||
value: '<base64 encoded file content of .ssh/known_hosts>'
|
||||
- name: SSH_KEY
|
||||
value: "<base64 encoded ssh private key from .ssh/id_rsa>"
|
||||
value: '<base64 encoded ssh private key from .ssh/id_rsa>'
|
||||
imagePullSecrets:
|
||||
- name: sec-dockerhub
|
||||
---
|
||||
|
@ -202,7 +202,7 @@ spec:
|
|||
ports:
|
||||
- name: https-web
|
||||
protocol: TCP
|
||||
#the port is not really used but mandatory
|
||||
#the port is not really used but mandatory
|
||||
port: 2049
|
||||
targetPort: 8080
|
||||
nodePort: <your assigned node port!>
|
||||
|
|
|
@ -117,4 +117,3 @@ For a k8s deplyoment example yaml see the k8s-pontoon-example.yaml in this folde
|
|||
* syncing all projects with writing changes to the soruce code `python manage.py sync_projects`.
|
||||
This is done by shell script every 30 minutes (evn var SYNC_INTERVAL)
|
||||
* to see if the sync works look at: http://127.0.0.1:8000/__sync/log/__
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Pontoon Error Pages
|
||||
|
||||
[Custom Error Pages](https://devcenter.heroku.com/articles/error-pages#customize-pages) for Pontoon deployment on Heroku, featuring Tetris via [blockrain.js](http://aerolab.github.io/blockrain.js/)!
|
||||
|
||||
Must be hosted outside the main application, which will be down when these pages are displayed.
|
||||
|
|
|
@ -1,18 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,400i,700" rel="stylesheet">
|
||||
<link href='https://fonts.googleapis.com/css?family=Play:400,700' rel='stylesheet' type='text/css'>
|
||||
<head>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Open+Sans:400,400i,700"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Play:400,700"
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
/>
|
||||
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<link rel="stylesheet" href="css/blockrain.css">
|
||||
<link rel="stylesheet" href="css/style.css" />
|
||||
<link rel="stylesheet" href="css/blockrain.css" />
|
||||
|
||||
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
|
||||
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
|
||||
|
||||
<title>Application Error</title>
|
||||
</head>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Application Error</h1>
|
||||
<h2>Please check back later. Or play some Tetris.</h2>
|
||||
|
@ -35,7 +42,9 @@
|
|||
</article>
|
||||
|
||||
<footer>
|
||||
<a id="credits" href="https://github.com/Aerolab/blockrain.js">Powered by Blockrain.js</a>
|
||||
<a id="credits" href="https://github.com/Aerolab/blockrain.js"
|
||||
>Powered by Blockrain.js</a
|
||||
>
|
||||
</footer>
|
||||
</section>
|
||||
|
||||
|
@ -47,8 +56,8 @@
|
|||
<script>
|
||||
var $demo = $('#tetris-demo').blockrain({
|
||||
theme: 'pontoon',
|
||||
playText: ''
|
||||
playText: '',
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,18 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,400i,700" rel="stylesheet">
|
||||
<link href='https://fonts.googleapis.com/css?family=Play:400,700' rel='stylesheet' type='text/css'>
|
||||
<head>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Open+Sans:400,400i,700"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Play:400,700"
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
/>
|
||||
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<link rel="stylesheet" href="css/blockrain.css">
|
||||
<link rel="stylesheet" href="css/style.css" />
|
||||
<link rel="stylesheet" href="css/blockrain.css" />
|
||||
|
||||
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
|
||||
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
|
||||
|
||||
<title>Offline for maintenance</title>
|
||||
</head>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Offline for maintenance</h1>
|
||||
<h2>Please check back later. Or play some Tetris.</h2>
|
||||
|
@ -35,7 +42,9 @@
|
|||
</article>
|
||||
|
||||
<footer>
|
||||
<a id="credits" href="https://github.com/Aerolab/blockrain.js">Powered by Blockrain.js</a>
|
||||
<a id="credits" href="https://github.com/Aerolab/blockrain.js"
|
||||
>Powered by Blockrain.js</a
|
||||
>
|
||||
</footer>
|
||||
</section>
|
||||
|
||||
|
@ -47,8 +56,8 @@
|
|||
<script>
|
||||
var $demo = $('#tetris-demo').blockrain({
|
||||
theme: 'pontoon',
|
||||
playText: ''
|
||||
playText: '',
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -20,9 +20,9 @@
|
|||
"build": "npm run build --workspaces --if-present",
|
||||
"build:prod": "npm run build:prod --workspaces --if-present",
|
||||
"heroku-postbuild": "echo Build is taken care of in ./bin/post_compile",
|
||||
"prettier": "prettier --write '**/translate/**/*.{js,ts,tsx,css}' '**/pontoon/**/*.{js,css}' '**/tag-admin/**/*.{js,css}'",
|
||||
"check-prettier": "prettier --check '**/translate/**/*.{js,ts,tsx,css}' '**/pontoon/**/*.{js,css}' '**/tag-admin/**/*.{js,css}'",
|
||||
"eslint": "eslint 'translate/**/*.{js,ts,tsx}' 'pontoon/**/*.js' 'tag-admin/**/*.js'"
|
||||
"prettier": "prettier --write .",
|
||||
"check-prettier": "prettier --check .",
|
||||
"eslint": "eslint ."
|
||||
},
|
||||
"workspaces": [
|
||||
"translate",
|
||||
|
@ -92,7 +92,7 @@
|
|||
"prettier": {
|
||||
"endOfLine": "lf",
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 4,
|
||||
"tabWidth": 2,
|
||||
"jsxSingleQuote": true,
|
||||
"singleQuote": true
|
||||
},
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
# GraphQL API
|
||||
|
||||
Pontoon exposes some of its data via a public API endpoint. The API is
|
||||
[GraphQL](http://graphql.org/)-based and available at ``/graphql``.
|
||||
|
||||
[GraphQL](http://graphql.org/)-based and available at `/graphql`.
|
||||
|
||||
## Production Deployments
|
||||
|
||||
When run in production (``DEV is False``) the API returns ``application/json``
|
||||
When run in production (`DEV is False`) the API returns `application/json`
|
||||
responses to GET and POST requests. In case of GET requests, any whitespace in
|
||||
the query must be escaped.
|
||||
|
||||
|
@ -22,14 +21,13 @@ An example POST requests may look like this:
|
|||
$ curl -X POST -d "query={ projects { name } }" https://example.com/graphql
|
||||
```
|
||||
|
||||
|
||||
## Local Development
|
||||
|
||||
In a local development setup (``DEV is True``) the endpoint has two modes of
|
||||
In a local development setup (`DEV is True`) the endpoint has two modes of
|
||||
operation: a JSON one and an HTML one.
|
||||
|
||||
When a request is sent, without any headers, with ``Accept: application/json`` or
|
||||
if it explicitly contains a ``raw`` query argument, the endpoint will behave like
|
||||
When a request is sent, without any headers, with `Accept: application/json` or
|
||||
if it explicitly contains a `raw` query argument, the endpoint will behave like
|
||||
a production one, returning JSON responses.
|
||||
|
||||
The following query in the CLI will return a JSON response:
|
||||
|
@ -38,22 +36,21 @@ The following query in the CLI will return a JSON response:
|
|||
$ curl --globoff http://localhost:8000/graphql?query={projects{name}}
|
||||
```
|
||||
|
||||
If however a request is sent with ``Accept: text/html`` such as is the case when
|
||||
If however a request is sent with `Accept: text/html` such as is the case when
|
||||
accessing the endpoint in a browser, a GUI query editor and explorer,
|
||||
[GraphiQL](https://github.com/graphql/graphiql), will be served::
|
||||
|
||||
http://localhost:8000/graphql?query={projects{name}}
|
||||
|
||||
To preview the JSON response in the browser, pass in the ``raw`` query argument::
|
||||
To preview the JSON response in the browser, pass in the `raw` query argument::
|
||||
|
||||
http://localhost:8000/graphql?query={projects{name}}&raw
|
||||
|
||||
|
||||
## Query IDE
|
||||
|
||||
The [GraphiQL](https://github.com/graphql/graphiql) query IDE is available at
|
||||
``http://localhost:8000/graphql`` when running Pontoon locally and the URL is
|
||||
accessed with the ``Accept: text/html`` header, e.g. using a browser.
|
||||
`http://localhost:8000/graphql` when running Pontoon locally and the URL is
|
||||
accessed with the `Accept: text/html` header, e.g. using a browser.
|
||||
|
||||
It offers a query editor with:
|
||||
|
||||
|
@ -62,4 +59,4 @@ It offers a query editor with:
|
|||
- real-time error reporting,
|
||||
- results folding,
|
||||
- autogenerated docs on shapes and their fields,
|
||||
- [introspection](http://docs.graphene-python.org/projects/django/en/latest/debug/) via the ``__debug``.
|
||||
- [introspection](http://docs.graphene-python.org/projects/django/en/latest/debug/) via the `__debug`.
|
||||
|
|
|
@ -51,9 +51,7 @@ var Pontoon = (function (my) {
|
|||
1000,
|
||||
function () {
|
||||
// Remove inline style and unread mark to make hover work again
|
||||
unreadNotifications
|
||||
.removeAttr('style')
|
||||
.removeAttr('data-unread');
|
||||
unreadNotifications.removeAttr('style').removeAttr('data-unread');
|
||||
},
|
||||
);
|
||||
},
|
||||
|
@ -286,10 +284,7 @@ $(function () {
|
|||
var $this = $(this);
|
||||
var loginUrl = $this.prop('href'),
|
||||
startSign = loginUrl.match(/\?/) ? '&' : '?';
|
||||
$this.prop(
|
||||
'href',
|
||||
loginUrl + startSign + 'next=' + getRedirectUrl(),
|
||||
);
|
||||
$this.prop('href', loginUrl + startSign + 'next=' + getRedirectUrl());
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -385,16 +380,12 @@ $(function () {
|
|||
|
||||
// General keyboard shortcuts
|
||||
generalShortcutsHandler = function (e) {
|
||||
const mediaQuery = window.matchMedia(
|
||||
'(prefers-reduced-motion: reduce)',
|
||||
);
|
||||
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
|
||||
|
||||
function moveMenu(type) {
|
||||
var options =
|
||||
type === 'up' ? ['first', 'last', -1] : ['last', 'first', 1];
|
||||
var items = menu.find(
|
||||
'li:visible:not(.horizontal-separator, :has(li))',
|
||||
);
|
||||
var items = menu.find('li:visible:not(.horizontal-separator, :has(li))');
|
||||
var element = null;
|
||||
|
||||
if (
|
||||
|
|
|
@ -11,9 +11,7 @@ $(function () {
|
|||
.siblings('.legend')
|
||||
.find('li')
|
||||
.each(function () {
|
||||
stats[$(this).attr('class')] = $(this)
|
||||
.find('.value')
|
||||
.data('value');
|
||||
stats[$(this).attr('class')] = $(this).find('.value').data('value');
|
||||
});
|
||||
|
||||
stats.all = progress
|
||||
|
@ -30,9 +28,7 @@ $(function () {
|
|||
? stats.missing / stats.all
|
||||
: 1 /* Draw "empty" progress if no projects enabled */,
|
||||
},
|
||||
number = Math.floor(
|
||||
(fraction.translated + fraction.warnings) * 100,
|
||||
);
|
||||
number = Math.floor((fraction.translated + fraction.warnings) * 100);
|
||||
|
||||
// Update graph
|
||||
var canvas = this,
|
||||
|
|
|
@ -32,12 +32,8 @@ $('body')
|
|||
action = $element.data('action'),
|
||||
name = $element.data('user-name'),
|
||||
link = $element.data('user-link'),
|
||||
date = date_formatter.format(
|
||||
new Date($element.attr('datetime')),
|
||||
),
|
||||
time = time_formatter.format(
|
||||
new Date($element.attr('datetime')),
|
||||
);
|
||||
date = date_formatter.format(new Date($element.attr('datetime'))),
|
||||
time = time_formatter.format(new Date($element.attr('datetime')));
|
||||
|
||||
$element.after(
|
||||
'<aside class="tooltip">' +
|
||||
|
@ -87,10 +83,7 @@ var Pontoon = (function (my) {
|
|||
* TODO: remove old search code from main.js
|
||||
*/
|
||||
filter: (function () {
|
||||
$('body').on(
|
||||
'input.filter',
|
||||
'input.table-filter',
|
||||
function (e) {
|
||||
$('body').on('input.filter', 'input.table-filter', function (e) {
|
||||
if (e.which === 9) {
|
||||
return;
|
||||
}
|
||||
|
@ -118,8 +111,7 @@ var Pontoon = (function (my) {
|
|||
)
|
||||
.parents(item)
|
||||
.show();
|
||||
},
|
||||
);
|
||||
});
|
||||
})(),
|
||||
|
||||
/*
|
||||
|
@ -131,12 +123,8 @@ var Pontoon = (function (my) {
|
|||
var legend = $(el).find('.progress .legend'),
|
||||
all = legend.find('.all .value').data('value') || 0,
|
||||
translated =
|
||||
legend
|
||||
.find('.translated .value')
|
||||
.data('value') / all || 0,
|
||||
fuzzy =
|
||||
legend.find('.fuzzy .value').data('value') /
|
||||
all || 0;
|
||||
legend.find('.translated .value').data('value') / all || 0,
|
||||
fuzzy = legend.find('.fuzzy .value').data('value') / all || 0;
|
||||
|
||||
if ($(el).find('.progress .not-ready').length) {
|
||||
return 'not-ready';
|
||||
|
@ -174,9 +162,7 @@ var Pontoon = (function (my) {
|
|||
}
|
||||
|
||||
function getNumber(el) {
|
||||
return parseInt(
|
||||
$(el).find('span').text().replace(/,/g, ''),
|
||||
);
|
||||
return parseInt($(el).find('span').text().replace(/,/g, ''));
|
||||
}
|
||||
|
||||
function getString(el) {
|
||||
|
@ -232,14 +218,8 @@ var Pontoon = (function (my) {
|
|||
var timeA = getTime(a),
|
||||
timeB = getTime(b);
|
||||
|
||||
if (
|
||||
timeA === defaultTime &&
|
||||
timeB === defaultTime
|
||||
) {
|
||||
return (
|
||||
getString(a).localeCompare(getString(b)) *
|
||||
dir
|
||||
);
|
||||
if (timeA === defaultTime && timeB === defaultTime) {
|
||||
return getString(a).localeCompare(getString(b)) * dir;
|
||||
} else if (timeA === defaultTime) {
|
||||
return 1 * dir;
|
||||
} else if (timeB === defaultTime) {
|
||||
|
@ -265,9 +245,7 @@ var Pontoon = (function (my) {
|
|||
|
||||
// Sort by alphabetical order
|
||||
} else {
|
||||
return (
|
||||
getString(a).localeCompare(getString(b)) * dir
|
||||
);
|
||||
return getString(a).localeCompare(getString(b)) * dir;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -22,9 +22,7 @@ $(function () {
|
|||
var isProjectNotification =
|
||||
$.inArray($(this).data('id'), notifications) > -1;
|
||||
$(this).toggle(isProjectNotification);
|
||||
$(this)
|
||||
.next('.horizontal-separator')
|
||||
.toggle(isProjectNotification);
|
||||
$(this).next('.horizontal-separator').toggle(isProjectNotification);
|
||||
});
|
||||
|
||||
$('.right-column .notification-item:visible:last')
|
||||
|
|
|
@ -12,9 +12,7 @@ const Sections = {
|
|||
},
|
||||
|
||||
goToNext() {
|
||||
this.goTo(
|
||||
Math.min(this.activeSectionIdx + 1, this.sections.length - 1),
|
||||
);
|
||||
this.goTo(Math.min(this.activeSectionIdx + 1, this.sections.length - 1));
|
||||
},
|
||||
|
||||
goToPrev() {
|
||||
|
|
|
@ -17,16 +17,11 @@ var Pontoon = (function (my) {
|
|||
});
|
||||
|
||||
// Select active users period
|
||||
$('#insights h3 .period-selector .selector').on(
|
||||
'click',
|
||||
function () {
|
||||
$(
|
||||
'#insights h3 .period-selector .selector',
|
||||
).removeClass('active');
|
||||
$('#insights h3 .period-selector .selector').on('click', function () {
|
||||
$('#insights h3 .period-selector .selector').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
Pontoon.insights.renderActiveUsers();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// Set up canvas to be HiDPI display ready
|
||||
$('#insights canvas.chart').each(function () {
|
||||
|
@ -57,9 +52,7 @@ var Pontoon = (function (my) {
|
|||
// Collect data
|
||||
var parent = $(this).parents('.active-users-chart');
|
||||
var id = parent.attr('id');
|
||||
var period = $('.period-selector .active')
|
||||
.data('period')
|
||||
.toString();
|
||||
var period = $('.period-selector .active').data('period').toString();
|
||||
var active = $('.active-users').data(period)[id];
|
||||
var total = $('.active-users').data('total')[id];
|
||||
|
||||
|
@ -96,13 +89,7 @@ var Pontoon = (function (my) {
|
|||
|
||||
function plot(start, end, color) {
|
||||
context.beginPath();
|
||||
context.arc(
|
||||
x,
|
||||
y,
|
||||
radius,
|
||||
start * Math.PI,
|
||||
end * Math.PI,
|
||||
);
|
||||
context.arc(x, y, radius, start * Math.PI, end * Math.PI);
|
||||
context.strokeStyle = color;
|
||||
context.stroke();
|
||||
}
|
||||
|
@ -149,8 +136,7 @@ var Pontoon = (function (my) {
|
|||
yPadding: 10,
|
||||
displayColors: false,
|
||||
callbacks: {
|
||||
label: (item) =>
|
||||
nf.format(item.value) + ' days',
|
||||
label: (item) => nf.format(item.value) + ' days',
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
|
@ -222,9 +208,7 @@ var Pontoon = (function (my) {
|
|||
{
|
||||
type: 'line',
|
||||
label: '12-month average',
|
||||
data: chart.data(
|
||||
'time-to-review-suggestions-12-month-avg',
|
||||
),
|
||||
data: chart.data('time-to-review-suggestions-12-month-avg'),
|
||||
borderColor: ['#3e7089'],
|
||||
borderWidth: 1,
|
||||
pointBackgroundColor: '#3e7089',
|
||||
|
@ -250,8 +234,7 @@ var Pontoon = (function (my) {
|
|||
yPadding: 10,
|
||||
callbacks: {
|
||||
label(items, chart) {
|
||||
const { label } =
|
||||
chart.datasets[items.datasetIndex];
|
||||
const { label } = chart.datasets[items.datasetIndex];
|
||||
return `${label}: ${items.value} days`;
|
||||
},
|
||||
},
|
||||
|
@ -376,34 +359,25 @@ var Pontoon = (function (my) {
|
|||
// Dataset order affects stacking, tooltip and
|
||||
// legend, but it doesn't work intuitively, so
|
||||
// we need to manually sort tooltip items.
|
||||
if (
|
||||
a.datasetIndex === 2 &&
|
||||
b.datasetIndex === 1
|
||||
) {
|
||||
if (a.datasetIndex === 2 && b.datasetIndex === 1) {
|
||||
return 1;
|
||||
}
|
||||
},
|
||||
callbacks: {
|
||||
label: function (items, chart) {
|
||||
const human =
|
||||
chart.datasets[1].data[items.index];
|
||||
const machinery =
|
||||
chart.datasets[2].data[items.index];
|
||||
const human = chart.datasets[1].data[items.index];
|
||||
const machinery = chart.datasets[2].data[items.index];
|
||||
|
||||
const label =
|
||||
chart.datasets[items.datasetIndex]
|
||||
.label;
|
||||
const label = chart.datasets[items.datasetIndex].label;
|
||||
const value = items.yLabel;
|
||||
const base =
|
||||
label + ': ' + nf.format(value);
|
||||
const base = label + ': ' + nf.format(value);
|
||||
|
||||
switch (label) {
|
||||
case 'Completion':
|
||||
return base + '%';
|
||||
case 'Human translations':
|
||||
case 'Machinery translations': {
|
||||
const pct =
|
||||
Pontoon.insights.getPercent(
|
||||
const pct = Pontoon.insights.getPercent(
|
||||
value,
|
||||
human + machinery,
|
||||
);
|
||||
|
@ -585,38 +559,28 @@ var Pontoon = (function (my) {
|
|||
// legend, but it doesn't work intuitively, so
|
||||
// we need to manually sort tooltip items.
|
||||
if (
|
||||
(a.datasetIndex === 3 &&
|
||||
b.datasetIndex === 2) ||
|
||||
(a.datasetIndex === 3 &&
|
||||
b.datasetIndex === 1) ||
|
||||
(a.datasetIndex === 2 &&
|
||||
b.datasetIndex === 1)
|
||||
(a.datasetIndex === 3 && b.datasetIndex === 2) ||
|
||||
(a.datasetIndex === 3 && b.datasetIndex === 1) ||
|
||||
(a.datasetIndex === 2 && b.datasetIndex === 1)
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
},
|
||||
callbacks: {
|
||||
label: function (items, chart) {
|
||||
const label =
|
||||
chart.datasets[items.datasetIndex]
|
||||
.label;
|
||||
const label = chart.datasets[items.datasetIndex].label;
|
||||
const value = items.yLabel;
|
||||
const base =
|
||||
label + ': ' + nf.format(value);
|
||||
const base = label + ': ' + nf.format(value);
|
||||
|
||||
if (chart.datasets.length < 4) return base;
|
||||
|
||||
const peerApproved =
|
||||
chart.datasets[1].data[items.index];
|
||||
const selfApproved =
|
||||
chart.datasets[2].data[items.index];
|
||||
const rejected =
|
||||
chart.datasets[3].data[items.index];
|
||||
const peerApproved = chart.datasets[1].data[items.index];
|
||||
const selfApproved = chart.datasets[2].data[items.index];
|
||||
const rejected = chart.datasets[3].data[items.index];
|
||||
|
||||
switch (label) {
|
||||
case 'Self-approved': {
|
||||
const pct =
|
||||
Pontoon.insights.getPercent(
|
||||
const pct = Pontoon.insights.getPercent(
|
||||
value,
|
||||
peerApproved + selfApproved,
|
||||
);
|
||||
|
@ -624,8 +588,7 @@ var Pontoon = (function (my) {
|
|||
}
|
||||
case 'Peer-approved':
|
||||
case 'Rejected': {
|
||||
const pct =
|
||||
Pontoon.insights.getPercent(
|
||||
const pct = Pontoon.insights.getPercent(
|
||||
value,
|
||||
peerApproved + rejected,
|
||||
);
|
||||
|
@ -709,9 +672,7 @@ var Pontoon = (function (my) {
|
|||
return chart.data.datasets
|
||||
.map(function (dataset) {
|
||||
var disabled = dataset.hidden ? 'disabled' : '';
|
||||
var color =
|
||||
dataset.borderColor ||
|
||||
dataset.backgroundColor;
|
||||
var color = dataset.borderColor || dataset.backgroundColor;
|
||||
|
||||
return (
|
||||
'<li class="' +
|
||||
|
|
|
@ -18,9 +18,7 @@ $(function () {
|
|||
var clipboard = new Clipboard('.machinery .machinery li');
|
||||
|
||||
clipboard.on('success', function (event) {
|
||||
var successMessage = $(
|
||||
'<span class="clipboard-success">Copied!</span>',
|
||||
),
|
||||
var successMessage = $('<span class="clipboard-success">Copied!</span>'),
|
||||
$trigger = $(event.trigger);
|
||||
|
||||
$('.clipboard-success').remove();
|
||||
|
@ -64,25 +62,17 @@ $(function () {
|
|||
data.source +
|
||||
'</span>' +
|
||||
(data.count
|
||||
? '<sup title="' +
|
||||
occurrencesTitle +
|
||||
'">' +
|
||||
data.count +
|
||||
'</sup>'
|
||||
? '<sup title="' + occurrencesTitle + '">' + data.count + '</sup>'
|
||||
: '') +
|
||||
'</a></li>',
|
||||
);
|
||||
|
||||
if (data.quality && sources.siblings('.stress').length === 0) {
|
||||
sources.prepend(
|
||||
'<span class="stress">' + data.quality + '</span>',
|
||||
);
|
||||
sources.prepend('<span class="stress">' + data.quality + '</span>');
|
||||
}
|
||||
} else {
|
||||
var originalTextForDiff = originalText;
|
||||
originalText = originalText
|
||||
? diff(original, originalTextForDiff)
|
||||
: '';
|
||||
originalText = originalText ? diff(original, originalTextForDiff) : '';
|
||||
|
||||
var li = $(
|
||||
'<li class="suggestion"' +
|
||||
|
@ -107,11 +97,7 @@ $(function () {
|
|||
data.source +
|
||||
'</span>' +
|
||||
(data.count
|
||||
? '<sup title="' +
|
||||
occurrencesTitle +
|
||||
'">' +
|
||||
data.count +
|
||||
'</sup>'
|
||||
? '<sup title="' + occurrencesTitle + '">' + data.count + '</sup>'
|
||||
: '') +
|
||||
'</a>' +
|
||||
'</li>' +
|
||||
|
@ -135,8 +121,7 @@ $(function () {
|
|||
'</li>',
|
||||
);
|
||||
ul.append(li);
|
||||
sourcesMap[data.original + data.translation] =
|
||||
li.find('.sources');
|
||||
sourcesMap[data.original + data.translation] = li.find('.sources');
|
||||
if (data.source === 'Translation memory') {
|
||||
preferred++;
|
||||
} else {
|
||||
|
@ -173,12 +158,8 @@ $(function () {
|
|||
listitems.sort(function (a, b) {
|
||||
var stressA = $(a).find('.stress'),
|
||||
stressB = $(b).find('.stress'),
|
||||
valA = stressA.length
|
||||
? parseInt(stressA.html().split('%')[0])
|
||||
: 0,
|
||||
valB = stressB.length
|
||||
? parseInt(stressB.html().split('%')[0])
|
||||
: 0,
|
||||
valA = stressA.length ? parseInt(stressA.html().split('%')[0]) : 0,
|
||||
valB = stressB.length ? parseInt(stressB.html().split('%')[0]) : 0,
|
||||
sourceA = getTranslationSource(a),
|
||||
sourceB = getTranslationSource(b);
|
||||
|
||||
|
@ -202,11 +183,7 @@ $(function () {
|
|||
sortedItems = sources.sort(function (a, b) {
|
||||
var sourceA = sourceMap[$(a).find('span').text()],
|
||||
sourceB = sourceMap[$(b).find('span').text()];
|
||||
return sourceA > sourceB
|
||||
? 1
|
||||
: sourceA < sourceB
|
||||
? -1
|
||||
: 0;
|
||||
return sourceA > sourceB ? 1 : sourceA < sourceB ? -1 : 0;
|
||||
});
|
||||
|
||||
$sourcesList.children('li').remove();
|
||||
|
@ -230,7 +207,8 @@ $(function () {
|
|||
function complete(jqXHR, status) {
|
||||
if (status !== 'abort') {
|
||||
requests--;
|
||||
tab.find('.count')
|
||||
tab
|
||||
.find('.count')
|
||||
.find('.preferred')
|
||||
.html(preferred)
|
||||
.toggle(preferred > 0)
|
||||
|
@ -441,11 +419,7 @@ $(function () {
|
|||
*/
|
||||
function getPlaceableMarkup(title, replacement) {
|
||||
return (
|
||||
'<mark class="placeable" title="' +
|
||||
title +
|
||||
'">' +
|
||||
replacement +
|
||||
'</mark>'
|
||||
'<mark class="placeable" title="' + title + '">' + replacement + '</mark>'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -468,11 +442,7 @@ $(function () {
|
|||
/* Special spaces */
|
||||
// Pontoon.doNotRender() replaces \u00A0 with
|
||||
string = markPlaceable(string, / /gi, 'Non-breaking space');
|
||||
string = markPlaceable(
|
||||
string,
|
||||
/[\u202F]/gi,
|
||||
'Narrow non-breaking space',
|
||||
);
|
||||
string = markPlaceable(string, /[\u202F]/gi, 'Narrow non-breaking space');
|
||||
string = markPlaceable(string, /[\u2009]/gi, 'Thin space');
|
||||
|
||||
/* Multiple spaces */
|
||||
|
|
|
@ -5,15 +5,11 @@ $(function () {
|
|||
$form.find('.errors p').css('visibility', 'hidden');
|
||||
|
||||
if (!locales) {
|
||||
$form
|
||||
.find('.locale-selector .errors p')
|
||||
.css('visibility', 'visible');
|
||||
$form.find('.locale-selector .errors p').css('visibility', 'visible');
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
$form
|
||||
.find('.message-wrapper .errors p')
|
||||
.css('visibility', 'visible');
|
||||
$form.find('.message-wrapper .errors p').css('visibility', 'visible');
|
||||
}
|
||||
|
||||
return locales && message;
|
||||
|
|
|
@ -9,7 +9,6 @@ directly, as well as to write its own changes back.
|
|||
|
||||
This document describes that sync process in detail.
|
||||
|
||||
|
||||
## Triggering a Sync
|
||||
|
||||
Pontoon is assumed to run a sync once an hour, although this is configurable.
|
||||
|
@ -18,7 +17,6 @@ disabled within the admin interface and schedules a sync task for each one.
|
|||
Sync tasks are executed in parallel, using [Celery](http://www.celeryproject.org/)
|
||||
to manage the worker queue.
|
||||
|
||||
|
||||
## Syncing a Project
|
||||
|
||||
Syncing an individual project is split into two tasks. The first one is syncing
|
||||
|
@ -46,7 +44,6 @@ The second step is syncing translations:
|
|||
changes, no commit is made.
|
||||
- Clean up leftover information in the database.
|
||||
|
||||
|
||||
## Comparing Entities
|
||||
|
||||
The heart of the syncing process is comparing an entity stored in Pontoon's
|
||||
|
@ -70,7 +67,6 @@ The actual comparison logic goes something like this:
|
|||
|
||||
![](./sync-process-diagram.png)
|
||||
|
||||
|
||||
## Executing Changes
|
||||
|
||||
Entity comparison produces a Changeset, which is used to make the necessary
|
||||
|
@ -80,24 +76,24 @@ Changesets can perform 4 different operations on an entity:
|
|||
|
||||
**Update Pontoon from VCS**
|
||||
|
||||
 Add a translation from VCS to Pontoon if necessary. Existing translations
|
||||
that match the VCS translation are re-used, and all non-matching translations
|
||||
are marked as unapproved.
|
||||
 Add a translation from VCS to Pontoon if necessary. Existing translations
|
||||
that match the VCS translation are re-used, and all non-matching translations
|
||||
are marked as unapproved.
|
||||
|
||||
**Update VCS from Pontoon**
|
||||
|
||||
 Add a translation from Pontoon to VCS, overwriting the existing translation
|
||||
if it exists.
|
||||
 Add a translation from Pontoon to VCS, overwriting the existing translation
|
||||
if it exists.
|
||||
|
||||
**Create New Entity in Pontoon**
|
||||
|
||||
 Create a new entity in the Pontoon database, including the VCS translation if
|
||||
it is present.
|
||||
 Create a new entity in the Pontoon database, including the VCS translation if
|
||||
it is present.
|
||||
|
||||
**Obsolete Pontoon Entity**
|
||||
|
||||
 Mark an entity in the database as obsolete, due to it not existing in VCS.
|
||||
The entity will no longer appear on the website.
|
||||
 Mark an entity in the database as obsolete, due to it not existing in VCS.
|
||||
The entity will no longer appear on the website.
|
||||
|
||||
When possible, Changesets perform database operations in bulk in order to speed
|
||||
up the syncing process.
|
||||
|
|
|
@ -27,15 +27,12 @@ var Pontoon = (function (my) {
|
|||
'type0-0-1': 'regexp',
|
||||
'value0-0-1': '^' + locale + ' / ',
|
||||
resolution: '---',
|
||||
include_fields:
|
||||
'id,summary,last_change_time,assigned_to',
|
||||
include_fields: 'id,summary,last_change_time,assigned_to',
|
||||
},
|
||||
success: function (data) {
|
||||
if (data.bugs.length) {
|
||||
data.bugs.sort(function (l, r) {
|
||||
return l.last_change_time < r.last_change_time
|
||||
? 1
|
||||
: -1;
|
||||
return l.last_change_time < r.last_change_time ? 1 : -1;
|
||||
});
|
||||
|
||||
var tbody = $('<tbody>'),
|
||||
|
@ -71,9 +68,7 @@ var Pontoon = (function (my) {
|
|||
$('<td>', {
|
||||
class: 'last-changed',
|
||||
datetime: bug.last_change_time,
|
||||
html: formatter.format(
|
||||
new Date(bug.last_change_time),
|
||||
),
|
||||
html: formatter.format(new Date(bug.last_change_time)),
|
||||
}).appendTo(tr);
|
||||
|
||||
$('<td>', {
|
||||
|
@ -106,10 +101,7 @@ var Pontoon = (function (my) {
|
|||
}
|
||||
},
|
||||
error: function (error) {
|
||||
if (
|
||||
error.status === 0 &&
|
||||
error.statusText !== 'abort'
|
||||
) {
|
||||
if (error.status === 0 && error.statusText !== 'abort') {
|
||||
errorCallback(
|
||||
'Oops, something went wrong. We were unable to load the bugs. Please try again later.',
|
||||
);
|
||||
|
@ -130,14 +122,11 @@ var Pontoon = (function (my) {
|
|||
}
|
||||
|
||||
function getNumber(el) {
|
||||
return parseInt(
|
||||
$(el).find('.id').text().replace(/,/g, ''),
|
||||
);
|
||||
return parseInt($(el).find('.id').text().replace(/,/g, ''));
|
||||
}
|
||||
|
||||
function getTime(el) {
|
||||
var date =
|
||||
$(el).find('.last-changed').attr('datetime') || 0;
|
||||
var date = $(el).find('.last-changed').attr('datetime') || 0;
|
||||
return new Date(date).getTime();
|
||||
}
|
||||
|
||||
|
@ -163,9 +152,7 @@ var Pontoon = (function (my) {
|
|||
|
||||
// Sort by alphabetical order
|
||||
} else {
|
||||
return (
|
||||
getString(a).localeCompare(getString(b)) * dir
|
||||
);
|
||||
return getString(a).localeCompare(getString(b)) * dir;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -28,11 +28,7 @@ $(function () {
|
|||
|
||||
if ($(this).parents('.general').length > 0) {
|
||||
$form.append(
|
||||
inputHidden(
|
||||
'general-' + value,
|
||||
itemId,
|
||||
'permissions-form-item',
|
||||
),
|
||||
inputHidden('general-' + value, itemId, 'permissions-form-item'),
|
||||
);
|
||||
} else {
|
||||
// We have to retrieve an index of parent project locale form
|
||||
|
@ -41,9 +37,7 @@ $(function () {
|
|||
.data('index');
|
||||
$form.append(
|
||||
inputHidden(
|
||||
'project-locale-' +
|
||||
localeProjectIndex +
|
||||
'-translators',
|
||||
'project-locale-' + localeProjectIndex + '-translators',
|
||||
itemId,
|
||||
'permissions-form-item',
|
||||
),
|
||||
|
@ -124,15 +118,13 @@ $(function () {
|
|||
|
||||
// Copy Translators from the General section
|
||||
// Reverse selector order to keep presentation order (prepend)
|
||||
$(
|
||||
$('.permissions-groups.general .translators li').get().reverse(),
|
||||
).each(function () {
|
||||
$($('.permissions-groups.general .translators li').get().reverse()).each(
|
||||
function () {
|
||||
$permsForm
|
||||
.find(
|
||||
'.user.available li[data-id="' + $(this).data('id') + '"]',
|
||||
)
|
||||
.find('.user.available li[data-id="' + $(this).data('id') + '"]')
|
||||
.click();
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Scroll to the right project locale
|
||||
$('html, body').animate(
|
||||
|
|
|
@ -58,8 +58,7 @@ var Pontoon = (function (my) {
|
|||
var show = condition;
|
||||
|
||||
if (type === 'locale-projects') {
|
||||
show =
|
||||
condition && $('.items td.enabled:visible').length > 0;
|
||||
show = condition && $('.items td.enabled:visible').length > 0;
|
||||
} else if (type === 'team') {
|
||||
show =
|
||||
condition &&
|
||||
|
@ -80,17 +79,10 @@ var Pontoon = (function (my) {
|
|||
projects: projects,
|
||||
},
|
||||
success: function () {
|
||||
Pontoon.endLoader(
|
||||
'New ' + type + ' request sent.',
|
||||
'',
|
||||
5000,
|
||||
);
|
||||
Pontoon.endLoader('New ' + type + ' request sent.', '', 5000);
|
||||
},
|
||||
error: function () {
|
||||
Pontoon.endLoader(
|
||||
'Oops, something went wrong.',
|
||||
'error',
|
||||
);
|
||||
Pontoon.endLoader('Oops, something went wrong.', 'error');
|
||||
},
|
||||
complete: function () {
|
||||
$('.items td.check').removeClass('enabled');
|
||||
|
@ -119,10 +111,7 @@ var Pontoon = (function (my) {
|
|||
if (res.status === 409) {
|
||||
Pontoon.endLoader(res.responseText, 'error');
|
||||
} else {
|
||||
Pontoon.endLoader(
|
||||
'Oops, something went wrong.',
|
||||
'error',
|
||||
);
|
||||
Pontoon.endLoader('Oops, something went wrong.', 'error');
|
||||
}
|
||||
},
|
||||
complete: function () {
|
||||
|
@ -139,9 +128,7 @@ var Pontoon = (function (my) {
|
|||
|
||||
$(function () {
|
||||
var container = $('#main .container');
|
||||
var type = $('#server').data('locale-projects')
|
||||
? 'locale-projects'
|
||||
: 'team';
|
||||
var type = $('#server').data('locale-projects') ? 'locale-projects' : 'team';
|
||||
|
||||
// Switch between available projects/teams and projects/team to request
|
||||
container.on('click', '.controls .request-toggle', function (e) {
|
||||
|
@ -226,33 +213,19 @@ $(function () {
|
|||
return $(element).siblings('.name').data('slug');
|
||||
})
|
||||
.get();
|
||||
locale =
|
||||
$('#server').data('locale') || Pontoon.getSelectedLocale();
|
||||
locale = $('#server').data('locale') || Pontoon.getSelectedLocale();
|
||||
|
||||
Pontoon.requestItem.requestProjects(
|
||||
locale,
|
||||
projects,
|
||||
'projects',
|
||||
);
|
||||
Pontoon.requestItem.requestProjects(locale, projects, 'projects');
|
||||
|
||||
$(this).removeClass('confirmed').html('Request new projects');
|
||||
}
|
||||
|
||||
// Requesting from project page
|
||||
else if (
|
||||
type === 'locale-projects' &&
|
||||
$('body').hasClass('project')
|
||||
) {
|
||||
else if (type === 'locale-projects' && $('body').hasClass('project')) {
|
||||
var project = $('#server').data('project');
|
||||
locale = $('.items td.radio.enabled')
|
||||
.siblings('.name')
|
||||
.data('slug');
|
||||
locale = $('.items td.radio.enabled').siblings('.name').data('slug');
|
||||
|
||||
Pontoon.requestItem.requestProjects(
|
||||
locale,
|
||||
[project],
|
||||
'language',
|
||||
);
|
||||
Pontoon.requestItem.requestProjects(locale, [project], 'language');
|
||||
|
||||
$(this).removeClass('confirmed').html('Request new language');
|
||||
} else if (type === 'team') {
|
||||
|
|
|
@ -23,9 +23,7 @@ test('TagResourceSearch renders select', () => {
|
|||
test('TagResourceSearch onChange', async () => {
|
||||
const search = jest.fn();
|
||||
const type = jest.fn();
|
||||
const wrapper = mount(
|
||||
<TagResourceSearch onSearch={search} onType={type} />,
|
||||
);
|
||||
const wrapper = mount(<TagResourceSearch onSearch={search} onType={type} />);
|
||||
|
||||
wrapper.find('input').simulate('change', { target: { value: 'FOO' } });
|
||||
wrapper.find('select').simulate('change', { target: { value: 'BAR' } });
|
||||
|
|
|
@ -67,9 +67,7 @@ export function isSameOrigin(url) {
|
|||
*/
|
||||
export function post(url, data) {
|
||||
// this is a bit sketchy but the only afaict way due to session_csrf
|
||||
const csrf = document.querySelector(
|
||||
'input[name=csrfmiddlewaretoken]',
|
||||
).value;
|
||||
const csrf = document.querySelector('input[name=csrfmiddlewaretoken]').value;
|
||||
|
||||
const init = {
|
||||
body: asFormData(data),
|
||||
|
|
|
@ -41,8 +41,7 @@ export function CheckboxTable({ data, onSubmit, submitMessage }) {
|
|||
const pruned = prune(checked, visible.current);
|
||||
// some rows can be empty strings if there are more visible rows than resources
|
||||
const some = pruned.size > 0;
|
||||
const all =
|
||||
some && pruned.size === visible.current.filter(Boolean).length;
|
||||
const all = some && pruned.size === visible.current.filter(Boolean).length;
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
|
@ -59,11 +58,7 @@ export function CheckboxTable({ data, onSubmit, submitMessage }) {
|
|||
visible.current[item.viewIndex] = name;
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
checked={checked.has(name)}
|
||||
name={name}
|
||||
onChange={selectOne}
|
||||
/>
|
||||
<Checkbox checked={checked.has(name)} name={name} onChange={selectOne} />
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -72,9 +72,7 @@ test('CheckboxTable sort change', () => {
|
|||
// removed if they are no longer visible
|
||||
|
||||
const table = mount(
|
||||
<CheckboxTable
|
||||
data={[['1'], ['2'], ['3'], ['4'], ['5'], ['6'], ['7']]}
|
||||
/>,
|
||||
<CheckboxTable data={[['1'], ['2'], ['3'], ['4'], ['5'], ['6'], ['7']]} />,
|
||||
);
|
||||
|
||||
// Select '2' and '3'
|
||||
|
@ -102,9 +100,7 @@ test('CheckboxTable sort change', () => {
|
|||
|
||||
test('CheckboxTable page change', () => {
|
||||
const table = mount(
|
||||
<CheckboxTable
|
||||
data={[['1'], ['2'], ['3'], ['4'], ['5'], ['6'], ['7']]}
|
||||
/>,
|
||||
<CheckboxTable data={[['1'], ['2'], ['3'], ['4'], ['5'], ['6'], ['7']]} />,
|
||||
);
|
||||
|
||||
expect(table.find('input[name="2"]')).toHaveLength(1);
|
||||
|
@ -121,9 +117,7 @@ test('CheckboxTable page change', () => {
|
|||
|
||||
test('CheckboxTable resize', () => {
|
||||
const table = mount(
|
||||
<CheckboxTable
|
||||
data={[['1'], ['2'], ['3'], ['4'], ['5'], ['6'], ['7']]}
|
||||
/>,
|
||||
<CheckboxTable data={[['1'], ['2'], ['3'], ['4'], ['5'], ['6'], ['7']]} />,
|
||||
);
|
||||
|
||||
// Resize to 10 rows
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/* global module */
|
||||
/* eslint-env node */
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
|
|
|
@ -45,7 +45,6 @@
|
|||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
## Code architecture
|
||||
|
||||
### Where code goes
|
||||
|
@ -74,6 +73,7 @@ Of course, more can be added if needed. For example, modules with a high number
|
|||
To import code from further away than the parent directory,
|
||||
use paths starting with `~` to refer to the root of the `src/` directory.
|
||||
For example:
|
||||
|
||||
```js
|
||||
import { SearchBox } from '~/modules/search';
|
||||
```
|
||||
|
@ -111,7 +111,6 @@ As far as we know, it is not possible to make that work in Chrome or Edge. This
|
|||
|
||||
If you can't turn on websockets, you will see errors in the console (that's not very impacting) and you'll have to reload your Django server regularly, because polling requests don't close, and after so many web page reloads, the Django process won't be able to accept new requests.
|
||||
|
||||
|
||||
## Dependencies
|
||||
|
||||
We manage our JavaScript dependencies with `npm`.
|
||||
|
@ -134,7 +133,6 @@ You might want to remove the `translate/node_modules` folder after you've run th
|
|||
(and the `package.json` and `package-lock.json` files have been updated)
|
||||
and before rebuilding the image, to reduce the size of the docker context.
|
||||
|
||||
|
||||
## Type checking
|
||||
|
||||
Our code uses TypeScript for type-checking the production code. Tests are not type-checked in general, which allows for smaller test fixtures. Visit the [TypeScript documentation](https://www.typescriptlang.org/docs) to learn more about TypeScript.
|
||||
|
@ -143,7 +141,6 @@ To check for TypeScript errors locally, run:
|
|||
|
||||
$ make types
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
Tests are run using [`jest`](https://facebook.github.io/jest/). We use [`enzyme`](http://airbnb.io/enzyme/docs/api/) for mounting React components and [`sinon`](http://sinonjs.org/) for mocking.
|
||||
|
@ -172,7 +169,6 @@ it('does something specific', () => {
|
|||
|
||||
We use `jest`'s [`expect`](https://facebook.github.io/jest/docs/en/expect.html) assertion tool.
|
||||
|
||||
|
||||
## Localization
|
||||
|
||||
The user interface is localized using [Fluent](https://projectfluent.org/) and the library `fluent-react`. Fluent allows to move the complexity of handling translated content from the developer to the translator. Thus, when using it, you should care only about the English version, and trust that the localizers will know what to do with your string in their language.
|
||||
|
@ -186,11 +182,13 @@ That would give:
|
|||
```js
|
||||
class Editor extends React.Component {
|
||||
render() {
|
||||
return <div>
|
||||
<Localized id="entitydetails-Editor--button-update">
|
||||
return (
|
||||
<div>
|
||||
<Localized id='entitydetails-Editor--button-update'>
|
||||
<button>Update</button>
|
||||
</Localized>
|
||||
</div>;
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -206,7 +204,7 @@ Those files use the FTL format. In its simplest form, a string in such a file (c
|
|||
|
||||
### Semantic identifiers
|
||||
|
||||
Fluent uses the concept of a *social contract* between developer and localizers. This contract is established by the selection of a unique identifier, called `l10n-id`, which carries a promise of being used in a particular place to carry a particular meaning.
|
||||
Fluent uses the concept of a _social contract_ between developer and localizers. This contract is established by the selection of a unique identifier, called `l10n-id`, which carries a promise of being used in a particular place to carry a particular meaning.
|
||||
|
||||
You should consider the `l10n-id` as a variable name. If the meaning of the content changes, then you should also change the ID. This will notify localizers that the content is different from before and that a new translation is needed. However, if you make minor changes (fix a typo, make a change that keeps the same meaning) you should instead keep the same ID.
|
||||
|
||||
|
@ -220,11 +218,10 @@ In Fluent, the developer is not to be bothered with inner logic and complexity t
|
|||
|
||||
In order to easily verify that a string is effectively localized, you can turn on pseudo-localization. To do that, add `pseudolocalization=accented` or `pseudolocalization=bidi` to the URL, then refresh the page.
|
||||
|
||||
Pseudo-localization turns every supported string into a different version of itself. We support two modes: "accented" (transforms "Accented English" into "Ȧȧƈƈḗḗƞŧḗḗḓ Ḗḗƞɠŀīīşħ") and "bidi" (transforms "Reversed English" into "ᴚǝʌǝɹsǝp Ǝuƃʅısɥ"). Because only strings that are actually localized (they exist in our reference en-US FTL file and they are properly set in a `<Localized>` component) get that transformation, it is easy to spot which strings are *not* properly localized in the interface.
|
||||
Pseudo-localization turns every supported string into a different version of itself. We support two modes: "accented" (transforms "Accented English" into "Ȧȧƈƈḗḗƞŧḗḗḓ Ḗḗƞɠŀīīşħ") and "bidi" (transforms "Reversed English" into "ᴚǝʌǝɹsǝp Ǝuƃʅısɥ"). Because only strings that are actually localized (they exist in our reference en-US FTL file and they are properly set in a `<Localized>` component) get that transformation, it is easy to spot which strings are _not_ properly localized in the interface.
|
||||
|
||||
You can read [more about pseudo-localization on Wikipedia](https://en.wikipedia.org/wiki/Pseudolocalization).
|
||||
|
||||
|
||||
## Development resources
|
||||
|
||||
### Integration between Django and React
|
||||
|
|
|
@ -47,9 +47,6 @@
|
|||
"start": "rollup -c --watch",
|
||||
"build": "rollup -c",
|
||||
"build:prod": "rollup -c --environment BUILD:production",
|
||||
"lint": "eslint 'src/**/*.{js,ts,tsx}'",
|
||||
"prettier": "prettier --write src/",
|
||||
"check-prettier": "prettier --check src/",
|
||||
"test": "jest",
|
||||
"types": "tsc --noEmit"
|
||||
}
|
||||
|
|
|
@ -104,17 +104,12 @@ class App extends React.Component<InternalProps> {
|
|||
<AddonPromotion />
|
||||
<header>
|
||||
<Navigation />
|
||||
<ResourceProgress
|
||||
stats={state.stats}
|
||||
parameters={state.parameters}
|
||||
/>
|
||||
<ResourceProgress stats={state.stats} parameters={state.parameters} />
|
||||
<ProjectInfo
|
||||
projectSlug={state.parameters.project}
|
||||
project={state.project}
|
||||
/>
|
||||
<notification.NotificationPanel
|
||||
notification={state.notification}
|
||||
/>
|
||||
<notification.NotificationPanel notification={state.notification} />
|
||||
<UserControls />
|
||||
</header>
|
||||
<section className='main-content'>
|
||||
|
|
|
@ -38,9 +38,7 @@ export default class APIBase {
|
|||
|
||||
isObject: (obj: any) => boolean = function (obj: any) {
|
||||
return (
|
||||
obj === Object(obj) &&
|
||||
!Array.isArray(obj) &&
|
||||
typeof obj !== 'function'
|
||||
obj === Object(obj) && !Array.isArray(obj) && typeof obj !== 'function'
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -95,9 +93,7 @@ export default class APIBase {
|
|||
const newObj: any = {};
|
||||
|
||||
Object.keys(results).forEach((key) => {
|
||||
newObj[this.toCamelCase(key)] = this.keysToCamelCase(
|
||||
results[key],
|
||||
);
|
||||
newObj[this.toCamelCase(key)] = this.keysToCamelCase(results[key]);
|
||||
});
|
||||
|
||||
return newObj;
|
||||
|
|
|
@ -127,12 +127,7 @@ export default class EntityAPI extends APIBase {
|
|||
const headers = new Headers();
|
||||
headers.append('X-Requested-With', 'XMLHttpRequest');
|
||||
|
||||
const results = await this.fetch(
|
||||
'/get-history/',
|
||||
'GET',
|
||||
payload,
|
||||
headers,
|
||||
);
|
||||
const results = await this.fetch('/get-history/', 'GET', payload, headers);
|
||||
|
||||
return this.keysToCamelCase(results);
|
||||
}
|
||||
|
|
|
@ -115,9 +115,7 @@ export default class MachineryAPI extends APIBase {
|
|||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{ sources: ['google-translate'], original: source, translation },
|
||||
];
|
||||
return [{ sources: ['google-translate'], original: source, translation }];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -171,9 +169,7 @@ export default class MachineryAPI extends APIBase {
|
|||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{ sources: ['systran-translate'], original: source, translation },
|
||||
];
|
||||
return [{ sources: ['systran-translate'], original: source, translation }];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -91,12 +91,7 @@ export default class TranslationAPI extends APIBase {
|
|||
}
|
||||
|
||||
unapprove(id: number, resource: string): Promise<any> {
|
||||
return this._changeStatus(
|
||||
'/translations/unapprove/',
|
||||
id,
|
||||
resource,
|
||||
false,
|
||||
);
|
||||
return this._changeStatus('/translations/unapprove/', id, resource, false);
|
||||
}
|
||||
|
||||
reject(id: number, resource: string): Promise<any> {
|
||||
|
@ -104,12 +99,7 @@ export default class TranslationAPI extends APIBase {
|
|||
}
|
||||
|
||||
unreject(id: number, resource: string): Promise<any> {
|
||||
return this._changeStatus(
|
||||
'/translations/unreject/',
|
||||
id,
|
||||
resource,
|
||||
false,
|
||||
);
|
||||
return this._changeStatus('/translations/unreject/', id, resource, false);
|
||||
}
|
||||
|
||||
delete(id: number): Promise<any> {
|
||||
|
|
|
@ -93,12 +93,7 @@ export default class UserAPI extends APIBase {
|
|||
headers.append('X-Requested-With', 'XMLHttpRequest');
|
||||
headers.append('X-CSRFToken', csrfToken);
|
||||
|
||||
return await this.fetch(
|
||||
'/update-tour-status/',
|
||||
'POST',
|
||||
payload,
|
||||
headers,
|
||||
);
|
||||
return await this.fetch('/update-tour-status/', 'POST', payload, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -169,9 +169,7 @@ export default function AddComment(props: Props): React.ReactElement<'div'> {
|
|||
const commentEditorSpan = document.querySelector(
|
||||
'.comments-list .add-comment .comment-editor p span',
|
||||
);
|
||||
const commentEditorSpanHeight = !(
|
||||
commentEditorSpan instanceof HTMLElement
|
||||
)
|
||||
const commentEditorSpanHeight = !(commentEditorSpan instanceof HTMLElement)
|
||||
? 0
|
||||
: commentEditorSpan.offsetHeight;
|
||||
|
||||
|
@ -193,8 +191,7 @@ export default function AddComment(props: Props): React.ReactElement<'div'> {
|
|||
const suggestionsHeight = el.clientHeight + suggestionsHeightAdjustment;
|
||||
const teamCommentsOverflow = !teamCommentsRect
|
||||
? false
|
||||
: setTop + el.clientHeight - tabIndexHeight >
|
||||
teamCommentsRect.height;
|
||||
: setTop + el.clientHeight - tabIndexHeight > teamCommentsRect.height;
|
||||
|
||||
if (
|
||||
(teamCommentsActive && teamCommentsOverflow) ||
|
||||
|
@ -278,15 +275,13 @@ export default function AddComment(props: Props): React.ReactElement<'div'> {
|
|||
switch (event.key) {
|
||||
case 'ArrowDown': {
|
||||
event.preventDefault();
|
||||
const prevIndex =
|
||||
index >= suggestedUsers.length - 1 ? 0 : index + 1;
|
||||
const prevIndex = index >= suggestedUsers.length - 1 ? 0 : index + 1;
|
||||
setIndex(prevIndex);
|
||||
break;
|
||||
}
|
||||
case 'ArrowUp': {
|
||||
event.preventDefault();
|
||||
const nextIndex =
|
||||
index <= 0 ? suggestedUsers.length - 1 : index - 1;
|
||||
const nextIndex = index <= 0 ? suggestedUsers.length - 1 : index - 1;
|
||||
setIndex(nextIndex);
|
||||
break;
|
||||
}
|
||||
|
@ -325,9 +320,7 @@ export default function AddComment(props: Props): React.ReactElement<'div'> {
|
|||
}
|
||||
};
|
||||
|
||||
const handleMentionsMouseDown = (
|
||||
event: React.MouseEvent<HTMLDivElement>,
|
||||
) => {
|
||||
const handleMentionsMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
event.preventDefault();
|
||||
if (target !== null) {
|
||||
const suggestedUserIndex = suggestedUsers.indexOf(
|
||||
|
@ -359,8 +352,7 @@ export default function AddComment(props: Props): React.ReactElement<'div'> {
|
|||
const wordBefore = Editor.before(editor, start, { unit: 'word' });
|
||||
const before = wordBefore && Editor.before(editor, wordBefore);
|
||||
const beforeRange = before && Editor.range(editor, before, start);
|
||||
const beforeText =
|
||||
beforeRange && Editor.string(editor, beforeRange);
|
||||
const beforeText = beforeRange && Editor.string(editor, beforeRange);
|
||||
// Unicode property escapes allow for matching non-ASCII characters
|
||||
const beforeMatch =
|
||||
beforeText && beforeText.match(/^@((\p{L}|\p{N}|\p{P})+)$/u);
|
||||
|
@ -434,22 +426,14 @@ export default function AddComment(props: Props): React.ReactElement<'div'> {
|
|||
|
||||
const removeStyleForHover = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
event.preventDefault();
|
||||
event.currentTarget.children[index].className =
|
||||
'mention active-mention';
|
||||
event.currentTarget.children[index].className = 'mention active-mention';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='comment add-comment'>
|
||||
<UserAvatar
|
||||
username={user.username}
|
||||
imageUrl={user.gravatarURLSmall}
|
||||
/>
|
||||
<UserAvatar username={user.username} imageUrl={user.gravatarURLSmall} />
|
||||
<div className='container'>
|
||||
<Slate
|
||||
editor={editor}
|
||||
value={value}
|
||||
onChange={handleEditorOnChange}
|
||||
>
|
||||
<Slate editor={editor} value={value} onChange={handleEditorOnChange}>
|
||||
<Localized
|
||||
id='comments-AddComment--input'
|
||||
attrs={{ placeholder: true }}
|
||||
|
@ -460,11 +444,7 @@ export default function AddComment(props: Props): React.ReactElement<'div'> {
|
|||
dir='auto'
|
||||
placeholder={`Write a comment…`}
|
||||
renderElement={renderElement}
|
||||
onKeyDown={
|
||||
target
|
||||
? handleMentionsKeyDown
|
||||
: handleEditorKeyDown
|
||||
}
|
||||
onKeyDown={target ? handleMentionsKeyDown : handleEditorKeyDown}
|
||||
/>
|
||||
</Localized>
|
||||
{target && suggestedUsers.length > 0 && (
|
||||
|
@ -479,9 +459,7 @@ export default function AddComment(props: Props): React.ReactElement<'div'> {
|
|||
<div
|
||||
key={suggestedUser}
|
||||
className={
|
||||
i === index
|
||||
? 'mention active-mention'
|
||||
: 'mention'
|
||||
i === index ? 'mention active-mention' : 'mention'
|
||||
}
|
||||
onMouseDown={handleMentionsMouseDown}
|
||||
>
|
||||
|
@ -491,18 +469,14 @@ export default function AddComment(props: Props): React.ReactElement<'div'> {
|
|||
>
|
||||
<span className='user-avatar'>
|
||||
<img
|
||||
src={getUserGravatar(
|
||||
suggestedUser,
|
||||
)}
|
||||
src={getUserGravatar(suggestedUser)}
|
||||
alt='User Avatar'
|
||||
width='22'
|
||||
height='22'
|
||||
/>
|
||||
</span>
|
||||
</Localized>
|
||||
<span className='name'>
|
||||
{suggestedUser}
|
||||
</span>
|
||||
<span className='name'>{suggestedUser}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
@ -575,11 +549,7 @@ const insertMention = (editor, character, users) => {
|
|||
|
||||
const MentionElement = ({ attributes, children, element }) => {
|
||||
return (
|
||||
<span
|
||||
{...attributes}
|
||||
contentEditable={false}
|
||||
className='mention-element'
|
||||
>
|
||||
<span {...attributes} contentEditable={false} className='mention-element'>
|
||||
@{element.character}
|
||||
{children}
|
||||
</span>
|
||||
|
|
|
@ -44,9 +44,7 @@ export default function Comment(props: Props): null | React.ReactElement<'li'> {
|
|||
href={`/contributors/${comment.username}`}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
onClick={(e: React.MouseEvent) =>
|
||||
e.stopPropagation()
|
||||
}
|
||||
onClick={(e: React.MouseEvent) => e.stopPropagation()}
|
||||
>
|
||||
{comment.author}
|
||||
</a>
|
||||
|
|
|
@ -64,11 +64,7 @@ export default function CommentsList(props: Props): React.ReactElement<'div'> {
|
|||
<h2 className='title'>PINNED COMMENTS</h2>
|
||||
</Localized>
|
||||
|
||||
<ul>
|
||||
{pinnedComments.map((comment) =>
|
||||
renderComment(comment),
|
||||
)}
|
||||
</ul>
|
||||
<ul>{pinnedComments.map((comment) => renderComment(comment))}</ul>
|
||||
{!hideAllComments ? (
|
||||
<Localized id='comments-CommentsList--all-comments'>
|
||||
<h2 className='title'>ALL COMMENTS</h2>
|
||||
|
@ -77,9 +73,7 @@ export default function CommentsList(props: Props): React.ReactElement<'div'> {
|
|||
</section>
|
||||
) : null}
|
||||
<section className='all-comments'>
|
||||
<ul>
|
||||
{unpinnedComments.map((comment) => renderComment(comment))}
|
||||
</ul>
|
||||
<ul>{unpinnedComments.map((comment) => renderComment(comment))}</ul>
|
||||
{!canComment ? null : (
|
||||
<AddComment
|
||||
parameters={parameters}
|
||||
|
|
|
@ -275,16 +275,12 @@ export function sendTranslation(
|
|||
// The translation that was provided is the same as an existing
|
||||
// translation for that entity.
|
||||
dispatch(
|
||||
notification.actions.add(
|
||||
notification.messages.SAME_TRANSLATION,
|
||||
),
|
||||
notification.actions.add(notification.messages.SAME_TRANSLATION),
|
||||
);
|
||||
} else if (content.status) {
|
||||
// Notify the user of the change that happened.
|
||||
dispatch(
|
||||
notification.actions.add(
|
||||
notification.messages.TRANSLATION_SAVED,
|
||||
),
|
||||
notification.actions.add(notification.messages.TRANSLATION_SAVED),
|
||||
);
|
||||
|
||||
// Ignore existing unsavedchanges because they are saved now.
|
||||
|
|
|
@ -28,9 +28,7 @@ describe('<EditorMainAction>', () => {
|
|||
const updateStatusMock = sinon.spy();
|
||||
sinon.stub(history.actions, 'updateStatus').returns(updateStatusMock);
|
||||
sinon.stub(user.selectors, 'isTranslator').returns(true);
|
||||
sinon
|
||||
.stub(editor.selectors, 'sameExistingTranslation')
|
||||
.returns({ pk: 1 });
|
||||
sinon.stub(editor.selectors, 'sameExistingTranslation').returns({ pk: 1 });
|
||||
|
||||
const [wrapper] = createComponent();
|
||||
|
||||
|
|
|
@ -109,11 +109,7 @@ export default function EditorMainAction(
|
|||
}
|
||||
|
||||
return (
|
||||
<Localized
|
||||
id={btn.id}
|
||||
attrs={{ title: true }}
|
||||
elems={{ glyph: btn.glyph }}
|
||||
>
|
||||
<Localized id={btn.id} attrs={{ title: true }} elems={{ glyph: btn.glyph }}>
|
||||
<button
|
||||
className={btn.className}
|
||||
onClick={btn.action}
|
||||
|
|
|
@ -78,10 +78,7 @@ function MenuContent(props: Props) {
|
|||
<KeyboardShortcuts />
|
||||
{props.translationLengthHook}
|
||||
<div className='actions'>
|
||||
<Localized
|
||||
id='editor-EditorMenu--button-copy'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<Localized id='editor-EditorMenu--button-copy' attrs={{ title: true }}>
|
||||
<button
|
||||
className='action-copy'
|
||||
onClick={props.copyOriginalIntoEditor}
|
||||
|
@ -90,10 +87,7 @@ function MenuContent(props: Props) {
|
|||
COPY
|
||||
</button>
|
||||
</Localized>
|
||||
<Localized
|
||||
id='editor-EditorMenu--button-clear'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<Localized id='editor-EditorMenu--button-clear' attrs={{ title: true }}>
|
||||
<button
|
||||
className='action-clear'
|
||||
onClick={props.clearEditor}
|
||||
|
|
|
@ -39,8 +39,7 @@ export function EditorSettings({
|
|||
>
|
||||
<li
|
||||
className={
|
||||
'check-box' +
|
||||
(settings.runQualityChecks ? ' enabled' : '')
|
||||
'check-box' + (settings.runQualityChecks ? ' enabled' : '')
|
||||
}
|
||||
title='Run Translate Toolkit checks before submitting translations'
|
||||
onClick={() => toggleSetting('runQualityChecks')}
|
||||
|
@ -56,8 +55,7 @@ export function EditorSettings({
|
|||
>
|
||||
<li
|
||||
className={
|
||||
'check-box' +
|
||||
(settings.forceSuggestions ? ' enabled' : '')
|
||||
'check-box' + (settings.forceSuggestions ? ' enabled' : '')
|
||||
}
|
||||
title='Save suggestions instead of translations'
|
||||
onClick={() => toggleSetting('forceSuggestions')}
|
||||
|
|
|
@ -136,10 +136,7 @@ describe('<FailedChecks>', () => {
|
|||
const [wrapper, store] = createFailedChecks();
|
||||
|
||||
store.dispatch(
|
||||
editor.actions.updateFailedChecks(
|
||||
{ pndbWarnings: ['a warning'] },
|
||||
'',
|
||||
),
|
||||
editor.actions.updateFailedChecks({ pndbWarnings: ['a warning'] }, ''),
|
||||
);
|
||||
wrapper.update();
|
||||
|
||||
|
|
|
@ -52,10 +52,7 @@ export default function FailedChecks(
|
|||
|
||||
return (
|
||||
<div className='failed-checks'>
|
||||
<Localized
|
||||
id='editor-FailedChecks--close'
|
||||
attrs={{ ariaLabel: true }}
|
||||
>
|
||||
<Localized id='editor-FailedChecks--close' attrs={{ ariaLabel: true }}>
|
||||
<button
|
||||
aria-label='Close failed checks popup'
|
||||
className='close'
|
||||
|
|
|
@ -62,9 +62,7 @@ function KeyboardShortcuts({ onDiscard }: KeyboardShortcutsProps) {
|
|||
mod1: <span />,
|
||||
}}
|
||||
>
|
||||
<td>
|
||||
{'<mod1>Shift</mod1> + <accel>Enter</accel>'}
|
||||
</td>
|
||||
<td>{'<mod1>Shift</mod1> + <accel>Enter</accel>'}</td>
|
||||
</Localized>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -108,9 +106,7 @@ function KeyboardShortcuts({ onDiscard }: KeyboardShortcutsProps) {
|
|||
}}
|
||||
>
|
||||
<td>
|
||||
{
|
||||
'<mod1>Ctrl</mod1> + <mod2>Shift</mod2> + <accel>C</accel>'
|
||||
}
|
||||
{'<mod1>Ctrl</mod1> + <mod2>Shift</mod2> + <accel>C</accel>'}
|
||||
</td>
|
||||
</Localized>
|
||||
</tr>
|
||||
|
@ -146,9 +142,7 @@ function KeyboardShortcuts({ onDiscard }: KeyboardShortcutsProps) {
|
|||
}}
|
||||
>
|
||||
<td>
|
||||
{
|
||||
'<mod1>Ctrl</mod1> + <mod2>Shift</mod2> + <accel>F</accel>'
|
||||
}
|
||||
{'<mod1>Ctrl</mod1> + <mod2>Shift</mod2> + <accel>F</accel>'}
|
||||
</td>
|
||||
</Localized>
|
||||
</tr>
|
||||
|
@ -165,9 +159,7 @@ function KeyboardShortcuts({ onDiscard }: KeyboardShortcutsProps) {
|
|||
}}
|
||||
>
|
||||
<td>
|
||||
{
|
||||
'<mod1>Ctrl</mod1> + <mod2>Shift</mod2> + <accel>A</accel>'
|
||||
}
|
||||
{'<mod1>Ctrl</mod1> + <mod2>Shift</mod2> + <accel>A</accel>'}
|
||||
</td>
|
||||
</Localized>
|
||||
</tr>
|
||||
|
@ -184,9 +176,7 @@ function KeyboardShortcuts({ onDiscard }: KeyboardShortcutsProps) {
|
|||
}}
|
||||
>
|
||||
<td>
|
||||
{
|
||||
'<mod1>Ctrl</mod1> + <mod2>Shift</mod2> + <accel>Up</accel>'
|
||||
}
|
||||
{'<mod1>Ctrl</mod1> + <mod2>Shift</mod2> + <accel>Up</accel>'}
|
||||
</td>
|
||||
</Localized>
|
||||
</tr>
|
||||
|
@ -203,9 +193,7 @@ function KeyboardShortcuts({ onDiscard }: KeyboardShortcutsProps) {
|
|||
}}
|
||||
>
|
||||
<td>
|
||||
{
|
||||
'<mod1>Ctrl</mod1> + <mod2>Shift</mod2> + <accel>Down</accel>'
|
||||
}
|
||||
{'<mod1>Ctrl</mod1> + <mod2>Shift</mod2> + <accel>Down</accel>'}
|
||||
</td>
|
||||
</Localized>
|
||||
</tr>
|
||||
|
|
|
@ -27,15 +27,15 @@ describe('<TranslationLength>', () => {
|
|||
);
|
||||
|
||||
expect(wrapper.find('.countdown')).toHaveLength(0);
|
||||
expect(
|
||||
wrapper.find('.translation-vs-original').childAt(0).text(),
|
||||
).toEqual('7');
|
||||
expect(
|
||||
wrapper.find('.translation-vs-original').childAt(1).text(),
|
||||
).toEqual('|');
|
||||
expect(
|
||||
wrapper.find('.translation-vs-original').childAt(2).text(),
|
||||
).toEqual('5');
|
||||
expect(wrapper.find('.translation-vs-original').childAt(0).text()).toEqual(
|
||||
'7',
|
||||
);
|
||||
expect(wrapper.find('.translation-vs-original').childAt(1).text()).toEqual(
|
||||
'|',
|
||||
);
|
||||
expect(wrapper.find('.translation-vs-original').childAt(2).text()).toEqual(
|
||||
'5',
|
||||
);
|
||||
});
|
||||
|
||||
it('shows translation length and plural original string length', () => {
|
||||
|
@ -48,9 +48,9 @@ describe('<TranslationLength>', () => {
|
|||
/>,
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper.find('.translation-vs-original').childAt(2).text(),
|
||||
).toEqual('6');
|
||||
expect(wrapper.find('.translation-vs-original').childAt(2).text()).toEqual(
|
||||
'6',
|
||||
);
|
||||
});
|
||||
|
||||
it('shows countdown if MAX_LENGTH provided in LANG entity comment', () => {
|
||||
|
@ -104,8 +104,8 @@ describe('<TranslationLength>', () => {
|
|||
/>,
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper.find('.translation-vs-original').childAt(0).text(),
|
||||
).toEqual('19');
|
||||
expect(wrapper.find('.translation-vs-original').childAt(0).text()).toEqual(
|
||||
'19',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -28,10 +28,7 @@ export default class TranslationLength extends React.Component<Props> {
|
|||
|
||||
if (parts[0].startsWith('MAX_LENGTH')) {
|
||||
try {
|
||||
return parseInt(
|
||||
parts[0].split('MAX_LENGTH: ')[1].split(' ')[0],
|
||||
10,
|
||||
);
|
||||
return parseInt(parts[0].split('MAX_LENGTH: ')[1].split(' ')[0], 10);
|
||||
} catch (e) {
|
||||
// Catch unexpected comment structure
|
||||
}
|
||||
|
@ -69,8 +66,7 @@ export default class TranslationLength extends React.Component<Props> {
|
|||
</div>
|
||||
) : (
|
||||
<div className='translation-vs-original'>
|
||||
<span>{translation.length}</span>|
|
||||
<span>{original.length}</span>
|
||||
<span>{translation.length}</span>|<span>{original.length}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -23,10 +23,7 @@ export default function useCopyMachineryTranslation(
|
|||
const updateMachinerySources = useCallback(
|
||||
(machinerySources: Array<SourceType>, machineryTranslation: string) => {
|
||||
dispatch(
|
||||
actions.updateMachinerySources(
|
||||
machinerySources,
|
||||
machineryTranslation,
|
||||
),
|
||||
actions.updateMachinerySources(machinerySources, machineryTranslation),
|
||||
);
|
||||
},
|
||||
[dispatch],
|
||||
|
@ -64,10 +61,7 @@ export default function useCopyMachineryTranslation(
|
|||
else {
|
||||
updateTranslation(translation.translation, 'machinery');
|
||||
}
|
||||
updateMachinerySources(
|
||||
translation.sources,
|
||||
translation.translation,
|
||||
);
|
||||
updateMachinerySources(translation.sources, translation.translation);
|
||||
},
|
||||
[
|
||||
isFluentTranslationMessage,
|
||||
|
|
|
@ -43,10 +43,7 @@ export default function useHandleShortcuts(): (
|
|||
|
||||
return (
|
||||
event: React.KeyboardEvent<HTMLTextAreaElement>,
|
||||
sendTranslation: (
|
||||
ignoreWarnings?: boolean,
|
||||
translation?: string,
|
||||
) => void,
|
||||
sendTranslation: (ignoreWarnings?: boolean, translation?: string) => void,
|
||||
clearEditorCustom?: () => void,
|
||||
copyOriginalIntoEditorCustom?: () => void,
|
||||
) => {
|
||||
|
@ -80,10 +77,7 @@ export default function useHandleShortcuts(): (
|
|||
// Approve anyway.
|
||||
else if (typeof source === 'number') {
|
||||
updateTranslationStatus(source, 'approve', ignoreWarnings);
|
||||
} else if (
|
||||
sameExistingTranslation &&
|
||||
!sameExistingTranslation.approved
|
||||
) {
|
||||
} else if (sameExistingTranslation && !sameExistingTranslation.approved) {
|
||||
updateTranslationStatus(
|
||||
sameExistingTranslation.pk,
|
||||
'approve',
|
||||
|
|
|
@ -19,10 +19,7 @@ export default function useReplaceSelectionContent(
|
|||
// the content differently for each Editor type. Thus each Editor
|
||||
// must use this hook and pass it a function specific to its needs.
|
||||
if (selectionReplacementContent) {
|
||||
updateTranslationSelectionWith(
|
||||
selectionReplacementContent,
|
||||
changeSource,
|
||||
);
|
||||
updateTranslationSelectionWith(selectionReplacementContent, changeSource);
|
||||
dispatch(editor.actions.resetSelection());
|
||||
}
|
||||
}, [
|
||||
|
|
|
@ -62,11 +62,5 @@ export default function useUpdateUnsavedChanges(richEditor: boolean) {
|
|||
dispatch(unsavedchanges.actions.hide());
|
||||
}
|
||||
prevTranslation.current = translation;
|
||||
}, [
|
||||
richEditor,
|
||||
translation,
|
||||
prevTranslation,
|
||||
unsavedChangesShown,
|
||||
dispatch,
|
||||
]);
|
||||
}, [richEditor, translation, prevTranslation, unsavedChangesShown, dispatch]);
|
||||
}
|
||||
|
|
|
@ -149,14 +149,8 @@ export default function reducer(
|
|||
case UPDATE_FAILED_CHECKS:
|
||||
return {
|
||||
...state,
|
||||
errors: extractFailedChecksOfType(
|
||||
action.failedChecks,
|
||||
'Errors',
|
||||
),
|
||||
warnings: extractFailedChecksOfType(
|
||||
action.failedChecks,
|
||||
'Warnings',
|
||||
),
|
||||
errors: extractFailedChecksOfType(action.failedChecks, 'Errors'),
|
||||
warnings: extractFailedChecksOfType(action.failedChecks, 'Warnings'),
|
||||
source: action.source,
|
||||
};
|
||||
case UPDATE_SELECTION:
|
||||
|
|
|
@ -118,9 +118,7 @@ describe('sameExistingTranslation', () => {
|
|||
{
|
||||
...EDITOR_FLUENT,
|
||||
translation: fluent.flattenMessage(
|
||||
fluent.parser.parseEntry(
|
||||
HISTORY_FLUENT.translations[0].string,
|
||||
),
|
||||
fluent.parser.parseEntry(HISTORY_FLUENT.translations[0].string),
|
||||
),
|
||||
},
|
||||
ACTIVE_TRANSLATION,
|
||||
|
@ -134,9 +132,7 @@ describe('sameExistingTranslation', () => {
|
|||
{
|
||||
...EDITOR_FLUENT,
|
||||
translation: fluent.flattenMessage(
|
||||
fluent.parser.parseEntry(
|
||||
HISTORY_FLUENT.translations[1].string,
|
||||
),
|
||||
fluent.parser.parseEntry(HISTORY_FLUENT.translations[1].string),
|
||||
),
|
||||
},
|
||||
ACTIVE_TRANSLATION,
|
||||
|
|
|
@ -66,8 +66,7 @@ export function _getPreviousEntity(
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const previous =
|
||||
currentIndex === 0 ? entities.length - 1 : currentIndex - 1;
|
||||
const previous = currentIndex === 0 ? entities.length - 1 : currentIndex - 1;
|
||||
return entities[previous];
|
||||
}
|
||||
|
||||
|
|
|
@ -75,10 +75,7 @@ export function get(locales: ReadonlyArray<string>) {
|
|||
// We know this is English, let's make it weird before bundling it.
|
||||
if (usePseudoLocalization) {
|
||||
bundleOptions = {
|
||||
transform:
|
||||
PSEUDO_STRATEGIES[
|
||||
urlParams.get('pseudolocalization')
|
||||
],
|
||||
transform: PSEUDO_STRATEGIES[urlParams.get('pseudolocalization')],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -49,8 +49,8 @@ describe('<AppLocalizationProvider>', () => {
|
|||
AppLocalizationProviderBase,
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper.find('#content-test-AppLocalizationProvider'),
|
||||
).toHaveLength(1);
|
||||
expect(wrapper.find('#content-test-AppLocalizationProvider')).toHaveLength(
|
||||
1,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -50,13 +50,13 @@ const ACCENTED_MAP = {
|
|||
const FLIPPED_MAP = {
|
||||
// ∀ԐↃᗡƎℲ⅁HIſӼ⅂WNOԀÒᴚS⊥∩ɅMX⅄Z
|
||||
caps: [
|
||||
8704, 1296, 8579, 5601, 398, 8498, 8513, 72, 73, 383, 1276, 8514, 87,
|
||||
78, 79, 1280, 210, 7450, 83, 8869, 8745, 581, 77, 88, 8516, 90,
|
||||
8704, 1296, 8579, 5601, 398, 8498, 8513, 72, 73, 383, 1276, 8514, 87, 78,
|
||||
79, 1280, 210, 7450, 83, 8869, 8745, 581, 77, 88, 8516, 90,
|
||||
],
|
||||
// ɐqɔpǝɟƃɥıɾʞʅɯuodbɹsʇnʌʍxʎz
|
||||
small: [
|
||||
592, 113, 596, 112, 477, 607, 387, 613, 305, 638, 670, 645, 623, 117,
|
||||
111, 100, 98, 633, 115, 647, 110, 652, 653, 120, 654, 122,
|
||||
592, 113, 596, 112, 477, 607, 387, 613, 305, 638, 670, 645, 623, 117, 111,
|
||||
100, 98, 633, 115, 647, 110, 652, 653, 120, 654, 122,
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
@ -37,8 +37,7 @@ describe('Linkify', () => {
|
|||
it('leaves existing links alone', () => {
|
||||
const wrapper = shallow(
|
||||
<Linkify>
|
||||
more <a href='https://example.com'>pontoon.mozilla.org</a>{' '}
|
||||
content
|
||||
more <a href='https://example.com'>pontoon.mozilla.org</a> content
|
||||
</Linkify>,
|
||||
);
|
||||
const links = wrapper.find('a');
|
||||
|
|
|
@ -8,11 +8,7 @@ export default function SkeletonLoader(props) {
|
|||
const list = Array.from(Array(itemCount).keys());
|
||||
|
||||
return (
|
||||
<ul
|
||||
className={`skeleton-loader entities ${
|
||||
firstLoad ? null : 'scroll'
|
||||
}`}
|
||||
>
|
||||
<ul className={`skeleton-loader entities ${firstLoad ? null : 'scroll'}`}>
|
||||
{list.map((i) => {
|
||||
const classes = `entity missing ${
|
||||
i === 0 && firstLoad ? 'selected' : null
|
||||
|
|
|
@ -57,9 +57,7 @@ export function get(code: string) {
|
|||
const locale = {
|
||||
...data,
|
||||
direction: data.direction.toLowerCase(),
|
||||
cldrPlurals: data.cldrPlurals
|
||||
.split(',')
|
||||
.map((i) => parseInt(i, 10)),
|
||||
cldrPlurals: data.cldrPlurals.split(',').map((i) => parseInt(i, 10)),
|
||||
};
|
||||
dispatch(receive(locale));
|
||||
};
|
||||
|
|
|
@ -27,9 +27,7 @@ describe('getPluralExamples', () => {
|
|||
const res = getPluralExamples(locale);
|
||||
const expected = { 0: 1, 1: 2 };
|
||||
expect(res).toEqual(expected);
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
'Unable to generate plural examples.',
|
||||
);
|
||||
expect(spy).toHaveBeenCalledWith('Unable to generate plural examples.');
|
||||
} finally {
|
||||
spy.mockRestore();
|
||||
}
|
||||
|
|
|
@ -9,9 +9,7 @@ export type NotificationType =
|
|||
|
||||
export type NotificationMessage = {
|
||||
readonly type: NotificationType;
|
||||
readonly content:
|
||||
| string
|
||||
| React.ReactElement<React.ComponentProps<any>, any>;
|
||||
readonly content: string | React.ReactElement<React.ComponentProps<any>, any>;
|
||||
readonly key?: string;
|
||||
};
|
||||
|
||||
|
|
|
@ -15,9 +15,7 @@ describe('<NotificationPanel>', () => {
|
|||
};
|
||||
|
||||
it('returns an empty element when there is no notification', () => {
|
||||
const wrapper = shallow(
|
||||
<NotificationPanel notification={EMPTY_NOTIF} />,
|
||||
);
|
||||
const wrapper = shallow(<NotificationPanel notification={EMPTY_NOTIF} />);
|
||||
expect(wrapper.children()).toHaveLength(1);
|
||||
expect(wrapper.find('span').text()).toEqual('');
|
||||
});
|
||||
|
@ -31,9 +29,7 @@ describe('<NotificationPanel>', () => {
|
|||
jest.useFakeTimers();
|
||||
|
||||
// Create a NotificationPanel with no message.
|
||||
const wrapper = shallow(
|
||||
<NotificationPanel notification={EMPTY_NOTIF} />,
|
||||
);
|
||||
const wrapper = shallow(<NotificationPanel notification={EMPTY_NOTIF} />);
|
||||
|
||||
expect(wrapper.find('span').text()).toEqual('');
|
||||
|
||||
|
|
|
@ -61,10 +61,7 @@ export default class NotificationPanel extends React.Component<Props, State> {
|
|||
const notif = notification.message;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={'notification-panel' + hideClass}
|
||||
onClick={this.hide}
|
||||
>
|
||||
<div className={'notification-panel' + hideClass} onClick={this.hide}>
|
||||
{!notif ? (
|
||||
<span />
|
||||
) : (
|
||||
|
|
|
@ -157,9 +157,7 @@ const messages: Record<string, NotificationMessage> = {
|
|||
},
|
||||
COMMENT_ADDED: {
|
||||
content: (
|
||||
<Localized id='notification--comment-added'>
|
||||
Comment added
|
||||
</Localized>
|
||||
<Localized id='notification--comment-added'>Comment added</Localized>
|
||||
),
|
||||
type: 'info',
|
||||
},
|
||||
|
|
|
@ -7,9 +7,7 @@ describe('<WithPlaceablesNoLeadingSpace>', () => {
|
|||
it('matches newlines in a string', () => {
|
||||
const content = 'Hello\nworld';
|
||||
const wrapper = shallow(
|
||||
<WithPlaceablesNoLeadingSpace>
|
||||
{content}
|
||||
</WithPlaceablesNoLeadingSpace>,
|
||||
<WithPlaceablesNoLeadingSpace>{content}</WithPlaceablesNoLeadingSpace>,
|
||||
);
|
||||
|
||||
expect(wrapper.find('mark')).toHaveLength(1);
|
||||
|
@ -19,9 +17,7 @@ describe('<WithPlaceablesNoLeadingSpace>', () => {
|
|||
it('does not match spaces at the beginning of a string', () => {
|
||||
const content = ' Hello world';
|
||||
const wrapper = shallow(
|
||||
<WithPlaceablesNoLeadingSpace>
|
||||
{content}
|
||||
</WithPlaceablesNoLeadingSpace>,
|
||||
<WithPlaceablesNoLeadingSpace>{content}</WithPlaceablesNoLeadingSpace>,
|
||||
);
|
||||
|
||||
expect(wrapper.text()).toEqual(content);
|
||||
|
|
|
@ -16,10 +16,7 @@ const altAttribute = {
|
|||
rule: /(alt=".*?")/i as RegExp,
|
||||
tag: (x: string): React.ReactElement<React.ElementType> => {
|
||||
return (
|
||||
<Localized
|
||||
id='placeable-parser-altAttribute'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<Localized id='placeable-parser-altAttribute' attrs={{ title: true }}>
|
||||
<mark
|
||||
className='placeable'
|
||||
title="'alt' attribute inside XML tag"
|
||||
|
|
|
@ -18,10 +18,7 @@ const camelCaseString = {
|
|||
matchIndex: 0,
|
||||
tag: (x: string): React.ReactElement<React.ElementType> => {
|
||||
return (
|
||||
<Localized
|
||||
id='placeable-parser-camelCaseString'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<Localized id='placeable-parser-camelCaseString' attrs={{ title: true }}>
|
||||
<mark className='placeable' title='Camel case string' dir='ltr'>
|
||||
{x}
|
||||
</mark>
|
||||
|
|
|
@ -18,10 +18,7 @@ const emailPattern = {
|
|||
matchIndex: 0,
|
||||
tag: (x: string): React.ReactElement<React.ElementType> => {
|
||||
return (
|
||||
<Localized
|
||||
id='placeable-parser-emailPattern'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<Localized id='placeable-parser-emailPattern' attrs={{ title: true }}>
|
||||
<mark className='placeable' title='Email' dir='ltr'>
|
||||
{x}
|
||||
</mark>
|
||||
|
|
|
@ -8,10 +8,7 @@ const escapeSequence = {
|
|||
rule: '\\',
|
||||
tag: (x: string): React.ReactElement<React.ElementType> => {
|
||||
return (
|
||||
<Localized
|
||||
id='placeable-parser-escapeSequence'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<Localized id='placeable-parser-escapeSequence' attrs={{ title: true }}>
|
||||
<mark className='placeable' title='Escape sequence' dir='ltr'>
|
||||
{x}
|
||||
</mark>
|
||||
|
|
|
@ -18,10 +18,7 @@ const filePattern = {
|
|||
matchIndex: 2,
|
||||
tag: (x: string): React.ReactElement<React.ElementType> => {
|
||||
return (
|
||||
<Localized
|
||||
id='placeable-parser-filePattern'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<Localized id='placeable-parser-filePattern' attrs={{ title: true }}>
|
||||
<mark className='placeable' title='File location' dir='ltr'>
|
||||
{x}
|
||||
</mark>
|
||||
|
|
|
@ -16,10 +16,7 @@ const fluentFunction = {
|
|||
rule: /({ ?[A-W0-9\-_]+[^}]* ?})/ as RegExp,
|
||||
tag: (x: string): React.ReactElement<React.ElementType> => {
|
||||
return (
|
||||
<Localized
|
||||
id='placeable-parser-fluentFunction'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<Localized id='placeable-parser-fluentFunction' attrs={{ title: true }}>
|
||||
<mark className='placeable' title='Fluent function' dir='ltr'>
|
||||
{x}
|
||||
</mark>
|
||||
|
|
|
@ -8,10 +8,7 @@ import fluentParametrizedTerm from './fluentParametrizedTerm';
|
|||
describe('fluentParametrizedTerm', () => {
|
||||
each([
|
||||
['{-brand(case: "test")}', 'Hello {-brand(case: "test")}'],
|
||||
[
|
||||
'{ -brand(case: "what ever") }',
|
||||
'Hello { -brand(case: "what ever") }',
|
||||
],
|
||||
['{ -brand(case: "what ever") }', 'Hello { -brand(case: "what ever") }'],
|
||||
[
|
||||
'{ -brand-name(foo-bar: "now that\'s a value!") }',
|
||||
'Hello { -brand-name(foo-bar: "now that\'s a value!") }',
|
||||
|
|
|
@ -21,11 +21,7 @@ const fluentParametrizedTerm = {
|
|||
id='placeable-parser-fluentParametrizedTerm'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<mark
|
||||
className='placeable'
|
||||
title='Fluent parametrized term'
|
||||
dir='ltr'
|
||||
>
|
||||
<mark className='placeable' title='Fluent parametrized term' dir='ltr'>
|
||||
{x}
|
||||
</mark>
|
||||
</Localized>
|
||||
|
|
|
@ -18,11 +18,7 @@ describe('fluentString', () => {
|
|||
});
|
||||
|
||||
each([
|
||||
[
|
||||
'{ "hello!" }',
|
||||
'{ "world!" }',
|
||||
'Hello { "hello!" } from { "world!" }',
|
||||
],
|
||||
['{ "hello!" }', '{ "world!" }', 'Hello { "hello!" } from { "world!" }'],
|
||||
]).it('marks `%s` and `%s` in `%s`', (mark1, mark2, content) => {
|
||||
const Marker = createMarker([fluentString]);
|
||||
const wrapper = shallow(<Marker>{content}</Marker>);
|
||||
|
|
|
@ -15,15 +15,8 @@ const fluentString = {
|
|||
rule: /({ ?"[^}]*" ?})/ as RegExp,
|
||||
tag: (x: string): React.ReactElement<React.ElementType> => {
|
||||
return (
|
||||
<Localized
|
||||
id='placeable-parser-fluentString'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<mark
|
||||
className='placeable'
|
||||
title='Fluent string expression'
|
||||
dir='ltr'
|
||||
>
|
||||
<Localized id='placeable-parser-fluentString' attrs={{ title: true }}>
|
||||
<mark className='placeable' title='Fluent string expression' dir='ltr'>
|
||||
{x}
|
||||
</mark>
|
||||
</Localized>
|
||||
|
|
|
@ -16,10 +16,7 @@ const jsonPlaceholder = {
|
|||
rule: /(\$[A-Z0-9_]+\$)/ as RegExp,
|
||||
tag: (x: string): React.ReactElement<React.ElementType> => {
|
||||
return (
|
||||
<Localized
|
||||
id='placeable-parser-jsonPlaceholder'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<Localized id='placeable-parser-jsonPlaceholder' attrs={{ title: true }}>
|
||||
<mark className='placeable' title='JSON placeholder' dir='ltr'>
|
||||
{x}
|
||||
</mark>
|
||||
|
|
|
@ -12,10 +12,7 @@ const leadingSpace = {
|
|||
rule: /(^ +)/ as RegExp,
|
||||
tag: (x: string): React.ReactElement<React.ElementType> => {
|
||||
return (
|
||||
<Localized
|
||||
id='placeable-parser-leadingSpace'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<Localized id='placeable-parser-leadingSpace' attrs={{ title: true }}>
|
||||
<mark className='placeable' title='Leading space' dir='ltr'>
|
||||
{x}
|
||||
</mark>
|
||||
|
|
|
@ -8,10 +8,7 @@ const multipleSpaces = {
|
|||
rule: /( +)/ as RegExp,
|
||||
tag: (x: string): React.ReactElement<React.ElementType> => {
|
||||
return (
|
||||
<Localized
|
||||
id='placeable-parser-multipleSpaces'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<Localized id='placeable-parser-multipleSpaces' attrs={{ title: true }}>
|
||||
<mark
|
||||
className='placeable'
|
||||
title='Multiple spaces'
|
||||
|
|
|
@ -12,11 +12,7 @@ const narrowNonBreakingSpace = {
|
|||
id='placeable-parser-narrowNonBreakingSpace'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<mark
|
||||
className='placeable'
|
||||
title='Narrow non-breaking space'
|
||||
dir='ltr'
|
||||
>
|
||||
<mark className='placeable' title='Narrow non-breaking space' dir='ltr'>
|
||||
{x}
|
||||
</mark>
|
||||
</Localized>
|
||||
|
|
|
@ -8,10 +8,7 @@ const newlineCharacter = {
|
|||
rule: '\n',
|
||||
tag: (x: string): React.ReactElement<React.ElementType> => {
|
||||
return (
|
||||
<Localized
|
||||
id='placeable-parser-newlineCharacter'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<Localized id='placeable-parser-newlineCharacter' attrs={{ title: true }}>
|
||||
<mark
|
||||
className='placeable'
|
||||
title='Newline character'
|
||||
|
|
|
@ -8,10 +8,7 @@ const newlineEscape = {
|
|||
rule: '\\n',
|
||||
tag: (x: string): React.ReactElement<React.ElementType> => {
|
||||
return (
|
||||
<Localized
|
||||
id='placeable-parser-newlineEscape'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<Localized id='placeable-parser-newlineEscape' attrs={{ title: true }}>
|
||||
<mark className='placeable' title='Escaped newline' dir='ltr'>
|
||||
{x}
|
||||
</mark>
|
||||
|
|
|
@ -8,15 +8,8 @@ const nonBreakingSpace = {
|
|||
rule: '\u00A0',
|
||||
tag: (x: string): React.ReactElement<React.ElementType> => {
|
||||
return (
|
||||
<Localized
|
||||
id='placeable-parser-nonBreakingSpace'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<mark
|
||||
className='placeable'
|
||||
title='Non-breaking space'
|
||||
dir='ltr'
|
||||
>
|
||||
<Localized id='placeable-parser-nonBreakingSpace' attrs={{ title: true }}>
|
||||
<mark className='placeable' title='Non-breaking space' dir='ltr'>
|
||||
{x}
|
||||
</mark>
|
||||
</Localized>
|
||||
|
|
|
@ -14,10 +14,7 @@ const nsisVariable = {
|
|||
matchIndex: 2,
|
||||
tag: (x: string): React.ReactElement<React.ElementType> => {
|
||||
return (
|
||||
<Localized
|
||||
id='placeable-parser-nsisVariable'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<Localized id='placeable-parser-nsisVariable' attrs={{ title: true }}>
|
||||
<mark className='placeable' title='NSIS variable' dir='ltr'>
|
||||
{x}
|
||||
</mark>
|
||||
|
|
|
@ -19,10 +19,7 @@ const numberString = {
|
|||
matchIndex: 0,
|
||||
tag: (x: string): React.ReactElement<React.ElementType> => {
|
||||
return (
|
||||
<Localized
|
||||
id='placeable-parser-numberString'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<Localized id='placeable-parser-numberString' attrs={{ title: true }}>
|
||||
<mark className='placeable' title='Number' dir='ltr'>
|
||||
{x}
|
||||
</mark>
|
||||
|
|
|
@ -17,15 +17,8 @@ const optionPattern = {
|
|||
matchIndex: 0,
|
||||
tag: (x: string): React.ReactElement<React.ElementType> => {
|
||||
return (
|
||||
<Localized
|
||||
id='placeable-parser-optionPattern'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<mark
|
||||
className='placeable'
|
||||
title='Command line option'
|
||||
dir='ltr'
|
||||
>
|
||||
<Localized id='placeable-parser-optionPattern' attrs={{ title: true }}>
|
||||
<mark className='placeable' title='Command line option' dir='ltr'>
|
||||
{x}
|
||||
</mark>
|
||||
</Localized>
|
||||
|
|
|
@ -27,10 +27,7 @@ const punctuation = {
|
|||
matchIndex: 0,
|
||||
tag: (x: string): React.ReactElement<React.ElementType> => {
|
||||
return (
|
||||
<Localized
|
||||
id='placeable-parser-punctuation'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<Localized id='placeable-parser-punctuation' attrs={{ title: true }}>
|
||||
<mark className='placeable' title='Punctuation' dir='ltr'>
|
||||
{x}
|
||||
</mark>
|
||||
|
|
|
@ -17,11 +17,7 @@ const pythonFormatNamedString = {
|
|||
id='placeable-parser-pythonFormatNamedString'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<mark
|
||||
className='placeable'
|
||||
title='Python format string'
|
||||
dir='ltr'
|
||||
>
|
||||
<mark className='placeable' title='Python format string' dir='ltr'>
|
||||
{x}
|
||||
</mark>
|
||||
</Localized>
|
||||
|
|
|
@ -21,11 +21,7 @@ const pythonFormatString = {
|
|||
id='placeable-parser-pythonFormatString'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<mark
|
||||
className='placeable'
|
||||
title='Python format string'
|
||||
dir='ltr'
|
||||
>
|
||||
<mark className='placeable' title='Python format string' dir='ltr'>
|
||||
{x}
|
||||
</mark>
|
||||
</Localized>
|
||||
|
|
|
@ -28,10 +28,7 @@ const qtFormatting = {
|
|||
matchIndex: 0,
|
||||
tag: (x: string): React.ReactElement<React.ElementType> => {
|
||||
return (
|
||||
<Localized
|
||||
id='placeable-parser-qtFormatting'
|
||||
attrs={{ title: true }}
|
||||
>
|
||||
<Localized id='placeable-parser-qtFormatting' attrs={{ title: true }}>
|
||||
<mark
|
||||
className='placeable'
|
||||
title='Qt string formatting variable'
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче