diff --git a/app.js b/app.js index 9028e3a..d9294f7 100755 --- a/app.js +++ b/app.js @@ -74,7 +74,7 @@ app.use('/dist/leaflet', express.static('node_modules/leaflet/dist')); app.use('/dist/feather', express.static('node_modules/feather-icons/dist')); app.use('/dist/axios', express.static('node_modules/axios/dist')); -app.use('/api/0', apiRouter); +app.use('/api/', apiRouter); const csrfProtection = csrf({ cookie: true }); app.use(csrfProtection); diff --git a/controllers/api.js b/controllers/api.js index 56bab18..b8553b8 100755 --- a/controllers/api.js +++ b/controllers/api.js @@ -7,13 +7,6 @@ const { getRegion } = require('./region'); const QUIZZ_SIZE = process.env.QUIZZ_SIZE ? process.env.QUIZZ_SIZE : 5; -function getHome(req, res) { - res.render('api', { - title: "SoundBirder API", - version: 0 - }); -} - async function region(req, res) { let {lat, lon} = req.query; lat = parseFloat(lat); @@ -27,6 +20,7 @@ async function region(req, res) { async function quizz(req, res) { debug('Generating quizz'); const { region } = req.query; + req.session.region = region; // debug(`Coordinates: ${lat}, ${lng}`); const locale = req.i18n.locale; debugLocale("Locale:", locale); @@ -77,13 +71,29 @@ function check(req, res) { res.json(result); } +async function birdSpeciesCompletion(req, res) { + const { q } = req.query; + const locale = req.i18n.locale; + const region = req.session.region; + if (region === undefined) { + res.status(400).json({error: "Your session is not associated with any region yet"}); + return; + } + if (q === undefined) { + res.status(400).json({error: "No query provided"}); + return; + } + let speciesCompletion = await quizzController.getSpeciesCompletion(region, locale, q); + res.json(speciesCompletion); +} + const game = { check, - quizz + quizz, + birdSpeciesCompletion } module.exports = { - getHome, game, region } \ No newline at end of file diff --git a/controllers/quizz.js b/controllers/quizz.js index c502967..799d59f 100755 --- a/controllers/quizz.js +++ b/controllers/quizz.js @@ -66,37 +66,17 @@ async function getSpeciesSelection(region, number) { const regionCode = region; const speciesList = await getSpeciesList(regionCode); const speciesSelection = choices(speciesList, number); - debug("Species proposals:", speciesSelection) return speciesSelection; } async function getLocalizedNames(speciesCodes, locale) { - let allNames = []; - for (let i = 0; i < speciesCodes.length; i++) { - const code = speciesCodes[i]; - let flag = false; - try { - names = await cache.getCached(`${code}-${locale}`); - if (names) { - allNames.push(names); - flag = true; - } - } catch (error) { - console.error(error); - } - if (!flag) { - let names; - try { - names = { speciesCode, sciName, comName } = await getLocalizedName(code, locale); - cache.cacheResponse(`${code}-${locale}`, names); - allNames.push(names); - } catch (error) { - console.error(error); - } - } + const localizedNames = []; + for (const speciesCode of speciesCodes) { + const localized = await getLocalizedName(speciesCode, locale); + localizedNames.push(localized); } - return allNames; + return localizedNames; } function getLocalizedName(speciesCode, locale) { @@ -127,7 +107,6 @@ async function getSpeciesList(regionCode) { } else { return eBird.product.spplist.in(regionCode)() .then(species => { - species = species.filter((sp) => !sp.includes(" x ")); cache.cacheResponse(`spplist-${regionCode}`, species); return species; }) @@ -142,7 +121,6 @@ function getAudio(speciesScientificName) { name: speciesScientificName, quality: 'A' }).then(response => { - debugResponses(response); const { recordings } = response; const randomRecord = choice(recordings); const audio = randomRecord.file; @@ -152,8 +130,23 @@ function getAudio(speciesScientificName) { }); } + +async function getSpeciesCompletion(region, locale, term) { + let speciesList = await getSpeciesList(region); + let localizedSpeciesList = await getLocalizedNames(speciesList, locale); + let termLower = term.toLowerCase(); + let speciesCompletion = localizedSpeciesList.filter(speciesNames => { + let { comName, sciName } = speciesNames; + let name = comName.toLowerCase(); + let sci = sciName.toLowerCase(); + return name.includes(termLower) || sci.includes(termLower); + }); + return speciesCompletion; +} + module.exports = { generateQuizz, getQuizzCached, + getSpeciesCompletion, cacheQuizz } \ No newline at end of file diff --git a/locales/en.json b/locales/en.json index f47d960..bf04e7b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -13,5 +13,6 @@ "SoundBirder is an open-source web application to learn bird song identification. It is based on bird records from Xeno-Canto and data from eBird.": "SoundBirder is an open-source web application to learn bird song identification. It is based on bird records from Xeno-Canto and data from eBird.", "Author": "Author", "The project is made with ♥ by Samuel Ortion.": "The project is made with ♥ by Samuel Ortion.", - "Correct!": "Correct!" + "Correct!": "Correct!", + "Set": "Set" } \ No newline at end of file diff --git a/locales/fr.json b/locales/fr.json index 19cf3ad..4356323 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -14,5 +14,7 @@ "Geolocalize yourself on the map": "Geolocalize yourself on the map", "Start the game": "Start the game", "Start a new quizz": "Start a new quizz", - "Return to the map": "Return to the map" + "Return to the map": "Return to the map", + "About SoundBirder": "About SoundBirder", + "Set": "Set" } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4a8e4fe..b84830a 100755 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,8 @@ "leaflet": "^1.8.0", "lodash": "^4.17.21", "morgan": "~1.9.1", + "postcss": "^8.4.33", + "postcss-cli": "^11.0.0", "pug": "^3.0.2", "redis": "^4.3.0", "tailwindcss": "^3.1.8" @@ -216,6 +218,17 @@ "@redis/client": "^1.0.0" } }, + "node_modules/@sindresorhus/merge-streams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz", + "integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@unclesamulus/ebird-api": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/@unclesamulus/ebird-api/-/ebird-api-0.0.0.tgz", @@ -255,6 +268,28 @@ "node": ">=0.4.0" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -536,9 +571,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001517", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz", - "integrity": "sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==", + "version": "1.0.30001579", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz", + "integrity": "sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==", "funding": [ { "type": "opencollective", @@ -604,6 +639,19 @@ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/cluster-key-slot": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", @@ -617,6 +665,22 @@ "resolved": "git+ssh://git@github.com/hlaw/codegrid-js.git#38decfe10dced9006c6b722cc403d7c1217abe98", "license": "WTFPL" }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -835,6 +899,14 @@ "node": ">= 0.6" } }, + "node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -880,6 +952,11 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.477.tgz", "integrity": "sha512-shUVy6Eawp33dFBFIoYbIwLHrX0IZ857AlH9ug2o4rvbWmpaCUdBpQ5Zw39HRrfzAFm4APJE9V+E2A/WB0YqJw==" }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -1091,9 +1168,9 @@ } }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -1240,6 +1317,19 @@ "node": ">= 0.6" } }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1279,6 +1369,14 @@ "just-clone": "^6.2.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", @@ -1293,6 +1391,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-stdin": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", + "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -1323,6 +1432,30 @@ "node": ">=10.13.0" } }, + "node_modules/globby": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz", + "integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==", + "dependencies": { + "@sindresorhus/merge-streams": "^1.0.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -1420,6 +1553,14 @@ "node": ">=0.10.0" } }, + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "engines": { + "node": ">= 4" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1481,6 +1622,14 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -1521,9 +1670,9 @@ } }, "node_modules/jiti": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz", - "integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", "bin": { "jiti": "bin/jiti.js" } @@ -1533,6 +1682,17 @@ "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==" }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/jstransformer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", @@ -1683,9 +1843,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "funding": [ { "type": "github", @@ -1805,6 +1965,17 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -1838,9 +2009,9 @@ } }, "node_modules/postcss": { - "version": "8.4.27", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz", - "integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==", + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", "funding": [ { "type": "opencollective", @@ -1856,7 +2027,7 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -1864,6 +2035,76 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-cli": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-11.0.0.tgz", + "integrity": "sha512-xMITAI7M0u1yolVcXJ9XTZiO9aO49mcoKQy6pCDFdMh9kGqhzLVpWxeD/32M/QBmkhcGypZFFOLNLmIW4Pg4RA==", + "dependencies": { + "chokidar": "^3.3.0", + "dependency-graph": "^0.11.0", + "fs-extra": "^11.0.0", + "get-stdin": "^9.0.0", + "globby": "^14.0.0", + "picocolors": "^1.0.0", + "postcss-load-config": "^5.0.0", + "postcss-reporter": "^7.0.0", + "pretty-hrtime": "^1.0.3", + "read-cache": "^1.0.0", + "slash": "^5.0.0", + "yargs": "^17.0.0" + }, + "bin": { + "postcss": "index.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-cli/node_modules/lilconfig": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", + "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "engines": { + "node": ">=14" + } + }, + "node_modules/postcss-cli/node_modules/postcss-load-config": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-5.0.2.tgz", + "integrity": "sha512-Q8QR3FYbqOKa0bnC1UQ2bFq9/ulHX5Bi34muzitMr8aDtUelO5xKeJEYC/5smE0jNE9zdB/NBnOwXKexELbRlw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + } + } + }, "node_modules/postcss-import": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", @@ -1944,6 +2185,31 @@ "postcss": "^8.2.14" } }, + "node_modules/postcss-reporter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.1.0.tgz", + "integrity": "sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "picocolors": "^1.0.0", + "thenby": "^1.3.4" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/postcss-selector-parser": { "version": "6.0.13", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", @@ -1961,6 +2227,14 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "node_modules/pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", @@ -2237,6 +2511,14 @@ "@redis/time-series": "1.0.4" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", @@ -2419,6 +2701,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", @@ -2440,6 +2733,30 @@ "node": ">= 0.6" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/sucrase": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", @@ -2508,6 +2825,11 @@ "node": ">=14.0.0" } }, + "node_modules/thenby": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz", + "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==" + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -2595,6 +2917,25 @@ "node": ">= 0.8" } }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -2675,23 +3016,72 @@ "node": ">= 10.0.0" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", "engines": { "node": ">= 14" } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } } } } diff --git a/package.json b/package.json index ce049bc..0cdc91d 100755 --- a/package.json +++ b/package.json @@ -26,6 +26,8 @@ "leaflet": "^1.8.0", "lodash": "^4.17.21", "morgan": "~1.9.1", + "postcss": "^8.4.33", + "postcss-cli": "^11.0.0", "pug": "^3.0.2", "redis": "^4.3.0", "tailwindcss": "^3.1.8" diff --git a/public/javascripts/api-client.js b/public/javascripts/api-client.js index 8d161a8..017abc0 100755 --- a/public/javascripts/api-client.js +++ b/public/javascripts/api-client.js @@ -1,5 +1,4 @@ -const API_VERSION = "0"; -const API_URL = `/api/${API_VERSION}`; +const API_URL = '/api'; const TOKEN = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); @@ -34,6 +33,12 @@ function checkAnswer(species) { }) } +function getSpeciesCompletion(term) { + return query('game/species/completion', { + q: term + }) +} + const client = { getQuizz, getRegion, diff --git a/public/stylesheets/game.css b/public/stylesheets/game.css index 40611dc..daf41d6 100755 --- a/public/stylesheets/game.css +++ b/public/stylesheets/game.css @@ -51,3 +51,4 @@ footer .link:after { .proposal-button { margin: 1em 0; } + diff --git a/public/stylesheets/spinner.css b/public/stylesheets/spinner.css old mode 100644 new mode 100755 diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 10ba29f..6c83eb9 100755 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -166,15 +166,15 @@ input, optgroup, select, textarea { - font-family: inherit; - font-feature-settings: inherit; - font-variation-settings: inherit; - font-size: 100%; - font-weight: inherit; - line-height: inherit; - color: inherit; - margin: 1em; - padding: 1em; + font-family: inherit; /* 1 */ + font-feature-settings: inherit; /* 1 */ + font-variation-settings: inherit; /* 1 */ + font-size: 100%; /* 1 */ + font-weight: inherit; /* 1 */ + line-height: inherit; /* 1 */ + color: inherit; /* 1 */ + margin: 0; /* 2 */ + padding: 0; /* 3 */ } /* @@ -492,6 +492,7 @@ h1 { color: var(--tw-main-color); } .button { + margin: 0.5rem; border-radius: 0.25rem; --tw-bg-opacity: 1; background-color: rgb(34 197 94 / var(--tw-bg-opacity)); @@ -507,21 +508,56 @@ h1 { --tw-bg-opacity: 1; background-color: rgb(21 128 61 / var(--tw-bg-opacity)); } +.pointer-events-none { + pointer-events: none; +} +.absolute { + position: absolute; +} +.relative { + position: relative; +} +.-top-1 { + top: -0.25rem; +} +.-top-1\.5 { + top: -0.375rem; +} +.left-0 { + left: 0px; +} .block { display: block; } .flex { display: flex; } +.h-10 { + height: 2.5rem; +} +.h-full { + height: 100%; +} .min-h-screen { min-height: 100vh; } +.w-10 { + width: 2.5rem; +} .w-full { width: 100%; } +.min-w-\[100px\] { + min-width: 100px; +} .flex-1 { flex: 1 1 0%; } +.select-none { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} .flex-row { flex-direction: row; } @@ -531,17 +567,286 @@ h1 { .justify-evenly { justify-content: space-evenly; } +.rounded-\[7px\] { + border-radius: 7px; +} +.border { + border-width: 1px; +} +.border-t-transparent { + border-top-color: transparent; +} .bg-black { --tw-bg-opacity: 1; background-color: rgb(0 0 0 / var(--tw-bg-opacity)); } +.bg-transparent { + background-color: transparent; +} +.p-1 { + padding: 0.25rem; +} .p-5 { padding: 1.25rem; } +.px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; +} +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} +.py-2\.5 { + padding-top: 0.625rem; + padding-bottom: 0.625rem; +} .text-center { text-align: center; } +.font-sans { + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +} +.text-\[11px\] { + font-size: 11px; +} +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} +.font-normal { + font-weight: 400; +} +.leading-tight { + line-height: 1.25; +} .text-white { --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity)); +} +.outline { + outline-style: solid; +} +.outline-0 { + outline-width: 0px; +} +.transition-all { + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} +.before\:pointer-events-none::before { + content: var(--tw-content); + pointer-events: none; +} +.before\:mr-1::before { + content: var(--tw-content); + margin-right: 0.25rem; +} +.before\:mt-\[6\.5px\]::before { + content: var(--tw-content); + margin-top: 6.5px; +} +.before\:box-border::before { + content: var(--tw-content); + box-sizing: border-box; +} +.before\:block::before { + content: var(--tw-content); + display: block; +} +.before\:h-1::before { + content: var(--tw-content); + height: 0.25rem; +} +.before\:h-1\.5::before { + content: var(--tw-content); + height: 0.375rem; +} +.before\:w-2::before { + content: var(--tw-content); + width: 0.5rem; +} +.before\:w-2\.5::before { + content: var(--tw-content); + width: 0.625rem; +} +.before\:rounded-tl-md::before { + content: var(--tw-content); + border-top-left-radius: 0.375rem; +} +.before\:border-l::before { + content: var(--tw-content); + border-left-width: 1px; +} +.before\:border-t::before { + content: var(--tw-content); + border-top-width: 1px; +} +.before\:transition-all::before { + content: var(--tw-content); + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} +.after\:pointer-events-none::after { + content: var(--tw-content); + pointer-events: none; +} +.after\:ml-1::after { + content: var(--tw-content); + margin-left: 0.25rem; +} +.after\:mt-\[6\.5px\]::after { + content: var(--tw-content); + margin-top: 6.5px; +} +.after\:box-border::after { + content: var(--tw-content); + box-sizing: border-box; +} +.after\:block::after { + content: var(--tw-content); + display: block; +} +.after\:h-1::after { + content: var(--tw-content); + height: 0.25rem; +} +.after\:h-1\.5::after { + content: var(--tw-content); + height: 0.375rem; +} +.after\:w-2::after { + content: var(--tw-content); + width: 0.5rem; +} +.after\:w-2\.5::after { + content: var(--tw-content); + width: 0.625rem; +} +.after\:flex-grow::after { + content: var(--tw-content); + flex-grow: 1; +} +.after\:rounded-tr-md::after { + content: var(--tw-content); + border-top-right-radius: 0.375rem; +} +.after\:border-r::after { + content: var(--tw-content); + border-right-width: 1px; +} +.after\:border-t::after { + content: var(--tw-content); + border-top-width: 1px; +} +.after\:transition-all::after { + content: var(--tw-content); + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} +.placeholder-shown\:border:-moz-placeholder-shown { + border-width: 1px; +} +.placeholder-shown\:border:placeholder-shown { + border-width: 1px; +} +.empty\:\!bg-gray-900:empty { + --tw-bg-opacity: 1 !important; + background-color: rgb(17 24 39 / var(--tw-bg-opacity)) !important; +} +.focus\:border-2:focus { + border-width: 2px; +} +.focus\:border-gray-900:focus { + --tw-border-opacity: 1; + border-color: rgb(17 24 39 / var(--tw-border-opacity)); +} +.focus\:border-t-transparent:focus { + border-top-color: transparent; +} +.focus\:outline-0:focus { + outline-width: 0px; +} +.disabled\:border-0:disabled { + border-width: 0px; +} +.peer:-moz-placeholder-shown ~ .peer-placeholder-shown\:text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} +.peer:placeholder-shown ~ .peer-placeholder-shown\:text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} +.peer:-moz-placeholder-shown ~ .peer-placeholder-shown\:leading-\[3\.75\] { + line-height: 3.75; +} +.peer:placeholder-shown ~ .peer-placeholder-shown\:leading-\[3\.75\] { + line-height: 3.75; +} +.peer:-moz-placeholder-shown ~ .peer-placeholder-shown\:before\:border-transparent::before { + content: var(--tw-content); + border-color: transparent; +} +.peer:placeholder-shown ~ .peer-placeholder-shown\:before\:border-transparent::before { + content: var(--tw-content); + border-color: transparent; +} +.peer:-moz-placeholder-shown ~ .peer-placeholder-shown\:after\:border-transparent::after { + content: var(--tw-content); + border-color: transparent; +} +.peer:placeholder-shown ~ .peer-placeholder-shown\:after\:border-transparent::after { + content: var(--tw-content); + border-color: transparent; +} +.peer:focus ~ .peer-focus\:text-\[11px\] { + font-size: 11px; +} +.peer:focus ~ .peer-focus\:leading-tight { + line-height: 1.25; +} +.peer:focus ~ .peer-focus\:text-gray-900 { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} +.peer:focus ~ .peer-focus\:before\:border-l-2::before { + content: var(--tw-content); + border-left-width: 2px; +} +.peer:focus ~ .peer-focus\:before\:border-t-2::before { + content: var(--tw-content); + border-top-width: 2px; +} +.peer:focus ~ .peer-focus\:before\:border-gray-900::before { + content: var(--tw-content); + --tw-border-opacity: 1; + border-color: rgb(17 24 39 / var(--tw-border-opacity)); +} +.peer:focus ~ .peer-focus\:after\:border-r-2::after { + content: var(--tw-content); + border-right-width: 2px; +} +.peer:focus ~ .peer-focus\:after\:border-t-2::after { + content: var(--tw-content); + border-top-width: 2px; +} +.peer:focus ~ .peer-focus\:after\:border-gray-900::after { + content: var(--tw-content); + --tw-border-opacity: 1; + border-color: rgb(17 24 39 / var(--tw-border-opacity)); +} +.peer:disabled ~ .peer-disabled\:text-transparent { + color: transparent; +} +.peer:disabled ~ .peer-disabled\:before\:border-transparent::before { + content: var(--tw-content); + border-color: transparent; +} +.peer:disabled ~ .peer-disabled\:after\:border-transparent::after { + content: var(--tw-content); + border-color: transparent; } \ No newline at end of file diff --git a/public/stylesheets/tailwind.css b/public/stylesheets/tailwind.css index 4dd50d5..4fee564 100755 --- a/public/stylesheets/tailwind.css +++ b/public/stylesheets/tailwind.css @@ -13,6 +13,6 @@ } .button { - @apply text-white font-bold py-2 px-4 rounded bg-green-500 hover:bg-green-700; + @apply text-white font-bold py-2 px-4 m-2 rounded bg-green-500 hover:bg-green-700; } } \ No newline at end of file diff --git a/routes/api.js b/routes/api.js index 6ac7d66..393c201 100755 --- a/routes/api.js +++ b/routes/api.js @@ -2,14 +2,15 @@ const express = require('express'); const router = express.Router(); const apiController = require('../controllers/api.js'); -router.route('/') - .get(apiController.getHome); router.route('/game/quizz') .get(apiController.game.quizz); router.route('/game/check') .get(apiController.game.check); + +router.route('/species/completion') + .get(apiController.game.birdSpeciesCompletion); router.route('/region') .get(apiController.region); diff --git a/utils/extract-i18n.js b/utils/extract-i18n.js old mode 100644 new mode 100755 diff --git a/utils/translate.sh b/utils/translate.sh old mode 100644 new mode 100755