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 @@ +