Grammalecte  Check-in [7217592c77]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:merge comdic
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256:7217592c77f23a9e439f41473408c523dadc5c7b57117a66d605a52efff780ea
User & Date: olr 2019-04-03 14:18:54
Context
2019-04-03
14:34
[tb] remove extended dictionary labels check-in: 77ef752105 user: olr tags: tb, trunk
14:18
merge comdic check-in: 7217592c77 user: olr tags: trunk
13:31
[fr] mise à jour du dictionnaire check-in: 0b6aa78216 user: olr tags: fr, trunk
2019-03-15
14:08
[fx] fix merge Closed-Leaf check-in: e8375db0be user: olr tags: comdic, fx
Changes

Changes to gc_core/js/lang_core/gc_engine.js.

    40     40   let _sAppContext = "";                                  // what software is running
    41     41   let _dOptions = null;
    42     42   let _dOptionsColors = null;
    43     43   let _oSpellChecker = null;
    44     44   let _oTokenizer = null;
    45     45   let _aIgnoredRules = new Set();
    46     46   
           47  +
           48  +function echo (x) {
           49  +    console.log(x);
           50  +    return true;
           51  +}
    47     52   
    48     53   
    49     54   var gc_engine = {
    50     55   
    51     56       //// Informations
    52     57   
    53     58       lang: "${lang}",

Changes to gc_lang/fr/config.ini.

    13     13   description = Correcteur grammatical pour le français.
    14     14   extras = README_fr.txt
    15     15   logo = logo.png
    16     16   
    17     17   # main dictionary
    18     18   lexicon_src = lexicons/French.lex
    19     19   dic_filenames = fr-allvars,fr-classic,fr-reform
    20         -dic_name = Français,Français (Classique/Moderne),Français (Réforme 1990)
           20  +dic_name = fr-allvars,fr-classic,fr-reform
           21  +dic_description = Français (Toutes variantes),Français (Classique),Français (Réforme 1990)
    21     22   dic_filter = ,[*CMPX]$,[*RPX]$
    22     23   dic_default_filename_py = fr-allvars
    23     24   dic_default_filename_js = fr-allvars
    24     25   # extended dictionary
    25     26   lexicon_extended_src = lexicons/French.extended.lex
    26     27   dic_extended_filename = fr.extended
    27         -dic_extended_name = Français - dictionnaire étendu
           28  +dic_extended_name = fr.extended
           29  +dic_extended_description = Français - dictionnaire étendu
    28     30   # community dictionary
    29     31   lexicon_community_src = lexicons/French.community.lex
    30     32   dic_community_filename = fr.community
    31         -dic_community_name = Français - dictionnaire communautaire
           33  +dic_community_name = fr.community
           34  +dic_community_description = Français - dictionnaire communautaire
    32     35   # personal dictionary
    33     36   lexicon_personal_src = lexicons/French.personal.lex
    34     37   dic_personal_filename = fr.personal
    35         -dic_personal_name = Français - dictionnaire personnel
           38  +dic_personal_name = fr.personal
           39  +dic_personal_description = Français - dictionnaire personnel
    36     40   # Finite state automaton compression: 1, 2 (experimental) or 3 (experimental)
    37     41   fsa_method = 1
    38     42   # stemming method: S for suffixes only, A for prefixes and suffixes
    39     43   stemming_method = S
    40     44   
    41     45   # LibreOffice
    42     46   unopkg = C:/Program Files/LibreOffice/program/unopkg.com

Changes to gc_lang/fr/dictionnaire/genfrdic.py.

   522    522                   hDst.write(oFlex.__str__(oStatsLex))
   523    523   
   524    524       def writeGrammarCheckerLexicon (self, spfDst, version):
   525    525           echo(' * Lexique simplifié >> [ {} ] '.format(spfDst))
   526    526           with open(spfDst[:-4]+".lex", 'w', encoding='utf-8', newline="\n") as hDst:
   527    527               hDst.write(MPLHEADER)
   528    528               hDst.write("# Lexique simplifié pour Grammalecte v{}\n# Licence : MPL v2.0\n\n".format(version))
   529         -            hDst.write("## LangCode: fr\n")
   530         -            hDst.write("## LangName: Français\n")
   531         -            hDst.write("## DicName: fr.commun\n")
   532         -            hDst.write("## Description: Français commun (toutes variantes)\n")
   533         -            hDst.write("## Author: Olivier R.\n\n")
   534    529               hDst.write(Flexion.simpleHeader())
   535    530               for oFlex in self.lFlexions:
   536    531                   hDst.write(oFlex.getGrammarCheckerRepr())
   537    532   
   538    533       def createFiles (self, spDst, lDictVars, nMode, bSimplified):
   539    534           sDicName = PREFIX_DICT_PATH + self.sVersion
   540    535           spDic = spDst + '/' + sDicName

Changes to gc_lang/fr/modules-js/lexicographe.js.

   457    457               if (aRes) {
   458    458                   yield aRes;
   459    459               }
   460    460           }
   461    461       }
   462    462   
   463    463       getListOfTokensReduc (sText, bInfo=true) {
   464         -        let aTokenList = this.getListOfTokens(sText.replace("'", "’").trim(), false);
   465         -        let iKey = 0;
          464  +        let lToken = this.getListOfTokens(sText.replace("'", "’").trim(), false);
          465  +        let iToken = 0;
   466    466           let aElem = [];
          467  +        if (lToken.length == 0) {
          468  +            return aElem;
          469  +        }
   467    470           do {
   468         -            let oToken = aTokenList[iKey];
          471  +            let oToken = lToken[iToken];
   469    472               let sMorphLoc = '';
   470    473               let aTokenTempList = [oToken];
   471    474               if (oToken.sType == "WORD" || oToken.sType == "WORD_ELIDED"){
   472         -                let iKeyTree = iKey + 1;
          475  +                let iLocEnd = iToken + 1;
   473    476                   let oLocNode = this.oLocGraph[oToken.sValue.toLowerCase()];
   474    477                   while (oLocNode) {
   475         -                    let oTokenNext = aTokenList[iKeyTree];
   476         -                    iKeyTree++;
          478  +                    let oTokenNext = lToken[iLocEnd];
          479  +                    iLocEnd++;
   477    480                       if (oTokenNext) {
   478    481                           oLocNode = oLocNode[oTokenNext.sValue.toLowerCase()];
   479    482                       }
   480         -                    if (oLocNode && iKeyTree <= aTokenList.length) {
          483  +                    if (oLocNode && iLocEnd <= lToken.length) {
   481    484                           sMorphLoc = oLocNode["_:_"];
   482    485                           aTokenTempList.push(oTokenNext);
   483    486                       } else {
   484    487                           break;
   485    488                       }
   486    489                   }
   487    490               }
   488    491   
   489    492               if (sMorphLoc) {
          493  +                // we have a locution
   490    494                   let sValue = '';
   491    495                   for (let oTokenWord of aTokenTempList) {
   492    496                       sValue += oTokenWord.sValue+' ';
   493    497                   }
   494    498                   let oTokenLocution = {
   495    499                       'nStart': aTokenTempList[0].nStart,
   496    500                       'nEnd': aTokenTempList[aTokenTempList.length-1].nEnd,
................................................................................
   520    524                           sValue: oTokenLocution.sValue,
   521    525                           aLabel: aFormatedTag,
   522    526                           aSubElem: aSubElem
   523    527                       });
   524    528                   } else {
   525    529                       aElem.push(oTokenLocution);
   526    530                   }
   527         -                iKey = iKey + aTokenTempList.length;
          531  +                iToken = iToken + aTokenTempList.length;
   528    532               } else {
          533  +                // No locution, we just add information
   529    534                   if (bInfo) {
   530    535                       let aRes = this.getInfoForToken(oToken);
   531    536                       if (aRes) {
   532    537                           aElem.push(aRes);
   533    538                       }
   534    539                   } else {
   535    540                       aElem.push(oToken);
   536    541                   }
   537         -                iKey++;
          542  +                iToken++;
   538    543               }
   539         -        } while (iKey < aTokenList.length);
          544  +        } while (iToken < lToken.length);
   540    545           return aElem;
   541    546       }
   542    547   }
   543    548   
   544    549   
   545    550   if (typeof(exports) !== 'undefined') {
   546    551       exports.Lexicographe = Lexicographe;
   547    552   }

Changes to gc_lang/fr/oxt/DictOptions/DictOptions.py.

    53     53           self.xDesktop = self.xSvMgr.createInstanceWithContext("com.sun.star.frame.Desktop", self.ctx)
    54     54           self.xDocument = self.xDesktop.getCurrentComponent()
    55     55           self.xOptionNode = helpers.getConfigSetting("/org.openoffice.Lightproof_grammalecte/Other/", True)
    56     56   
    57     57           # dialog
    58     58           self.xDialog = self.xSvMgr.createInstanceWithContext('com.sun.star.awt.UnoControlDialogModel', self.ctx)
    59     59           self.xDialog.Width = 200
    60         -        self.xDialog.Height = 310
           60  +        self.xDialog.Height = 285
    61     61           self.xDialog.Title = self.dUI.get('title', "#title#")
    62     62           xWindowSize = helpers.getWindowSize()
    63     63           self.xDialog.PositionX = int((xWindowSize.Width / 2) - (self.xDialog.Width / 2))
    64     64           self.xDialog.PositionY = int((xWindowSize.Height / 2) - (self.xDialog.Height / 2))
    65     65   
    66     66           # fonts
    67     67           xFDTitle = uno.createUnoStruct("com.sun.star.awt.FontDescriptor")
................................................................................
    75     75           xFDSubTitle.Name = "Verdana"
    76     76   
    77     77           # widget
    78     78           nX = 10
    79     79           nY1 = 10
    80     80           nY2 = nY1 + 60
    81     81           nY3 = nY2 + 25
    82         -        nY4 = nY3 + 25
    83         -        nY5 = nY4 + 45
    84         -        nY6 = nY5 + 95
           82  +        nY4 = nY3 + 45
           83  +        nY5 = nY4 + 95
    85     84   
    86     85           nWidth = self.xDialog.Width - 20
    87     86           nHeight = 10
    88     87   
    89     88           # Graphspell dictionary section
    90     89           self._addWidget("graphspell_section", 'FixedLine', nX, nY1, nWidth, nHeight, Label = self.dUI.get("graphspell_section", "#err"), FontDescriptor = xFDTitle)
    91     90           self.xMainDic = self._addWidget('activate_main', 'CheckBox', nX, nY1+15, nWidth, nHeight, Label = self.dUI.get('activate_main', "#err"), FontDescriptor = xFDSubTitle, TextColor = 0x000088, State = True)
    92     91           self._addWidget('activate_main_descr', 'FixedText', nX+10, nY1+25, nWidth-10, nHeight*2, Label = self.dUI.get('activate_main_descr', "#err"), MultiLine = True)
    93     92           self._addWidget('spelling', 'FixedText', nX+10, nY1+45, nWidth-80, nHeight, Label = self.dUI.get('spelling', "#err"), FontDescriptor = xFDSubTitle)
    94     93           self.xInfoDicButton = self._addWidget('info_dic_button', 'Button', nX+160, nY1+45, 12, 9, Label = "‹i›")
    95     94           self.xSelClassic = self._addWidget('classic', 'RadioButton', nX+10, nY1+55, 50, nHeight, Label = self.dUI.get('classic', "#err"))
    96     95           self.xSelReform = self._addWidget('reform', 'RadioButton', nX+65, nY1+55, 55, nHeight, Label = self.dUI.get('reform', "#err"))
    97     96           self.xSelAllvars = self._addWidget('allvars', 'RadioButton', nX+120, nY1+55, 60, nHeight, Label = self.dUI.get('allvars', "#err"))
    98         -        self.xExtendedDic = self._addWidget('activate_extended', 'CheckBox', nX, nY2+15, nWidth, nHeight, Label = self.dUI.get('activate_extended', "#err"), FontDescriptor = xFDSubTitle, TextColor = 0x000088, Enabled = False)
    99         -        self._addWidget('activate_extended_descr', 'FixedText', nX+10, nY2+25, nWidth-10, nHeight*1, Label = self.dUI.get('activate_extended_descr', "#err"), MultiLine = True)
   100         -        self.xCommunityDic = self._addWidget('activate_community', 'CheckBox', nX, nY3+15, nWidth, nHeight, Label = self.dUI.get('activate_community', "#err"), FontDescriptor = xFDSubTitle, TextColor = 0x000088, Enabled = False)
   101         -        self._addWidget('activate_community_descr', 'FixedText', nX+10, nY3+25, nWidth-10, nHeight*1, Label = self.dUI.get('activate_community_descr', "#err"), MultiLine = True)
   102         -        self.xPersonalDic = self._addWidget('activate_personal', 'CheckBox', nX, nY4+15, nWidth, nHeight, Label = self.dUI.get('activate_personal', "#err"), FontDescriptor = xFDSubTitle, TextColor = 0x000088)
   103         -        self._addWidget('activate_personal_descr', 'FixedText', nX+10, nY4+25, nWidth-10, nHeight*1, Label = self.dUI.get('activate_personal_descr', "#err"), MultiLine = True)
           97  +        self.xCommunityDic = self._addWidget('activate_community', 'CheckBox', nX, nY2+15, nWidth, nHeight, Label = self.dUI.get('activate_community', "#err"), FontDescriptor = xFDSubTitle, TextColor = 0x000088, Enabled = False)
           98  +        self._addWidget('activate_community_descr', 'FixedText', nX+10, nY2+25, nWidth-10, nHeight*1, Label = self.dUI.get('activate_community_descr', "#err"), MultiLine = True)
           99  +        self.xPersonalDic = self._addWidget('activate_personal', 'CheckBox', nX, nY3+15, nWidth, nHeight, Label = self.dUI.get('activate_personal', "#err"), FontDescriptor = xFDSubTitle, TextColor = 0x000088)
          100  +        self._addWidget('activate_personal_descr', 'FixedText', nX+10, nY3+25, nWidth-10, nHeight*1, Label = self.dUI.get('activate_personal_descr', "#err"), MultiLine = True)
   104    101           
   105    102           # Spell suggestion engine section
   106         -        self._addWidget("suggestion_section", 'FixedLine', nX, nY5, nWidth, nHeight, Label = self.dUI.get("suggestion_section", "#err"), FontDescriptor = xFDTitle)
   107         -        self.xGraphspellSugg = self._addWidget('activate_spell_sugg', 'CheckBox', nX, nY5+15, nWidth, nHeight, Label = self.dUI.get('activate_spell_sugg', "#err"))
   108         -        self._addWidget('activate_spell_sugg_descr', 'FixedText', nX, nY5+25, nWidth, nHeight*6, Label = self.dUI.get('activate_spell_sugg_descr', "#err"), MultiLine = True)
          103  +        self._addWidget("suggestion_section", 'FixedLine', nX, nY4, nWidth, nHeight, Label = self.dUI.get("suggestion_section", "#err"), FontDescriptor = xFDTitle)
          104  +        self.xGraphspellSugg = self._addWidget('activate_spell_sugg', 'CheckBox', nX, nY4+15, nWidth, nHeight, Label = self.dUI.get('activate_spell_sugg', "#err"))
          105  +        self._addWidget('activate_spell_sugg_descr', 'FixedText', nX, nY4+25, nWidth, nHeight*6, Label = self.dUI.get('activate_spell_sugg_descr', "#err"), MultiLine = True)
   109    106   
   110    107           # Restart message
   111         -        self._addWidget('restart', 'FixedText', nX, nY6, nWidth, nHeight*2, Label = self.dUI.get('restart', "#err"), FontDescriptor = xFDTitle, MultiLine = True, TextColor = 0x880000)
          108  +        self._addWidget('restart', 'FixedText', nX, nY5, nWidth, nHeight*2, Label = self.dUI.get('restart', "#err"), FontDescriptor = xFDTitle, MultiLine = True, TextColor = 0x880000)
   112    109   
   113    110           # Button
   114    111           self._addWidget('apply_button', 'Button', self.xDialog.Width-115, self.xDialog.Height-20, 50, 14, Label = self.dUI.get('apply_button', "#err"), FontDescriptor = xFDTitle, TextColor = 0x005500)
   115    112           self._addWidget('cancel_button', 'Button', self.xDialog.Width-60, self.xDialog.Height-20, 50, 14, Label = self.dUI.get('cancel_button', "#err"), FontDescriptor = xFDTitle, TextColor = 0x550000)
   116    113   
   117    114           self._loadOptions()
   118    115   
................................................................................
   131    128           self.xContainer.execute()
   132    129   
   133    130       # XActionListener
   134    131       def actionPerformed (self, xActionEvent):
   135    132           try:
   136    133               if xActionEvent.ActionCommand == 'Apply':
   137    134                   xChild = self.xOptionNode.getByName("o_fr")
   138         -                #xChild.setPropertyValue("use_extended_dic", self.xExtendedDic.State)
   139    135                   #xChild.setPropertyValue("use_community_dic", self.xCommunityDic.State)
   140    136                   xChild.setPropertyValue("use_personal_dic", self.xPersonalDic.State)
   141    137                   xChild.setPropertyValue("use_graphspell_sugg", self.xGraphspellSugg.State)
   142    138                   sMainDicName = "classic"
   143    139                   if self.xSelClassic.State:
   144    140                       sMainDicName = "classic"
   145    141                   elif self.xSelReform.State:
................................................................................
   165    161               traceback.print_exc()
   166    162   
   167    163       def _loadOptions (self):
   168    164           try:
   169    165               xChild = self.xOptionNode.getByName("o_fr")
   170    166               #self.xGraphspell.State = xChild.getPropertyValue("use_graphspell")
   171    167               self.xGraphspellSugg.State = xChild.getPropertyValue("use_graphspell_sugg")
   172         -            #self.xExtendedDic.State = xChild.getPropertyValue("use_extended_dic")
   173    168               #self.xCommunityDic.State = xChild.getPropertyValue("use_community_dic")
   174    169               self.xPersonalDic.State = xChild.getPropertyValue("use_personal_dic")
   175    170               sMainDicName = xChild.getPropertyValue("main_dic_name")
   176    171               if sMainDicName == "classic":
   177    172                   self.xSelClassic.State = 1
   178    173               elif sMainDicName == "reform":
   179    174                   self.xSelReform.State = 1

Changes to gc_lang/fr/oxt/DictOptions/LexiconEditor.py.

   407    407       @_waitPointer
   408    408       def saveLexicon (self):
   409    409           xGridDataModel = self.xGridModelLex.GridDataModel
   410    410           lEntry = []
   411    411           for i in range(xGridDataModel.RowCount):
   412    412               lEntry.append(xGridDataModel.getRowData(i))
   413    413           if lEntry:
   414         -            oDAWG = dawg.DAWG(lEntry, "S", "fr", "Français", "fr.personal")
          414  +            oDAWG = dawg.DAWG(lEntry, "S", "fr", "Français", "fr.personal", "Dictionnaire personnel")
   415    415               self.oPersonalDicJSON = oDAWG.getBinaryAsJSON()
   416    416               self.xOptionNode.setPropertyValue("personal_dic", json.dumps(self.oPersonalDicJSON, ensure_ascii=False))
   417    417               self.xSettingNode.commitChanges()
   418    418               self.xNumDic.Label = str(self.oPersonalDicJSON["nEntry"])
   419    419               self.xDateDic.Label = self.oPersonalDicJSON["sDate"]
   420    420           else:
   421    421               self.xOptionNode.setPropertyValue("personal_dic", "")

Changes to gc_lang/fr/oxt/DictOptions/do_strings.py.

    19     19           "activate_main": "Dictionnaire principal",
    20     20           "activate_main_descr": "Environ 83 000 entrées, 500 000 flexions.\nNi éditable, ni désactivable.",
    21     21           "spelling": "Orthographe",
    22     22           "spelling_descr": "Le dictionnaire “Classique” propose l’orthographe telle qu’elle est écrite aujourd’hui le plus couramment. C’est le dictionnaire recommandé. Il contient les graphies usuelles et classiques, certaines encore communément utilisées, d’autres désuètes.\n\nAvec le dictionnaire “Réforme 1990”, seule l’orthographe réformée est reconnue. Attendu que bon nombre de graphies réformées sont considérées comme erronées par beaucoup, ce dictionnaire est déconseillé. Les graphies passées dans l’usage sont déjà incluses dans le dictionnaire “Classique”.\n\nLe dictionnaire “Toutes variantes” contient toutes les graphies, classiques ou réformées, ainsi que d’autres plus rares encore. Ce dictionnaire est déconseillé à ceux qui ne connaissent pas très bien la langue française.",
    23     23           "allvars": "Toutes variantes",
    24     24           "classic": "Classique",
    25     25           "reform": "Réforme 1990",
    26         -        "activate_extended": "Dictionnaire étendu",
    27         -        "activate_extended_descr": "Fonctionnalité à venir",
    28     26           "activate_community": "Dictionnaire communautaire",
    29     27           "activate_community_descr": "Fonctionnalité à venir",
    30     28           "activate_personal": "Dictionnaire personnel",
    31     29           "activate_personal_descr": "Créable et éditable via l’éditeur lexical.",
    32     30   
    33     31           "restart": "Le changement ne prendra effet qu’après le redémarrage du logiciel.",
    34     32   
................................................................................
    50     48           "activate_main": "Main dictionary",
    51     49           "activate_main_descr": "About 83 000 entries, 500 000 flexions.\nNot editable, not deactivable.",
    52     50           "spelling": "Spelling",
    53     51           "spelling_descr": "The dictionary “Classic” offers the French spelling as it is written nowadays most often. This is the recommended dictionary. It contains usual and classical spellings, some of them still widely used, others obsolete.\n\nWith the dictionary “Reform 1990”, only the reformed spelling is recognized. As many of reformed spellings are considered erroneous by many people, this dictionary is unadvised. Reformed spellings commonly used are already included in the “Classic” dictionary.\n\nThe dictionary “All variants” contains all spelling variants, classical and reformed, and some others even rarer. This dictionary is unadvised for those who don’t know very well the French language.",
    54     52           "allvars": "All variants",
    55     53           "classic": "Classic",
    56     54           "reform": "Reform 1990",
    57         -        "activate_extended": "Extended dictionary",
    58         -        "activate_extended_descr": "Feature to come.",
    59     55           "activate_community": "Community dictionary",
    60     56           "activate_community_descr": "Feature to come.",
    61     57           "activate_personal": "Personal dictionary",
    62     58           "activate_personal_descr": "Creatible and editable via the lexicon editor.",
    63     59   
    64     60           "restart": "The modification will be effective only after restarting the software.",
    65     61   
    66     62           "apply_button": "Apply",
    67     63           "cancel_button": "Cancel",
    68     64       },
    69     65   }

Changes to gc_lang/fr/rules.grx.

   503    503   __[s>(p_MM_point)__
   504    504       MM[.] <<- ~>> "MM "
   505    505   __[s>(p_Mr_Mgr_Mme_point)__
   506    506       M(?:r|gr|me) [A-ZÉ]([.])(?=\W+[a-zéèêâîïû]) @@$ <<- ~1>> *
   507    507   
   508    508   # Patronyme réduit à une seule lettre
   509    509   __[s](p_prénom_lettre_point_patronyme)__
   510         -    ([A-ZÉÈÂÎ][\w-]+)[  ][A-ZÉÈÂ]([.])[  ]([A-ZÉÈÂ][\w-]+) @@0,$,$
          510  +    ([A-ZÉÈÂÎ][\w-]+)[  ][A-ZÉÈÂ]([.])[  ]([A-ZÉÈÂ][\w-]+) @@0,*,$
   511    511       <<- morph(\1, ":M[12]") and (morph(\3, ":(?:M[12]|V)") or not spell(\3)) ~2>> *
   512    512   __[s>(p_prénom_lettre_point)__
   513    513       ([A-ZÉÈÂÎ][\w-]+)[  ][A-ZÉÈÂ]([.]) @@0,$
   514    514       <<- morph(\1, ":M[12]") and after("^\\W+[a-zéèêîïâ]") ~2>> _
   515    515   
   516    516   # Patronymes composés avec Le/La/Les
   517    517   __[s](p_patronyme_composé_avec_le_la_les)__
................................................................................
   739    739   !!
   740    740   !!!! Ponctuation de fin de paragraphe                                                             !!
   741    741   !!
   742    742   !!
   743    743   
   744    744   __[i>/poncfin(poncfin_règle1)__
   745    745       ({w_1}) *$  @@0
   746         -    <<- before("\\w+(?:[.]|[   ][!?]) +(?:[A-ZÉÈÎ]\\w+|[ÀÔ])") -1>> \1.|\1 !|\1 ?                   # Il semble manquer une ponctuation finale (s’il s’agit d’un titre, le point final n’est pas requis).
          746  +    <<- before("\\w+(?:\\.|[   ][!?]) +(?:[A-ZÉÈÎ]\\w+|[ÀÔ])") -1>> \1.|\1 !|\1 ?                   # Il semble manquer une ponctuation finale (s’il s’agit d’un titre, le point final n’est pas requis).
   747    747   
   748    748   TEST: __poncfin__ Vraiment. Quel {{ennui}}
   749    749   TEST: Internet : le nouvel eldorado
   750    750   TEST: OMC-FMI : Les nouveaux maîtres du monde
   751    751   
   752    752   
   753    753   !!

Changes to gc_lang/fr/tb/content/lex_editor.js.

   474    474           document.getElementById("import_button").addEventListener("click", () => { this.import(); }, false);
   475    475       },
   476    476   
   477    477       build: function () {
   478    478           let xProgressNode = document.getElementById("wait_progress");
   479    479           let lEntry = oLexiconTable.getEntries();
   480    480           if (lEntry.length > 0) {
   481         -            let oDAWG = new DAWG(lEntry, "S", "fr", "Français", "fr.personal", xProgressNode);
          481  +            let oDAWG = new DAWG(lEntry, "S", "fr", "Français", "fr.personal", "Dictionnaire personnel", xProgressNode);
   482    482               let oJSON = oDAWG.createBinaryJSON(1);
   483    483               oFileHandler.saveFile("fr.personal.json", JSON.stringify(oJSON));
   484    484               this.oIBDAWG = new IBDAWG(oJSON);
   485    485               this.setDictData(this.oIBDAWG.nEntry, this.oIBDAWG.sDate);
   486    486               //browser.runtime.sendMessage({ sCommand: "setDictionary", dParam: {sType: "personal", oDict: oJSON}, dInfo: {} });
   487    487               enableElement("export_button");
   488    488           } else {

Changes to gc_lang/fr/webext/background.js.

   115    115       xGCEWorker.postMessage({
   116    116           sCommand: "setDictionaryOnOff",
   117    117           dParam: { sDictionary: sDictionary, bActivate: bActivate },
   118    118           dInfo: {}
   119    119       });
   120    120   }
   121    121   
   122         -function initSCOptions (dSavedOptions) {
   123         -    if (!dSavedOptions.hasOwnProperty("sc_options")) {
          122  +function initSCOptions (oData) {
          123  +    if (!oData.hasOwnProperty("sc_options")) {
   124    124           browser.storage.local.set({"sc_options": {
   125    125               extended: true,
   126    126               community: true,
   127    127               personal: true
   128    128           }});
   129         -        setDictionaryOnOff("extended", true);
   130    129           setDictionaryOnOff("community", true);
   131    130           setDictionaryOnOff("personal", true);
   132    131       } else {
   133         -        let dOptions = dSavedOptions.sc_options;
   134         -        setDictionaryOnOff("extended", dOptions["extended"]);
   135         -        setDictionaryOnOff("community", dOptions["community"]);
   136         -        setDictionaryOnOff("personal", dOptions["personal"]);
          132  +        setDictionaryOnOff("community", oData.sc_options["community"]);
          133  +        setDictionaryOnOff("personal", oData.sc_options["personal"]);
   137    134       }
   138    135   }
   139    136   
   140    137   function setDictionary (sDictionary, oDictionary) {
   141    138       xGCEWorker.postMessage({
   142    139           sCommand: "setDictionary",
   143    140           dParam: { sDictionary: sDictionary, oDict: oDictionary },
   144    141           dInfo: {}
   145    142       });
   146    143   }
   147    144   
   148         -function setSpellingDictionary (dSavedDictionary) {
   149         -    if (dSavedDictionary.hasOwnProperty("oExtendedDictionary")) {
   150         -        setDictionary("extended", dSavedDictionary["oExtendedDictionary"]);
          145  +function setSpellingDictionaries (oData) {
          146  +    if (oData.hasOwnProperty("oPersonalDictionary")) {
          147  +        // deprecated (to be removed in 2020)
          148  +        console.log("personal dictionary migration");
          149  +        browser.storage.local.set({ "personal_dictionary": oData["oPersonalDictionary"] });
          150  +        setDictionary("personal", oData["oPersonalDictionary"]);
          151  +        browser.storage.local.remove("oPersonalDictionary");
   151    152       }
   152         -    if (dSavedDictionary.hasOwnProperty("oCommunityDictionary")) {
   153         -        setDictionary("community", dSavedDictionary["oCommunityDictionary"]);
          153  +    if (oData.hasOwnProperty("personal_dictionary")) {
          154  +        setDictionary("personal", oData["personal_dictionary"]);
   154    155       }
   155         -    if (dSavedDictionary.hasOwnProperty("oPersonalDictionary")) {
   156         -        setDictionary("personal", dSavedDictionary["oPersonalDictionary"]);
          156  +    if (oData.hasOwnProperty("community_dictionary")) {
          157  +        setDictionary("community", oData["community_dictionary"]);
   157    158       }
   158    159   }
   159    160   
   160    161   function init () {
   161    162       if (bChrome) {
   162    163           browser.storage.local.get("gc_options", initGrammarChecker);
   163    164           browser.storage.local.get("ui_options", initUIOptions);
   164         -        browser.storage.local.get("oExtendedDictionary", setSpellingDictionary);
   165         -        browser.storage.local.get("oCommunityDictionary", setSpellingDictionary);
   166         -        browser.storage.local.get("oPersonalDictionary", setSpellingDictionary);
          165  +        browser.storage.local.get("personal_dictionary", setSpellingDictionaries);
          166  +        browser.storage.local.get("community_dictionary", setSpellingDictionaries);
          167  +        browser.storage.local.get("oPersonalDictionary", setSpellingDictionaries); // deprecated
   167    168           browser.storage.local.get("sc_options", initSCOptions);
   168    169           return;
   169    170       }
   170    171       browser.storage.local.get("gc_options").then(initGrammarChecker, showError);
   171    172       browser.storage.local.get("ui_options").then(initUIOptions, showError);
   172         -    browser.storage.local.get("oExtendedDictionary").then(setSpellingDictionary, showError);
   173         -    browser.storage.local.get("oCommunityDictionary").then(setSpellingDictionary, showError);
   174         -    browser.storage.local.get("oPersonalDictionary").then(setSpellingDictionary, showError);
          173  +    browser.storage.local.get("personal_dictionary").then(setSpellingDictionaries, showError);
          174  +    browser.storage.local.get("community_dictionary").then(setSpellingDictionaries, showError);
          175  +    browser.storage.local.get("oPersonalDictionary").then(setSpellingDictionaries, showError); // deprecated
   175    176       browser.storage.local.get("sc_options").then(initSCOptions, showError);
   176    177   }
   177    178   
   178    179   init();
   179    180   
   180    181   
   181    182   browser.runtime.onInstalled.addListener(function (oDetails) {
................................................................................
   213    214           case "setDictionary":
   214    215           case "setDictionaryOnOff":
   215    216               xGCEWorker.postMessage(oRequest);
   216    217               break;
   217    218           case "openURL":
   218    219               browser.tabs.create({url: dParam.sURL});
   219    220               break;
          221  +        case "openConjugueurTab":
          222  +            openConjugueurTab();
          223  +            break;
          224  +        case "openLexiconEditor":
          225  +            openLexiconEditor(dParam["dictionary"]);
          226  +            break;
          227  +        case "openDictionaries":
          228  +            openDictionaries();
          229  +            break;
   220    230           default:
   221    231               console.log("[background] Unknown command: " + sCommand);
   222    232               console.log(oRequest);
   223    233       }
   224    234       //sendResponse({response: "response from background script"});
   225    235   }
   226    236   
   227    237   browser.runtime.onMessage.addListener(handleMessage);
   228    238   
   229    239   
   230    240   function handleConnexion (xPort) {
          241  +    // Messages from tabs
   231    242       let iPortId = xPort.sender.tab.id; // identifier for the port: each port can be found at dConnx[iPortId]
   232    243       dConnx.set(iPortId, xPort);
   233    244       xPort.onMessage.addListener(function (oRequest) {
   234    245           let {sCommand, dParam, dInfo} = oRequest;
   235    246           switch (sCommand) {
   236    247               case "parse":
   237    248               case "parseAndSpellcheck":
................................................................................
   275    286   browser.contextMenus.create({ id: "rightClickLxgEditableNode",  title: "Lexicographe (zone de texte)",              contexts: ["editable"] });
   276    287   browser.contextMenus.create({ id: "rightClickGCEditableNode",   title: "Correction grammaticale (zone de texte)",   contexts: ["editable"] });
   277    288   browser.contextMenus.create({ id: "separator_editable",         type: "separator",                                  contexts: ["editable"] });
   278    289   // Page
   279    290   browser.contextMenus.create({ id: "rightClickLxgPage",          title: "Lexicographe (page)",                       contexts: ["all"] }); // on all parts, due to unwanted selection
   280    291   browser.contextMenus.create({ id: "rightClickGCPage",           title: "Correction grammaticale (page)",            contexts: ["all"] });
   281    292   browser.contextMenus.create({ id: "separator_page",             type: "separator",                                  contexts: ["all"] });
   282         -// Conjugueur
          293  +// Tools
   283    294   browser.contextMenus.create({ id: "conjugueur_window",          title: "Conjugueur [fenêtre]",                      contexts: ["all"] });
   284    295   browser.contextMenus.create({ id: "conjugueur_tab",             title: "Conjugueur [onglet]",                       contexts: ["all"] });
          296  +browser.contextMenus.create({ id: "dictionaries",               title: "Dictionnaires",                             contexts: ["all"] });
          297  +browser.contextMenus.create({ id: "lexicon_editor",             title: "Éditeur lexical",                           contexts: ["all"] });
   285    298   // Rescan page
   286    299   browser.contextMenus.create({ id: "separator_rescan",           type: "separator",                                  contexts: ["editable"] });
   287    300   browser.contextMenus.create({ id: "rescanPage",                 title: "Rechercher à nouveau les zones de texte",   contexts: ["editable"] });
   288    301   
   289    302   
   290    303   browser.contextMenus.onClicked.addListener(function (xInfo, xTab) {
   291    304       // xInfo = https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/contextMenus/OnClickData
................................................................................
   321    334           // conjugueur
   322    335           case "conjugueur_window":
   323    336               openConjugueurWindow();
   324    337               break;
   325    338           case "conjugueur_tab":
   326    339               openConjugueurTab();
   327    340               break;
          341  +        case "lexicon_editor":
          342  +            openLexiconEditor();
          343  +            break;
          344  +        case "dictionaries":
          345  +            openDictionaries();
          346  +            break;
   328    347           // rescan page
   329    348           case "rescanPage":
   330    349               let xPort = dConnx.get(xTab.id);
   331    350               xPort.postMessage({sActionDone: "rescanPage"});
   332    351               break;
   333    352           default:
   334    353               console.log("[Background] Unknown menu id: " + xInfo.menuItemId);
................................................................................
   339    358   
   340    359   
   341    360   /*
   342    361       Keyboard shortcuts
   343    362   */
   344    363   browser.commands.onCommand.addListener(function (sCommand) {
   345    364       switch (sCommand) {
          365  +        case "lexicographer":
          366  +            sendCommandToCurrentTab("shortcutLexicographer");
          367  +            break;
          368  +        case "text_formatter":
          369  +            sendCommandToCurrentTab("shortcutTextFormatter");
          370  +            break;
          371  +        case "grammar_checker":
          372  +            sendCommandToCurrentTab("shortcutGrammarChecker");
          373  +            break;
   346    374           case "conjugueur_tab":
   347    375               openConjugueurTab();
   348    376               break;
   349    377           case "conjugueur_window":
   350    378               openConjugueurWindow();
   351    379               break;
   352         -        case "lex_editor":
   353         -            openLexEditor();
          380  +        case "lexicon_editor":
          381  +            openLexiconEditor();
          382  +            break;
          383  +        case "dictionaries":
          384  +            openDictionaries();
   354    385               break;
   355    386       }
   356    387   });
   357    388   
          389  +
          390  +/*
          391  +    Tabs
          392  +*/
          393  +let nTabLexiconEditor = null;
          394  +let nTabDictionaries = null;
          395  +let nTabConjugueur = null;
          396  +
          397  +browser.tabs.onRemoved.addListener(function (nTabId, xRemoveInfo) {
          398  +    switch (nTabId) {
          399  +        case nTabLexiconEditor: nTabLexiconEditor = null; break;
          400  +        case nTabDictionaries:  nTabDictionaries = null; break;
          401  +        case nTabConjugueur:    nTabConjugueur = null; break;
          402  +    }
          403  +});
          404  +
   358    405   
   359    406   /*
   360    407       Actions
   361    408   */
   362    409   
   363    410   function storeGCOptions (dOptions) {
   364    411       if (dOptions instanceof Map) {
................................................................................
   368    415   }
   369    416   
   370    417   function sendCommandToTab (sCommand, iTab) {
   371    418       let xTabPort = dConnx.get(iTab);
   372    419       xTabPort.postMessage({sActionDone: sCommand, result: null, dInfo: null, bEnd: false, bError: false});
   373    420   }
   374    421   
   375         -function openLexEditor () {
          422  +function sendCommandToCurrentTab (sCommand) {
          423  +    console.log(sCommand);
          424  +    if (bChrome) {
          425  +        browser.tabs.query({ currentWindow: true, active: true }, (lTabs) => {
          426  +            for (let xTab of lTabs) {
          427  +                console.log(xTab);
          428  +                browser.tabs.sendMessage(xTab.id, {sActionRequest: sCommand});
          429  +            }
          430  +        });
          431  +        return;
          432  +    }
          433  +    browser.tabs.query({ currentWindow: true, active: true }).then((lTabs) => {
          434  +        for (let xTab of lTabs) {
          435  +            console.log(xTab);
          436  +            browser.tabs.sendMessage(xTab.id, {sActionRequest: sCommand});
          437  +        }
          438  +    }, onError);
          439  +}
          440  +
          441  +function openLexiconEditor (sName="__personal__") {
          442  +    if (nTabLexiconEditor === null) {
   376    443       if (bChrome) {
   377    444           browser.tabs.create({
   378    445               url: browser.extension.getURL("panel/lex_editor.html")
   379         -        });
          446  +            }, onLexiconEditorOpened);
   380    447           return;
   381    448       }
   382    449       let xLexEditor = browser.tabs.create({
   383    450           url: browser.extension.getURL("panel/lex_editor.html")
   384    451       });
   385         -    xLexEditor.then(onCreated, onError);
          452  +        xLexEditor.then(onLexiconEditorOpened, onError);
          453  +    }
          454  +    else {
          455  +        browser.tabs.update(nTabLexiconEditor, {active: true});
          456  +    }
          457  +}
          458  +
          459  +function onLexiconEditorOpened (xTab) {
          460  +    nTabLexiconEditor = xTab.id;
          461  +}
          462  +
          463  +function openDictionaries () {
          464  +    if (nTabDictionaries === null) {
          465  +        if (bChrome) {
          466  +            browser.tabs.create({
          467  +                url: browser.extension.getURL("panel/dictionaries.html")
          468  +            }, onDictionariesOpened);
          469  +            return;
          470  +        }
          471  +        let xLexEditor = browser.tabs.create({
          472  +            url: browser.extension.getURL("panel/dictionaries.html")
          473  +        });
          474  +        xLexEditor.then(onDictionariesOpened, onError);
          475  +    }
          476  +    else {
          477  +        browser.tabs.update(nTabDictionaries, {active: true});
          478  +    }
          479  +}
          480  +
          481  +function onDictionariesOpened (xTab) {
          482  +    nTabDictionaries = xTab.id;
   386    483   }
   387    484   
   388    485   function openConjugueurTab () {
          486  +    if (nTabDictionaries === null) {
   389    487       if (bChrome) {
   390    488           browser.tabs.create({
   391    489               url: browser.extension.getURL("panel/conjugueur.html")
   392         -        });
          490  +            }, onConjugueurOpened);
   393    491           return;
   394    492       }
   395    493       let xConjTab = browser.tabs.create({
   396    494           url: browser.extension.getURL("panel/conjugueur.html")
   397    495       });
   398         -    xConjTab.then(onCreated, onError);
          496  +        xConjTab.then(onConjugueurOpened, onError);
          497  +    }
          498  +    else {
          499  +        browser.tabs.update(nTabConjugueur, {active: true});
          500  +    }
          501  +}
          502  +
          503  +function onConjugueurOpened (xTab) {
          504  +    nTabConjugueur = xTab.id;
   399    505   }
   400    506   
   401    507   function openConjugueurWindow () {
   402    508       if (bChrome) {
   403    509           browser.windows.create({
   404    510               url: browser.extension.getURL("panel/conjugueur.html"),
   405    511               type: "popup",
................................................................................
   410    516       }
   411    517       let xConjWindow = browser.windows.create({
   412    518           url: browser.extension.getURL("panel/conjugueur.html"),
   413    519           type: "popup",
   414    520           width: 710,
   415    521           height: 980
   416    522       });
   417         -    xConjWindow.then(onCreated, onError);
   418    523   }
   419    524   
   420    525   
   421         -function onCreated (xWindowInfo) {
   422         -    //console.log(`Created window: ${xWindowInfo.id}`);
   423         -}
   424         -
   425         -function onError (error) {
   426         -    console.log(`Error: ${error}`);
          526  +function onError (e) {
          527  +    console.error(e);
   427    528   }

Changes to gc_lang/fr/webext/content_scripts/init.js.

    99     99                       this.nMenu += 1;
   100    100                   }
   101    101               }
   102    102           }
   103    103       },
   104    104   
   105    105       observePage: function () {
   106         -        /*
   107         -            When a textarea is added via jascript we add the menu :)
   108         -        */
          106  +        //    When a textarea is added via jascript we add the menu
   109    107           let that = this;
   110    108           this.xObserver = new MutationObserver(function (mutations) {
   111    109               mutations.forEach(function (mutation) {
   112    110                   for (let i = 0;  i < mutation.addedNodes.length;  i++){
   113    111                       if (mutation.addedNodes[i].tagName == "TEXTAREA") {
   114    112                           if (that.oOptions === null || that.oOptions.textarea) {
   115    113                               oGrammalecte.lMenu.push(new GrammalecteMenu(oGrammalecte.nMenu, mutation.addedNodes[i]));
................................................................................
   253    251   /*
   254    252       Connexion to the background
   255    253   */
   256    254   let xGrammalectePort = browser.runtime.connect({name: "content-script port"});
   257    255   
   258    256   xGrammalectePort.onMessage.addListener(function (oMessage) {
   259    257       let {sActionDone, result, dInfo, bEnd, bError} = oMessage;
   260         -    let sText = "";
   261    258       switch (sActionDone) {
   262    259           case "init":
   263    260               oGrammalecte.sExtensionUrl = oMessage.sUrl;
   264    261               // Start
   265    262               oGrammalecte.listenRightClick();
   266    263               oGrammalecte.createMenus();
   267    264               oGrammalecte.observePage();
................................................................................
   289    286           /*
   290    287               Commands received from the context menu
   291    288               (Context menu are initialized in background)
   292    289           */
   293    290           // Grammar checker commands
   294    291           case "rightClickGCEditableNode":
   295    292               if (oGrammalecte.xRightClickedNode !== null) {
   296         -                oGrammalecte.startGCPanel(oGrammalecte.xRightClickedNode);
   297         -                sText = (oGrammalecte.xRightClickedNode.tagName == "TEXTAREA") ? oGrammalecte.xRightClickedNode.value : oGrammalecte.xRightClickedNode.innerText;
   298         -                xGrammalectePort.postMessage({
   299         -                    sCommand: "parseAndSpellcheck",
   300         -                    dParam: {sText: sText, sCountry: "FR", bDebug: false, bContext: false},
   301         -                    dInfo: {sTextAreaId: oGrammalecte.xRightClickedNode.id}
   302         -                });
          293  +                parseAndSpellcheckEditableNode(oGrammalecte.xRightClickedNode);
   303    294               } else {
   304    295                   oGrammalecte.showMessage("Erreur. Le node sur lequel vous avez cliqué n’a pas pu être identifié. Sélectionnez le texte à corriger et relancez le correcteur via le menu contextuel.");
   305    296               }
   306    297               break;
   307    298           case "rightClickGCPage":
   308         -            oGrammalecte.startGCPanel();
   309         -            xGrammalectePort.postMessage({
   310         -                sCommand: "parseAndSpellcheck",
   311         -                dParam: {sText: oGrammalecte.getPageText(), sCountry: "FR", bDebug: false, bContext: false},
   312         -                dInfo: {}
   313         -            });
          299  +            parseAndSpellcheckPage();
   314    300               break;
   315    301           case "rightClickGCSelectedText":
   316    302               oGrammalecte.startGCPanel();
   317    303               // selected text is sent to the GC worker in the background script.
   318    304               break;
   319    305           // Lexicographer commands
   320    306           case "rightClickLxgEditableNode":
   321    307               if (oGrammalecte.xRightClickedNode !== null) {
   322         -                oGrammalecte.startLxgPanel();
   323         -                sText = (oGrammalecte.xRightClickedNode.tagName == "TEXTAREA") ? oGrammalecte.xRightClickedNode.value : oGrammalecte.xRightClickedNode.innerText;
   324         -                xGrammalectePort.postMessage({
   325         -                    sCommand: "getListOfTokens",
   326         -                    dParam: {sText: sText},
   327         -                    dInfo: {sTextAreaId: oGrammalecte.xRightClickedNode.id}
   328         -                });
          308  +                lexicographerEditableNode(oGrammalecte.xRightClickedNode);
   329    309               } else {
   330    310                   oGrammalecte.showMessage("Erreur. Le node sur lequel vous avez cliqué n’a pas pu être identifié. Sélectionnez le texte à analyser et relancez le lexicographe via le menu contextuel.");
   331    311               }
   332    312               break;
   333    313           case "rightClickLxgPage":
   334         -            oGrammalecte.startLxgPanel();
   335         -            xGrammalectePort.postMessage({
   336         -                sCommand: "getListOfTokens",
   337         -                dParam: {sText: oGrammalecte.getPageText()},
   338         -                dInfo: {}
   339         -            });
          314  +            lexicographerPage();
   340    315               break;
   341    316           case "rightClickLxgSelectedText":
   342    317               oGrammalecte.startLxgPanel();
   343    318               // selected text is sent to the GC worker in the background script.
   344    319               break;
   345    320           // Text formatter command
   346    321           case "rightClickTFEditableNode":
   347    322               if (oGrammalecte.xRightClickedNode !== null) {
   348         -                if (oGrammalecte.xRightClickedNode.tagName == "TEXTAREA") {
          323  +                if (oGrammalecte.xRightClickedNode.tagName == "TEXTAREA" || oGrammalecte.xRightClickedNode.tagName == "INPUT") {
   349    324                       oGrammalecte.startFTPanel(oGrammalecte.xRightClickedNode);
   350    325                   } else {
   351    326                       oGrammalecte.showMessage("Cette zone de texte n’est pas réellement un champ de formulaire, mais un node HTML éditable. Le formateur de texte n’est pas disponible pour ce type de champ de saisie.");
   352    327                   }
   353    328               } else {
   354    329                   oGrammalecte.showMessage("Erreur. Le node sur lequel vous avez cliqué n’a pas pu être identifié.");
   355    330               }
   356    331               break;
   357    332           // rescan page command
   358    333           case "rescanPage":
   359    334               oGrammalecte.rescanPage();
   360    335               break;
          336  +
          337  +    }
          338  +});
          339  +
          340  +
          341  +/*
          342  +    Other messages from background
          343  +*/
          344  +browser.runtime.onMessage.addListener(function (oMessage) {
          345  +    let {sActionRequest} = oMessage;
          346  +    let xActiveNode = document.activeElement;
          347  +    switch (sActionRequest) {
          348  +        /*
          349  +            Commands received from the keyboard (shortcuts)
          350  +        */
          351  +        case "shortcutLexicographer":
          352  +            if (xActiveNode && (xActiveNode.tagName == "TEXTAREA" || xActiveNode.tagName == "INPUT")) {
          353  +                lexicographerEditableNode(xActiveNode);
          354  +            } else {
          355  +                lexicographerPage();
          356  +            }
          357  +            break;
          358  +        case "shortcutTextFormatter":
          359  +            if (xActiveNode && (xActiveNode.tagName == "TEXTAREA" || xActiveNode.tagName == "INPUT")) {
          360  +                oGrammalecte.startFTPanel(xActiveNode);
          361  +            }
          362  +            break;
          363  +        case "shortcutGrammarChecker":
          364  +            if (xActiveNode && (xActiveNode.tagName == "TEXTAREA" || xActiveNode.tagName == "INPUT")) {
          365  +                parseAndSpellcheckEditableNode(xActiveNode);
          366  +            } else {
          367  +                parseAndSpellcheckPage();
          368  +            }
          369  +            break;
   361    370           default:
   362    371               console.log("[Content script] Unknown command: " + sActionDone);
   363    372       }
   364    373   });
          374  +
          375  +
          376  +/*
          377  +    Actions
          378  +*/
          379  +
          380  +function parseAndSpellcheckPage () {
          381  +    oGrammalecte.startGCPanel();
          382  +    xGrammalectePort.postMessage({
          383  +        sCommand: "parseAndSpellcheck",
          384  +        dParam: {sText: oGrammalecte.getPageText(), sCountry: "FR", bDebug: false, bContext: false},
          385  +        dInfo: {}
          386  +    });
          387  +}
          388  +
          389  +function parseAndSpellcheckEditableNode (xNode) {
          390  +    oGrammalecte.startGCPanel(xNode);
          391  +    let sText = (xNode.tagName == "TEXTAREA" || xNode.tagName == "INPUT") ? xNode.value : xNode.innerText;
          392  +    xGrammalectePort.postMessage({
          393  +        sCommand: "parseAndSpellcheck",
          394  +        dParam: {sText: sText, sCountry: "FR", bDebug: false, bContext: false},
          395  +        dInfo: {sTextAreaId: xNode.id}
          396  +    });
          397  +}
          398  +
          399  +function lexicographerPage () {
          400  +    oGrammalecte.startLxgPanel();
          401  +    xGrammalectePort.postMessage({
          402  +        sCommand: "getListOfTokens",
          403  +        dParam: {sText: oGrammalecte.getPageText()},
          404  +        dInfo: {}
          405  +    });
          406  +}
          407  +
          408  +function lexicographerEditableNode (xNode) {
          409  +    oGrammalecte.startLxgPanel();
          410  +    let sText = (xNode.tagName == "TEXTAREA" || xNode.tagName == "INPUT") ? xNode.value : xNode.innerText;
          411  +    xGrammalectePort.postMessage({
          412  +        sCommand: "getListOfTokens",
          413  +        dParam: {sText: sText},
          414  +        dInfo: {sTextAreaId: xNode.id}
          415  +    });
          416  +}

Changes to gc_lang/fr/webext/content_scripts/panel_gc.js.

    60     60       }
    61     61   
    62     62       start (xNode=null) {
    63     63           this.oTooltip.hide();
    64     64           this.clear();
    65     65           if (xNode) {
    66     66               this.oNodeControl.setNode(xNode);
    67         -            if (xNode.tagName != "TEXTAREA") {
           67  +            if (!(xNode.tagName == "TEXTAREA" || xNode.tagName == "INPUT")) {
    68     68                   this.addMessage("Note : cette zone de texte n’est pas un champ de formulaire “textarea” mais un node HTML éditable. Une telle zone de texte est susceptible de contenir des éléments non textuels qui seront effacés lors de la correction.");
    69     69               }
    70     70           }
    71     71       }
    72     72   
    73     73       clear () {
    74     74           while (this.xParagraphList.firstChild) {
................................................................................
   461    461           this.dParagraph = new Map();
   462    462           this.bTextArea = null;
   463    463       }
   464    464   
   465    465       setNode (xNode) {
   466    466           this.clear();
   467    467           this.xNode = xNode;
   468         -        this.bTextArea = (xNode.tagName == "TEXTAREA");
          468  +        this.bTextArea = (xNode.tagName == "TEXTAREA" || xNode.tagName == "INPUT");
   469    469           this.xNode.disabled = true;
   470    470           this._loadText();
   471    471       }
   472    472   
   473    473       clear () {
   474    474           if (this.xNode !== null) {
   475    475               this.xNode.disabled = false;

Changes to gc_lang/fr/webext/gce_worker.js.

   316    316           return;
   317    317       }
   318    318       //console.log("setDictionary", sDictionary);
   319    319       switch (sDictionary) {
   320    320           case "main":
   321    321               oSpellChecker.setMainDictionary(oDict);
   322    322               break;
   323         -        case "extended":
   324         -            oSpellChecker.setExtendedDictionary(oDict);
   325         -            break;
   326    323           case "community":
   327    324               oSpellChecker.setCommunityDictionary(oDict);
   328    325               break;
   329    326           case "personal":
   330    327               oSpellChecker.setPersonalDictionary(oDict);
   331    328               break;
   332    329           default:
................................................................................
   338    335   function setDictionaryOnOff (sDictionary, bActivate, dInfo) {
   339    336       if (!oSpellChecker) {
   340    337           postMessage(createResponse("setDictionary", "# Error. SpellChecker not loaded.", dInfo, true));
   341    338           return;
   342    339       }
   343    340       //console.log("setDictionaryOnOff", sDictionary, bActivate);
   344    341       switch (sDictionary) {
   345         -        case "extended":
   346         -            if (bActivate) {
   347         -                oSpellChecker.activateExtendedDictionary();
   348         -            } else {
   349         -                oSpellChecker.deactivateExtendedDictionary();
   350         -            }
   351         -            break;
   352    342           case "community":
   353    343               if (bActivate) {
   354    344                   oSpellChecker.activateCommunityDictionary();
   355    345               } else {
   356    346                   oSpellChecker.deactivateCommunityDictionary();
   357    347               }
   358    348               break;

Changes to gc_lang/fr/webext/manifest.json.

    76     76           "content_scripts/init.js"
    77     77         ],
    78     78         "run_at": "document_idle"
    79     79       }
    80     80     ],
    81     81   
    82     82     "commands": {
    83         -    "conjugueur_tab": {
    84         -      "suggested_key": {
    85         -        "default": "Ctrl+Shift+6"
           83  +    "lexicographer": {
           84  +      "suggested_key": { "default": "Ctrl+Shift+L" },
           85  +      "description": "Ouvre le lexicographe"
           86  +    },
           87  +    "text_formatter": {
           88  +      "suggested_key": { "default": "Ctrl+Shift+F" },
           89  +      "description": "Ouvre le formateur de texte"
           90  +    },
           91  +    "grammar_checker": {
           92  +      "suggested_key": { "default": "Ctrl+Shift+V" },
           93  +      "description": "Ouvre le correcteur grammatical"
    86     94         },
           95  +    "conjugueur_tab": {
           96  +      "suggested_key": { "default": "Ctrl+Shift+6" },
    87     97         "description": "Ouvre le conjugueur dans un onglet"
    88     98       },
    89     99       "conjugueur_window": {
    90         -      "suggested_key": {
    91         -        "default": "Ctrl+Shift+7"
    92         -      },
          100  +      "suggested_key": { "default": "Ctrl+Shift+7" },
    93    101         "description": "Ouvre le conjugueur dans une fenêtre"
    94    102       },
    95         -    "lex_editor": {
    96         -      "suggested_key": {
    97         -        "default": "Ctrl+Shift+8"
          103  +    "lexicon_editor": {
          104  +      "suggested_key": { "default": "Ctrl+Shift+8" },
          105  +      "description": "Ouvre l’éditeur lexical"
    98    106         },
    99         -      "description": "Ouvre l’éditeur lexical"
          107  +    "dictionaries": {
          108  +      "suggested_key": { "default": "Ctrl+Shift+9" },
          109  +      "description": "Ouvre le gestionnaire de dictionnaires communautaires"
   100    110       }
   101    111     },
   102    112   
   103    113     "web_accessible_resources": [
   104    114       "content_scripts/panel.css",
   105    115       "content_scripts/panel_tf.css",
   106    116       "content_scripts/panel_gc.css",
................................................................................
   114    124       "grammalecte/fr/mfsp_data.json",
   115    125       "grammalecte/fr/phonet_data.json",
   116    126       "grammalecte/fr/tests_data.json",
   117    127       "img/logo-16.png"
   118    128     ],
   119    129   
   120    130     "permissions": [
          131  +    "*://localhost/*",
          132  +    "*://dic.grammalecte.net/*",
   121    133       "activeTab",
   122    134       "contextMenus",
          135  +    "cookies",
   123    136       "downloads",
   124    137       "storage"
   125    138     ],
   126    139   
   127    140     "chrome_settings_overrides": {
   128    141       "search_provider": {
   129    142         "name": "Grammalecte",

Added gc_lang/fr/webext/panel/dictionaries.css.

            1  +/*
            2  +   CSS Document
            3  +   White
            4  +   Design par Olivier R.
            5  +*/
            6  +
            7  +* { margin: 0; padding: 0; }
            8  +img { border: none; }
            9  +
           10  +
           11  +/* Generic classes */
           12  +
           13  +.fleft { float: left; }
           14  +.fright { float: right; }
           15  +.center { text-align: center; }
           16  +.right { text-align: right; }
           17  +.left { text-align: left; }
           18  +.justify { text-align: justify; }
           19  +.hidden { display: none; }
           20  +.clearer { clear: both; font-size: 0; height: 0; }
           21  +
           22  +body {
           23  +    background: hsl(0, 0%, 100%) url(../img/lines.png);
           24  +    font: normal 16px "Trebuchet MS", "Fira Sans", "Liberation Sans", sans-serif;
           25  +    color: #505050;
           26  +}
           27  +
           28  +.inbox {
           29  +    width: 800px;
           30  +    margin: 20px auto 10px auto;
           31  +    padding: 10px 30px 30px 30px;
           32  +    background: hsl(0, 0%, 100%);
           33  +    border: 2px solid hsl(210, 0%, 90%);
           34  +    border-radius: 20px;
           35  +}
           36  +
           37  +
           38  +#message_box {
           39  +    display: none;
           40  +    position: fixed;
           41  +    top: 33%;
           42  +    left: calc(50% - 325px);
           43  +}
           44  +#message {
           45  +    display: inline-block;
           46  +    padding: 5px 20px;
           47  +    width: 600px;
           48  +    background-color: hsl(0, 50%, 50%);
           49  +    color: hsl(0, 50%, 98%);
           50  +    border-style: solid;
           51  +    border-width: 3px 0 3px 3px;
           52  +    border-color: hsla(0, 50%, 40%, .5);
           53  +    border-radius: 5px 0 0 5px;
           54  +}
           55  +#message_close_button {
           56  +    display: inline-block;
           57  +    padding: 5px 10px;
           58  +    background-color: hsl(0, 50%, 40%);
           59  +    color: hsl(0, 90%, 90%);
           60  +    border-style: solid;
           61  +    border-width: 3px 3px 3px 0;
           62  +    border-color: hsla(0, 50%, 30%, .5);
           63  +    border-radius: 0 5px 5px 0;
           64  +    cursor: pointer;
           65  +}
           66  +
           67  +
           68  +h1 {
           69  +    margin: 5px 0 5px 0;
           70  +    color: hsl(210, 50%, 50%);
           71  +    font: bold 24px "Trebuchet MS", "Fira Sans", "Liberation Sans", sans-serif;
           72  +}
           73  +h2 {
           74  +    margin: 10px 0 2px 0;
           75  +    color: hsl(0, 50%, 50%);
           76  +    font: bold 20px "Trebuchet MS", "Fira Sans", "Liberation Sans", sans-serif;
           77  +}
           78  +
           79  +
           80  +input[type=text].large {
           81  +    display: inline-block;
           82  +    width: 250px;
           83  +    padding: 5px 10px;
           84  +    border: 2px solid hsl(0, 0%, 80%);
           85  +    border-radius: 3px;
           86  +    height: 24px;
           87  +    background: transparent;
           88  +    font: normal 20px Tahoma, "Ubuntu Condensed";
           89  +    color: hsl(0, 0%, 20%);
           90  +}
           91  +
           92  +input[type=text].medium {
           93  +    display: inline-block;
           94  +    width: 175px;
           95  +    padding: 2px 5px;
           96  +    border: 2px solid hsl(0, 0%, 80%);
           97  +    border-radius: 3px;
           98  +    height: 20px;
           99  +    background: transparent;
          100  +    font: normal 18px Tahoma, "Ubuntu Condensed";
          101  +    color: hsl(0, 0%, 20%);
          102  +}
          103  +
          104  +input[placeholder] {
          105  +    color: hsl(0, 0%, 50%);
          106  +}
          107  +
          108  +
          109  +#connect_panel {
          110  +    background-color: hsl(210, 50%, 90%);
          111  +    border-radius: 5px;
          112  +    padding: 3px 10px;
          113  +}
          114  +#submit_button {
          115  +    display: inline-block;
          116  +    padding: 1px 5px;
          117  +    background-color: hsl(210, 50%, 30%);
          118  +    color: hsl(210, 0%, 100%);
          119  +    border-radius: 3px;
          120  +    cursor: pointer;
          121  +}
          122  +
          123  +
          124  +.dic_button {
          125  +    margin: 2px;
          126  +    display: inline-block;
          127  +}
          128  +.dic_button_close {
          129  +    display: inline-block;
          130  +    padding: 1px 5px;
          131  +    background-color: hsl(0, 50%, 50%);
          132  +    color: hsl(0, 90%, 90%);
          133  +    border-style: solid;
          134  +    border-width: 1px 0 1px 1px;
          135  +    border-color: hsl(0, 50%, 45%);
          136  +    border-radius: 3px 0 0 3px;
          137  +    cursor: pointer;
          138  +}
          139  +.dic_button_label {
          140  +    display: inline-block;
          141  +    padding: 1px 10px;
          142  +    background-color: hsl(210, 50%, 94%);
          143  +    border-style: solid;
          144  +    border-width: 1px 1px 1px 0;
          145  +    border-color: hsl(210, 50%, 70%);
          146  +    border-radius: 0 3px 3px 0;
          147  +}
          148  +
          149  +.apply {
          150  +    display: none;
          151  +    float: right;
          152  +    padding: 2px 10px;
          153  +    background: hsl(120, 50%, 30%);
          154  +    color: hsl(120, 50%, 96%);
          155  +    cursor: pointer;
          156  +    border-radius: 3px;
          157  +}
          158  +
          159  +
          160  +/*
          161  +    Table
          162  +*/
          163  +#wait_progress {
          164  +    width: 100%;
          165  +    height: 4px;
          166  +}
          167  +
          168  +table {
          169  +    border: 1px solid hsl(210, 10%, 50%);
          170  +    width: 100%;
          171  +    font-size: 14px;
          172  +}
          173  +th {
          174  +    padding: 5px 10px;
          175  +    border-left: 1px solid hsl(210, 10%, 90%);
          176  +    text-align: left;
          177  +}
          178  +td {
          179  +    padding: 0 10px;
          180  +    vertical-align: top;
          181  +}
          182  +.delete_entry {
          183  +    cursor: pointer;
          184  +    font-weight: bold;
          185  +    color: hsl(0, 100%, 50%);
          186  +}
          187  +.select_entry {
          188  +    cursor: pointer;
          189  +    background-color: hsl(210, 50%, 30%);
          190  +    color: hsl(210, 50%, 100%);
          191  +    border-radius: 3px;
          192  +    text-align: center;
          193  +}

Added gc_lang/fr/webext/panel/dictionaries.html.

            1  +<!DOCTYPE HTML>
            2  +<html>
            3  +  <head>
            4  +    <link rel="stylesheet" type="text/css" href="dictionaries.css" />
            5  +    <title>Grammalecte · Dictionnaires communautaires</title>
            6  +    <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
            7  +  </head>
            8  +
            9  +  <body>
           10  +
           11  +    <div id="message_box">
           12  +      <div id="message">[néant]</div><div id="message_close_button">×</div>
           13  +    </div>
           14  +
           15  +    <div class="inbox">
           16  +      <h1>Grammalecte · Dictionnaires communautaires</h1>
           17  +
           18  +      <div id="connect_panel">
           19  +        <form id="connect_form" method="post" enctype="multipart/form-data">
           20  +          <p>
           21  +            Non connecté.
           22  +            <label for="email">E-mail : </label> <input type="email" id="email" name="email" />
           23  +            <label for="password">Mot de passe : </label> <input type="password" id="pw" name="pw" />
           24  +            <span id="submit_button">Se connecter</span>
           25  +            |
           26  +            <a href="http://dic.grammalecte.net" target="_blank">Inscription</a>
           27  +          </p>
           28  +        </form>
           29  +        <div id="connect_info">
           30  +          <p>
           31  +            Connecté. Identifiants : <span id="login_label"> </span>, <span id="email_label"> </span>
           32  +          </p>
           33  +        </div>
           34  +      </div>
           35  +
           36  +      <h2>Dictionnaires sélectionnés</h2>
           37  +      <p id="dictionaries_list">[Aucun]<p>
           38  +
           39  +      <div id="apply" class="apply">Appliquer les modifications</div>
           40  +      <h2><span id="num_dic">0</span> dictionnaires disponibles</h2>
           41  +      <progress id="wait_progress" value="0"></progress>
           42  +      <table id="dictionaries_table">
           43  +      </table>
           44  +    </div> <!-- inbox -->
           45  +
           46  +
           47  +    <script src="../grammalecte/graphspell/helpers.js"></script>
           48  +    <script src="../grammalecte/graphspell/char_player.js"></script>
           49  +    <script src="../grammalecte/graphspell/str_transform.js"></script>
           50  +    <script src="../grammalecte/graphspell/dawg.js"></script>
           51  +    <script src="../grammalecte/graphspell/ibdawg.js"></script>
           52  +    <script src="../grammalecte/graphspell/dic_merger.js"></script>
           53  +    <script src="../grammalecte/graphspell/spellchecker.js"></script>
           54  +    <script src="dictionaries.js"></script>
           55  +  </body>
           56  +
           57  +</html>

Added gc_lang/fr/webext/panel/dictionaries.js.

            1  +// JavaScript
            2  +
            3  +"use strict";
            4  +
            5  +
            6  +// Chrome don’t follow the W3C specification:
            7  +// https://browserext.github.io/browserext/
            8  +let bChrome = false;
            9  +if (typeof(browser) !== "object") {
           10  +    var browser = chrome;
           11  +    bChrome = true;
           12  +}
           13  +
           14  +
           15  +/*
           16  +    Common functions
           17  +*/
           18  +
           19  +function showError (e) {
           20  +    console.error(e.fileName + "\n" + e.name + "\nline: " + e.lineNumber + "\n" + e.message);
           21  +}
           22  +
           23  +function createNode  (sType, oAttr, oDataset=null) {
           24  +    try {
           25  +        let xNode = document.createElement(sType);
           26  +        Object.assign(xNode, oAttr);
           27  +        if (oDataset) {
           28  +            Object.assign(xNode.dataset, oDataset);
           29  +        }
           30  +        return xNode;
           31  +    }
           32  +    catch (e) {
           33  +        showError(e);
           34  +    }
           35  +}
           36  +
           37  +function showElement (sElemId, sDisplay="block") {
           38  +    if (document.getElementById(sElemId)) {
           39  +        document.getElementById(sElemId).style.display = sDisplay;
           40  +    } else {
           41  +        console.log("HTML node named <" + sElemId + "> not found.")
           42  +    }
           43  +}
           44  +
           45  +function hideElement (sElemId) {
           46  +    if (document.getElementById(sElemId)) {
           47  +        document.getElementById(sElemId).style.display = "none";
           48  +    } else {
           49  +        console.log("HTML node named <" + sElemId + "> not found.")
           50  +    }
           51  +}
           52  +
           53  +async function hashText (sText, sAlgorithm = 'SHA-256') {
           54  +    let msgBuffer = new TextEncoder('utf-8').encode(sText);
           55  +    let hashBuffer = await crypto.subtle.digest(sAlgorithm, msgBuffer);
           56  +    let hashArray = Array.from(new Uint8Array(hashBuffer));
           57  +    return hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join('');
           58  +}
           59  +
           60  +
           61  +const oMessage = {
           62  +    show: function (sMessage, nDelay=10000) {
           63  +        document.getElementById("message").textContent = sMessage;
           64  +        showElement("message_box");
           65  +        window.setTimeout(this.close, nDelay);
           66  +    },
           67  +
           68  +    listen: function () {
           69  +        document.getElementById("message_close_button").addEventListener("click", (xEvent) => { this.close(); });
           70  +    },
           71  +
           72  +    close: function () {
           73  +        hideElement("message_box");
           74  +    }
           75  +}
           76  +
           77  +
           78  +const oConnect = {
           79  +    bConnected: false,
           80  +
           81  +    init: function () {
           82  +        if (bChrome) {
           83  +            browser.cookies.getAll({ domain: "localhost" }, this._init.bind(this));
           84  +            return;
           85  +        }
           86  +        let xPromise = browser.cookies.getAll({ domain: "localhost" });
           87  +        xPromise.then(this._init.bind(this), showError);
           88  +    },
           89  +
           90  +    _init: function (lData) {
           91  +        for (let xCookie of lData) {
           92  +            console.log(xCookie.name, xCookie.value);
           93  +            this.bConnected = true;
           94  +        }
           95  +        if (this.bConnected) {
           96  +            hideElement("connect_form");
           97  +            showElement("connect_info");
           98  +        }
           99  +        else {
          100  +            showElement("connect_form");
          101  +            hideElement("connect_info");
          102  +        }
          103  +    },
          104  +
          105  +    listen: function () {
          106  +        document.getElementById("submit_button").addEventListener("click", (xEvent) => { this.connect(); });
          107  +    },
          108  +
          109  +    connect: function () {
          110  +        if (!this.checkValues()) {
          111  +            oMessage.show("Les valeurs des champs du formulaire ne sont pas conformes.");
          112  +            return;
          113  +        }
          114  +        let xForm = new FormData(document.getElementById('connect_form'));
          115  +        for (let [k, v] of xForm.entries()) {
          116  +            console.log("* ", k, v);
          117  +        }
          118  +        fetch("http://localhost/connect/", {
          119  +            method: "POST", // *GET, POST, PUT, DELETE, etc.
          120  +            //mode: "cors", // no-cors, cors, *same-origin
          121  +            //cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
          122  +            headers: {
          123  +                "Accept-Charset": "utf-8"
          124  +                //"Content-Type": "multipart/form-data",  // text/plain, application/json
          125  +            },
          126  +            credentials: "omit", // include, *same-origin, omit
          127  +            body: xForm
          128  +        })
          129  +        .then((response) => {
          130  +            if (response.ok) {
          131  +                if (response.status == 204) {
          132  +                    oMessage.show("Échec d’identification. Vérifiez l’e-mail et le mot de passe envoyés…");
          133  +                    return null;
          134  +                }
          135  +                for (let param in response) {
          136  +                    console.log(param, response[param]);
          137  +                }
          138  +                console.log(response.body);
          139  +                return response.json();
          140  +            } else {
          141  +                oMessage.show("Erreur. Le serveur ne semble pas en état de répondre. Veuillez réessayer ultérieurement.");
          142  +                return null;
          143  +            }
          144  +        })
          145  +        .then((response) => {
          146  +            if (response) {
          147  +                console.log("response: ", response);
          148  +            }
          149  +        })
          150  +        .catch((e) => {
          151  +            showError(e);
          152  +        });
          153  +    },
          154  +
          155  +    checkValues () {
          156  +        if (document.getElementById("email").value === "") {
          157  +            return false;
          158  +        }
          159  +        let sEmail = document.getElementById("email").value;
          160  +        if (sEmail.search(/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/) === -1) {
          161  +            return false;
          162  +        }
          163  +        return true;
          164  +    },
          165  +
          166  +    showId (sLogin, sEmail) {
          167  +        document.getElementById("login_label").textContent = sLogin;
          168  +        document.getElementById("email_label").textContent = sEmail;
          169  +        hideElement("connect_form");
          170  +        showElement("connect_info");
          171  +    }
          172  +}
          173  +
          174  +
          175  +class Table {
          176  +
          177  +    constructor (sNodeId, lColumn, sProgressBarId, sResultId="", bDeleteButtons=true, bActionButtons) {
          178  +        this.sNodeId = sNodeId;
          179  +        this.xTable = document.getElementById(sNodeId);
          180  +        this.xApply = document.getElementById("apply");
          181  +        this.nColumn = lColumn.length;
          182  +        this.lColumn = lColumn;
          183  +        this.xProgressBar = document.getElementById(sProgressBarId);
          184  +        this.xNumEntry = document.getElementById(sResultId);
          185  +        this.lEntry = [];
          186  +        this.nEntry = 0;
          187  +        this.dSelectedDictionaries = new Map();
          188  +        this.lSelectedDictionaries = [];
          189  +        this.dDict = new Map();
          190  +        this.bDeleteButtons = bDeleteButtons;
          191  +        this.bActionButtons = bActionButtons;
          192  +        this._createHeader();
          193  +        this.listen();
          194  +    }
          195  +
          196  +    _createHeader () {
          197  +        let xRowNode = createNode("tr");
          198  +        if (this.bDeleteButtons) {
          199  +            xRowNode.appendChild(createNode("th", { textContent: "·", width: "12px" }));
          200  +        }
          201  +        for (let sColumn of this.lColumn) {
          202  +            xRowNode.appendChild(createNode("th", { textContent: sColumn }));
          203  +        }
          204  +        this.xTable.appendChild(xRowNode);
          205  +    }
          206  +
          207  +    clear () {
          208  +        while (this.xTable.firstChild) {
          209  +            this.xTable.removeChild(this.xTable.firstChild);
          210  +        }
          211  +        this.lEntry = [];
          212  +        this.nEntry = 0;
          213  +        this._createHeader();
          214  +        this.showEntryNumber();
          215  +    }
          216  +
          217  +    fill (lFlex) {
          218  +        this.clear();
          219  +        if (lFlex.length > 0) {
          220  +            this.xProgressBar.max = lFlex.length;
          221  +            this.xProgressBar.value = 1;
          222  +            for (let lData of lFlex) {
          223  +                this._addRow(lData);
          224  +                this.xProgressBar.value += 1;
          225  +            }
          226  +            this.xProgressBar.value = this.xProgressBar.max;
          227  +        }
          228  +        this.lEntry = lFlex;
          229  +        this.nEntry = lFlex.length;
          230  +        this.showEntryNumber();
          231  +    }
          232  +
          233  +    addEntries (lFlex) {
          234  +        this.lEntry.push(...lFlex);
          235  +        for (let lData of lFlex) {
          236  +            this._addRow(lData);
          237  +        }
          238  +        this.nEntry += lFlex.length;
          239  +        this.showEntryNumber();
          240  +    }
          241  +
          242  +    init () {
          243  +        if (bChrome) {
          244  +            browser.storage.local.get("selected_dictionaries_list", this._init.bind(this));
          245  +            return;
          246  +        }
          247  +        let xPromise = browser.storage.local.get("selected_dictionaries_list");
          248  +        xPromise.then(this._init.bind(this), showError);
          249  +    }
          250  +
          251  +    _init (oResult) {
          252  +        if (oResult.hasOwnProperty("selected_dictionaries_list")) {
          253  +            this.lSelectedDictionaries = oResult.selected_dictionaries_list;
          254  +            console.log(this.lSelectedDictionaries);
          255  +        }
          256  +        this.getDictionarieslist();
          257  +    }
          258  +
          259  +    getDictionarieslist () {
          260  +        fetch("http://localhost/dictionaries/")
          261  +        .then((response) => {
          262  +            if (response.ok) {
          263  +                return response.json();
          264  +            } else {
          265  +                return null;
          266  +            }
          267  +        })
          268  +        .then((response) => {
          269  +            if (response) {
          270  +                this.fill(response);
          271  +                this.showSelectedDictionaries(true);
          272  +            } else {
          273  +                // todo
          274  +            }
          275  +        })
          276  +        .catch((e) => {
          277  +            showError(e);
          278  +        });
          279  +    }
          280  +
          281  +    getDictionary (sId, sName) {
          282  +        console.log("get: "+sName);
          283  +        fetch("http://localhost/download/"+sName)
          284  +        .then((response) => {
          285  +            if (response.ok) {
          286  +                return response.json();
          287  +            } else {
          288  +                console.log("dictionary not loaded: " + sName);
          289  +                return null;
          290  +            }
          291  +        })
          292  +        .then((response) => {
          293  +            if (response) {
          294  +                this.selectEntry(sId, sName);
          295  +                this.dDict.set(sName, response);
          296  +                browser.storage.local.set({ "stored_dictionaries": this.dDict });
          297  +            } else {
          298  +                //
          299  +            }
          300  +        })
          301  +        .catch((e) => {
          302  +            showError(e);
          303  +        });
          304  +    }
          305  +
          306  +    showEntryNumber () {
          307  +        if (this.xNumEntry) {
          308  +            this.xNumEntry.textContent = this.nEntry;
          309  +        }
          310  +    }
          311  +
          312  +    _addRow (lData) {
          313  +        let [nDicId, sName, nEntry, sDescription, sLastUpdate, ...data] = lData;
          314  +        let xRowNode = createNode("tr", { id: this.sNodeId + "_row_" + nDicId });
          315  +        if (this.bDeleteButtons) {
          316  +            xRowNode.appendChild(createNode("td", { textContent: "×", className: "delete_entry", title: "Effacer cette entrée" }, { id_entry: nDicId }));
          317  +        }
          318  +        xRowNode.appendChild(createNode("td", { textContent: sName }));
          319  +        xRowNode.appendChild(createNode("td", { textContent: nEntry }));
          320  +        xRowNode.appendChild(createNode("td", { textContent: sDescription }));
          321  +        xRowNode.appendChild(createNode("td", { textContent: sLastUpdate }));
          322  +        if (this.bActionButtons) {
          323  +            xRowNode.appendChild(createNode("td", { textContent: "+", className: "select_entry", title: "Sélectionner/Désélectionner cette entrée" }, { id_entry: nDicId, dict_name: sName }));
          324  +        }
          325  +        this.xTable.appendChild(xRowNode);
          326  +        if (this.lSelectedDictionaries.includes(sName)) {
          327  +            this.dSelectedDictionaries.set(sName, nDicId);
          328  +        }
          329  +    }
          330  +
          331  +    listen () {
          332  +        if (this.bDeleteButtons || this.bActionButtons) {
          333  +            this.xTable.addEventListener("click", (xEvent) => { this.onTableClick(xEvent); }, false);
          334  +        }
          335  +        this.xApply.addEventListener("click", (xEvent) => { this.generateCommunityDictionary(xEvent); }, false);
          336  +    }
          337  +
          338  +    onTableClick (xEvent) {
          339  +        try {
          340  +            let xElem = xEvent.target;
          341  +            if (xElem.className) {
          342  +                switch (xElem.className) {
          343  +                    case "delete_entry":
          344  +                        this.deleteRow(xElem.dataset.id_entry, xElem.dataset.dict_name);
          345  +                        break;
          346  +                    case "select_entry":
          347  +                        this.getDictionary(xElem.dataset.id_entry, xElem.dataset.dict_name);
          348  +                        break;
          349  +                }
          350  +            }
          351  +        }
          352  +        catch (e) {
          353  +            showError(e);
          354  +        }
          355  +    }
          356  +
          357  +    deleteRow (iEntry) {
          358  +        this.lEntry[parseInt(iEntry)] = null;
          359  +        if (document.getElementById(this.sNodeId + "_row_" + iEntry)) {
          360  +            document.getElementById(this.sNodeId + "_row_" + iEntry).style.display = "none";
          361  +        }
          362  +        this.nEntry -= 1;
          363  +        this.showEntryNumber();
          364  +        if (this.sNodeId == "lexicon_table") {
          365  +            showElement("save_button", "inline-block");
          366  +        }
          367  +        showElement("apply");
          368  +    }
          369  +
          370  +    selectEntry (nEntryId, sDicName) {
          371  +        let sRowId = this.sNodeId + "_row_" + nEntryId;
          372  +        if (!this.dSelectedDictionaries.has(sDicName)) {
          373  +            this.dSelectedDictionaries.set(sDicName, nEntryId);
          374  +            document.getElementById(sRowId).style.backgroundColor = "hsl(210, 50%, 90%)";
          375  +        }
          376  +        else {
          377  +            this.dSelectedDictionaries.delete(sDicName);
          378  +            document.getElementById(sRowId).style.backgroundColor = "";
          379  +        }
          380  +        showElement("apply");
          381  +        this.showSelectedDictionaries();
          382  +    }
          383  +
          384  +    clearSelectedDict () {
          385  +        let xDicList = document.getElementById("dictionaries_list");
          386  +        while (xDicList.firstChild) {
          387  +            xDicList.removeChild(xDicList.firstChild);
          388  +        }
          389  +    }
          390  +
          391  +    showSelectedDictionaries (bUpdateTable=false) {
          392  +        this.clearSelectedDict();
          393  +        let xDicList = document.getElementById("dictionaries_list");
          394  +        if (this.dSelectedDictionaries.size === 0) {
          395  +            xDicList.textContent = "[Aucun]";
          396  +            return;
          397  +        }
          398  +        for (let [sName, nDicId] of this.dSelectedDictionaries) {
          399  +            xDicList.appendChild(this._createDictLabel(nDicId, sName));
          400  +            if (bUpdateTable) {
          401  +                document.getElementById(this.sNodeId + "_row_" + nDicId).style.backgroundColor = "hsl(210, 50%, 90%)";
          402  +            }
          403  +        }
          404  +    }
          405  +
          406  +    _createDictLabel (nDicId, sLabel) {
          407  +        let xLabel = createNode("div", {className: "dic_button"});
          408  +        let xCloseButton = createNode("div", {className: "dic_button_close", textContent: "×"}, {id_entry: nDicId});
          409  +        xCloseButton.addEventListener("click", () => {
          410  +            this.dSelectedDictionaries.delete(sLabel);
          411  +            document.getElementById(this.sNodeId+"_row_"+nDicId).style.backgroundColor = "";
          412  +            xLabel.style.display = "none";
          413  +            showElement("apply");
          414  +        });
          415  +        xLabel.appendChild(xCloseButton);
          416  +        xLabel.appendChild(createNode("div", {className: "dic_button_label", textContent: sLabel}));
          417  +        return xLabel;
          418  +    }
          419  +
          420  +    generateCommunityDictionary (xEvent) {
          421  +        hideElement("apply");
          422  +        let lDict = [];
          423  +        for (let sName of this.dSelectedDictionaries.keys()) {
          424  +            lDict.push(this.dDict.get(sName));
          425  +        }
          426  +        let oDict = dic_merger.merge(lDict, "S", "fr", "Français", "fr.community", "Dictionnaire communautaire (personnalisé)", this.xProgressBar);
          427  +        console.log(oDict);
          428  +        browser.storage.local.set({ "community_dictionary": oDict });
          429  +        browser.storage.local.set({ "selected_dictionaries_list": Array.from(this.dSelectedDictionaries.keys()) });
          430  +        browser.runtime.sendMessage({ sCommand: "setDictionary", dParam: {sDictionary: "community", oDict: oDict}, dInfo: {} });
          431  +    }
          432  +}
          433  +
          434  +const oDicTable = new Table("dictionaries_table", ["Nom", "Entrées", "Description", "Date"], "wait_progress", "num_dic", false, true);
          435  +
          436  +oDicTable.init();
          437  +
          438  +oMessage.listen();
          439  +
          440  +oConnect.init();
          441  +oConnect.listen();

Changes to gc_lang/fr/webext/panel/lex_editor.css.

    46     46       font: bold 20px "Trebuchet MS", "Fira Sans", "Liberation Sans", sans-serif;
    47     47   }
    48     48   h3 {
    49     49       margin: 3px 0 2px 0;
    50     50       color: hsl(210, 50%, 50%);
    51     51       font: bold 16px "Trebuchet MS", "Fira Sans", "Liberation Sans", sans-serif;
    52     52   }
           53  +
           54  +#dic_selector {
           55  +    color: hsl(210, 50%, 50%);
           56  +    font: bold 16px "Trebuchet MS", "Fira Sans", "Liberation Sans", sans-serif;
           57  +}
    53     58   
    54     59   details {
    55     60       font-size: 11px;
    56     61       font-variant: small-caps;
    57     62       color: hsl(210, 50%, 50%);
    58     63       cursor: pointer;
    59     64   }
................................................................................
    64     69       cursor: auto;
    65     70   }
    66     71   details.inline {
    67     72       padding: 3px;
    68     73       width: 260px;
    69     74   }
    70     75   
           76  +
    71     77   
    72     78   /*
    73     79       Main buttons
    74     80   */
    75     81   #buttons {
    76     82       padding: 10px 0;
    77     83       justify-content: center;
    78     84   }
    79     85   .main_button {
    80     86       margin: 0 5px;
    81         -    width: 100px;
           87  +    min-width: 100px;
    82     88       padding: 10px 20px;
    83     89       background-color: hsl(210, 10%, 95%);
    84     90       border-radius: 5px;
    85     91       text-align: center;
    86     92       cursor: pointer;
    87     93   }
    88     94   #lexicon_button {
................................................................................
   292    298       width: 500px;
   293    299   }
   294    300   
   295    301   
   296    302   /*
   297    303       Dictionary
   298    304   */
   299         -#save_button, #export_button, #import_button {
          305  +#new_dictionary_button, #delete_dictionary_button {
          306  +    margin-left: 5px;
          307  +    padding: 1px 5px;
          308  +    background-color: hsl(150, 50%, 50%);
          309  +    color: hsl(150, 0%, 100%);
          310  +    border-radius: 3px;
          311  +    cursor: pointer;
          312  +}
          313  +#delete_dictionary_button {
          314  +    background-color: hsl(0, 50%, 50%);
          315  +    color: hsl(0, 0%, 100%);
          316  +}
          317  +
          318  +#save_button {
   300    319       display: none;
   301         -    float: right;
   302         -    padding: 2px 10px;
          320  +    margin-left: 5px;
          321  +    padding: 1px 5px;
   303    322       background-color: hsl(150, 50%, 50%);
   304    323       color: hsl(150, 0%, 100%);
   305    324       border-radius: 3px;
   306    325       cursor: pointer;
   307    326   }
   308    327   #export_button, #import_button {
   309    328       display: block;
   310    329       margin-left: 5px;
          330  +    float: right;
          331  +    padding: 2px 10px;
   311    332       background-color: hsl(210, 50%, 50%);
   312    333       color: hsl(210, 0%, 100%);
          334  +    border-radius: 3px;
          335  +    cursor: pointer;
          336  +}
          337  +
          338  +#new_dictionary_section {
          339  +    margin: 5px 0;
          340  +    padding: 5px 10px;
          341  +    background-color: hsl(210, 10%, 90%);
          342  +    border-radius: 3px;
          343  +}
          344  +#create_dictionary_button {
          345  +    padding: 1px 5px;
          346  +    background-color: hsl(120, 50%, 30%);
          347  +    color: hsl(120, 10%, 100%);
          348  +    border-radius: 3px;
          349  +    cursor: pointer;
   313    350   }
          351  +
   314    352   #wait_progress {
   315    353       width: 100%;
   316    354       height: 4px;
   317    355   }
   318    356   
   319    357   
   320    358   /*

Changes to gc_lang/fr/webext/panel/lex_editor.html.

    10     10   
    11     11       <div class="inbox">
    12     12   
    13     13         <h1>Éditeur lexical</h1>
    14     14   
    15     15         <div class="big_block">
    16     16           <div class="dic_container">
    17         -          <div><h3>Dictionnaire personnel</h3></div>
    18         -          <div>Enregistré le : <span id="dic_save_date">—</span></div>
           17  +          <div><h3>Dictionnaire</h3></div>
           18  +          <div class="dic_container">
           19  +            <select id="dic_selector">
           20  +              <option value="__personal__">__personnel__</option>
           21  +            </select>
           22  +            <div id="new_dictionary_button" title="Créer un dictionnaire partagé">+</div>
           23  +            <div id="delete_dictionary_button" title="Supprimer ce dictionnaire">−</div>
           24  +          </div>
           25  +
           26  +          <div>[<span id="dic_save_date">—</span>]</div>
    19     27             <div><span id="dic_num_entries">0</span> entrées</div>
    20         -          <!--<div id="import_button" class="fright">Importer</div>-->
    21         -          <div class="fright">
           28  +          <div>
    22     29               <input type="file" id="import_input" accept=".json" style="display: none;">
    23     30               <label id="import_button" for="import_input">Importer</label>
    24     31             </div>
    25         -          <div id="export_button" class="fright">Exporter</div>
    26         -
           32  +          <div id="export_button">Exporter</div>
           33  +        </div>
           34  +        <div id="new_dictionary_section" style="display: none;">
           35  +          <div class="dic_container">
           36  +            <div>Nouveau dictionnaire partagé</div>
           37  +            <div>Nom : fr.x.<input type="text" id="new_dictionary_name" required> · Description : <input type="text" id="new_dictionary_description" required></div>
           38  +            <div id="create_dictionary_button">Créer</div>
           39  +          </div>
    27     40           </div>
    28     41           <progress id="wait_progress" value="0"></progress>
    29     42         </div>
    30     43   
    31     44         <div id="buttons" class="container">
    32         -        <div id="lexicon_button" class="main_button">Lexique</div>
           45  +        <div id="lexicon_button" class="main_button">
           46  +          Lexique · <span id="num_entries">0</span> entrées <span id="save_button">Enregistrer</span>
           47  +        </div>
    33     48           <div id="add_button" class="main_button">Ajout</div>
    34     49           <div id="search_button" class="main_button">Recherche</div>
    35     50           <div id="info_button" class="main_button">Informations</div>
    36     51         </div>
    37     52   
    38     53         <div id="add_page">
    39     54           <div class="columns">
................................................................................
   243    258             </div> <!-- #generated_words -->
   244    259           </div> <!-- .columns -->
   245    260         </div> <!-- #add_page -->
   246    261   
   247    262   
   248    263         <div id="lexicon_page">
   249    264           <h2>Votre lexique</h2>
   250         -        <div class="big_block">
   251         -          <div id="save_button" class="fright">
   252         -            Enregistrer
   253         -          </div>
   254         -          <p>Nombre d’entrées : <span id="num_entries">0</span>.</p>
   255         -        </div>
   256         -
   257    265           <table id="lexicon_table">
   258    266                 
   259    267           </table>
   260    268         </div> <!-- #lexicon_page -->
   261    269   
   262    270   
   263    271         <div id="search_page">

Changes to gc_lang/fr/webext/panel/lex_editor.js.

    29     29           return xNode;
    30     30       }
    31     31       catch (e) {
    32     32           showError(e);
    33     33       }
    34     34   }
    35     35   
    36         -function showElement (sElemId) {
           36  +function showElement (sElemId, sDisplay="block") {
    37     37       if (document.getElementById(sElemId)) {
    38         -        document.getElementById(sElemId).style.display = "block";
           38  +        document.getElementById(sElemId).style.display = sDisplay;
    39     39       } else {
    40     40           console.log("HTML node named <" + sElemId + "> not found.")
    41     41       }
    42     42   }
    43     43   
    44     44   function hideElement (sElemId) {
    45     45       if (document.getElementById(sElemId)) {
    46     46           document.getElementById(sElemId).style.display = "none";
    47     47       } else {
    48     48           console.log("HTML node named <" + sElemId + "> not found.")
    49     49       }
    50     50   }
           51  +
           52  +function switchDisplay (sElemId, sDisplay="block") {
           53  +    if (document.getElementById(sElemId)) {
           54  +        if (document.getElementById(sElemId).style.display != "none") {
           55  +            document.getElementById(sElemId).style.display = "none";
           56  +        } else {
           57  +            document.getElementById(sElemId).style.display = sDisplay;
           58  +        }
           59  +    }
           60  +}
           61  +
           62  +function showMessage (sMessage) {
           63  +    console.log(sMessage);
           64  +}
    51     65   
    52     66   
    53     67   const oTabulations = {
    54     68   
    55     69       lPage: ["lexicon_page", "add_page", "search_page", "info_page"],
    56     70   
    57     71       showPage: function (sRequestedPage) {
................................................................................
   123    137           this.xTable.appendChild(xRowNode);
   124    138       }
   125    139   
   126    140       clear () {
   127    141           while (this.xTable.firstChild) {
   128    142               this.xTable.removeChild(this.xTable.firstChild);
   129    143           }
          144  +        this.lEntry = [];
          145  +        this.nEntry = 0;
   130    146           this.iEntryIndex = 0;
   131    147           this._createHeader();
          148  +        this.showEntryNumber();
   132    149       }
   133    150   
   134    151       fill (lFlex) {
   135    152           this.clear();
   136    153           if (lFlex.length > 0) {
   137    154               this.xProgressBar.max = lFlex.length;
   138    155               this.xProgressBar.value = 1;
................................................................................
   198    215           this.lEntry[parseInt(iEntry)] = null;
   199    216           if (document.getElementById(this.sNodeId + "_row_" + iEntry)) {
   200    217               document.getElementById(this.sNodeId + "_row_" + iEntry).style.display = "none";
   201    218           }
   202    219           this.nEntry -= 1;
   203    220           this.showEntryNumber();
   204    221           if (this.sNodeId == "lexicon_table") {
   205         -            showElement("save_button");
          222  +            showElement("save_button", "inline-block");
   206    223           }
   207    224       }
   208    225   
   209    226       getEntries () {
   210    227           return this.lEntry.filter((e) => e !== null);
   211    228       }
   212    229   }
................................................................................
   478    495           try {
   479    496               oLexiconTable.addEntries(this.createFlexLemmaTagArray());
   480    497               oGenWordsTable.clear();
   481    498               document.getElementById("lemma").value = "";
   482    499               document.getElementById("lemma").focus();
   483    500               this.hideAllSections();
   484    501               hideElement("editor");
   485         -            showElement("save_button");
          502  +            showElement("save_button", "inline-block");
   486    503               this.clear();
   487    504               this.cMainTag = "";
   488    505           }
   489    506           catch (e) {
   490    507               showError(e);
   491    508           }
   492    509       }
   493    510   }
   494    511   
          512  +
          513  +const oDictHandler = {
          514  +    oDictionaries: {},
          515  +    oPersonalDictionary: null,
          516  +    lCreatedDictionaries: [],
          517  +    xDicSelector: document.getElementById("dic_selector"),
          518  +
          519  +    loadDictionaries: function () {
          520  +        if (bChrome) {
          521  +            browser.storage.local.get("shared_dictionaries", this._loadDictionaries.bind(this));
          522  +            browser.storage.local.get("personal_dictionary", this._loadDictionaries.bind(this));
          523  +            browser.storage.local.get("created_dictionaries_list", this._loadDictionaries.bind(this));
          524  +            return;
          525  +        }
          526  +        let xPromise = browser.storage.local.get("shared_dictionaries");
          527  +        xPromise.then(this._loadDictionaries.bind(this), showError);
          528  +        let xPromise2 = browser.storage.local.get("personal_dictionary");
          529  +        xPromise2.then(this._loadDictionaries.bind(this), showError);
          530  +        let xPromise3 = browser.storage.local.get("created_dictionaries_list");
          531  +        xPromise3.then(this._loadDictionaries.bind(this), showError);
          532  +    },
          533  +
          534  +    _loadDictionaries: function (oResult) {
          535  +        if (oResult.hasOwnProperty("shared_dictionaries")) {
          536  +            this.oDictionaries = oResult.shared_dictionaries;
          537  +        }
          538  +        if (oResult.hasOwnProperty("personal_dictionary")) {
          539  +            this.oPersonalDictionary = oResult.personal_dictionary;
          540  +            oBinaryDict.load("__personal__");
          541  +        }
          542  +        if (oResult.hasOwnProperty("created_dictionaries_list")) {
          543  +            this.lCreatedDictionaries = oResult.created_dictionaries_list;
          544  +            for (let sDicName of this.lCreatedDictionaries) {
          545  +                let xOption = createNode("option", {value: sDicName, textContent: sDicName});
          546  +                this.xDicSelector.appendChild(xOption);
          547  +            }
          548  +        }
          549  +    },
          550  +
          551  +    addDictionary: function (sDicName) {
          552  +        if (!this.oDictionaries.hasOwnProperty(sDicName)) {
          553  +            let xOption = createNode("option", {value: sDicName, textContent: sDicName});
          554  +            this.xDicSelector.appendChild(xOption);
          555  +            this.xDicSelector.selectedIndex = this.xDicSelector.options.length-1;
          556  +            this.lCreatedDictionaries.push(sDicName);
          557  +            browser.storage.local.set({ "created_dictionaries_list": this.lCreatedDictionaries });
          558  +        }
          559  +    },
          560  +
          561  +    deleteDictionary: function (sDicName) {
          562  +        this.saveDictionary(sDicName, null);
          563  +        if (sDicName != "__personal__") {
          564  +            let iDict = this.lCreatedDictionaries.indexOf(sDicName);
          565  +            if (iDict > -1) {
          566  +                this.lCreatedDictionaries.splice(iDict, 1);
          567  +            }
          568  +            browser.storage.local.set({ "created_dictionaries_list": this.lCreatedDictionaries });
          569  +            for (let xNode of this.xDicSelector.childNodes) {
          570  +                if (xNode.value == sDicName) {
          571  +                    this.xDicSelector.removeChild(xNode);
          572  +                }
          573  +            }
          574  +            this.xDicSelector.selectedIndex = 0;
          575  +            oBinaryDict.load("__personal__");
          576  +        }
          577  +    },
          578  +
          579  +    getDictionary: function (sName) {
          580  +        if (sName == "__personal__") {
          581  +            return this.oPersonalDictionary;
          582  +        }
          583  +        else if (this.oDictionaries  &&  this.oDictionaries.hasOwnProperty(sName)) {
          584  +            //console.log(this.oDictionaries[sName]);
          585  +            return this.oDictionaries[sName];
          586  +        }
          587  +        return null;
          588  +    },
          589  +
          590  +    saveDictionary: function (sName, oJSON) {
          591  +        if (sName == "__personal__") {
          592  +            browser.runtime.sendMessage({ sCommand: "setDictionary", dParam: {sDictionary: "personal", oDict: oJSON}, dInfo: {} });
          593  +            browser.storage.local.set({ "personal_dictionary": oJSON });
          594  +        }
          595  +        else {
          596  +            this.oDictionaries[sName] = oJSON;
          597  +            if (oJSON === null) {
          598  +                delete this.oDictionaries[sName];
          599  +            }
          600  +            browser.storage.local.set({ "shared_dictionaries": this.oDictionaries });
          601  +            this.sendDictionaryOnline(oJSON);
          602  +        }
          603  +    },
          604  +
          605  +    sendDictionaryOnline: function (oJSON) {
          606  +        let sJSON = "";
          607  +        try {
          608  +            sJSON = JSON.stringify(oJSON);
          609  +        }
          610  +        catch (e) {
          611  +            showError(e);
          612  +            return e.message;
          613  +        }
          614  +        console.log("Send online dictionary: " + oJSON.sDicName);
          615  +        fetch("http://localhost/receive/", {
          616  +            method: "POST", // *GET, POST, PUT, DELETE, etc.
          617  +            //mode: "cors", // no-cors, cors, *same-origin
          618  +            //cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
          619  +            credentials: "include", // include, *same-origin, omit
          620  +            headers: {
          621  +                "Content-Type": "application/json",  // text/plain, application/json
          622  +                "Content-Length": sJSON.length
          623  +            },
          624  +            redirect: "follow", // manual, *follow, error
          625  +            referrer: "no-referrer", // no-referrer, *client
          626  +            body: sJSON, // body data type must match "Content-Type" header
          627  +        })
          628  +        .then((response) => {
          629  +            if (response.ok) {
          630  +                for (let param in response) {
          631  +                    console.log(param, response[param]);
          632  +                }
          633  +                console.log(response);
          634  +                return response.json();
          635  +            } else {
          636  +                console.log("Error: dictionary not sent: " + oJSON.sDicName);
          637  +                return null;
          638  +            }
          639  +        })
          640  +        .then((response) => {
          641  +            if (response) {
          642  +                console.log(response);
          643  +            } else {
          644  +                //
          645  +            }
          646  +        })
          647  +        .catch((e) => {
          648  +            showError(e);
          649  +        });
          650  +    }
          651  +}
          652  +
   495    653   
   496    654   const oBinaryDict = {
   497    655   
   498    656       oIBDAWG: null,
          657  +    sName: "",
          658  +    sDescription: "",
   499    659   
   500         -    load: function () {
   501         -        if (bChrome) {
   502         -            browser.storage.local.get("oPersonalDictionary", this._load.bind(this));
   503         -            return;
   504         -        }
   505         -        let xPromise = browser.storage.local.get("oPersonalDictionary");
   506         -        xPromise.then(this._load.bind(this), showError);
   507         -    },
   508         -
   509         -    _load: function (oResult) {
   510         -        if (!oResult.hasOwnProperty("oPersonalDictionary")) {
   511         -            hideElement("export_button");
   512         -            return;
   513         -        }
   514         -        let oJSON = oResult.oPersonalDictionary;
          660  +    load: function (sName) {
          661  +        console.log("lexicon editor, load: " + sName);
          662  +        this.sName = sName;
          663  +        let oJSON = oDictHandler.getDictionary(sName);
   515    664           if (oJSON) {
   516         -            this.__load(oJSON);
          665  +            this.parseDict(oJSON);
   517    666           } else {
   518    667               oLexiconTable.clear();
   519    668               this.setDictData(0, "[néant]");
          669  +            hideElement("save_button");
   520    670           }
   521    671       },
          672  +
          673  +    newDictionary: function () {
          674  +        this.oIBDAWG = null;
          675  +        this.sName = document.getElementById("new_dictionary_name").value;
          676  +        this.sDescription = document.getElementById("new_dictionary_description").value;
          677  +        oDictHandler.addDictionary(this.sName);
          678  +        oLexiconTable.clear();
          679  +        this.setDictData(0, "[néant]");
          680  +        hideElement("save_button");
          681  +        document.getElementById("new_dictionary_name").value = "";
          682  +        document.getElementById("new_dictionary_description").value = "";
          683  +        hideElement("new_dictionary_section")
          684  +    },
   522    685   
   523         -    __load: function (oJSON) {
          686  +    parseDict: function (oJSON) {
   524    687           try {
   525    688               this.oIBDAWG = new IBDAWG(oJSON);
   526    689           }
   527    690           catch (e) {
   528    691               console.error(e);
   529    692               this.setDictData(0, "#Erreur. Voir la console.");
   530    693               return;
................................................................................
   531    694           }
   532    695           let lEntry = [];
   533    696           for (let aRes of this.oIBDAWG.select()) {
   534    697               lEntry.push(aRes);
   535    698           }
   536    699           oLexiconTable.fill(lEntry);
   537    700           this.setDictData(this.oIBDAWG.nEntry, this.oIBDAWG.sDate);
   538         -    },
   539         -
   540         -    save: function (oJSON) {
   541         -        browser.storage.local.set({ "oPersonalDictionary": oJSON });
   542         -        browser.runtime.sendMessage({ sCommand: "setDictionary", dParam: {sDictionary: "personal", oDict: oJSON}, dInfo: {} });
          701  +        hideElement("save_button");
   543    702       },
   544    703   
   545    704       import: function () {
   546    705           let xInput = document.getElementById("import_input");
   547    706           let xFile = xInput.files[0];
   548    707           let xURL = URL.createObjectURL(xFile);
   549    708           let sJSON = helpers.loadFile(xURL);
   550    709           if (sJSON) {
   551    710               try {
   552    711                   let oJSON = JSON.parse(sJSON);
   553         -                this.__load(oJSON);
   554         -                this.save(oJSON);
          712  +                this.parseDict(oJSON);
          713  +                oDictHandler.saveDictionary(this.sName, oJSON);
   555    714               }
   556    715               catch (e) {
   557    716                   console.error(e);
   558    717                   this.setDictData(0, "#Erreur. Voir la console.");
   559    718                   return;
   560    719               }
   561    720           } else {
   562    721               this.setDictData(0, "[néant]");
   563         -            this.save(null);
          722  +            oDictHandler.saveDictionary(this.sName, null);
   564    723           }
   565    724       },
   566    725   
   567    726       setDictData: function (nEntries, sDate) {
   568    727           document.getElementById("dic_num_entries").textContent = nEntries;
   569    728           document.getElementById("dic_save_date").textContent = sDate;
   570    729           if (nEntries == 0) {
................................................................................
   571    730               hideElement("export_button");
   572    731           } else {
   573    732               showElement("export_button");
   574    733           }
   575    734       },
   576    735   
   577    736       listen: function () {
          737  +        document.getElementById("dic_selector").addEventListener("change", () => {this.load(document.getElementById("dic_selector").value)}, false);
   578    738           document.getElementById("save_button").addEventListener("click", () => { this.build(); }, false);
   579    739           document.getElementById("export_button").addEventListener("click", () => { this.export(); }, false);
   580    740           document.getElementById("import_input").addEventListener("change", () => { this.import(); }, false);
          741  +        document.getElementById("new_dictionary_button").addEventListener("click", () => { switchDisplay("new_dictionary_section"); }, false);
          742  +        document.getElementById("delete_dictionary_button").addEventListener("click", () => { this.delete(); }, false);
          743  +        document.getElementById("create_dictionary_button").addEventListener("click", () => { this.newDictionary(); }, false);
   581    744       },
   582    745   
   583    746       build: function () {
   584    747           let xProgressNode = document.getElementById("wait_progress");
   585    748           let lEntry = oLexiconTable.getEntries();
   586    749           if (lEntry.length > 0) {
   587         -            let oDAWG = new DAWG(lEntry, "S", "fr", "Français", "fr.personal", xProgressNode);
          750  +            let oDAWG = new DAWG(lEntry, "S", "fr", "Français", this.sName, this.sDescription, xProgressNode);
   588    751               let oJSON = oDAWG.createBinaryJSON(1);
   589         -            this.save(oJSON);
          752  +            oDictHandler.saveDictionary(this.sName, oJSON);
   590    753               this.oIBDAWG = new IBDAWG(oJSON);
   591    754               this.setDictData(this.oIBDAWG.nEntry, this.oIBDAWG.sDate);
   592    755           } else {
          756  +            oDictHandler.saveDictionary(this.sName, null);
   593    757               this.setDictData(0, "[néant]");
   594         -            this.save(null);
   595    758           }
   596    759           hideElement("save_button");
   597    760       },
          761  +
          762  +    delete: function () {
          763  +        if (confirm("Voulez-vous effacer le dictionnaire “"+this.sName+"” ?")) {
          764  +            oLexiconTable.clear();
          765  +            this.setDictData(0, "[néant]");
          766  +            oDictHandler.deleteDictionary(this.sName);
          767  +        }
          768  +    },
   598    769   
   599    770       export: function () {
   600    771           let xBlob = new Blob([ JSON.stringify(this.oIBDAWG.getJSON()) ], {type: 'application/json'});
   601    772           let sURL = URL.createObjectURL(xBlob);
   602         -        browser.downloads.download({ filename: "fr.personal.json", url: sURL, saveAs: true });
          773  +        browser.downloads.download({ filename: "fr."+this.sName+".json", url: sURL, saveAs: true });
   603    774       }
   604    775   }
   605    776   
   606    777   
   607    778   const oSearch = {
   608    779   
   609    780       oSpellChecker: null,
................................................................................
   662    833   const oLexiconTable = new Table("lexicon_table", ["Flexions", "Lemmes", "Étiquettes"], "wait_progress", "num_entries");
   663    834   const oSearchTable = new Table("search_table", ["Flexions", "Lemmes", "Étiquettes"], "wait_progress", "search_num_entries", false);
   664    835   const oTagsTable = new Table("tags_table", ["Étiquette", "Signification"], "wait_progress", "", false);
   665    836   
   666    837   
   667    838   oTagsInfo.load();
   668    839   oSearch.load();
   669         -oBinaryDict.load();
          840  +oDictHandler.loadDictionaries();
   670    841   oBinaryDict.listen();
   671    842   oGenerator.listen();
   672    843   oTabulations.listen();
   673    844   oSearch.listen();

Changes to gc_lang/fr/webext/panel/main.css.

   243    243       padding: 5px 10px;
   244    244       border-radius: 3px;
   245    245       font-size: 16px;
   246    246       text-align: center;
   247    247       cursor: pointer;
   248    248   }
   249    249   
          250  +
          251  +.option_section {
          252  +    padding: 10px;
          253  +    margin-top: 10px;
          254  +    border-radius: 5px;
          255  +    background-color: hsl(210, 20%, 96%);
          256  +}
          257  +.option_section label {
          258  +    font-size: 16px;
          259  +    line-height: 20px;
          260  +    color: hsl(210, 20%, 50%);
          261  +    font-weight: bold;
          262  +}
          263  +.option_description {
          264  +    padding: 0 0 0 20px;
          265  +    color: hsl(0, 0%, 0%);
          266  +    font-size: 12px;
          267  +}
          268  +
   250    269   
   251    270   /*
   252    271       Spell checking options
   253    272   */
   254    273   #sc_options_page {
   255    274       display: none;
   256    275       padding: 20px;
................................................................................
   263    282       color: hsl(0, 10%, 96%);
   264    283   }
   265    284   #sc_options_page h2 {
   266    285       margin-top: 20px;
   267    286       font: normal 22px 'Yanone Kaffeesatz', "Oswald", "Liberation Sans Narrow", sans-serif;
   268    287       color: hsl(210, 50%, 50%);
   269    288   }
   270         -
   271         -/*
   272         -    Options
   273         -*/
   274         -.option_section {
   275         -    padding: 10px;
   276         -    margin-top: 10px;
   277         -    border-radius: 5px;
   278         -    background-color: hsl(210, 20%, 96%);
          289  +.button_row {
          290  +    display: flex;
          291  +    flex-direction: row-reverse;
          292  +    padding: 5px 0 0 0;
   279    293   }
   280         -.option_section label {
   281         -    font-size: 16px;
   282         -    line-height: 20px;
   283         -    color: hsl(210, 20%, 50%);
   284         -    font-weight: bold;
   285         -}
   286         -.option_description {
   287         -    padding: 0 0 0 20px;
   288         -    color: hsl(0, 0%, 0%);
   289         -    font-size: 12px;
          294  +.dic_button {
          295  +    margin-left: 10px;
          296  +    padding: 2px 10px;
          297  +    background-color: hsl(210, 50%, 50%);
          298  +    color: hsl(210, 10%, 96%);
          299  +    cursor: pointer;
          300  +    font-size: 14px;
          301  +    font-variant-caps: small-caps;
          302  +    border-radius: 3px;
   290    303   }
   291    304   
   292    305   
   293    306   /*
   294    307     Test page
   295    308   */
   296    309   #test_page {

Changes to gc_lang/fr/webext/panel/main.html.

    84     84                 <p class="option_description">Ces zones de texte sont les champs de formulaire usuels pour saisir du texte. Ils ne permettent que la saisie de texte brut, sans fioritures.</p>
    85     85             </div>
    86     86             <div class="option_section" id="ui_option_editablenode_box">
    87     87                 <p><input type="checkbox" id="ui_option_editablenode" /> <label for="ui_option_editablenode">“Nodes” éditables</label></p>
    88     88                 <p class="option_description">Ces zones de texte sont des sections de page web éditables. Il est fréquent que ces zones de texte apparaissent comme des “textareas” standard. Il est aussi fréquent que ces zones de texte soient couplées avec toutes sortes de logiciels (de simples scripts d’aide à la mise en page ou des applications complexes). Ces zones de texte permettent l’affichage de texte enrichi (italique, gras, hyperlien, images, etc.).</p>
    89     89             </div>
    90     90             <h2>Raccourcis clavier</h2>
           91  +          <p class="shortcut">CTRL+MAJ+L</p>
           92  +          <p class="shortcut_label">Lexicographe</p>
           93  +          <p class="shortcut">CTRL+MAJ+F</p>
           94  +          <p class="shortcut_label">Formateur de texte</p>
           95  +          <p class="shortcut">CTRL+MAJ+V</p>
           96  +          <p class="shortcut_label">Correcteur grammatical</p>
    91     97             <p class="shortcut">CTRL+MAJ+6</p>
    92     98             <p class="shortcut_label">Conjugueur (dans un onglet)</p>
    93     99             <p class="shortcut">CTRL+MAJ+7</p>
    94    100             <p class="shortcut_label">Conjugueur (dans une fenêtre)</p>
    95    101             <p class="shortcut">CTRL+MAJ+8</p>
    96    102             <p class="shortcut_label">Éditeur lexical</p>
          103  +          <p class="shortcut">CTRL+MAJ+9</p>
          104  +          <p class="shortcut_label">Dictionnaires communautaires</p>
    97    105           </section> <!-- #ui_options_page -->
    98    106   
    99    107           <section id="gc_options_page" class="page">
   100    108             <h1>OPTIONS GRAMMATICALES</h1>
   101    109             <div id="grammar_options">
   102    110               ${webextOptionsHTML}
   103    111             </div>
................................................................................
   104    112             <div id="default_options_button" class="button blue center">Options par défaut</div>
   105    113           </section> <!-- #gc_options_page -->
   106    114   
   107    115           <section id="sc_options_page" class="page">
   108    116             <h1>OPTIONS ORTHOGRAPHIQUES</h1>
   109    117             <div id="hunspell_options">
   110    118               <h2>DICTIONNAIRES DE GRAMMALECTE</h2>
   111         -            <p class="dictionaries_info">Ces dictionnaires ne sont utilisés que lors de l’analyse grammaticale.</p>
   112    119               <div class="option_section" id="main_dic_box">
   113         -                <p><input type="checkbox" id="main_dic" data-dictionary="personal" checked disabled="disabled" /> <label for="main_dic">Dictionnaire principal</label></p>
   114         -                <p class="option_description">Environ 83 000 entrées, 500 000 flexions.<br/>Ni éditable, ni désactivable.</p>
          120  +                <p><input type="checkbox" id="main_dic" data-dictionary="main" checked disabled="disabled" /> <label for="main_dic">Dictionnaire principal</label></p>
          121  +                <p class="option_description">Environ 83 000 entrées, 500 000 flexions.<br/>Ni éditable, ni désactivable.<br/>Ce dictionnaire est créé à partir du dictionnaire orthographique pour Firefox et LibreOffice, conçu sur le <a id="link_grammalecte" class="link" data-url="http://grammalecte.net/home.php?prj=fr">site de Grammalecte</a>.</p>
   115    122               </div>
   116         -            <div class="option_section" id="extended_dic_box" style="opacity: .33;">
   117         -                <p><input type="checkbox" id="extended_dic" data-dictionary="personal" disabled="disabled" /> <label for="extended_dic">Dictionnaire étendu</label></p>
   118         -                <p class="option_description">Fonctionnalité à venir.</p>
          123  +            <div class="option_section" id="community_dic_box">
          124  +                <p><input type="checkbox" id="community_dic" data-dictionary="community" /> <label for="community_dic">Dictionnaire communautaire</label></p>
          125  +                <p class="option_description">Ce dictionnaire est créé et édité via l’éditeur lexical et est sauvegardé sur un serveur en ligne accessible à tous les membres.</p>
          126  +                <div class="button_row">
          127  +                  <div class="dic_button" id="dic_community_button">Éditer</div>
          128  +                  <div class="dic_button" id="dictionaries_button">Dictionnaires en ligne</div>
   119    129               </div>
   120         -            <div class="option_section" id="community_dic_box" style="opacity: .33;">
   121         -                <p><input type="checkbox" id="community_dic" data-dictionary="personal" disabled="disabled" /> <label for="community_dic">Dictionnaire communautaire</label></p>
   122         -                <p class="option_description">Fonctionnalité à venir.</p>
   123    130               </div>
   124    131               <div class="option_section" id="personal_dic_box">
   125    132                   <p><input type="checkbox" id="personal_dic" data-dictionary="personal" /> <label for="personal_dic">Dictionnaire personnel</label></p>
   126         -                <p class="option_description">Ce dictionnaire est créé et édité via l’éditeur lexical.</p>
          133  +                <p class="option_description">Ce dictionnaire est créé et édité via l’éditeur lexical et n’est pas partagé.</p>
          134  +                <div class="button_row">
          135  +                  <div class="dic_button" id="dic_personal_button">Éditer</div>
          136  +                </div>
   127    137               </div>
   128    138             </div>
   129    139   
   130    140             <div id="hunspell_options" style="display: none; opacity: .25">
   131    141               <h2>DICTIONNAIRES HUNSPELL</h2>
   132    142               <p class="dictionaries_info">Les dictionnaires Hunspell ne sont utilisées que pour la correction orthographique proposée par Firefox (soulignement rouge).<br/><br/>Section inactive.<br/>Le système d’extension <i>WebExtension</i> ne propose pas encore d’interface de programmation pour le correcteur orthographique.</p>
   133    143               <div class="option_section" id="fr-FR-modern_box">

Changes to gc_lang/fr/webext/panel/main.js.

    68     68               else if (xElem.id.startsWith("ui_option_")) {
    69     69                   storeUIOptions();
    70     70               }
    71     71               else if (xElem.id.startsWith("link_")) {
    72     72                   browser.tabs.create({url: xElem.dataset.url});
    73     73               }
    74     74               else if (xElem.id == "conj_button") {
    75         -                openConjugueurTab();
           75  +                browser.runtime.sendMessage({
           76  +                    sCommand: "openConjugueurTab",
           77  +                    dParam: {},
           78  +                    dInfo: {}
           79  +                });
           80  +            }
           81  +            else if (xElem.id == "dictionaries_button") {
           82  +                browser.runtime.sendMessage({
           83  +                    sCommand: "openDictionaries",
           84  +                    dParam: { "dictionary": "__community__"},
           85  +                    dInfo: {}
           86  +                });
           87  +            }
           88  +            else if (xElem.id == "dic_community_button") {
           89  +                browser.runtime.sendMessage({
           90  +                    sCommand: "openLexiconEditor",
           91  +                    dParam: { "dictionary": "__community__"},
           92  +                    dInfo: {}
           93  +                });
           94  +            }
           95  +            else if (xElem.id == "dic_personal_button") {
           96  +                browser.runtime.sendMessage({
           97  +                    sCommand: "openLexiconEditor",
           98  +                    dParam: { "dictionary": "__personal__"},
           99  +                    dInfo: {}
          100  +                });
    76    101               }
    77    102           } else if (xElem.className.startsWith("select")) {
    78    103               showPage(xElem.dataset.page);
    79    104           }/* else if (xElem.tagName === "A") {
    80    105               openURL(xElem.getAttribute("href"));
    81    106           }*/
    82    107       },
................................................................................
   154    179   }
   155    180   
   156    181   
   157    182   function showTestResult (sText) {
   158    183       document.getElementById("tests_result").textContent = sText;
   159    184   }
   160    185   
   161         -function openConjugueurTab () {
   162         -    if (bChrome) {
   163         -        browser.tabs.create({
   164         -            url: browser.extension.getURL("panel/conjugueur.html")
   165         -        });
   166         -        return;
   167         -    }
   168         -    let xConjTab = browser.tabs.create({
   169         -        url: browser.extension.getURL("panel/conjugueur.html")
   170         -    });
   171         -    xConjTab.then(onCreated, onError);
   172         -}
   173         -
   174         -function openConjugueurTab () {
   175         -    if (bChrome) {
   176         -        browser.tabs.create({
   177         -            url: browser.extension.getURL("panel/conjugueur.html")
   178         -        });
   179         -        return;
   180         -    }
   181         -    let xConjTab = browser.tabs.create({
   182         -        url: browser.extension.getURL("panel/conjugueur.html")
   183         -    });
   184         -    xConjTab.then(onCreated, onError);
   185         -}
   186         -
   187    186   
   188    187   /*
   189    188       UI options
   190    189   */
   191    190   function displayUIOptionsLoadedFromStorage () {
   192    191       if (bChrome) {
   193    192           browser.storage.local.get("ui_options", displayUIOptions);
................................................................................
   232    231   
   233    232   function displaySCOptions (dOptions) {
   234    233       if (!dOptions.hasOwnProperty("sc_options")) {
   235    234           console.log("no sc options found");
   236    235           return;
   237    236       }
   238    237       dOptions = dOptions.sc_options;
   239         -    //document.getElementById("extended_dic").checked = dOptions.extended_dic;
   240         -    //document.getElementById("community_dic").checked = dOptions.community_dic;
          238  +    //document.getElementById("extended_dic").checked = dOptions.extended;
          239  +    document.getElementById("community_dic").checked = dOptions.community;
   241    240       document.getElementById("personal_dic").checked = dOptions.personal;
   242    241   }
   243    242   
   244    243   function storeSCOptions () {
   245    244       browser.storage.local.set({"sc_options": {
   246    245           extended: false,
   247         -        community: false,
          246  +        community: document.getElementById("community_dic").checked,
   248    247           personal: document.getElementById("personal_dic").checked
   249    248       }});
   250    249   }
   251    250   
   252    251   
   253    252   /*
   254    253       GC options

Changes to graphspell-js/dawg.js.

   416    416   
   417    417       _getDate () {
   418    418           let oDate = new Date();
   419    419           let sMonth = (oDate.getMonth() + 1).toString().padStart(2, "0"); // Month+1: Because JS always sucks somehow.
   420    420           let sDay = (oDate.getDate()).toString().padStart(2, "0");
   421    421           let sHours = (oDate.getHours()).toString().padStart(2, "0");
   422    422           let sMinutes = (oDate.getMinutes()).toString().padStart(2, "0");
   423         -        return `${oDate.getFullYear()}-${sMonth}-${sDay}, ${sHours}:${sMinutes}`;
          423  +        let sSeconds = (oDate.getSeconds()).toString().padStart(2, "0");
          424  +        return `${oDate.getFullYear()}-${sMonth}-${sDay} ${sHours}:${sMinutes}:${sSeconds}`;
   424    425       }
   425    426   }
   426    427   
   427    428   
   428    429   const oNodeCounter = {
   429    430       nNextId: 0,
   430    431   

Added graphspell-js/dic_merger.js.

            1  +// Dictionaries merger
            2  +
            3  +"use strict";
            4  +
            5  +if (typeof(process) !== 'undefined') {
            6  +    var dawg = require("./dawg.js");
            7  +    var ibdawg = require("./ibdawg.js");
            8  +}
            9  +else if (typeof(require) !== 'undefined') {
           10  +    var dawg = require("resource://grammalecte/graphspell/dawg.js");
           11  +    var ibdawg = require("resource://grammalecte/graphspell/ibdawg.js");
           12  +}
           13  +
           14  +
           15  +const dic_merger = {
           16  +
           17  +    merge: function (lDict, cStemming, sLangCode, sLangName, sDicName, sDescription, xProgressBar=null) {
           18  +        // merge a list of dictionaries (given as JSON objects)
           19  +        // return a merged dictionary as JSON object.
           20  +        if (xProgressBar) {
           21  +            xProgressBar.max = lDict.length;
           22  +            xProgressBar.value = 0;
           23  +        }
           24  +        let lEntry = [];
           25  +        for (let oDict of lDict) {
           26  +            // generate words list from selected dictionaries
           27  +            if (xProgressBar) {
           28  +                xProgressBar.value += 1;
           29  +            }
           30  +            try {
           31  +                let oIBDAWG = new IBDAWG(oDict);
           32  +                for (let aRes of oIBDAWG.select()) {
           33  +                    lEntry.push(aRes);
           34  +                }
           35  +            }
           36  +            catch (e) {
           37  +                console.error(e);
           38  +            }
           39  +        }
           40  +        if (xProgressBar) {
           41  +            xProgressBar.value = xProgressBar.max;
           42  +        }
           43  +        try {
           44  +            let oDAWG = new DAWG(lEntry, cStemming, sLangCode, sLangName, sDicName, sDescription, xProgressBar);
           45  +            let oDict = oDAWG.createBinaryJSON(1);
           46  +            return oDict;
           47  +        }
           48  +        catch (e) {
           49  +            console.log("Dictionaries merger: unable to generate merged dictionary");
           50  +            console.error(e);
           51  +            return null;
           52  +        }
           53  +    }
           54  +
           55  +}

Changes to graphspell/dawg.py.

    18     18   import re
    19     19   import traceback
    20     20   
    21     21   from . import str_transform as st
    22     22   from .progressbar import ProgressBar
    23     23   
    24     24   
           25  +
           26  +dLexiconData = {}
    25     27   
    26     28   def readFile (spf):
    27     29       "generator: read file <spf> and return for each line a list of elements separated by a tabulation."
    28     30       print(" < Read lexicon: " + spf)
    29     31       if os.path.isfile(spf):
           32  +        dLexiconData.clear()
    30     33           with open(spf, "r", encoding="utf-8") as hSrc:
    31     34               for sLine in hSrc:
    32     35                   sLine = sLine.strip()
    33         -                if sLine and not sLine.startswith("#"):
           36  +                if sLine.startswith("##") :
           37  +                    m = re.match("## *(\\w+) *:(.*)$", sLine)
           38  +                    if m:
           39  +                        dLexiconData[m.group(1)] = m.group(2).strip()
           40  +                elif sLine and not sLine.startswith("#"):
    34     41                       yield sLine.split("\t")
           42  +        if dLexiconData:
           43  +            print("Data from dictionary:")
           44  +            print(dLexiconData)
    35     45       else:
    36     46           raise OSError("# Error. File not found or not loadable: " + spf)
    37     47   
    38     48   
    39     49   
    40     50   class DAWG:
    41     51       """DIRECT ACYCLIC WORD GRAPH"""
................................................................................
   118    128                           + [ (dTag[tag]+nChar+nAff, dTagOccur[tag]) for tag in dTag ] )
   119    129   
   120    130           self.sFileName = src  if type(src) is str  else "[None]"
   121    131           self.sLangCode = sLangCode
   122    132           self.sLangName = sLangName
   123    133           self.sDicName = sDicName
   124    134           self.sDescription = sDescription
          135  +        if dLexiconData:
          136  +            self.sLangCode = dLexiconData.get("LangCode", self.sLangCode)
          137  +            self.sLangName = dLexiconData.get("LangName", self.sLangName)
          138  +            self.sDicName = dLexiconData.get("DicName", self.sDicName)
          139  +            self.sDescription = dLexiconData.get("Description", self.sDescription)
   125    140           self.nEntry = len(lWord)
   126    141           self.aPreviousEntry = []
   127    142           DawgNode.resetNextId()
   128    143           self.oRoot = DawgNode()
   129    144           self.lUncheckedNodes = []  # list of nodes that have not been checked for duplication.
   130    145           self.lMinimizedNodes = {}  # list of unique nodes that have been checked for duplication.
   131    146           self.lSortedNodes = []     # version 2 and 3
................................................................................
   541    556                   hDst.write(self.oRoot.convToBytes3(self.nBytesArc, self.nBytesNodeAddress, self.nBytesOffset))
   542    557                   for oNode in self.lSortedNodes:
   543    558                       hDst.write(oNode.convToBytes3(self.nBytesArc, self.nBytesNodeAddress, self.nBytesOffset))
   544    559           if bDebug:
   545    560               self._writeNodes(sPathFile, nCompressionMethod)
   546    561   
   547    562       def _getDate (self):
   548         -        return time.strftime("%Y.%m.%d, %H:%M")
          563  +        return time.strftime("%Y-%m-%d %H:%M:%S")
   549    564   
   550    565       def _writeNodes (self, sPathFile, nCompressionMethod):
   551    566           "for debugging only"
   552    567           print(" > Write nodes")
   553    568           with open(sPathFile+".nodes."+str(nCompressionMethod)+".txt", 'w', encoding='utf-8', newline="\n") as hDst:
   554    569               if nCompressionMethod == 1:
   555    570                   hDst.write(self.oRoot.getTxtRepr1(self.nBytesArc, self.lArcVal)+"\n")

Changes to lex_build.py.

    27     27       xParser.add_argument("lang_code", type=str, help="language code")
    28     28       xParser.add_argument("lang_name", type=str, help="language name")
    29     29       xParser.add_argument("dic_filename", type=str, help="dictionary file name (without extension)")
    30     30       xParser.add_argument("-js", "--json", help="Build dictionary in JSON", action="store_true")
    31     31       xParser.add_argument("-s", "--stemming", help="stemming method: S=suffixes, A=affixes, N=no stemming", type=str, choices=["S", "A", "N"], default="S")
    32     32       xParser.add_argument("-c", "--compress", help="compression method: 1, 2 (beta), 3, (beta)", type=int, choices=[1, 2, 3], default=1)
    33     33       xArgs = xParser.parse_args()
    34         -    build(xArgs.src_lexicon, xArgs.lang_code, xArgs.lang_name, xArgs.dic_filename, "", xArgs.json)
           34  +    build(xArgs.src_lexicon, xArgs.lang_code, xArgs.lang_name, xArgs.dic_filename, xArgs.json)
    35     35   
    36     36   
    37     37   if __name__ == '__main__':
    38     38       main()

Changes to lexicons/French.lex.

     1      1   # This Source Code Form is subject to the terms of the Mozilla Public
     2      2   # License, v. 2.0. If a copy of the MPL was not distributed with this
     3      3   # file, You can obtain one at http://mozilla.org/MPL/2.0/.
     4      4   
     5      5   # Lexique simplifié pour Grammalecte v6.4
     6      6   # Licence : MPL v2.0
     7      7   
     8         -## LangCode: fr
     9         -## LangName: Français
    10         -## DicName: fr.commun
    11         -## Description: Français commun (toutes variantes)
    12         -## Author: Olivier R.
    13         -
    14      8   # :POS ;LEX ~SEM =FQ /DIC
    15      9   de	de	:G:D:e:i/*
    16     10   de	de	:G:R:Rv/*
    17     11   et	et	:G:Cc/*
    18     12   à	à	:G:R:Rv/*
    19     13   des	des	:G:D:e:p/*
    20     14   du	du	:G:D:m:s/*

Changes to make.py.

   346    346   
   347    347   def buildDictionary (dVars, sType, bJavaScript=False):
   348    348       "build binary dictionary for Graphspell from lexicons"
   349    349       if sType == "main":
   350    350           spfLexSrc = dVars['lexicon_src']
   351    351           lSfDictDst = dVars['dic_filenames'].split(",")
   352    352           lDicName = dVars['dic_name'].split(",")
          353  +        lDescription = dVars['dic_description'].split(",")
   353    354           lFilter = dVars['dic_filter'].split(",")
   354         -        for sfDictDst, sDicName, sFilter in zip(lSfDictDst, lDicName, lFilter):
   355         -            lex_build.build(spfLexSrc, dVars['lang'], dVars['lang_name'], sfDictDst, bJavaScript, sDicName, "", sFilter, dVars['stemming_method'], int(dVars['fsa_method']))
          355  +        for sfDictDst, sDicName, sDescription, sFilter in zip(lSfDictDst, lDicName, lDescription, lFilter):
          356  +            lex_build.build(spfLexSrc, dVars['lang'], dVars['lang_name'], sfDictDst, bJavaScript, sDicName, sDescription, sFilter, dVars['stemming_method'], int(dVars['fsa_method']))
   356    357       else:
   357    358           if sType == "extended":
   358    359               spfLexSrc = dVars['lexicon_extended_src']
   359    360               sfDictDst = dVars['dic_extended_filename']
   360    361               sDicName = dVars['dic_extended_name']
          362  +            sDescription = dVars['dic_extended_description']
   361    363           elif sType == "community":
   362    364               spfLexSrc = dVars['lexicon_community_src']
   363    365               sfDictDst = dVars['dic_community_filename']
   364    366               sDicName = dVars['dic_community_name']
          367  +            sDescription = dVars['dic_community_description']
   365    368           elif sType == "personal":
   366    369               spfLexSrc = dVars['lexicon_personal_src']
   367    370               sfDictDst = dVars['dic_personal_filename']
   368    371               sDicName = dVars['dic_personal_name']
   369         -        lex_build.build(spfLexSrc, dVars['lang'], dVars['lang_name'], sfDictDst, bJavaScript, sDicName, "", "", dVars['stemming_method'], int(dVars['fsa_method']))
   370         -
          372  +            sDescription = dVars['dic_personal_description']
          373  +        lex_build.build(spfLexSrc, dVars['lang'], dVars['lang_name'], sfDictDst, bJavaScript, sDicName, sDescription, "", dVars['stemming_method'], int(dVars['fsa_method']))
   371    374   
   372    375   
   373    376   def main ():
   374    377       "build Grammalecte with requested options"
   375    378       print("Python: " + sys.version)
   376    379       xParser = argparse.ArgumentParser()
   377    380       xParser.add_argument("lang", type=str, nargs='+', help="lang project to generate (name of folder in /lang)")