diff --git a/.gitignore b/.gitignore index 60983c2a40b8e3bf220a928cd4264032ff4d8df2..f3bbe791827e1e43022bc293dc848e49b06fd59a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ node_modules/ -public/ -.zanata-cache/etag-cache.xml +dist/ npm-debug.log -/zanata.xml pnpm-lock.yaml tools/backup/ tools/tmp/ -app/locales/*/sfs.yml +src/translations/*/sfs.yml +src/translations/*/hos.yml +public/wiki/ +public/wiki.json diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8af12238c1e03c35923b4238ec9586df8653d873..3d95d34f2aa2d315932cc6a92f59710183c7de57 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,14 +7,15 @@ test: stage: test script: - npm install -g yaml-lint - - for f in $(find ./app/ -name "*.yml" -type f);do echo $f; yamllint $f;done; + - for f in $(find ./src/ -name "*.yml" -type f);do echo $f; yamllint $f;done; pages: stage: deploy script: - npm run commons - - npm run preview - - mv -f public/$(echo $CI_PAGES_URL | cut -d/ -f4-)/* public + - npm run sfs && npm run hos + - npm run prod + - rm -r public/ && mv -f dist/$(echo $CI_PAGES_URL | cut -d/ -f4-)/ public/ - find public -type f -iregex '.*\.\(htm\|html\|txt\|text\|js\|css\)$' -execdir gzip -f --keep {} \; artifacts: paths: @@ -28,22 +29,25 @@ pages: except: variables: - $DEPLOYMENT_KEY - - $DEPLOYMENT_KNOWN_HOSTS - $DEPLOYMENT_USER - $DEPLOYMENT_HOST production: stage: deploy +# before_script: +# - apt-get update +# - apt-get --yes --force-yes install rsync script: - npm run commons - - npm run prod -- --root=accueil - - cp ./public/accueil/fr/index.html ./public/accueil/index.html - - for f in $(find -type l);do cp --remove-destination $(readlink -f $f) $f;done; - - mkdir "${HOME}/.ssh" - - chmod 700 "${HOME}/.ssh" + - npm run sfs && npm run hos + - npm run prod -- --base=abc + - rm -r public/ && mv dist/ public/ + - cp ./public/abc/fr/index.html ./public/abc/index.html + - cd public/abc && for f in $(find -type l);do cp --remove-destination $(readlink -f $f) $f;done; - eval `ssh-agent -s` - ssh-add <(echo "${DEPLOYMENT_KEY}" | base64 --decode -i); - - cd public && echo "put -r . ${DEPLOYMENT_USER}/" | sftp -o "VerifyHostKeyDNS yes" ${DEPLOYMENT_USER}@${DEPLOYMENT_HOST}; + - echo "put -r . ${DEPLOYMENT_USER}/" | sftp -o "VerifyHostKeyDNS yes" ${DEPLOYMENT_USER}@${DEPLOYMENT_HOST}:framatalk/abc/; +# - rsync -a --rsh='ssh -o "VerifyHostKeyDNS yes"' --delete . ${DEPLOYMENT_USER}@${DEPLOYMENT_HOST}:framatalk/abc/; only: refs: - master diff --git a/Makefile b/Makefile deleted file mode 100644 index d0bb14a597b17c26c6ab9ae02f0f100d89d445cc..0000000000000000000000000000000000000000 --- a/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -backup-locales: - mkdir -p tools/backup/ - cp -r app/locales/* tools/backup/ - -restore-locales: - cp -r tools/backup/* app/locales/ - -clean-locales: backup-locales - cd tools && ./cleanyml.sh - -diff-locales: - (meld app/locales/ tools/backup/ &) - -preview: - npm run preview - -build: - npm run prod diff --git a/README.md b/README.md index 19649007174067a8ed11b69dd6f17c1b7b2d205a..495982dc7762550a37328d62ce3b7c9221133e85 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![](https://upload.wikimedia.org/wikipedia/commons/thumb/1/18/GitLab_Logo.svg/48px-GitLab_Logo.svg.png)](https://framagit.org) +[![](https://framasoft.org/nav/img/icons/ati/sites/git.png)](https://framagit.org) 🇬🇧 **Framasoft uses GitLab** for the development of its free softwares. Our Github repositories are only mirrors. If you want to work with us, **fork us on [framagit.org](https://framagit.org)**. (no registration needed, you can sign in with your Github account) @@ -10,15 +10,12 @@ Si vous souhaitez travailler avec nous, **forkez-nous sur [framagit.org](https:/ Framatalk est un service en ligne de visio-conférence que [Framasoft](https://framasoft.org) propose sur le site : . Il repose sur le logiciel [Jitsi Meet](https://jitsi.org/Projects/JitsiMeet) que nous n'avons pas modifié. -La page d'accueil s'appuie sur le framework CSS Bootstrap dont les fichiers se trouvent dans la [Framanav](https://framagit.org/framasoft/framanav). Si vous souhaitez traduire la page d’accueil, allez sur . - * * * Framatalk is an visio-conference online service provided by [Framasoft](https://framasoft.org) on . It’s based on [Jitsi Meet](https://jitsi.org/Projects/JitsiMeet) that we didn’t modify. -The homepage uses the CSS framework Bootstrap which you can find files in the [Framanav](https://framagit.org/framasoft/framanav). If you want to translate the homepage, go on . diff --git a/app/App.vue b/app/App.vue deleted file mode 100644 index a13490fbc43ae81b664f5a1f3feaa8a87bf3a028..0000000000000000000000000000000000000000 --- a/app/App.vue +++ /dev/null @@ -1,120 +0,0 @@ - - - diff --git a/app/assets/img/fr/slide-framatalk01.png b/app/assets/img/fr/slide-framatalk01.png deleted file mode 100644 index c90d2f67fdd72baf16572ceb36be702779b440f0..0000000000000000000000000000000000000000 Binary files a/app/assets/img/fr/slide-framatalk01.png and /dev/null differ diff --git a/app/assets/img/fr/slide-framatalk02.png b/app/assets/img/fr/slide-framatalk02.png deleted file mode 100644 index 186210d810fffb77d1db11ecc174c8372decda1e..0000000000000000000000000000000000000000 Binary files a/app/assets/img/fr/slide-framatalk02.png and /dev/null differ diff --git a/app/assets/img/fr/slide-framatalk03.png b/app/assets/img/fr/slide-framatalk03.png deleted file mode 100644 index be8441b384e5212f23810e785ea731b54500052d..0000000000000000000000000000000000000000 Binary files a/app/assets/img/fr/slide-framatalk03.png and /dev/null differ diff --git a/app/assets/img/fr/slide-framatalk04.png b/app/assets/img/fr/slide-framatalk04.png deleted file mode 100644 index cb17dd3c58e47be49eb2057249e15e4cdfb4383d..0000000000000000000000000000000000000000 Binary files a/app/assets/img/fr/slide-framatalk04.png and /dev/null differ diff --git a/app/assets/img/icons/fa/solid/cloud.svg b/app/assets/img/icons/fa/solid/cloud.svg deleted file mode 100644 index 38d2dc5306b486a0606a171c57560694f311919c..0000000000000000000000000000000000000000 --- a/app/assets/img/icons/fa/solid/cloud.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/img/icons/fa/solid/egg.svg b/app/assets/img/icons/fa/solid/egg.svg deleted file mode 100644 index 804ac67c94b785690e935fad6e7661ca2fbc1c0d..0000000000000000000000000000000000000000 --- a/app/assets/img/icons/fa/solid/egg.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/img/icons/fa/solid/external-link-alt.svg b/app/assets/img/icons/fa/solid/external-link-alt.svg deleted file mode 100644 index 859914bab20a5c6b9670b23b66ec6e6e9cd31095..0000000000000000000000000000000000000000 --- a/app/assets/img/icons/fa/solid/external-link-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/img/icons/fa/solid/language.svg b/app/assets/img/icons/fa/solid/language.svg deleted file mode 100644 index e45129e4e631c892c5c25afb83dcf8cb19fde413..0000000000000000000000000000000000000000 --- a/app/assets/img/icons/fa/solid/language.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/img/icons/fa/solid/leaf.svg b/app/assets/img/icons/fa/solid/leaf.svg deleted file mode 100644 index f31abe1b8f2bdd958cd7347020034fb0260e9ff2..0000000000000000000000000000000000000000 --- a/app/assets/img/icons/fa/solid/leaf.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/img/icons/fa/solid/pause.svg b/app/assets/img/icons/fa/solid/pause.svg deleted file mode 100644 index 63a15b6eea3815505676583256a61fd77e8ff192..0000000000000000000000000000000000000000 --- a/app/assets/img/icons/fa/solid/pause.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/img/icons/fa/solid/play.svg b/app/assets/img/icons/fa/solid/play.svg deleted file mode 100644 index d7fa87f32b22333e4b7cb8f07b395582e7f46796..0000000000000000000000000000000000000000 --- a/app/assets/img/icons/fa/solid/play.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/img/icons/fa/solid/plus.svg b/app/assets/img/icons/fa/solid/plus.svg deleted file mode 100644 index 95992e1f5c44705b4177b8391d4450fb84310ba5..0000000000000000000000000000000000000000 --- a/app/assets/img/icons/fa/solid/plus.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/img/icons/fa/solid/question-circle.svg b/app/assets/img/icons/fa/solid/question-circle.svg deleted file mode 100644 index a8f424362eb4ff787548429a217e8651ba6e80f7..0000000000000000000000000000000000000000 --- a/app/assets/img/icons/fa/solid/question-circle.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/img/icons/fa/solid/random.svg b/app/assets/img/icons/fa/solid/random.svg deleted file mode 100644 index 3c23b13cac0296c250bdf4bc503c10664ecb92b2..0000000000000000000000000000000000000000 --- a/app/assets/img/icons/fa/solid/random.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/img/icons/fa/solid/times.svg b/app/assets/img/icons/fa/solid/times.svg deleted file mode 100644 index 571a32a1220c5c0daaf121475be796dd0ae022a4..0000000000000000000000000000000000000000 --- a/app/assets/img/icons/fa/solid/times.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/img/icons/fa/solid/video.svg b/app/assets/img/icons/fa/solid/video.svg deleted file mode 100644 index e14b3b9361d87c13f644c4d93cb567492501c77a..0000000000000000000000000000000000000000 --- a/app/assets/img/icons/fa/solid/video.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/app/assets/img/opengraph/home.jpg b/app/assets/img/opengraph/home.jpg deleted file mode 100644 index d769091dd3207b736ada9df47b1df9649b8cd50b..0000000000000000000000000000000000000000 Binary files a/app/assets/img/opengraph/home.jpg and /dev/null differ diff --git a/app/assets/scss/fonts/fork-awesome.scss b/app/assets/scss/fonts/fork-awesome.scss deleted file mode 100644 index 64898bd6a219a798e5e7d84d7a9e1115a4708f87..0000000000000000000000000000000000000000 --- a/app/assets/scss/fonts/fork-awesome.scss +++ /dev/null @@ -1,22 +0,0 @@ -@import '~fork-awesome/scss/variables'; -@import '~fork-awesome/scss/mixins'; -@import '~fork-awesome/scss/functions'; - -@font-face { - font-family: 'ForkAwesome'; - src: url('~fork-awesome/fonts/forkawesome-webfont.woff2') format('woff2'), - url('~fork-awesome/fonts/forkawesome-webfont.woff') format('woff'); - font-weight: normal; - font-style: normal; -} - -@import '~fork-awesome/scss/core'; -@import '~fork-awesome/scss/larger'; -@import '~fork-awesome/scss/fixed-width'; -@import '~fork-awesome/scss/list'; -@import '~fork-awesome/scss/bordered-pulled'; -@import '~fork-awesome/scss/animated'; -@import '~fork-awesome/scss/rotated-flipped'; -@import '~fork-awesome/scss/stacked'; -@import '~fork-awesome/scss/icons'; -@import '~fork-awesome/scss/screen-reader'; \ No newline at end of file diff --git a/app/assets/scss/frama/bootstrap4.scss b/app/assets/scss/frama/bootstrap4.scss deleted file mode 100644 index 467bd54054591dcd2b81b950f691356289faa00b..0000000000000000000000000000000000000000 --- a/app/assets/scss/frama/bootstrap4.scss +++ /dev/null @@ -1,32 +0,0 @@ -@import '~vue-fs-commons/app/assets/scss/frama/bootstrap4/alert'; -@import '~vue-fs-commons/app/assets/scss/frama/bootstrap4/badge'; -@import '~vue-fs-commons/app/assets/scss/frama/bootstrap4/buttons'; -@import '~vue-fs-commons/app/assets/scss/frama/bootstrap4/links'; -@import '~vue-fs-commons/app/assets/scss/frama/bootstrap4/type&code'; -@import '~vue-fs-commons/app/assets/scss/frama/bootstrap4/carousel'; - -#f-i18n { - button { - color: $secondary; - fill: $secondary; - border: none; - - &:hover { - color: $light; - fill: $light; - } - } - - &.show button{ - color: $light; - fill: $light; - } - - li a { - background: transparent; - - &:hover, &focus { - background-color: $light; - } - } -} diff --git a/app/assets/scss/main.scss b/app/assets/scss/main.scss deleted file mode 100644 index dc59db3fbfe6cba89c75d7b012510239f66d0d32..0000000000000000000000000000000000000000 --- a/app/assets/scss/main.scss +++ /dev/null @@ -1,35 +0,0 @@ -@import '~vue-fs-commons/app/assets/scss/frama/colors'; - -/* Variables */ - -/* Brown variant -$bsColor: (('f', 7), ('m', 7), ('m', 8), ('m', 2), ('b', 7), ('v', 6), ('o', 7), ('r', 8), ('d', 7)); - -$bgcolor: c(m, 3); - -$secondary: bs(c, 2); -$dark: bs(c, 3); -$light: bs(c, 4); -*/ - -$ombre: 0px 3px 4px rgba(50, 50, 50, 0.2); -$img-ombre: 0px 2px 6px rgba(50, 50, 50, 0.4); -$carousel-ombre: 0px 3px 3px rgba(50, 50, 50, 0.5); -$button-ombre: 0px 1px 1px rgba(50, 50, 50, 0.5); - -/* Frameworks */ -@import 'frameworks/bootstrap4'; - -/* Fonts */ -@import 'fonts/dejavu-sans'; -@import 'fonts/roboto'; - -/* Frama */ -@import 'frama/bootstrap4'; -@import '~vue-fs-commons/app/assets/scss/frama/common'; - -/* CHATONS color */ -$chatons: #ca8e0e; -#frama_other_message a { - color: $chatons; -} diff --git a/app/components/partials/Carousel.vue b/app/components/partials/Carousel.vue deleted file mode 100644 index 9ffe18af8b2c9542e849a9a75747a6071ca69c33..0000000000000000000000000000000000000000 --- a/app/components/partials/Carousel.vue +++ /dev/null @@ -1,73 +0,0 @@ - - - diff --git a/app/components/partials/Header.vue b/app/components/partials/Header.vue deleted file mode 100644 index 68c0c0d84d5d497413635778ad2380d940ce5962..0000000000000000000000000000000000000000 --- a/app/components/partials/Header.vue +++ /dev/null @@ -1,33 +0,0 @@ - - - - diff --git a/app/data/project.tpl.yml b/app/data/project.tpl.yml deleted file mode 100644 index 4b4637c8e1f8cb232978fb6c85167a348b7d1a9d..0000000000000000000000000000000000000000 --- a/app/data/project.tpl.yml +++ /dev/null @@ -1,46 +0,0 @@ -# NB: The content of this file overwrites what is defined in commons/*.yml -# For example, -# link: -# soft: https://frama.pink -# -# will overwrites the value above in commons/link.yml -# soft: https://framasoft.org - -commons: # import everything if empty -# - blog - - cloud - - color -# - country - - doc -# - emoji - - git - - icon - - license - - link - - people - - soft - - src - - status - - taxonomy - -meta: - title: Framasoft - author: Framasoft - canonical: https://framasoft.org - description: Description is neededed for search engines, Twitter card and OpenGraph -# i18n: https://weblate.framasoft.org/projects/ - preload: - - /fonts/Roboto-Regular.woff2 - - /img/icons/apple-touch-icon.png - - /img/icons/favicon.png - example: - title: '@:meta.title - @:about' - description: '@:meta.lead' - preload: - - /img/opengraph/home.jpg - -txt: -# soft: text from color.soft (but you can reassign it here) - -html: -# soft: color.soft diff --git a/app/index.js b/app/index.js deleted file mode 100644 index a110e37a9ccd1c55b22c6d171cce61b9ca2b7144..0000000000000000000000000000000000000000 --- a/app/index.js +++ /dev/null @@ -1,283 +0,0 @@ -import Vue from 'vue'; -import VueRouter from 'vue-router'; -import VueI18n from 'vue-i18n'; -import vueHeadful from 'vue-headful'; -import BootstrapVue from 'bootstrap-vue'; - -import is from 'vue-fs-commons/app/plugins/is'; -import cookie from 'vue-fs-commons/app/plugins/cookie'; -import merge from 'vue-fs-commons/app/plugins/merge'; -import text from 'vue-fs-commons/app/plugins/text'; -import SVGIcon from 'vue-fs-commons/app/components/commons/SVGIcon.vue'; -import App from './App.vue'; - -import './assets/scss/main.scss'; - -Vue.use(BootstrapVue); -Vue.use(VueRouter); -Vue.use(VueI18n); -Vue.component('vue-headful', vueHeadful); - -Vue.use(is); -Vue.use(cookie); -Vue.use(merge); -Vue.use(text); -Vue.component('svg-icon', SVGIcon); - -const defaultLocale = 'fr'; -const locales = {}; -const pages = []; -let commons = []; - -// Import locales list -// in locales/[lg]/[file].yml -let req = require.context('./locales/', true, /\.\/.*\/.*\.yml$/); -req.keys().forEach((key) => { - const lg = key.split('/')[1]; - const file = key.split('/')[2].replace(/\.yml/, ''); - if (locales[lg] === undefined) { - locales[lg] = []; - } - if (!locales[lg].includes(file)) { - locales[lg].push(file); - } -}); - -// Import pages list -req = require.context('./components/pages', false, /\.vue$/); -req.keys().forEach((key) => { - pages.push(key.replace(/\.\/(.*)\.vue/, '$1')); -}); - -// Import commons data list -req = require.context('../node_modules/vue-fs-commons/app/data/commons/', false, /\.yml$/); -req.keys().forEach((key) => { - commons.push(key.replace(/\.\/(.*)\.yml/, '$1')); -}); - -const lang = window.location.href - .replace(`${window.location.host}/${process.env.BASE_URL}`, '') - .split('/')[(process.env.BASE_URL === '') ? 2 : 3] - .substr(0, 2) - .toLowerCase() || ''; -document.getElementsByTagName('html')[0].setAttribute('lang', lang); -const userLang = navigator.languages - || [navigator.language || navigator.userLanguage]; -let defaultRouteLang = ''; - -const messages = {}; -const numberFormats = {}; -messages.locales = require('../node_modules/vue-fs-commons/app/locales/lang.yml'); // eslint-disable-line -messages.locales.available = Object - .keys(messages.locales) - .filter(n => Object.keys(locales).includes(n) - && (locales[n].includes('main') || locales[n].includes('_main'))); -messages.locales.visible = Object - .keys(messages.locales) - .filter(n => Object.keys(locales).includes(n) && (locales[n].includes('_main'))); - -// Data import -let data = {}; -let project = {}; -const scripts = document.getElementsByTagName('script'); -project = require('./data/project.yml') || {}; // eslint-disable-line -if (Array.isArray(project.commons) && project.commons.length > 0) { - [commons] = [project.commons]; -} -for (let i = 0; i < commons.length; i += 1) { - req = require(`../node_modules/vue-fs-commons/app/data/commons/${commons[i]}.yml`) || {}; // eslint-disable-line - data[commons[i]] = merge.$(data[commons[i]], JSON.parse(JSON.stringify(req))); -} -data = merge.$(data, JSON.parse(JSON.stringify(project))); - -Object.assign(data, { - host: window.location.host, - url: window.location.href, - '/': `/${process.env.BASE_URL.replace(/(.+)/, '$1/')}`, - inframe: window.top.location !== window.self.document.location ? 'true' : 'false', - hash: window.location.hash.replace('#', ''), - date: process.env.DATE, - year: { - current: (new Date().getFullYear()).toString(), - next: (new Date().getFullYear() + 1).toString(), - }, -}); -data.self = new URL(scripts[scripts.length - 1].src, data.url).href; -if (process.env.NODE_ENV === 'production' - && data.meta.canonical !== undefined - && /^http/.test(data.meta.canonical)) { - data.baseurl = data.meta.canonical.replace(/(.+?)\/?$/, '$1/'); -} else { - data.baseurl = `${data.self.split('/').slice(0, -1).join('/')}/`; -} - -const routes = []; -let msg = {}; -messages.locales.map = {}; - -// Import locales -messages.locales.available.forEach((k) => { - /* eslint-disable */ - messages[k] = {}; - messages[k].lang = k; - - numberFormats[k] = {}; - numberFormats[k].eur = { - style: 'currency', - currency: 'EUR', - maximumFractionDigits: 0, - minimumFractionDigits: 0, - }; - - // Import data - msg = {txt: {}, html: {}, tmp: {}}; - msg = merge.$(msg, JSON.parse(JSON.stringify(data))); - - const mainFile = locales[k].filter(filename => /^_?main$/.test(filename)); - if (mainFile.length > 0) { - // Init with ../node_modules/vue-fs-commons/app/locales/lg/_commons.yml - req = require(`../node_modules/vue-fs-commons/app/locales/${k}/_commons.yml`) || {}; - msg.tmp = merge.$(msg.tmp, JSON.parse(JSON.stringify(req))); - // then import locales/lg/_main.yml (active and visible) - // or locales/lg/main.yml (active and hidden) - req = require(`./locales/${k}/${mainFile[0]}.yml`) || {}; - msg.tmp = merge.$(msg.tmp, JSON.parse(JSON.stringify(req))); - } - - // locales/lg/*.yml - for (let i = 0; i < locales[k].length; i += 1) { - const file = locales[k][i]; - if (!/^_?main$/.test(file)) { - msg.tmp[file] = msg.tmp[file] || {}; - req = require(`./locales/${k}/${file}.yml`) || {}; - msg.tmp[file] = merge.$(msg.tmp[file], JSON.parse(JSON.stringify(req))); - } - } - - msg = merge.$(msg, msg.tmp); - - // Create locale template to be able to use the English text - // instead of the yaml key for translation - // (disabled by default) - if (false && k === 'en') { - Object.assign(messages.locales.map, - ...function _flatten(o, k) { - return [].concat(...Object.keys(o) - .map(i => { - const tmp = k ? `${k}.${i}` : i; - return typeof o[i] !== 'object' - ? ({[JSON.stringify(o[i].trim().replace(/\n[ ]+/g, '\n'))]: `@:${tmp}`}) - : _flatten(o[i], tmp); - } - ) - ); - }(msg.tmp) - ); - } - delete msg.tmp; - - Object.keys(msg.color).forEach((j) => { - if (msg.txt[j] === undefined) { - msg.txt[j] = text.textTransform(msg.color[j], '-t'); - } - }); - Object.keys(msg.link).forEach((j) => { - if (msg.html[j] === undefined) { - if (msg.color[j] !== undefined) { - msg.html[j] = `@:color.${j}`; - } else if (msg.txt[j] !== undefined) { - msg.html[j] = `@:txt.${j}`; - } - } - }); - messages[k] = merge.$(messages[k], msg); - /* eslint-enable */ - - // Localized routes - for (let j = 0; j < pages.length; j += 1) { - const component = require(`./components/pages/${pages[j]}.vue`); // eslint-disable-line - routes.push({ - path: `/${k}${pages[j].toLowerCase().replace(/^/, '/').replace('/home', '')}`, - component: component.default, - meta: { id: pages[j].toLowerCase(), lang: k }, - }); - } -}); - -// define defaultRouteLang -for (let j = 0; j < userLang.length; j += 1) { // check if user locales - const lg = userLang[j].substring(0, 2).toLowerCase(); - if (defaultRouteLang === '' && Object.keys(locales).includes(lg)) { // matches with app locales - defaultRouteLang = lg; - } -} - -// Redirections -for (let i = 0; i < pages.length; i += 1) { - if (!window.vuefsPrerender) { - routes.push({ - path: `/${pages[i].toLowerCase().replace('home', '')}`, - redirect: `/${defaultRouteLang}/${pages[i].toLowerCase().replace('home', '')}`, - }); - } else { - // Component needed for SEO - const component = require(`./components/pages/${pages[i]}.vue`); // eslint-disable-line - routes.push({ - path: `/${pages[i].toLowerCase().replace('home', '')}`, - component: component.default, - }); - } -} -messages.routes = routes; -messages.base = `${__dirname}${process.env.BASE_URL}`; - -// Create VueI18n instance with options -const i18n = new VueI18n({ - locale: lang === '' ? defaultRouteLang : lang, - fallbackLocale: defaultLocale, - messages, - numberFormats, - silentTranslationWarn: true, -}); - -// Override $t() for markdown and formatting -Vue.prototype.$t = (key, locale, values) => text.$t(key, locale, values, i18n); - -// Framanav -if (!window.vuefsPrerender - && document.querySelectorAll('script[src$="nav.js"]').length < 1 - && process.env.NODE_ENV !== 'development') { - const nav = document.createElement('script'); - nav.src = 'https://framasoft.org/nav/nav.js'; - document.getElementsByTagName('head')[0].appendChild(nav); -} - -// Routes -const router = new VueRouter({ - base: `${__dirname}${process.env.BASE_URL}`.replace('//', '/'), - mode: 'history', - routes, - scrollBehavior(to, from, savedPosition) { - if (to.hash) { - return { - selector: to.hash, - offset: { x: 0, y: 60 }, - }; - } - if (savedPosition) { - return savedPosition; - } - return { x: 0, y: 0 }; - }, -}); - -new Vue({ // eslint-disable-line no-new - el: '#app', - router, - i18n, - mounted() { - // You'll need this for renderAfterDocumentEvent. - document.dispatchEvent(new Event('render-event')); - }, - render: h => h(App), -}); diff --git a/app/locales/en/_main.yml b/app/locales/en/_main.yml deleted file mode 100644 index a932bcade30166886874a806f3ad461c9c06c3f5..0000000000000000000000000000000000000000 --- a/app/locales/en/_main.yml +++ /dev/null @@ -1,88 +0,0 @@ -meta: - lead: Videoconferencing -what: - md: "@:color.talk allows you to chat with your friends without complex software\n\ - installation or registration.
\nYou simply discuss, without any analysis of\ - \ data related to conversations.
\nEverything happens between you and your\ - \ friends, directly from your web browser." -tip: |- - **Tip**: if your conversation is slowed down, it’s probably - a bandwidth problem (internet speed too low). - - Disable video (button “video {icon}”) to solve the problem ;) -public: - link: Room link - create: Create - change: Change instance - atalk: a room - external: Following a great use of the video-conferencing service @:color.talk hosted - by the @:color.soft association, this video-conference will be created on **the - [{title}]({website}) instance** - chatons: (member of the [CHATONS](https://chatons.org) collective). - full_list: Complete list of the **{nbinstances} known instances**. - frama_other: This room will be created on the @:color.talk service. Other - CHATONS and structures also offer a similar service with open - access. -slides: - - '@:color.talk: a two-click audio/video conversation' - - Allow access to a microphone - - Allow access to a camera - - Share the web address (URL) to exchange on @:color.talk! -help: - title: Getting Started - md: "1. Create a room\n2. Allow your browser to use your microphone and webcam\n\ - 3. Invite your collaborators to join you by sending them by e-mail the address\n\ - \ of the room (click on the button “Invite a participant {icon}”\n in the bottom\ - \ bar to get it)\n\nIn the options, you can protect your room with a password,\ - \ chat, manage your\ncontacts, enable/disable sound and video…" -software: - title: Software - md: |- - @:color.talk is based on free software [@:soft.talk](@:src.talk) - - This is one instance among others, [Jitsi Meet developers also offer - their own]({link}) - - Jitsi Meet is under @:(license.apache2). -garden: - title: Cultivate your garden - md: |- - To participate in the development of the software, propose improvements - or simply download it, go to [the development site](@:src.talk) - - If you want to install this software for your own use and thus gain in - autonomy, we help you on: - -page_info: - title: List of Jitsi Meet instances - intro: Here is the list of instances to which you can be redirected by creating - a conference room from the Framatalk home page. - title_pool: List of the {nbinstances} instances inside the pool - add_yourself: If you are an organization that has the resources to host such a service - and would like to be added to it, you can contact us through [contact.framasoft.org](https://contact.framasoft.org). - title_other: List of {nbinstances} other instances - intro_other: We don’t know the people behind these instance, so we don’t redirect - to those because we don’t know their privacy policy or their ability to handle - many users, however we list them below. - title_specific: Specific instances - columns: - title: Instance name - chatons: Type - url: URL - country: Country - instance: - type: - chatons: CHATONS - other: Other - title_bbb: "Big Blue Button instances" - description_bbb: "[Big Blue Button](https://bigbluebutton.org/) is a web conferencing system designed for online \ - learning. In some cases, it can be a good alternative to Jitsi. It is however required to create an account to \ - create web conferencing rooms." - audio_only: - title: Audioconference - description: If you don't need the video, a Mumble meeting will do. - It is advisable to [install the Mumble client]((https://www.mumble.info/downloads/)) for your platform for a - better experience. - resources: - title: Resources diff --git a/app/locales/fr/_main.yml b/app/locales/fr/_main.yml deleted file mode 100644 index 8dff8735ebb28b057746fca343318f44bcfb9671..0000000000000000000000000000000000000000 --- a/app/locales/fr/_main.yml +++ /dev/null @@ -1,93 +0,0 @@ -meta: - lead: Visioconférence -what: - md: |- - @:color.talk vous permet de discuter avec vos amis sans installation - complexe de logiciel ou inscription.
- Vous discutez simplement, sans aucune analyse des données liées aux conversations.
- Tout se passe entre vous et vos amis, directement depuis votre navigateur web. -tip: |- - **Astuce** : si votre conversation subit des ralentissements, - c’est probablement un problème de bande passante (débit internet trop faible). - - Désactivez la vidéo (bouton « vidéo {icon} ») pour résoudre le problème ;) -public: - link: Lien du salon - create: Créer - change: Changer d'instance - atalk: un salon - external: Suite à une grande utilisation du service de vidéo-conférence @:color.talk - hébergé par l'association @:color.soft, ce salon sera créé sur **l'instance [{title}]({website})** - chatons: (membre du collectif [CHATONS](https://chatons.org)). - full_list: Liste complète des **{nbinstances} instances répertoriées**. - frama_other: Ce salon sera créé sur le service @:color.talk\. D'autres - CHATONS et structures proposent également un service - semblable en accès libre. -slides: -- '@:color.talk : une conversation audio/vidéo en deux clics' -- Autorisez l’accès à un micro -- Autorisez l’accès à une caméra -- Partagez l’adresse web (URL) pour échanger sur @:color.talk ! -help: - title: Prise en main - md: |- - 1. Créez un salon - 2. Autorisez votre navigateur à utiliser votre micro et votre webcam - 3. Invitez vos collaborateurs à vous rejoindre en leur envoyant par courriel - l’adresse du salon (cliquez sur le bouton « Inviter un participant {icon} » - dans la barre du bas pour l’obtenir) - - Dans les options, il vous est possible de protéger votre salon par un mot - de passe, tchatter, gérer vos contacts, activer/désactiver le son et la vidéo… -software: - title: Le logiciel - md: |- - @:color.talk repose sur le logiciel libre [@:soft.talk](@:src.talk) - - Il s’agit d’une instance parmi d’autres, [les développeurs de Jitsi Meet - proposent également la leur]({link}) - - Jitsi Meet est sous licence @:(license.apache2). -garden: - title: Cultivez votre jardin - md: |- - Pour participer au développement du logiciel, proposer des améliorations - ou simplement le télécharger, rendez-vous sur [le site de développement](@:src.talk) - - Si vous souhaitez installer ce logiciel pour votre propre usage et ainsi - gagner en autonomie, nous vous aidons sur : - -page_info: - title: Liste des instances Jitsi Meet - intro: Voici la liste des instances vers lequelles vous pouvez être redirigé⋅e⋅s - en créeant un salon de vidéo-conférence à partir de la page d’accueil de Framatalk. - title_pool: Liste des {nbinstances} instances dans le pool - add_yourself: Si vous êtes **une organisation** ayant les ressources pour héberger - un tel service et souhaitez y être ajouté⋅e, vous pouvez nous contacter via [contact.framasoft.org](https://contact.framasoft.org). - title_other: Liste de {nbinstances} autres instances - intro_other: Nous ne connaissons pas spécialement ces instances, donc nous ne redirigons - pas vers elles car nous ne connaissons pas leurs politique de vie privée ni leur - capacité à tenir la charge mais nous les listons ci-dessous. - title_specific: Instances spécifiques - columns: - title: Nom de l’instance - chatons: Type - url: URL - country: Pays - instance: - type: - chatons: CHATONS - other: Autre - title_bbb: "Instances Big Blue Button" - description_bbb: "[Big Blue Button](https://bigbluebutton.org/) est un système de\ - \ visioconférence développé pour la formation à distance. Dans certains cas, il\ - \ peut s'agit d'une bonne alternative à Jitsi. Il est toutefois nécessaire d'avoir\ - \ un compte pour créer des salons de visioconférence." - audio_only: - title: Audioconférence - description: Si vous n'avez pas besoin de la vidéo, une réunion Mumble fera l'affaire. - Il est conseillé d'[installer le client Mumble](https://www.mumble.info/downloads/) - pour votre plateforme pour une meilleure expérience. - resources: - title: Ressources diff --git a/commons b/commons new file mode 120000 index 0000000000000000000000000000000000000000..fb73ecfa89164265eafc0110a8310c7dc7c08205 --- /dev/null +++ b/commons @@ -0,0 +1 @@ +./node_modules/vue-fs-commons/src/ \ No newline at end of file diff --git a/config/common.js b/config/common.js new file mode 100644 index 0000000000000000000000000000000000000000..2ecbf79e38d3373442587a029f4851e39bac56ba --- /dev/null +++ b/config/common.js @@ -0,0 +1,79 @@ +/* eslint-disable import/no-commonjs */ +/* eslint-disable import/no-extraneous-dependencies */ +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const VueLoaderPlugin = require('vue-loader/lib/plugin'); +const Webpack = require('webpack'); + +function buildConfig(env) { + const config = { + entry: './src/main.js', + output: { + path: `${env.dist}${env.basepath}`, + publicPath: env.basepath, + }, + module: { + rules: [ + { + test: /\.vue$/, + loader: 'vue-loader', + }, + { + test: /\.(png|jpg|gif|svg)$/, + loader: 'file-loader', + options: { + name: '[path][name].[ext]', + }, + }, + { + test: /\.(ttf|eot|woff(2)?)(\?[a-z0-9=&.]+)?$/, + loader: 'file-loader', + options: { + name: 'fonts/[name].[ext]', + }, + }, + { + test: /\.ya?ml$/, + loader: 'yaml-import-loader', + }, + { + test: /\.s?css$/, + use: [ + 'vue-style-loader', + 'css-hot-loader', + MiniCssExtractPlugin.loader, + 'css-loader', + 'sass-loader', + ], + }, + { + enforce: 'pre', + test: /\.js$/, + exclude: /node_modules/, + loader: 'eslint-loader', + }, + { + test: /\.js$/, + exclude: /node_modules/, + loader: 'babel-loader', + }, + ], + }, + resolve: { + alias: { + vue$: 'vue/dist/vue.esm.js', + }, + }, + plugins: [ + new VueLoaderPlugin(), + new CopyWebpackPlugin(env.assets), + new Webpack.DefinePlugin({ + 'process.env.CONFIG': JSON.stringify(env), + }), + ], + }; + + return config; +} + +module.exports = buildConfig; diff --git a/config/dev.js b/config/dev.js new file mode 100644 index 0000000000000000000000000000000000000000..6a236132e965803ec0729eaa9496b711cd8ea2b5 --- /dev/null +++ b/config/dev.js @@ -0,0 +1,40 @@ +/* eslint-disable import/no-commonjs */ +/* eslint-disable import/no-extraneous-dependencies */ +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + +function buildConfig(env) { + const config = Object.assign({}, require('./common')(env)); // eslint-disable-line + + config.output.filename = '[name].bundle.js'; + + Object.assign(config, { + devServer: { + contentBase: env.dist, + historyApiFallback: true, + hot: true, + inline: true, + open: false, + publicPath: '/', + writeToDisk: true, + }, + devtool: 'eval-source-map', + mode: 'development', + }); + + config.plugins.push( + new HtmlWebpackPlugin({ + title: 'DEVELOPMENT prerender-spa-plugin', + template: 'public/index.html', + filename: 'index.html', + }), + new MiniCssExtractPlugin({ + filename: '[name].css', + chunkFilename: '[id].css', + }), + ); + + return config; +} + +module.exports = buildConfig; diff --git a/config/env.js b/config/env.js new file mode 100644 index 0000000000000000000000000000000000000000..ae76b539a3c755c3ddb25490374a8fccef1d66d3 --- /dev/null +++ b/config/env.js @@ -0,0 +1,163 @@ +/* eslint-disable global-require */ +/* eslint-disable import/no-commonjs */ +/* eslint-disable import/no-extraneous-dependencies */ + +const download = require('download'); +const fs = require('fs'); +const path = require('path'); +const yaml = require('js-yaml'); + +const data = yaml.load(fs.readFileSync('./src/data/main.yml', { encoding: 'utf-8' })); +if (!('config' in data)) { data.config = {}; } +if (!('wiki' in data.config)) { data.config.wiki = {}; } + +const mode = { + dev: process.env.NODE_ENV === 'development', + preview: /^pages/.test(process.env.CI_JOB_NAME) && process.env.NODE_ENV === 'production', + prod: !/^pages/.test(process.env.CI_JOB_NAME) && process.env.NODE_ENV === 'production', +}; + +const env = { + assets: [], + basepath: mode.preview + ? `${process.env.CI_PAGES_URL.replace(/^.*\/\/[^/]+/, '')}/` + : '/', + commons: { + available: [], + enabled: [], + }, + date: fs.statSync('./src').mtime, + dist: path.resolve(__dirname, '../dist'), + img: { + assets: './public/img/', + dist: './dist/img/', + }, + framanav: ('framanav' in data.config) ? data.config.framanav : false, + meta: data.meta, + mode: mode.preview ? 'preview' : process.env.NODE_ENV, + routes: [], + src: path.resolve(__dirname, '../src'), + translations: { + all: yaml.load(fs.readFileSync('./commons/translations/lang.yml', { encoding: 'utf-8' })), + available: [], + default: ('defaultLang' in data.config) ? data.config.defaultLang : 'en', + translate: ('translate' in data.config) ? data.config.translate : '', + }, + url: mode.preview + ? process.env.CI_PAGES_URL.replace(/\/?$/, '/').replace(/^http:/, 'https:') + : 'http://localhost:8080/', + views: [], + wiki: { + api: ('api' in data.config.wiki) ? data.config.wiki.api : '', + base: ('base' in data.config.wiki) ? data.config.wiki.base : '', + }, +}; + +if (mode.prod && env.meta.canonical !== undefined) { + env.url = env.meta.canonical.replace(/\/?$/, '/'); +} + +// Custom base +for (let i = 0; i < process.argv.length; i += 1) { + if (process.argv[i].indexOf('--base=') > -1) { + env.basepath = `/${process.argv[i].split('=')[1]}/`; + } +} +env.img.dist = `./dist${env.basepath}img/`; +env.base = env.basepath.replace(/\/$/, '').replace(/^\//, ''); + +// Get assets list +fs.readdirSync('./public').forEach((file) => { + env.assets.push({ from: path.resolve(__dirname, `../public/${file}`), to: file }); +}); + +// Get translations list +fs.readdirSync('./src/translations') + .forEach((lg) => { + if (env.translations[lg] === undefined) { + env.translations[lg] = []; + } + fs.readdirSync(`./src/translations/${lg}`) + .forEach((file) => { + if (/[A-Za-z0-9-]+\.ya?ml/.test(file)) { + env.translations[lg].push(file.replace(/([A-Za-z0-9-]+)\.ya?ml/, '$1')); + } + }); + }); + +env.translations.available = Object + .keys(env.translations.all) + .filter(lg => Object.keys(env.translations).includes(lg) + && (env.translations[lg].includes('main'))); + +// Get views list +fs.readdirSync('./src/views') + .forEach((file) => { + if (/[A-Za-z0-9-]+\.vue/.test(file)) { + env.views.push(file.replace(/([A-Za-z0-9-]+)\.vue/, '$1')); + } + }); + +// Get commons data list +fs.readdirSync('./commons/data') + .forEach((file) => { + if (/[A-Za-z0-9-]+\.ya?ml/.test(file)) { + env.commons.available.push(file.replace(/([A-Za-z0-9-]+)\.ya?ml/, '$1')); + } + }); +/* Take all data commons if enabled list is empty */ +[env.commons.enabled] = ('commons' in data.config && data.config.commons.length) + ? [data.config.commons] + : [env.commons.available]; + +for (let j = 0; j < env.views.length; j += 1) { + env.routes.push(`${env.basepath}${env.views[j].toLowerCase().replace('home', '')}`); + // Localized routes + for (let i = 0; i < env.translations.available.length; i += 1) { + env.routes.push(`${env.basepath}${env.translations.available[i]}${ + env.views[j].toLowerCase().replace(/^/, '/').replace('/home', '')}`); + } +} + +/* --- Wiki ----------------------------------------------------------------- */ +fs.writeFileSync(path.resolve(__dirname, '../public/wiki.json'), '[]'); + +async function downloadWikiFiles(json) { + const files = [...new Set( // no duplicate + Array.from( // find files in the wiki pages + JSON.stringify(json).matchAll(/\(uploads\/(.*?)\)/g), + m => `${env.wiki.base}uploads/${m[1]}`, + ), + )]; + + await Promise.all(files.map(url => download( + url, + url.replace(`${env.wiki.base}uploads/`, './public/wiki/').replace(/([^/]+$)/, ''), + ))); +} + +if (env.wiki.api !== '') { + fs.mkdirSync(path.resolve(__dirname, '../public/wiki/'), { recursive: true }); + env.assets.push( + { from: path.resolve(__dirname, '../public/wiki.json'), to: 'wiki.json' }, + { from: path.resolve(__dirname, '../public/wiki/'), to: 'wiki/' }, + ); + + // Download json + download(env.wiki.api, './public/', { filename: 'wiki.json' }) + .then(() => { + const json = require('../public/wiki.json'); // eslint-disable-line + // Get wiki data + const wiki = require('../src/wiki')(env, json); + // Download attached files + downloadWikiFiles(json); + // RSS + require('./feed')(env, wiki); + }) + .catch((err) => { console.error(err); }); // eslint-disable-line +} + +/* --- Symlinks-------------------------------------------------------------- */ +require('./symlinks')(env); + +module.exports = env; diff --git a/config/feed.js b/config/feed.js new file mode 100644 index 0000000000000000000000000000000000000000..d10a1efde2ce6262bdee3b0fe44ce457d5df6f9c --- /dev/null +++ b/config/feed.js @@ -0,0 +1,54 @@ +/* eslint-disable import/no-commonjs */ +/* eslint-disable import/no-extraneous-dependencies */ + +const Feed = require('feed').Feed; // eslint-disable-line +const fs = require('fs'); +const marked = require('marked'); + +function buildFeeds(env, wiki) { + env.translations.available.forEach((lg) => { + if (wiki[lg] !== undefined) { + if (Object.keys(wiki[lg]).includes('news') + || Object.keys(wiki[lg]).includes('newsletter')) { + const type = Object.keys(wiki[lg]).includes('news') ? 'news' : 'newsletter'; + + const feed = new Feed({ + author: env.meta.author, + copyright: env.meta.copyright || '', + description: env.meta.description || '', + favicon: `${env.url}img/icons/favicon.png`, + generator: 'Vue-Fs', + id: env.url, + image: `${env.url}img/opengraph/${type}.jpg`, + lg, + link: env.url, + title: env.meta.title || '', + updated: new Date(), + }); + + Object.keys(wiki[lg][type].posts) + .slice() // shallow copy of the array + .sort() // posts sorted + .reverse() // with latest in first + .slice(0, 10) // and only 10 posts kept in the feed + .forEach((date) => { + feed.addItem({ + content: marked(wiki[lg][type].posts[date].text), + date: new Date(`20${date}`), + id: wiki[lg][type].posts[date].url, + link: wiki[lg][type].posts[date].url, + title: wiki[lg][type].posts[date].title, + }); + }); + + fs.mkdirSync(`./dist${env.basepath}${lg}/${type}/`, { recursive: true }); + fs.writeFileSync( + `./dist${env.basepath}${lg}/${type}/feed.xml`, + feed.rss2(), + ); + } + } + }); +} + +module.exports = buildFeeds; diff --git a/config/preview.js b/config/preview.js new file mode 100644 index 0000000000000000000000000000000000000000..1f4b3ed3abbe5459c8decd98a188d3dc2f8b7852 --- /dev/null +++ b/config/preview.js @@ -0,0 +1,64 @@ +/* eslint-disable import/no-commonjs */ +/* eslint-disable import/no-extraneous-dependencies */ +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); +const PrerenderSPAPlugin = require('prerender-spa-plugin'); +const Renderer = require('@prerenderer/renderer-jsdom'); +const TerserPlugin = require('terser-webpack-plugin'); + +function buildConfig(env) { + const config = Object.assign({}, require('./common')(env)); // eslint-disable-line + + config.output.filename = '[name].[hash].bundle.js'; + + Object.assign(config, { + devtool: '#source-map', + mode: 'production', + optimization: { + minimizer: [ + new TerserPlugin({ + cache: true, + parallel: true, + sourceMap: true, // set to true if you want JS source maps + }), + new OptimizeCSSAssetsPlugin({}), + ], + splitChunks: { + chunks: 'all', + }, + }, + }); + + config.plugins.push( + new HtmlWebpackPlugin({ + title: 'PRODUCTION prerender-spa-plugin', + template: 'public/index.html', + filename: `${env.dist}/index.html`, + }), + new MiniCssExtractPlugin({ + filename: '[name].css', + chunkFilename: '[id].css', + }), + new PrerenderSPAPlugin({ + staticDir: env.dist, + routes: env.routes, + renderer: new Renderer({ + maxConcurrentRoutes: 1, + renderAfterElementExists: 'meta[name="description"]', // when vue-meta is ok + }), + postProcess(renderedRoute) { + // eslint-disable-next-line no-param-reassign + renderedRoute.html = renderedRoute.html + .replace(/