diff --git a/.circleci/config.yml b/.circleci/config.yml
index 62fea2b62..95d136980 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -8,7 +8,7 @@ jobs:
# Install development dependencies
- run: sudo apt-get install python3 python3-pip
- - run: sudo npm install -g jsonlint eslint htmlhint sass-lint
+ - run: sudo npm install -g jsonlint eslint flow flow-bin htmlhint sass-lint
- run: sudo pip3 install flake8
# Run tests
@@ -17,6 +17,7 @@ jobs:
- run: htmlhint --config .htmlhintrc.json *.html
- run: sass-lint --config assets/css/.sass-lint.yml --verbose --no-exit --max-warnings 0
- run: flake8 --config tools/.flake8.ini *.py **/**.py **/**/*.py
+ - run: flow check
# Build project
- run: cp config.conf.example config.conf
diff --git a/.flowconfig b/.flowconfig
index 28eb81569..d5f5265f9 100644
--- a/.flowconfig
+++ b/.flowconfig
@@ -1,5 +1,5 @@
[ignore]
-build/
+.*/build/.*
[include]
diff --git a/Makefile b/Makefile
index 59be3f5dc..e7e14a43c 100644
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,7 @@ debug: debugjs debugcss build/index.debug.html build/not-found.html fonts build/
prod: js css html fonts build/sitemap.xml build/manifest.json build/strings/locales.json localhtml images
js: build/js/min.js build/js/compat.js build/js/jquery.min.js build/js/bootstrap.min.js debugjs
-debugjs: build/js/jquery.jsonp-2.4.0.min.js build/js/config.js build/js/util.js build/js/store.js build/js/persistence.js build/js/localization.js build/js/translator.js build/js/analyzer.js build/js/generator.js build/js/sandbox.js
+debugjs: build/js/jquery.jsonp-2.4.0.min.js build/js/config.js build/js/util.js build/js/init.js build/js/store.js build/js/persistence.js build/js/localization.js build/js/translator.js build/js/analyzer.js build/js/generator.js build/js/sandbox.js
css: build/css/min.css build/css/font-awesome.min.css build/css/bootstrap-rtl.min.css debugcss
debugcss: build/css/bootstrap.css build/css/style.css
html: build/index.html build/index.debug.html build/not-found.html
@@ -37,6 +37,7 @@ JSFILES= \
build/js/locales.js \
build/js/listrequests.js \
assets/js/util.js \
+ assets/js/init.js \
assets/js/store.js \
assets/js/persistence.js \
assets/js/localization.js \
@@ -226,10 +227,6 @@ build/img/%: assets/img/%
images: $(IMAGES_BUILD)
-### Typechecking ###
-# grab the bin from https://github.com/facebook/flow/releases
-flow: build/js/all.js
- grep -Ev '/\*:: *(ex|im)port ' $< | flow check-contents
### Test server ###
server:
@@ -240,4 +237,4 @@ server:
### Clean ###
clean:
rm -rf build/
-
\ No newline at end of file
+
diff --git a/assets/js/analyzer.js b/assets/js/analyzer.js
index 0542eb711..3efe268fa 100644
--- a/assets/js/analyzer.js
+++ b/assets/js/analyzer.js
@@ -240,3 +240,9 @@ function formatUnit(unit) {
function handleAnalyzeErrorResponse(xOptions, error) {
$('#morphAnalyzerOutput').text(error).removeClass('blurred');
}
+
+/*:: import {config} from "./config.js" */
+/*:: import {modeEnabled, ajaxSend, ajaxComplete, filterLangList, allowedLang, sendEvent, callApy, apyRequestTimeout} from "./util.js" */
+/*:: import {ENTER_KEY_CODE} from "./util.js" */
+/*:: import {localizeInterface, getLangByCode} from "./localization.js" */
+/*:: import {persistChoices, restoreChoices, readCache, cache} from "./persistence.js" */
diff --git a/assets/js/init.js b/assets/js/init.js
new file mode 100644
index 000000000..6cb0ab129
--- /dev/null
+++ b/assets/js/init.js
@@ -0,0 +1,181 @@
+/* global config, persistChoices, iso639Codes, iso639CodesInverse, populateTranslationList, showTranslateWebpageInterface */
+/* global ajaxSend, ajaxComplete, debounce, resizeFooter, synchronizeTextareaHeights */
+
+var BACK_TO_TOP_BUTTON_ACTIVATION_HEIGHT = 300;
+
+$(document).ajaxSend(ajaxSend);
+$(document).ajaxComplete(ajaxComplete);
+$(document).ajaxError(ajaxComplete);
+
+$.jsonp.setup({
+ callbackParameter: 'callback'
+});
+
+$(document).ready(function () {
+ $('#noscript').hide();
+ $('.navbar').css('margin-top', '0px');
+ $('body > .container').css('margin-top', '0px');
+
+ if(config.SUBTITLE) {
+ $('.apertiumSubLogo')
+ .text(config.SUBTITLE)
+ .show();
+ if(config.SUBTITLE_COLOR) {
+ $('.apertiumSubLogo').css('color', config.SUBTITLE_COLOR);
+ }
+ }
+ else {
+ $('.apertiumSubLogo').hide();
+ }
+
+ if(config.SHOW_NAVBAR) {
+ if(config.ENABLED_MODES === null) {
+ $('.nav a').removeClass('hide');
+ }
+ else {
+ $.each(config.ENABLED_MODES, function () {
+ $('.nav a[data-mode=' + this + ']').removeClass('hide');
+ });
+ }
+ }
+ else {
+ $('.navbar-default .navbar-toggle').hide();
+ $('.navbar-default .nav').hide();
+ }
+
+ var hash = parent.location.hash;
+
+ try {
+ if(!hash || !$(hash + 'Container')) {
+ hash = '#' + config.DEFAULT_MODE;
+ parent.location.hash = hash;
+ }
+ }
+ catch(e) {
+ console.error('Invalid hash: ' + e);
+ hash = '#' + config.DEFAULT_MODE;
+ parent.location.hash = hash;
+ }
+
+ try {
+ if(hash === '#webpageTranslation') {
+ hash = '#translation';
+ showTranslateWebpageInterface();
+ }
+ else if(!hash || !$(hash + 'Container').length) {
+ hash = '#' + config.DEFAULT_MODE;
+ parent.location.hash = hash;
+ }
+ }
+ catch(e) {
+ console.error('Invalid hash: ' + e);
+ hash = '#' + config.DEFAULT_MODE;
+ parent.location.hash = hash;
+ }
+
+ $('.modeContainer' + hash + 'Container').show();
+ $('.navbar-default .nav li > a[data-mode=' + hash.substring(1) + ']').parent().addClass('active');
+
+ $('.navbar-default .nav a').click(function () {
+ var mode = $(this).data('mode');
+ $('.nav li').removeClass('active');
+ $(this).parent('li').addClass('active');
+ $('.modeContainer:not(#' + mode + 'Container)').stop().hide({
+ queue: false
+ });
+ $('#' + mode + 'Container').stop().show({
+ queue: false
+ });
+ synchronizeTextareaHeights();
+ });
+
+ resizeFooter();
+ $(window)
+ .on('hashchange', persistChoices)
+ .resize(debounce(function () {
+ populateTranslationList();
+ resizeFooter();
+ }));
+
+ if(config.ALLOWED_LANGS) {
+ var withIso = [];
+ $.each(config.ALLOWED_LANGS, function () {
+ if(iso639Codes[this]) {
+ withIso.push(iso639Codes[this]);
+ }
+ if(iso639CodesInverse[this]) {
+ withIso.push(iso639CodesInverse[this]);
+ }
+ });
+ Array.prototype.push.apply(config.ALLOWED_LANGS, withIso);
+ }
+
+ $('form').submit(function () {
+ return false;
+ });
+
+ $('.modal').on('show.bs.modal', function () {
+ $('a[data-target=#' + $(this).attr('id') + ']').parents('li').addClass('active');
+ $.each($(this).find('img[data-src]'), function () {
+ $(this).attr('src', $(this).attr('data-src'));
+ });
+ });
+
+ $('.modal').on('hide.bs.modal', function () {
+ $('a[data-target=#' + $(this).attr('id') + ']').parents('li').removeClass('active');
+ });
+
+ $('#backToTop').addClass('hide');
+ $(window).scroll(function () {
+ $('#backToTop').toggleClass('hide', $(window).scrollTop() < BACK_TO_TOP_BUTTON_ACTIVATION_HEIGHT);
+ });
+
+ $('#backToTop').click(function () {
+ $('html, body').animate({
+ scrollTop: 0
+ }, 'fast');
+ return false;
+ });
+
+ $('#installationNotice').addClass('hide');
+});
+
+if(config.PIWIK_SITEID && config.PIWIK_URL) {
+ var url = config.PIWIK_URL;
+ if(document.location.protocol === 'https:') {
+ url = url.replace(/^(http(s)?)?:/, 'https:');
+ }
+ // but if we're on plain http, we keep whatever was in the config
+ if(url.charAt(url.length - 1) !== '/') {
+ url += '/';
+ }
+
+ /* eslint-disable */
+ var _paq = _paq || [];
+ _paq.push(['trackPageView']);
+ _paq.push(['enableLinkTracking']);
+ (function() {
+ var u=url;
+ _paq = _paq || [];
+ _paq.push(['setTrackerUrl', u+'piwik.php']);
+ _paq.push(['setSiteId', config.PIWIK_SITEID]);
+ var d=document,
+ g=d.createElement('script'),
+ s=d.getElementsByTagName('script')[0];
+ g.type='text/javascript';
+ g.defer=true;
+ g.async=true;
+ g.src=u+'piwik.js';
+ if(s.parentNode) {
+ s.parentNode.insertBefore(g,s);
+ }
+ })();
+ /* eslint-enable */
+}
+
+/*:: export {_paq} */
+/*:: import {config} from "./config.js" */
+/*:: import {persistChoices} from "./persistence.js" */
+/*:: import {iso639Codes, iso639CodesInverse} from "./localization.js" */
+/*:: import {populateTranslationList, showTranslateWebpageInterface} from "./translator.js" */
+/*:: import {ajaxSend, ajaxComplete, debounce, resizeFooter, synchronizeTextareaHeights} from "./util.js" */
diff --git a/assets/js/translator.js b/assets/js/translator.js
index d137f30f7..d15087b2e 100644
--- a/assets/js/translator.js
+++ b/assets/js/translator.js
@@ -24,7 +24,7 @@ var PUNCTUATION_KEY_CODES = [46, 33, 58, 63, 47, 45, 190, 171, 49]; // eslint-di
/* global config, modeEnabled, synchronizeTextareaHeights, persistChoices, getLangByCode, sendEvent, onlyUnique, restoreChoices
getDynamicLocalization, locale, ajaxSend, ajaxComplete, localizeInterface, filterLangList, cache, readCache, iso639Codes,
callApy, apyRequestTimeout, isURL */
-/* global SPACE_KEY_CODE, ENTER_KEY_CODE, HTTP_OK_CODE, XHR_LOADING, XHR_DONE, HTTP_OK_CODE, HTTP_BAD_REQUEST_CODE */
+/* global SPACE_KEY_CODE, ENTER_KEY_CODE, HTTP_OK_CODE, XHR_LOADING, XHR_DONE, HTTP_BAD_REQUEST_CODE */
if(modeEnabled('translation')) {
$(document).ready(function () {
@@ -1074,10 +1074,11 @@ function autoSelectDstLang() {
}
}
-/*:: import {synchronizeTextareaHeights, modeEnabled, ajaxSend, ajaxComplete, filterLangList, onlyUnique, getLangByCode,
- callApy, apyRequestTimeout} from "./util.js" */
+/*:: export {populateTranslationList, showTranslateWebpageInterface} */
+/*:: import {synchronizeTextareaHeights, modeEnabled, ajaxSend, ajaxComplete, filterLangList, onlyUnique, sendEvent, callApy,
+ apyRequestTimeout, SPACE_KEY_CODE, ENTER_KEY_CODE, HTTP_OK_CODE, XHR_LOADING, XHR_DONE, HTTP_BAD_REQUEST_CODE} from "./util.js" */
/*:: import {persistChoices, restoreChoices} from "./persistence.js" */
-/*:: import localizeInterface from "./localization.js" */
-/*:: import {readCache,cache} from "./cache.js" */
+/*:: import {localizeInterface, getLangByCode, getDynamicLocalization, locale, iso639Codes} from "./localization.js" */
+/*:: import {readCache, cache} from "./persistence.js" */
/*:: import {config} from "./config.js" */
/*:: import {isURL} from "./util.js" */
diff --git a/assets/js/util.js b/assets/js/util.js
index af135608f..602da80a6 100644
--- a/assets/js/util.js
+++ b/assets/js/util.js
@@ -1,14 +1,14 @@
/* @flow */
-/* exported sendEvent, modeEnabled, filterLangList, getURLParam, onlyUnique, isSubset, safeRetrieve, callApy, apyRequestTimeout, isURL */
+/* exported debounce, ajaxComplete, sendEvent, modeEnabled, resizeFooter, filterLangList, getURLParam, onlyUnique, isSubset */
+/* exported synchronizeTextareaHeights, callApy, apyRequestTimeout, isURL */
/* exported SPACE_KEY_CODE, ENTER_KEY_CODE, HTTP_OK_CODE, HTTP_BAD_REQUEST_CODE, XHR_LOADING, XHR_DONE */
-/* global config, persistChoices, iso639Codes, iso639CodesInverse, populateTranslationList, showTranslateWebpageInterface */
+/* global _paq, config */
var SPACE_KEY_CODE = 32, ENTER_KEY_CODE = 13,
HTTP_OK_CODE = 200, HTTP_BAD_REQUEST_CODE = 400,
XHR_LOADING = 3, XHR_DONE = 4;
var TEXTAREA_AUTO_RESIZE_MINIMUM_WIDTH = 768,
- BACK_TO_TOP_BUTTON_ACTIVATION_HEIGHT = 300,
APY_REQUEST_URL_THRESHOLD_LENGTH = 2000, // maintain 48 characters buffer for generated parameters
DEFAULT_DEBOUNCE_DELAY = 100;
@@ -71,176 +71,6 @@ function ajaxComplete() {
}
}
-$(document).ajaxSend(ajaxSend);
-$(document).ajaxComplete(ajaxComplete);
-$(document).ajaxError(ajaxComplete);
-
-$.jsonp.setup({
- callbackParameter: 'callback'
-});
-
-$(document).ready(function () {
- $('#noscript').hide();
- $('.navbar').css('margin-top', '0px');
- $('body > .container').css('margin-top', '0px');
-
- if(config.SUBTITLE) {
- $('.apertiumSubLogo')
- .text(config.SUBTITLE)
- .show();
- if(config.SUBTITLE_COLOR) {
- $('.apertiumSubLogo').css('color', config.SUBTITLE_COLOR);
- }
- }
- else {
- $('.apertiumSubLogo').hide();
- }
-
- if(config.SHOW_NAVBAR) {
- if(config.ENABLED_MODES === null) {
- $('.nav a').removeClass('hide');
- }
- else {
- $.each(config.ENABLED_MODES, function () {
- $('.nav a[data-mode=' + this + ']').removeClass('hide');
- });
- }
- }
- else {
- $('.navbar-default .navbar-toggle').hide();
- $('.navbar-default .nav').hide();
- }
-
- var hash = parent.location.hash;
-
- try {
- if(!hash || !$(hash + 'Container')) {
- hash = '#' + config.DEFAULT_MODE;
- parent.location.hash = hash;
- }
- }
- catch(e) {
- console.error('Invalid hash: ' + e);
- hash = '#' + config.DEFAULT_MODE;
- parent.location.hash = hash;
- }
-
- try {
- if(hash === '#webpageTranslation') {
- hash = '#translation';
- showTranslateWebpageInterface();
- }
- else if(!hash || !$(hash + 'Container').length) {
- hash = '#' + config.DEFAULT_MODE;
- parent.location.hash = hash;
- }
- }
- catch(e) {
- console.error('Invalid hash: ' + e);
- hash = '#' + config.DEFAULT_MODE;
- parent.location.hash = hash;
- }
-
- $('.modeContainer' + hash + 'Container').show();
- $('.navbar-default .nav li > a[data-mode=' + hash.substring(1) + ']').parent().addClass('active');
-
- $('.navbar-default .nav a').click(function () {
- var mode = $(this).data('mode');
- $('.nav li').removeClass('active');
- $(this).parent('li').addClass('active');
- $('.modeContainer:not(#' + mode + 'Container)').stop().hide({
- queue: false
- });
- $('#' + mode + 'Container').stop().show({
- queue: false
- });
- synchronizeTextareaHeights();
- });
-
- resizeFooter();
- $(window)
- .on('hashchange', persistChoices)
- .resize(debounce(function () {
- populateTranslationList();
- resizeFooter();
- }));
-
- if(config.ALLOWED_LANGS) {
- var withIso = [];
- $.each(config.ALLOWED_LANGS, function () {
- if(iso639Codes[this]) {
- withIso.push(iso639Codes[this]);
- }
- if(iso639CodesInverse[this]) {
- withIso.push(iso639CodesInverse[this]);
- }
- });
- Array.prototype.push.apply(config.ALLOWED_LANGS, withIso);
- }
-
- $('form').submit(function () {
- return false;
- });
-
- $('.modal').on('show.bs.modal', function () {
- $('a[data-target=#' + $(this).attr('id') + ']').parents('li').addClass('active');
- $.each($(this).find('img[data-src]'), function () {
- $(this).attr('src', $(this).attr('data-src'));
- });
- });
-
- $('.modal').on('hide.bs.modal', function () {
- $('a[data-target=#' + $(this).attr('id') + ']').parents('li').removeClass('active');
- });
-
- $('#backToTop').addClass('hide');
- $(window).scroll(function () {
- $('#backToTop').toggleClass('hide', $(window).scrollTop() < BACK_TO_TOP_BUTTON_ACTIVATION_HEIGHT);
- });
-
- $('#backToTop').click(function () {
- $('html, body').animate({
- scrollTop: 0
- }, 'fast');
- return false;
- });
-
- $('#installationNotice').addClass('hide');
-});
-
-if(config.PIWIK_SITEID && config.PIWIK_URL) {
- var url = config.PIWIK_URL;
- if(document.location.protocol === 'https:') {
- url = url.replace(/^(http(s)?)?:/, 'https:');
- }
- // but if we're on plain http, we keep whatever was in the config
- if(url.charAt(url.length - 1) !== '/') {
- url += '/';
- }
-
- /* eslint-disable */
- var _paq = _paq || [];
- _paq.push(['trackPageView']);
- _paq.push(['enableLinkTracking']);
- (function() {
- var u=url;
- _paq = _paq || [];
- _paq.push(['setTrackerUrl', u+'piwik.php']);
- _paq.push(['setSiteId', config.PIWIK_SITEID]);
- var d=document,
- g=d.createElement('script'),
- s=d.getElementsByTagName('script')[0];
- g.type='text/javascript';
- g.defer=true;
- g.async=true;
- g.src=u+'piwik.js';
- if(s.parentNode) {
- s.parentNode.insertBefore(g,s);
- }
- })();
- /* eslint-enable */
-}
-
/* eslint-disable id-blacklist */
function sendEvent(category, action, label, value) {
if(config.PIWIK_SITEID && config.PIWIK_URL && _paq) {
@@ -407,9 +237,7 @@ function displayInstallationNotification() {
}
}
-/*:: export {synchronizeTextareaHeights, modeEnabled, ajaxSend, ajaxComplete, filterLangList, onlyUnique, callApy,
- SPACE_KEY_CODE, ENTER_KEY_CODE, HTTP_OK_CODE, HTTP_BAD_REQUEST_CODE, XHR_LOADING, XHR_DONE, apyRequestTimeout} */
+/*:: export {debounce, synchronizeTextareaHeights, modeEnabled, resizeFooter, ajaxSend, ajaxComplete, filterLangList, onlyUnique, callApy,
+ SPACE_KEY_CODE, ENTER_KEY_CODE, HTTP_OK_CODE, HTTP_BAD_REQUEST_CODE, XHR_LOADING, XHR_DONE, apyRequestTimeout, isURL, sendEvent} */
+/*:: import {_paq} from "./init.js" */
/*:: import {config} from "./config.js" */
-/*:: import {persistChoices} from "./persistence.js" */
-/*:: import {iso639Codes, iso639CodesInverse} from "./localization.js" */
-/*:: import {populateTranslationList, showTranslateWebpageInterface} from "./translator.js" */
diff --git a/debug-head.html b/debug-head.html
index 324e970df..fa3a4668c 100644
--- a/debug-head.html
+++ b/debug-head.html
@@ -3,6 +3,7 @@
+