From 1eb23e12f757dc4e9a6161b1b933f35a761755c3 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 5 Sep 2019 15:39:52 +0200 Subject: [PATCH] Add contributors in about peertube page --- CREDITS.md | 34 +++- README.md | 2 +- .../about-instance.component.html | 14 +- ...about-peertube-contributors.component.html | 13 ++ ...about-peertube-contributors.component.scss | 15 ++ .../about-peertube-contributors.component.ts | 19 ++ .../about-peertube.component.html | 163 +++++++++--------- .../about-peertube.component.scss | 43 ++++- client/src/app/+about/about.module.ts | 3 +- .../app/shared/renderer/markdown.service.ts | 19 +- client/src/assets/images/framasoft.png | Bin 0 -> 5232 bytes scripts/generate-code-contributors.ts | 4 +- 12 files changed, 224 insertions(+), 105 deletions(-) create mode 100644 client/src/app/+about/about-peertube/about-peertube-contributors.component.html create mode 100644 client/src/app/+about/about-peertube/about-peertube-contributors.component.scss create mode 100644 client/src/app/+about/about-peertube/about-peertube-contributors.component.ts create mode 100644 client/src/assets/images/framasoft.png diff --git a/CREDITS.md b/CREDITS.md index 1b8ef7370..09719974d 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,4 +1,4 @@ -# Code +# Code contributors * [Chocobozzz](https://github.com/Chocobozzz) * [rigelk](https://github.com/rigelk) @@ -8,10 +8,12 @@ * [Jorropo](https://github.com/Jorropo) * [buoyantair](https://github.com/buoyantair) * [bnjbvr](https://github.com/bnjbvr) + * [frankstrater](https://github.com/frankstrater) * [jankeromnes](https://github.com/jankeromnes) * [lucas-dclrcq](https://github.com/lucas-dclrcq) - * [DavidLibeau](https://github.com/DavidLibeau) * [JohnXLivingston](https://github.com/JohnXLivingston) + * [DavidLibeau](https://github.com/DavidLibeau) + * [fflorent](https://github.com/fflorent) * [kaiyou](https://github.com/kaiyou) * [ldidry](https://github.com/ldidry) * [McFlat](https://github.com/McFlat) @@ -21,25 +23,25 @@ * [NassimBounouas](https://github.com/NassimBounouas) * [thomaskuntzz](https://github.com/thomaskuntzz) * [rezonant](https://github.com/rezonant) + * [Wirebrass](https://github.com/Wirebrass) * [clementbrizard](https://github.com/clementbrizard) * [LecygneNoir](https://github.com/LecygneNoir) * [okhin](https://github.com/okhin) * [daftaupe](https://github.com/daftaupe) * [tcitworld](https://github.com/tcitworld) - * [fflorent](https://github.com/fflorent) * [dedesite](https://github.com/dedesite) * [Nautigsam](https://github.com/Nautigsam) * [scanlime](https://github.com/scanlime) * [am97](https://github.com/am97) * [dadall](https://github.com/dadall) * [jonathanraes](https://github.com/jonathanraes) - * [Wirebrass](https://github.com/Wirebrass) * [yohanboniface](https://github.com/yohanboniface) * [anoadragon453](https://github.com/anoadragon453) * [auberanger](https://github.com/auberanger) * [darnuria](https://github.com/darnuria) * [rhaamo](https://github.com/rhaamo) * [mrflos](https://github.com/mrflos) + * [Yetangitu](https://github.com/Yetangitu) * [jocelynj](https://github.com/jocelynj) * [lucaspontoexe](https://github.com/lucaspontoexe) * [flyingrub](https://github.com/flyingrub) @@ -57,6 +59,7 @@ * [Anton-Latukha](https://github.com/Anton-Latukha) * [noplanman](https://github.com/noplanman) * [austinheap](https://github.com/austinheap) + * [BO41](https://github.com/BO41) * [benabbottnz](https://github.com/benabbottnz) * [ewft](https://github.com/ewft) * [bradsk88](https://github.com/bradsk88) @@ -67,7 +70,6 @@ * [ebrehault](https://github.com/ebrehault) * [DatBewar](https://github.com/DatBewar) * [ReK2Fernandez](https://github.com/ReK2Fernandez) - * [Yetangitu](https://github.com/Yetangitu) * [grizio](https://github.com/grizio) * [Glandos](https://github.com/Glandos) * [lanodan](https://github.com/lanodan) @@ -80,6 +82,7 @@ * [pichouk](https://github.com/pichouk) * [LeoMouyna](https://github.com/LeoMouyna) * [LiPeK](https://github.com/LiPeK) + * [Findus23](https://github.com/Findus23) * [zapashcanon](https://github.com/zapashcanon) * [mart-e](https://github.com/mart-e) * [0mp](https://github.com/0mp) @@ -95,6 +98,7 @@ * [quentinDupont](https://github.com/quentinDupont) * [Quenty31](https://github.com/Quenty31) * [sundowndev](https://github.com/sundowndev) + * [robinkooli](https://github.com/robinkooli) * [sesn](https://github.com/sesn) * [ALSai](https://github.com/ALSai) * [Simounet](https://github.com/Simounet) @@ -103,14 +107,13 @@ * [FrozenDroid](https://github.com/FrozenDroid) * [fallen](https://github.com/fallen) * [melongbob](https://github.com/melongbob) - * [Zig-03](https://github.com/Zig-03) * [anmol26s](https://github.com/anmol26s) * [imbsky](https://github.com/imbsky) * [ctlaltdefeat](https://github.com/ctlaltdefeat) * [jomo](https://github.com/jomo) * [libertysoft3](https://github.com/libertysoft3) * [lsde](https://github.com/lsde) - * [memoryboxes](https://github.com/memoryboxes) + * [brain-zhang](https://github.com/brain-zhang) * [norrist](https://github.com/norrist) * [osauzet](https://github.com/osauzet) * [SansPseudoFix](https://github.com/SansPseudoFix) @@ -121,7 +124,7 @@ * [ewasion](https://github.com/ewasion) -# Translations +# Translation contributors * [abdhessuk](https://trad.framasoft.org/zanata/profile/view/abdhessuk) * [abidin24](https://trad.framasoft.org/zanata/profile/view/abidin24) @@ -196,6 +199,7 @@ * [abidin24](https://trad.framasoft.org/zanata/profile/view/abidin24) * [aditoo](https://trad.framasoft.org/zanata/profile/view/aditoo) * [alidemirtas](https://trad.framasoft.org/zanata/profile/view/alidemirtas) + * [anastasia](https://trad.framasoft.org/zanata/profile/view/anastasia) * [ariasuni](https://trad.framasoft.org/zanata/profile/view/ariasuni) * [autom](https://trad.framasoft.org/zanata/profile/view/autom) * [balaji](https://trad.framasoft.org/zanata/profile/view/balaji) @@ -203,13 +207,16 @@ * [bristow](https://trad.framasoft.org/zanata/profile/view/bristow) * [butterflyoffire](https://trad.framasoft.org/zanata/profile/view/butterflyoffire) * [c0dr](https://trad.framasoft.org/zanata/profile/view/c0dr) + * [canony](https://trad.framasoft.org/zanata/profile/view/canony) * [cat](https://trad.framasoft.org/zanata/profile/view/cat) * [chocobozzz](https://trad.framasoft.org/zanata/profile/view/chocobozzz) * [clerie](https://trad.framasoft.org/zanata/profile/view/clerie) * [curupira](https://trad.framasoft.org/zanata/profile/view/curupira) * [dhsets](https://trad.framasoft.org/zanata/profile/view/dhsets) + * [dibek](https://trad.framasoft.org/zanata/profile/view/dibek) * [digitalkiller](https://trad.framasoft.org/zanata/profile/view/digitalkiller) * [dwsage](https://trad.framasoft.org/zanata/profile/view/dwsage) + * [fkohrt](https://trad.framasoft.org/zanata/profile/view/fkohrt) * [flauta](https://trad.framasoft.org/zanata/profile/view/flauta) * [frankstrater](https://trad.framasoft.org/zanata/profile/view/frankstrater) * [gillux](https://trad.framasoft.org/zanata/profile/view/gillux) @@ -220,17 +227,25 @@ * [jhertel](https://trad.framasoft.org/zanata/profile/view/jhertel) * [joss2lyon](https://trad.framasoft.org/zanata/profile/view/joss2lyon) * [kekkotranslates](https://trad.framasoft.org/zanata/profile/view/kekkotranslates) + * [kingu](https://trad.framasoft.org/zanata/profile/view/kingu) * [kittybecca](https://trad.framasoft.org/zanata/profile/view/kittybecca) + * [kousha](https://trad.framasoft.org/zanata/profile/view/kousha) * [krkk](https://trad.framasoft.org/zanata/profile/view/krkk) + * [lapor](https://trad.framasoft.org/zanata/profile/view/lapor) * [laufor](https://trad.framasoft.org/zanata/profile/view/laufor) * [leeroyepold48](https://trad.framasoft.org/zanata/profile/view/leeroyepold48) * [lstamellos](https://trad.framasoft.org/zanata/profile/view/lstamellos) * [mablr](https://trad.framasoft.org/zanata/profile/view/mablr) * [marcinmalecki](https://trad.framasoft.org/zanata/profile/view/marcinmalecki) * [matograine](https://trad.framasoft.org/zanata/profile/view/matograine) + * [mayana](https://trad.framasoft.org/zanata/profile/view/mayana) * [mikeorlov](https://trad.framasoft.org/zanata/profile/view/mikeorlov) * [nin](https://trad.framasoft.org/zanata/profile/view/nin) + * [noncommutativegeo](https://trad.framasoft.org/zanata/profile/view/noncommutativegeo) * [norbipeti](https://trad.framasoft.org/zanata/profile/view/norbipeti) + * [nvivant](https://trad.framasoft.org/zanata/profile/view/nvivant) + * [osoitz](https://trad.framasoft.org/zanata/profile/view/osoitz) + * [ppnplus](https://trad.framasoft.org/zanata/profile/view/ppnplus) * [predatorix](https://trad.framasoft.org/zanata/profile/view/predatorix) * [quentin](https://trad.framasoft.org/zanata/profile/view/quentin) * [quentind](https://trad.framasoft.org/zanata/profile/view/quentind) @@ -238,11 +253,14 @@ * [robin](https://trad.framasoft.org/zanata/profile/view/robin) * [rond](https://trad.framasoft.org/zanata/profile/view/rond) * [s8321414](https://trad.framasoft.org/zanata/profile/view/s8321414) + * [sato_ss](https://trad.framasoft.org/zanata/profile/view/sato_ss) * [secreet](https://trad.framasoft.org/zanata/profile/view/secreet) + * [sercom_kc](https://trad.framasoft.org/zanata/profile/view/sercom_kc) * [severo](https://trad.framasoft.org/zanata/profile/view/severo) * [silkevicious](https://trad.framasoft.org/zanata/profile/view/silkevicious) * [sporiff](https://trad.framasoft.org/zanata/profile/view/sporiff) * [tekuteku](https://trad.framasoft.org/zanata/profile/view/tekuteku) + * [thecatjustmeow](https://trad.framasoft.org/zanata/profile/view/thecatjustmeow) * [tirifto](https://trad.framasoft.org/zanata/profile/view/tirifto) * [tmota](https://trad.framasoft.org/zanata/profile/view/tmota) * [tuxayo](https://trad.framasoft.org/zanata/profile/view/tuxayo) diff --git a/README.md b/README.md index 4a785478c..5ed7d5b4c 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Be part of a network of multiple small federated, interoperable video hosting pr

- Framasoft logo + Framasoft logo

diff --git a/client/src/app/+about/about-instance/about-instance.component.html b/client/src/app/+about/about-instance/about-instance.component.html index 9499bbe4e..25d416740 100644 --- a/client/src/app/+about/about-instance/about-instance.component.html +++ b/client/src/app/+about/about-instance/about-instance.component.html @@ -4,7 +4,7 @@
About {{ instanceName }} instance
-
Contact administrator
+
Contact administrator
@@ -16,10 +16,10 @@
{{ shortDescription }}
-
This instance is dedicated to sensitive/NSFW content.
+
This instance is dedicated to sensitive/NSFW content.
-
+
Administrators & sustainability
@@ -47,7 +47,7 @@

{{ businessModel }}

-
+
Information
@@ -57,7 +57,7 @@
-
+
Moderation
@@ -79,7 +79,7 @@
-
+
Other information
@@ -91,7 +91,7 @@
- +
diff --git a/client/src/app/+about/about-peertube/about-peertube-contributors.component.html b/client/src/app/+about/about-peertube/about-peertube-contributors.component.html new file mode 100644 index 000000000..997a6a3e1 --- /dev/null +++ b/client/src/app/+about/about-peertube/about-peertube-contributors.component.html @@ -0,0 +1,13 @@ +

Who made this software?

+ +

+ Developed with ❤ by Framasoft +

+ +

+ + Framasoft logo + +

+ +
diff --git a/client/src/app/+about/about-peertube/about-peertube-contributors.component.scss b/client/src/app/+about/about-peertube/about-peertube-contributors.component.scss new file mode 100644 index 000000000..9c3b0a46b --- /dev/null +++ b/client/src/app/+about/about-peertube/about-peertube-contributors.component.scss @@ -0,0 +1,15 @@ +@import '_variables'; +@import '_mixins'; + +/deep/ h1 { + font-size: 1rem; +} + +/deep/ ul { + padding: 0; + + li { + display: inline-block; + margin-right: 10px; + } +} diff --git a/client/src/app/+about/about-peertube/about-peertube-contributors.component.ts b/client/src/app/+about/about-peertube/about-peertube-contributors.component.ts new file mode 100644 index 000000000..fa2c0daa0 --- /dev/null +++ b/client/src/app/+about/about-peertube/about-peertube-contributors.component.ts @@ -0,0 +1,19 @@ +import { Component, OnInit } from '@angular/core' +import { MarkdownService } from '@app/shared/renderer' + +@Component({ + selector: 'my-about-peertube-contributors', + templateUrl: './about-peertube-contributors.component.html', + styleUrls: [ './about-peertube-contributors.component.scss' ] +}) +export class AboutPeertubeContributorsComponent implements OnInit { + creditsHtml: string + + private markdown = require('raw-loader!../../../../../CREDITS.md') + + constructor (private markdownService: MarkdownService) { } + + async ngOnInit () { + this.creditsHtml = await this.markdownService.completeMarkdownToHTML(this.markdown) + } +} diff --git a/client/src/app/+about/about-peertube/about-peertube.component.html b/client/src/app/+about/about-peertube/about-peertube.component.html index d3fc9a828..423f7bce7 100644 --- a/client/src/app/+about/about-peertube/about-peertube.component.html +++ b/client/src/app/+about/about-peertube/about-peertube.component.html @@ -14,84 +14,89 @@

-
-

P2P & Privacy

+
+ + +
+

P2P & Privacy

+ +

+ PeerTube uses the BitTorrent protocol to share bandwidth between users. + This implies that your IP address is stored in the instance's BitTorrent tracker as long as you download or watch the video. +

+ +
What are the consequences?
+ +

+ In theory, someone with enough technical skills could create a script that tracks which IP is downloading which video. + In practice, this is much more difficult because: +

+ +
    +
  • + An HTTP request has to be sent on each tracker for each video to spy. + If we want to spy all PeerTube's videos, we have to send as many requests as there are videos (so potentially a lot) +
  • + +
  • + For each request sent, the tracker returns random peers at a limited number. + For instance, if there are 1000 peers in the swarm and the tracker sends only 20 peers for each request, there must be at least 50 requests sent to know every peers in the swarm +
  • + +
  • + Those requests have to be sent regularly to know who starts/stops watching a video. It is easy to detect that kind of behaviour +
  • + +
  • + If an IP address is stored in the tracker, it doesn't mean that the person behind the IP (if this person exists) has watched the video +
  • + +
  • + The IP address is a vague information : usually, it regularly changes and can represent many persons or entities +
  • + +
  • + Web peers are not publicly accessible: because we use WebRTC inside the web browser (with the WebTorrent library), the protocol is different from classic BitTorrent. + When you are in a web browser, you send a signal containing your IP address to the tracker that will randomly choose other peers to forward the information to. + See this document for more information +
  • +
+ +

+ The worst-case scenario of an average person spying on their friends is quite unlikely. + There are much more effective ways to get that kind of information. +

+ +
How does PeerTube compare with YouTube?
+ +

+ The threats to privacy in YouTube are different from PeerTube's. + In YouTube's case, the platform gathers a huge amount of your personal information (not only your IP) to analyze them and track you. + Moreover, YouTube is owned by Google/Alphabet, a company that tracks you across many websites (via AdSense or Google Analytics). +

+ +
What can I do to limit the exposure of my IP address?
+ +

+ Your IP address is public so every time you consult a website, there is a number of actors (in addition to the final website) seeing your IP in their connection logs: ISP/routers/trackers/CDN and more. + PeerTube is transparent about it: we warn you that if you want to keep your IP private, you must use a VPN or Tor Browser. + Thinking that removing P2P from PeerTube will give you back anonymity doesn't make sense. +

+ +
What will be done to mitigate this problem?
+ +

+ PeerTube is in its early stages, and want to deliver the best countermeasures possible by the time the stable is released. + In the meantime, we want to test different ideas related to this issue: +

+ +
    +
  • Set a limit to the number of peers sent by the tracker
  • +
  • Set a limit on the request frequency received by the tracker (being tested)
  • +
  • Ring a bell if there are unusual requests (being tested)
  • +
  • Disable P2P from the administration interface
  • +
  • An automatic video redundancy program: we wouldn't know if the IP downloaded the video on purpose or if it was the automatized program
  • +
+
-

- PeerTube uses the BitTorrent protocol to share bandwidth between users. - This implies that your IP address is stored in the instance's BitTorrent tracker as long as you download or watch the video. -

- -
What are the consequences?
- -

- In theory, someone with enough technical skills could create a script that tracks which IP is downloading which video. - In practice, this is much more difficult because: -

- -
    -
  • - An HTTP request has to be sent on each tracker for each video to spy. - If we want to spy all PeerTube's videos, we have to send as many requests as there are videos (so potentially a lot) -
  • - -
  • - For each request sent, the tracker returns random peers at a limited number. - For instance, if there are 1000 peers in the swarm and the tracker sends only 20 peers for each request, there must be at least 50 requests sent to know every peers in the swarm -
  • - -
  • - Those requests have to be sent regularly to know who starts/stops watching a video. It is easy to detect that kind of behaviour -
  • - -
  • - If an IP address is stored in the tracker, it doesn't mean that the person behind the IP (if this person exists) has watched the video -
  • - -
  • - The IP address is a vague information : usually, it regularly changes and can represent many persons or entities -
  • - -
  • - Web peers are not publicly accessible: because we use WebRTC inside the web browser (with the WebTorrent library), the protocol is different from classic BitTorrent. - When you are in a web browser, you send a signal containing your IP address to the tracker that will randomly choose other peers to forward the information to. - See this document for more information -
  • -
- -

- The worst-case scenario of an average person spying on their friends is quite unlikely. - There are much more effective ways to get that kind of information. -

- -
How does PeerTube compare with YouTube?
- -

- The threats to privacy in YouTube are different from PeerTube's. - In YouTube's case, the platform gathers a huge amount of your personal information (not only your IP) to analyze them and track you. - Moreover, YouTube is owned by Google/Alphabet, a company that tracks you across many websites (via AdSense or Google Analytics). -

- -
What can I do to limit the exposure of my IP address?
- -

- Your IP address is public so every time you consult a website, there is a number of actors (in addition to the final website) seeing your IP in their connection logs: ISP/routers/trackers/CDN and more. - PeerTube is transparent about it: we warn you that if you want to keep your IP private, you must use a VPN or Tor Browser. - Thinking that removing P2P from PeerTube will give you back anonymity doesn't make sense. -

- -
What will be done to mitigate this problem?
- -

- PeerTube is in its early stages, and want to deliver the best countermeasures possible by the time the stable is released. - In the meantime, we want to test different ideas related to this issue: -

- -
    -
  • Set a limit to the number of peers sent by the tracker
  • -
  • Set a limit on the request frequency received by the tracker (being tested)
  • -
  • Ring a bell if there are unusual requests (being tested)
  • -
  • Disable P2P from the administration interface
  • -
  • An automatic video redundancy program: we wouldn't know if the IP downloaded the video on purpose or if it was the automatized program
  • -
diff --git a/client/src/app/+about/about-peertube/about-peertube.component.scss b/client/src/app/+about/about-peertube/about-peertube.component.scss index 0d2e2bb68..8fca53e90 100644 --- a/client/src/app/+about/about-peertube/about-peertube.component.scss +++ b/client/src/app/+about/about-peertube/about-peertube.component.scss @@ -2,12 +2,12 @@ @import '_mixins'; .about-peertube-title { - font-size: 25px; - font-weight: bold; + font-size: 20px; + font-weight: $font-semibold; margin-bottom: 15px; } -.section-title { +/deep/ .section-title { font-weight: $font-semibold; font-size: 20px; margin-bottom: 5px; @@ -17,6 +17,41 @@ margin-bottom: 30px; } +.description, +.p2p-privacy, +my-about-peertube-contributors { + /deep/ { + p, li { + font-size: 15px; + } + } +} + .p2p-privacy-title { margin-top: 15px; -} \ No newline at end of file +} + +.privacy-contributors { + display: flex; + flex-direction: row; + + > div, + > my-about-peertube-contributors { + flex-basis: 100%; + display: block; + } + + .p2p-privacy { + h6 { + font-size: 20px; + } + } + + my-about-peertube-contributors { + margin: 0 40px 40px 0; + } + + @media screen and (max-width: $small-view) { + flex-direction: column; + } +} diff --git a/client/src/app/+about/about.module.ts b/client/src/app/+about/about.module.ts index 49a7a52f8..14bf76e55 100644 --- a/client/src/app/+about/about.module.ts +++ b/client/src/app/+about/about.module.ts @@ -1,5 +1,4 @@ import { NgModule } from '@angular/core' - import { AboutRoutingModule } from './about-routing.module' import { AboutComponent } from './about.component' import { SharedModule } from '../shared' @@ -7,6 +6,7 @@ import { AboutInstanceComponent } from '@app/+about/about-instance/about-instanc import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertube.component' import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' import { AboutFollowsComponent } from '@app/+about/about-follows/about-follows.component' +import { AboutPeertubeContributorsComponent } from '@app/+about/about-peertube/about-peertube-contributors.component' @NgModule({ imports: [ @@ -19,6 +19,7 @@ import { AboutFollowsComponent } from '@app/+about/about-follows/about-follows.c AboutInstanceComponent, AboutPeertubeComponent, AboutFollowsComponent, + AboutPeertubeContributorsComponent, ContactAdminModalComponent ], diff --git a/client/src/app/shared/renderer/markdown.service.ts b/client/src/app/shared/renderer/markdown.service.ts index 9a9066351..0e24f3085 100644 --- a/client/src/app/shared/renderer/markdown.service.ts +++ b/client/src/app/shared/renderer/markdown.service.ts @@ -13,9 +13,11 @@ export class MarkdownService { 'list' ] static ENHANCED_RULES = MarkdownService.TEXT_RULES.concat([ 'image' ]) + static COMPLETE_RULES = MarkdownService.ENHANCED_RULES.concat([ 'block', 'inline', 'heading', 'html_inline', 'html_block', 'paragraph' ]) private textMarkdownIt: MarkdownIt private enhancedMarkdownIt: MarkdownIt + private completeMarkdownIt: MarkdownIt async textMarkdownToHTML (markdown: string) { if (!markdown) return '' @@ -39,11 +41,22 @@ export class MarkdownService { return this.avoidTruncatedTags(html) } - private async createMarkdownIt (rules: string[]) { - // FIXME: import('..') returns a struct module, containing a "default" field corresponding to our sanitizeHtml function + async completeMarkdownToHTML (markdown: string) { + if (!markdown) return '' + + if (!this.completeMarkdownIt) { + this.completeMarkdownIt = await this.createMarkdownIt(MarkdownService.COMPLETE_RULES, true) + } + + const html = this.completeMarkdownIt.render(markdown) + return this.avoidTruncatedTags(html) + } + + private async createMarkdownIt (rules: string[], html = false) { + // FIXME: import('...') returns a struct module, containing a "default" field corresponding to our sanitizeHtml function const MarkdownItClass: typeof import ('markdown-it') = (await import('markdown-it') as any).default - const markdownIt = new MarkdownItClass('zero', { linkify: true, breaks: true }) + const markdownIt = new MarkdownItClass('zero', { linkify: true, breaks: true, html }) for (const rule of rules) { markdownIt.enable(rule) diff --git a/client/src/assets/images/framasoft.png b/client/src/assets/images/framasoft.png new file mode 100644 index 0000000000000000000000000000000000000000..57be8c2191bdacb70e3532061d2e1af5dbcb0bdf GIT binary patch literal 5232 zcma)AXHXN|(hj{tsG;{FO?ofVNkWr^-h>c9dXXkYKmjRI0*EMrD7+Lw1f(}(2vP+J zT@YgEMY?q4p47YM#&p!2)Bpg04gxlS-C+K|my-PE z)g%fg+yJSUu9+?XP@hF}=|*;=^Sgs#W&l8xC;$+j2mqYlnBvy~fG}AAVAB-k`teiy~Y zSWUj{`+Q>VnFF8-B$txe;<5tPkkL#!q<7ePcSytP5z@^&Qy&awYTr#ZInPi1fPK#h zw{K#ts28q%mZtJ?I@~q{qKv}X@+*!n3l{FsH z1#%bN@?ko3z_ahP9F?RaBs(O{V_|8g+78)cI}m&!dR8Z_{u-bkFHV!Bhql9apgnOk zQo-94E;?)^YmEG;*swv`+OBnK3BeVEL5$O*g~fxj)Hvtuxr65jv!tS#{0zOfdC<#E z7HrD3yr8lSH?a%ZBPvaXPROpKn}ZG+;VTC=987$&Z-MH3_^h^4GpV3B$p&EC3&1SEJ9xm2Y+~O&O@`hhr{sZ#~?V z#ape#S^&<8+}6NSI0qEt-&(ypDf~1v%epRrc~U2Ec=e1v`WnDMBqPYs1U%9&C(tAe zpf_~lmPwjWUfdK29f}5AEXY334=_byiI3lU<%iP77G?S$3X;9TM$ zDT+0+s1~CnO!;)s4-Z?OTkCDL&^`v-(J9Y}sY8uzKuScIQY!~?#j1PLLDhe@rmJHw zj;k^=>7Ngq$aIyFGw{CN;B{B~>mTe2+LOdmU0F6TyYKcks8dW+r6d#&QIuo{?vw5K ziauVhT8F#ndXy;uWm!%Ig3!(WVIyj5mB#rpwin00;Tx)vdnK<_B5E7_HKwC?cJ7B3 z_ue61l4DHoR3qb%3%Sw>Ct9qN*2vk&@G}_(4K;`YEJ%E?d%t|f-WmGrE_8X5O-@Xd zP1V?!mYWrp>%Gl3*YzjcF)GKSTkWAqjOeB1zrPfP;O;-9XuMuu(q_i_Z%$N6aAt%{ zmPcyVZfDww6 zOyY}v1=&qbO@(VffB*bs{K2Km;YNEuR%%=g7Je7~!x#M$L-$vfcHeR3JfSyU!*A$4 zI7J-BX6_WtSy$T70Q(uW_2GS6+nK8F@A)?PXjwl~Ofuyl8q!04wLamnz9<@j$!!Ln_)f zJ|B`RT9urfY_8jjkZTHAfhze-R3fM(f*XlcST5WuO79n+_{un@Bx#Ds%y(?E$y6A| zYcPH`uf1fetgWeey@!9oAn!XR1<2%9!{msV*fri~%*e>-jq(c!2Y^VfZMV4~NxPW# zxke#yVU>!O)uSVh2}e#(hdiUc|m8-DvrH3k14iZ&k{48y{dZgNQ3 z)y4l~@&Y%t>Sd}SbWL>0OfX^Wr}ucEi1?7d z);pz@d^zOfO~0;;8S{CZA8l}J#}se{cA{=_wZo$yV{Gu_BcBs6ATh_B%E5#{e%)zD z6!05=du=}kY`Xgi9VeK?#<#N?B{�iNKXP0vquM{hc< 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