├── .gitignore ├── src ├── hacks │ ├── README.md │ ├── polyfill.js │ ├── interceptador.js │ ├── ie.js │ └── chrome.js ├── lexml │ ├── ArticulacaoInvalidaException.js │ └── importarDeLexML.js ├── editor-articulacao.js ├── editor-articulacao-css-shadow.js ├── validacao │ ├── ValidadorCaixaAlta.js │ ├── ValidadorPontuacaoArtigoParagrafo.js │ ├── ValidadorIniciarLetraMaiuscula.js │ ├── ValidadorPontuacaoEnumeracao.js │ ├── ProblemaValidacao.js │ ├── ValidadorSentencaUnica.js │ ├── ValidadorEnumeracaoElementos.js │ ├── Validador.js │ ├── ValidadorCitacao.js │ └── ValidacaoController.js ├── eventos │ ├── ArticulacaoChangeEvent.js │ ├── ContextoArticulacaoAtualizadoEvent.js │ ├── TransformacaoAutomaticaEvent.js │ └── EventoInterno.js ├── transformacaoAutomatica │ ├── transformacoes │ │ ├── AoFecharAspas.js │ │ ├── DoisPontos.js │ │ ├── AoIniciarAspas.js │ │ ├── PontoFinal.js │ │ ├── RecuarComEnterEmDispositivoVazio.js │ │ └── Transformacao.js │ └── transformacaoAutomatica.js ├── editor-html.js ├── opcoesPadrao.js ├── util.js ├── ComponenteEdicao.js ├── ControleAlteracao.js ├── editor-articulacao-css.js ├── ContextoArticulacao.js ├── ClipboardController.js └── editor-articulacao.d.ts ├── bower.json ├── .github └── workflows │ └── node.js.yml ├── package.json ├── webpack.config.js ├── empacotamento ├── karma.js ├── angular1.js └── plain-js.js ├── protractor-conf.js ├── test ├── protractor │ ├── validacao.spec.js │ ├── utilitariosTeste.js │ ├── teste.html │ └── formatacaoDispositivos.spec.js └── karma │ ├── validacao.spec.js │ ├── clipboardController.spec.js │ ├── exportacaoParaLexML.spec.js │ └── importacaoDeLexML.spec.js ├── karma.conf.js ├── Gruntfile.js ├── exemplo └── exemplo.html ├── COPYING.LESSER └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .factorypath 3 | .project 4 | .settings 5 | /.vscode 6 | /build/*.js 7 | /build/*.map 8 | /coverage 9 | /node_modules 10 | debug.log 11 | -------------------------------------------------------------------------------- /src/hacks/README.md: -------------------------------------------------------------------------------- 1 | # Hacks para navegadores 2 | 3 | Qualquer código que seja exclusivamente para tratar um comportamento específico 4 | de um determinado navegador deve ser externalizado, de forma a melhorar 5 | a legibilidade do código e manutenibilidade, de forma a permitir a 6 | validação dos hacks em versões novas dos navegadores. Tais códigos devem 7 | ser separados por navegador, de forma a agrupar qualquer intervenção 8 | específica para um navegador. 9 | 10 | # Polyfill 11 | 12 | Polyfill deve também ser implementado à parte, no arquivo `polyfill.js`. -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "silegismg-editor-articulacao", 3 | "description": "Biblioteca javasciprt de edição da articulação de uma norma ou projeto de lei, conforme formato especificado pelo LexML.", 4 | "main": "src/editor-articulacao.js", 5 | "authors": [ 6 | "Assembleia Legislativa de Minas Gerais (ALMG)" 7 | ], 8 | "license": "LGPL", 9 | "keywords": [ 10 | "silegismg", 11 | "lexml", 12 | "articulacao", 13 | "editor", 14 | "almg", 15 | "assembleia", 16 | "camara", 17 | "senado", 18 | "legislativa", 19 | "legislativo", 20 | "lexedit" 21 | ], 22 | "homepage": "", 23 | "ignore": [ 24 | "**/.*", 25 | "node_modules", 26 | "bower_components", 27 | "test", 28 | "tests" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Teste de Unidade 5 | 6 | on: 7 | push: 8 | branches: [ master, develop ] 9 | pull_request: 10 | branches: [ master, develop ] 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Use Node.js 19 | uses: actions/setup-node@v2 20 | with: 21 | node-version: 18 22 | cache: 'npm' 23 | - run: npm ci 24 | - name: jshint 25 | run: npx grunt jshint 26 | - name: unit tests 27 | run: npx grunt karma:unit 28 | 29 | -------------------------------------------------------------------------------- /src/lexml/ArticulacaoInvalidaException.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | class ArticulacaoInvalidaException { 19 | constructor(dispositivo, mensagem) { 20 | this.dispositivo = dispositivo; 21 | this.mensagem = mensagem; 22 | } 23 | 24 | get tipo() { 25 | return dispositivo.tagName; 26 | } 27 | } 28 | 29 | export default ArticulacaoInvalidaException; -------------------------------------------------------------------------------- /src/editor-articulacao.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import ComponenteEdicao from './ComponenteEdicao'; 19 | import EditorArticulacaoController from './EditorArticulacaoController'; 20 | import interpretadorArticulacao from './interpretadorArticulacao'; 21 | 22 | export default { 23 | ComponenteEdicao: ComponenteEdicao, 24 | EditorArticulacaoController: EditorArticulacaoController, 25 | interpretadorArticulacao: interpretadorArticulacao 26 | }; 27 | 28 | export { ComponenteEdicao, EditorArticulacaoController, interpretadorArticulacao }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "silegismg-editor-articulacao", 3 | "version": "1.0.10", 4 | "description": "Editor de Articulação", 5 | "main": "src/editor-articulacao.js", 6 | "types": "src/editor-articulacao.d.ts", 7 | "scripts": { 8 | "build": "npx grunt build", 9 | "test": "npx grunt test", 10 | "start": "npx grunt" 11 | }, 12 | "keywords": [ 13 | "silegismg", 14 | "lexml", 15 | "articulacao", 16 | "editor", 17 | "almg", 18 | "assembleia", 19 | "camara", 20 | "senado", 21 | "legislativa", 22 | "legislativo", 23 | "lexedit" 24 | ], 25 | "author": "Assembleia Legislativa de Minas Gerais (ALMG)", 26 | "license": "LGPL-3.0", 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/silegis-mg/editor-articulacao.git" 30 | }, 31 | "devDependencies": { 32 | "grunt-contrib-jshint": "^3.2.0", 33 | "grunt-karma": "^4.0.2", 34 | "grunt-webpack": "^6.0.0", 35 | "jasmine": "^2.99.0", 36 | "jshint": "^2.13.4", 37 | "karma-chrome-launcher": "^3.2.0", 38 | "karma-firefox-launcher": "^2.1.2", 39 | "karma-jasmine": "^5.1.0", 40 | "karma-webpack": "^5.0.0", 41 | "matchdep": "^2.0.0", 42 | "webpack": "^5.88.2", 43 | "webpack-dev-server": "^4.15.1" 44 | }, 45 | "dependencies": {} 46 | } 47 | -------------------------------------------------------------------------------- /src/editor-articulacao-css-shadow.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | const cssShadow = ` 18 | :host { 19 | display: block; 20 | text-indent: 4ex; 21 | } 22 | 23 | :host([disabled]) { 24 | color: gray; 25 | } 26 | 27 | .silegismg-editor-articulacao p { 28 | text-indent: inherit !important; 29 | } 30 | 31 | .silegismg-editor-articulacao p:before { 32 | color: var(--rotulo-cor, currentColor); 33 | font-weight: var(--rotulo-peso, bolder); 34 | background: var(--rotulo-background, inherit); 35 | } 36 | `; 37 | 38 | export default cssShadow; -------------------------------------------------------------------------------- /src/validacao/ValidadorCaixaAlta.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import Validador from './Validador'; 19 | 20 | class ValidadorIniciarLetraMaiuscula extends Validador { 21 | constructor() { 22 | super(['artigo', 'paragrafo'], 'Artigos e parágrafos não devem ser escritos usando apenas caixa alta.'); 23 | } 24 | 25 | validar(dispositivo) { 26 | var texto = dispositivo.textContent; 27 | 28 | return texto.length === 0 || texto.toUpperCase() !== texto; 29 | } 30 | } 31 | 32 | export default ValidadorIniciarLetraMaiuscula; -------------------------------------------------------------------------------- /src/validacao/ValidadorPontuacaoArtigoParagrafo.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import Validador from './Validador'; 19 | 20 | class ValidadorPontuacaoArtigoParagrafo extends Validador { 21 | constructor() { 22 | super(['artigo', 'paragrafo'], 'Artigos e parágrafos devem ser terminados com ponto final (.) ou dois pontos (:), sem espaço antes da pontuação.'); 23 | } 24 | 25 | validar(dispositivo) { 26 | return /.*\S+[:.]$/.test(dispositivo.textContent.trim()); 27 | } 28 | } 29 | 30 | export default ValidadorPontuacaoArtigoParagrafo; -------------------------------------------------------------------------------- /src/validacao/ValidadorIniciarLetraMaiuscula.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import Validador from './Validador'; 19 | 20 | class ValidadorIniciarLetraMaiuscula extends Validador { 21 | constructor() { 22 | super(['artigo', 'paragrafo'], 'Artigos e parágrafos devem ser iniciados com letra maiúscula.'); 23 | } 24 | 25 | validar(dispositivo) { 26 | var texto = dispositivo.textContent.trim(); 27 | 28 | return texto.charAt(0).toUpperCase() === texto.charAt(0); 29 | } 30 | } 31 | 32 | export default ValidadorIniciarLetraMaiuscula; -------------------------------------------------------------------------------- /src/validacao/ValidadorPontuacaoEnumeracao.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import Validador from './Validador'; 19 | 20 | class ValidadorPontuacaoEnumeracao extends Validador { 21 | constructor() { 22 | super(['inciso', 'alinea', 'item'], 'Enumerações devem ser terminadas com ponto final (.), ponto e vírgula (;) ou dois pontos (:), sem espaço antes da pontuação.'); 23 | } 24 | 25 | validar(dispositivo) { 26 | return /.*\S+(?:[;.:]|; ou|; e)$/.test(dispositivo.textContent.trim()); 27 | } 28 | } 29 | 30 | export default ValidadorPontuacaoEnumeracao; -------------------------------------------------------------------------------- /src/eventos/ArticulacaoChangeEvent.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import EventoInterno from './EventoInterno'; 19 | 20 | /** 21 | * Evento de notificação de atualização do conteúdo da articulação. 22 | */ 23 | class ArticulacaoChangeEvent extends EventoInterno { 24 | /** 25 | * Constrói o evento. 26 | * 27 | * @param {EditorArticulacaoController} editorArticulacaoCtrl 28 | */ 29 | constructor(editorArticulacaoCtrl) { 30 | super('change', { 31 | bubbles: true, 32 | cancelable: false 33 | }); 34 | } 35 | } 36 | 37 | export default ArticulacaoChangeEvent; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | const path = require('path'); 18 | 19 | module.exports = function (empacotamento) { 20 | var entry = './empacotamento/' + empacotamento + '.js'; 21 | var sufixo = empacotamento; 22 | 23 | return { 24 | entry: entry, 25 | output: { 26 | path: __dirname + '/build', 27 | filename: 'silegismg-editor-articulacao-' + sufixo + '.js' 28 | }, 29 | mode: 'production', 30 | devServer: { 31 | static: { 32 | directory: path.join(__dirname, 'test/puppeteer') 33 | }, 34 | port: 9000 35 | } 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /src/eventos/ContextoArticulacaoAtualizadoEvent.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import EventoInterno from './EventoInterno'; 19 | 20 | /** 21 | * Evento de notificação de atualização do contexto de edição. 22 | * Útil para atualização da interface de usuário, habilitando ou 23 | * desabilitando botões. 24 | */ 25 | class ContextoArticulacaoAtualizadoEvent extends EventoInterno { 26 | /** 27 | * Constrói o evento. 28 | * 29 | * @param {ContextoArticulacao} contexto 30 | */ 31 | constructor(contexto) { 32 | super('contexto', { 33 | detail: contexto 34 | }); 35 | } 36 | } 37 | 38 | export default ContextoArticulacaoAtualizadoEvent; -------------------------------------------------------------------------------- /src/transformacaoAutomatica/transformacoes/AoFecharAspas.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import { TransformacaoDoProximo } from './Transformacao'; 19 | 20 | /** 21 | * Quando usuário encerra as aspas na continuação do caput de um artigo, 22 | * então cria-se um novo artigo. 23 | */ 24 | class AoFecharAspas extends TransformacaoDoProximo { 25 | constructor() { 26 | super('"\n', '".\n'); 27 | } 28 | 29 | get tipoTransformacao() { 30 | return 'AoFecharAspas'; 31 | } 32 | 33 | proximoTipo(editor, ctrl, contexto) { 34 | return contexto.cursor.artigo && contexto.cursor.continuacao ? 'artigo' : null; 35 | } 36 | } 37 | 38 | export default AoFecharAspas; -------------------------------------------------------------------------------- /src/validacao/ProblemaValidacao.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | /** 19 | * Representa um problema que descreve a invalidação de um dispositivo. 20 | */ 21 | class ProblemaValidacao { 22 | /** 23 | * Constrói o problema. 24 | * 25 | * @param {Element} dispositivo Dispositivo inválido. 26 | * @param {String} tipo Tipo do validador (nome da classe) que considerou o dispositivo inválido. 27 | * @param {String} descricao Descrição para o problema. 28 | */ 29 | constructor(dispositivo, tipo, descricao) { 30 | this.dispositivo = dispositivo; 31 | this.tipo = tipo; 32 | this.descricao = descricao; 33 | } 34 | } 35 | 36 | export default ProblemaValidacao; -------------------------------------------------------------------------------- /empacotamento/karma.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import importarDeLexML from '../src/lexml/importarDeLexML'; 19 | import exportarParaLexML from '../src/lexml/exportarParaLexML'; 20 | import interpretadorArticulacao from '../src/interpretadorArticulacao'; 21 | import { transformarTextoPuro, transformar } from '../src/ClipboardController'; 22 | import ValidacaoController from '../src/validacao/ValidacaoController'; 23 | 24 | window.importarDeLexML = importarDeLexML; 25 | window.exportarParaLexML = exportarParaLexML; 26 | window.interpretadorArticulacao = interpretadorArticulacao; 27 | window.clipboardControllerModule = { 28 | interpretarTextoPuro: transformarTextoPuro, 29 | transformar: transformar 30 | }; 31 | window.ValidacaoController = ValidacaoController; -------------------------------------------------------------------------------- /src/validacao/ValidadorSentencaUnica.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import Validador from './Validador'; 19 | 20 | class ValidadorSentencaUnica extends Validador { 21 | constructor() { 22 | super(['artigo', 'paragrafo', 'inciso', 'alinea', 'item'], 'Dispositivos devem conter uma única sentença.'); 23 | } 24 | 25 | validar(dispositivo) { 26 | let texto = dispositivo.textContent.trim(); 27 | let regexp = /[.;:]\s*(\S)/g; 28 | let m; 29 | let valido = true; 30 | 31 | for (let m = regexp.exec(texto); m && valido; m = regexp.exec(texto)) { 32 | valido = m[1].toLowerCase() === m[1]; 33 | } 34 | 35 | return valido; 36 | } 37 | } 38 | 39 | export default ValidadorSentencaUnica; -------------------------------------------------------------------------------- /src/validacao/ValidadorEnumeracaoElementos.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import Validador from './Validador'; 19 | import { encontrarDispositivoAnteriorDoTipo, encontrarDispositivoPosteriorDoTipo, getTiposSuperiores } from '../util'; 20 | 21 | class ValidadorEnumeracaoElementos extends Validador { 22 | constructor() { 23 | super(['inciso', 'alinea', 'item'], 'Enumerações devem possuir mais de um elemento.'); 24 | } 25 | 26 | validar(dispositivo) { 27 | var tipo = dispositivo.getAttribute('data-tipo'); 28 | var superiores = getTiposSuperiores(tipo); 29 | 30 | return !!encontrarDispositivoAnteriorDoTipo(dispositivo, [tipo], superiores) || !!encontrarDispositivoPosteriorDoTipo(dispositivo, [tipo], superiores); 31 | } 32 | } 33 | 34 | export default ValidadorEnumeracaoElementos; -------------------------------------------------------------------------------- /src/transformacaoAutomatica/transformacoes/DoisPontos.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import { TransformacaoDoProximo } from './Transformacao'; 19 | 20 | /** 21 | * Ao terminar um dispositivo com dois pontos (:), assume-se 22 | * que será feita uma enumeração e, portanto, transforma 23 | * o próximo dispositivo em inciso, alínea ou item, conforme 24 | * contexto. 25 | */ 26 | class DoisPontos extends TransformacaoDoProximo { 27 | constructor() { 28 | super(':\n'); 29 | } 30 | 31 | get tipoTransformacao() { 32 | return 'DoisPontos'; 33 | } 34 | 35 | proximoTipo(editor, ctrl, contexto) { 36 | return { 37 | artigo: 'inciso', 38 | inciso: 'alinea', 39 | alinea: 'item', 40 | paragrafo: 'inciso' 41 | }[contexto.cursor.tipo]; 42 | } 43 | } 44 | 45 | export default DoisPontos; -------------------------------------------------------------------------------- /src/eventos/TransformacaoAutomaticaEvent.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import EventoInterno from './EventoInterno'; 19 | 20 | /** 21 | * Evento de notificação de transformação automática ocorrida 22 | * em dispositivo. 23 | */ 24 | class TransformacaoAutomaticaEvent extends EventoInterno { 25 | /** 26 | * Constrói o evento. 27 | * 28 | * @param {EditorArticulacaoController} editorArticulacaoCtrl 29 | */ 30 | constructor(editorArticulacaoCtrl, tipoAnterior, novoTipo, transformacao) { 31 | super('transformacao', { 32 | detail: { 33 | automatica: true, 34 | tipoAnterior: tipoAnterior, 35 | novoTipo: novoTipo, 36 | transformacao: transformacao, 37 | editorArticulacaoCtrl 38 | } 39 | }); 40 | } 41 | } 42 | 43 | export default TransformacaoAutomaticaEvent; -------------------------------------------------------------------------------- /src/transformacaoAutomatica/transformacoes/AoIniciarAspas.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import { Transformacao } from './Transformacao'; 19 | import TransformacaoAutomaticaEvent from '../../eventos/TransformacaoAutomaticaEvent'; 20 | 21 | /** 22 | * Quando usuário cria um novo artigo e o inicia com aspas, 23 | * este é transformado em continuação do caput do artigo. 24 | */ 25 | class AoIniciarAspas extends Transformacao { 26 | constructor() { 27 | super('\n"'); 28 | } 29 | 30 | get tipoTransformacao() { 31 | return 'AoIniciarAspas'; 32 | } 33 | 34 | transformar(editor, ctrl, contexto) { 35 | if (contexto.cursor.tipoAnterior === 'artigo') { 36 | ctrl.alterarTipoDispositivoSelecionado('continuacao'); 37 | ctrl.dispatchEvent(new TransformacaoAutomaticaEvent(ctrl, 'artigo', 'continuacao', this.tipoTransformacao)); 38 | } 39 | 40 | return null; 41 | } 42 | } 43 | 44 | export default AoIniciarAspas; -------------------------------------------------------------------------------- /protractor-conf.js: -------------------------------------------------------------------------------- 1 | 2 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 3 | * 4 | * This file is part of Editor-Articulacao. 5 | * 6 | * Editor-Articulacao is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, version 3. 9 | * 10 | * Editor-Articulacao is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU Lesser General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with Editor-Articulacao. If not, see . 17 | */ 18 | 19 | exports.config = { 20 | framework: 'jasmine', 21 | specs: ['test/protractor/*.spec.js'], 22 | directConnect: true, 23 | multiCapabilities: [ 24 | { 25 | browserName: 'chrome', 26 | chromeOptions: { 27 | args: ['--no-sandbox', '--headless'] 28 | } 29 | }/*, { https://github.com/angular/protractor/issues/4177 30 | 'browserName': 'firefox' 31 | }*/ 32 | ], 33 | localSeleniumStandaloneOpts: { 34 | seleniumArgs: ['-browserTimeout=60'] 35 | }, 36 | baseUrl: 'http://localhost:8075/', 37 | onPrepare: function () { 38 | browser.ignoreSynchronization = true; 39 | browser.manage().timeouts().pageLoadTimeout(40000); 40 | browser.manage().timeouts().implicitlyWait(25000); 41 | 42 | browser.driver.manage().window().setSize(1024, 768); 43 | browser.driver.get('http://localhost:8075/protractor/teste.html'); 44 | }, 45 | maxSessions: 1 46 | }; 47 | -------------------------------------------------------------------------------- /test/protractor/validacao.spec.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | const { $describe } = require('./utilitariosTeste'); 19 | 20 | $describe('Formatação do editor de articulação', it => { 21 | it('Deve validar todos os dispositivos ao colar', () => { 22 | browser.executeScript(function () { 23 | const selecao = this.getSelection(); 24 | selecao.removeAllRanges(); 25 | 26 | const range = document.createRange(); 27 | range.selectNodeContents(document.querySelector('[contenteditable]').firstElementChild); 28 | selecao.addRange(range); 29 | 30 | let dataTrans = new DataTransfer(); 31 | 32 | dataTrans.items.add('Primeira linha\nSegunda linha\nTerceira linha\nQuarta linha\nQuinta linha', 'text/plain'); 33 | 34 | document.querySelector('[contenteditable]').dispatchEvent(new ClipboardEvent('paste', { 35 | clipboardData: dataTrans 36 | })); 37 | }); 38 | 39 | expect(element.all(by.css('p[data-invalido]')).count()).toBe(4); 40 | }) 41 | }); -------------------------------------------------------------------------------- /src/validacao/Validador.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | /** 19 | * Classe abstrata para validar dispositivos. 20 | */ 21 | class Validador { 22 | /** 23 | * Constrói o validador. 24 | * 25 | * @param {String[]} tipos Tipos de dispositivos que serão validados. 26 | * @param {String} descricao Descrição da validação. 27 | */ 28 | constructor(tipos, descricao) { 29 | this.tipos = tipos instanceof Array ? new Set(tipos) : new Set([tipos]); 30 | this.descricao = descricao; 31 | } 32 | 33 | /** 34 | * Verifiac se o validador se aplica ao dispositivo. 35 | * 36 | * @param {Element} dispositivo Dispositivo a ser validado. 37 | */ 38 | aplicaSeA(dispositivo) { 39 | return this.tipos.has(dispositivo.getAttribute('data-tipo')); 40 | } 41 | 42 | /** 43 | * Realiza a validação do dispositivo. 44 | * 45 | * @param {Element} dispositivo Dispositivo a ser validado. 46 | * @returns {Boolean} Verdadeiro se o dispositivo estiver válido. 47 | */ 48 | validar(dispositivo) { 49 | throw 'Não implementado.'; 50 | } 51 | } 52 | 53 | export default Validador; -------------------------------------------------------------------------------- /src/eventos/EventoInterno.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | /** 19 | * Representa um evento interno, a ser disparado pelo EditorArticulacaoController.dispatchEvent. 20 | * Como o IE 11 possui problemas para herdar classes de CustomEvent, foi criada esta classe 21 | * intermediária para gerar o CustomEvent. 22 | */ 23 | class EventoInterno { 24 | constructor(nome, dados) { 25 | this.nome = nome; 26 | this.dados = dados; 27 | } 28 | 29 | getCustomEvent() { 30 | return new CustomEvent(this.nome, this.dados); 31 | } 32 | } 33 | 34 | // Polyfill de https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill 35 | (function () { 36 | if (typeof window.CustomEvent === "function") return false; 37 | 38 | function CustomEvent(event, params) { 39 | params = params || { bubbles: false, cancelable: false, detail: undefined }; 40 | var evt = document.createEvent('CustomEvent'); 41 | evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); 42 | return evt; 43 | } 44 | 45 | CustomEvent.prototype = window.Event.prototype; 46 | 47 | window.CustomEvent = CustomEvent; 48 | })(); 49 | 50 | export default EventoInterno; -------------------------------------------------------------------------------- /src/transformacaoAutomatica/transformacoes/PontoFinal.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import { TransformacaoDoProximo } from './Transformacao'; 19 | 20 | /** 21 | * Ao finalizar o dispositivo com ponto final (.) e o usuário 22 | * estiver no contexto de uma enumeração, então encerra-se ela, 23 | * transformando o próximo dispositivo em um nível anterior 24 | * (ex.: alínea para inciso). 25 | */ 26 | class PontoFinal extends TransformacaoDoProximo { 27 | constructor() { 28 | super('.\n'); 29 | } 30 | 31 | get tipoTransformacao() { 32 | return 'PontoFinal'; 33 | } 34 | 35 | proximoTipo(editor, ctrl, contexto) { 36 | return { 37 | get inciso() { 38 | let dispositivo = contexto.cursor.dispositivo; 39 | let tipo; 40 | 41 | do { 42 | dispositivo = dispositivo.previousElementSibling; 43 | tipo = dispositivo.getAttribute('data-tipo'); 44 | } while (tipo !== 'artigo' && tipo !== 'paragrafo'); 45 | 46 | return tipo; 47 | }, 48 | alinea: 'inciso', 49 | item: 'alinea' 50 | }[contexto.cursor.tipo]; 51 | } 52 | } 53 | 54 | export default PontoFinal; -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | let webpackConfig = require("./webpack.config.js")('karma', true); 19 | 20 | module.exports = function (config) { 21 | config.set({ 22 | files: ['empacotamento/karma.js', 'test/karma/**/*.js'], 23 | frameworks: ['jasmine'], 24 | preprocessors: { 25 | 'empacotamento/karma.js': ['webpack'] 26 | }, 27 | webpack: webpackConfig, 28 | port: 11111, 29 | colors: true, 30 | logLevel: config.LOG_WARN, 31 | autoWatch: true, 32 | browsers: ['ChromeNoSandbox', 'FirefoxHeadless'], 33 | customLaunchers: { // https://github.com/karma-runner/karma-chrome-launcher/issues/73 34 | ChromeNoSandbox: { 35 | base: 'ChromeHeadless', 36 | flags: ['--no-sandbox', '--disable-extensions'] 37 | }, 38 | FirefoxHeadless: { 39 | base: 'Firefox', 40 | flags: [ '-headless' ], 41 | } 42 | }, 43 | singleRun: false, 44 | browserNoActivityTimeout: 300000, 45 | browserDisconnectTimeout: 300000, 46 | browserConsoleLogOptions: { 47 | level: "error", 48 | format: "%b %T: %m", 49 | terminal: true 50 | }, 51 | browserDisconnectTolerance: 5, 52 | captureTimeout: 120000, 53 | concurrency: 1 54 | }); 55 | }; 56 | -------------------------------------------------------------------------------- /test/protractor/utilitariosTeste.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | module.exports = { $describe, escrever } 19 | 20 | function $describe(texto, fn) { 21 | describe(texto, function () { 22 | var editor = element(by.id('articulacao')); 23 | var testes = 0, totalTestes = 0; 24 | 25 | function $it(descricao, funcao) { 26 | it(descricao, function () { 27 | testes++; 28 | 29 | browser.executeScript(function (texto) { 30 | titulo.textContent = texto; 31 | }, `Teste ${testes}/${totalTestes}: ${descricao}`); 32 | 33 | funcao.apply(this, arguments); 34 | }); 35 | 36 | totalTestes++; 37 | } 38 | 39 | beforeEach(function () { 40 | browser.actions() 41 | .mouseMove(editor) 42 | .click() 43 | .perform(); 44 | }); 45 | 46 | afterEach(function () { 47 | browser.executeScript(function () { 48 | ctrl.lexml = ''; 49 | }); 50 | }); 51 | 52 | fn($it); 53 | }); 54 | } 55 | function escrever(buffer, rapido) { 56 | for (let i = 0; i < buffer.length; i++) { 57 | browser.actions().sendKeys(buffer[i]).perform(); 58 | browser.sleep(25); 59 | } 60 | 61 | if (!rapido) { 62 | browser.sleep(1000); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /empacotamento/angular1.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import EditorArticulacaoController from '../src/EditorArticulacaoController'; 19 | import interpretadorArticulacao from '../src/interpretadorArticulacao'; 20 | 21 | window.angular.module('silegismg-editor-articulacao', []) 22 | .directive('silegismgEditorArticulacaoConteudo', editorArticulacaoConteudoDirective) 23 | .service('silegismgInterpretadorArticulacaoService', interpretadorArticulacaoService); 24 | 25 | function editorArticulacaoConteudoDirective() { 26 | return { 27 | restrict: 'EAC', 28 | require: 'ngModel', 29 | scope: { 30 | opcoes: ' { 34 | scope.ctrl.lexml = ngModel.$viewValue; 35 | }; 36 | 37 | element[0].addEventListener('change', () => { 38 | try { 39 | ngModel.$setViewValue(scope.ctrl.lexml.outerHTML, 'change'); 40 | ngModel.$setValidity('lexml', true); 41 | } catch (e) { 42 | ngModel.$setValidity('lexml', false); 43 | throw e; 44 | } 45 | }); 46 | 47 | scope.$on('$destroy', () => scope.ctrl.desregistrar()); 48 | }, 49 | controller: ['$element', '$scope', ($element, $scope) => new EditorArticulacaoController($element[0], $scope.opcoes)], 50 | controllerAs: 'ctrl' 51 | }; 52 | } 53 | 54 | function interpretadorArticulacaoService() { 55 | return interpretadorArticulacao; 56 | } -------------------------------------------------------------------------------- /test/karma/validacao.spec.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | describe('Validação', function() { 19 | 'use strict'; 20 | 21 | var ctrl = new window.ValidacaoController({}); 22 | 23 | function criarDispositivo(tipo, texto) { 24 | var dispositivo = document.createElement('p'); 25 | 26 | dispositivo.setAttribute('data-tipo', tipo); 27 | dispositivo.textContent = texto; 28 | 29 | return dispositivo; 30 | } 31 | 32 | function testar(tipo, texto/*, ...erros*/) { 33 | var dispositivo = criarDispositivo(tipo, texto); 34 | var validacao = ctrl.validar(dispositivo); 35 | var erros = []; 36 | 37 | Array.prototype.push.apply(erros, arguments); 38 | 39 | erros.splice(0, 2); 40 | 41 | expect(validacao.length).toBe(erros.length); 42 | 43 | for (let i = 0; i < validacao.length; i++) { 44 | expect(validacao[i].tipo).toBe(erros[i]); 45 | } 46 | } 47 | 48 | describe('Validador de sentença única', function() { 49 | it ('Único período deve ser válido.', function() { 50 | testar('artigo', 'Esta é uma sentença única.') 51 | }); 52 | 53 | it ('Abreviação deve ser válida.', function() { 54 | testar('artigo', 'Esta é um teste de citação do art. 5º da constituição.') 55 | }); 56 | 57 | it ('Dois períodos devem ser inválidos.', function() { 58 | testar('artigo', 'Este é um teste. Este é outro.', 'sentencaUnica'); 59 | }); 60 | 61 | it ('Dois períodos com abreviação no primeiro deve tornar artigo inválido.', function() { 62 | testar('artigo', 'Este é um teste de citação do art. 5º da constituição. Este é outro.', 'sentencaUnica'); 63 | }); 64 | }); 65 | }); -------------------------------------------------------------------------------- /src/validacao/ValidadorCitacao.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import Validador from './Validador'; 19 | import { encontrarDispositivoAnteriorDoTipo, encontrarDispositivoPosteriorDoTipo } from '../util'; 20 | 21 | class ValidadorCitacao extends Validador { 22 | constructor() { 23 | super('continuacao', 'Citações devem estar entre aspas e terminar com ponto final (.).'); 24 | } 25 | 26 | validar(dispositivo) { 27 | let texto = dispositivo.textContent.trim(); 28 | let inicio = seNulo(encontrarDispositivoAnteriorDoTipo(dispositivo, ['artigo']), posterior => posterior.nextElementSibling, dispositivo); 29 | 30 | if (dispositivo === inicio) { 31 | return /^["“]/.test(texto); 32 | } 33 | 34 | let termino = seNulo(encontrarDispositivoPosteriorDoTipo(dispositivo, ['continuacao', 'artigo', 'paragrafo', 'inciso']), 35 | posterior => posterior.getAttribute('data-tipo') === 'continuacao' ? posterior : posterior.previousElementSibling, 36 | dispositivo); 37 | 38 | if (dispositivo === termino) { 39 | return /.+[”"]\.$/.test(texto); 40 | } 41 | 42 | // Se estamos no meio da citação, não devem haver aspas. 43 | return !/[“”"]/.test(texto); 44 | } 45 | } 46 | 47 | /** 48 | * Se o objeto não estiver nulo, então executa uma função consumidora deste objeto. 49 | * Caso o contrário, retorna um outro valor. 50 | * 51 | * @param {*} obj Objeto a ser verificado. 52 | * @param {*} fnSeNaoNulo Função cujo resultado será retornado caso o objeto seja diferente de nulo. 53 | * @param {*} seNulo Valor retornado caso seja nulo. 54 | */ 55 | function seNulo(obj, fnSeNaoNulo, seNulo) { 56 | return obj ? fnSeNaoNulo(obj) : seNulo; 57 | } 58 | 59 | export default ValidadorCitacao; -------------------------------------------------------------------------------- /empacotamento/plain-js.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | /* Cria uma função global, chamada silegismgEditorArticulacao, 19 | * que permite transformar um DIV em um editor de articulação. 20 | * A função cria e retorna uma nova instância de EditorArticulacaoController. 21 | */ 22 | 23 | import ComponenteEdicao from '../src/ComponenteEdicao'; 24 | import EditorArticulacaoController from '../src/EditorArticulacaoController'; 25 | import interpretadorArticulacao from '../src/interpretadorArticulacao'; 26 | 27 | /** 28 | * Prepara um elemento do DOM como um editor de articulação. 29 | * 30 | * @param {Element} elemento 31 | */ 32 | function criarControllerEditorArticulacao(elemento, opcoes) { 33 | elemento.ctrlArticulacao = new EditorArticulacaoController(elemento, opcoes); 34 | 35 | Object.defineProperty(elemento, 'lexml', { 36 | get: function () { 37 | return this.ctrlArticulacao.lexml; 38 | }, 39 | set: function (valor) { 40 | this.ctrlArticulacao.lexml = valor; 41 | } 42 | }); 43 | 44 | return elemento.ctrlArticulacao; 45 | } 46 | 47 | function prepararEditorArticulacaoCompleto(elemento, opcoes) { 48 | elemento.componenteEdicao = new ComponenteEdicao(elemento, opcoes); 49 | 50 | Object.defineProperty(elemento, 'lexml', { 51 | get: function () { 52 | return this.componenteEdicao.ctrl.lexml; 53 | }, 54 | set: function (valor) { 55 | this.componenteEdicao.ctrl.lexml = valor; 56 | } 57 | }); 58 | 59 | return elemento.componenteEdicao; 60 | } 61 | 62 | window.silegismgEditorArticulacao = prepararEditorArticulacaoCompleto; 63 | window.silegismgEditorArticulacaoController = criarControllerEditorArticulacao; 64 | window.silegismgInterpretadorArticulacao = interpretadorArticulacao; -------------------------------------------------------------------------------- /test/protractor/teste.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

Teste do editor de articulação

10 |

Editor

11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 |
26 |
27 |

28 |     
29 |     
30 | 
31 |     
60 | 
61 | 
62 | 


--------------------------------------------------------------------------------
/src/transformacaoAutomatica/transformacoes/RecuarComEnterEmDispositivoVazio.js:
--------------------------------------------------------------------------------
 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais
 2 |  * 
 3 |  * This file is part of Editor-Articulacao.
 4 |  *
 5 |  * Editor-Articulacao is free software: you can redistribute it and/or modify
 6 |  * it under the terms of the GNU Lesser General Public License as published by
 7 |  * the Free Software Foundation, version 3.
 8 |  *
 9 |  * Editor-Articulacao is distributed in the hope that it will be useful,
10 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 |  * GNU Lesser General Public License for more details.
13 |  *
14 |  * You should have received a copy of the GNU Lesser General Public License
15 |  * along with Editor-Articulacao.  If not, see .
16 |  */
17 | 
18 | import Transformacao from './Transformacao';
19 | 
20 | /**
21 |  * Quando usuário der um enter em uma linha vazia, então
22 |  * transforma-se o dispositivo para um nível anterior
23 |  * (ex.: de alínea para inciso).
24 |  */
25 | class RecuarComEnterEmDispositivoVazio extends Transformacao {
26 |     constructor() {
27 |         super('\n');
28 |     }
29 | 
30 |     get tipoTransformacao() {
31 |         return 'RecuarComEnterEmDispositivoVazio';
32 |     }
33 | 
34 |     /**
35 |      * Efetua a transformação.
36 |      * 
37 |      * @param {Element} elementoEditor Elemento em que está o editor de articulação.
38 |      * @param {EditorArticulacaoController} ctrl Controlador do editor.
39 |      * @param {ContextoArticulacao} contexto Contexto atual.
40 |      * @param {String} sequencia Sequência que disparou a transformação.
41 |      * @param {KeyboardEvent} event Evento do teclado.
42 |      */
43 |     transformar(elementoEditor, ctrl, contexto, sequencia, event) {
44 |         if (contexto.cursor.dispositivo.textContent.trim().length === 0) {
45 |             let novoTipo = {
46 |                 item: 'alinea',
47 |                 alinea: 'inciso',
48 |                 get inciso() {
49 |                     for (let dispositivo = contexto.cursor.dispositivoAnterior; dispositivo; dispositivo = dispositivo.previousElementSibling) {
50 |                         let tipo = dispositivo.getAttribute('data-tipo');
51 | 
52 |                         if (tipo === 'artigo' || tipo === 'paragrafo') {
53 |                             return tipo;
54 |                         }
55 |                     }
56 | 
57 |                     return 'artigo';
58 |                 }
59 |             }[contexto.cursor.tipo] || 'artigo';
60 | 
61 |             ctrl.alterarTipoDispositivoSelecionado(novoTipo);
62 |             event.preventDefault();
63 |         }
64 |     }
65 | }
66 | 
67 | export default RecuarComEnterEmDispositivoVazio;


--------------------------------------------------------------------------------
/src/hacks/polyfill.js:
--------------------------------------------------------------------------------
 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais
 2 |  * 
 3 |  * This file is part of Editor-Articulacao.
 4 |  *
 5 |  * Editor-Articulacao is free software: you can redistribute it and/or modify
 6 |  * it under the terms of the GNU Lesser General Public License as published by
 7 |  * the Free Software Foundation, version 3.
 8 |  *
 9 |  * Editor-Articulacao is distributed in the hope that it will be useful,
10 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 |  * GNU Lesser General Public License for more details.
13 |  *
14 |  * You should have received a copy of the GNU Lesser General Public License
15 |  * along with Editor-Articulacao.  If not, see .
16 |  */
17 | 
18 |  function polyfill() {
19 |     // IE 11 não tem Object.assing
20 | 
21 |     // Object polyfill from https://developer.mozilla.org/en-US/docs/Glossary/Polyfill
22 |     if (!Object.assign) {
23 |         Object.defineProperty(Object, 'assign', {
24 |             enumerable: false,
25 |             configurable: true,
26 |             writable: true,
27 |             value: function (target) {
28 |                 'use strict';
29 |                 if (target === undefined || target === null) {
30 |                     throw new TypeError('Cannot convert first argument to object');
31 |                 }
32 | 
33 |                 var to = Object(target);
34 |                 for (var i = 1; i < arguments.length; i++) {
35 |                     var nextSource = arguments[i];
36 |                     if (nextSource === undefined || nextSource === null) {
37 |                         continue;
38 |                     }
39 |                     nextSource = Object(nextSource);
40 | 
41 |                     var keysArray = Object.keys(Object(nextSource));
42 |                     for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
43 |                         var nextKey = keysArray[nextIndex];
44 |                         var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
45 |                         if (desc !== undefined && desc.enumerable) {
46 |                             to[nextKey] = nextSource[nextKey];
47 |                         }
48 |                     }
49 |                 }
50 |                 return to;
51 |             }
52 |         });
53 |     }
54 | 
55 |     // IE 11 não tem lastElementChild no DocumentFragment
56 |     if (!('lastElementChild' in DocumentFragment.prototype)) {
57 |         Object.defineProperty(DocumentFragment.prototype, 'lastElementChild', {
58 |             get: function () {
59 |                 var ultimo = this.lastChild;
60 | 
61 |                 while (ultimo && ultimo.nodeType !== Node.ELEMENT_NODE) {
62 |                     ultimo = ultimo.previousSibling;
63 |                 }
64 | 
65 |                 return ultimo;
66 |             }
67 |         });
68 |     }
69 |  }
70 | 
71 |  export default polyfill;


--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais
 2 |  * 
 3 |  * This file is part of Editor-Articulacao.
 4 |  *
 5 |  * Editor-Articulacao is free software: you can redistribute it and/or modify
 6 |  * it under the terms of the GNU Lesser General Public License as published by
 7 |  * the Free Software Foundation, version 3.
 8 |  *
 9 |  * Editor-Articulacao is distributed in the hope that it will be useful,
10 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 |  * GNU Lesser General Public License for more details.
13 |  *
14 |  * You should have received a copy of the GNU Lesser General Public License
15 |  * along with Editor-Articulacao.  If not, see .
16 |  */
17 | 
18 | module.exports = function (grunt) {
19 | 	require("matchdep").filterAll("grunt-*").forEach(grunt.loadNpmTasks);
20 | 	var webpackConfig = require("./webpack.config.js");
21 | 	
22 | 	grunt.initConfig({
23 | 		karma: {
24 | 			unit: {
25 | 				configFile: 'karma.conf.js',
26 | 				background: false,
27 | 				singleRun: true
28 | 			},
29 | 			debug: {
30 | 				configFile: 'karma.conf.js',
31 | 				background: false,
32 | 				singleRun: false
33 | 			}
34 | 		},
35 | 		webpack: {
36 | 			buildPlain: webpackConfig('plain-js'),
37 | 			buildAngular1: webpackConfig('angular1'),
38 | 			buildPlainPolyfill: webpackConfig('plain-js', false, true),
39 | 			buildAngular1Polyfill: webpackConfig('angular1', false, true),
40 | 			"build-dev": webpackConfig('plain-js', true)
41 | 		},
42 | 		"webpack-dev-server": {
43 | 			options: webpackConfig('plain-js', true),
44 | 			start: {
45 | 			}
46 | 		},
47 | 		watch: {
48 | 			app: {
49 | 				files: ["src/**/*"],
50 | 				tasks: ["webpack:build-dev", "jshint", "karma:unit"],
51 | 				options: {
52 | 					spawn: false,
53 | 				}
54 | 			}
55 | 		},
56 | 		jshint: {
57 | 			all: ['*.js', 'src/**/*.js', 'empacotamento/**/*.js'],
58 | 			options: {
59 | 				browser: true,
60 | 				esversion: 6
61 | 			}
62 | 		}
63 | 	});
64 | 
65 | 	// The development server (the recommended option for development)
66 | 	grunt.registerTask("default", ["webpack-dev-server:start"]);
67 | 
68 | 	// Build and watch cycle (another option for development)
69 | 	// Advantage: No server required, can run app from filesystem
70 | 	// Disadvantage: Requests are not blocked until bundle is available,
71 | 	//               can serve an old app on too fast refresh
72 | 	grunt.registerTask("dev", ["jshint", "webpack:build-dev", "watch:app"]);
73 | 
74 | 	// Production build
75 | 	grunt.registerTask("build", ["webpack:buildPlain", "webpack:buildAngular1", "webpack:buildPlainPolyfill", "webpack:buildAngular1Polyfill"]);
76 | 	grunt.registerTask("build-plain", ["webpack:buildPlain"]);
77 | 	grunt.registerTask("build-plain-polyfill", ["webpack:buildPlainPolyfill"]);
78 | 	grunt.registerTask("build-angular1", ["webpack:buildAngular1"]);
79 | 
80 | 	grunt.registerTask('test', ['jshint', 'karma:unit']);
81 | 
82 | 	grunt.registerTask('debug', ['karma:debug']);
83 | };
84 | 


--------------------------------------------------------------------------------
/src/hacks/interceptador.js:
--------------------------------------------------------------------------------
 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais
 2 |  * 
 3 |  * This file is part of Editor-Articulacao.
 4 |  *
 5 |  * Editor-Articulacao is free software: you can redistribute it and/or modify
 6 |  * it under the terms of the GNU Lesser General Public License as published by
 7 |  * the Free Software Foundation, version 3.
 8 |  *
 9 |  * Editor-Articulacao is distributed in the hope that it will be useful,
10 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 |  * GNU Lesser General Public License for more details.
13 |  *
14 |  * You should have received a copy of the GNU Lesser General Public License
15 |  * along with Editor-Articulacao.  If not, see .
16 |  */
17 | 
18 | /**
19 |  * Intercepta um método após sua execução.
20 |  * 
21 |  * @param {Object} objeto Objeto ou classe a ser interceptada. Se o tipo for uma classe, a interceptação ocorrerá sobre o prototype.
22 |  * @param {String} metodo Método a ser interceptado.
23 |  * @param {function} interceptador Função interceptadora, que receberá o objeto, o valor retornado e os argumentos.
24 |  */
25 | function interceptar(objeto, metodo, interceptador) {
26 |     if (!objeto) throw 'Objeto não fornecido.';
27 |     if (!metodo) throw 'Método não fornecido.';
28 |     if (!interceptador) throw 'Interceptador não fornecido.';
29 | 
30 |     if (typeof objeto === 'function') {
31 |         interceptar(objeto.prototype, metodo, interceptador);
32 |     } else {
33 |         let metodoOriginal = objeto[metodo];
34 | 
35 |         if (!metodoOriginal) {
36 |             throw 'Método não encontrado: ' + metodo;
37 |         }
38 | 
39 |         objeto[metodo] = function() {
40 |             return interceptador(this, metodoOriginal, arguments);
41 |         };
42 |     }
43 | }
44 | 
45 | /**
46 |  * Intercepta um método após sua execução.
47 |  * 
48 |  * @param {Object} objeto Objeto ou classe a ser interceptada. Se o tipo for uma classe, a interceptação ocorrerá sobre o prototype.
49 |  * @param {String} metodo Método a ser interceptado.
50 |  * @param {function} interceptador Função interceptadora, que receberá o objeto, o valor retornado e os argumentos.
51 |  */
52 | function interceptarApos(objeto, metodo, interceptador) {
53 |     if (!objeto) throw 'Objeto não fornecido.';
54 |     if (!metodo) throw 'Método não fornecido.';
55 |     if (!interceptador) throw 'Interceptador não fornecido.';
56 | 
57 |     if (typeof objeto === 'function') {
58 |         interceptarApos(objeto.prototype, metodo, interceptador);
59 |     } else {
60 |         let metodoOriginal = objeto[metodo];
61 | 
62 |         if (!metodoOriginal) {
63 |             throw 'Método não encontrado: ' + metodo;
64 |         }
65 | 
66 |         objeto[metodo] = function() {
67 |             var resultado = metodoOriginal.apply(this, arguments);
68 | 
69 |             return interceptador(this, resultado, arguments);
70 |         };
71 |     }
72 | }
73 | 
74 | export { interceptar, interceptarApos };
75 | export default {};


--------------------------------------------------------------------------------
/src/hacks/ie.js:
--------------------------------------------------------------------------------
 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais
 2 |  * 
 3 |  * This file is part of Editor-Articulacao.
 4 |  *
 5 |  * Editor-Articulacao is free software: you can redistribute it and/or modify
 6 |  * it under the terms of the GNU Lesser General Public License as published by
 7 |  * the Free Software Foundation, version 3.
 8 |  *
 9 |  * Editor-Articulacao is distributed in the hope that it will be useful,
10 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 |  * GNU Lesser General Public License for more details.
13 |  *
14 |  * You should have received a copy of the GNU Lesser General Public License
15 |  * along with Editor-Articulacao.  If not, see .
16 |  */
17 | 
18 | import { interceptar, interceptarApos } from './interceptador';
19 | 
20 | function hackIE(controller) {
21 |     controller.limpar = function() {
22 |         // IE 11 precisa de conteúdo dentro do elemento para que o mesmo ganhe foco e seja editável
23 |         this._elemento.innerHTML = '

 

'; 24 | }; 25 | 26 | // Toggle do classList não funciona no IE 11 27 | // https://connect.microsoft.com/IE/Feedback/details/878564/ 28 | interceptar(DOMTokenList.prototype, 'toggle', (classList, metodo, argumentos) => { 29 | if (argumentos.length === 1) { 30 | metodo.apply(classList, argumentos); 31 | } else if (argumentos[1]) { 32 | classList.add(argumentos[0]); 33 | } else { 34 | classList.remove(argumentos[0]); 35 | } 36 | }); 37 | 38 | // Construtor do Set não recebe parâmetros no IE 11 39 | if (new Set([1, 2]).size === 0) { 40 | substituirSet(); 41 | } 42 | 43 | // Garante sempre que o dispositivo tenha algum conteúdo, para evitar que o IE tenha um parágrafo não editável. 44 | interceptarApos(controller, '_normalizarDispositivo', function(controller, resultado, argumentos) { 45 | let dispositivo = argumentos[0]; 46 | 47 | if (dispositivo && !dispositivo.firstElementChild && dispositivo.textContent === '') { 48 | dispositivo.innerHTML = ' '; 49 | } 50 | }); 51 | } 52 | 53 | function substituirSet() { 54 | function substituirMetodo(metodo) { 55 | if (typeof SetNativo.prototype[metodo] === 'function') { 56 | window.Set.prototype[metodo] = function() { return this.$set[metodo].apply(this.$set, arguments); }; 57 | } 58 | } 59 | 60 | let SetNativo = window.Set; 61 | 62 | window.Set = function(iteravel) { 63 | this.$set = new SetNativo(); 64 | 65 | if (iteravel) { 66 | for (let i in iteravel) { 67 | this.$set.add(iteravel[i]); 68 | } 69 | } 70 | }; 71 | 72 | let metodos = ['add', 'clear', 'delete', 'entries', 'forEach', 'has', 'keys', 'values', '@@iterator']; 73 | 74 | for (let i = 0; i < metodos.length; i++) { 75 | substituirMetodo(metodos[i]); 76 | } 77 | } 78 | 79 | export default hackIE; -------------------------------------------------------------------------------- /src/editor-html.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | const editorHtml = ` 18 | 57 |
58 | 59 |
60 |
61 | 62 | 63 | 64 | 65 |
66 |
67 | 68 | 69 | 70 | 71 | 72 | 73 |
74 |
75 |
76 |
77 |
78 | `; 79 | 80 | export default editorHtml; -------------------------------------------------------------------------------- /src/opcoesPadrao.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | /** 19 | * Definição padrão das opções do editor de articulação. 20 | */ 21 | export default { 22 | /** 23 | * Determina o escapamento de caracteres de código alto unicode durante a exportação 24 | * para lexmlString. 25 | */ 26 | escaparXML: false, 27 | 28 | /** 29 | * Determina o sufixo para os rótulos dos dispositivos. 30 | */ 31 | rotulo: { 32 | separadorArtigo: ' \u2013', 33 | separadorArtigoSemOrdinal: ' \u2013', 34 | separadorParagrafo: ' \u2013', 35 | separadorParagrafoSemOrdinal: ' \u2013', 36 | separadorParagrafoUnico: ' \u2013', 37 | separadorInciso: ' \u2013', 38 | separadorAlinea: ')', 39 | separadorItem: ')' 40 | }, 41 | 42 | /** 43 | * Determina se deve adotar o Shadow DOM, se suportado pelo navegador. 44 | */ 45 | shadowDOM: false, 46 | 47 | /** 48 | * Determina se deve permitir a edição, ou se o componente será somente para leitura. 49 | */ 50 | somenteLeitura: false, 51 | 52 | /** 53 | * Determina se o editor de articulação deve aplicar transformação automática. 54 | */ 55 | transformacaoAutomatica: true, 56 | 57 | /** 58 | * Determina se deve validar o conteúdo atribuído ao componente. 59 | */ 60 | validarAoAtribuir: true, 61 | 62 | /** 63 | * Determina as validações que devem ocorrer. 64 | * Nenhuma validação ocorre se o editor for somente para leitura. 65 | */ 66 | validacao: { 67 | /** 68 | * Determina se deve validar o uso de caixa alta. 69 | */ 70 | caixaAlta: true, 71 | 72 | /** 73 | * Determina se deve validar o uso de aspas em citações. 74 | */ 75 | citacao: true, 76 | 77 | /** 78 | * Determina se deve validar a presença de múltiplos elementos em uma enumeração. 79 | */ 80 | enumeracaoElementos: true, 81 | 82 | /** 83 | * Determina se deve validar o uso de letra maiúscula no caput do artigo e em parágrafos. 84 | */ 85 | inicialMaiuscula: true, 86 | 87 | /** 88 | * Determina se deve validar as pontuações. 89 | */ 90 | pontuacao: true, 91 | 92 | /** 93 | * Determina se deve validar pontuação de enumeração. 94 | */ 95 | pontuacaoEnumeracao: true, 96 | 97 | /** 98 | * Determina se deve exigir sentença única no dispositivo. 99 | */ 100 | sentencaUnica: true 101 | } 102 | }; -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | /** 19 | * Obtém o dispositivo anterior, de determinado tipo. 20 | * 21 | * @param {Element} dispositivo 22 | * @param {String[]} pontosParada Tipos de dispositivos desejados. 23 | * @param {String[]} pontosInterrupcao Tipos de dispositivos que, se encontrados, interromperá a obtenção, retornando nulo. 24 | * @returns {Element} Dispositivo, se encontrado, ou nulo. 25 | */ 26 | function encontrarDispositivoAnteriorDoTipo(dispositivo, pontosParada, pontosInterrupcao) { 27 | while (!dispositivo.hasAttribute('data-tipo')) { 28 | dispositivo = dispositivo.parentNode; 29 | } 30 | 31 | let setParada = new Set(pontosParada); 32 | let setInterrupcao = new Set(pontosInterrupcao); 33 | 34 | for (let prev = dispositivo.previousElementSibling; prev; prev = prev.previousElementSibling) { 35 | let tipoAnterior = prev.getAttribute('data-tipo'); 36 | 37 | if (setParada.has(tipoAnterior)) { 38 | return prev; 39 | } else if (setInterrupcao.has(tipoAnterior)) { 40 | return null; 41 | } 42 | } 43 | 44 | return null; 45 | } 46 | 47 | /** 48 | * Obtém o dispositivo posterior, de determinado tipo. 49 | * 50 | * @param {Element} dispositivo 51 | * @param {String[]} pontosParada Tipos de dispositivos desejados. 52 | * @param {String[]} pontosInterrupcao Tipos de dispositivos que, se encontrados, interromperá a obtenção, retornando nulo. 53 | * @returns {Element} Dispositivo, se encontrado, ou nulo. 54 | */ 55 | function encontrarDispositivoPosteriorDoTipo(elemento, pontosParada, pontosInterrupcao) { 56 | while (!elemento.hasAttribute('data-tipo')) { 57 | elemento = elemento.parentNode; 58 | } 59 | 60 | let setParada = new Set(pontosParada); 61 | let setInterrupcao = new Set(pontosInterrupcao); 62 | 63 | for (let proximo = elemento.nextElementSibling; proximo; proximo = proximo.nextElementSibling) { 64 | let tipoProximo = proximo.getAttribute('data-tipo'); 65 | 66 | if (setParada.has(tipoProximo)) { 67 | return proximo; 68 | } else if (setInterrupcao.has(tipoProximo)) { 69 | return null; 70 | } 71 | } 72 | 73 | return null; 74 | } 75 | 76 | /** 77 | * Obtém os tipos de dispositivos em níveis superiores. 78 | * 79 | * @param {String} tipo Tipo cujos tipos superiores serão obtidos. 80 | */ 81 | function getTiposSuperiores(tipo) { 82 | return { 83 | inciso: ['artigo', 'paragrafo'], 84 | alinea: ['artigo', 'paragrafo', 'inciso'], 85 | item: ['artigo', 'paragrafo', 'inciso', 'alinea'] 86 | }[tipo] || []; 87 | } 88 | 89 | export { encontrarDispositivoAnteriorDoTipo, encontrarDispositivoPosteriorDoTipo, getTiposSuperiores }; -------------------------------------------------------------------------------- /src/transformacaoAutomatica/transformacoes/Transformacao.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import TransformacaoAutomaticaEvent from '../../eventos/TransformacaoAutomaticaEvent'; 19 | 20 | /** 21 | * Definição abstrata de uma transformação a ser realizada na 22 | * seleção da articulação. 23 | */ 24 | class Transformacao { 25 | constructor(/*sequencias*/) { 26 | this.sequencias = []; 27 | 28 | for (let i = 0; i < arguments.length; i++) { 29 | let sequencia = arguments[i]; 30 | this.sequencias.push(sequencia); 31 | 32 | if (sequencia.indexOf('\n') >= 0) { 33 | this.sequencias.push(sequencia.replace(/\n/g, '\r')); 34 | } 35 | } 36 | } 37 | 38 | get tipoTransformacao() { 39 | // Deve-se sobrescrever este getter, pois na minificação de js, perde-se o nome do construtor! 40 | return this.constructor.name; 41 | } 42 | 43 | /** 44 | * Efetua a transformação. 45 | * 46 | * @param {Element} elementoEditor Elemento em que está o editor de articulação. 47 | * @param {EditorArticulacaoController} ctrl Controlador do editor. 48 | * @param {ContextoArticulacao} contexto Contexto atual. 49 | * @param {String} sequencia Sequência que disparou a transformação. 50 | * @param {KeyboardEvent} event Evento do teclado. 51 | */ 52 | transformar(elementoEditor, ctrl, contexto, sequencia, event) { 53 | throw 'Método não implementado'; 54 | } 55 | } 56 | 57 | /** 58 | * Definição abstrata de uma transformação a ser realizada no 59 | * próximo dispositivo selecionado, após o disparo do evento 60 | * 'keyup'. Útil para transformações a ser executadas após 61 | * a criação de um novo dispositivo. 62 | */ 63 | class TransformacaoDoProximo extends Transformacao { 64 | transformar(editor, ctrl, contexto) { 65 | let novoTipo = this.proximoTipo(editor, ctrl, contexto); 66 | 67 | if (novoTipo) { 68 | onKeyUp(editor, contexto.cursor.elemento, () => { 69 | let tipoAnterior = ctrl.contexto.cursor.tipo; 70 | ctrl.alterarTipoDispositivoSelecionado(novoTipo); 71 | ctrl.dispatchEvent(new TransformacaoAutomaticaEvent(ctrl, tipoAnterior, novoTipo, this.tipoTransformacao)); 72 | }); 73 | } 74 | } 75 | 76 | proximoTipo(editor, ctrl, contexto) { 77 | throw 'Método não implementado'; 78 | } 79 | } 80 | 81 | function onKeyUp(editor, elementoAtual, callback) { 82 | var handler = function(event) { 83 | callback(event); 84 | editor.removeEventListener('keyup', handler); 85 | }; 86 | 87 | editor.addEventListener('keyup', handler); 88 | } 89 | 90 | export { Transformacao, TransformacaoDoProximo }; 91 | export default Transformacao; -------------------------------------------------------------------------------- /exemplo/exemplo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

13 | 
14 |     
15 |     
16 | 17 | 18 |
19 |         Clique aqui para atualizar o lexml.
20 |     
21 | 22 | 23 | 24 | 25 | 26 | 42 | 43 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/transformacaoAutomatica/transformacaoAutomatica.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import DoisPontos from './transformacoes/DoisPontos'; 19 | import PontoFinal from './transformacoes/PontoFinal'; 20 | import AoIniciarAspas from './transformacoes/AoIniciarAspas'; 21 | import AoFecharAspas from './transformacoes/AoFecharAspas'; 22 | import RecuarComEnterEmDispositivoVazio from './transformacoes/RecuarComEnterEmDispositivoVazio'; 23 | 24 | /** 25 | * Adiciona a transformação automática ao editor de articulação. 26 | * 27 | * @param {EditorArticulacaoController} editorArticulacaoCtrl 28 | * @param {Element} elemento 29 | */ 30 | function adicionarTransformacaoAutomatica(editorArticulacaoCtrl, elemento) { 31 | var parser = {}, estado = []; 32 | 33 | adicionarParser(parser, new DoisPontos()); 34 | adicionarParser(parser, new PontoFinal()); 35 | adicionarParser(parser, new AoIniciarAspas()); 36 | adicionarParser(parser, new AoFecharAspas()); 37 | adicionarParser(parser, new RecuarComEnterEmDispositivoVazio()); 38 | 39 | editorArticulacaoCtrl.registrarEventListener('keypress', event => estado = processarEstado(event, parser, estado, editorArticulacaoCtrl, elemento)); 40 | editorArticulacaoCtrl.registrarEventListener('mouseup', event => estado = []); 41 | editorArticulacaoCtrl.registrarEventListener('touchend', event => estado = []); 42 | } 43 | 44 | /** 45 | * Adiciona um transformador ao parser. 46 | * 47 | * @param {Object} parser 48 | * @param {Transformacao} transformador 49 | */ 50 | function adicionarParser(parser, transformador) { 51 | transformador.sequencias.forEach(function(sequencia) { 52 | var i, pAtual = parser, c; 53 | var handler = transformador.transformar.bind(transformador); 54 | 55 | for (i = 0; i < sequencia.length; i++) { 56 | c = sequencia.charCodeAt(i); 57 | 58 | if (!pAtual[c]) { 59 | pAtual[c] = {}; 60 | } 61 | 62 | pAtual = pAtual[c]; 63 | } 64 | 65 | let objHandler = { 66 | sequencia: sequencia, 67 | handler: handler 68 | }; 69 | 70 | if (pAtual.$handler) { 71 | pAtual.$handler.push(objHandler); 72 | } else { 73 | pAtual.$handler = [objHandler]; 74 | } 75 | }); 76 | } 77 | 78 | /** 79 | * Realiza o parsing da edição. 80 | */ 81 | function processarEstado(event, _parser, _estadoParser, controller, elemento) { 82 | var novoEstado = [], 83 | c = event.charCode || event.keyCode; 84 | 85 | _estadoParser.forEach(function (estado) { 86 | if (estado[c]) { 87 | novoEstado.push(estado[c]); 88 | } 89 | }); 90 | 91 | if (_parser[c]) { 92 | novoEstado.push(_parser[c]); 93 | } 94 | 95 | novoEstado.forEach(function (estado) { 96 | if (estado.$handler) { 97 | estado.$handler.forEach(objHandler => objHandler.handler(elemento, controller, controller.contexto, objHandler.sequencia.replace(/\r/g, '\n'), event)); 98 | } 99 | }); 100 | 101 | return novoEstado; 102 | } 103 | 104 | export default adicionarTransformacaoAutomatica; -------------------------------------------------------------------------------- /src/validacao/ValidacaoController.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import ProblemaValidacao from './ProblemaValidacao'; 19 | import ValidadorCaixaAlta from './ValidadorCaixaAlta'; 20 | import ValidadorCitacao from './ValidadorCitacao'; 21 | import ValidadorEnumeracaoElementos from './ValidadorEnumeracaoElementos'; 22 | import ValidadorIniciarLetraMaiuscula from './ValidadorIniciarLetraMaiuscula'; 23 | import ValidadorPontuacaoArtigoParagrafo from './ValidadorPontuacaoArtigoParagrafo'; 24 | import ValidadorPontuacaoEnumeracao from './ValidadorPontuacaoEnumeracao'; 25 | import ValidadorSentencaUnica from './ValidadorSentencaUnica'; 26 | 27 | /** 28 | * Controler para realizar validação nos dispositivos. 29 | */ 30 | class ValidacaoController { 31 | constructor(opcoes) { 32 | this.validadores = []; 33 | 34 | if (opcoes !== false) { 35 | habilitar(opcoes, 'caixaAlta', this.validadores, ValidadorCaixaAlta); 36 | habilitar(opcoes, 'citacao', this.validadores, ValidadorCitacao); 37 | habilitar(opcoes, 'enumeracaoElementos', this.validadores, ValidadorEnumeracaoElementos); 38 | habilitar(opcoes, 'inicialMaiuscula', this.validadores, ValidadorIniciarLetraMaiuscula); 39 | habilitar(opcoes, 'pontuacao', this.validadores, ValidadorPontuacaoArtigoParagrafo); 40 | habilitar(opcoes, 'pontuacaoEnumeracao', this.validadores, ValidadorPontuacaoEnumeracao); 41 | habilitar(opcoes, 'sentencaUnica', this.validadores, ValidadorSentencaUnica); 42 | } 43 | } 44 | 45 | /** 46 | * Realiza a validação do dispositivo. 47 | * 48 | * @param {Element} dispositivo Dispositivo a ser validado. 49 | * @returns {ProblemaValidacao[]} Problemas encontrados. 50 | */ 51 | validar(dispositivo) { 52 | if (!dispositivo) { 53 | return; 54 | } 55 | 56 | let problemas = []; 57 | 58 | if (dispositivo.textContent.trim().length > 0) { 59 | this.validadores.forEach(item => { 60 | var validador = item.validador; 61 | var opcao = item.opcao; 62 | 63 | if (validador.aplicaSeA(dispositivo) && !validador.validar(dispositivo)) { 64 | problemas.push(new ProblemaValidacao(dispositivo, opcao, validador.descricao)); 65 | } 66 | }); 67 | 68 | if (problemas.length > 0) { 69 | dispositivo.setAttribute('data-invalido', problemas.reduce((prev, problema) => prev ? prev + ' ' + problema.descricao : problema.descricao, null)); 70 | } else { 71 | dispositivo.removeAttribute('data-invalido'); 72 | } 73 | } 74 | 75 | return problemas; 76 | } 77 | } 78 | 79 | /** 80 | * Habilita um validador. 81 | * 82 | * @param {Object} opcoes Conjunto de opções fornecidas para o controller de validação. 83 | * @param {String} nomeOpcao Nome da opção para habilitar um validador. 84 | * @param {Validador[]} validadores Vetor de validadores. 85 | * @param {Class} Validador Classe do validador. 86 | */ 87 | function habilitar(opcoes, nomeOpcao, validadores, Validador) { 88 | if (opcoes === true || opcoes[nomeOpcao] !== false) { 89 | validadores.push({ validador: new Validador(), opcao: nomeOpcao }); 90 | } 91 | } 92 | 93 | export default ValidacaoController; -------------------------------------------------------------------------------- /src/ComponenteEdicao.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import html from './editor-html'; 19 | import EditorArticulacaoController from './EditorArticulacaoController'; 20 | 21 | /** 22 | * Transforma um elemento do DOM em um editor de articulação com 23 | * barra de ferramentas. 24 | */ 25 | class ComponenteEdicao { 26 | constructor(elemento, opcoes) { 27 | var container, botoes, containerBotoes, ctrl; 28 | 29 | /* Se houver suporte ao shadow-dom, então vamos usá-lo 30 | * para garantir o isolamento da árvore interna do componente 31 | * e possíveis problemas com CSS. 32 | */ 33 | if (opcoes.shadowDOM && 'attachShadow' in elemento) { 34 | let shadow = elemento.attachShadow({ mode: (typeof opcoes.shadowDOM === 'string' ? opcoes.shadowDOM : 'open') }); 35 | 36 | shadow.innerHTML = html.toString(); 37 | 38 | container = shadow.querySelector('.silegismg-editor-conteudo'); 39 | containerBotoes = shadow.querySelector('.silegismg-editor-botoes'); 40 | botoes = containerBotoes.querySelectorAll('button[data-tipo-destino]'); 41 | 42 | elemento.addEventListener('focus', focusEvent => container.focus()); 43 | elemento.focus = function () { container.focus(); }; 44 | elemento.ctrlArticulacao = this.ctrl = ctrl = new EditorArticulacaoController(container, opcoes); 45 | } else { 46 | elemento.innerHTML = html.toString(); 47 | container = elemento.querySelector('.silegismg-editor-conteudo'); 48 | containerBotoes = elemento.querySelector('.silegismg-editor-botoes'); 49 | botoes = elemento.querySelectorAll('button[data-tipo-destino]'); 50 | elemento.ctrlArticulacao = this.ctrl = ctrl = new EditorArticulacaoController(container, opcoes); 51 | } 52 | 53 | this.ctrl.dispatchEvent = eventoInterno => elemento.dispatchEvent(eventoInterno.getCustomEvent()); 54 | 55 | function alterarDispositivo(event) { 56 | ctrl.alterarTipoDispositivoSelecionado(event.target.getAttribute('data-tipo-destino')); 57 | container.focus(); 58 | } 59 | 60 | // Trata cliques nos botões de formatação. 61 | for (let i = 0; i < botoes.length; i++) { 62 | botoes[i].addEventListener('click', alterarDispositivo.bind(this)); 63 | } 64 | 65 | /* Monitora atualização do contexto do usuário no editor de articulação, 66 | * controlando habilitação de botões de transformação de dispositivo. 67 | * 68 | * A atualização é feita por meio do evento "contexto" no DOM. 69 | */ 70 | elemento.addEventListener('contexto', function (evento) { 71 | /* Os botões de transformação de dispositivo só sõ habilitados se for possível 72 | * usar o dispositivo onde o cursor estiver. 73 | */ 74 | for (let i = 0; i < botoes.length; i++) { 75 | botoes[i].disabled = !evento.detail.permissoes[botoes[i].getAttribute('data-tipo-destino')]; 76 | botoes[i].classList.remove('atual'); 77 | } 78 | 79 | let tipoAtual = containerBotoes.querySelector('button[data-tipo-destino="' + evento.detail.cursor.tipo + '"]'); 80 | 81 | if (tipoAtual) { 82 | tipoAtual.classList.add('atual'); 83 | } 84 | }); 85 | 86 | elemento.addEventListener('scroll', function (evento) { 87 | containerBotoes.style.position = 'relative'; 88 | containerBotoes.style.top = elemento.scrollTop + 'px'; 89 | }); 90 | } 91 | } 92 | 93 | export default ComponenteEdicao; -------------------------------------------------------------------------------- /test/karma/clipboardController.spec.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | describe('ClipboardController', function() { 19 | 'use strict'; 20 | 21 | describe('transformar', function() { 22 | var transformar = window.clipboardControllerModule.transformar; 23 | 24 | it('Única linha deve ser um nó textual', function() { 25 | var fragmento = transformar('linha única', 'artigo'); 26 | 27 | expect(fragmento.childNodes.length).toBe(1); 28 | expect(fragmento.firstChild.nodeType).toBe(Node.TEXT_NODE); 29 | expect(fragmento.firstChild.textContent).toBe('linha única'); 30 | }); 31 | 32 | it('Deve inserir a primeira linha em um TextNode e a segunda linha em P', function() { 33 | var fragmento = transformar('linha 1\nlinha 2', 'artigo'); 34 | 35 | expect(fragmento.childNodes.length).toBe(2); 36 | expect(fragmento.firstChild.nodeType).toBe(Node.TEXT_NODE); 37 | expect(fragmento.firstChild.textContent).toBe('linha 1'); 38 | expect(fragmento.lastChild.outerHTML).toBe('

linha 2

'); 39 | }); 40 | 41 | it('Deve inserir a primeira linha em um TextNode e a segunda e terceira linha em P', function() { 42 | var fragmento = transformar('linha 1\nlinha 2\nlinha 3', 'artigo'); 43 | 44 | expect(fragmento.childNodes.length).toBe(3); 45 | expect(fragmento.firstChild.nodeType).toBe(Node.TEXT_NODE); 46 | expect(fragmento.firstChild.textContent).toBe('linha 1'); 47 | expect(fragmento.firstElementChild.outerHTML).toBe('

linha 2

'); 48 | expect(fragmento.lastChild.outerHTML).toBe('

linha 3

'); 49 | }); 50 | 51 | it('Deve interpretar artigos com incisos, alíneas e itens', function() { 52 | var fragmento = transformar('Art. 1º - Artigo 1\nI - Inciso\na) Alínea\n1) Item\n2) Item 2\nb) Alínea b\nII - Inciso 2\nParágrafo único - Parágrafo\nI - Inciso do parágrafo\nArt. 2º - Artigo 2', 'artigo', false); 53 | let container = document.createElement('div'); 54 | container.appendChild(fragmento); 55 | 56 | expect(container.innerHTML).toBe('

Artigo 1

Inciso

Alínea

Item

Item 2

Alínea b

Inciso 2

Parágrafo

Inciso do parágrafo

Artigo 2

'); 57 | }); 58 | 59 | it('Deve interpretar artigos com texto anterior', function() { 60 | var fragmento = transformar('final do anterior.\nArt. 2º - Artigo 2.', 'artigo', false); 61 | let container = document.createElement('div'); 62 | container.appendChild(fragmento); 63 | 64 | expect(container.innerHTML).toBe('final do anterior.

Artigo 2.

'); 65 | }); 66 | 67 | it('Deve inserir continuação sem interpretação', function() { 68 | var fragmento = transformar('continuação.\nArt. 2º - Artigo 2.', 'artigo', true); 69 | let container = document.createElement('div'); 70 | container.appendChild(fragmento); 71 | 72 | expect(container.innerHTML).toBe('continuação.

Art. 2º - Artigo 2.

'); 73 | }); 74 | 75 | it('Deve formatar automaticamente texto puro', function() { 76 | var fragmento = transformar('Enumeração:\nInciso:\nAlínea:\nItem;\nItem 2.\nAlínea 2.\nInciso 2.\nArtigo 2.\nArtigo 3.', 'artigo', false); 77 | let container = document.createElement('div'); 78 | container.appendChild(fragmento); 79 | 80 | expect(container.innerHTML).toBe('Enumeração:

Inciso:

Alínea:

Item;

Item 2.

Alínea 2.

Inciso 2.

Artigo 2.

Artigo 3.

'); 81 | }); 82 | 83 | it('Deve corrigir formatação de parágrafo único', function() { 84 | const fragmento = transformar(` 85 | Art. 1º - Teste. 86 | § 1º - Teste. 87 | Art. 2º - Teste. 88 | § 1º - Teste. 89 | § 2º - Teste. 90 | Art. 3º - Teste. 91 | `, false); 92 | 93 | const paragrafos = fragmento.querySelectorAll('p[data-tipo="paragrafo"]'); 94 | 95 | expect(paragrafos[0].classList.contains('unico')).toBe(true); 96 | expect(paragrafos[1].classList.contains('unico')).toBe(false); 97 | expect(paragrafos[2].classList.contains('unico')).toBe(false); 98 | }); 99 | }); 100 | }); -------------------------------------------------------------------------------- /test/karma/exportacaoParaLexML.spec.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | describe('Importação do LexML', function () { 19 | 'use strict'; 20 | 21 | var exportarParaLexML = window.exportarParaLexML; 22 | 23 | it('Deve exportar os artigos 1 a 3 da constituição federal', function () { 24 | var div = document.createElement('div'); 25 | div.innerHTML = '

Dos Princípios Fundamentais

A República Federativa do Brasil, formada pela união indissolúvel dos Estados e Municípios e do Distrito Federal, constitui-se em Estado democrático de direito e tem como fundamentos:

a soberania;

a cidadania;

a dignidade da pessoa humana;

os valores sociais do trabalho e da livre iniciativa;

o pluralismo político.

Todo o poder emana do povo, que o exerce por meio de representantes eleitos ou diretamente, nos termos desta Constituição.

São Poderes da União, independentes e harmônicos entre si, o Legislativo, o Executivo e o Judiciário.

Constituem objetivos fundamentais da República Federativa do Brasil:

construir uma sociedade livre, justa e solidária;

garantir o desenvolvimento nacional;

erradicar a pobreza e a marginalização e reduzir as desigualdades sociais e regionais;

promover o bem de todos, sem preconceitos de origem, raça, sexo, cor, idade e quaisquer outras formas de discriminação.

'; 26 | 27 | var lexml = exportarParaLexML(div.firstElementChild); 28 | expect(lexml.outerHTML).toBe('Título IDos Princípios FundamentaisArt. 1º –

A República Federativa do Brasil, formada pela união indissolúvel dos Estados e Municípios e do Distrito Federal, constitui-se em Estado democrático de direito e tem como fundamentos:

I –

a soberania;

II –

a cidadania;

III –

a dignidade da pessoa humana;

IV –

os valores sociais do trabalho e da livre iniciativa;

V –

o pluralismo político.

Parágrafo único –

Todo o poder emana do povo, que o exerce por meio de representantes eleitos ou diretamente, nos termos desta Constituição.

Art. 2º –

São Poderes da União, independentes e harmônicos entre si, o Legislativo, o Executivo e o Judiciário.

Art. 3º –

Constituem objetivos fundamentais da República Federativa do Brasil:

I –

construir uma sociedade livre, justa e solidária;

II –

garantir o desenvolvimento nacional;

III –

erradicar a pobreza e a marginalização e reduzir as desigualdades sociais e regionais;

IV –

promover o bem de todos, sem preconceitos de origem, raça, sexo, cor, idade e quaisquer outras formas de discriminação.

'); 29 | }); 30 | 31 | it('Deve exportar citação dentro do caput', function () { 32 | var div = document.createElement('div'); 33 | div.innerHTML = '

Caput:

"Citação.".

'; 34 | 35 | var lexml = exportarParaLexML(div.firstElementChild); 36 | expect(lexml.outerHTML).toBe('Art. 1º –

Caput:

"Citação.".

'); 37 | }); 38 | 39 | it('Deve suportar dois parágrafos', function() { 40 | var div = document.createElement('div'); 41 | div.innerHTML = '

Este é um artigo.

Este é um parágrafo.

Este é outro parágrafo.

'; 42 | 43 | var lexml = exportarParaLexML(div.firstElementChild); 44 | expect(lexml.outerHTML).toBe('Art. 1º –

Este é um artigo.

§ 1º –

Este é um parágrafo.

§ 2º –

Este é outro parágrafo.

'); 45 | }); 46 | 47 | it('Deve suportar itálico', function() { 48 | var div = document.createElement('div'); 49 | div.innerHTML = '

Este é um article.

'; 50 | 51 | var lexml = exportarParaLexML(div.firstElementChild); 52 | expect(lexml.outerHTML).toBe('Art. 1º –

Este é um article.

'); 53 | }); 54 | }); -------------------------------------------------------------------------------- /src/lexml/importarDeLexML.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | /** 19 | * Transforma o LexML no conteúdo para editor de articulação. 20 | * 21 | * @param {Element} elemento 22 | * @param {DocumentFragment} resultado 23 | * @returns {DocumentFragment} 24 | */ 25 | function importarDeLexML(elemento, resultado) { 26 | if (!resultado) { 27 | resultado = document.createDocumentFragment(); 28 | } 29 | 30 | if (!elemento) { 31 | return resultado; 32 | } else if (elemento instanceof NodeList || elemento instanceof HTMLCollection) { 33 | for (let i = 0; i < elemento.length; i++) { 34 | importarDeLexML(elemento[i], resultado); 35 | } 36 | } else if (typeof elemento === 'string') { 37 | let container = document.createElement('html'); 38 | container.innerHTML = elemento.replace(/<([^ >]+)\s*\/>/g, '<$1>'); // HTML não suporta notação como no XML. 39 | importarDeLexML(container.querySelector('body').children, resultado); 40 | } else { 41 | switch (elemento.tagName) { 42 | case 'ARTICULACAO': 43 | importarDeLexML(elemento.children, resultado); 44 | break; 45 | 46 | case 'TITULO': 47 | case 'CAPITULO': 48 | case 'SECAO': 49 | case 'SUBSECAO': 50 | resultado.appendChild(criarAgrupador(elemento.tagName.toLowerCase(), elemento.querySelector('NomeAgrupador').innerHTML)); 51 | importarDeLexML(elemento.children, resultado); 52 | break; 53 | 54 | case 'NOMEAGRUPADOR': 55 | case 'ROTULO': 56 | case 'P': 57 | // Ignora. 58 | break; 59 | 60 | case 'ARTIGO': 61 | clonar(elemento, 'Caput > p', idx => idx === 0 ? 'artigo' : 'continuacao', resultado); 62 | 63 | if (/\d+º?-[A-Z]/.test(elemento.querySelector('Rotulo').textContent)) { 64 | resultado.lastElementChild.classList.add('emenda'); 65 | } 66 | 67 | let incisos = elemento.querySelectorAll('Caput > Inciso'); 68 | importarDeLexML(incisos, resultado); 69 | 70 | let paragrafos = elemento.querySelectorAll('Paragrafo'); 71 | let dispositivoAtual = resultado.lastElementChild; 72 | 73 | importarDeLexML(paragrafos, resultado); 74 | 75 | if (paragrafos.length === 1) { 76 | dispositivoAtual.nextElementSibling.classList.add('unico'); 77 | } 78 | break; 79 | 80 | case 'INCISO': 81 | case 'ALINEA': 82 | case 'ITEM': 83 | case 'PARAGRAFO': 84 | let p = obterP(elemento); 85 | clonar(p, null, elemento.tagName.toLowerCase(), resultado); 86 | importarDeLexML(elemento.children, resultado); 87 | break; 88 | 89 | default: 90 | throw 'Elemento não esperado: ' + elemento.tagName; 91 | } 92 | } 93 | 94 | return resultado; 95 | } 96 | 97 | /** 98 | * Cria um elemento agrupador. 99 | * 100 | * @param {String} agrupador Tipo do agrupador. 101 | * @param {String} nome Nome do agrupador. 102 | * @returns {Element} Agrupador 103 | */ 104 | function criarAgrupador(agrupador, nome) { 105 | let elemento = document.createElement('p'); 106 | elemento.setAttribute('data-tipo', agrupador); 107 | elemento.innerHTML = nome; 108 | return elemento; 109 | } 110 | 111 | /** 112 | * Obtém o primeiro elemento P filho. 113 | * 114 | * @param {Element} elemento 115 | * @returns {Element} 116 | */ 117 | function obterP(elemento) { 118 | let p = elemento.firstElementChild; 119 | 120 | while (p.tagName !== 'P') { 121 | p = p.nextElementSibling; 122 | } 123 | 124 | return p; 125 | } 126 | 127 | /** 128 | * Clona um elemento. 129 | * 130 | * @param {Element} elemento Elemento que será clonado ou que contém os elementos a serem clonados, se a query for especificada. 131 | * @param {String} query Query a ser executada para filtrar os elementos filhos. 132 | * @param {String|function} tipoFinal Tipo final do elemento clonado (string) ou uma função que recebe (índice, elemento) e retorna o tipo em string. 133 | * @param {DocumentFragment} resultado 134 | */ 135 | function clonar(elemento, query, tipoFinal, resultado) { 136 | let fnTipo; 137 | 138 | switch (typeof tipoFinal) { 139 | case 'string': 140 | fnTipo = () => tipoFinal; 141 | break; 142 | 143 | case 'function': 144 | fnTipo = tipoFinal; 145 | break; 146 | 147 | default: 148 | throw new Error('Tipo final não é literal nem função.'); 149 | } 150 | 151 | if (query) { 152 | var itens = elemento.querySelectorAll(query); 153 | 154 | for (let i = 0; i < itens.length; i++) { 155 | let novoItem = itens[i].cloneNode(true); 156 | novoItem.setAttribute('data-tipo', fnTipo(i, novoItem)); 157 | resultado.appendChild(novoItem); 158 | } 159 | } else { 160 | let novoItem = elemento.cloneNode(true); 161 | novoItem.setAttribute('data-tipo', fnTipo(0, novoItem)); 162 | resultado.appendChild(novoItem); 163 | } 164 | 165 | return resultado; 166 | } 167 | 168 | export default importarDeLexML; -------------------------------------------------------------------------------- /src/ControleAlteracao.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import ArticulacaoChangeEvent from './eventos/ArticulacaoChangeEvent'; 19 | 20 | /** 21 | * Monitora alterações no editor de articulação e dispara o evento change. 22 | */ 23 | class ControleAlteracao { 24 | constructor(editorCtrl) { 25 | this._editorCtrl = editorCtrl; 26 | 27 | editorCtrl.registrarEventListener('focus', event => { 28 | this._comFoco = true; 29 | 30 | this.iniciar(event.target, editorCtrl); 31 | }, true); 32 | 33 | editorCtrl.registrarEventListener('blur', event => { 34 | this._comFoco = false; 35 | 36 | this.finalizar(event.target, editorCtrl); 37 | this.comprometer(); 38 | }, true); 39 | } 40 | 41 | get alterado() { 42 | throw 'Não implementado.'; 43 | } 44 | 45 | set alterado(valor) { 46 | throw 'Não implementado.'; 47 | } 48 | 49 | /** 50 | * Inicia a monitoração de alterações. 51 | * 52 | * @param {Element} elemento 53 | * @param {EditorArticulacaoController} editorCtrl Editor de articulação 54 | */ 55 | iniciar(elemento, editorCtrl) { 56 | throw 'Não implementado'; 57 | } 58 | 59 | /** 60 | * Finaliza a monitoração de alterações. 61 | * 62 | * @param {Element} elemento 63 | * @param {EditorArticulacaoController} editorCtrl Editor de articulação 64 | */ 65 | finalizar(elemento, editorCtrl) { 66 | throw 'Não implementado'; 67 | } 68 | 69 | comprometer() { 70 | if (this.alterado) { 71 | this.alterado = false; 72 | this._editorCtrl.dispatchEvent(new ArticulacaoChangeEvent(this._editorCtrl)); 73 | } 74 | } 75 | } 76 | 77 | /** 78 | * Monitora as alterações utilizando MutationObserver. 79 | */ 80 | class ControleAlteracaoMutationObserver extends ControleAlteracao { 81 | constructor(editorCtrl) { 82 | super(editorCtrl); 83 | 84 | this._observer = new MutationObserver(this._mutationCallback.bind(this)); 85 | this._iniciado = false; 86 | this._alterado = false; 87 | } 88 | 89 | get alterado() { 90 | return this._alterado; 91 | } 92 | 93 | set alterado(valor) { 94 | this._alterado = valor; 95 | 96 | if (!this._alterado && !this._conectado && this._comFoco) { 97 | this.iniciar(this._editorCtrl._elemento, this._editorCtrl); 98 | } 99 | 100 | if (this._alterado && !this._comFoco) { 101 | this.comprometer(); 102 | } 103 | 104 | if (this._alterado) { 105 | this.finalizar(); 106 | } 107 | } 108 | 109 | /** 110 | * Inicia a monitoração de alterações. 111 | * 112 | * @param {Element} elemento 113 | * @param {EditorArticulacaoController} editorCtrl Editor de articulação 114 | */ 115 | iniciar(elemento, editorCtrl) { 116 | if (!this._conectado) { 117 | this._observer.observe(elemento, { 118 | childList: true, 119 | attributes: true, 120 | characterData: true, 121 | subtree: true 122 | }); 123 | this._conectado = true; 124 | } 125 | this._iniciado = true; 126 | } 127 | 128 | /** 129 | * Finaliza a monitoração de alterações. 130 | * 131 | * @param {Element} elemento 132 | * @param {EditorArticulacaoController} editorCtrl Editor de articulação 133 | */ 134 | finalizar(elemento, editorCtrl) { 135 | if (this._conectado) { 136 | this._observer.disconnect(); 137 | this._conectado = false; 138 | } 139 | 140 | this._iniciado = false; 141 | } 142 | 143 | _mutationCallback() { 144 | try { 145 | this._conectado = false; 146 | this._observer.disconnect(); 147 | } finally { 148 | this.alterado = true; 149 | } 150 | } 151 | } 152 | 153 | /** 154 | * Monitora as alterações comparando o lexml no foco e no blur. 155 | */ 156 | class ControleAlteracaoComparativo extends ControleAlteracao { 157 | constructor(editorCtrl) { 158 | super(editorCtrl); 159 | 160 | this._alteradoCache = false; 161 | } 162 | 163 | get alterado() { 164 | if (this._alteradoCache) { 165 | return true; 166 | } else { 167 | this._alteradoCache = this._editorCtrl.lexmlString !== this._lexml; 168 | return this._alteradoCache; 169 | } 170 | } 171 | 172 | set alterado(valor) { 173 | this._lexml = valor ? '' : this._editorCtrl.lexmlString; 174 | this._alteradoCache = !!valor; 175 | 176 | if (valor && !this._comFoco) { 177 | this.comprometer(); 178 | } 179 | } 180 | 181 | /** 182 | * Inicia a monitoração de alterações. 183 | * 184 | * @param {Element} elemento 185 | * @param {EditorArticulacaoController} editorCtrl Editor de articulação 186 | */ 187 | iniciar(elemento, editorCtrl) { 188 | this._lexml = editorCtrl.lexmlString; 189 | } 190 | 191 | /** 192 | * Finaliza a monitoração de alterações. 193 | * 194 | * @param {Element} elemento 195 | * @param {EditorArticulacaoController} editorCtrl Editor de articulação 196 | */ 197 | finalizar(elemento, editorCtrl) { 198 | } 199 | } 200 | 201 | function criarControleAlteracao(editorCtrl) { 202 | if ('MutationObserver' in window) { 203 | return new ControleAlteracaoMutationObserver(editorCtrl); 204 | } else { 205 | return new ControleAlteracaoComparativo(editorCtrl); 206 | } 207 | } 208 | 209 | export default criarControleAlteracao; -------------------------------------------------------------------------------- /src/editor-articulacao-css.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | const css = ` 18 | .silegismg-editor-articulacao { 19 | counter-reset: parte livro titulo capitulo secao subsecao artigo; 20 | outline: none; 21 | } 22 | 23 | .silegismg-editor-articulacao p:before { 24 | font-weight: bolder; 25 | animation: .25s ease-in rotulo; 26 | } 27 | 28 | .silegismg-editor-articulacao p[data-tipo]:before { 29 | display: inline-block; /* Resolve problema de cursor antes do rótulo no chrome */ 30 | margin-right: 1ex; 31 | text-indent: 0; 32 | } 33 | 34 | .silegismg-editor-articulacao p { 35 | text-indent: 4ex; 36 | text-align: justify; 37 | border-left: solid 2px transparent; 38 | padding-left: 1ex; 39 | } 40 | 41 | .silegismg-editor-articulacao p[data-invalido] { 42 | border-left-color: red; 43 | } 44 | 45 | .silegismg-editor-articulacao p[data-invalido]:after { 46 | display: block; 47 | content: attr(data-invalido); 48 | color: red; 49 | text-indent: 0; 50 | margin-left: auto; 51 | font-size: 90%; 52 | } 53 | 54 | .silegismg-editor-articulacao p[data-tipo="titulo"]:before { 55 | content: 'Título ' counter(titulo, upper-roman); 56 | counter-increment: titulo; 57 | display: block; 58 | text-align: center; 59 | margin-right: 0; 60 | text-indent: 0; 61 | } 62 | 63 | .silegismg-editor-articulacao p[data-tipo="titulo"] { 64 | counter-reset: capitulo; 65 | text-transform: uppercase; 66 | font-weight: bolder; 67 | font-size: 110%; 68 | text-align: center; 69 | text-indent: 0; 70 | } 71 | 72 | .silegismg-editor-articulacao p[data-tipo="capitulo"]:before { 73 | content: 'Capítulo ' counter(capitulo, upper-roman); 74 | counter-increment: capitulo; 75 | display: block; 76 | text-align: center; 77 | font-weight: normal; 78 | margin-right: 0; 79 | text-indent: 0; 80 | } 81 | 82 | .silegismg-editor-articulacao p[data-tipo="capitulo"] { 83 | counter-reset: secao; 84 | text-transform: uppercase; 85 | font-size: 110%; 86 | text-align: center; 87 | text-indent: 0; 88 | } 89 | 90 | .silegismg-editor-articulacao p[data-tipo="secao"]:before { 91 | content: 'Seção ' counter(secao, upper-roman); 92 | counter-increment: secao; 93 | display: block; 94 | text-align: center; 95 | margin-right: 0; 96 | text-indent: 0; 97 | } 98 | 99 | .silegismg-editor-articulacao p[data-tipo="secao"] { 100 | counter-reset: subsecao; 101 | font-weight: bolder; 102 | font-size: 110%; 103 | text-align: center; 104 | text-indent: 0; 105 | } 106 | 107 | .silegismg-editor-articulacao p[data-tipo="subsecao"]:before { 108 | content: 'Subseção ' counter(subsecao, upper-roman); 109 | counter-increment: subsecao; 110 | display: block; 111 | text-align: center; 112 | margin-right: 0; 113 | text-indent: 0; 114 | } 115 | 116 | .silegismg-editor-articulacao p[data-tipo="subsecao"] { 117 | font-weight: bolder; 118 | font-size: 110%; 119 | text-align: center; 120 | text-indent: 0; 121 | } 122 | 123 | 124 | .silegismg-editor-articulacao p[data-tipo="artigo"]:before { 125 | content: 'Art. ' counter(artigo) 'º' '\${separadorArtigo}'; 126 | counter-increment: artigo; 127 | } 128 | 129 | .silegismg-editor-articulacao p[data-tipo="artigo"].emenda:before { 130 | content: 'Art. ' counter(artigo) 'º-' counter(emenda, upper-latin) '\${separadorArtigo}'; 131 | counter-increment: emenda; 132 | } 133 | 134 | /* A partir do artigo 10, não se usa "º" */ 135 | 136 | .silegismg-editor-articulacao p[data-tipo="artigo"]:not(.emenda) ~ p[data-tipo="artigo"]:not(.emenda) ~ p[data-tipo="artigo"]:not(.emenda) ~ p[data-tipo="artigo"]:not(.emenda) ~ p[data-tipo="artigo"]:not(.emenda) ~ p[data-tipo="artigo"]:not(.emenda) ~ p[data-tipo="artigo"]:not(.emenda) ~ p[data-tipo="artigo"]:not(.emenda) ~ p[data-tipo="artigo"]:not(.emenda) ~ p[data-tipo="artigo"]:not(.emenda):before { 137 | content: 'Art. ' counter(artigo) '\${separadorArtigoSemOrdinal}'; 138 | } 139 | 140 | .silegismg-editor-articulacao p[data-tipo="artigo"]:not(.emenda) ~ p[data-tipo="artigo"]:not(.emenda) ~ p[data-tipo="artigo"]:not(.emenda) ~ p[data-tipo="artigo"]:not(.emenda) ~ p[data-tipo="artigo"]:not(.emenda) ~ p[data-tipo="artigo"]:not(.emenda) ~ p[data-tipo="artigo"]:not(.emenda) ~ p[data-tipo="artigo"]:not(.emenda) ~ p[data-tipo="artigo"]:not(.emenda) ~ p[data-tipo="artigo"]:not(.emenda) ~ p[data-tipo="artigo"].emenda:before { 141 | content: 'Art. ' counter(artigo) '-' counter(emenda, upper-latin) '\${separadorArtigoSemOrdinal}'; 142 | } 143 | 144 | .silegismg-editor-articulacao p[data-tipo='artigo'] { 145 | counter-reset: paragrafo inciso emenda; 146 | } 147 | 148 | .silegismg-editor-articulacao p[data-tipo='artigo'].emenda { 149 | counter-reset: paragrafo inciso; 150 | } 151 | 152 | .silegismg-editor-articulacao p[data-tipo='paragrafo'] { 153 | counter-reset: inciso; 154 | } 155 | 156 | .silegismg-editor-articulacao p[data-tipo='paragrafo']:before { 157 | content: '§ ' counter(paragrafo) 'º' '\${separadorParagrafo}'; 158 | counter-increment: paragrafo; 159 | } 160 | 161 | .silegismg-editor-articulacao p.semOrdinal[data-tipo='paragrafo']:before { 162 | content: '§ ' counter(paragrafo) '\${separadorParagrafoSemOrdinal}'; 163 | counter-increment: paragrafo; 164 | } 165 | 166 | .silegismg-editor-articulacao p[data-tipo='paragrafo'].unico:before { 167 | content: 'Parágrafo único\${separadorParagrafoUnico}'; 168 | } 169 | 170 | .silegismg-editor-articulacao p[data-tipo='inciso'] { 171 | counter-reset: alinea; 172 | } 173 | 174 | .silegismg-editor-articulacao p[data-tipo='inciso']:before { 175 | content: counter(inciso, upper-roman) '\${separadorInciso}'; 176 | counter-increment: inciso; 177 | } 178 | 179 | .silegismg-editor-articulacao p[data-tipo='alinea'] { 180 | counter-reset: item; 181 | } 182 | 183 | .silegismg-editor-articulacao p[data-tipo='alinea']:before { 184 | content: counter(alinea, lower-latin) '\${separadorAlinea}'; 185 | counter-increment: alinea; 186 | } 187 | 188 | .silegismg-editor-articulacao p[data-tipo='item']:before { 189 | content: counter(item) '\${separadorItem}'; 190 | counter-increment: item; 191 | } 192 | 193 | 194 | /* O primeiro artigo não deve ter margem superior */ 195 | 196 | .silegismg-editor-articulacao > *:first-child { 197 | margin-top: 0; 198 | } 199 | 200 | 201 | /* O último artigo não deve ter margem inferior */ 202 | 203 | .silegismg-editor-articulacao > *:last-child { 204 | margin-bottom: 0; 205 | } 206 | 207 | @keyframes rotulo { 208 | 0% { 209 | opacity: 0; 210 | } 211 | 10% { 212 | opacity: 0; 213 | } 214 | 100% { 215 | opacity: 1; 216 | } 217 | } 218 | `; 219 | 220 | export default css; -------------------------------------------------------------------------------- /COPYING.LESSER: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /test/karma/importacaoDeLexML.spec.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | describe('Importação do LexML', function () { 19 | 'use strict'; 20 | 21 | var importarDeLexML = window.importarDeLexML; 22 | 23 | it('Deve importar os artigos 1 a 3 da constituição federal', function () { 24 | var articulacao = ` 25 | 26 | Título I 27 | Dos Princípios Fundamentais 28 | 29 | Art. 1º 30 | 31 |

A República Federativa do Brasil, formada pela união indissolúvel dos Estados e Municípios e do Distrito Federal, constitui-se em Estado democrático de direito e tem como fundamentos:

32 | 33 | I - 34 |

a soberania;

35 |
36 | 37 | II - 38 |

a cidadania;

39 |
40 | 41 | III - 42 |

a dignidade da pessoa humana;

43 |
44 | 45 | IV - 46 |

os valores sociais do trabalho e da livre iniciativa;

47 |
48 | 49 | V - 50 |

o pluralismo político.

51 |
52 |
53 | 54 | Parágrafo único. 55 |

Todo o poder emana do povo, que o exerce por meio de representantes eleitos ou diretamente, nos termos desta Constituição.

56 |
57 |
58 | 59 | Art. 2º 60 | 61 |

São Poderes da União, independentes e harmônicos entre si, o Legislativo, o Executivo e o Judiciário.

62 |
63 |
64 | 65 | Art. 3º 66 | 67 |

Constituem objetivos fundamentais da República Federativa do Brasil:

68 | 69 | I - 70 |

construir uma sociedade livre, justa e solidária;

71 |
72 | 73 | II - 74 |

garantir o desenvolvimento nacional;

75 |
76 | 77 | III - 78 |

erradicar a pobreza e a marginalização e reduzir as desigualdades sociais e regionais;

79 |
80 | 81 | IV - 82 |

promover o bem de todos, sem preconceitos de origem, raça, sexo, cor, idade e quaisquer outras formas de discriminação.

83 |
84 |
85 |
86 |
`; 87 | 88 | var fragmento = importarDeLexML(articulacao); 89 | var container = document.createElement('div'); 90 | container.appendChild(fragmento); 91 | 92 | expect(container.innerHTML).toBe('

Dos Princípios Fundamentais

A República Federativa do Brasil, formada pela união indissolúvel dos Estados e Municípios e do Distrito Federal, constitui-se em Estado democrático de direito e tem como fundamentos:

a soberania;

a cidadania;

a dignidade da pessoa humana;

os valores sociais do trabalho e da livre iniciativa;

o pluralismo político.

Todo o poder emana do povo, que o exerce por meio de representantes eleitos ou diretamente, nos termos desta Constituição.

São Poderes da União, independentes e harmônicos entre si, o Legislativo, o Executivo e o Judiciário.

Constituem objetivos fundamentais da República Federativa do Brasil:

construir uma sociedade livre, justa e solidária;

garantir o desenvolvimento nacional;

erradicar a pobreza e a marginalização e reduzir as desigualdades sociais e regionais;

promover o bem de todos, sem preconceitos de origem, raça, sexo, cor, idade e quaisquer outras formas de discriminação.

'); 93 | }); 94 | 95 | it('Deve importar citações dentro de artigo', function () { 96 | var articulacao = ` 97 | 98 | Art. 1º – 99 | 100 |

Ficam acrescidos ao art. 2º da Lei nº 1.234, de 31 de fevereiro de 2018, os incisos III e IV:

101 |

"Art. 2º - (...)

102 |

III - testar a importação;

103 |

IV - outro teste.".

104 |
105 |
106 | 107 | Art. 2º – 108 | 109 |

Esta lei entra em vigor na data de sua publicação.

110 |
111 |
112 |
`; 113 | 114 | var fragmento = importarDeLexML(articulacao); 115 | var container = document.createElement('div'); 116 | container.appendChild(fragmento); 117 | 118 | expect(container.innerHTML).toBe('

Ficam acrescidos ao art. 2º da Lei nº 1.234, de 31 de fevereiro de 2018, os incisos III e IV:

' + 119 | '

"Art. 2º - (...)

' + 120 | '

III - testar a importação;

' + 121 | '

IV - outro teste.".

' + 122 | '

Esta lei entra em vigor na data de sua publicação.

'); 123 | }); 124 | 125 | it('Deve transformar parágrafo vazio', function() { 126 | var articulacao = `Articulacao xmlns="http://www.lexml.gov.br/1.0"> 127 | 128 | Art. 1º – 129 | 130 |

Teste:

131 | 132 | II – 133 |

Teste.

134 | 135 | a) 136 |

137 | 138 | 1) 139 |

Teste

140 | 141 |
142 |
143 |
144 |
145 | `; 146 | 147 | var fragmento = importarDeLexML(articulacao); 148 | var container = document.createElement('div'); 149 | container.appendChild(fragmento); 150 | 151 | expect(container.innerHTML).toBe('

Teste:

Teste.

Teste

'); 152 | }); 153 | }); -------------------------------------------------------------------------------- /src/ContextoArticulacao.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | /** 19 | * Representa o contexto do usuário no editor de articulação. 20 | * Possui dois atributos: cursor, contendo o contexto no cursor, 21 | * e as permissões de alteração de dispositivo na seleção. 22 | */ 23 | class ContextoArticulacao { 24 | constructor(elementoArticulacao, dispositivo) { 25 | let cursor = { 26 | italico: dispositivo.tagName === 'I', 27 | desconhecido: false, 28 | titulo: false, 29 | capitulo: false, 30 | secao: false, 31 | subsecao: false, 32 | artigo: false, 33 | continuacao: false, 34 | paragrafo: false, 35 | inciso: false, 36 | alinea: false, 37 | item: false, 38 | raiz: false, 39 | elemento: dispositivo, 40 | get tipo() { 41 | return this.dispositivo ? this.dispositivo.getAttribute('data-tipo') : 'desconhecido'; 42 | } 43 | }; 44 | 45 | while (dispositivo && dispositivo !== elementoArticulacao && !dispositivo.hasAttribute('data-tipo')) { 46 | dispositivo = dispositivo.parentElement; 47 | } 48 | 49 | cursor.dispositivo = dispositivo !== elementoArticulacao ? dispositivo : null; 50 | 51 | while (dispositivo && dispositivo.getAttribute('data-tipo') === 'continuacao') { 52 | dispositivo = dispositivo.previousElementSibling; 53 | cursor.continuacao = true; 54 | } 55 | 56 | if (!dispositivo) { 57 | cursor.desconhecido = true; 58 | } else if (dispositivo === elementoArticulacao) { 59 | dispositivo.raiz = true; 60 | } else if (dispositivo.hasAttribute('data-tipo')) { 61 | cursor[dispositivo.getAttribute('data-tipo')] = true; 62 | } else { 63 | cursor.desconhecido = true; 64 | } 65 | 66 | let primeiroDoTipo, tipoAnterior; 67 | 68 | Object.defineProperty(cursor, 'primeiroDoTipo', { 69 | get: function() { 70 | if (primeiroDoTipo === undefined) { 71 | primeiroDoTipo = dispositivo && verificarPrimeiroDoTipo(dispositivo); 72 | } 73 | 74 | return primeiroDoTipo; 75 | } 76 | }); 77 | 78 | cursor.dispositivoAnterior = cursor.continuacao ? dispositivo : dispositivo && obterDispositivoAnterior(dispositivo, elementoArticulacao); 79 | cursor.tipoAnterior = cursor.dispositivoAnterior && cursor.dispositivoAnterior.getAttribute('data-tipo'); 80 | 81 | let matches = Element.prototype.matches || Element.prototype.webkitMatchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.onMatchesSelector || function() { return true; }; 82 | 83 | function possuiAnterior(dispositivo, tipo) { 84 | /* Implementação falha/incompleta. Uma subseção deve existir depois de uma seção, 85 | * mas não deveria permitir capítulo + seção + artigo + capítulo + subseção. 86 | * 87 | * Cuidado pois esta implementação não pode ser cara! 88 | */ 89 | return dispositivo && matches.call(dispositivo, 'p[data-tipo="' + tipo + '"] ~ *'); 90 | } 91 | 92 | let anteriorAgrupador = cursor.tipoAnterior === 'titulo' || cursor.tipoAnterior === 'capitulo' || cursor.tipoAnterior === 'secao' || cursor.tipoAnterior === 'subsecao'; 93 | 94 | let permissoes = { 95 | titulo: !anteriorAgrupador, 96 | capitulo: !anteriorAgrupador || cursor.tipoAnterior === 'titulo', 97 | get secao() { 98 | return (!anteriorAgrupador || cursor.tipoAnterior === 'capitulo') && possuiAnterior(cursor.dispositivo, 'capitulo'); 99 | }, 100 | get subsecao() { 101 | return (!anteriorAgrupador || cursor.tipoAnterior === 'secao') && possuiAnterior(cursor.dispositivo, 'secao'); 102 | }, 103 | artigo: true /*cursor.tipoAnterior !== 'titulo' - No manual de redação parlamentar da ALMG, dá a entender que o título é um agrupamento de capítulos, mas a constituição possui Art 1º. logo após o título.*/, 104 | continuacao: cursor.tipoAnterior === 'artigo', 105 | inciso: cursor.tipoAnterior && !anteriorAgrupador && (!cursor.artigo || (cursor.artigo && !cursor.primeiroDoTipo)), 106 | paragrafo: cursor.tipoAnterior && !anteriorAgrupador && (!cursor.artigo || (cursor.artigo && (!cursor.primeiroDoTipo || cursor.continuacao))), 107 | alinea: (cursor.inciso && !cursor.primeiroDoTipo) || cursor.tipoAnterior === 'inciso' || cursor.tipoAnterior === 'alinea' || cursor.tipoAnterior === 'item', 108 | item: (cursor.alinea && !cursor.primeiroDoTipo) || cursor.tipoAnterior === 'alinea' || cursor.tipoAnterior === 'item' 109 | }; 110 | 111 | Object.defineProperty(this, 'cursor', { value: cursor }); 112 | Object.defineProperty(this, 'permissoes', { value: permissoes }); 113 | 114 | Object.freeze(this); 115 | Object.freeze(cursor); 116 | Object.freeze(permissoes); 117 | } 118 | 119 | comparar(obj2) { 120 | for (let i in this.cursor) { 121 | if (this.cursor[i] !== obj2.cursor[i]) { 122 | return true; 123 | } 124 | } 125 | 126 | for (let i in this.permissoes) { 127 | if (this.permissoes[i] !== obj2.permissoes[i]) { 128 | return true; 129 | } 130 | } 131 | 132 | return false; 133 | } 134 | 135 | /** 136 | * Determina se o contexto atual é válido. 137 | * 138 | * @returns {Boolean} Verdadeiro, se o contexto estiver válido. 139 | */ 140 | get valido() { 141 | return this.permissoes[this.cursor.tipo]; 142 | } 143 | } 144 | 145 | /** 146 | * Determina se o dispositivo é o primeiro do tipo. 147 | * 148 | * @param {Element} dispositivo 149 | * @returns {Boolean} Verdadeiro, se for o primeiro do tipo. 150 | */ 151 | function verificarPrimeiroDoTipo(dispositivo) { 152 | var tipo = dispositivo.getAttribute('data-tipo'); 153 | 154 | if (!tipo) { 155 | return null; 156 | } 157 | 158 | while (tipo === 'continuacao') { 159 | dispositvo = dispositivo.previousElementSibling; 160 | tipo = dispositivo.getAttribute('data-tipo'); 161 | } 162 | 163 | let pontosParagem = ({ 164 | parte: [], 165 | livro: [], 166 | titulo: [], 167 | capitulo: ['titulo'], 168 | secao: ['capitulo', 'titulo'], 169 | subsecao: ['secao', 'capitulo', 'titulo'], 170 | artigo: [], 171 | paragrafo: ['artigo'], 172 | inciso: ['paragrafo', 'artigo'], 173 | alinea: ['inciso'], 174 | item: ['alinea'] 175 | })[tipo].reduce((prev, item) => { 176 | prev[item] = true; 177 | return prev; 178 | }, {}); 179 | 180 | for (let prev = dispositivo.previousElementSibling; prev; prev = prev.previousElementSibling) { 181 | let tipoAnterior = prev.getAttribute('data-tipo'); 182 | 183 | if (tipoAnterior === tipo) { 184 | return false; 185 | } else if (pontosParagem[tipoAnterior]) { 186 | return true; 187 | } 188 | } 189 | 190 | return true; 191 | } 192 | 193 | /** 194 | * Obtém o tipo de dispositivo anterior. 195 | * 196 | * @param {Element} dispositivo 197 | * @returns {Element} Elemento do dispositivo anterior. 198 | */ 199 | function obterDispositivoAnterior(dispositivo, elementoArticulacao) { 200 | while (dispositivo && !dispositivo.hasAttribute('data-tipo') && dispositivo !== elementoArticulacao) { 201 | dispositivo = dispositivo.parentElement; 202 | } 203 | 204 | if (!dispositivo || dispositivo === elementoArticulacao) { 205 | return null; 206 | } 207 | 208 | for (let anterior = dispositivo.previousElementSibling; anterior; anterior = anterior.previousElementSibling) { 209 | if (anterior.hasAttribute('data-tipo')) { 210 | let tipo = anterior.getAttribute('data-tipo'); 211 | 212 | if (tipo !== 'continuacao') { 213 | return anterior; 214 | } 215 | } 216 | } 217 | 218 | return null; 219 | } 220 | 221 | export default ContextoArticulacao; -------------------------------------------------------------------------------- /src/hacks/chrome.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import { interceptar } from './interceptador'; 19 | 20 | function hackChrome(controller) { 21 | interceptar(controller, 'alterarTipoDispositivoSelecionado', hackAlterarTipo); 22 | 23 | controller.registrarEventListener('keydown', event => hackInterceptarKeydown(event, controller)); 24 | 25 | /* A partir do Chrome 62, começou a ter problema no foco após limpar o editor. 26 | * O problema foi evitado adicionando um \n após o
ou substituindo o conteúdo 27 | * por " "" como feito no IE. 28 | */ 29 | controller.limpar = function() { 30 | this._elemento.innerHTML = '


\n

'; 31 | }; 32 | } 33 | 34 | /** 35 | * O chrome faz cache do counter-reset e, quando um counter-reset é introduzido 36 | * (por mudança de css), os elementos seguintes não são afetados. 37 | * 38 | * @param {EditorArticulacaoController} ctrl 39 | * @param {function} metodo 40 | * @param {Array} argumentos 41 | */ 42 | function hackAlterarTipo(ctrl, metodo, argumentos) { 43 | let dispositivo = ctrl.contexto.cursor.dispositivo; 44 | let contadoresAntigos = extrairContadores(dispositivo); 45 | let retorno = metodo.apply(ctrl, argumentos); 46 | let contadoresNovos = extrairContadores(dispositivo, contadoresAntigos.hash); 47 | 48 | /* Para cada contador novo, vamos redefinir o tipo dos próximos elementos, 49 | * até que se encontre em um elemento antigo o reinício do contador em questão. 50 | */ 51 | for (let i = 0, proximo = ctrl.contexto.cursor.dispositivo.nextElementSibling; proximo && i < contadoresNovos.total; proximo = proximo.nextElementSibling) { 52 | let tipo = proximo.getAttribute('data-tipo'); 53 | let contadoresProximo = extrairContadores(proximo); 54 | 55 | if (contadoresNovos.hash[tipo]) { 56 | redefinirContador(proximo); 57 | } 58 | 59 | // Não há necessidade de reiniciar contadores quando o próximo já o reiniciava. 60 | for (let contador in contadoresProximo.hash) { 61 | if (contadoresNovos.hash[contador]) { 62 | contadoresNovos.hash[contador] = false; 63 | i++; 64 | } 65 | } 66 | } 67 | 68 | return retorno; 69 | } 70 | 71 | /** 72 | * Extrai os contadores reinciiados por este elemento. 73 | * 74 | * @param {Element} dispositivo 75 | * @param {Object} contadoresDesconsiderar Hash contendo os contadores que não deverão ser considerados. 76 | */ 77 | function extrairContadores(dispositivo, contadoresDesconsiderar) { 78 | let counterReset = getComputedStyle(dispositivo).counterReset; 79 | let contadores = {}; 80 | let cnt = 0; 81 | 82 | for (let regexp = /(.+?)\s\d+\s*/g, m = regexp.exec(counterReset); m; m = regexp.exec(counterReset)) { 83 | let contador = m[1]; 84 | 85 | if (!contadoresDesconsiderar || !contadoresDesconsiderar[contador]) { 86 | contadores[contador] = true; 87 | cnt++; 88 | } 89 | } 90 | 91 | return { total: cnt, hash: contadores }; 92 | } 93 | 94 | /** 95 | * Efetua a redefinição do contador. 96 | * 97 | * @param {Element} dispositivo 98 | */ 99 | function redefinirContador(dispositivo) { 100 | let tipo = dispositivo.getAttribute('data-tipo'); 101 | dispositivo.removeAttribute('data-tipo'); 102 | setTimeout(() => dispositivo.setAttribute('data-tipo', tipo)); 103 | } 104 | 105 | /** 106 | * O Chrome tem um problema de desempenho que fica evidente ao importar 107 | * o LexML da Constituição Federal, selecionar todo o texto no editor 108 | * de articulação, e pressionar qualquer tecla que iria remover o texto 109 | * selecionado (seja por exclusão ou substituição). 110 | * 111 | * Para cada elemento no DOM a ser excluído ou substituído, o navegador 112 | * processa os estilos do próximo elemento (conforme ferramenta de profile 113 | * do próprio navegador), tornando a operação O(n²). Para contornar o problema, 114 | * realizamos a exclusão do conteúdo via API, interceptando o keydown. 115 | * 116 | * @param {KeyboardEvent} keyboardEvent 117 | * @param {EditorArticulacaoController} editorCtrl 118 | */ 119 | function hackInterceptarKeydown(keyboardEvent, editorCtrl) { 120 | /* Somente serão tratadas as alterações de conteúdo. Portanto, 121 | * se houver qualquer tecla modificativa (ctrl, alt ou meta), 122 | * o evento será ignorado. O evento só será tratado se a tecla for 123 | * de conteúdo (letra, número ou enter), ou remoção (delete, backspace). 124 | */ 125 | if (!keyboardEvent.ctrlKey && !keyboardEvent.altKey && !keyboardEvent.metaKey && 126 | keyboardEvent.key.length === 1 || keyboardEvent.key === 'Delete' || keyboardEvent.key === 'Backspace' || 127 | keyboardEvent.key === 'Enter') { 128 | 129 | let selection = editorCtrl.getSelection(); 130 | let range = selection.getRangeAt(0); 131 | 132 | // Se não houver nada selecionado, então não há problema de desempenho. 133 | if (!range.collapsed) { 134 | let inicio = range.startContainer.parentNode; 135 | let final = range.endContainer.parentNode; 136 | 137 | range.deleteContents(); 138 | 139 | /* Se a tecla for de remoção, então evitamos a ação padrão. 140 | * Entretanto, se for de conteúdo, então deixamos que a ação 141 | * padrão seja executada, para que o novo conteúdo seja inserido 142 | * no lugar do conteúdo excluído. 143 | */ 144 | if (keyboardEvent.key === 'Delete' || keyboardEvent.key === 'Backspace') { 145 | keyboardEvent.preventDefault(); 146 | } 147 | 148 | /* O range da seleção, ainda que seja de todo o conteúdo, não abrange 149 | * o elemento inicial e o final. Isto é, se o usuário selecionar tudo 150 | * usando Ctrl+A e prosseguir com a exclusão com a tecla Delete, 151 | * o conteúdo selecionado correpsonderá ao primeiro nó textual do primeiro 152 | * P até o último nó textual do útlimo P. 153 | * 154 | *

nó textual

155 | *

nó textual intermediário

156 | *

último nó textual<-- final da seleção -->

157 | * 158 | * Assim, a exclusão irá remover todos os nós textuais e os elementos intermediários, 159 | * mantendo, entretanto, o primeiro e último elemento (veja ilustração acima, 160 | * em que a seleção foi demarcada em comentário). 161 | * 162 | * Para contornar esta situação, realizamos manualmente a exclusão dos elementos, 163 | * se o elemento inicial da seleção for diferente do final, condicionando 164 | * a exclusão à presença de conteúdo. Deve-se executar o procedimento somente 165 | * se o início for diferente do final, pois a seleção pode ser apenas de 166 | * conteúdo intermediário, como ilustrado a seguir: 167 | * 168 | *

este é um exemplo.

169 | */ 170 | if (inicio !== final) { 171 | if (inicio != editorCtrl._elemento && inicio.textContent.length === 0) { 172 | inicio.remove(); 173 | } 174 | 175 | if (final != editorCtrl._elemento && final.textContent.length === 0) { 176 | final.remove(); 177 | } 178 | 179 | /* Caso todo o conteúdo estivesse selecionado, o editor de articulação 180 | * passará a ter nenhum elemento neste momento. Neste caso, 181 | * deve-se recriar o conteúdo mínimo. 182 | */ 183 | if (editorCtrl._elemento.children.length === 0) { 184 | editorCtrl.limpar(); 185 | } 186 | } 187 | // ... mas se a seleção for todo o conteúdo de um único elemento... 188 | else if (inicio.textContent.length === 0 && inicio.children.length === 0) { 189 | /* então deve-se garantir o conteúdo mínimo, para que o cursor do parágrafo 190 | * fique posicionado corretamente. 191 | */ 192 | inicio.innerHTML = '
'; 193 | } 194 | } 195 | } 196 | } 197 | 198 | export default hackChrome; -------------------------------------------------------------------------------- /src/ClipboardController.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | import { interceptarApos } from './hacks/interceptador'; 19 | import { interpretarArticulacao, transformarQuebrasDeLinhaEmP } from './interpretadorArticulacao'; 20 | 21 | /** 22 | * Controlador da área de transferência do editor de articulação. 23 | */ 24 | class ClipboardController { 25 | constructor(editorCtrl, validacaoCtrl) { 26 | this.editorCtrl = editorCtrl; 27 | this.validacaoCtrl = validacaoCtrl; 28 | 29 | editorCtrl.registrarEventListener('paste', (event) => aoColar(event, this)); 30 | } 31 | 32 | /** 33 | * Cola um texto de articulação no editor. 34 | * 35 | * @param {String} texto 36 | */ 37 | colarTexto(texto) { 38 | let selecao = this.editorCtrl.getSelection(); 39 | let range = selecao.getRangeAt(0); 40 | 41 | if (!range.collapsed) { 42 | range.deleteContents(); 43 | this.editorCtrl.atualizarContexto(); 44 | } 45 | 46 | for (let brs = (range.endContainer.nodeType === Node.TEXT_NODE ? range.endContainer.parentNode : range.endContainer).querySelectorAll('br'), i = 0; i < brs.length; i++) { 47 | brs[i].remove(); 48 | } 49 | 50 | let fragmento = transformar(texto, this.editorCtrl.contexto.cursor.tipo, this.editorCtrl.contexto.cursor.continuacao); 51 | 52 | colarFragmento(fragmento, this.editorCtrl, this.validacaoCtrl); 53 | } 54 | } 55 | 56 | /** 57 | * Transforma um texto em um fragmento a ser colado. 58 | * 59 | * @param {*} texto Texto a ser transformado. 60 | * @param {*} tipo Tipo do contexto atual do cursor. 61 | * @param {*} continuacao Se o tipo é uma continuação. 62 | */ 63 | function transformar(texto, tipo, continuacao) { 64 | var fragmento; 65 | 66 | if (continuacao) { 67 | fragmento = transformarTextoPuro(texto, 'continuacao'); 68 | } else { 69 | let dados = interpretarArticulacao(texto, 'json'); 70 | 71 | if (dados.textoAnterior) { 72 | fragmento = transformarTextoPuro(dados.textoAnterior, tipo || 'artigo'); 73 | } 74 | 75 | if (dados.articulacao.length > 0) { 76 | let fragmentoArticulacao = transformarArticulacao(dados.articulacao); 77 | 78 | if (fragmento) { 79 | fragmento.appendChild(fragmentoArticulacao); 80 | } else { 81 | fragmento = fragmentoArticulacao; 82 | } 83 | } 84 | } 85 | 86 | return fragmento; 87 | } 88 | 89 | /** 90 | * Trata a colagem da área de transferência. 91 | * 92 | * @param {ClipboardEvent} event 93 | * @param {ClipboardController} clipboardCtrl 94 | */ 95 | function aoColar(event, clipboardCtrl) { 96 | var clipboardData = event.clipboardData || window.clipboardData; 97 | var itens = clipboardData.items; 98 | 99 | if (itens) { 100 | for (let i = 0; i < itens.length; i++) { 101 | if (itens[i].type === 'text/plain') { 102 | itens[i].getAsString(clipboardCtrl.colarTexto.bind(clipboardCtrl)); 103 | event.preventDefault(); 104 | } 105 | } 106 | } else if (clipboardData.getData) { 107 | clipboardCtrl.colarTexto(clipboardData.getData('text/plain')); 108 | event.preventDefault(); 109 | } 110 | } 111 | 112 | /** 113 | * Cola um fragmento no DOM. 114 | * 115 | * @param {DocumentFragment} fragmento 116 | * @param {EditorArticulacaoController} editorCtrl 117 | * @param {ValidacaoController} validacaoCtrl 118 | */ 119 | function colarFragmento(fragmento, editorCtrl, validacaoCtrl) { 120 | prepararDesfazer(fragmento, editorCtrl); 121 | 122 | let proximaSelecao = fragmento.lastChild; 123 | let selecao = editorCtrl.getSelection(); 124 | let range = selecao.getRangeAt(0); 125 | let noInicial = range.startContainer; 126 | let excluirNoAtual = fragmento.firstChild.nodeType !== Node.TEXT_NODE && noInicial !== editorCtrl._elemento && noInicial.textContent.trim().length === 0; 127 | let primeiroElementoAValidar; 128 | 129 | // Se a seleção estiver no container, então devemos inserir elementos filhos... 130 | if (range.collapsed && noInicial === editorCtrl._elemento) { 131 | // Remove as quebras de linha, usada como placeholder pelos contentEditable. 132 | for (let itens = editorCtrl._elemento.querySelectorAll('br'), i = 0; i < itens.length; i++) { 133 | itens[i].remove(); 134 | } 135 | 136 | // Remove os elementos vazios. 137 | for (let itens = editorCtrl._elemento.querySelectorAll('*:empty'), i = 0; i < itens.length; i++) { 138 | itens[i].remove(); 139 | } 140 | 141 | // Transforma os nós textuais em parágrafos. 142 | for (let item = fragmento.firstChild; item && item.nodeType === Node.TEXT_NODE; item = fragmento.firstChild) { 143 | let p = document.createElement('p'); 144 | p.appendChild(item); 145 | p.setAttribute('data-tipo', 'artigo'); 146 | fragmento.insertBefore(p, fragmento.firstElementChild); 147 | } 148 | 149 | primeiroElementoAValidar = fragmento.firstElementChild; 150 | range.insertNode(fragmento); 151 | } else { 152 | if (excluirNoAtual) { 153 | primeiroElementoAValidar = fragmento.firstElementChild; 154 | } else { 155 | primeiroElementoAValidar = noInicial.nodeType === Node.ELEMENT_NODE ? noInicial : noInicial.parentElement; 156 | } 157 | 158 | // Insere os primeiros nós textuais na própria seleção. 159 | for (let item = fragmento.firstChild; item && item.nodeType === Node.TEXT_NODE; item = fragmento.firstChild) { 160 | range.insertNode(item); 161 | } 162 | 163 | // Insere os elementos em seguida, no container, pois não podem estar aninhados ao elemento da seleção. 164 | let referencia = range.endContainer; 165 | 166 | while (referencia.parentElement !== editorCtrl._elemento) { 167 | referencia = referencia.parentElement; 168 | } 169 | 170 | referencia.parentElement.insertBefore(fragmento, referencia.nextSibling); 171 | } 172 | 173 | if (excluirNoAtual) { 174 | noInicial.remove(); 175 | } 176 | 177 | // Altera a seleção. 178 | selecao.removeAllRanges(); 179 | range = document.createRange(); 180 | 181 | range.setStartAfter(proximaSelecao); 182 | selecao.addRange(range); 183 | 184 | editorCtrl.atualizarContexto(); 185 | 186 | // Realiza validação do fragmento colado. 187 | let ultimoElementoAValidar = proximaSelecao.nodeType === Node.ELEMENT_NODE ? proximaSelecao : proximaSelecao.parentElement; 188 | 189 | for (let noAlterado = primeiroElementoAValidar; noAlterado && noAlterado !== ultimoElementoAValidar; noAlterado = noAlterado.nextElementSibling) { 190 | validacaoCtrl.validar(noAlterado); 191 | } 192 | } 193 | 194 | /** 195 | * Realiza uma cópia do fragmento e monitora ctrl+z para remover fragmentos. 196 | * 197 | * @param {DocumentFragment} fragmento 198 | * @param {EditorArticulacaoController} editorCtrl 199 | */ 200 | function prepararDesfazer(fragmento, editorCtrl) { 201 | var copia = []; 202 | 203 | for (let i = 0, l = fragmento.childNodes.length; i < l; i++) { 204 | copia.push(fragmento.childNodes[i]); 205 | } 206 | 207 | let desfazer = function() { 208 | let anterior = copia[0].previousSibling; 209 | let posterior = copia[copia.length - 1].nextSibling; 210 | 211 | // Remove os elementos 212 | for (let i = 0; i < copia.length; i++) { 213 | copia[i].remove(); 214 | } 215 | 216 | removerListeners(); 217 | 218 | // Restaura o cursor. 219 | let selecao = editorCtrl.getSelection(); 220 | let range = document.createRange(); 221 | 222 | if (anterior) { 223 | range.setStartAfter(anterior); 224 | selecao.removeAllRanges(); 225 | selecao.addRange(range); 226 | } else if (posterior) { 227 | range.setStartBefore(posterior); 228 | selecao.removeAllRanges(); 229 | selecao.addRange(range); 230 | } 231 | }; 232 | 233 | let keyDownListener = function(keyboardEvent) { 234 | // Desfaz se pressionar o ctrl+z 235 | if (keyboardEvent.ctrlKey && (keyboardEvent.key === 'z' || keyboardEvent.key === 'Z')) { 236 | desfazer(); 237 | keyboardEvent.preventDefault(); 238 | } 239 | }; 240 | 241 | let bakExecCommand = document.execCommand; 242 | 243 | let removerListeners = function() { 244 | editorCtrl._elemento.removeEventListener('keydown', keyDownListener); 245 | editorCtrl._elemento.removeEventListener('keypress', removerListeners); 246 | document.execCommand = bakExecCommand; 247 | }; 248 | 249 | editorCtrl._elemento.addEventListener('keydown', keyDownListener); 250 | editorCtrl._elemento.addEventListener('keypress', removerListeners); 251 | 252 | document.execCommand = function(comando) { 253 | if (comando === 'undo') { 254 | desfazer(); 255 | } else { 256 | return bakExecCommand.apply(document, arguments); 257 | } 258 | }; 259 | } 260 | 261 | /** 262 | * Insere texto simples no contexto atual. 263 | * 264 | * @param {String} texto Texto a ser interpretado. 265 | * @param {String} tipo Tipo atual. 266 | * @returns {DocumentFragment} Fragmento gerado para colagem. 267 | */ 268 | function transformarTextoPuro(texto, tipo) { 269 | if (texto.length === 0) { 270 | return; 271 | } 272 | 273 | var fragmento = document.createDocumentFragment(); 274 | let primeiraQuebra = texto.indexOf('\n'); 275 | 276 | if (primeiraQuebra !== 0) { 277 | /* A primeira linha deve ser inserida no dispositivo atual. 278 | * Para tanto, utiliza-se um TextNode. 279 | */ 280 | let textNode = document.createTextNode(primeiraQuebra > 0 ? texto.substr(0, primeiraQuebra) : texto); 281 | fragmento.appendChild(textNode); 282 | } 283 | 284 | if (primeiraQuebra !== -1) { 285 | // Demais linhas devem ser criadas em novos parágrafos. 286 | let novoTexto = transformarQuebrasDeLinhaEmP(texto.substr(primeiraQuebra + 1)); 287 | fragmento.appendChild(novoTexto); 288 | 289 | /** 290 | * Define o tipo para cada P, formatando automaticamente com base na terminação de dois pontos (:) ou ponto final(.). 291 | */ 292 | let anterior = []; 293 | 294 | if (fragmento.firstChild.nodeType === Node.TEXT_NODE && fragmento.firstChild.textContent.endsWith(':')) { 295 | let enumeracao = novoTipoEnumeracao(tipo); 296 | 297 | if (tipo !== enumeracao) { 298 | anterior.push(tipo); 299 | tipo = enumeracao; 300 | } 301 | } 302 | 303 | for (let item = fragmento.firstElementChild; item; item = item.nextElementSibling) { 304 | item.setAttribute('data-tipo', tipo); 305 | 306 | if (item.textContent.endsWith(':')) { 307 | let enumeracao = novoTipoEnumeracao(tipo); 308 | 309 | if (tipo !== enumeracao) { 310 | anterior.push(tipo); 311 | tipo = enumeracao; 312 | } 313 | } else if (anterior.length > 0 && item.textContent.endsWith('.')) { 314 | tipo = anterior.pop(); 315 | } 316 | } 317 | } 318 | 319 | return fragmento; 320 | } 321 | 322 | function novoTipoEnumeracao(tipoAtual) { 323 | switch (tipoAtual) { 324 | case 'artigo': 325 | case 'paragrafo': 326 | return 'inciso'; 327 | 328 | case 'inciso': 329 | return 'alinea'; 330 | 331 | case 'alinea': 332 | return 'item'; 333 | 334 | default: 335 | return null; 336 | } 337 | } 338 | 339 | function transformarArticulacao(articulacao) { 340 | let fragmento = document.createDocumentFragment(); 341 | 342 | articulacao.forEach(item => fragmento.appendChild(item.paraEditor())); 343 | 344 | return fragmento; 345 | } 346 | 347 | export { ClipboardController, transformarQuebrasDeLinhaEmP, transformarTextoPuro, transformar }; 348 | export default ClipboardController; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://badge.fury.io/js/silegismg-editor-articulacao.svg)](https://badge.fury.io/js/silegismg-editor-articulacao) 2 | [![Node.js CI](https://github.com/silegis-mg/editor-articulacao/actions/workflows/node.js.yml/badge.svg)](https://github.com/silegis-mg/editor-articulacao/actions/workflows/node.js.yml) 3 | [![Maintainability](https://api.codeclimate.com/v1/badges/053249e72dc77f0e2e3b/maintainability)](https://codeclimate.com/github/silegis-mg/editor-articulacao/maintainability) 4 | 5 | # Editor de Articulação 6 | 7 | O editor de articulação é uma biblioteca javascript elaborada pela Assembleia Legislativa de Minas Gerais, 8 | como parte do Sistema de Informação Legislativa de Minas Gerais (Silegis-MG). 9 | 10 | Ele permite a edição de texto articulado, formatando e numerando automaticamente artigos, parágrafos, 11 | incisos, alíneas e itens, bem como as divisões em títulos, capítulos, seções e subseções. O texto articulado 12 | é estruturado em formato XML, conforme elemento `Articulacao` definido pelo schema do [LexML](https://github.com/lexml/lexml-xml-schemas/tree/master/src/main/resources/xsd). 13 | 14 | ## Demonstração 15 | 16 | Acesse https://silegis-mg.github.io/editor-articulacao/ para ver uma simples demonstração do editor de articulação funcionando em seu navegador. 17 | 18 | ## Funcionalidades 19 | 20 | * Criação de **rótulo e numeração automática** para dispositivos (artigo, parágrafo, inciso, alínea e item); 21 | 22 | * Formatação padrão dos dispositivos, conforme regras de redação definidas no art. 12 da [LCP 78/2004](http://www.almg.gov.br/consulte/legislacao/completa/completa.html?tipo=LCP&num=78&comp=&ano=2004). 23 | 24 | * A formatação pode ser **configurada**, para atender ao padrão de redação federal, sem alteração no código. 25 | 26 | * Divisão dos artigos em títulos, capítulos, seções e subseções; 27 | * Validação automática de: 28 | * **caixa alta:** artigos e parágrafos não devem ser escritos usando apenas caixa alta; 29 | * **uso de aspas:** citações devem estar entre aspas e terminar com ponto final (.); 30 | * **enumerações:** enumerações devem possuir mais de um elemento; 31 | * **letra maiúscula:** artigos e parágrafos devem ser iniciados com letra maiúscula; 32 | * **pontuação:** artigos e parágrafos devem ser terminados com ponto final (.) ou dois pontos (:), sem espaço antes da pontuação, e enumerações devem ser terminadas com ponto final (.), ponto e vírgula (;) ou dois pontos (:), sem espaço antes da pontuação.; 33 | * **sentença única:** dispositivos devem conter uma única sentença. 34 | * Auto-formatação: 35 | * **ao abrir aspas**, formata-se automaticamente como um texto dentro do caput, permitindo múltiplas linhas dentro das aspas; 36 | * **ao fechar aspas**, formata-se a próxima linha como um novo artigo; 37 | * **ao finalizar com dois pontos**, inicia-se uma enumeração (de artigo ou parágrafo para inciso, de inciso para alínea e de alínea para item); 38 | * **ao finalizar com ponto final**, finaliza-se a enumeração (de item para alínea, de alínea para inciso, de inciso para artigo ou parágrafo); 39 | * **ao dar enter em uma linha vazia**, troca a formatação da linha vazia para artigo, quando formatado como parágrafo, ou encerra a enumeração, quando formatado como inciso, alínea ou item. 40 | * Articulação pode ser importada/exportada de/para XML, seguindo especificação do **LexML**; 41 | * Interpretação de conteúdo colado, de forma a permitir a formatação e numeração automática em dispositivos estruturados. 42 | 43 | ## Como usar a partir do código fonte 44 | 45 | ### Pré-requisitos para compilação 46 | 47 | * [NodeJS com npm](https://nodejs.org/en/download/) 48 | 49 | ### Baixando o editor 50 | 51 | ``` 52 | git clone https://github.com/silegis-mg/editor-articulacao.git 53 | ``` 54 | 55 | ### Instalação das dependências 56 | 57 | Após baixar o editor, mude para o diretório onde encontram os fontes e instale as dependências: 58 | 59 | ``` 60 | cd editor-articulacao 61 | npm install 62 | ``` 63 | 64 | ### Executando exemplo 65 | 66 | Finalizado o passo anterior, execute: 67 | 68 | ``` 69 | npm start 70 | ``` 71 | 72 | Em seguida, basta abrir o navegador no endereço http://localhost:8080/exemplo.html 73 | 74 | ### Testando 75 | 76 | O editor de articulação possui testes automatizados utilizando karma e protractor. 77 | 78 | ``` 79 | npm test 80 | ``` 81 | 82 | Se estiver utlizando proxy, defina a variável de ambiente http_proxy para que o teste consiga baixar o webdriver do Chrome mais atual. 83 | 84 | ### Gerando pacote para aplicações finais em ES5 85 | 86 | O javascript minificado é gerado por meio do webpack, a partir de uma tarefa do grunt. Existem dois empacotamentos para uso em aplicações finais: 87 | 88 | #### Plain-JS 89 | 90 | O empacotamento `plain-js` define `silegismgEditorArticulacao` como uma função global para transformar um elemento no DOM em um editor de articulação. 91 | Também define a função `silegismgEditorArticulacaoController` para criar o controller, caso o utilizador queira maior controle 92 | da interface de usuário. 93 | 94 | Também é definida a função global [`silegismgInterpretadorArticulacao.interpretar`](#api-interpretador) para interpretação de texto articulado. 95 | 96 | ##### Gerando pacote 97 | 98 | ``` 99 | npx grunt build-plain 100 | ``` 101 | 102 | É possível incluir o polyfill do babel também, utilizando: 103 | 104 | ``` 105 | npx grunt build-plain-polyfill 106 | ``` 107 | 108 | ##### Utilizando plain-js 109 | 110 | Existem duas possibilidades para criar o editor de articulação. Uma que incorpora a barra de ferramentas 111 | e outra que apenas vincula o controlador do editor de articulação, permitindo ao utilizador personalizar por 112 | completo a interface de usuário. 113 | 114 | Para criar o editor de articulação com barra de ferramentas padrão, utilize a sintaxe `silegismgEditorArticulacao(elemento, opcoes)`. 115 | Para criar o editor de articulação personalizando por completa a interface de usuário, utilize a sintaxe `silegismgEditorArticulacaoController(elemento, opcoes)`, que retornará o controlador, cujos métodos estão descritos na [API do controlador](#api-controlador). Para exemplo de como personalizar a interface, veja o [arquivo de teste do protractor](test/protractor/teste.html). 116 | 117 | Veja também a [API do interpretador de articulação](#api-interpretador). 118 | 119 | ##### Exemplo 120 | 121 | ```html 122 | 123 |
124 | 127 | ``` 128 | 129 | #### Angular 1 130 | 131 | O empacotamento `angular1` registra a diretiva `silegismgEditorArticulacaoConteudo` no módulo `silegismg-editor-articulacao` para AngularJS 1.x. 132 | 133 | ##### Gerando pacote 134 | 135 | ``` 136 | npx grunt build-angular1 137 | ``` 138 | 139 | ##### Exemplo 140 | 141 | ```html 142 | 143 | 144 | ``` 145 | 146 | 147 | 148 | ### Utilizando como módulo ES6 e webpack 149 | 150 | ``` 151 | npm install silegismg-editor-articulacao 152 | ``` 153 | 154 | JS: 155 | ```js 156 | import { ComponenteEdicao, EditorArticulacaoController, interpretadorArticulacao } from 'silegismg-editor-articulacao'; 157 | 158 | const opcoes = { /* ... */ }; 159 | var elemento = document.getElementById('exemplo'); 160 | var ctrl = new EditorArticulacaoController(elemento, opcoes); 161 | ``` 162 | 163 | HTML: 164 | ```html 165 |
166 | ``` 167 | 168 | #### Configuração do webpack 169 | 170 | O editor de articulação importa o conteúdo do CSS e manipula em tempo de execução, a fim de aplicar os parâmetros de configuração. Para tanto, deve-se utilizar o seguinte loader para os arquivos CSS deste módulo: 171 | 172 | ```json 173 | { 174 | test: /\.css$/, 175 | use: { 176 | loader: 'css-loader', 177 | options: { 178 | minimize: true, 179 | sourceMap: true 180 | } 181 | } 182 | } 183 | ``` 184 | 185 | ## Opções do editor de articulação 186 | 187 | | Atributo | Tipo | Valor padrão | Descrição | 188 | | -------- | ---- | ------------ | --------- | 189 | | shadowDOM | Boolean | false | **(Experimental)** Determina se deve adotar o Shadow DOM, se suportado pelo navegador. | 190 | | transformacaoAutomatica | Boolean | true | Determina se o editor de articulação deve aplicar transformação automática. | 191 | | escaparXML | Boolean | false | Determina o escapamento de caracteres de código alto unicode durante a exportação para lexmlString. | 192 | | rotulo | [Object](#opcoes.rotulo) | | Determina o sufixo para os rótulos dos dispositivos. | 193 | | validarAoAtribuir | Boolean | true | Determina se deve validar o conteúdo atribuído ao componente. | 194 | | validacao | [Object](#opcoes.validacao) | | Determina as validações que devem ocorrer. | 195 | 196 | 197 | 198 | ### Opções de rótulo 199 | 200 | Todos os atributos de rótulo são do tipo literal (String). 201 | 202 | | Atributo | Valor padrão | Descrição | 203 | | -------- | ------------ | --------- | 204 | | separadorArtigo | – | Separador do rótulo do artigo 1º ao 9º | 205 | | separadorArtigoSemOrdinal | – | Separador do rótulo do artigo 10 em diante | 206 | | separadorParagrafo | – | Separador do rótulo do parágrafo 1º ao 9º | 207 | | separadorParagrafoSemOrdinal | – | Separador do rótulo do parágrafo 10 em diante | 208 | | separadorParagrafoUnico | – | Separador do rótulo parágrafo único | 209 | | separadorInciso | – | Separador do rótulo de inciso | 210 | | separadorAlinea | ) | Separador do rótulo da alínea | 211 | | separadorItem | ) | Separador do rótulo do item | 212 | 213 | 214 | 215 | ### Opções de validação 216 | 217 | Todas as opções de validação são habilitadas (valor true) por padrão. 218 | 219 | | Atributo | Descrição | 220 | | -------- | --------- | 221 | | caixaAlta | Determina se deve validar o uso de caixa alta. | 222 | | citacao | Determina se deve validar o uso de aspas em citações. | 223 | | enumeracaoElementos | Determina se deve validar a presença de múltiplos elementos em uma enumeração. | 224 | | inicialMaiuscula | Determina se deve validar o uso de letra maiúscula no caput do artigo e em parágrafos. | 225 | | pontuacao | Determina se deve validar as pontuações. | 226 | | pontuacaoEnumeracao | Determina se deve validar pontuação de enumeração. | 227 | | sentencaUnica | Determina se deve exigir sentença única no dispositivo. | 228 | 229 | 230 | 231 | ## API do controlador 232 | 233 | | Propriedade/Função | Retorno/Valor | Descrição | 234 | | ------------------ | ------------- | --------- | 235 | | lexml *(propriedade)* | ElementNS | Obtém ou define o XML da articulação no formato LexML. | 236 | | lexmlString *(propriedade)* | String | Obtém ou define o XML da articulação no formato LexML, porém em String. | 237 | | alterado *(propriedade, somente leitura)* | Boolean | Verifica se o editor de articulação sofreu alteração. | 238 | | alterarTipoDispositivoSelecionado(novoTipo) | void | Altera o tipo do dispositivo em que o cursor se encontra, pelo novo tipo (String) fornecido como parâmetro. Os tipos possíveis são: titulo, capitulo, secao, subsecao, artigo, paragrafo, inciso, alinea e continuacao (todos sem acentuação ou cedilha). | 239 | | contexto | object | Obtém o contexto atual do editor | 240 | 241 | ### Eventos do controlador 242 | 243 | | Evento | Disparo | Condição | Classe do evento | Dados do evento | 244 | |--------|---------|----------|------------------|-----------------| 245 | | change | blur | Texto articulado alterado | ArticulacaoChangeEvent | N/A | 246 | | contexto | | Objeto de contexto atualizado | ContextoArticulacaoAtualizadoEvent | ContextoArticulacao | 247 | | transformacao | | Controlador aplicou alguma transformação automática | TransformacaoAutomaticaEvent | Objeto contendo os seguintes atributos: automatica (booleano), tipoAnterior (literal, tipo do elemento antes da alteração), novoTipo (literal, tipo do elemento depois da alteração), transformacao (literal, nome da transformacao), editorArticulacaoCtrl (controller) | 248 | 249 | 250 | 251 | 252 | ## API do interpretador 253 | 254 | Para interpretar um texto puro, transformando em um texto estruturado utilizando LexML, utilize a função interpretar (veja [código-fonte](src/interpretadorArticulacao.js)), com a seguinte sintaxe: 255 | 256 | ```javascript 257 | interpretadorArticulacao.interpretar(texto, formatoDestino, formatoOrigem); 258 | ``` 259 | 260 | onde ``texto`` é uma `string`, ``formatoDestino`` é uma das opções "json", "lexml" (padrão) ou "lexmlString" e ``formatoOrigem`` é "texto" (padrão) ou "html". 261 | 262 | Contribuições desejadas 263 | ----------------------- 264 | * Identificação de remissões; 265 | * Renumeração automática de remissões, em caso de alterações nos dispositivos; 266 | * Modo de edição de norma, em que alterações a um texto original são consideradas emendas. 267 | 268 | Limitações conhecidas (aceita-se contribuições) 269 | ----------------------------------------------- 270 | As limitações conhecidas correspondem a um conjunto de funcionalidade que não funciona como deveria, mas seu comportamento é aceitável para a proposta do editor. Contribuições são bem-vindas. 271 | 272 | * Copiar do editor de articulação e colar em editor externo omite os rótulos; 273 | * Interpretação de artigo com emenda (exemplo: Art. 283-A da Constituição do Estado de Minas Gerais), apesar de haver suporte para importação de LexML com este tipo de dispositivo. 274 | 275 | # Compatibilidade com navegadores 276 | 277 | | Navegador | Compatível | Mantida[1](#rodape1) | 278 | | ---------- |:----------:|:----------:| 279 | | Firefox 52 | ✓ | ✓ | 280 | | Firefox Quantum 57 | ✓ | | 281 | | Chrome 62 | ✓ | ✓ | 282 | | IE 11 | ✓ | | 283 | | Edge | ✓ | | 284 | | Safari | ? | | 285 | 286 | 1: Considera-se compatibilidade com navegador mantida aquela que é constantemente testada pela equipe de desenvolvimento. 287 | -------------------------------------------------------------------------------- /src/editor-articulacao.d.ts: -------------------------------------------------------------------------------- 1 | import ContextoArticulacao from './ContextoArticulacao'; 2 | import interpretadorArticulacao from './interpretadorArticulacao'; 3 | 4 | /** 5 | * Transforma um elemento do DOM em um editor de articulação com 6 | * barra de ferramentas. 7 | */ 8 | declare class ComponenteEdicao { 9 | /** 10 | * Cria um controlador do Editor de Articulação vinculado a um elemento do DOM. 11 | * 12 | * @param Elemento que receberá o editor de articulação. 13 | * @param opcoes Opções do editor de articulação. 14 | */ 15 | constructor(elemento: HTMLElement, opcoes?: IOpcoesEditorArticulacaoController); 16 | } 17 | 18 | declare class EditorArticulacaoController { 19 | /** 20 | * Cria um controlador do Editor de Articulação vinculado a um elemento do DOM. 21 | * 22 | * @param Elemento que receberá o editor de articulação. 23 | * @param opcoes Opções do editor de articulação. 24 | */ 25 | constructor(elemento: HTMLElement, opcoes?: IOpcoesEditorArticulacaoController); 26 | 27 | /** 28 | * Obtém o XML da articulação no formato LexML. 29 | */ 30 | get lexml(): Element | DocumentFragment; 31 | 32 | /** 33 | * Define o XML da articulação no formato LexML. 34 | */ 35 | set lexml(valor: Element | DocumentFragment); 36 | 37 | /** 38 | * Obtém o XML da articulação no formato LexML, porém em String. 39 | */ 40 | get lexmlString(): string; 41 | 42 | /** 43 | * Define o XML da articulação no formato LexML, porém em String. 44 | */ 45 | set lexmlString(valor: string); 46 | 47 | /** 48 | * Verifica se o editor de articulação sofreu alteração. 49 | */ 50 | get alterado(): boolean; 51 | 52 | /** 53 | * Altera o tipo do dispositivo em que o cursor se encontra, pelo novo tipo fornecido como parâmetro. 54 | * 55 | * @param novoTipo Novo tipo do dispositivo. 56 | */ 57 | alterarTipoDispositivoSelecionado(novoTipo: TipoDispositivo): void; 58 | 59 | get contexto(): ContextoArticulacao; 60 | } 61 | 62 | declare interface IOpcoesEditorArticulacaoController { 63 | /** 64 | * Determina se deve adotar o Shadow DOM, caso suportado pelo navegador. 65 | * (Padrão: false) 66 | */ 67 | shadowDOM?: boolean; 68 | 69 | /** 70 | * Determina se o editor de articulação deve aplicar transformação automática. 71 | * (Padrão: true) 72 | */ 73 | transformacaoAutomatica?: boolean; 74 | 75 | /** 76 | * Determina o escapamento de caracteres de código alto unicode durante 77 | * a exportação para lexmlString. 78 | * (Padrão: false) 79 | */ 80 | escaparXML?: boolean; 81 | 82 | /** 83 | * Determina o sufixo para os rótulos dos dispositivos. 84 | */ 85 | rotulo?: { 86 | /** 87 | * Separador do rótulo do artigo 1º ao 9º 88 | */ 89 | separadorArtigo?: string; 90 | 91 | /** 92 | * Separador do rótulo do artigo 10 em diante 93 | */ 94 | separadorArtigoSemOrdinal?: string; 95 | 96 | /** 97 | * Separador do rótulo do parágrafo 1º ao 9º 98 | */ 99 | separadorParagrafo?: string; 100 | 101 | /** 102 | * Separador do rótulo do parágrafo 10 em diante 103 | */ 104 | separadorParagrafoSemOrdinal?: string; 105 | 106 | /** 107 | * Separador do rótulo parágrafo único 108 | */ 109 | separadorParagrafoUnico?: string; 110 | 111 | /** 112 | * Separador do rótulo de inciso 113 | */ 114 | separadorInciso?: string; 115 | 116 | /** 117 | * Separador do rótulo da alínea 118 | */ 119 | separadorAlinea?: string; 120 | 121 | /** 122 | * Separador do rótulo do item 123 | */ 124 | separadorItem?: string; 125 | }; 126 | 127 | /** 128 | * Determina se deve validar o conteúdo atribuído ao componente. 129 | * (Padrão: true) 130 | */ 131 | validarAoAtribuir?: boolean; 132 | 133 | /** 134 | * Determina as validações que devem ocorrer. 135 | */ 136 | validacao?: { 137 | /** 138 | * Determina se deve validar o uso de caixa alta. 139 | */ 140 | caixaAlta?: boolean; 141 | 142 | /** 143 | * Determina se deve validar o uso de aspas em citações. 144 | */ 145 | citacao?: boolean; 146 | 147 | /** 148 | * Determina se deve validar a presença de múltiplos elementos em uma enumeração. 149 | */ 150 | enumeracaoElementos?: boolean; 151 | 152 | /** 153 | * Determina se deve validar o uso de letra maiúscula no caput do artigo e em parágrafos. 154 | */ 155 | inicialMaiuscula?: boolean; 156 | 157 | /** 158 | * Determina se deve validar as pontuações. 159 | */ 160 | pontuacao?: boolean; 161 | 162 | /** 163 | * Determina se deve validar pontuação de enumeração. 164 | */ 165 | pontuacaoEnumeracao?: boolean; 166 | 167 | /** 168 | * Determina se deve exigir sentença única no dispositivo. 169 | */ 170 | sentencaUnica?: boolean; 171 | } 172 | } 173 | 174 | declare enum TipoDispositivo { 175 | TITULO = 'titulo', 176 | CAPITULO = 'capitulo', 177 | SECAO = 'secao', 178 | SUBSECAO = 'subsecao', 179 | ARTIGO = 'artigo', 180 | PARAGRAFO = 'paragrafo', 181 | INCISO = 'inciso', 182 | ALINEA = 'alinea', 183 | CONTINUACAO = 'continuacao' 184 | } 185 | 186 | declare const interpretadorArticulacao = { 187 | Artigo, 188 | Paragrafo, 189 | Inciso, 190 | Alinea, 191 | Item, 192 | Titulo, 193 | Capitulo, 194 | Secao, 195 | Subsecao, 196 | transformarQuebrasDeLinhaEmP, 197 | interpretar 198 | } 199 | 200 | /** 201 | * Interpreta conteúdo de articulação. 202 | * 203 | * @param texto Texto a ser interpretado 204 | * @param formatoDestino Formato a ser retornado: 'json', 'lexml' (padrão) ou "lexmlString". 205 | * @param formatoOrigem Formatao a ser processado: 'texto' (padrão), 'html'. 206 | */ 207 | function interpretar(texto: string, formatoDestino: TFORMATO_DESTINO = 'lexml', formatoOrigem: TFORMATO_ORIGEM = 'texto'): IResultadoInterpretacao | Element | DocumentFragment | string; 208 | 209 | /** 210 | * Interpreta conteúdo de articulação. 211 | * 212 | * @param texto Texto a ser interpretado 213 | */ 214 | function interpretar(texto: string): Element | DocumentFragment; 215 | 216 | /** 217 | * Interpreta conteúdo de articulação. 218 | * 219 | * @param texto Texto a ser interpretado 220 | * @param formatoDestino Formato a ser retornado: 'json', 'lexml' (padrão) ou "lexmlString". 221 | * @param formatoOrigem Formatao a ser processado: 'texto' (padrão), 'html'. 222 | */ 223 | function interpretar(texto: string, formatoDestino: 'json', formatoOrigem: TFORMATO_ORIGEM = 'texto'): IResultadoInterpretacao; 224 | 225 | /** 226 | * Interpreta conteúdo de articulação. 227 | * 228 | * @param texto Texto a ser interpretado 229 | * @param formatoDestino Formato a ser retornado: 'json', 'lexml' (padrão) ou "lexmlString". 230 | * @param formatoOrigem Formatao a ser processado: 'texto' (padrão), 'html'. 231 | */ 232 | function interpretar(texto: string, formatoDestino: 'lexml', formatoOrigem: TFORMATO_ORIGEM = 'texto'): Element | DocumentFragment; 233 | 234 | /** 235 | * Interpreta conteúdo de articulação. 236 | * 237 | * @param texto Texto a ser interpretado 238 | * @param formatoDestino Formato a ser retornado: 'json', 'lexml' (padrão) ou "lexmlString". 239 | * @param formatoOrigem Formatao a ser processado: 'texto' (padrão), 'html'. 240 | */ 241 | function interpretar(texto: string, formatoDestino: 'lexmlString', formatoOrigem: TFORMATO_ORIGEM = 'texto'): string; 242 | 243 | declare interface IResultadoInterpretacao { 244 | textoAnterior: string; 245 | articulacao: Dispositivo[]; 246 | } 247 | 248 | declare type TFORMATO_DESTINO = 'json' | 'lexml' | 'lexmlString'; 249 | 250 | declare type TFORMATO_ORIGEM = 'texto' | 'html'; 251 | 252 | declare class Dispositivo { 253 | constructor(tipo: string, numero: string, descricao: string, derivacoes: string[]); 254 | 255 | adicionar(dispositivo: Dispositivo); 256 | 257 | /** 258 | * Transforma o conteúdo na descrição em fragmento do DOM. 259 | */ 260 | transformarConteudoEmFragmento(): DocumentFragment; 261 | 262 | /** 263 | * Transforma o dispositivo no formato do editor. 264 | */ 265 | paraEditor(): DocumentFragment; 266 | } 267 | 268 | declare class Artigo extends Dispositivo { 269 | constructor(numero: string, caput: string); 270 | 271 | adicionar(incisoOuParagrafo: Inciso | Paragrafo); 272 | } 273 | 274 | declare class Paragrafo extends Dispositivo { 275 | constructor(numero: string, descricao: string); 276 | 277 | adicionar(inciso: Inciso); 278 | } 279 | 280 | declare class Inciso extends Dispositivo { 281 | constructor(numero: string, descricao: string); 282 | 283 | adicionar(alinea: Alinea); 284 | } 285 | 286 | declare class Alinea extends Dispositivo { 287 | constructor(numero: string, descricao: string); 288 | 289 | adicionar(item: Item); 290 | } 291 | 292 | declare class Item extends Dispositivo { 293 | constructor(numero: string, descricao: string); 294 | } 295 | 296 | declare class Divisao extends Dispositivo { 297 | constructor(tipo: string, numero: string, descricao: string); 298 | 299 | adicionar(item); 300 | } 301 | 302 | declare class Titulo extends Divisao { 303 | constructor(numero: string, descricao: string); 304 | } 305 | 306 | declare class Capitulo extends Divisao { 307 | constructor(numero: string, descricao: string); 308 | } 309 | 310 | declare class Secao extends Divisao { 311 | constructor(numero: string, descricao: string); 312 | } 313 | 314 | declare class Subsecao extends Divisao { 315 | constructor(numero: string, descricao: string); 316 | } 317 | 318 | /** 319 | * Representa o contexto do usuário no editor de articulação. 320 | * Possui dois atributos: cursor, contendo o contexto no cursor, 321 | * e as permissões de alteração de dispositivo na seleção. 322 | */ 323 | declare class ContextoArticulacao { 324 | constructor(elementoArticulacao: HTMLElement, dispositivo: Dispositivo); 325 | 326 | comparar(obj2: ContextoArticulacao): boolean { 327 | for (let i in this.cursor) { 328 | if (this.cursor[i] !== obj2.cursor[i]) { 329 | return true; 330 | } 331 | } 332 | 333 | for (let i in this.permissoes) { 334 | if (this.permissoes[i] !== obj2.permissoes[i]) { 335 | return true; 336 | } 337 | } 338 | 339 | return false; 340 | } 341 | 342 | /** 343 | * Determina se o contexto atual é válido. 344 | * 345 | * @returns {Boolean} Verdadeiro, se o contexto estiver válido. 346 | */ 347 | get valido(): boolean; 348 | 349 | /** 350 | * Verifica quais tipos de dispositivos são permitidos no contexto atual. 351 | */ 352 | readonly permissoes: { 353 | readonly titulo: boolean; 354 | readonly capitulo: boolean; 355 | readonly secao: boolean; 356 | readonly subsecao: boolean; 357 | readonly artigo: boolean; 358 | readonly continuacao: boolean; 359 | readonly inciso: boolean; 360 | readonly paragrafo: boolean; 361 | readonly alinea: boolean; 362 | readonly item: boolean; 363 | } 364 | 365 | /** 366 | * Informações sobre o contexto onde está o cursor. 367 | */ 368 | readonly cursor: { 369 | /** 370 | * Indica se o cursor está em um trecho com itálico. 371 | */ 372 | readonly italico: boolean; 373 | 374 | /** 375 | * Indica se o cursor está em um elemento desconhecido. 376 | */ 377 | readonly desconhecido: boolean; 378 | 379 | /** 380 | * Indica se o cursor está em um título. 381 | */ 382 | readonly titulo: boolean; 383 | 384 | /** 385 | * Indica se o cursor está em um capítulo. 386 | */ 387 | readonly capitulo: boolean; 388 | 389 | /** 390 | * Indica se o cursor está em uma seção. 391 | */ 392 | readonly secao: boolean; 393 | 394 | /** 395 | * Indica se o cursor está em uma subseção. 396 | */ 397 | readonly subsecao: boolean; 398 | 399 | /** 400 | * Indica se o cursor está em um artigo. 401 | */ 402 | readonly artigo: boolean; 403 | 404 | /** 405 | * Indica se o cursor está em uma continuação (outra linha) 406 | * do dispositivo. 407 | */ 408 | readonly continuacao: boolean; 409 | 410 | /** 411 | * Indica se o cursor está em um parágrafo. 412 | */ 413 | readonly paragrafo: boolean; 414 | 415 | /** 416 | * Indica se o cursor está em uma alínea. 417 | */ 418 | readonly inciso: boolean; 419 | 420 | /** 421 | * Indica se o cursor está em uma alínea. 422 | */ 423 | readonly alinea: boolean; 424 | 425 | /** 426 | * Indica se o cursor está em um item. 427 | */ 428 | readonly item: boolean; 429 | 430 | /** 431 | * Indica se o cursor está na raíz do editor. 432 | */ 433 | readonly raiz: boolean; 434 | 435 | /** 436 | * Elemento do DOM do dispositivo atual. 437 | */ 438 | readonly elemento: HTMLElement; 439 | 440 | /** 441 | * Tipo do dispositivo atual. 442 | */ 443 | readonly tipo: TipoDispositivo; 444 | 445 | /** 446 | * Dispositivo atual. 447 | */ 448 | readonly dispositivo: Dispositivo; 449 | 450 | /** 451 | * Instância do dispositivo anterior. 452 | */ 453 | readonly dispositivoAnterior: Dispositivo; 454 | 455 | /** 456 | * Tipo do dispositivo anterior. 457 | */ 458 | readonly tipoAnterior: TipoDispositivo; 459 | } 460 | } 461 | 462 | export = { 463 | ComponenteEdicao, 464 | EditorArticulacaoController, 465 | interpretadorArticulacao 466 | } -------------------------------------------------------------------------------- /test/protractor/formatacaoDispositivos.spec.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Assembleia Legislativa de Minas Gerais 2 | * 3 | * This file is part of Editor-Articulacao. 4 | * 5 | * Editor-Articulacao is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, version 3. 8 | * 9 | * Editor-Articulacao is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Lesser General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with Editor-Articulacao. If not, see . 16 | */ 17 | 18 | const { $describe, escrever } = require('./utilitariosTeste'); 19 | 20 | $describe('Formatação do editor de articulação', function(it) { 21 | var editor = element(by.id('articulacao')); 22 | var resultado = element(by.id('lexml')); 23 | 24 | function finalizar() { 25 | browser.actions() 26 | .mouseMove(element(by.id('atualizar'))) 27 | .click() 28 | .perform(); 29 | } 30 | 31 | it('Vários artigos', function() { 32 | escrever('Primeiro.' + protractor.Key.ENTER, true); 33 | escrever('Segundo.' + protractor.Key.ENTER, true); 34 | escrever('Terceiro.' + protractor.Key.ENTER, true); 35 | escrever('Quarto.' + protractor.Key.ENTER, true); 36 | escrever('Quinto.' + protractor.Key.ENTER, true); 37 | escrever('Sexto.' + protractor.Key.ENTER, true); 38 | escrever('Sétimo.' + protractor.Key.ENTER, true); 39 | escrever('Oitavo.' + protractor.Key.ENTER, true); 40 | escrever('Nono.' + protractor.Key.ENTER, true); 41 | escrever('Dez.'); 42 | 43 | finalizar(); 44 | 45 | expect(resultado.getText()).toEqual('Art. 1º –

Primeiro.

Art. 2º –

Segundo.

Art. 3º –

Terceiro.

Art. 4º –

Quarto.

Art. 5º –

Quinto.

Art. 6º –

Sexto.

Art. 7º –

Sétimo.

Art. 8º –

Oitavo.

Art. 9º –

Nono.

Art. 10 –

Dez.

'); 46 | }); 47 | 48 | it('Ao digitar dois pontos, deve avançar o nível e recuar ao dar enter em linha vazia', function() { 49 | escrever('Este é um artigo:' + protractor.Key.ENTER); 50 | escrever('Este é um inciso:' + protractor.Key.ENTER); 51 | escrever('Esta é uma alínea:' + protractor.Key.ENTER); 52 | escrever('Este é um item.'); 53 | escrever(protractor.Key.ENTER); 54 | escrever(protractor.Key.ENTER); 55 | escrever(protractor.Key.ENTER); 56 | escrever('Segundo artigo.'); 57 | 58 | finalizar(); 59 | 60 | expect(resultado.getText()).toEqual('Art. 1º –

Este é um artigo:

I –

Este é um inciso:

a)

Esta é uma alínea:

1)

Este é um item.

Art. 2º –

Segundo artigo.

'); 61 | }); 62 | 63 | it('Deve-se sair do inciso automaticamente quando for terminado com ponto final', function() { 64 | escrever('Este é um artigo:' + protractor.Key.ENTER); 65 | escrever('este é um inciso;' + protractor.Key.ENTER); 66 | escrever('este é outro inciso.' + protractor.Key.ENTER); 67 | escrever('Este é outro artigo.'); 68 | 69 | finalizar(); 70 | 71 | expect(resultado.getText()).toEqual('Art. 1º –

Este é um artigo:

I –

este é um inciso;

II –

este é outro inciso.

Art. 2º –

Este é outro artigo.

'); 72 | }); 73 | 74 | it('Quando houver apenas um parágrafo, deve formatar como parágrafo único', function() { 75 | escrever('Este é um artigo.' + protractor.Key.ENTER); 76 | escrever('Este é um parágrafo.'); 77 | 78 | browser.actions() 79 | .mouseMove(element(by.css('button[data-tipo-destino="paragrafo"]'))) 80 | .click() 81 | .perform(); 82 | 83 | finalizar(); 84 | 85 | expect(resultado.getText()).toEqual('Art. 1º –

Este é um artigo.

Parágrafo único –

Este é um parágrafo.

'); 86 | }); 87 | 88 | it('Quando houver dois parágrafos, a formatação deve ser numérica', function() { 89 | escrever('Este é um artigo.' + protractor.Key.ENTER); 90 | 91 | browser.actions() 92 | .mouseMove(element(by.css('button[data-tipo-destino="paragrafo"]'))) 93 | .click() 94 | .perform(); 95 | 96 | browser.sleep(250); 97 | 98 | escrever('Este é um parágrafo.' + protractor.Key.ENTER); 99 | escrever('Este é outro parágrafo.'); 100 | 101 | finalizar(); 102 | 103 | expect(resultado.getText()).toEqual('Art. 1º –

Este é um artigo.

§ 1º –

Este é um parágrafo.

§ 2º –

Este é outro parágrafo.

'); 104 | }); 105 | 106 | it('Incisos podem estar no caput e em parágrafos', function() { 107 | escrever('Este é um artigo:' + protractor.Key.ENTER); 108 | escrever('Este é um inciso.' + protractor.Key.ENTER); 109 | 110 | browser.actions() 111 | .mouseMove(element(by.css('button[data-tipo-destino="paragrafo"]'))) 112 | .click() 113 | .perform(); 114 | 115 | browser.sleep(250); 116 | 117 | escrever('Este é um parágrafo:' + protractor.Key.ENTER); 118 | escrever('Este é o inciso do parágrafo.' + protractor.Key.ENTER); 119 | escrever('Este é outro parágrafo formatado automaticamente.'); 120 | 121 | finalizar(); 122 | 123 | expect(resultado.getText()).toEqual('Art. 1º –

Este é um artigo:

I –

Este é um inciso.

§ 1º –

Este é um parágrafo:

I –

Este é o inciso do parágrafo.

§ 2º –

Este é outro parágrafo formatado automaticamente.

'); 124 | }); 125 | 126 | it('Remoção de parágrafo deve mover inciso para caput.', function() { 127 | escrever('Este é um artigo:' + protractor.Key.ENTER); 128 | escrever('este é um inciso;' + protractor.Key.ENTER); 129 | 130 | browser.actions() 131 | .mouseMove(element(by.css('button[data-tipo-destino="paragrafo"]'))) 132 | .click() 133 | .perform(); 134 | 135 | browser.sleep(250); 136 | 137 | escrever('Este é um parágrafo:' + protractor.Key.ENTER); 138 | escrever('este é o inciso do parágrafo.'); 139 | 140 | for (let i = 'este é o inciso do parágrafo.'.length; i >= 0; i--) { 141 | escrever(protractor.Key.LEFT, true); 142 | } 143 | 144 | browser.actions().keyDown(protractor.Key.SHIFT) 145 | .sendKeys(protractor.Key.HOME) 146 | .keyUp(protractor.Key.SHIFT) 147 | .perform(); 148 | 149 | browser.sleep(250); 150 | 151 | browser.actions().sendKeys(protractor.Key.BACK_SPACE).sendKeys(protractor.Key.BACK_SPACE).perform(); 152 | 153 | browser.sleep(250) 154 | 155 | finalizar(); 156 | 157 | expect(resultado.getText()).toEqual('Art. 1º –

Este é um artigo:

I –

este é um inciso;

II –

este é o inciso do parágrafo.

'); 158 | }); 159 | 160 | it('Deve ser possível escrever citação com única linha', function() { 161 | escrever('Estou testando:' + protractor.Key.ENTER); 162 | escrever('"Esta é a única linha da citação.".' + protractor.Key.ENTER); 163 | escrever('Segundo artigo.'); 164 | 165 | finalizar(); 166 | 167 | expect(resultado.getText()).toEqual('Art. 1º –

Estou testando:

"Esta é a única linha da citação.".

Art. 2º –

Segundo artigo.

'); 168 | }); 169 | 170 | it('Deve ser possível escrever citação com dois parágrafos', function() { 171 | escrever('Estou testando:' + protractor.Key.ENTER); 172 | escrever('"Esta é a primeira linha da citação.' + protractor.Key.ENTER); 173 | escrever('Esta é a segunda linha da citação.".' + protractor.Key.ENTER); 174 | escrever('Segundo artigo.'); 175 | 176 | finalizar(); 177 | 178 | expect(resultado.getText()).toEqual('Art. 1º –

Estou testando:

"Esta é a primeira linha da citação.

Esta é a segunda linha da citação.".

Art. 2º –

Segundo artigo.

'); 179 | }); 180 | 181 | it('Deve ser possível escrever citação com três parágrafos', function() { 182 | escrever('Primeiro artigo:' + protractor.Key.ENTER); 183 | escrever('"Primeira linha.' + protractor.Key.ENTER); 184 | escrever('Segunda linha.' + protractor.Key.ENTER); 185 | escrever('Terceira linha.".' + protractor.Key.ENTER); 186 | escrever('Segundo artigo.'); 187 | 188 | finalizar(); 189 | 190 | expect(resultado.getText()).toEqual('Art. 1º –

Primeiro artigo:

"Primeira linha.

Segunda linha.

Terceira linha.".

Art. 2º –

Segundo artigo.

'); 191 | }); 192 | 193 | it('Deve ser possível quebrar linha dentro da citação', function() { 194 | escrever('Primeiro artigo:' + protractor.Key.ENTER); 195 | escrever('"Primeira linha com segunda linha.' + protractor.Key.ENTER); 196 | escrever('Terceira linha.".' + protractor.Key.ENTER); 197 | escrever('Segundo artigo.'); 198 | 199 | for (let i = 'segunda linha. Terceira linha.".Segundo artigo.'.length; i >= 0; i--) { 200 | escrever(protractor.Key.LEFT, true); 201 | } 202 | 203 | for (let i = ' com '.length; i > 0; i--) { 204 | escrever(protractor.Key.BACK_SPACE, true); 205 | } 206 | 207 | escrever('.' + protractor.Key.ENTER); 208 | escrever('S' + protractor.Key.DELETE); 209 | 210 | finalizar(); 211 | 212 | expect(resultado.getText()).toEqual('Art. 1º –

Primeiro artigo:

"Primeira linha.

Segunda linha.

Terceira linha.".

Art. 2º –

Segundo artigo.

'); 213 | }); 214 | 215 | it('Botões contextuais devem alternar formatação', function() { 216 | escrever('Artigo.' + protractor.Key.ENTER); 217 | 218 | browser.actions() 219 | .mouseMove(element(by.css('button[data-tipo-destino="paragrafo"]'))) 220 | .click() 221 | .perform(); 222 | 223 | browser.sleep(250); 224 | 225 | escrever('Parágrafo:' + protractor.Key.ENTER); 226 | escrever('Artigo.'); 227 | 228 | browser.actions() 229 | .mouseMove(element(by.css('button[data-tipo-destino="artigo"]'))) 230 | .click() 231 | .perform(); 232 | 233 | browser.sleep(250); 234 | 235 | finalizar(); 236 | 237 | expect(resultado.getText()).toEqual('Art. 1º –

Artigo.

Parágrafo único –

Parágrafo:

Art. 2º –

Artigo.

'); 238 | }); 239 | 240 | it('"Desfazer" não pode violar formatação', function() { 241 | escrever('Teste.' + protractor.Key.HOME + protractor.Key.ENTER); 242 | browser.actions() 243 | .mouseMove(element(by.css('button[data-tipo-destino="continuacao"]'))) 244 | .click() 245 | .perform(); 246 | escrever(protractor.Key.chord(protractor.Key.CONTROL, 'z')); 247 | finalizar(); 248 | expect(resultado.getText()).toEqual('Art. 1º –

Teste.

'); 249 | }); 250 | 251 | }); --------------------------------------------------------------------------------