From f387fece31913efd9ce6449a4e02c0769427df31 Mon Sep 17 00:00:00 2001 From: Alberto Sanchez Gonzalez Date: Wed, 26 Apr 2023 12:11:38 +0200 Subject: [PATCH 1/9] feat: add brazilian portuguese language --- locales/pt-BR/messages.po | 1683 +++++++++++++++++ locales/pt-BR/messages_po.js | 1 + locales/pt-BR/qtiCat.rdf.po | 29 + locales/pt-BR/qtitest.rdf.po | 89 + locales/pt-BR/taoQtiTestItemRunner.rdf.po | 44 + views/js/loader/taoQtiTest.min.js.map | 2 +- views/js/loader/taoQtiTestRunner.min.js.map | 2 +- .../js/loader/taoQtiTestXMLEditor.min.js.map | 2 +- 8 files changed, 1849 insertions(+), 3 deletions(-) create mode 100644 locales/pt-BR/messages.po create mode 100644 locales/pt-BR/messages_po.js create mode 100644 locales/pt-BR/qtiCat.rdf.po create mode 100644 locales/pt-BR/qtitest.rdf.po create mode 100644 locales/pt-BR/taoQtiTestItemRunner.rdf.po diff --git a/locales/pt-BR/messages.po b/locales/pt-BR/messages.po new file mode 100644 index 0000000000..4352a2f347 --- /dev/null +++ b/locales/pt-BR/messages.po @@ -0,0 +1,1683 @@ +msgid "" +msgstr "" +"Project-Id-Version: tao-3x\n" +"PO-Revision-Date: 2023-04-21 15:46\n" +"Last-Translator: TAO Translation Team \n" +"MIME-Version: 1.0\n" +"Language: pt\n" +"sourceLanguage: en-US\n" +"targetLanguage: en-US\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Basepath: ../../\n" +"X-Poedit-KeywordsList: __\n" +"X-Poedit-SearchPath-0: .\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Crowdin-Project: tao-3x\n" +"X-Crowdin-Project-ID: 582145\n" +"X-Crowdin-Language: pt-BR\n" +"X-Crowdin-File: /taoQtiTest/locales/en-US/messages.po\n" +"X-Crowdin-File-ID: 1723\n" +"Language-Team: Portuguese, Brazilian\n" + +msgid "%s to answer %s" +msgstr "%s para responder %s" + +msgid "(Visual) Clear all active highlights" +msgstr "(Visual) Limpe todos os destaques ativos" + +msgid "(Visual) Highlight Text" +msgstr "(Visual) Destacar Texto" + +msgid "A QTI Item involved in the QTI Test '%s' could not be compiled." +msgstr "Um item QTI envolvido no teste QTI '%s' não pode ser compilado." + +msgid "A QTI Test must contain at least one QTI Item to be compiled. None found." +msgstr "Um teste QTI deve conter pelo menos um item QTI para ser compilado. Nenhum encontrado." + +msgid "A qti testpackage" +msgstr "Pacote de teste qti" + +msgid "A remote resource referenced in QTI test '%s' could not be retrieved." +msgstr "Um recurso remoto referenciado no teste QTI '%s' não pôde ser recuperado." + +msgid "A response to every question in this item is required." +msgstr "É necessária uma resposta a cada pergunta nesse item." + +msgid "A response to this item is required." +msgstr "É necessária uma resposta para este item." + +msgid "A valid response to this item is required." +msgstr "É necessária uma resposta válida para este item." + +msgid "A visible section is one that is identifiable by the candidate." +msgstr "Uma secção visível é identificável pelo candidato." + +msgid "Activated" +msgstr "Ativado" + +msgid "add a new weight" +msgstr "crie uma nova carga" + +msgid "Add a zip file containing QTI/APIP tests and items" +msgstr "Adicionar um arquivo zip contendo testes e itens QTI/APIP" + +msgid "Add selected item(s) here." +msgstr "Adicionar itens selecionados aqui." + +msgid "Add the source file" +msgstr "Adicionar o arquivo fonte" + +msgid "After you complete the section it would be impossible to return to this section to make changes. Are you sure you want to end the section?" +msgstr "Após completar a seção, será impossível retornar a esta seção para fazer alterações. Você tem certeza que quer completar a seção?" + +msgid "All" +msgstr "Todos" + +msgid "Allow Comment" +msgstr "Permitir Comentários" + +msgid "Allow Review" +msgstr "Permitir Revisão" + +msgid "Allow Section Skipping" +msgstr "Permitir pular seção" + +msgid "Allow Skipping" +msgstr "Permitir Pular" + +msgid "Allow skipping of the current section." +msgstr "Permitir pular a seção atual." + +msgid "Allow Test-taker to playback media files associated according to APIP protocol to item content." +msgstr "Permitir ao candidato reproduzir arquivos de mídia associados com o protocolo APIP para o conteúdo do item." + +msgid "Allow the candidate to review his answers." +msgstr "Permitir ao candidato analisar suas respostas." + +msgid "Allow the test-taker to eliminate / strikethrough answers in choice interactions." +msgstr "Permitir que o candidato elimine / busca em respostas na escolha de interações." + +msgid "Allow the test-taker to highlight parts of the item text." +msgstr "Permitir que o candidato destaque de partes do texto do item." + +msgid "Allow the test-taker to mask and unmask answers in choice interactions." +msgstr "Permitir que o candidato esconda as respostas em interações de escolha." + +msgid "Allow the test-taker to mask parts of the item with a movable mask." +msgstr "Permitir que o candidato esconda partes do item com uma máscara móvel." + +msgid "Allow the test-taker to use a basic calculator." +msgstr "Permitir que o candidato use uma calculadora básica." + +msgid "Allow the test-taker to use a calculator respecting the order of operations (BODMAS)." +msgstr "Permite que o candidato use uma calculadora respeitando a ordem de operações (BODMAS)." + +msgid "Allow the test-taker to use a movable magnifier tool." +msgstr "Permite que o candidato use uma ferramenta de lupa móvel." + +msgid "Allow the test-taker to use a scientific calculator." +msgstr "Permitir que o candidato use uma calculadora científica." + +msgid "Allow the test-taker to visually isolate a line of text." +msgstr "Permitir que o candidato isole visualmente uma linha de texto." + +msgid "Allows Test-taker to zoom in and out the item content." +msgstr "Permite que o candidato aumente e diminua o conteúdo do item." + +msgid "Also compute the score per categories" +msgstr "Também calcular a pontuação por categorias" + +msgid "An error occured while retrieving items" +msgstr "Ocorreu um erro durante a recuperação dos itens" + +msgid "An error occurred during results submission. Please retry." +msgstr "Ocorreu um erro durante o envio dos resultados. Por favor, tente novamente." + +msgid "An error occurred during the test initialization!" +msgstr "Ocorreu um erro durante a inicialização do teste!" + +msgid "An error occurred during the test, please content your administrator." +msgstr "Ocorreu um erro durante o teste, por favor, conteúdo o administrador." + +msgid "An error occurred!" +msgstr "Ocorreu um erro!" + +msgid "An error occurs, please contact your administrator" +msgstr "Ocorreu um erro. Por favor, informe o administrador" + +msgid "An invisible section with a parent that is subject to shuffling can specify whether or not its children, which will appear to the candidate as if they were part of the parent, are shuffled as a block or mixed up with the other children of the parent section." +msgstr "Uma seção invisível com um pai que está sujeito a embaralhar pode especificar se seus filhos ou não, que aparecerão para o candidato como se eles fossem parte do pai, são misturados como um bloco ou misturados com as outras crianças da seção principal." + +msgid "An item involved in the test cannot be read or is not QTI compliant." +msgstr "Um item envolvido no teste não pode ser lido ou não é compatível com QTI." + +msgid "An item involved in the test cannot be written." +msgstr "Um item envolvido no teste não pode ser escrito." + +msgid "An unknown error occured while compiled QTI Test '%s'." +msgstr "Ocorreu um erro desconhecido ao compilar QTI Test '%s'." + +msgid "An unknown error occured while compiling QTI test '%s'." +msgstr "Ocorreu um erro desconhecido ao compilar o teste QTI '%s'." + +msgid "An unrecoverable error occurred. Your test session will be paused." +msgstr "Ocorreu um erro irrecuperável. Sua sessão de teste será pausada." + +msgid "and" +msgstr "e" + +msgid "and flagged %s of them" +msgstr "e sinalizou %s deles" + +msgid "and you flagged %s item(s) that you can review now" +msgstr "você sinalizou %s item(s) que você pode revisar agora" + +msgid "Answer Eliminator" +msgstr "Eliminador de Resposta" + +msgid "Answer Masking" +msgstr "Mascarar Respostas" + +msgid "Answered" +msgstr "Respondido" + +msgid "APIP Text To Speech" +msgstr "APIP Texto para Fala" + +msgid "Are you sure you want to clear your answer and go to the next item? You will not be able to go back and provide an answer." +msgstr "Tem certeza que deseja limpar a sua resposta e ir para o próximo item? Não poderá voltar atrás e dar uma resposta." + +msgid "Are you sure you want to end the test?" +msgstr "Tem certeza que deseja finalizar o teste?" + +msgid "Are you sure you want to go to the next item? You will not be able to go back and change your answer." +msgstr "Tem certeza de que deseja ir para o próximo item? Você não poderá voltar nem mudar sua resposta." + +msgid "Are you sure you want to go to the next item? You will not be able to go back and provide an answer." +msgstr "Tem certeza de que deseja ir para o próximo item? Você não poderá voltar para dar uma resposta." + +msgid "Are you sure you want to go to the next item? You will not be able to go back." +msgstr "Tem certeza de que deseja ir para o próximo item? Não poderá voltar." + +msgid "Area Masking" +msgstr "Mascarar Respostas" + +msgid "Associate a blueprint to a section allow you to validate this section against the specified blueprint." +msgstr "Associe um diagrama a uma seção permite que você valide essa seção para o modelo especificado." + +msgid "Associate Blueprint" +msgstr "Diagrama associado" + +msgid "At least one mandatory parameter was required but found missing in your request" +msgstr "Pelo menos um parâmetro obrigatório era requerido, mas encontrado faltando em seu pedido" + +msgid "Author" +msgstr "Autor" + +msgid "Auxiliary file not found at location \"%s\"." +msgstr "Arquivo auxiliar não encontrado na localização \"%s\"." + +msgid "Black on Cream" +msgstr "Preto em Creme" + +msgid "Black on Light Blue" +msgstr "Preto em Azul Claro" + +msgid "Black on Light Magenta" +msgstr "Preto em Magenta Claro" + +msgid "Black on White" +msgstr "Preto em Branco" + +msgid "Calculator" +msgstr "Calculadora" + +msgid "Calculator BODMAS" +msgstr "Calculadora BODMAS" + +msgid "Can not playback media file!" +msgstr "Não é possível reproduzir o arquivo de mídia!" + +msgid "CANCEL" +msgstr "CANCELAR" + +msgid "Cancel" +msgstr "Cancelar" + +msgid "Candidate" +msgstr "Candidato" + +msgid "Cardinality" +msgstr "Cardinalidade" + +msgid "Categories" +msgstr "Categorias" + +msgid "Category score" +msgstr "Pontuação da categoria" + +msgid "Change the current color preset" +msgstr "Alterar a predefinição de cor atual" + +msgid "Class" +msgstr "Classe" + +msgid "Class successfully created." +msgstr "Classe criada com sucesso." + +msgid "Clear all active highlights" +msgstr "Limpe todos os destaques ativos" + +msgid "Clear Highlights" +msgstr "Limpar Destaques" + +msgid "Click \"%s\" to continue" +msgstr "Clique \"%s\" para continuar" + +msgid "Click to speak" +msgstr "Clique para falar" + +msgid "Close" +msgstr "Fechar" + +msgid "Close Magnifier" +msgstr "Fechar Lupa" + +msgid "Close the mask" +msgstr "Fechar máscara" + +msgid "Close this Section" +msgstr "Fechar esta seção" + +msgid "Collapse the review panel" +msgstr "Fechar painel de revisão" + +msgid "Comment" +msgstr "Comentar" + +msgid "Completed items" +msgstr "Itens concluídos" + +msgid "Connected to server" +msgstr "Conectado ao Servidor" + +msgid "CONTINUE TO THE NEXT SECTION" +msgstr "CONTINUAR PARA A PRÓXIMA SEÇÃO" + +msgid "Contrast" +msgstr "Contraste" + +msgid "Controls the contribution of an individual item score to the overall test score." +msgstr "Controla a contribuição de uma pontuação individual de item para a pontuação geral do teste." + +msgid "Controls the maximum number of attempts allowed. 0 means unlimited." +msgstr "Controla o número máximo de tentativas permitidas. 0 significa ilimitado." + +msgid "Covers parts of the item" +msgstr "Cobre partes do item" + +msgid "Custom" +msgstr "Personalizado" + +msgid "custom categories" +msgstr "personalizar categorias" + +msgid "Custom outcome processing. No changes will be made to the existing rules." +msgstr "Processamento de resultado personalizado. Nenhuma alteração será feita nas regras existentes." + +msgid "Cut score" +msgstr "Pontuação de corte" + +msgid "Cut score (pass ratio)" +msgstr "Pontuação de corte (taxa de passo)" + +msgid "Disable validation" +msgstr "Desativar validação" + +msgid "Disconnected from server" +msgstr "Desconectado do Servidor" + +msgid "Display a warning before the test-taker ends a test part and there are still items left unanswered or marked for review." +msgstr "Exibir um aviso antes que o candidato termine uma parte do teste, e ainda há itens que não foram respondidos ou marcados para revisão." + +msgid "Display a warning before the test-taker ends the test part." +msgstr "Exibir um aviso antes que o test-taker termine a parte de teste." + +msgid "Display a warning before the test-taker ends the test." +msgstr "Exibir um aviso antes que o candidato termine a parte de teste." + +msgid "Display a warning before the test-taker skips the section. Requires the Section Skipping option." +msgstr "Exibe um aviso antes que o candidato pule a seção. Requer a opção Pular Seção." + +msgid "Display End Test Warning" +msgstr "Exibir Alerta de Final de Teste" + +msgid "Display Next Part Warning" +msgstr "Exibir Aviso de Próxima Parte" + +msgid "Display Next Section Warning" +msgstr "Exibir Aviso de Próxima Parte" + +msgid "Display Unanswered Warning" +msgstr "Exibir Aviso de Item Não Respondido" + +msgid "Displayed label" +msgstr "Mostrar label" + +msgid "Displays a customisable magnifier" +msgstr "Exibe uma lupa customizável" + +msgid "Do not flag the current item for later review" +msgstr "Não sinalizar o item atual para posterior revisão" + +msgid "Do not rename it." +msgstr "Não renomear." + +msgid "Do not show alert on timeout" +msgstr "Não exibir alerta no tempo limite" + +msgid "Don\\'t save" +msgstr "Não salvar" + +msgid "Download" +msgstr "Baixar" + +msgid "Download the pupil responses and submit manually." +msgstr "Baixar as respostas para os alunos e enviar manualmente." + +msgid "Eliminate choices" +msgstr "Eliminar opções" + +msgid "Enable Mark for Review" +msgstr "Habilitar para revisão" + +msgid "Enable mark for review of items. Requires the Review Screen option." +msgstr "Habilitar marca para revisão dos itens. Requer a opção da tela de revisão." + +msgid "Enable Review Screen" +msgstr "Ativar a tela de revisão" + +msgid "Enable selection" +msgstr "Habilitar seleção" + +msgid "Enable Skipping Ahead" +msgstr "Habilitar pular para frente" + +msgid "Enable text to speech" +msgstr "Habilitar síntese de voz" + +msgid "Enable the item review screen / navigator." +msgstr "Habilite a tela de revisão do item / navegador." + +msgid "Enables skipping to items within this section. Requires the review screen option." +msgstr "Permite pular para outros itens nesta seção. Requer a opção da tela de revisão." + +msgid "End test" +msgstr "Finalizar teste" + +msgid "End Test" +msgstr "Finalizar teste" + +msgid "Enter a blueprint" +msgstr "Inserir um diagrama" + +msgid "Enter a custom category" +msgstr "Inserir uma categoria personalizada" + +msgid "Error" +msgstr "Erro" + +msgid "Error during file import" +msgstr "Erro durante importação do arquivo" + +msgid "Error found in the IMS QTI Test:\n" +"%s" +msgstr "Erro encontrado no teste IMS QTI\n" +"%s" + +msgid "Expand the review panel" +msgstr "Abrir o Painel de Revisão" + +msgid "Export failed for the test \"%s" +msgstr "Exportação falhou para o teste \"%s" + +msgid "Export metadata" +msgstr "Exportar metadados" + +msgid "Export QTI 2.1 Test Package" +msgstr "Exportar Pacote de Teste QTI 2.1" + +msgid "Export QTI 2.2 Test Package" +msgstr "Exportar Pacote de Teste QTI 2.2" + +msgid "Export successful for the test \"%s" +msgstr "Sucesso na exportação do teste \"%s" + +msgid "Feedback block" +msgstr "Bloco de Feedback" + +msgid "File name" +msgstr "Nome do Arquivo" + +msgid "File not found" +msgstr "Arquivo não encontrado" + +msgid "File not found!" +msgstr "Arquivo não encontrado!" + +msgid "Filter" +msgstr "Filtro" + +msgid "Fixed" +msgstr "Afixada" + +msgid "Fixed position in a shuffled the selection." +msgstr "Posição fixa em uma seleção embaralhada." + +msgid "Flag for Review" +msgstr "Sinalizar para revisão" + +msgid "Flag the current item for later review" +msgstr "Sinalizar o item atual para revisão posterior" + +msgid "Flagged" +msgstr "Marcado" + +msgid "Flagged for review" +msgstr "Marcado para revisão" + +msgid "Force the item to be considered as informational and not taken into account in (un)answered / flagged counters." +msgstr "Forçar o item a ser considerado como informativo e não considerado nos contadores (não )respondidos/sinalizados." + +msgid "Go to next item" +msgstr "Ir para o próximo item" + +msgid "Go to the current question" +msgstr "Ir para a questão atual" + +msgid "Go to the next item?" +msgstr "Ir para o próximo item?" + +msgid "Go to the next question" +msgstr "Ir para a próxima questão" + +msgid "Go to the previous question" +msgstr "Ir para a questão anterior" + +msgid "Go to the top of the page" +msgstr "Ir para o topo da página" + +msgid "Gray on Green" +msgstr "Cinza em Verde" + +msgid "Hide Review" +msgstr "Ocultar Revisão" + +msgid "Hide the review screen" +msgstr "Ocultar a tela de revisão" + +msgid "Hide the warning automatically displayed when a test-taker exit a timed section." +msgstr "Ocultar o aviso exibido automaticamente quando um test-taker sair de uma seção cronometrada." + +msgid "Hide Timed Section Warning" +msgstr "Ocultar Aviso de Seção Temporizada" + +msgid "Hide timers" +msgstr "Ocultar cronômetros" + +msgid "Highlight" +msgstr "Destacar" + +msgid "Highlight Text" +msgstr "Destacar Texto" + +msgid "Highlighter" +msgstr "Destaque" + +msgid "hours" +msgstr "horas" + +msgid "Html title" +msgstr "Título HTML" + +msgid "Identifier" +msgstr "Identifier" + +msgid "If required it must appear (at least once) in the selection." +msgstr "Se necessário ele deve aparecer (pelo menos uma vez) na seleção." + +msgid "If required, it must appears at least once in the selection." +msgstr "Se necessário, deve aparecer pelo menos uma vez na seleção." + +msgid "If set, it causes the order of the child elements to be randomized, otherwise it uses the order in which the child elements are defined." +msgstr "Se atribuído, faz com que a ordem dos elementos dos filhos seja randomizada, caso contrário usa a ordem em que os elementos filhos são definidos." + +msgid "If the candidate can skip the item, without submitting a response (default is true)." +msgstr "Se o candidato puder pular o item, sem enviar uma resposta (padrão é verdadeiro)." + +msgid "If the category option is set, the score will also be processed per categories, and each results will take place in the PASS_xxx outcome, where xxx is the name of the category." +msgstr "Se a opção da categoria estiver definida, a nota também será processada por categorias, e cada resultado será realizado no resultado PASS_xxx, onde xxx é o nome da categoria." + +msgid "If the category option is set, the score will also be processed per categories, and each results will take place in the SCORE_xxx outcome, where xxx is the name of the category." +msgstr "Se a opção da categoria estiver definida, a nota também será processada por categorias, e cada resultado terá lugar no resultado SCORE_xxx, onde xxx é o nome da categoria." + +msgid "If the connection cannot be restored, see option B." +msgstr "Se a conexão não puder ser restaurada, veja a opção B." + +msgid "If you need help, please contact our Helpline on 0330 088 4171." +msgstr "Se precisar de ajuda, entre em contato com o nosso Helpline no dia 0330 088 4171." + +msgid "Import" +msgstr "Importação" + +msgid "Import a QTI/APIP Content Package" +msgstr "Importar um Pacote de Conteúdo QTI/APIP" + +msgid "Import QTI TEST into \"%s" +msgstr "Importar TESTE QTI para \"%s" + +msgid "IMS QTI Item referenced as \"%s\" in the IMS Manifest file could not be imported." +msgstr "Item IMS QTI referenciado como \"%s\" no arquivo IMS Manifesto não pôde ser importado." + +msgid "IMS QTI Item referenced as \"%s\" in the IMS Manifest file successfully imported." +msgstr "Item IMS QTI referenciado como \"%sno arquivo IMS Manifest importado com sucesso." + +msgid "IMS QTI Test Package successfully imported." +msgstr "Pacote de Teste IMS QTI importado com sucesso." + +msgid "IMS QTI Test referenced as \"%s\" in the IMS Manifest file successfully imported." +msgstr "Teste IMS QTI referenciado como \"%s\" no arquivo IMS Manifest importado com sucesso." + +msgid "In this part of the test navigation is not allowed." +msgstr "Nesta parte da navegação de teste não é permitido." + +msgid "Inconsistency at line %1d:" +msgstr "Inconsistência na linha %1d:" + +msgid "Individual" +msgstr "Individual" + +msgid "Informational Item Usage" +msgstr "Uso de item informativo" + +msgid "Internal server error!" +msgstr "Erro interno do servidor!" + +msgid "is already used in the test." +msgstr "já é usado no teste." + +msgid "is not a valid identifier (alphanum, underscore, dash and dots)" +msgstr "não é um identificador válido (alfabeto, sublinhado, traço e pontos)" + +msgid "is not a valid identifier (everything except spaces)" +msgstr "não é um identificador válido (tudo, exceto espaços)" + +msgid "It is not possible to save your responses while working offline." +msgstr "Não é possível salvar suas respostas enquanto trabalha offline." + +msgid "Item \"%s\" is completed." +msgstr "Item \"%s\" está completo." + +msgid "Item %d" +msgstr "Item %d" + +msgid "Item %d of %d" +msgstr "Item %d de %d" + +msgid "Item %s loaded" +msgstr "Item %s carregado" + +msgid "Item Reference Properties" +msgstr "Propriedades de Referência do Item" + +msgid "Item response identifier does not match current item" +msgstr "Identificador de resposta do item não corresponde ao item atual" + +msgid "Item Session Control" +msgstr "Controle de sessão do item" + +msgid "items" +msgstr "itens" + +msgid "Items" +msgstr "Itens" + +msgid "Items can optionally be assigned to one or more categories." +msgstr "Os itens podem opcionalmente ser atribuídos a uma ou mais categorias." + +msgid "Items Compilation" +msgstr "Compilação de itens" + +msgid "Items marked for later review" +msgstr "Itens marcados para revisão posterior" + +msgid "Items with assessmentItemRef identifiers \"%s\" are not registered in the related CAT endpoint." +msgstr "Itens com identificadores de avalimentItemRef \"%s\" não estão registrados no endpoint CAT relacionado." + +msgid "Jump Menu" +msgstr "Pular Menu" + +msgid "Jump to:" +msgstr "Ir para:" + +msgid "Keep Together" +msgstr "Manter junto" + +msgid "Keyboard Navigation" +msgstr "Navegação por teclado" + +msgid "Keyboard shortcuts for the Accessibility Tools are available to the Test-taker." +msgstr "Atalhos de teclado para as Ferramentas de Acessibilidade estão disponíveis para o candidato." + +msgid "Late submission allowed" +msgstr "Submissão atrasada permitida" + +msgid "Leave a comment" +msgstr "Deixar um comentário" + +msgid "Line Reader" +msgstr "Leitor de Linha" + +msgid "Linear" +msgstr "Linear" + +msgid "Link durations to activate the guided navigation" +msgstr "Vincular durações para ativar navegação guiada" + +msgid "Locked duration : guided navigation. The test transition to the next item once the duration reaches 0." +msgstr "Duração bloqueada: navegação guiada. A transição de teste para o próximo item uma vez que a duração atinge 0." + +msgid "Magnifier" +msgstr "Lupa" + +msgid "Magnify less" +msgstr "Ampliar menos" + +msgid "Magnify more" +msgstr "Ampliar mais" + +msgid "Magnifying Glass" +msgstr "Lupa" + +msgid "Main navigation" +msgstr "Navegação principal" + +msgid "Manage Rubric Blocks" +msgstr "Gerenciar blocos de Rubrica" + +msgid "Manage test properties" +msgstr "Gerenciar propriedades do teste" + +#, tao-public +msgid "Manage tests" +msgstr "Gerenciar testes" + +msgid "Manage Tests" +msgstr "Gerenciar testes" + +msgid "Mark for review" +msgstr "Marcar para Revisão" + +msgid "Masking" +msgstr "Máscara" + +msgid "Match value" +msgstr "Valor correspondente" + +msgid "Max Attempts" +msgstr "Máx. de tentativas" + +msgid "Maximum Duration" +msgstr "Duração máxima" + +msgid "Maximum duration : the items times out when the duration reaches 0." +msgstr "Duração máxima: os itens expiram quando a duração atinge 0." + +msgid "Maximum duration for the all test." +msgstr "Duração máxima para todo o teste." + +msgid "Maximum duration for this item." +msgstr "Duração máxima para este item." + +msgid "Maximum duration for this section." +msgstr "Duração máxima para esta seção." + +msgid "Maximum duration for this test part." +msgstr "Duração máxima para esta parte do teste." + +msgid "Metadata export is not available for test \"%s." +msgstr "Exportação de metadados não está disponível para o teste \"%s." + +msgid "Minimum Duration" +msgstr "Duração mínima" + +msgid "Minimum duration : enforces the test taker to stay on the item for the given duration." +msgstr "Duração mínima: obriga o candidato a ficar no item durante a duração indicada." + +msgid "Minimum duration for the test." +msgstr "Duração mínima do teste." + +msgid "Minimum duration for this item." +msgstr "Duração mínima para este item." + +msgid "Minimum duration for this section." +msgstr "Duração mínima para esta seção." + +msgid "Minimum duration for this test part." +msgstr "Duração mínima para esta parte do teste." + +msgid "minutes" +msgstr "minutos" + +msgid "Missed test package file" +msgstr "Arquivo do pacote do teste não encontrado" + +msgid "Missing required configuration option %s" +msgstr "Falta a opção de configuração necessária %s" + +msgid "Move" +msgstr "Mover" + +msgid "Move Down" +msgstr "Mover para baixo" + +msgid "Move Up" +msgstr "Mover para cima" + +msgid "Moving to the next item without time limit reached message." +msgstr "Mover para o próximo item sem limite de tempo atingido mensagem." + +msgid "name of the pupil" +msgstr "nome do aluno" + +msgid "Navigation" +msgstr "Navegação" + +msgid "Navigation shortcuts" +msgstr "Atalhos de navegação" + +msgid "Navigation Warnings" +msgstr "Avisos de navegação" + +msgid "network" +msgstr "rede" + +msgid "New Rubric Block" +msgstr "Novo Bloco de Rubrica" + +msgid "New section" +msgstr "Nova Seção" + +msgid "New test part" +msgstr "Nova parte de teste" + +msgid "Next" +msgstr "Próximo" + +msgid "Next Section" +msgstr "Próxima seção" + +msgid "No" +msgstr "Não" + +msgid "No CAT Engine configured for CAT Endpoint \"%s\"." +msgstr "Nenhum motor CAT configurado para CAT Endpoint \"%s\"." + +msgid "No file found at location \"%s\"." +msgstr "Nenhum arquivo encontrado na localização \"%s\"." + +msgid "No more attempts allowed for item \"%s\"." +msgstr "Não há mais tentativas permitidas para o item \"%s\"." + +msgid "no outcome declaration found" +msgstr "nenhuma declaração de resultado encontrada" + +msgid "No outcome processing. Erase the existing rules, if any." +msgstr "Sem processamento de resultado. Limpe as regras existentes, se houver." + +msgid "No reference to any IMS QTI Item found." +msgstr "Nenhuma referência a qualquer item IMS QTI encontrado." + +msgid "Non Linear" +msgstr "Não linear" + +msgid "None" +msgstr "Vazio" + +msgid "Not all test could be exported" +msgstr "Nem todo o teste pôde ser exportado" + +msgid "Not answered" +msgstr "Não respondido" + +msgid "Not seen" +msgstr "Não visto" + +msgid "Not shuffled, the position remains fixed." +msgstr "Não randomizado, a posição permanece fixa." + +msgid "Offline" +msgstr "Offline" + +msgid "OK" +msgstr "OK" + +msgid "Ok" +msgstr "Ok" + +msgid "OK & End test" +msgstr "OK & Terminar teste" + +msgid "Once you close this section, you cannot return to it or change your answers." +msgstr "Depois de fechar esta seção, você não pode voltar a ela ou mudar suas respostas." + +msgid "One or more dependent IMS QTI Items could not be imported." +msgstr "Um ou mais itens IMS QTI dependentes não puderam ser importados." + +msgid "Online" +msgstr "Online" + +msgid "Only display the items marked for review" +msgstr "Exibir apenas os itens marcados para revisão" + +msgid "Only display the unanswered items" +msgstr "Exibir apenas os itens não respondidos" + +msgid "Open Calculator" +msgstr "Abrir Calculadora" + +msgid "Open Keyboard Shortcuts" +msgstr "Atalhos de Teclado" + +msgid "Option A" +msgstr "Opção A" + +msgid "Option B" +msgstr "Opção B" + +msgid "or" +msgstr "ou" + +msgid "Ordering" +msgstr "Ordem" + +msgid "Outcome" +msgstr "Resultado" + +msgid "Outcome declarations" +msgstr "Declarações dos resultados" + +msgid "Outcome processing" +msgstr "Processamento dos resultados" + +msgid "Package is valid but no tests were found. Make sure that it contains valid QTI tests." +msgstr "O pacote é válido mas nenhum teste foi encontrado. Certifique-se de que ele contém testes válidos de QTI." + +msgid "Part %d" +msgstr "Parte %d" + +msgid "Passage" +msgstr "Passagem" + +msgid "Pause" +msgstr "Pausar" + +msgid "Pause the test" +msgstr "Pausar o teste" + +msgid "Play" +msgstr "Reproduzir" + +msgid "Please confirm you want to leave the test." +msgstr "Por favor, confirme que deseja sair do teste." + +msgid "Please try reloading the page or pause the test. If you pause, you will be able to resume the test from this page." +msgstr "Por favor, tente recarregar a página ou pause o teste. Se você pausar, você será capaz de retomar o teste a partir desta página." + +msgid "Please wait while we try to restore the connection." +msgstr "Por favor, aguarde enquanto tentamos restaurar a conexão." + +msgid "Preview" +msgstr "Pré-visualizar" + +msgid "Preview the covered area" +msgstr "Pré-visualizar a área coberta" + +msgid "Previous" +msgstr "Anterior" + +msgid "PROCEED & END ASSESSMENT" +msgstr "CONTINUAR E FINALIZAR A AVALIAÇÃO" + +msgid "Proctor" +msgstr "Vigilante" + +msgid "Properties" +msgstr "Propriedades" + +msgid "QTI 2.1 Test Driver" +msgstr "Driver de Teste QTI 2.1" + +msgid "QTI Item Runner" +msgstr "Executor de teste QTI" + +msgid "QTI Test \"%s\" publishing failed." +msgstr "Falha na publicação do QTI Test \"%s." + +msgid "QTI Test \"%s\" successfully published." +msgstr "Teste QTI \"%s\" publicado com sucesso." + +msgid "QTI Test Package 2.1" +msgstr "Pacote de Teste QTI 2.1" + +msgid "QTI Test Package 2.2" +msgstr "Pacote de Teste QTI 2.2" + +msgid "QTI/APIP Test Content Package" +msgstr "Pacote de conteúdo de teste QTI/APIP" + +msgid "question" +msgstr "pergunta" + +msgid "Question" +msgstr "Pergunta" + +msgid "questions" +msgstr "perguntas" + +msgid "Reference" +msgstr "Referência" + +msgid "Regenerate" +msgstr "Regenerar" + +msgid "Reload the page" +msgstr "Atualize a página" + +msgid "remaining %s %s" +msgstr "restante %s %s" + +msgid "Remove Item Reference" +msgstr "Remover referência do item" + +msgid "Remove Rubric Block" +msgstr "Remover Bloco de Rubrica" + +msgid "Remove Section" +msgstr "Remover Seção" + +msgid "Remove Test Part" +msgstr "Remover Parte do Teste" + +msgid "Required" +msgstr "Obrigatório" + +msgid "Required field" +msgstr "Campo obrigatório" + +msgid "Reset filters" +msgstr "Redefinir filtros" + +msgid "Resource(s) successfully exported." +msgstr "Recurso(s) eexportados com sucesso." + +msgid "Review" +msgstr "Revisar" + +msgid "Review my Answers" +msgstr "Revisar as minhas respostas" + +msgid "Rubric Block" +msgstr "Bloco de Rubrica" + +msgid "Rubric Block Properties" +msgstr "Propriedades do Bloco de Rubrica" + +msgid "Rubric Blocks" +msgstr "Blocos de Rubrica" + +msgid "Save" +msgstr "Salvar" + +msgid "Saved" +msgstr "Salvo" + +msgid "Scientific Calculator" +msgstr "Calculadora científica" + +msgid "Scorer" +msgstr "Pontoador" + +msgid "Scoring" +msgstr "Pontuação" + +msgid "Scratch pad" +msgstr "Painel de rascunho" + +msgid "Scratch Pad" +msgstr "Painel de rascuho" + +msgid "Scratchpad" +msgstr "Painel de rascunho" + +msgid "seconds" +msgstr "segundos" + +msgid "Section %d" +msgstr "Seção %d" + +msgid "Section %d of %d" +msgstr "Seção %d de %d" + +msgid "Section level category enables configuring the categories of its composing items all at once. A category in gray means that all items have that category. A category in white means that only a few items have that category." +msgstr "A categoria do nível de seção permite configurar as categorias de seus itens compondo todos de uma só vez. Uma categoria em cinza significa que todos os itens têm essa categoria. Uma categoria em branco significa que apenas alguns itens têm essa categoria." + +msgid "Section Properties" +msgstr "Propriedades da seção" + +msgid "Select" +msgstr "Selecionar" + +msgid "Select a blueprint" +msgstr "Selecione um diagrama" + +msgid "Select Items" +msgstr "Selecionar itens" + +msgid "Select the way the responses of your test should be processed" +msgstr "Selecione a forma como as respostas do seu teste devem ser processadas" + +msgid "Selection" +msgstr "Seleção" + +msgid "Send" +msgstr "Mandar" + +msgid "Set outcome identifier the feedback block is related to" +msgstr "Definir identificador de resultado que o bloco de feedback está relacionado com" + +msgid "Set the cut score (or pass score ratio) associated to the test. It must be a float between 0 and 1." +msgstr "Defina a pontuação de corte (ou taxa de pontuação) associada ao teste. Deve ser um ponto flutuante entre 0 e 1." + +msgid "Set the rubric block as a feedback block" +msgstr "Defina o bloco de rubrica como um bloco de feedback" + +msgid "Set the value of the outcome that will activate the feedback block" +msgstr "Defina o valor do resultado que irá ativar o bloco de feedback" + +msgid "Set the weight identifier used to process the score" +msgstr "Definir o identificador de peso usado para processar a pontuação" + +msgid "Set the XHTML-QTI class of the rubric block." +msgstr "Defina a classe XHTML-QTI do bloco rubricado." + +msgid "Set the XHTML-QTI identifier of the rubric block." +msgstr "Defina o identificador XHTML-QTI do bloco de rubrica." + +msgid "SetSynchronisationService successfully configured." +msgstr "Serviço configurado com sucesso." + +msgid "Settings" +msgstr "Confirgurações" + +msgid "Show Feedback" +msgstr "Mostrar Feedback" + +msgid "Show Review" +msgstr "Exibir avaliação" + +msgid "Show Solution" +msgstr "Mostrar Solução" + +msgid "Show the review screen" +msgstr "Mostrar a tela de revisão" + +msgid "Show the solution once the answer is submitted." +msgstr "Mostrar a solução quando a resposta for submetida." + +msgid "Show timers" +msgstr "Mostrar temporizadores" + +msgid "Show/Hide the review screen" +msgstr "Mostrar/Ocultar a tela de revisão" + +msgid "Shuffle" +msgstr "Aleatório" + +msgid "Simultaneous" +msgstr "simultâneo" + +msgid "Skip" +msgstr "Ignorar" + +msgid "Skip & End Test" +msgstr "Ignorar & Testar o Fim" + +msgid "Skip and end test" +msgstr "Pular e terminar teste" + +msgid "Skip and go to the end of the test" +msgstr "Pular e ir para o final do teste" + +msgid "Skip and go to the next item" +msgstr "Pular e ir para o próximo item" + +msgid "Skip to the end of the test" +msgstr "Pular para o final do teste" + +msgid "Skip to the next item" +msgstr "Pular para o próximo item" + +msgid "Skip to the next section" +msgstr "Pular para a próxima seção" + +msgid "Some rubric blocks have been updated to reflect the changes in the list of outcomes." +msgstr "Alguns blocos de rubrica foram atualizados para refletir as alterações na lista de resultados." + +msgid "Something unexpected happened." +msgstr "Algo inesperado aconteceu." + +msgid "Something went wrong..." +msgstr "Algo deu errado..." + +msgid "Speech speed" +msgstr "Velocidade da fala" + +msgid "Speed" +msgstr "Velocidade" + +msgid "Start Test-part" +msgstr "Iniciar Teste-parte" + +msgid "Step 1: Download the file (see below when available)." +msgstr "Passo 1: Baixar o arquivo (veja abaixo quando disponível)." + +msgid "Step 2: When you are online again, please send the downloaded file to receptionbaseline@nfer.ac.uk together with the " +msgstr "Passo 2: Quando estiver online novamente, envie o arquivo baixado para receptionbaseline@nfer.ac.uk junto com o " + +msgid "Step 3: Once you have downloaded the file you can close the browser window. Do not do anything further with the assessment whilst it shows as \"in progress\"; the status will be updated once the helpline has uploaded the file." +msgstr "Passo 3: Depois de fazer o download do arquivo, você pode fechar a janela do navegador. Não faça nada mais com a avaliação enquanto ela se revela como \"em progresso\"; o status será atualizado assim que a linha de ajuda tiver carregado o arquivo." + +msgid "Stop" +msgstr "Interromper" + +msgid "Submission" +msgstr "Submissão" + +msgid "Submit and go to the end of the test" +msgstr "Enviar e ir para o final do teste" + +msgid "Submit and go to the item %s" +msgstr "Submeter e ir para o item %s" + +msgid "Submit and go to the next item" +msgstr "Enviar e ir para o próximo item" + +msgid "Submit and go to the previous item" +msgstr "Enviar e ir para o item anterior" + +msgid "SUBMIT THE TEST" +msgstr "ENVIAR O TEST" + +msgid "SUBMIT THIS PART" +msgstr "ENVIAR ESTA PARTE" + +msgid "technical" +msgstr "técnico" + +msgid "Test" +msgstr "Teste" + +msgid "Test constructor" +msgstr "Criador de testes" + +msgid "Test metadata successfully exported." +msgstr "Metadados de teste exportados com sucesso." + +msgid "Test Navigation" +msgstr "Navegação do Teste" + +msgid "Test Part Properties" +msgstr "Propriedades da Parte do Teste" + +msgid "Test Preview is not installed, please contact to your administrator." +msgstr "Pré-visualização de teste não está instalada, por favor, contate o administrador." + +msgid "Test Saved" +msgstr "Teste salvo" + +msgid "Test status" +msgstr "Status do teste" + +msgid "Test status and review structure" +msgstr "Estado de teste e revisão da estrutura" + +msgid "Test Status and Structure" +msgstr "Estado e estrutura do teste" + +msgid "Test-Taker Tools" +msgstr "Ferramentas do candidato" + +#, tao-public +msgid "Tests" +msgstr "Testes" + +msgid "Text content" +msgstr "Conteúdo do teste" + +msgid "Text to Speech" +msgstr "Texto para voz" + +msgid "Text To Speech" +msgstr "Texto para voz" + +msgid "that was being taken." +msgstr "que estava sendo feito." + +msgid "The 'imsmanifest.xml' file found in the archive is not valid." +msgstr "O arquivo 'imsmanifest.xml' encontrado no arquivo não é válido." + +msgid "the assessment (LCL or Mathematics)" +msgstr "a avaliação (LCL ou Matemática)" + +msgid "The assessment has been created but is not already running." +msgstr "A avaliação foi criada, mas ainda não está em execução." + +msgid "The assessment has been suspended by an authorized proctor. If you wish to resume your assessment, please relaunch it and contact your proctor if required." +msgstr "A avaliação foi suspensa por um vigilante autorizado. Se você deseja retomar sua avaliação, por favor, reinicie-a e entre em contato com seu vigilante, se necessário." + +msgid "The assessment has been suspended. To resume your assessment, please relaunch it and contact your proctor if required." +msgstr "A avaliação foi suspensa. Para retomar sua avaliação, por favor, reinicie-a e entre em contato com seu vigilante, se necessário." + +msgid "The assessment has been suspended. To resume your assessment, please relaunch it." +msgstr "A avaliação foi suspensa. Para retomar a avaliação, por favor, reinicie-a." + +msgid "The assessment has been terminated." +msgstr "A avaliação foi terminada." + +msgid "The assessment has been terminated. You cannot interact with it anymore." +msgstr "A avaliação foi terminada. Você não pode interagir mais com ela." + +msgid "The assessment is still running." +msgstr "A avaliação ainda está em curso." + +msgid "The candidate is not allowed to submit invalid responses." +msgstr "O candidato não pode enviar respostas inválidas." + +msgid "The connection seems to be back, please proceed" +msgstr "A conexão parece estar de volta, por favor prossiga" + +msgid "the current question" +msgstr "a pergunta atual" + +msgid "The dependency to the IMS QTI AssessmentItemRef \"%s\" in the IMS Manifest file could not be resolved." +msgstr "A dependência do IMS QTI AssessmentItemRef \"%s no arquivo do Manifesto IMS não pôde ser resolvida." + +msgid "The download will be available in %d seconds" +msgstr "O download estará disponível em %d segundos" + +msgid "The following identifiers are not unique accross the test : %s" +msgstr "Os seguintes identificadores não são originais únicos no teste: %s" + +msgid "The identifier of the item reference." +msgstr "O identificador da referência do item." + +msgid "The identifier of the section." +msgstr "O identificador da seção." + +msgid "The IMS QTI Item referenced as \"%s\" in the IMS Manifest file was already stored in the Item Bank." +msgstr "O item IMS QTI referenciado como \"%s\" no arquivo IMS Manifesto já estava armazenado no Item Bank." + +msgid "The IMS QTI Test Package could not be imported." +msgstr "O pacote de teste IMS QTI não pôde ser importado." + +msgid "The IMS QTI Test referenced as \"%s\" in the IMS Manifest file could not be imported." +msgstr "O teste IMS QTI referenciado como \"%s\" no arquivo IMS Manifest não pôde ser importado." + +msgid "The navigation mode determines the general paths that the candidate may take. A linear mode restricts the candidate to attempt each item in turn. Non Linear removes this restriction." +msgstr "O modo de navegação determina os caminhos gerais que o candidato pode seguir. O modo linear restringe o candidato a tentar cada item em volta. Não linear remove esta restrição." + +msgid "The number of child elements to be selected." +msgstr "O número de elementos a serem seleccionados." + +msgid "The outcome \"%s\" does not exist, but it is referenced by a feedback block!" +msgstr "O resultado \"%s não existe, mas é referenciado por um bloco de feedback!" + +msgid "The outcomes have been regenerated!" +msgstr "Os resultados foram regenerados!" + +msgid "The principle identifier of the test." +msgstr "O identificador principal do teste." + +msgid "The provided archive is invalid. Make sure it is not corrupted and that it contains an 'imsmanifest.xml' file." +msgstr "O arquivo fornecido é inválido. Verifique se ele não está corrompido e se contém um arquivo 'imsmanifest.xml'." + +msgid "The QTI Test '%s' to be compiled must contain at least 1 QTI Item. None found." +msgstr "O teste QTI '%s' para ser compilado deve conter pelo menos 1 Item QTI. Nenhum encontrado." + +msgid "The QTI test could not be retrieved correctly." +msgstr "O teste QTI não pôde ser recuperado corretamente." + +msgid "The QTI Test XML or one of its dependencies is malformed or empty." +msgstr "O teste QTI XML ou uma de suas dependências está malformado ou vazio." + +msgid "The QTI-XML test could not be written correctly." +msgstr "O teste QTI-XML não pôde ser escrito corretamente." + +msgid "The reference." +msgstr "A referência." + +msgid "The resources related to the IMS QTI Test referenced as \"%s\" in the IMS Manifest file were rolled back." +msgstr "Os recursos relacionados ao teste IMS QTI referenciado como \"%s no arquivo do Manifesto IMS foram devolvidos." + +msgid "The score will be processed for the entire test. A sum of all SCORE outcomes will be computed and divided by the sum of MAX SCORE, the result will be compared to the cut score (or pass ratio), then the PASS_TOTAL outcome will be set accordingly." +msgstr "A pontuação será processada para o teste inteiro. Uma soma de todos os resultados SCORE será calculada e dividida pela soma MÁX SCORE, o resultado será comparado com a pontuação de corte (ou taxa de aprovação), então o resultado PASS_TOTAL será definido em conformidade." + +msgid "The score will be processed for the entire test. A sum of all SCORE outcomes will be computed, the result will take place in the SCORE_TOTAL outcome." +msgstr "A pontuação será processada para o teste inteiro. Uma soma de todos os resultados SCORE será calculada, o resultado terá lugar no resultado do SCORE_TOTAL ." + +msgid "The section title." +msgstr "O título da turma." + +msgid "The submission mode determines when the candidate's responses are submitted for response processing. A testPart in individual mode requires the candidate to submit their responses on an item-by-item basis. In simultaneous mode the candidate's responses are all submitted together at the end of the testPart." +msgstr "O modo de envio determina quando as respostas do candidato são enviadas para o processo de resposta. Uma testPart no modo individual requer que o candidato envie suas respostas de acordo com o item. Ao mesmo tempo, as respostas do candidato são todas enviadas em conjunto no final do teste." + +msgid "The test has been closed/suspended!" +msgstr "O teste foi fechado/suspenso!" + +msgid "The test has been paused, we are unable to connect to the server." +msgstr "O teste foi pausado, não foi possível conectar ao servidor." + +msgid "The test has not been saved." +msgstr "O teste não foi salvo." + +msgid "The test has unsaved changes, are you sure you want to leave?" +msgstr "O teste possui alterações não salvas. Tem certeza que deseja sair?" + +msgid "The test has unsaved changes, would you like to save it?" +msgstr "O teste tem alterações não salvas. Deseja salvá-las?" + +msgid "The test needs to be saved before it can be previewed." +msgstr "O teste precisa ser salvo antes de ser pré-visualizado." + +msgid "The test part identifier." +msgstr "Identificador da peça de teste." + +msgid "The test title." +msgstr "O título do teste." + +msgid "The time limit has been reached for this part of the test." +msgstr "O tempo limite foi atingido para esta parte do teste." + +msgid "There are %s unanswered questions" +msgstr "Há %s perguntas não respondidas" + +msgid "There are %s unanswered questions in this part of the test" +msgstr "Há %s perguntas em aberto nesta parte do teste" + +msgid "There is %s unanswered question" +msgstr "Há %s perguntas não respondidas" + +msgid "There is %s unanswered question in this part of the test" +msgstr "Há %s pergunta não respondida nesta parte do teste" + +msgid "There is not outcomes!" +msgstr "Não há resultados!" + +msgid "This constraint affects the visibility of feedback after the end of the last attempt." +msgstr "Esta restrição afeta a visibilidade do feedback após o fim da última tentativa." + +msgid "This constraint controls whether or not the candidate is allowed to provide a comment on the item during the session. Comments are not part of the assessed responses." +msgstr "Esta restrição controla se o candidato pode ou não fornecer um comentário sobre o item durante a sessão. Os comentários não fazem parte das respostas avaliadas." + +msgid "This functionality is blocked. Please contact with your administrator for more details." +msgstr "Esta funcionalidade está bloqueada. Entre em contato com o seu administrador para mais detalhes." + +msgid "This test has been suspended" +msgstr "Este teste foi suspenso" + +msgid "This test has been terminated" +msgstr "Este teste foi finalizado" + +msgid "Time limit reached for item \"%s\"." +msgstr "Tempo limite atingido para o item \"%s\"." + +msgid "Time limit reached, this part of the test has ended." +msgstr "Tempo limite atingido, esta parte do teste terminou." + +msgid "Time limit reached, this part of the test has ended. However you are allowed to finish the current item." +msgstr "Limite de tempo atingido, esta parte do teste terminou. No entanto, você pode terminar o item atual." + +msgid "Time Limits" +msgstr "Limite de tempo" + +msgid "Time Remaining" +msgstr "Tempo Restante" + +msgid "Title" +msgstr "Título" + +msgid "Toggle playback" +msgstr "Ativar/desativar reprodução" + +msgid "Toggle start from here mode" +msgstr "Alternar início a partir daqui modo" + +msgid "Tool box and flagging for review" +msgstr "Caixa de ferramentas e sinalização para revisão" + +msgid "Toolbox" +msgstr "Ferramentas" + +msgid "Total score" +msgstr "Pontuação total" + +msgid "Try connecting the machine to the internet via cable, or move to an area with a better wifi signal." +msgstr "Tente conectar a máquina à internet via cabo ou mova para uma área com um sinal de Wi-Fi melhor." + +msgid "Tutor" +msgstr "Professor" + +msgid "Type" +msgstr "tipo" + +msgid "Unanswered" +msgstr "Sem resposta" + +msgid "Unanswered items" +msgstr "Itens não respondidos" + +msgid "Unflag for Review" +msgstr "Remover sinalização para revisão" + +msgid "Unknown error." +msgstr "Erro Desconhecido" + +msgid "Unlink to use separated durations" +msgstr "Desvincular para usar durações separadas" + +msgid "Unseen" +msgstr "Unseen" + +msgid "Upload a QTI 2.1 Test Package File" +msgstr "Enviar um arquivo de teste QTI 2.1" + +msgid "Use Ctrl/Meta key or Lasso for multiple selection" +msgstr "Usar a tecla Ctrl/Meta ou Laço para seleção múltipla" + +msgid "Validate Responses" +msgstr "Validar respostas" + +msgid "Value" +msgstr "Valor" + +msgid "Viewed" +msgstr "Visualizado" + +msgid "Viewed items" +msgstr "Itens visualizados" + +msgid "Views" +msgstr "Visualizações" + +msgid "Visible" +msgstr "Visível" + +msgid "Volume" +msgstr "Volume" + +msgid "Wait for your connection to come back online." +msgstr "Aguarde a sua conexão voltar online." + +msgid "Warning – You have %s remaining to complete the test." +msgstr "Aviso – Você tem %s para concluir o teste." + +msgid "Warning – You have %s remaining to complete this item." +msgstr "Aviso – Você tem %s restante(s) para completar este item." + +msgid "Warning – You have %s remaining to complete this section." +msgstr "Aviso – Você tem %s restantes para completar esta seção." + +msgid "Warning – You have %s remaining to complete this test part." +msgstr "Aviso – Você tem %s restante para completar esta parte do teste." + +msgid "Warning – You have %s remaining to complete this test." +msgstr "Aviso – Você tem %s restante(s) para completar este item." + +msgid "We are unable to connect the server to submit your results." +msgstr "Não foi possível conectar com o servidor para enviar seus resultados." + +msgid "We are unable to connect to the server to retrieve the next item." +msgstr "Não foi possível conectar ao servidor para recuperar o próximo item." + +msgid "Weight" +msgstr "Peso" + +msgid "Weights" +msgstr "Pesos" + +msgid "When selecting child elements each element is normally eligible for selection once only." +msgstr "Ao selecionar elementos filhos, cada elemento normalmente é elegível para seleção apenas uma vez." + +msgid "Whether a candidate's response that is beyond the maximum duration of the item should still be accepted." +msgstr "Se a resposta de um candidato que exceda o prazo máximo de duração do item deverá continuar a ser aceite." + +msgid "Whether a candidate's response that is beyond the maximum duration of the section should still be accepted." +msgstr "Se a resposta de um candidato que exceda o período máximo de tempo da secção deve ainda ser aceite." + +msgid "Whether a candidate's response that is beyond the maximum duration of the test part should still be accepted." +msgstr "Se a resposta de um candidato que exceda a duração máxima da parte do teste ainda deve ser aceite." + +msgid "Whether a candidate's response that is beyond the maximum duration should still be accepted." +msgstr "Se a resposta de um candidato que exceda o prazo máximo deve continuar a ser aceite." + +msgid "White on Black" +msgstr "Branco em preto" + +msgid "White on Blue (TAO standard theme)" +msgstr "Branco sobre azul (cores padrão da TAO)" + +msgid "Who can view the rubric block during the delivery." +msgstr "Quem pode ver o bloco de rubrica durante o exame." + +msgid "With Replacement" +msgstr "Com Substituição" + +msgid "Wrong file mime type" +msgstr "Tipo de mime de arquivo errado" + +msgid "XML Content" +msgstr "Conteúdo XML" + +#, tao-public +msgid "XML Editor" +msgstr "Editor XML" + +msgid "XML error at line %1$d column %2$d \"%3$s\"." +msgstr "Erro XML na linha %1$d coluna %2$d \"%3$s\"." + +msgid "Yellow on Blue" +msgstr "Amarelo em azul" + +msgid "Yes" +msgstr "Sim" + +msgid "You answered %s of %s question(s) for this section of the test" +msgstr "Você respondeu %s de %s questão(ões) para esta seção do teste" + +msgid "You answered all %s question(s) in this section" +msgstr "Você respondeu todas as %s perguntas nesta seção" + +msgid "You answered all %s question(s) in this test" +msgstr "Você respondeu a todas %s questão(ões) neste teste" + +msgid "You answered only %s of the %s question(s) in this section" +msgstr "Você respondeu apenas %s das %s perguntas nesta seção" + +msgid "You are about to go to the next item. Click OK to continue and go to the next item." +msgstr "Você está prestes a ir para o próximo item. Clique em OK para continuar e ir para o próximo item." + +msgid "You are about to go to the previous item. Click OK to continue and go to the previous item." +msgstr "Você está prestes a ir ao item anterior. Clique em OK para continuar e ir para o item anterior." + +msgid "You are about to leave this section." +msgstr "Você está prestes a sair desta seção." + +msgid "You are about to submit the test." +msgstr "Você está prestes a enviar o teste." + +msgid "You are about to submit the test. You will not be able to access this test once submitted. Click OK to continue and submit the test." +msgstr "Você está prestes a enviar o teste. Não será possível acessar este teste depois de enviado. Clique em OK para continuar e enviar o teste." + +msgid "You are about to submit this test part." +msgstr "Você está prestes a enviar o teste." + +msgid "You are currently working offline." +msgstr "Você está no modo offline." + +msgid "You are encountering a prolonged connectivity loss. " +msgstr "Você está enfrentando uma perda de conectividade prolongada. " + +msgid "You can magnify the content by up to 200%. Check your browser settings to find out how to do it." +msgstr "Você pode ampliar o conteúdo até 200%. Verifique as configurações do seu navegador para saber como fazer isso." + +msgid "You flagged %s item(s) that you can review now" +msgstr "Você sinalizou %s item(ns) que você pode revisar agora" + +msgid "You have %s item(s) marked for review." +msgstr "Você tem %s item(s) marcado(s) para revisão." + +msgid "You have %s left to answer remaining %s questions." +msgstr "Você tem %s restante(s) para responder %s perguntas." + +msgid "You have %s remaining to complete the current item." +msgstr "Você tem %s restante(s) para completar o item atual." + +msgid "You have %s unanswered question(s)" +msgstr "Você tem %s perguntas não respondidas" + +msgid "You have %s unanswered question(s) and have %s item(s) marked for review." +msgstr "Você tem %s pergunta(s) não respondidas e tem %s item(s) marcados para revisão." + +msgid "You have %s unanswered question(s)." +msgstr "Você tem %s pergunta(s) não respondidas." + +msgid "You will not be able to access this test once submitted. Click \"%s\" to continue and submit the test." +msgstr "Você não poderá acessar este teste depois de enviado. Clique \"%s\" para continuar e enviar o teste." + +msgid "Your comment…" +msgstr "Seu comentário…" + +msgid "Your connection seems to be back, please proceed." +msgstr "A conexão parece estar de volta, por favor prossiga." + +msgid "Zoom" +msgstr "Ampliar" + +msgid "Zoom in" +msgstr "Aumentar zoom" + +msgid "Zoom out" +msgstr "Diminuir zoom" + diff --git a/locales/pt-BR/messages_po.js b/locales/pt-BR/messages_po.js new file mode 100644 index 0000000000..6381aa357b --- /dev/null +++ b/locales/pt-BR/messages_po.js @@ -0,0 +1 @@ +{" AND ":" E "," and ":" e "," has been deleted successfully":" foi removido com sucesso"," is ":" \u00e9 "," next":" pr\u00f3ximo"," Remove criteria":" Remover crit\u00e9rios"," requires a subgrid adapter":" requer um adaptador de subgrid","%1$s expected but %2$s detected":"%1$s esperado, mas %2$s detectado","%d attempts left before your account is locked.":"%d tentativas restantes antes de sua conta ser bloqueada.","%d day":"%d dia","%d days":"%d dias","%h hour":"%h hora","%h hours":"%h% hora(s)","%i minute":"%i minuto","%i minutes":"%i minutos","%m month":"%m m\u00eas","%m months":"%m meses","%s %s selected":"%s de %s selecionados","%s (%s kb)":"%s (%s kb)","%s Class saved":"Classe %s salva","%s classes":"%s classes","%s Confirmation":"%s Confirma\u00e7\u00e3o","%s could not be released":"%s n\u00e3o p\u00f4de ser lan\u00e7ado","%s created":"%s criado","%s extension(s) to install.":"%s extens\u00e3o(\u00f5es) a instalar.","%s has been deleted":"%s foi exclu\u00eddo","%s has been released":"%s foi publicado","%s instances":"%s inst\u00e2ncia","%s is of a type that cannot be published":"%s \u00e9 de um tipo que n\u00e3o pode ser publicado","%s Locked":"%s Bloqueado","%s to answer %s":"%s para responder %s","%s\u0022 is not a valid index identifier. It must start with a letter and contain letters, numbers or underscores only":"%s\u0022 n\u00e3o \u00e9 um identificador de \u00edndice v\u00e1lido. Deve come\u00e7ar com uma letra e conter apenas letras, n\u00fameros ou sublinhados","%y year":"%y ano","%y years":"%y anos","(empty)":"(vazio)","(Visual) Clear all active highlights":"(Visual) Limpe todos os destaques ativos","(Visual) Highlight Text":"(Visual) Destacar Texto","... reloading page.":"... atualizando a p\u00e1gina.","7-zip archive":"arquivo 7-zip","Filters<\/del>":"Filtros<\/del>","Options<\/del>":"Op\u00e7\u00f5es<\/del>"," %s <\/strong> has been moved to the background.":" %s <\/strong> foi movido para o plano de fundo.","> Extension %s succesfully installed.":"> Extens\u00e3o %s instalada com sucesso.","> Installation done.":"> Instala\u00e7\u00e3o conclu\u00edda.","a class":"uma classe","A date has to be a Datetime or timestamp":"Uma data deve ser uma do tipo datetime ou timestamp","A fatal error occured during the installation process.":"Ocorreu um erro fatal durante o processo de instala\u00e7\u00e3o.","A maintenance status must have a STATUS: \u0022%s\u0022 or \u0022%s":"Um status de manuten\u00e7\u00e3o deve ter um STATUS: \u0022%s\u0022 ou \u0022%s","A message with further instructions has been sent to your email address: %s":"Uma mensagem com mais instru\u00e7\u00f5es foi enviada para o seu endere\u00e7o de e-mail: %s","A QTI Item involved in the QTI Test \u0027%s\u0027 could not be compiled.":"Um item QTI envolvido no teste QTI \u0027%s\u0027 n\u00e3o pode ser compilado.","A QTI Test must contain at least one QTI Item to be compiled. None found.":"Um teste QTI deve conter pelo menos um item QTI para ser compilado. Nenhum encontrado.","A qti testpackage":"Pacote de teste qti","A remote resource referenced in QTI test \u0027%s\u0027 could not be retrieved.":"Um recurso remoto referenciado no teste QTI \u0027%s\u0027 n\u00e3o p\u00f4de ser recuperado.","A response to every question in this item is required.":"\u00c9 necess\u00e1ria uma resposta a cada pergunta nesse item.","A response to this item is required.":"\u00c9 necess\u00e1ria uma resposta para este item.","A valid response to this item is required.":"\u00c9 necess\u00e1ria uma resposta v\u00e1lida para este item.","A visible section is one that is identifiable by the candidate.":"Uma sec\u00e7\u00e3o vis\u00edvel \u00e9 identific\u00e1vel pelo candidato.","AAC audio":"\u00c1udio AAC","About":"Sobre","abs":"abs","Absolute value":"Valor absoluto","AC":"AC","Access Denied":"Acesso Negado","Access denied. Please renew your authentication!":"Acesso negado. Por favor, renove sua autentica\u00e7\u00e3o!","Account status":"Status da conta","Action":"A\u00e7\u00e3o","Action %s - removed %s positions":"A\u00e7\u00e3o %s - removeu %s posi\u00e7\u00f5es","Action `%s` is not configured in the action queue service":"A a\u00e7\u00e3o `%s` n\u00e3o est\u00e1 configurada na fila de tarefas","Action was not executed yet":"A a\u00e7\u00e3o ainda n\u00e3o foi executada","Actions":"A\u00e7\u00f5es","Activated":"Ativado","Add":"Adicionar","Add \/ Edit lists":"Adicionar \/ Editar listas","Add a CSV file":"Adicionar arquivo CSV","add a new weight":"crie uma nova carga","Add a user":"Adicionar usu\u00e1rio","Add a zip file containing QTI\/APIP tests and items":"Adicionar um arquivo zip contendo testes e itens QTI\/APIP","Add an RDF\/XML file":"Adicionar um arquivo RDF\/XML","Add Consumer":"Adicionar Consumidor","add criteria":"adicionar crit\u00e9rios","Add file(s)":"Adicionar arquivo(s)","Add property":"Adicionar propriedade","Add Provider":"Adicionar provedor","Add selected item(s) here.":"Adicionar itens selecionados aqui.","Add the source file":"Adicionar o arquivo fonte","Adding search index for %s":"Adicionando \u00edndice de pesquisa para %s","Adding search index for created resource":"Adicionando \u00edndice de pesquisa para o recurso criado","Adding the new class has failed":"Falha ao adicionar nova classe","Adding the new resource has failed":"Falha ao adicionar o novo recurso","Adding\/updating search index for updated resource":"Adicionando\/atualizando \u00edndice de pesquisa do recurso","Adobe Flash file":"Arquivo Adobe Flash","Advanced search, add filters":"Pesquisa avan\u00e7ada, adicionar filtros","After you complete the section it would be impossible to return to this section to make changes. Are you sure you want to end the section?":"Ap\u00f3s completar a se\u00e7\u00e3o, ser\u00e1 imposs\u00edvel retornar a esta se\u00e7\u00e3o para fazer altera\u00e7\u00f5es. Voc\u00ea tem certeza que quer completar a se\u00e7\u00e3o?","aliases:":"alias:","All":"Todos","all":"todos","all audios":"todos os \u00e1udios","All data will be removed in %s":"Todos os dados ser\u00e3o removidos em %s","All fields are required":"Todos os campos s\u00e3o obrigat\u00f3rios","all images":"todas as imagens","All rights reserved.":"Todos os direitos reservados.","all videos":"todos os v\u00eddeos","Allow Comment":"Permitir Coment\u00e1rios","Allow for all domains":"Permitir para todos os dom\u00ednios","Allow for the following domains":"Permitir para os seguintes dom\u00ednios","Allow Review":"Permitir Revis\u00e3o","Allow Section Skipping":"Permitir pular se\u00e7\u00e3o","Allow Skipping":"Permitir Pular","Allow skipping of the current section.":"Permitir pular a se\u00e7\u00e3o atual.","Allow Test-taker to playback media files associated according to APIP protocol to item content.":"Permitir ao candidato reproduzir arquivos de m\u00eddia associados com o protocolo APIP para o conte\u00fado do item.","Allow the candidate to review his answers.":"Permitir ao candidato analisar suas respostas.","Allow the test-taker to eliminate \/ strikethrough answers in choice interactions.":"Permitir que o candidato elimine \/ busca em respostas na escolha de intera\u00e7\u00f5es.","Allow the test-taker to highlight parts of the item text.":"Permitir que o candidato destaque de partes do texto do item.","Allow the test-taker to mask and unmask answers in choice interactions.":"Permitir que o candidato esconda as respostas em intera\u00e7\u00f5es de escolha.","Allow the test-taker to mask parts of the item with a movable mask.":"Permitir que o candidato esconda partes do item com uma m\u00e1scara m\u00f3vel.","Allow the test-taker to use a basic calculator.":"Permitir que o candidato use uma calculadora b\u00e1sica.","Allow the test-taker to use a calculator respecting the order of operations (BODMAS).":"Permite que o candidato use uma calculadora respeitando a ordem de opera\u00e7\u00f5es (BODMAS).","Allow the test-taker to use a movable magnifier tool.":"Permite que o candidato use uma ferramenta de lupa m\u00f3vel.","Allow the test-taker to use a scientific calculator.":"Permitir que o candidato use uma calculadora cient\u00edfica.","Allow the test-taker to visually isolate a line of text.":"Permitir que o candidato isole visualmente uma linha de texto.","Allow to generate publishable versions of deliveries ":"Permitir a gera\u00e7\u00e3o de vers\u00f5es public\u00e1veis de entregas ","Allows Test-taker to zoom in and out the item content.":"Permite que o candidato aumente e diminua o conte\u00fado do item.","Alpha version":"Vers\u00e3o alfa","Also compute the score per categories":"Tamb\u00e9m calcular a pontua\u00e7\u00e3o por categorias","An email has been sent":"Um e-mail foi enviado","An error occured while retrieving items":"Ocorreu um erro durante a recupera\u00e7\u00e3o dos itens","An error occurred during results submission. Please retry.":"Ocorreu um erro durante o envio dos resultados. Por favor, tente novamente.","An error occurred during the test initialization!":"Ocorreu um erro durante a inicializa\u00e7\u00e3o do teste!","An error occurred during the test, please content your administrator.":"Ocorreu um erro durante o teste, por favor, conte\u00fado o administrador.","An error occurred during user deletion":"Ocorreu um erro durante a exclus\u00e3o do usu\u00e1rio","An error occurred!":"Ocorreu um erro!","An error occurs, please contact your administrator":"Ocorreu um erro. Por favor, informe o administrador","An exception occured while running \u0022%s":"Ocorreu uma exce\u00e7\u00e3o ao executar \u0022%s","an instance":"uma inst\u00e2ncia","An invisible section with a parent that is subject to shuffling can specify whether or not its children, which will appear to the candidate as if they were part of the parent, are shuffled as a block or mixed up with the other children of the parent section.":"Uma se\u00e7\u00e3o invis\u00edvel com um pai que est\u00e1 sujeito a embaralhar pode especificar se seus filhos ou n\u00e3o, que aparecer\u00e3o para o candidato como se eles fossem parte do pai, s\u00e3o misturados como um bloco ou misturados com as outras crian\u00e7as da se\u00e7\u00e3o principal.","An item involved in the test cannot be read or is not QTI compliant.":"Um item envolvido no teste n\u00e3o pode ser lido ou n\u00e3o \u00e9 compat\u00edvel com QTI.","An item involved in the test cannot be written.":"Um item envolvido no teste n\u00e3o pode ser escrito.","An unknown error occured while compiled QTI Test \u0027%s\u0027.":"Ocorreu um erro desconhecido ao compilar QTI Test \u0027%s\u0027.","An unknown error occured while compiling QTI test \u0027%s\u0027.":"Ocorreu um erro desconhecido ao compilar o teste QTI \u0027%s\u0027.","An unrecoverable error occurred. Your test session will be paused.":"Ocorreu um erro irrecuper\u00e1vel. Sua sess\u00e3o de teste ser\u00e1 pausada.","and":"e","and flagged %s of them":"e sinalizou %s deles","and you flagged %s item(s) that you can review now":"voc\u00ea sinalizou %s item(s) que voc\u00ea pode revisar agora","Ans":"Ans","Answer Eliminator":"Eliminador de Resposta","Answer Masking":"Mascarar Respostas","Answered":"Respondido","APIP Text To Speech":"APIP Texto para Fala","Apply":"Aplicar","Apply date range":"Aplicar intervalo de datas","Arc cosine":"Cosseno de arco","Arc sine":"Seno de arco","Arc tangent":"Arco tangente","Archived":"Arquivado","Are you sure to delete the versioned resource?\nThe history will be lost as well.":"Tem certeza que deseja excluir o recurso versionado?\nO hist\u00f3rico tamb\u00e9m ser\u00e1 perdido.","Are you sure you want to clear your answer and go to the next item? You will not be able to go back and provide an answer.":"Tem certeza que deseja limpar a sua resposta e ir para o pr\u00f3ximo item? N\u00e3o poder\u00e1 voltar atr\u00e1s e dar uma resposta.","Are you sure you want to delete it?":"Tem a certeza que deseja remover?","Are you sure you want to end the test?":"Tem certeza que deseja finalizar o teste?","Are you sure you want to go to the next item? You will not be able to go back and change your answer.":"Tem certeza de que deseja ir para o pr\u00f3ximo item? Voc\u00ea n\u00e3o poder\u00e1 voltar nem mudar sua resposta.","Are you sure you want to go to the next item? You will not be able to go back and provide an answer.":"Tem certeza de que deseja ir para o pr\u00f3ximo item? Voc\u00ea n\u00e3o poder\u00e1 voltar para dar uma resposta.","Are you sure you want to go to the next item? You will not be able to go back.":"Tem certeza de que deseja ir para o pr\u00f3ximo item? N\u00e3o poder\u00e1 voltar.","Area Masking":"Mascarar Respostas","Argument check failed: %s":"Falha ao verificar argumento: %s","Arguments %s and %s are mutually exclusive":"Argumentos %s e %s s\u00e3o mutuamente exclusivos","Arguments separator":"Separador de argumentos","array":"array","Assessment Activity":"Atividade da Avalia\u00e7\u00e3o","Assets":"M\u00eddias","Assign":"Atribuir","Assign User to role":"Atribuir fun\u00e7\u00e3o ao usu\u00e1rio","Assign Users":"Atribuir Usu\u00e1rios","Associate a blueprint to a section allow you to validate this section against the specified blueprint.":"Associe um diagrama a uma se\u00e7\u00e3o permite que voc\u00ea valide essa se\u00e7\u00e3o para o modelo especificado.","Associate Blueprint":"Diagrama associado","At least one mandatory parameter was required but found missing in your request":"Pelo menos um par\u00e2metro obrigat\u00f3rio era requerido, mas encontrado faltando em seu pedido","Audio Video Interleave":"Intercala\u00e7\u00e3o de \u00e1udio e v\u00eddeo","Author":"Autor","Author Item":"Criar Item","Authoring":"Autoria","auto unlocked in %s":"auto desbloqueado em %s","Auxiliary file not found at location \u0022%s\u0022.":"Arquivo auxiliar n\u00e3o encontrado na localiza\u00e7\u00e3o \u0022%s\u0022.","Available":"Dispon\u00edvel","Available Extensions":"Extens\u00f5es dispon\u00edveis","Back":"Voltar","Back to listing":"Voltar \u00e0 lista","Base-10 logarithm":"Logaritmo na base 10","Beta version":"Vers\u00e3o Beta","Binary operator *":"Operador bin\u00e1rio *","Binary operator +":"Operador bin\u00e1rio +","Binary operator -":"Operador bin\u00e1rio -","Binary operator \/":"Operador bin\u00e1rio \/","Binary operator modulo":"M\u00f3dulo do operador bin\u00e1rio","Bitmap image":"Imagem bitmap","Black on Cream":"Preto em Creme","Black on Light Blue":"Preto em Azul Claro","Black on Light Magenta":"Preto em Magenta Claro","Black on White":"Preto em Branco","Blueprint":"Planta","Blueprints":"Diagramas","Blueprints library":"Biblioteca de Diagramas","Booklet":"Livraria","Booklets":"Livros","Booklets library":"Biblioteca de Favoritos","boolean":"booleano","Browse folders:":"Navegar em pastas:","Browse...":"Navegar...","C":"C","C file":"Arquivo C","C++ file (.cpp)":"Arquivo C++ (.cpp)","Calc speadsheet (Staroffice)":"Planilha Calc (Staroffice)","Calculator":"Calculadora","Calculator BODMAS":"Calculadora BODMAS","Calendar":"Calend\u00e1rio","Can not playback media file!":"N\u00e3o \u00e9 poss\u00edvel reproduzir o arquivo de m\u00eddia!","Cancel":"Cancelar","CANCEL":"CANCELAR","cancel":"cancelar","cancel the action":"cancelar a\u00e7\u00e3o","Cancelled":"Cancelado","Candidate":"Candidato","Cannot be edited":"N\u00e3o pode ser editado","Cannot divide by zero":"N\u00e3o \u00e9 poss\u00edvel dividir por 0","Cardinality":"Cardinalidade","Cascading Style Sheets":"Folhas de estilo em cascata","Case sensitive":"Distin\u00e7\u00e3o entre mai\u00fasculas e min\u00fasculas","Categories":"Categorias","Category score":"Pontua\u00e7\u00e3o da categoria","ceil":"teto","Change password":"Alterar senha","Change the current color preset":"Alterar a predefini\u00e7\u00e3o de cor atual","Change the password":"Alterar senha","Change the sign of the current operand":"Alterar o sinal do operador atual","Check all":"Marcar todas","Check All":"Marcar todas","Check-in":"Check-in","choices:":"op\u00e7\u00f5es:","Choose a value...":"Escolha um valor...","Choose export format":"Escolher formato de exporta\u00e7\u00e3o","Choose import format":"Escolha formato da importa\u00e7\u00e3o","Class":"Classe","class selected":"classe selecionada","Class successfully created.":"Classe criada com sucesso.","Class uri provided is not a valid class.":"A classe uri fornecida n\u00e3o \u00e9 uma classe v\u00e1lida.","Cleaning up tmp space complete":"Limpeza de espa\u00e7o tmp completa","Cleaning up tmp space started":"Limpeza espa\u00e7o tmp iniciado","Clear":"Apagar","Clear All":"Apagar tudo","Clear all active highlights":"Limpe todos os destaques ativos","Clear all data":"Apagar todos os dados","Clear expression":"Apagar express\u00e3o","Clear Highlights":"Limpar Destaques","Clear History":"Limpar Hist\u00f3rico","Clear history":"Limpar hist\u00f3rico","Clear selection":"Apagar sele\u00e7\u00e3o","Clear the date time picker":"Limpar o seletor de data e hora","Clear the stored variables":"Limpar as vari\u00e1veis armazenadas","Click \u0022%s\u0022 to continue":"Clique \u0022%s\u0022 para continuar","Click to reload.":"Clique para recarregar.","Click to speak":"Clique para falar","Close":"Fechar","Close dialog":"Fechar di\u00e1logo","Close Magnifier":"Fechar Lupa","Close message":"Fechar mensagem","Close the mask":"Fechar m\u00e1scara","Close this Section":"Fechar esta se\u00e7\u00e3o","Code Version":"Vers\u00e3o do C\u00f3digo","Collapse the review panel":"Fechar painel de revis\u00e3o","Collecting of abandoned actions in the action queue ...":"Coletando a\u00e7\u00f5es abandonadas na fila de tarefas ...","Column":"Coluna","Combine a selection of items into tests.":"Combinar uma sele\u00e7\u00e3o de itens em testes.","command":"comando","Commands:":"Comandos:","Comment":"Comentar","comment...":"coment\u00e1rio...","Commit":"Aplicar","Completed":"Conclu\u00eddo","Completed - Error":"Conclu\u00eddo - Erro","Completed items":"Itens conclu\u00eddos","Compressed tar file":"Arquivo tar comprimido","Compute the expression":"Calcular a express\u00e3o","Confirm":"Confirmar","Connect to the TAO platform":"Conectar-se \u00e0 plataforma de TAO","Connected to server":"Conectado ao Servidor","Continue":"Continuar","CONTINUE TO THE NEXT SECTION":"CONTINUAR PARA A PR\u00d3XIMA SE\u00c7\u00c3O","Contrast":"Contraste","Controls the contribution of an individual item score to the overall test score.":"Controla a contribui\u00e7\u00e3o de uma pontua\u00e7\u00e3o individual de item para a pontua\u00e7\u00e3o geral do teste.","Controls the maximum number of attempts allowed. 0 means unlimited.":"Controla o n\u00famero m\u00e1ximo de tentativas permitidas. 0 significa ilimitado.","Copy":"Copiar","Copy to":"Copiar para","Copy To":"Copiar em","Copy to clipboard":"Copiar para \u00e1rea de transfer\u00eancia","cos":"cos","cosh":"cosh","Cosine":"Cosseno","count":"contar","Covers parts of the item":"Cobre partes do item","Create":"Criar","Create and design items and exercises.":"Criar e projetar itens e exerc\u00edcios.","Create instance of ":"Criar inst\u00e2ncia de ","Created":"Criado","Creating report ...":"Criando o relat\u00f3rio...","CSP Header settings were saved successfully!":"Configura\u00e7\u00f5es de cabe\u00e7alho CSP foram salvas com sucesso!","CSV":"CSV","CSV file":"Arquivo CSV","CSV Options":"Op\u00e7\u00f5es CSV","Cube root":"Raiz C\u00fabica","Current language":"Idioma atual","Current playback position":"Posi\u00e7\u00e3o atual da reprodu\u00e7\u00e3o","Custom":"Personalizado","custom categories":"personalizar categorias","Custom outcome processing. No changes will be made to the existing rules.":"Processamento de resultado personalizado. Nenhuma altera\u00e7\u00e3o ser\u00e1 feita nas regras existentes.","Cut score":"Pontua\u00e7\u00e3o de corte","Cut score (pass ratio)":"Pontua\u00e7\u00e3o de corte (taxa de passo)","Data imported":"Dados importados","Data imported successfully":"Importado com sucesso","Data imported. Some records are invalid.":"Dados importados. Alguns registros s\u00e3o inv\u00e1lidos.","Data language":"Idioma dos dados","Data Language":"Idioma dos dados","Data not imported. All records are invalid.":"Os dados n\u00e3o importados. Todos os registros s\u00e3o inv\u00e1lidos.","Data source URI":"URI fonte de dados","Database file":"Arquivo de banco de dados","Date":"Data","date range":"per\u00edodo","date time":"data\/hora","date time range":"intervalo de tempo","Default search":"Pesquisa padr\u00e3o","default:":"padr\u00e3o:","Define the default values":"Definir valores padr\u00e3o","Deg":"Deg","Degree":"Grau","Delete":"Remover","Delete Left":"Excluir Esquerda","Delete Right":"Excluir Direita","Delete the term on the left side of the cursor":"Excluir o termo no lado esquerdo do cursor","Delete the term on the right side of the cursor":"Excluir o termo no lado direito do cursor","Deleting search index for %s":"Excluindo \u00edndice de pesquisa para %s","Deliveries":"Entregas","Deliveries library":"Biblioteca de entregas","Demo Sandbox":"Demo Sandbox","Did you mean %s?":"Voc\u00ea quis dizer %s?","Digit 0":"D\u00edgito 0","Digit 1":"D\u00edgito 1","Digit 2":"D\u00edgito 2","Digit 3":"D\u00edgito 3","Digit 4":"D\u00edgito 4","Digit 5":"D\u00edgito 5","Digit 6":"D\u00edgito 6","Digit 7":"D\u00edgito 7","Digit 8":"D\u00edgito 8","Digit 9":"D\u00edgito 9","Disable validation":"Desativar valida\u00e7\u00e3o","Disabled %s":"Desativado %s","Disconnected from server":"Desconectado do Servidor","Display a warning before the test-taker ends a test part and there are still items left unanswered or marked for review.":"Exibir um aviso antes que o candidato termine uma parte do teste, e ainda h\u00e1 itens que n\u00e3o foram respondidos ou marcados para revis\u00e3o.","Display a warning before the test-taker ends the test part.":"Exibir um aviso antes que o test-taker termine a parte de teste.","Display a warning before the test-taker ends the test.":"Exibir um aviso antes que o candidato termine a parte de teste.","Display a warning before the test-taker skips the section. Requires the Section Skipping option.":"Exibe um aviso antes que o candidato pule a se\u00e7\u00e3o. Requer a op\u00e7\u00e3o Pular Se\u00e7\u00e3o.","Display End Test Warning":"Exibir Alerta de Final de Teste","Display Next Part Warning":"Exibir Aviso de Pr\u00f3xima Parte","Display Next Section Warning":"Exibir Aviso de Pr\u00f3xima Parte","Display Unanswered Warning":"Exibir Aviso de Item N\u00e3o Respondido","Displayed label":"Mostrar label","Displays a customisable magnifier":"Exibe uma lupa customiz\u00e1vel","Do not flag the current item for later review":"N\u00e3o sinalizar o item atual para posterior revis\u00e3o","Do not rename it.":"N\u00e3o renomear.","Do not show alert on timeout":"N\u00e3o exibir alerta no tempo limite","Do you wish to install the following extension(s):\n%s?":"Voc\u00ea deseja instalar a(s) seguinte(s) extens\u00e3o(s):\n%s?","Document templates (Staroffice)":"Modelos de documentos (Staroffice)","does not match":"n\u00e3o confere","Don\u0027t set":"N\u00e3o definir","Don\\\\\u0027t save":"N\u00e3o salvar","Dot":"Ponto","Download":"Baixar","Download the pupil responses and submit manually.":"Baixar as respostas para os alunos e enviar manualmente.","Download this file":"Download do arquivo","Drag file here":"Arraste o arquivo aqui","Drag files here":"Arraste os arquivos aqui","Drop the files to upload":"Solte o arquivo aqui para fazer o upload","Duplicate":"Duplicar","Edit":"Editar","Edit a user":"Editar usu\u00e1rio","Edit class %s":"Editar classe %s","Edit Instance":"Editar inst\u00e2ncia","Edit Tree":"Editar \u00e1rvore","Element":"Elemento","Element deleted.":"Elemento exclu\u00eddo.","Eliminate choices":"Eliminar op\u00e7\u00f5es","Email":"Email","Enable Mark for Review":"Habilitar para revis\u00e3o","Enable mark for review of items. Requires the Review Screen option.":"Habilitar marca para revis\u00e3o dos itens. Requer a op\u00e7\u00e3o da tela de revis\u00e3o.","Enable Review Screen":"Ativar a tela de revis\u00e3o","Enable selection":"Habilitar sele\u00e7\u00e3o","Enable Skipping Ahead":"Habilitar pular para frente","Enable text to speech":"Habilitar s\u00edntese de voz","Enable the item review screen \/ navigator.":"Habilite a tela de revis\u00e3o do item \/ navegador.","enabled":"habilitado","Enabled %s":"Habilitado %s","Enables skipping to items within this section. Requires the review screen option.":"Permite pular para outros itens nesta se\u00e7\u00e3o. Requer a op\u00e7\u00e3o da tela de revis\u00e3o.","End of document reached. Continuing from the beginning.":"Fim do documento alcan\u00e7ado. Continuando desde o in\u00edcio.","End Test":"Finalizar teste","End test":"Finalizar teste","Enhanced metafile":"Metarquivo aprimorado","Enter":"Entrar","Enter a blueprint":"Inserir um diagrama","Enter a custom category":"Inserir uma categoria personalizada","Error":"Erro","Error adding search index for %s with message %s":"Erro ao adicionar \u00edndice de pesquisa para %s com mensagem %s","Error during file import":"Erro durante importa\u00e7\u00e3o do arquivo","Error found in the IMS QTI Test:\n%s":"Erro encontrado no teste IMS QTI\n%s","Error in result":"Erro no resultado","Error in syntax":"Erro na sintaxe","Error in value":"Erro no valor","Error: %s":"Erro: %s","Event Log":"Registro de Eventos","Examples:":"Exemplos:","Exclusive (OR)":"Exclusivo (OU)","Execute":"Executar","Executing task %s failed":"Falha ao executar tarefa %s","exp":"exp","Expand the review panel":"Abrir o Painel de Revis\u00e3o","Exponent":"Expoente","Export":"Exportar","Export ":"Exportar ","Export \u0022%s\u0022 in %s":"Exportar \u0022%s\u0022 em %s","Export CSV":"Exportar como CSV","Export failed for the test \u0022%s":"Exporta\u00e7\u00e3o falhou para o teste \u0022%s","Export failed.":"Falha ao exportar.","Export Irregularities":"Inregularidades de exporta\u00e7\u00e3o","Export metadata":"Exportar metadados","Export Metadata as RDF\/XML file":"Exportar metadados como arquivo RDF\/XML","Export QTI 2.1 Test Package":"Exportar Pacote de Teste QTI 2.1","Export QTI 2.2 Test Package":"Exportar Pacote de Teste QTI 2.2","Export succeeded.":"Exportado com sucesso.","Export successful for the test \u0022%s":"Sucesso na exporta\u00e7\u00e3o do teste \u0022%s","Export Table":"Exportar tabela","Extension \u0022%s\u0022 has been installed":"Extens\u00e3o \u0022%s\u0022 foi instalada","Extensions manager":"Gerenciador de extens\u00f5es","Factorial":"Fatorial","Failed":"Falhou","Failed to delete index for %s":"Falha ao excluir arquivo %s","False":"Falso","Feedback block":"Bloco de Feedback","Field delimiter":"Delimitador de campo","Field encloser":"Encerrador de campo","Fields must match":"Os campos devem coincidir","File":"Arquivo","File download failed":"O download do arquivo falhou","File name":"Nome do Arquivo","File not found":"Arquivo n\u00e3o encontrado","File not found!":"Arquivo n\u00e3o encontrado!","File Properties":"Propriedades do arquivo","file selected":"arquivo selecionado","File Uploader":"Carregador de Arquivos","files selected":"arquivo selecionado","Filter":"Filtro","Filter by value":"Filtro por valor","Filtering mode":"Modo de filtragem","First Name":"Primeiro Nome","First page":"Primeira p\u00e1gina","First row column names":"Nome das colunas da primeira linha","Fit to width":"Ajustar \u00e0 largura","Fixed":"Afixada","Fixed position in a shuffled the selection.":"Posi\u00e7\u00e3o fixa em uma sele\u00e7\u00e3o embaralhada.","Flag for Review":"Sinalizar para revis\u00e3o","Flag the current item for later review":"Sinalizar o item atual para revis\u00e3o posterior","Flagged":"Marcado","Flagged for review":"Marcado para revis\u00e3o","Flash video":"V\u00eddeo Flash","floor":"piso","Flowchart-based programming environment & TI Interactive Workbook":"Ambiente de programa\u00e7\u00e3o baseado em fluxograma e TI Interactive Workbook","Forbid for all domains":"Proibir todos os dom\u00ednios","Force HTTPS on this platform":"For\u00e7ar HTTPS nesta plataforma","Force Release":"For\u00e7ar Lan\u00e7amento","Force the item to be considered as informational and not taken into account in (un)answered \/ flagged counters.":"For\u00e7ar o item a ser considerado como informativo e n\u00e3o considerado nos contadores (n\u00e3o )respondidos\/sinalizados.","Forgot your password?":"Esqueceu sua senha?","Form":"Formul\u00e1rio","Form data not saved.":"Dados do formul\u00e1rio n\u00e3o salvos.","From":"De","Fuzzy Matching":"Fuzzy Matching","generated-value":"valores gerados","Generis Form":"Generis Form","Geogebra data file":"Arquivo de dados Geogebra","Get History":"Obter Hist\u00f3rico","Get the history list":"Obter a lista de hist\u00f3rico","GIF image":"Imagem GIF","Go Back":"Voltar","go back to home page":"retornar a p\u00e1gina inicial","Go to next item":"Ir para o pr\u00f3ximo item","Go to the current question":"Ir para a quest\u00e3o atual","Go to the next item?":"Ir para o pr\u00f3ximo item?","Go to the next question":"Ir para a pr\u00f3xima quest\u00e3o","Go to the previous question":"Ir para a quest\u00e3o anterior","Go to the top of the page":"Ir para o topo da p\u00e1gina","Gray on Green":"Cinza em Verde","Group test takers according to global features and classifications.":"takers de teste do grupo de acordo com recursos e classifica\u00e7\u00f5es globais.","Groups":"grupos","Groups library":"Biblioteca de grupos","GZip Compressed Archive":"Arquivo Comprimido GZip","Header file with extensionsLabels":"Arquivo de cabe\u00e7alho com extensLabels","Height":"Altura","Help":"Socorro","Hi, %s
You indicated that you have forgotten your TAO password.":"Ol\u00e1, %s
Voc\u00ea indicou que esqueceu a sua senha de TAO.","Hide detailed report":"Ocultar relat\u00f3rio detalhado","Hide password":"Ocultar senha","Hide Review":"Ocultar Revis\u00e3o","Hide the review screen":"Ocultar a tela de revis\u00e3o","Hide the warning automatically displayed when a test-taker exit a timed section.":"Ocultar o aviso exibido automaticamente quando um test-taker sair de uma se\u00e7\u00e3o cronometrada.","Hide Timed Section Warning":"Ocultar Aviso de Se\u00e7\u00e3o Temporizada","Hide timers":"Ocultar cron\u00f4metros","Highlight":"Destacar","Highlight all":"Destaque todos","Highlight Text":"Destacar Texto","Highlighter":"Destaque","History":"Hist\u00f3rico","hours":"horas","Html title":"T\u00edtulo HTML","Hyperbolic arc cosine":"Arco cosseno hiperb\u00f3lico","Hyperbolic arc sine":"Arco seno hiperb\u00f3lico","Hyperbolic arc tangent":"Arco tangente hiperb\u00f3lico","Hyperbolic cosine":"Cosseno hiperb\u00f3lico","Hyperbolic sine":"Seno hiperb\u00f3lico","Hyperbolic tangent":"Tangente hiperb\u00f3lica","Hypertext markup language":"Linguagem de marca\u00e7\u00e3o de hipertexto","I understand that this action is permanent.":"Eu entendo que esta a\u00e7\u00e3o n\u00e3o poder\u00e1 ser desfeita.","Identifier":"Identifier","If required it must appear (at least once) in the selection.":"Se necess\u00e1rio ele deve aparecer (pelo menos uma vez) na sele\u00e7\u00e3o.","If required, it must appears at least once in the selection.":"Se necess\u00e1rio, deve aparecer pelo menos uma vez na sele\u00e7\u00e3o.","If set, it causes the order of the child elements to be randomized, otherwise it uses the order in which the child elements are defined.":"Se atribu\u00eddo, faz com que a ordem dos elementos dos filhos seja randomizada, caso contr\u00e1rio usa a ordem em que os elementos filhos s\u00e3o definidos.","If the candidate can skip the item, without submitting a response (default is true).":"Se o candidato puder pular o item, sem enviar uma resposta (padr\u00e3o \u00e9 verdadeiro).","If the category option is set, the score will also be processed per categories, and each results will take place in the PASS_xxx outcome, where xxx is the name of the category.":"Se a op\u00e7\u00e3o da categoria estiver definida, a nota tamb\u00e9m ser\u00e1 processada por categorias, e cada resultado ser\u00e1 realizado no resultado PASS_xxx, onde xxx \u00e9 o nome da categoria.","If the category option is set, the score will also be processed per categories, and each results will take place in the SCORE_xxx outcome, where xxx is the name of the category.":"Se a op\u00e7\u00e3o da categoria estiver definida, a nota tamb\u00e9m ser\u00e1 processada por categorias, e cada resultado ter\u00e1 lugar no resultado SCORE_xxx, onde xxx \u00e9 o nome da categoria.","If the connection cannot be restored, see option B.":"Se a conex\u00e3o n\u00e3o puder ser restaurada, veja a op\u00e7\u00e3o B.","If you do not wish to reset your password, just ignore this email and your password will remain the same.":"Se voc\u00ea n\u00e3o fez esta solicita\u00e7\u00e3o, voc\u00ea pode ignorar este e-mail e sua senha permanecer\u00e1 a mesma.","If you need help, please contact our Helpline on 0330 088 4171.":"Se precisar de ajuda, entre em contato com o nosso Helpline no dia 0330 088 4171.","Implications failed:":"Implica\u00e7\u00f5es falharam:","Import":"Importa\u00e7\u00e3o","Import ":"Importar ","Import a %s into \u0022%s":"Importar um %s para \u0022%s","Import a QTI\/APIP Content Package":"Importar um Pacote de Conte\u00fado QTI\/APIP","Import Completed":"Importa\u00e7\u00e3o Completa","Import failed.":"A importa\u00e7\u00e3o falhou.","Import Metadata from CSV file":"Importar metadados de um arquivo CSV","Import Metadata from RDF file":"Importar Metadados de arquivo RDF","Import QTI TEST into \u0022%s":"Importar TESTE QTI para \u0022%s","Import succeeded.":"Importa\u00e7\u00e3o bem sucedida.","Imported %1$d\/%2$d. Some records are invalid.":"Importado %1$d\/%2$d. Alguns registros s\u00e3o inv\u00e1lidos.","Imported %d resources":"%d recursos importados","Imported resource \u0022%s":"Recurso importado \u0022%s","impossible to update task status":"imposs\u00edvel atualizar o status da tarefa","IMS QTI Item referenced as \u0022%s\u0022 in the IMS Manifest file could not be imported.":"Item IMS QTI referenciado como \u0022%s\u0022 no arquivo IMS Manifesto n\u00e3o p\u00f4de ser importado.","IMS QTI Item referenced as \u0022%s\u0022 in the IMS Manifest file successfully imported.":"Item IMS QTI referenciado como \u0022%sno arquivo IMS Manifest importado com sucesso.","IMS QTI Test Package successfully imported.":"Pacote de Teste IMS QTI importado com sucesso.","IMS QTI Test referenced as \u0022%s\u0022 in the IMS Manifest file successfully imported.":"Teste IMS QTI referenciado como \u0022%s\u0022 no arquivo IMS Manifest importado com sucesso.","In progress":"Em progresso","In Progress":"Em Progresso","In this part of the test navigation is not allowed.":"Nesta parte da navega\u00e7\u00e3o de teste n\u00e3o \u00e9 permitido.","Inclusive (AND)":"Inclusivo (E)","Inconsistency at line %1d:":"Inconsist\u00eancia na linha %1d:","Index":"Indexa\u00e7\u00e3o","Index has been deleted for %s":"O \u00edndice foi exclu\u00eddo para %s","Individual":"Individual","Infinity":"Infinito","Informational Item Usage":"Uso de item informativo","Injector has to contains a valid \u0022destination\u0022 field.":"Injetor deve conter um campo \u0022destino\u0022 v\u00e1lido.","Injector has to contains a valid \u0022source\u0022 field.":"Injetor deve conter um campo \u0022fonte\u0022 v\u00e1lido.","Inline":"Alinhado","Install":"Instalar","Installation done.":"Instala\u00e7\u00e3o conclu\u00edda.","Installation...":"Instala\u00e7\u00e3o...","Installed Extensions":"Extens\u00f5es Instaladas","Installed Version":"Vers\u00e3o Instalada","Installing extension %s...":"Instalando extens\u00f5es %s...","Instance saved":"Inst\u00e2ncia salva","Interface Language":"Linguagem da Interface","Interface language":"Linguagem da interface","Internal server error!":"Erro interno do servidor!","Invalid CSP header.":"Cabe\u00e7alho CSP inv\u00e1lido.","Invalid date range (must be after or the same as: %s)":"Intervalo de data inv\u00e1lido (deve ser depois ou o mesmo de: %s)","Invalid date range (must be after: %s)":"Intervalo de data inv\u00e1lido (deve ser ap\u00f3s: %s)","Invalid date range (must be before or the same as: %s)":"Intervalo de data inv\u00e1lido (deve ser depois ou o mesmo de: %s)","Invalid date range (must be before: %s)":"Intervalo de data inv\u00e1lido (deve ser ap\u00f3s: %s)","Invalid field length":"Tamanho do campo inv\u00e1lido","Invalid field range (maximum value: %s)":"Intervalo de campo inv\u00e1lido (valor m\u00e1ximo: %s)","Invalid field range (minimum value: %1$s, maximum value: %2$s)":"Intervalo de campo inv\u00e1lido (valor m\u00ednimo: %1$s, valor m\u00e1ximo: %2$s)","Invalid field range (minimum value: %s)":"Intervalo de campo inv\u00e1lido (valor m\u00ednimo: %s)","Invalid file size (minimum %1$s bytes, maximum %2$s bytes)":"Tamanho do arquivo inv\u00e1lido (m\u00ednimo de %1$s bytes, m\u00e1ximo de %2$s bytes)","Invalid file type!":"Tipo de arquivo inv\u00e1lido!","Invalid input":"Entrada inv\u00e1lida","Invalid input for function":"Entrada inv\u00e1lida para a fun\u00e7\u00e3o","Invalid JSON config file: %s":"Arquivo JSON de configura\u00e7\u00e3o inv\u00e1lido: %s","Invalid login or password. Please try again.":"Login ou senha inv\u00e1lidos. Por favor tente novamente.","Invalid login or password. Your account has been locked, please contact your administrator.":"Login ou senha inv\u00e1lidos. Sua conta foi bloqueada, por favor, entre em contato com o administrador.","invalid regular expression":"express\u00e3o regular inv\u00e1lida","Invalid values:":"Valor inv\u00e1lido:","is already used in the test.":"j\u00e1 \u00e9 usado no teste.","is not a valid identifier (alphanum, underscore, dash and dots)":"n\u00e3o \u00e9 um identificador v\u00e1lido (alfabeto, sublinhado, tra\u00e7o e pontos)","is not a valid identifier (everything except spaces)":"n\u00e3o \u00e9 um identificador v\u00e1lido (tudo, exceto espa\u00e7os)","It is not possible to save your responses while working offline.":"N\u00e3o \u00e9 poss\u00edvel salvar suas respostas enquanto trabalha offline.","Item \u0022%s\u0022 is completed.":"Item \u0022%s\u0022 est\u00e1 completo.","Item %d":"Item %d","Item %d of %d":"Item %d de %d","Item %s loaded":"Item %s carregado","Item Reference Properties":"Propriedades de Refer\u00eancia do Item","Item response identifier does not match current item":"Identificador de resposta do item n\u00e3o corresponde ao item atual","Item Session Control":"Controle de sess\u00e3o do item","Items":"Itens","items":"itens","Items can optionally be assigned to one or more categories.":"Os itens podem opcionalmente ser atribu\u00eddos a uma ou mais categorias.","Items Compilation":"Compila\u00e7\u00e3o de itens","Items library":"Biblioteca de itens","Items marked for later review":"Itens marcados para revis\u00e3o posterior","items per page":"itens por p\u00e1gina","Items with assessmentItemRef identifiers \u0022%s\u0022 are not registered in the related CAT endpoint.":"Itens com identificadores de avalimentItemRef \u0022%s\u0022 n\u00e3o est\u00e3o registrados no endpoint CAT relacionado.","Javascript code":"C\u00f3digo javascript","JPEG image":"imagem JPEG","Jump Menu":"Pular Menu","Jump to:":"Ir para:","Keep Together":"Manter junto","Keyboard Navigation":"Navega\u00e7\u00e3o por teclado","Keyboard shortcuts for the Accessibility Tools are available to the Test-taker.":"Atalhos de teclado para as Ferramentas de Acessibilidade est\u00e3o dispon\u00edveis para o candidato.","Label":"T\u00edtulo","Label Path":"Endere\u00e7o do T\u00edtulo","Language":"Idioma","Last attempt before your account is locked.":"\u00daltima tentativa antes de sua conta ser bloqueada.","Last Name":"Sobrenome","Last page":"\u00daltima p\u00e1gina","Last result":"\u00daltimo resultado","Late submission allowed":"Submiss\u00e3o atrasada permitida","Layout":"Layout","Leave a comment":"Deixar um coment\u00e1rio","Left parenthesis":"Par\u00eantese esquerdo","less than a minute":"menos de um minuto","Line Reader":"Leitor de Linha","Linear":"Linear","Link durations to activate the guided navigation":"Vincular dura\u00e7\u00f5es para ativar navega\u00e7\u00e3o guiada","List":"Lista","List - Multiple choice - Check box":"Lista - M\u00faltipla escolha - Caixa de sele\u00e7\u00e3o","List - Multiple choice - Search input":"Lista - M\u00faltipla escolha - Entrada de pesquisa","List - Single choice - Drop down":"Lista - Simples - Drop down","List - Single choice - Radio button":"Lista - Simples - Radio button","List - Single choice - Search input":"Lista - Simples - Pesquisa","List item URI duplications found":"Duplica\u00e7\u00f5es de item de lista URI encontradas","List values":"Listar valores","Lists":"Listas","Load more":"Carregar mais","Loaded":"Carregado","Loaded at Startup":"Carregado na Inicializa\u00e7\u00e3o","Loading":"Carregando","Loading status":"Carregando status","Loading task status ...":"Carregando status da tarefa...","locked by %s":"bloqueado por %s","Locked duration : guided navigation. The test transition to the next item once the duration reaches 0.":"Dura\u00e7\u00e3o bloqueada: navega\u00e7\u00e3o guiada. A transi\u00e7\u00e3o de teste para o pr\u00f3ximo item uma vez que a dura\u00e7\u00e3o atinge 0.","Log in":"Entrar","Log Out":"Sair","Login":"Entrar","Login available":"Acesso dispon\u00edvel","Login is already in use.":"Login j\u00e1 est\u00e1 em uso.","Login is empty.":"Login est\u00e1 vazio.","Logout":"Sair","LTI":"LTI","LTI Consumers":"Consumidores LTI","LTI Providers":"Provedores LTI","M4A audio":"\u00e1udio M4A","Magnifier":"Lupa","Magnify less":"Ampliar menos","Magnify more":"Ampliar mais","Magnifying Glass":"Lupa","Main Menu":"Menu Principal","Main navigation":"Navega\u00e7\u00e3o principal","Maintenance service is already installed.":"O servi\u00e7o de manuten\u00e7\u00e3o j\u00e1 est\u00e1 instalado.","Maintenance service must have a persistence option.":"O servi\u00e7o de manuten\u00e7\u00e3o deve ter uma op\u00e7\u00e3o de persist\u00eancia.","Maintenance service was installed. Platform is disabled.":"O servi\u00e7o de manuten\u00e7\u00e3o foi instalado. A plataforma est\u00e1 desativada.","Maintenance service was installed. Platform is enabled.":"O servi\u00e7o de manuten\u00e7\u00e3o foi instalado. Plataforma habilitada.","Maintenance storage driver is not set":"O driver de armazenamento de manuten\u00e7\u00e3o n\u00e3o est\u00e1 definido","Manage Access Rights":"Gerenciar Permiss\u00f5es de Acesso","Manage Assets":"Gerenciar Ativos","Manage class schema":"Gerenciar esquema de classe","Manage data list":"Gerenciar lista de dados","Manage groups":"Gerenciar grupos","Manage items":"Gerenciar itens","Manage Media":"Gerenciar m\u00eddia","Manage Results":"Gerenciar Resultados","Manage roles":"Gerenciar fun\u00e7\u00f5es","Manage Roles and apply them to users.":"Gerenciar Fun\u00e7\u00f5es e aplic\u00e1-las aos usu\u00e1rios.","Manage Rubric Blocks":"Gerenciar blocos de Rubrica","Manage Schema":"Gerenciar esquema","Manage Test Center":"Gerenciar Centro de Testes","Manage test properties":"Gerenciar propriedades do teste","Manage test takers":"Gerenciar candidatos","Manage Tests":"Gerenciar testes","Manage tests":"Gerenciar testes","Manage users":"Gerenciar usu\u00e1rios","Manage Versioned File":"Gerenciar arquivo versionado","Map the properties to the CSV columns":"Mapear as propriedades das colunas CSV","Mark for review":"Marcar para Revis\u00e3o","Masking":"M\u00e1scara","Match value":"Valor correspondente","Max Attempts":"M\u00e1x. de tentativas","Maximum Duration":"Dura\u00e7\u00e3o m\u00e1xima","Maximum duration : the items times out when the duration reaches 0.":"Dura\u00e7\u00e3o m\u00e1xima: os itens expiram quando a dura\u00e7\u00e3o atinge 0.","Maximum duration for the all test.":"Dura\u00e7\u00e3o m\u00e1xima para todo o teste.","Maximum duration for this item.":"Dura\u00e7\u00e3o m\u00e1xima para este item.","Maximum duration for this section.":"Dura\u00e7\u00e3o m\u00e1xima para esta se\u00e7\u00e3o.","Maximum duration for this test part.":"Dura\u00e7\u00e3o m\u00e1xima para esta parte do teste.","Maximum selection reached":"Foi atingido o limite de sele\u00e7\u00f5es","Media":"M\u00eddia","Media Manager":"Gerenciador de M\u00eddia","Media Manager extension":"Extens\u00e3o do Media Manager","Media resize along with their container, e.g. 50% means half of the container size not half of the media size.":"Redimensionar a m\u00eddia junto com seu cont\u00eainer, por exemplo, 50% significa metade do tamanho do cont\u00eainer n\u00e3o metade do tamanho da m\u00eddia.","Message":"Mensagem","Messages":"Mensagens","Metadata export is not available for test \u0022%s.":"Exporta\u00e7\u00e3o de metadados n\u00e3o est\u00e1 dispon\u00edvel para o teste \u0022%s.","Metadata export is not available for the selected resource.":"Exporta\u00e7\u00e3o de metadados n\u00e3o est\u00e1 dispon\u00edvel para o recurso selecionado.","Microsoft Excel":"Microsoft Excel","Microsoft Excel binary format":"Formato bin\u00e1rio do Microsoft Excel","Microsoft Office Document Imaging":"Imagens de documentos do Microsoft Office","Microsoft Powerpoint":"Microsoft PowerPoint","Microsoft Powerpoint slideshow":"Slideshow do Microsoft Powerpoint","Microsoft Project file":"Arquivo do Microsoft Project","Microsoft Visio file":"Arquivo do Microsoft Visio","Microsoft Word":"Microsoft Word","Microsoft Word template":"Modelo do Microsoft Word","Microsoft Works file":"Arquivo Microsoft Works","Microsoft XPS file":"Arquivo Microsoft XPS","MIME encapsulation of aggregate HTML documents":"Encapsulamento MIME de documentos HTML agregados","Mind mapping software application (free mind open source)":"Aplicativo de software de mapeamento mental (c\u00f3digo aberto livre)","Minimum Duration":"Dura\u00e7\u00e3o m\u00ednima","Minimum duration : enforces the test taker to stay on the item for the given duration.":"Dura\u00e7\u00e3o m\u00ednima: obriga o candidato a ficar no item durante a dura\u00e7\u00e3o indicada.","Minimum duration for the test.":"Dura\u00e7\u00e3o m\u00ednima do teste.","Minimum duration for this item.":"Dura\u00e7\u00e3o m\u00ednima para este item.","Minimum duration for this section.":"Dura\u00e7\u00e3o m\u00ednima para esta se\u00e7\u00e3o.","Minimum duration for this test part.":"Dura\u00e7\u00e3o m\u00ednima para esta parte do teste.","minutes":"minutos","Missed test package file":"Arquivo do pacote do teste n\u00e3o encontrado","missing config %s for the service %s":"falta a configura\u00e7\u00e3o %s para o servi\u00e7o %s","Missing Parameters":"Par\u00e2metros ausentes","Missing required configuration option %s":"Falta a op\u00e7\u00e3o de configura\u00e7\u00e3o necess\u00e1ria %s","modulo":"m\u00f3dulo","More":"Mais","Move":"Mover","Move Down":"Mover para baixo","Move Left":"Mover \u00e0 esquerda","Move Right":"Mover \u00e0 direita","Move role":"Mover fun\u00e7\u00e3o","Move the cursor one step on the left":"Mova o cursor um passo \u00e0 esquerda","Move the cursor one step on the right":"Mova o cursor um passo \u00e0 direita","Move To":"Mover em","Move to":"Mover para","Move Up":"Mover para cima","Moving this element will replace the properties of the previous class by those of the destination class :":"Mover este elemento ir\u00e1 substituir as propriedades da classe anterior por aquelas da classe de destino :","Moving to the next item without time limit reached message.":"Mover para o pr\u00f3ximo item sem limite de tempo atingido mensagem.","MP4 video":"V\u00eddeo MP4","MPEG audio":"\u00c1udio MPEG","MPEG video":"V\u00eddeo do MPEG","MPEG-4 audio file":"Arquivos de \u00e1udio MPEG-4","Multiple values delimiter":"Separador de valores m\u00faltiplos","Multiply the value by 10^x":"Multiplique o valor por 10^x","Mute":"Mudo","My custom text translation":"Tradu\u00e7\u00e3o de texto personalizada","My settings":"Minhas configura\u00e7\u00f5es","My settings (%s)":"Minhas configura\u00e7\u00f5es (%s)","Name":"Nome","name of the pupil":"nome do aluno","Natural logarithm":"Logaritmo natural","Navigation":"Navega\u00e7\u00e3o","Navigation shortcuts":"Atalhos de navega\u00e7\u00e3o","Navigation Warnings":"Avisos de navega\u00e7\u00e3o","network":"rede","New blueprint":"Novo diagrama","New booklet":"Novo booklet","New class":"Nova pasta","New delivery":"Nova entrega","New Group":"Novo Grupo","New item":"Novo item","New passage":"Nova passagem","New password":"Nova senha","New role":"Nova fun\u00e7\u00e3o","New Rubric Block":"Novo Bloco de Rubrica","New section":"Nova Se\u00e7\u00e3o","New test":"Novo teste","New Test Center":"Novo Centro de Testes","New test part":"Nova parte de teste","New test-taker":"Novo candidato","Next":"Pr\u00f3ximo","Next match":"Pr\u00f3xima Correspond\u00eancia","Next page":"Pr\u00f3xima p\u00e1gina","Next Section":"Pr\u00f3xima se\u00e7\u00e3o","No":"N\u00e3o","No action specified":"Nenhuma a\u00e7\u00e3o especificada","No CAT Engine configured for CAT Endpoint \u0022%s\u0022.":"Nenhum motor CAT configurado para CAT Endpoint \u0022%s\u0022.","No client translation bundles updated":"Nenhum pacote de tradu\u00e7\u00e3o de cliente atualizado","No extensions available.":"Nenhuma extens\u00e3o dispon\u00edvel.","No file found at location \u0022%s\u0022.":"Nenhum arquivo encontrado na localiza\u00e7\u00e3o \u0022%s\u0022.","no file not found in this location":"nenhum arquivo encontrado neste local","No file selected":"Nenhum arquivo selecionado","No files":"Nenhum arquivo","No item found. Please try other search criteria.":"Nenhum item encontrado. Verifique os crit\u00e9rios de pesquisa.","no label":"sem r\u00f3tulos","No more attempts allowed for item \u0022%s\u0022.":"N\u00e3o h\u00e1 mais tentativas permitidas para o item \u0022%s\u0022.","no outcome declaration found":"nenhuma declara\u00e7\u00e3o de resultado encontrada","No outcome processing. Erase the existing rules, if any.":"Sem processamento de resultado. Limpe as regras existentes, se houver.","No reference to any IMS QTI Item found.":"Nenhuma refer\u00eancia a qualquer item IMS QTI encontrado.","No Task yet":"N\u00e3o h\u00e1 tarefas ainda","Node duplication has failed":"Falha ao duplicar n\u00f3","Non Linear":"N\u00e3o linear","None":"Vazio","Not all test could be exported":"Nem todo o teste p\u00f4de ser exportado","Not answered":"N\u00e3o respondido","Not enough arguments following: %s":"Argumentos insuficientes: %s","Not enough non-option arguments: got %s, need at least %s":"Argumentos n\u00e3o opcionais insuficientes: obteve %s, precisa de pelo menos %s","Not seen":"N\u00e3o visto","Not shuffled, the position remains fixed.":"N\u00e3o randomizado, a posi\u00e7\u00e3o permanece fixa.","Not started":"N\u00e3o iniciado","Nothing imported":"Nada foi importado","Nothing to install !":"Nada para instalar!","Nothing to list!":"Nada para exibir!","Notifications":"Notifica\u00e7\u00f5es","notifications drivers aren\\\\\u0027t persistent. use a persistent database storage driver":"notifica\u00e7\u00f5es drivers n\u00e3o s\u00e3o salvos. use um driver de armazenamento","Nth root":"Raiz en\u00e9sima","number":"n\u00famero","Number of elements":"N\u00famero de elementos","of":"de","Off":"Desligado","Offline":"Offline","Ogg Vorbis audio":"\u00c1udio Ogg Vorbis","Ogg Vorbis video":"V\u00eddeo Ogg Vorbis","OK":"OK","Ok":"Ok","OK & End test":"OK & Terminar teste","Old Password":"Senha Antiga","On":"Ligado","Once you close this section, you cannot return to it or change your answers.":"Depois de fechar esta se\u00e7\u00e3o, voc\u00ea n\u00e3o pode voltar a ela ou mudar suas respostas.","One or more dependent IMS QTI Items could not be imported.":"Um ou mais itens IMS QTI dependentes n\u00e3o puderam ser importados.","Online":"Online","Only allow for my own domain (%s)":"Permitir apenas meu dom\u00ednio (%s)","Only display the items marked for review":"Exibir apenas os itens marcados para revis\u00e3o","Only display the unanswered items":"Exibir apenas os itens n\u00e3o respondidos","Only one of the field must have value %s":"Apenas um dos campos deve ter o valor %s","Open Calculator":"Abrir Calculadora","Open Keyboard Shortcuts":"Atalhos de Teclado","OpenDocument Database":"Banco de dados OpenDocument","OpenDocument Presentation":"Apresenta\u00e7\u00e3o OpenDocument","OpenDocument spreadsheet document":"Documento de planilha OpenDocument","OpenDocument text document":"Documento de texto OpenDocument","OpenDocument Text Template":"Modelo de Texto OpenDocument","Operated By ":"Operado por ","Option A":"Op\u00e7\u00e3o A","Option B":"Op\u00e7\u00e3o B","or":"ou","Ordering":"Ordem","Outcome":"Resultado","Outcome declarations":"Declara\u00e7\u00f5es dos resultados","Outcome processing":"Processamento dos resultados","Outlook on the next Synchronization":"Outlook na pr\u00f3xima Sincroniza\u00e7\u00e3o","Package is valid but no tests were found. Make sure that it contains valid QTI tests.":"O pacote \u00e9 v\u00e1lido mas nenhum teste foi encontrado. Certifique-se de que ele cont\u00e9m testes v\u00e1lidos de QTI.","Page":"P\u00e1gina","Part %d":"Parte %d","Pascal file (.pas)":"Arquivo Pascal (.pas)","Passage":"Passagem","Password":"Senha","Password changed":"Senha Alterada","Password is empty.":"A senha est\u00e1 vazia.","Password reset":"Recuperar senha","Password successfully changed":"Senha alterada com sucesso","Passwords are not matching":"As senhas n\u00e3o coincidem","Pause":"Pausar","Pause the test":"Pausar o teste","PDF file":"Arquivo PDF","Permission denied to write in the selected class":"Permiss\u00e3o negada para escrever na classe selecionada","Plain text":"Texto sem formata\u00e7\u00e3o","Platform Theming":"Tema da plataforma","Play":"Reproduzir","Please confirm deletion":"Confirme a exclus\u00e3o","Please confirm deletion of %s.":"Por favor, confirme a exclus\u00e3o de %s.","Please confirm deletion of user %s":"Por favor, confirme a exclus\u00e3o do usu\u00e1rio %s","Please confirm locking of account %s":"Por favor confirme o bloqueio da conta %s","Please confirm property deletion!":"Por favor confirme a exclus\u00e3o da propriedade!","Please confirm this operation.":"Por favor confirme esta opera\u00e7\u00e3o.","Please confirm unlocking of account %s":"Por favor, confirme o desbloqueio da conta %s","Please confirm you want to leave the test.":"Por favor, confirme que deseja sair do teste.","Please contact %s or an administrator to release it":"Por favor contate %s ou um administrador para liber\u00e1-lo","Please contact your administrator.":"Por favor, contate o administrador.","Please contact your system administrator.":"Por favor, contate o administrador do sistema.","Please define your search in the search panel.":"Por favor, defina sua pesquisa no painel de pesquisa.","Please download the track and listen offline.":"Por favor, baixe a faixa e ou\u00e7a offline.","Please download the video and view offline.":"Por favor, baixe o v\u00eddeo e veja offline.","Please give a message to your commit":"Por favor informe o coment\u00e1rio para essa a\u00e7\u00e3o","Please provide a reason":"Por favor, informe um motivo","Please report bugs, ideas, comments or feedback on the TAO Forum":"Por favor, reporte bugs, ideias, coment\u00e1rios ou coment\u00e1rios no F\u00f3rum TAO","Please try reloading the page or pause the test. If you pause, you will be able to resume the test from this page.":"Por favor, tente recarregar a p\u00e1gina ou pause o teste. Se voc\u00ea pausar, voc\u00ea ser\u00e1 capaz de retomar o teste a partir desta p\u00e1gina.","Please wait":"Aguarde","Please wait while ...":"Por favor aguarde um instante...","Please wait while we try to restore the connection.":"Por favor, aguarde enquanto tentamos restaurar a conex\u00e3o.","PNG image":"Imagem PNG","Portable Custom Interactions":"Intera\u00e7\u00f5es Personalizadas Port\u00e1teis","Position":"Posi\u00e7\u00e3o","Positionals:":"Posicionais:","Post install processing":"Processamento da p\u00f3s-instala\u00e7\u00e3o","Power of":"Poder de","Power of 10":"Poder de 10","Prepare, publish deliveries sessions.":"Prepare, publique as sess\u00f5es de entregas.","Preparing installation...":"Preparando a instala\u00e7\u00e3o...","Preview":"Pr\u00e9-visualizar","Preview the covered area":"Pr\u00e9-visualizar a \u00e1rea coberta","Previous":"Anterior","Previous match":"Combina\u00e7\u00e3o anterior","Previous page":"P\u00e1gina anterior","Proceed":"Prosseguir","PROCEED & END ASSESSMENT":"CONTINUAR E FINALIZAR A AVALIA\u00c7\u00c3O","Proctor":"Vigilante","Proctorio Review":"Revis\u00e3o do Proctorio","Properties":"Propriedades","Provided URL is not valid":"A URL fornecida n\u00e3o \u00e9 v\u00e1lida","Publish":"Publicar","Publish the test":"Publicar o teste","QTI 2.1 Test Driver":"Driver de Teste QTI 2.1","QTI Item Runner":"Executor de teste QTI","QTI Test \u0022%s\u0022 publishing failed.":"Falha na publica\u00e7\u00e3o do QTI Test \u0022%s.","QTI Test \u0022%s\u0022 successfully published.":"Teste QTI \u0022%s\u0022 publicado com sucesso.","QTI Test Package 2.1":"Pacote de Teste QTI 2.1","QTI Test Package 2.2":"Pacote de Teste QTI 2.2","QTI\/APIP Test Content Package":"Pacote de conte\u00fado de teste QTI\/APIP","Question":"Pergunta","question":"pergunta","questions":"perguntas","Queued":"Na fila","Quicktime video":"V\u00eddeo Quicktime","Rad":"Rad","Radian":"Radianos","random":"aleat\u00f3rio","Random value":"Valor aleat\u00f3rio","Range field is required":"O campo Intervalo \u00e9 obrigat\u00f3rio","RAR archive":"Arquivo RAR","RDF":"RDF","RealMedia file":"Arquivo RealMedia","Record and manage test takers.":"Registre e gerencie os candidatos.","Reference":"Refer\u00eancia","Refresh":"Atualizar","Regenerate":"Regenerar","Release":"Lan\u00e7ar","Releasing the lock might cause the work in progress by %s to be lost.":"Liberar a trava pode fazer com que o trabalho em andamento por %s seja perdido.","Reload":"Recarregar","Reload the page":"Atualize a p\u00e1gina","remaining %s %s":"restante %s %s","Remind":"Lembrar","Remind Last":"Lembrar \u00faltimo","Remind the last value":"Lembrar o \u00faltimo valor","Remind the next expression in the history":"Lembrar a pr\u00f3xima express\u00e3o na hist\u00f3ria","Remind the previous expression in the history":"Lembrar a pr\u00f3xima express\u00e3o na hist\u00f3ria","Remind the recorded value":"Lembrar o valor gravado","Remote Lists":"Listas Remotas","Remove":"Excluir","Remove criteria":"Remover crit\u00e9rios","Remove Item Reference":"Remover refer\u00eancia do item","Remove Message":"Remover Mensagem","Remove Rubric Block":"Remover Bloco de Rubrica","Remove Section":"Remover Se\u00e7\u00e3o","Remove Test Part":"Remover Parte do Teste","Remove this file":"Remover esse arquivo","Removed %s directories":"%s diret\u00f3rios removidos","Removed %s files":"%s arquivos removidos","Removing: %s":"Removendo: %s","Rename":"Renomear","Repeat new password":"Repita a nova senha","Repeat password":"Repita a senha","Request a password reset via Email.":"Solicitar redefini\u00e7\u00e3o de senha por e-mail.","require notification ID":"requer notifica\u00e7\u00e3o ID","Required":"Obrigat\u00f3rio","required":"obrigat\u00f3rio","Required field":"Campo obrigat\u00f3rio","required length":"comprimento obrigat\u00f3rio","Requires":"Obrigat\u00f3rio","Reset":"Redefinir","Reset filters":"Redefinir filtros","Reset the range values":"Redefinir os valores de intervalo","Resource":"Recurso","Resource copied":"Recurso copiado","Resource deleted":"Recurso exclu\u00eddo","Resource Identifier":"Identificador do Recurso","Resource Identifier could not be copied to the clipboard":"Identificador do recurso n\u00e3o p\u00f4de ser copiado para a \u00e1rea de transfer\u00eancia","Resource Identifier has been copied to the clipboard":"Identificador do Recurso foi copiado para a \u00e1rea de transfer\u00eancia","Resource not imported due to multiple super classes":"Recurso n\u00e3o foi importado devido a m\u00faltiplas super classes","Resource not imported due to multiple types":"Recurso n\u00e3o importado devido a m\u00faltiplos tipos","Resource saved":"Recursos salvos","Resource(s) successfully exported.":"Recurso(s) eexportados com sucesso.","resources":"recursos","Resources moved":"Recursos movidos","Resources successfully indexed":"Recursos indexados com sucesso","Resources was not indexed":"Os recursos n\u00e3o foram indexados","Responsive mode":"Modo responsivo","Restore sound":"Restaurar som","Results":"Resultados","Results library":"Biblioteca de resultados","Results Monitoring":"Monitoramento de Resultados","Return to sign in page":"Retornar para a p\u00e1gina de login","Review":"Revisar","Review my Answers":"Revisar as minhas respostas","Rich Text Format file":"Formato de arquivo Rich Text","Right click the tree to manage your lists":"Clique com o bot\u00e3o direito na \u00e1rvore para gerenciar suas listas","Right parenthesis":"Par\u00eanteses direito","Role saved":"Fun\u00e7\u00e3o salva","Roles":"Fun\u00e7\u00f5es","Roles library":"Biblioteca de fun\u00e7\u00f5es","round":"rodada","Round to closest whole number":"Arredondar para o n\u00famero inteiro mais pr\u00f3ximo","Round to lower whole number":"Arredondar para baixo n\u00famero inteiro","Round to upper whole number":"Arredondar para cima n\u00famero inteiro","Row %s":"Linha %s","Rubric Block":"Bloco de Rubrica","Rubric Block Properties":"Propriedades do Bloco de Rubrica","Rubric Blocks":"Blocos de Rubrica","Running task %s":"Executando a tarefa: %s","Save":"Salvar","Saved":"Salvo","Saved.":"Salvo.","Scientific Calculator":"Calculadora cient\u00edfica","Scope":"Escopo","Scorer":"Pontoador","Scoring":"Pontua\u00e7\u00e3o","Scratch Pad":"Painel de rascuho","Scratch pad":"Painel de rascunho","Scratchpad":"Painel de rascunho","Search":"Pesquisa","Search %s":"Buscar %s","Search by properties":"Pesquisa por propriedades","Search sub-classes":"Pesquisar sub-classes","second":"segundo","seconds":"segundos","Section %d":"Se\u00e7\u00e3o %d","Section %d of %d":"Se\u00e7\u00e3o %d de %d","Section level category enables configuring the categories of its composing items all at once. A category in gray means that all items have that category. A category in white means that only a few items have that category.":"A categoria do n\u00edvel de se\u00e7\u00e3o permite configurar as categorias de seus itens compondo todos de uma s\u00f3 vez. Uma categoria em cinza significa que todos os itens t\u00eam essa categoria. Uma categoria em branco significa que apenas alguns itens t\u00eam essa categoria.","Section Properties":"Propriedades da se\u00e7\u00e3o","Security":"Seguran\u00e7a","Security settings":"Op\u00e7\u00f5es de seguran\u00e7a","select":"selecionar","Select":"Selecionar","Select a blueprint":"Selecione um diagrama","Select a class":"Selecione uma classe","Select a destination":"Selecione um destino","Select a language":"Selecione um idioma","Select Items":"Selecionar itens","Select loaded %s":"Selecionar %s carregado","Select the way the responses of your test should be processed":"Selecione a forma como as respostas do seu teste devem ser processadas","Select this file":"Selecione este arquivo","select...":"selecionar...","Selected":"Selecionado","Selected file:":"Selecione um arquivo:","Selection":"Sele\u00e7\u00e3o","Selection saved successfully":"Sele\u00e7\u00e3o salva com sucesso","self-locked":"autobloqueado","Send":"Mandar","Set outcome identifier the feedback block is related to":"Definir identificador de resultado que o bloco de feedback est\u00e1 relacionado com","Set the cut score (or pass score ratio) associated to the test. It must be a float between 0 and 1.":"Defina a pontua\u00e7\u00e3o de corte (ou taxa de pontua\u00e7\u00e3o) associada ao teste. Deve ser um ponto flutuante entre 0 e 1.","Set the rubric block as a feedback block":"Defina o bloco de rubrica como um bloco de feedback","Set the trigonometric function to work in degrees":"Defina a fun\u00e7\u00e3o trigonom\u00e9trica para funcionar em graus","Set the trigonometric function to work in radians":"Defina a fun\u00e7\u00e3o trigonom\u00e9trica para trabalhar em radianos","Set the value of the outcome that will activate the feedback block":"Defina o valor do resultado que ir\u00e1 ativar o bloco de feedback","Set the weight identifier used to process the score":"Definir o identificador de peso usado para processar a pontua\u00e7\u00e3o","Set the XHTML-QTI class of the rubric block.":"Defina a classe XHTML-QTI do bloco rubricado.","Set the XHTML-QTI identifier of the rubric block.":"Defina o identificador XHTML-QTI do bloco de rubrica.","SetSynchronisationService successfully configured.":"Servi\u00e7o configurado com sucesso.","Settings":"Confirgura\u00e7\u00f5es","Settings updated":"Configura\u00e7\u00f5es atualizadas","Show detailed report":"Mostrar relat\u00f3rio detalhado","Show Feedback":"Mostrar Feedback","Show password":"Mostrar senha","Show Review":"Exibir avalia\u00e7\u00e3o","Show Solution":"Mostrar Solu\u00e7\u00e3o","Show the review screen":"Mostrar a tela de revis\u00e3o","Show the solution once the answer is submitted.":"Mostrar a solu\u00e7\u00e3o quando a resposta for submetida.","Show timers":"Mostrar temporizadores","Show\/Hide the review screen":"Mostrar\/Ocultar a tela de revis\u00e3o","Shuffle":"Aleat\u00f3rio","Sibelius music notation":"Nota\u00e7\u00e3o musical do Sibelius","Sign change":"Mudar sinal","Simultaneous":"simult\u00e2neo","sin":"seno","Sine":"Seno","sinh":"sinh","Size":"Tamanho","Skip":"Ignorar","Skip & End Test":"Ignorar & Testar o Fim","Skip and end test":"Pular e terminar teste","Skip and go to the end of the test":"Pular e ir para o final do teste","Skip and go to the next item":"Pular e ir para o pr\u00f3ximo item","Skip to the end of the test":"Pular para o final do teste","Skip to the next item":"Pular para o pr\u00f3ximo item","Skip to the next section":"Pular para a pr\u00f3xima se\u00e7\u00e3o","Some resources have not been moved: %s":"Alguns recursos n\u00e3o foram movidos: %s","Some rubric blocks have been updated to reflect the changes in the list of outcomes.":"Alguns blocos de rubrica foram atualizados para refletir as altera\u00e7\u00f5es na lista de resultados.","Something unexpected happened.":"Algo inesperado aconteceu.","Something went wrong...":"Algo deu errado...","Sources that can embed this platform in an iFrame":"Fontes que podem incorporar essa plataforma em um iFrame","Speech speed":"Velocidade da fala","Speed":"Velocidade","Square root":"Raiz quadrada","Start of document reached. Continuing from the end.":"In\u00edcio do documento alcan\u00e7ado. Continuando do final.","Start Test-part":"Iniciar Teste-parte","Status":"Status","Status of":"Status de","Step 1: Download the file (see below when available).":"Passo 1: Baixar o arquivo (veja abaixo quando dispon\u00edvel).","Step 2: When you are online again, please send the downloaded file to receptionbaseline@nfer.ac.uk together with the ":"Passo 2: Quando estiver online novamente, envie o arquivo baixado para receptionbaseline@nfer.ac.uk junto com o ","Step 3: Once you have downloaded the file you can close the browser window. Do not do anything further with the assessment whilst it shows as \u0022in progress\u0022; the status will be updated once the helpline has uploaded the file.":"Passo 3: Depois de fazer o download do arquivo, voc\u00ea pode fechar a janela do navegador. N\u00e3o fa\u00e7a nada mais com a avalia\u00e7\u00e3o enquanto ela se revela como \u0022em progresso\u0022; o status ser\u00e1 atualizado assim que a linha de ajuda tiver carregado o arquivo.","Stop":"Interromper","Store":"Armazenar","Store the value a variable":"Armazenar o valor de uma vari\u00e1vel","Storing digital video data on a computer game":"Armazenando dados de v\u00eddeo digitais em uma partida de computador","string":"string","Submission":"Submiss\u00e3o","Submit":"Enviar","Submit and go to the end of the test":"Enviar e ir para o final do teste","Submit and go to the item %s":"Submeter e ir para o item %s","Submit and go to the next item":"Enviar e ir para o pr\u00f3ximo item","Submit and go to the previous item":"Enviar e ir para o item anterior","SUBMIT THE TEST":"ENVIAR O TEST","SUBMIT THIS PART":"ENVIAR ESTA PARTE","Success":"Sucesso","Successfully cloned instance as %s":"Inst\u00e2ncia clonada com sucesso como %s","Successfully deleted %s":"Exclu\u00eddo com sucesso %s","Successfully imported \u0022%s":"Importado com sucesso \u0022%s","Successfully imported %s tripples":"%s tripples foram importados com sucesso","Successfully imported class \u0022%s":"Classe importada com sucesso \u0022%s","Successfully updated %s client translation bundles":"%s pacotes de tradu\u00e7\u00e3o de cliente atualizados com sucesso","Supported export formats":"Formatos de exporta\u00e7\u00e3o suportados","SVG image":"Imagem SVG","Syntax error":"Erro de sintaxe","Tagged image file":"Arquivo de imagem marcados","tan":"tan","Tangent":"Tangente","tanh":"tanh","Tao Default Theme":"Tema Padr\u00e3o do Tao","TAO Home":"TAO In\u00edcio","TAO Login":"TAO Login","TAO platform is already on live mode since %s":"Plataforma TAO j\u00e1 est\u00e1 ativa desde %s","TAO platform is already on maintenance mode since %s":"Plataforma TAO j\u00e1 est\u00e1 em modo de manuten\u00e7\u00e3o desde %s","TAO platform is live since %s.":"Plataforma TAO est\u00e1 ativa desde %s.","TAO platform is now live. It was in maintenance since %s":"A plataforma de TAO est\u00e1 ativa. Estava em manuten\u00e7\u00e3o desde %s","TAO platform is now under maintenance. It was in maintenance since %s":"Plataforma TAO agora est\u00e1 em manuten\u00e7\u00e3o. Estava em manuten\u00e7\u00e3o desde %s","TAO platform is under maintenance since %s":"Plataforma TAO est\u00e1 em manuten\u00e7\u00e3o desde %s","Task %s has been cancelled, message was not processed.":"Tarefa %s foi cancelada, mensagem n\u00e3o foi processada.","task created":"tarefa criada","Task Listing":"Listas de Tarefas","Task Name":"Nome da tarefa","Task not found":"Tarefa n\u00e3o encontrada","Task not returned any report.":"Tarefa n\u00e3o retornou nenhum relat\u00f3rio.","Task Queue":"Lista de tarefas","taskqueue":"taskqueue","technical":"t\u00e9cnico","Term":"Termo","Test":"Teste","Test center contains test taker and groups and are administer by a proctor":"Centro de testes cont\u00e9m taker e grupos de teste e s\u00e3o administrados por um perfil","Test Center List":"Lista Central de Teste","Test Centers":"Centros de teste","Test constructor":"Criador de testes","Test metadata successfully exported.":"Metadados de teste exportados com sucesso.","Test Navigation":"Navega\u00e7\u00e3o do Teste","Test Part Properties":"Propriedades da Parte do Teste","Test Preview is not installed, please contact to your administrator.":"Pr\u00e9-visualiza\u00e7\u00e3o de teste n\u00e3o est\u00e1 instalada, por favor, contate o administrador.","Test Saved":"Teste salvo","Test status":"Status do teste","Test status and review structure":"Estado de teste e revis\u00e3o da estrutura","Test Status and Structure":"Estado e estrutura do teste","Test takers library":"Biblioteca de candidatos","Test-Taker Tools":"Ferramentas do candidato","Test-takers":"Candidatos","Tests":"Testes","Tests library":"Biblioteca de testes","TeX file":"Arquivo TeX","Text - Long - Box":"Texto Longo - Caixa","Text - Long - HTML editor":"Texto - Longo - Editor HTML","Text - Short - Field":"Texto - Curto - Campo","Text content":"Conte\u00fado do teste","Text document file format (Staroffice)":"Formato de arquivo de documento de texto (Staroffice)","Text to Speech":"Texto para voz","Text To Speech":"Texto para voz","that was being taken.":"que estava sendo feito.","The \u0027imsmanifest.xml\u0027 file found in the archive is not valid.":"O arquivo \u0027imsmanifest.xml\u0027 encontrado no arquivo n\u00e3o \u00e9 v\u00e1lido.","The action will be applied to":"A a\u00e7\u00e3o ser\u00e1 aplicada em","The action will be applied to the following":"A a\u00e7\u00e3o ser\u00e1 aplicada para os seguintes","The action will not be applied to ":"A a\u00e7\u00e3o n\u00e3o ser\u00e1 aplicada em ","The action will not be applied to the following":"A a\u00e7\u00e3o n\u00e3o ser\u00e1 aplicada para os seguintes","the assessment (LCL or Mathematics)":"a avalia\u00e7\u00e3o (LCL ou Matem\u00e1tica)","The assessment has been created but is not already running.":"A avalia\u00e7\u00e3o foi criada, mas ainda n\u00e3o est\u00e1 em execu\u00e7\u00e3o.","The assessment has been suspended by an authorized proctor. If you wish to resume your assessment, please relaunch it and contact your proctor if required.":"A avalia\u00e7\u00e3o foi suspensa por um vigilante autorizado. Se voc\u00ea deseja retomar sua avalia\u00e7\u00e3o, por favor, reinicie-a e entre em contato com seu vigilante, se necess\u00e1rio.","The assessment has been suspended. To resume your assessment, please relaunch it and contact your proctor if required.":"A avalia\u00e7\u00e3o foi suspensa. Para retomar sua avalia\u00e7\u00e3o, por favor, reinicie-a e entre em contato com seu vigilante, se necess\u00e1rio.","The assessment has been suspended. To resume your assessment, please relaunch it.":"A avalia\u00e7\u00e3o foi suspensa. Para retomar a avalia\u00e7\u00e3o, por favor, reinicie-a.","The assessment has been terminated.":"A avalia\u00e7\u00e3o foi terminada.","The assessment has been terminated. You cannot interact with it anymore.":"A avalia\u00e7\u00e3o foi terminada. Voc\u00ea n\u00e3o pode interagir mais com ela.","The assessment is still running.":"A avalia\u00e7\u00e3o ainda est\u00e1 em curso.","The candidate is not allowed to submit invalid responses.":"O candidato n\u00e3o pode enviar respostas inv\u00e1lidas.","The column ":"A coluna ","The connection seems to be back, please proceed":"A conex\u00e3o parece estar de volta, por favor prossiga","the current question":"a pergunta atual","The dependency to the IMS QTI AssessmentItemRef \u0022%s\u0022 in the IMS Manifest file could not be resolved.":"A depend\u00eancia do IMS QTI AssessmentItemRef \u0022%s no arquivo do Manifesto IMS n\u00e3o p\u00f4de ser resolvida.","The download will be available in %d<\/strong> seconds":"O download estar\u00e1 dispon\u00edvel em %d<\/strong> segundos","The following criteria are not applicable to the selected class and have been removed from the search":"Os seguintes crit\u00e9rios n\u00e3o s\u00e3o aplic\u00e1veis \u00e0 classe selecionada e foram removidos da pesquisa","The following identifiers are not unique accross the test : %s":"Os seguintes identificadores n\u00e3o s\u00e3o originais \u00fanicos no teste: %s","The format of this field is not valid.":"O formato deste campo n\u00e3o \u00e9 v\u00e1lido.","The identifier of the item reference.":"O identificador da refer\u00eancia do item.","The identifier of the section.":"O identificador da se\u00e7\u00e3o.","The imported file is not properly UTF-8 encoded.":"O arquivo importado n\u00e3o \u00e9 codificado corretamente em UTF-8.","The IMS QTI Item referenced as \u0022%s\u0022 in the IMS Manifest file was already stored in the Item Bank.":"O item IMS QTI referenciado como \u0022%s\u0022 no arquivo IMS Manifesto j\u00e1 estava armazenado no Item Bank.","The IMS QTI Test Package could not be imported.":"O pacote de teste IMS QTI n\u00e3o p\u00f4de ser importado.","The IMS QTI Test referenced as \u0022%s\u0022 in the IMS Manifest file could not be imported.":"O teste IMS QTI referenciado como \u0022%s\u0022 no arquivo IMS Manifest n\u00e3o p\u00f4de ser importado.","The index identifier should not be empty":"O identificador de \u00edndice n\u00e3o deve estar vazio","The lock has been released":"O bloqueio foi liberado","The navigation mode determines the general paths that the candidate may take. A linear mode restricts the candidate to attempt each item in turn. Non Linear removes this restriction.":"O modo de navega\u00e7\u00e3o determina os caminhos gerais que o candidato pode seguir. O modo linear restringe o candidato a tentar cada item em volta. N\u00e3o linear remove esta restri\u00e7\u00e3o.","The number of child elements to be selected.":"O n\u00famero de elementos a serem seleccionados.","The outcome \u0022%s\u0022 does not exist, but it is referenced by a feedback block!":"O resultado \u0022%s n\u00e3o existe, mas \u00e9 referenciado por um bloco de feedback!","The outcomes have been regenerated!":"Os resultados foram regenerados!","The principle identifier of the test.":"O identificador principal do teste.","The properties of the source class will be replaced by those of the destination class. This might result in a loss of metadata. Continue anyway?":"As propriedades da classe de origem ser\u00e3o substitu\u00eddas por aquelas da classe de destino. Isto pode resultar em perda de metadados. Continuar mesmo assim?","The provided archive is invalid. Make sure it is not corrupted and that it contains an \u0027imsmanifest.xml\u0027 file.":"O arquivo fornecido \u00e9 inv\u00e1lido. Verifique se ele n\u00e3o est\u00e1 corrompido e se cont\u00e9m um arquivo \u0027imsmanifest.xml\u0027.","The QTI Test \u0027%s\u0027 to be compiled must contain at least 1 QTI Item. None found.":"O teste QTI \u0027%s\u0027 para ser compilado deve conter pelo menos 1 Item QTI. Nenhum encontrado.","The QTI test could not be retrieved correctly.":"O teste QTI n\u00e3o p\u00f4de ser recuperado corretamente.","The QTI Test XML or one of its dependencies is malformed or empty.":"O teste QTI XML ou uma de suas depend\u00eancias est\u00e1 malformado ou vazio.","The QTI-XML test could not be written correctly.":"O teste QTI-XML n\u00e3o p\u00f4de ser escrito corretamente.","The reference.":"A refer\u00eancia.","The resources related to the IMS QTI Test referenced as \u0022%s\u0022 in the IMS Manifest file were rolled back.":"Os recursos relacionados ao teste IMS QTI referenciado como \u0022%s no arquivo do Manifesto IMS foram devolvidos.","The score will be processed for the entire test. A sum of all SCORE outcomes will be computed and divided by the sum of MAX SCORE, the result will be compared to the cut score (or pass ratio), then the PASS_TOTAL outcome will be set accordingly.":"A pontua\u00e7\u00e3o ser\u00e1 processada para o teste inteiro. Uma soma de todos os resultados SCORE ser\u00e1 calculada e dividida pela soma M\u00c1X SCORE, o resultado ser\u00e1 comparado com a pontua\u00e7\u00e3o de corte (ou taxa de aprova\u00e7\u00e3o), ent\u00e3o o resultado PASS_TOTAL ser\u00e1 definido em conformidade.","The score will be processed for the entire test. A sum of all SCORE outcomes will be computed, the result will take place in the SCORE_TOTAL outcome.":"A pontua\u00e7\u00e3o ser\u00e1 processada para o teste inteiro. Uma soma de todos os resultados SCORE ser\u00e1 calculada, o resultado ter\u00e1 lugar no resultado do SCORE_TOTAL .","The section title.":"O t\u00edtulo da turma.","The server has sent an empty response":"O servidor enviou uma resposta vazia","The submission mode determines when the candidate\u0027s responses are submitted for response processing. A testPart in individual mode requires the candidate to submit their responses on an item-by-item basis. In simultaneous mode the candidate\u0027s responses are all submitted together at the end of the testPart.":"O modo de envio determina quando as respostas do candidato s\u00e3o enviadas para o processo de resposta. Uma testPart no modo individual requer que o candidato envie suas respostas de acordo com o item. Ao mesmo tempo, as respostas do candidato s\u00e3o todas enviadas em conjunto no final do teste.","The test has been closed\/suspended!":"O teste foi fechado\/suspenso!","The test has been paused, we are unable to connect to the server.":"O teste foi pausado, n\u00e3o foi poss\u00edvel conectar ao servidor.","The test has not been saved.":"O teste n\u00e3o foi salvo.","The test has unsaved changes, are you sure you want to leave?":"O teste possui altera\u00e7\u00f5es n\u00e3o salvas. Tem certeza que deseja sair?","The test has unsaved changes, would you like to save it?":"O teste tem altera\u00e7\u00f5es n\u00e3o salvas. Deseja salv\u00e1-las?","The test needs to be saved before it can be previewed.":"O teste precisa ser salvo antes de ser pr\u00e9-visualizado.","The test part identifier.":"Identificador da pe\u00e7a de teste.","The test title.":"O t\u00edtulo do teste.","The time limit has been reached for this part of the test.":"O tempo limite foi atingido para esta parte do teste.","The uploaded file is too large (maximum %s bytes)":"O arquivo enviado \u00e9 muito grande (m\u00e1ximo %s bytes)","The value for the property \u0022%s\u0022 must be unique.":"O valor para a propriedade \u0022%s\u0022 deve ser \u00fanico.","The value of this field must be a valid date format, e.g. YYYY-MM-DD":"O valor deste campo deve ser um formato de data v\u00e1lido, por exemplo AAAA-MM-DD","The value of this field must be an integer":"O valor deste campo deve ser um n\u00famero inteiro","The value of this field must be numeric":"O valor deste campo deve ser num\u00e9rico","There are %s unanswered questions":"H\u00e1 %s perguntas n\u00e3o respondidas","There are %s unanswered questions in this part of the test":"H\u00e1 %s perguntas em aberto nesta parte do teste","There is %s unanswered question":"H\u00e1 %s perguntas n\u00e3o respondidas","There is %s unanswered question in this part of the test":"H\u00e1 %s pergunta n\u00e3o respondida nesta parte do teste","There is not outcomes!":"N\u00e3o h\u00e1 resultados!","There is nothing to list!":"N\u00e3o h\u00e1 nada para listar!","This %s is currently checked out":"Este %s est\u00e1 atualmente check-out","This constraint affects the visibility of feedback after the end of the last attempt.":"Esta restri\u00e7\u00e3o afeta a visibilidade do feedback ap\u00f3s o fim da \u00faltima tentativa.","This constraint controls whether or not the candidate is allowed to provide a comment on the item during the session. Comments are not part of the assessed responses.":"Esta restri\u00e7\u00e3o controla se o candidato pode ou n\u00e3o fornecer um coment\u00e1rio sobre o item durante a sess\u00e3o. Os coment\u00e1rios n\u00e3o fazem parte das respostas avaliadas.","This field is required":"Este campo \u00e9 obrigat\u00f3rio","This field is too long":"Este campo \u00e9 muito longo","This field is too short":"Este campo \u00e9 muito curto","This functionality is blocked. Please contact with your administrator for more details.":"Esta funcionalidade est\u00e1 bloqueada. Entre em contato com o seu administrador para mais detalhes.","This is not a valid email address.":"Este n\u00e3o \u00e9 um endere\u00e7o de e-mail v\u00e1lido.","this is required":"campo obrigat\u00f3rio","This Login is already in use":"Login j\u00e1 est\u00e1 em uso","This media cannot be played!":"Esta m\u00eddia n\u00e3o pode ser reproduzida!","This or one of %s must have a value":"Este ou um dos %s devem ter um valor","This password reset link is no longer valid. It may have already been used. If you still wish to reset your password please request a new link":"Este link de redefini\u00e7\u00e3o de senha n\u00e3o \u00e9 mais v\u00e1lido. Ele pode ter j\u00e1 sido usado. Se voc\u00ea ainda deseja redefinir sua senha, solicite um novo link","This resource is locked":"Este recurso est\u00e1 bloqueado","This role is still given to one or more users. Please remove the role to these users first.":"Esta fun\u00e7\u00e3o ainda \u00e9 dado a um ou mais usu\u00e1rios. Por favor, remova a primeira fun\u00e7\u00e3o para esses usu\u00e1rios.","This should equal %s":"Isto deve ser igual a %s","This should not equal %s":"Isto n\u00e3o deve ser igual a %s","This test has been suspended":"Este teste foi suspenso","This test has been terminated":"Este teste foi finalizado","time":"hora","Time limit reached for item \u0022%s\u0022.":"Tempo limite atingido para o item \u0022%s\u0022.","Time limit reached, this part of the test has ended.":"Tempo limite atingido, esta parte do teste terminou.","Time limit reached, this part of the test has ended. However you are allowed to finish the current item.":"Limite de tempo atingido, esta parte do teste terminou. No entanto, voc\u00ea pode terminar o item atual.","Time Limits":"Limite de tempo","Time Remaining":"Tempo Restante","Time zone":"Fuso hor\u00e1rio","Title":"T\u00edtulo","to":"para","To reset your password, click the link below, or paste it into your browser. You will then be prompted to create a new password.":"Para redefinir sua senha, clique no link abaixo ou cole-o no seu navegador. Em seguida, ser\u00e1 solicitado a criar uma nova senha.","to translate":"para traduzir","Toggle multiple\/single selection":"Alternar sele\u00e7\u00e3o m\u00faltipla\/\u00fanica","Toggle playback":"Ativar\/desativar reprodu\u00e7\u00e3o","Toggle start from here mode":"Alternar in\u00edcio a partir daqui modo","Toggle the date time picker":"Alternar o seletor de data e hora","Tokenizer":"Tokenizer","Too many non-option arguments: got %s, maximum of %s":"Muitos argumentos n\u00e3o-op\u00e7\u00e3o: obteve %s, m\u00e1ximo de %s","Too many suggestions match your query. Only a few are listed":"Muitas sugest\u00f5es correspondem \u00e0 sua consulta. Somente algumas est\u00e3o listadas","Tool box and flagging for review":"Caixa de ferramentas e sinaliza\u00e7\u00e3o para revis\u00e3o","Toolbox":"Ferramentas","Tools":"Ferramentas","Total duration":"Dura\u00e7\u00e3o total","Total score":"Pontua\u00e7\u00e3o total","Total:":"Total:","Translate":"Traduzir","Translate to":"Traduzir para","Translation parameters":"Par\u00e2metros de tradu\u00e7\u00e3o","Translation saved":"Tradu\u00e7\u00e3o salva","Transport Layer Security":"Seguran\u00e7a de Camada de Transporte","Tree - Multiple node choice ":"\u00c1rvore - M\u00faltipla escolha de n\u00f3 ","Tree values":"Valores da \u00e1rvore","Trees":"\u00c1rvores","True":"Verdadeiro","trunc":"trunc","Try connecting the machine to the internet via cable, or move to an area with a better wifi signal.":"Tente conectar a m\u00e1quina \u00e0 internet via cabo ou mova para uma \u00e1rea com um sinal de Wi-Fi melhor.","Tutor":"Professor","Type":"tipo","Type your search":"Digite sua pesquisa","Unable to access your account?":"N\u00e3o foi poss\u00edvel acessar sua conta?","Unable to change passwords in demo mode":"N\u00e3o foi poss\u00edvel alterar a senha em modo de demonstra\u00e7\u00e3o","Unable to copy the resource":"N\u00e3o \u00e9 poss\u00edvel copiar o recurso","Unable to delete %s":"N\u00e3o foi poss\u00edvel excluir %s","Unable to delete the selected resource":"N\u00e3o \u00e9 poss\u00edvel excluir o recurso selecionado","Unable to delete the selected resources":"N\u00e3o foi poss\u00edvel excluir os recursos selecionados","Unable to move the resources":"N\u00e3o \u00e9 poss\u00edvel mover recursos","Unable to open file %s":"N\u00e3o foi poss\u00edvel abrir o arquivo %s","Unable to process your request":"N\u00e3o foi poss\u00edvel processar sua solicita\u00e7\u00e3o","Unable to release the lock":"Imposs\u00edvel liberar o bloqueio","Unable to remove the property.":"N\u00e3o \u00e9 poss\u00edvel remover a propriedade.","Unable to save resource.":"N\u00e3o foi poss\u00edvel salvar o recurso.","unable to search into notification.":"n\u00e3o \u00e9 poss\u00edvel pesquisar na notifica\u00e7\u00e3o.","Unable to send the password reset request":"N\u00e3o foi poss\u00edvel enviar a solicita\u00e7\u00e3o de recupera\u00e7\u00e3o de senha","Unable to update the asset service":"N\u00e3o \u00e9 poss\u00edvel atualizar o servi\u00e7o","Unable to upload file":"N\u00e3o foi poss\u00edvel enviar o arquivo","Unable to upload file %s : %s":"N\u00e3o foi poss\u00edvel enviar o arquivo %s : %s","Unanswered":"Sem resposta","Unanswered items":"Itens n\u00e3o respondidos","Unary operator +":"Operador Un\u00e1rio +","Unary operator -":"Operador Un\u00e1rio -","Uncheck All":"Desmarcar todos","Uncheck all":"Desmarcar todos","Undefined amount of the pages for pagination":"Valor indefinido das p\u00e1ginas para pagina\u00e7\u00e3o","Undefined amount of the totalPages for pagination":"Quantidade indefinida de p\u00e1ginas totais para pagina\u00e7\u00e3o","Undo":"Desfazer","Unexpected error. Please contact administrator":"Ocorreu um erro inesperado. Por favor, contate o administrador do sistema","Unflag for Review":"Remover sinaliza\u00e7\u00e3o para revis\u00e3o","Uninstall of %s failed":"Falha ao desinstalar de %s","Uninstalled %s":"%s desinstalado","UNIX Compressed Archive File":"Arquivo de Compress\u00e3o UNIX","unknown":"desconhecido","Unknown Error":"Erro desconhecido","Unknown error.":"Erro Desconhecido","unkown task id %s":"id tarefa desconhecido %s","Unlink to use separated durations":"Desvincular para usar dura\u00e7\u00f5es separadas","Unseen":"Unseen","Update":"Atualizar","Update ID : %s":"Atualizar o ID : %s","Updating search index":"Atualizando \u00edndice de busca","Upload":"Enviar","Upload a QTI 2.1 Test Package File":"Enviar um arquivo de teste QTI 2.1","Upload into:":"Enviar para:","URI Path":"URI Path","Usage: ImportRdf RDF_FILE [MODEL_ID]":"Uso: ImportRdf RDF_FILE [MODEL_ID]","USAGE: please provide a valid extension id as second parameter":"USO: Por favor, forne\u00e7a um ID de extens\u00e3o v\u00e1lido como segundo par\u00e2metro","USAGE: please provide a valid service name as first parameter":"USO: Por favor, forne\u00e7a um nome de servi\u00e7o v\u00e1lido como primeiro par\u00e2metro","USAGE: please provide the service name as first parameter":"USO: por favor, forne\u00e7a o nome de servi\u00e7o como primeiro par\u00e2metro","Use a term":"Utilizar um termo","Use a variable":"Utilizar uma vari\u00e1vel","Use Ctrl\/Meta key or Lasso for multiple selection":"Usar a tecla Ctrl\/Meta ou La\u00e7o para sele\u00e7\u00e3o m\u00faltipla","Use default value":"Utilizar valor padr\u00e3o","User":"Usu\u00e1rio","User %s can not be locked":"O usu\u00e1rio %s n\u00e3o pode ser bloqueado","User %s can not be unlocked":"O usu\u00e1rio %s n\u00e3o pode ser desbloqueado","User %s successfully locked":"Usu\u00e1rio %s bloqueado com sucesso","User %s successfully unlocked":"Usu\u00e1rio %s desbloqueado com sucesso","User added":"Usu\u00e1rio adicionado","User deleted successfully":"Usu\u00e1rio exclu\u00eddo com sucesso","User deletion not permitted on a demo instance":"Exclus\u00e3o de usu\u00e1rio n\u00e3o permitida em uma inst\u00e2ncia de demonstra\u00e7\u00e3o","User not found":"Usu\u00e1rio n\u00e3o encontrado","User saved":"Usu\u00e1rio salvo","User Settings":"Configura\u00e7\u00f5es de Usu\u00e1rio","Users":"Usu\u00e1rios","Validate Responses":"Validar respostas","Validation for field \u0027%s\u0027 has failed. Language does not exist in the system":"Valida\u00e7\u00e3o do campo \u0027%s\u0027 falhou. Idioma n\u00e3o existe no sistema","Validation for field \u0027%s\u0027 has failed. List of values expected":"Valida\u00e7\u00e3o do campo \u0027%s\u0027 falhou. Lista de valores esperados","Validation for field \u0027%s\u0027 has failed. Should not be empty":"Valida\u00e7\u00e3o do campo \u0027%s\u0027 falhou. N\u00e3o deve estar vazio","Validation for field \u0027%s\u0027 has failed. Valid role expected. Given: %s":"Valida\u00e7\u00e3o para o campo \u0027%s\u0027 falhou. Fun\u00e7\u00e3o v\u00e1lida esperada. Dado: %s","Validation for field \u0027%s\u0027 has failed. Valid URI expected":"Falha na valida\u00e7\u00e3o do campo \u0027.%s\u0027. URI v\u00e1lido esperado","Validation for field \u0027%s\u0027 has failed. Valid URI expected. Given: %s":"A valida\u00e7\u00e3o do campo \u0027%s\u0027 falhou. URI v\u00e1lido esperado. Dado: %s","Value":"Valor","Value ellipsis":"Elipses de valor","Value of E":"Valor de E","Value of PI":"Valor do PI","Variable":"Vari\u00e1vel","Variable %s":"Vari\u00e1vel %s","Version":"Vers\u00e3o","View":"Visualizar","View and format the collected results.":"Visualizar e formatar os resultados coletados.","View report":"Visualizar relat\u00f3rio","View resources as a list":"Ver recursos como uma lista","View resources as a tree":"Ver recursos como uma \u00e1rvore","View Tree":"Ver \u00c1rvore","Viewed":"Visualizado","Viewed items":"Itens visualizados","Views":"Visualiza\u00e7\u00f5es","Visible":"Vis\u00edvel","Volume":"Volume","Wait for your connection to come back online.":"Aguarde a sua conex\u00e3o voltar online.","Wait is over":"Espera acabou","Waiting":"Aguardando","Warning \u2013 You have %s remaining to complete the test.":"Aviso \u2013 Voc\u00ea tem %s para concluir o teste.","Warning \u2013 You have %s remaining to complete this item.":"Aviso \u2013 Voc\u00ea tem %s restante(s) para completar este item.","Warning \u2013 You have %s remaining to complete this section.":"Aviso \u2013 Voc\u00ea tem %s restantes para completar esta se\u00e7\u00e3o.","Warning \u2013 You have %s remaining to complete this test part.":"Aviso \u2013 Voc\u00ea tem %s restante para completar esta parte do teste.","Warning \u2013 You have %s remaining to complete this test.":"Aviso \u2013 Voc\u00ea tem %s restante(s) para completar este item.","WAV audio":"\u00c1udio WAV","We are unable to connect the server to submit your results.":"N\u00e3o foi poss\u00edvel conectar com o servidor para enviar seus resultados.","We are unable to connect to the server to retrieve the next item.":"N\u00e3o foi poss\u00edvel conectar ao servidor para recuperar o pr\u00f3ximo item.","WebM audio":"\u00c1udio WebM","WebM video":"WebM v\u00eddeo","Weight":"Peso","Weights":"Pesos","When selecting child elements each element is normally eligible for selection once only.":"Ao selecionar elementos filhos, cada elemento normalmente \u00e9 eleg\u00edvel para sele\u00e7\u00e3o apenas uma vez.","Whether a candidate\u0027s response that is beyond the maximum duration of the item should still be accepted.":"Se a resposta de um candidato que exceda o prazo m\u00e1ximo de dura\u00e7\u00e3o do item dever\u00e1 continuar a ser aceite.","Whether a candidate\u0027s response that is beyond the maximum duration of the section should still be accepted.":"Se a resposta de um candidato que exceda o per\u00edodo m\u00e1ximo de tempo da sec\u00e7\u00e3o deve ainda ser aceite.","Whether a candidate\u0027s response that is beyond the maximum duration of the test part should still be accepted.":"Se a resposta de um candidato que exceda a dura\u00e7\u00e3o m\u00e1xima da parte do teste ainda deve ser aceite.","Whether a candidate\u0027s response that is beyond the maximum duration should still be accepted.":"Se a resposta de um candidato que exceda o prazo m\u00e1ximo deve continuar a ser aceite.","White on Black":"Branco em preto","White on Blue (TAO standard theme)":"Branco sobre azul (cores padr\u00e3o da TAO)","Who can view the rubric block during the delivery.":"Quem pode ver o bloco de rubrica durante o exame.","Whole number part":"Parte n\u00famero inteiro","Width":"Largura","Windows help file":"Arquivo de ajuda do Windows","Windows Media audio":"\u00c1udio do Windows Media","Windows Media file (metafile)":"Arquivo de m\u00eddia Windows (metafile)","Windows Media video":"\u00c1udio do Windows Media","With Replacement":"Com Substitui\u00e7\u00e3o","Wrap image left":"Ajustar imagem \u00e0 direita","Wrap image right":"Ajustar imagem \u00e0 direita","Write Document":"Escrever Documento","Wrong file mime type":"Tipo de mime de arquivo errado","Wrong or missing parameter extension, module or action":"Extens\u00e3o de par\u00e2metro errada ou ausente","Wrong parameter shownExtension":"Par\u00e2metro errado shownExtension","XML Content":"Conte\u00fado XML","XML Editor":"Editor XML","XML error at line %1$d column %2$d \u0022%3$s\u0022.":"Erro XML na linha %1$d coluna %2$d \u0022%3$s\u0022.","XML file":"Arquivo XML","xmlEdit":"xmlEdit","Yellow on Blue":"Amarelo em azul","Yes":"Sim","You answered %s of %s question(s) for this section of the test":"Voc\u00ea respondeu %s de %s quest\u00e3o(\u00f5es) para esta se\u00e7\u00e3o do teste","You answered all %s question(s) in this section":"Voc\u00ea respondeu todas as %s perguntas nesta se\u00e7\u00e3o","You answered all %s question(s) in this test":"Voc\u00ea respondeu a todas %s quest\u00e3o(\u00f5es) neste teste","You answered only %s of the %s question(s) in this section":"Voc\u00ea respondeu apenas %s das %s perguntas nesta se\u00e7\u00e3o","You are about to go to the next item. Click OK to continue and go to the next item.":"Voc\u00ea est\u00e1 prestes a ir para o pr\u00f3ximo item. Clique em OK para continuar e ir para o pr\u00f3ximo item.","You are about to go to the previous item. Click OK to continue and go to the previous item.":"Voc\u00ea est\u00e1 prestes a ir ao item anterior. Clique em OK para continuar e ir para o item anterior.","You are about to leave this section.":"Voc\u00ea est\u00e1 prestes a sair desta se\u00e7\u00e3o.","You are about to submit the test.":"Voc\u00ea est\u00e1 prestes a enviar o teste.","You are about to submit the test. You will not be able to access this test once submitted. Click OK to continue and submit the test.":"Voc\u00ea est\u00e1 prestes a enviar o teste. N\u00e3o ser\u00e1 poss\u00edvel acessar este teste depois de enviado. Clique em OK para continuar e enviar o teste.","You are about to submit this test part.":"Voc\u00ea est\u00e1 prestes a enviar o teste.","You are currently working offline.":"Voc\u00ea est\u00e1 no modo offline.","You are encountering a prolonged connectivity loss.":"Voc\u00ea est\u00e1 enfrentando uma perda de conectividade prolongada.","You are encountering a prolonged connectivity loss. ":"Voc\u00ea est\u00e1 enfrentando uma perda de conectividade prolongada. ","You are not allowed to write in the class %s":"Voc\u00ea n\u00e3o tem permiss\u00e3o para escrever na classe %s","You are not authorised to remove this lock":"Voc\u00ea n\u00e3o est\u00e1 autorizado a remover este bloqueio","You can magnify the content by up to 200%. Check your browser settings to find out how to do it.":"Voc\u00ea pode ampliar o conte\u00fado at\u00e9 200%. Verifique as configura\u00e7\u00f5es do seu navegador para saber como fazer isso.","You cannot delete the root node":"Voc\u00ea n\u00e3o pode remover o n\u00f3 ra\u00edz","You cannot move the selected resources in the class %s":"Voc\u00ea n\u00e3o pode mover os recursos selecionados para a classe %s","You currently have no access to the platform":"Atualmente voc\u00ea n\u00e3o tem acesso \u00e0 plataforma","You do not have the required rights to edit this resource.":"Voc\u00ea n\u00e3o tem permiss\u00e3o para editar este recurso.","You flagged %s item(s) that you can review now":"Voc\u00ea sinalizou %s item(ns) que voc\u00ea pode revisar agora","You have %s item(s) marked for review.":"Voc\u00ea tem %s item(s) marcado(s) para revis\u00e3o.","You have %s left to answer remaining %s questions.":"Voc\u00ea tem %s restante(s) para responder %s perguntas.","You have %s remaining to complete the current item.":"Voc\u00ea tem %s restante(s) para completar o item atual.","You have %s unanswered question(s)":"Voc\u00ea tem %s perguntas n\u00e3o respondidas","You have %s unanswered question(s) and have %s item(s) marked for review.":"Voc\u00ea tem %s pergunta(s) n\u00e3o respondidas e tem %s item(s) marcados para revis\u00e3o.","You have %s unanswered question(s).":"Voc\u00ea tem %s pergunta(s) n\u00e3o respondidas.","You have been locked due to too many failed login attempts. ":"Voc\u00ea foi bloqueado devido a muitas tentativas de login. ","You have been logged out. Please login again":"A sua sess\u00e3o foi encerrada. Por favor, inicie sess\u00e3o novamente","You must activate JavaScript in your browser to run this application.":"Voc\u00ea deve ativar JavaScript no seu navegador para executar este aplicativo.","You will not be able to access this test once submitted. Click \u0022%s\u0022 to continue and submit the test.":"Voc\u00ea n\u00e3o poder\u00e1 acessar este teste depois de enviado. Clique \u0022%s\u0022 para continuar e enviar o teste.","Your account has been locked, please contact your administrator.":"Sua conta foi bloqueada, por favor, contate o administrador.","Your browser does not meet the technical requirements to run TAO.":"Seu navegador n\u00e3o atende aos requisitos t\u00e9cnicos para executar o TAO.","Your browser does not support copying to the clipboard":"Seu navegador n\u00e3o suporta copiar para a \u00e1rea de transfer\u00eancia","Your browser doesn\u2019t support the audio player.":"Seu navegador n\u00e3o suporta o reprodutor de \u00e1udio.","Your browser doesn\u2019t support the video player.":"Seu navegador n\u00e3o suporta o reprodutor de v\u00eddeo.","Your comment\u2026":"Seu coment\u00e1rio\u2026","Your connection seems to be back, please proceed.":"A conex\u00e3o parece estar de volta, por favor prossiga.","Your mail":"Seu e-mail","Your TAO Password":"Sua senha TAO","ZIP archive":"Arquivo ZIP","Zoom":"Ampliar","Zoom in":"Aumentar zoom","Zoom out":"Diminuir zoom"} \ No newline at end of file diff --git a/locales/pt-BR/qtiCat.rdf.po b/locales/pt-BR/qtiCat.rdf.po new file mode 100644 index 0000000000..971683bab6 --- /dev/null +++ b/locales/pt-BR/qtiCat.rdf.po @@ -0,0 +1,29 @@ +msgid "" +msgstr "" +"Project-Id-Version: tao-3x\n" +"PO-Revision-Date: 2023-04-21 15:46\n" +"Last-Translator: TAO Translation Team \n" +"MIME-Version: 1.0\n" +"Language: pt\n" +"sourceLanguage: en-US\n" +"targetLanguage: en-US\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Crowdin-Project: tao-3x\n" +"X-Crowdin-Project-ID: 582145\n" +"X-Crowdin-Language: pt-BR\n" +"X-Crowdin-File: /taoQtiTest/locales/en-US/qtiCat.rdf.po\n" +"X-Crowdin-File-ID: 1659\n" +"Language-Team: Portuguese, Brazilian\n" + +# http://www.tao.lu/Ontologies/TAOTest.rdf#QtiCatAdaptiveSections +msgctxt "http://www.w3.org/2000/01/rdf-schema#label" +msgid "CAT Sections" +msgstr "Seções da CAT" + +# http://www.tao.lu/Ontologies/TAOTest.rdf#QtiCatAdaptiveSections +msgctxt "http://www.w3.org/2000/01/rdf-schema#comment" +msgid "QTI CAT Section identifiers" +msgstr "Identificadores de Seção CAT QTI" + diff --git a/locales/pt-BR/qtitest.rdf.po b/locales/pt-BR/qtitest.rdf.po new file mode 100644 index 0000000000..0a1b936ee7 --- /dev/null +++ b/locales/pt-BR/qtitest.rdf.po @@ -0,0 +1,89 @@ +msgid "" +msgstr "" +"Project-Id-Version: tao-3x\n" +"PO-Revision-Date: 2023-04-21 15:46\n" +"Last-Translator: TAO Translation Team \n" +"MIME-Version: 1.0\n" +"Language: pt\n" +"sourceLanguage: en-US\n" +"targetLanguage: en-US\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Crowdin-Project: tao-3x\n" +"X-Crowdin-Project-ID: 582145\n" +"X-Crowdin-Language: pt-BR\n" +"X-Crowdin-File: /taoQtiTest/locales/en-US/qtitest.rdf.po\n" +"X-Crowdin-File-ID: 1663\n" +"Language-Team: Portuguese, Brazilian\n" + +# http://www.tao.lu/Ontologies/TAOTest.rdf#QtiTestModel +msgctxt "http://www.w3.org/2000/01/rdf-schema#label" +msgid "QTI 2.1 Test Driver" +msgstr "Driver de Teste QTI 2.1" + +# http://www.tao.lu/Ontologies/TAOTest.rdf#QtiTestModel +msgctxt "http://www.w3.org/2000/01/rdf-schema#comment" +msgid "Driver and configuration/authoring tool implementing the test interface for driving compliant QTI 2.1 tests" +msgstr "Driver e configuração/autoria implementando a interface de teste para lançar testes QTI 2.1 conforme a conformidade" + +# http://www.tao.lu/Ontologies/TAOTest.rdf#FormalParamQtiTestDefinition +msgctxt "http://www.w3.org/2000/01/rdf-schema#label" +msgid "QTI Test Definition" +msgstr "Definição de teste QTI" + +# http://www.tao.lu/Ontologies/TAOTest.rdf#FormalParamQtiTestDefinition +msgctxt "http://www.w3.org/2000/01/rdf-schema#comment" +msgid "QTI Test Definition Uri" +msgstr "Uri de definição de teste QTI" + +# http://www.tao.lu/Ontologies/TAOTest.rdf#FormalParamQtiTestCompilation +msgctxt "http://www.w3.org/2000/01/rdf-schema#label" +msgid "QTI Test Compilation" +msgstr "Compilação de teste QTI" + +# http://www.tao.lu/Ontologies/TAOTest.rdf#FormalParamQtiTestCompilation +msgctxt "http://www.w3.org/2000/01/rdf-schema#comment" +msgid "QTI Test Compilation Uri" +msgstr "Uri de compilação de teste QTI" + +# http://www.tao.lu/Ontologies/TAODelivery.rdf#ServiceQtiTestRunner +msgctxt "http://www.w3.org/2000/01/rdf-schema#label" +msgid "QTI test runner" +msgstr "Executor de teste QTI" + +# http://www.tao.lu/Ontologies/TAODelivery.rdf#ServiceQtiTestRunner +msgctxt "http://www.w3.org/2000/01/rdf-schema#comment" +msgid "runs a QTI 2.1 test" +msgstr "executar um teste QTI 2.1" + +# http://www.tao.lu/Ontologies/generis.rdf#TestXMLEditor +msgctxt "http://www.w3.org/2000/01/rdf-schema#label" +msgid "Test XML Editor" +msgstr "Editor XML do teste" + +# http://www.tao.lu/Ontologies/generis.rdf#TestXMLEditor +msgctxt "http://www.w3.org/2000/01/rdf-schema#comment" +msgid "The Test XML Editor Role" +msgstr "Função de Editor XML do Teste" + +# http://www.tao.lu/Ontologies/TAOTest.rdf#QtiTestModel +msgctxt "http://www.w3.org/2000/01/rdf-schema#comment" +msgid "QTI 2.1 Test Driver" +msgstr "Driver de Teste QTI 2.1" + +# http://www.tao.lu/Ontologies/TAOTest.rdf#FormalParamQtiTestDefinition +msgctxt "http://www.w3.org/2000/01/rdf-schema#comment" +msgid "QTI Test Definition" +msgstr "Definição de teste QTI" + +# http://www.tao.lu/Ontologies/TAOTest.rdf#FormalParamQtiTestCompilation +msgctxt "http://www.w3.org/2000/01/rdf-schema#comment" +msgid "QTI Test Compilation" +msgstr "Compilação de teste QTI" + +# http://www.tao.lu/Ontologies/TAODelivery.rdf#ServiceQtiTestRunner +msgctxt "http://www.w3.org/2000/01/rdf-schema#comment" +msgid "QTI test runner" +msgstr "Executor de teste QTI" + diff --git a/locales/pt-BR/taoQtiTestItemRunner.rdf.po b/locales/pt-BR/taoQtiTestItemRunner.rdf.po new file mode 100644 index 0000000000..7eebb67748 --- /dev/null +++ b/locales/pt-BR/taoQtiTestItemRunner.rdf.po @@ -0,0 +1,44 @@ +msgid "" +msgstr "" +"Project-Id-Version: tao-3x\n" +"PO-Revision-Date: 2023-04-21 15:46\n" +"Last-Translator: TAO Translation Team \n" +"MIME-Version: 1.0\n" +"Language: pt\n" +"sourceLanguage: en-US\n" +"targetLanguage: en-US\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Crowdin-Project: tao-3x\n" +"X-Crowdin-Project-ID: 582145\n" +"X-Crowdin-Language: pt-BR\n" +"X-Crowdin-File: /taoQtiTest/locales/en-US/taoQtiTestItemRunner.rdf.po\n" +"X-Crowdin-File-ID: 1657\n" +"Language-Team: Portuguese, Brazilian\n" + +# http://www.tao.lu/Ontologies/TAOTest.rdf#FormalParamQtiTestParentServiceCallId +msgctxt "http://www.w3.org/2000/01/rdf-schema#label" +msgid "QTI Test Parent Service Call ID" +msgstr "ID da chamada de serviço principal QTI" + +# http://www.tao.lu/Ontologies/TAOTest.rdf#FormalParamQtiTestParentServiceCallId +msgctxt "http://www.w3.org/2000/01/rdf-schema#comment" +msgid "QTI Test Parent Service Call ID" +msgstr "ID da chamada de serviço principal QTI" + +# http://www.tao.lu/Ontologies/TAODelivery.rdf#ServiceQtiTestItemRunner +msgctxt "http://www.w3.org/2000/01/rdf-schema#label" +msgid "QTI Test Item Runner" +msgstr "Motor de testes de itens QTI" + +# http://www.tao.lu/Ontologies/TAODelivery.rdf#ServiceQtiTestItemRunner +msgctxt "http://www.w3.org/2000/01/rdf-schema#comment" +msgid "Embed and run an item into a QTI Test. This service initialize the TAO Apis and contexts" +msgstr "Incorporar e executar um item em um QTI Test. Este serviço inicializa a API TAO e contextos" + +# http://www.tao.lu/Ontologies/TAODelivery.rdf#ServiceQtiTestItemRunner +msgctxt "http://www.w3.org/2000/01/rdf-schema#comment" +msgid "QTI Test Item Runner" +msgstr "Motor de testes de itens QTI" + diff --git a/views/js/loader/taoQtiTest.min.js.map b/views/js/loader/taoQtiTest.min.js.map index f74c5943ce..ef082c5bfc 100644 --- a/views/js/loader/taoQtiTest.min.js.map +++ b/views/js/loader/taoQtiTest.min.js.map @@ -1 +1 @@ -{"version":3,"mappings":"AAyBAA,mDACA,SACA,gBACA,wBACA,aAEA,kBACA,UACA,oBACA,sBACA,gBACA,uBACA,CAYA,yCAEA,GC/BAA,wDACA,SACA,SACA,UACAC,EACAC,OACA,CACA,aAEA,oIACA,GCEAF,yDACA,SACA,aACA,gBAMAG,eASAC,aAUAC,UAUAC,UAUAC,QAUAC,SAYAC,QAUAC,OAWAC,gBAaAC,WAYAC,OASAC,OAUAC,qBAOAC,UAQAC,OAQAC,OACA,EAEAC,kBAKAC,2BACA,mBACA,EAQAC,6CAiBA,MAhBApB,kBACA,0CACAqB,SAGAA,4CAGA,YACArB,6CAGAqB,QAFAA,8CAMAA,IACA,EAQAC,uCAKA,OAJAtB,mBACAqB,6CAGAA,MACA,sBACA,yBACA,6BACA,gBAEA,0BACA,qBACA,4BACA,WACA,SACA,YACA,UAGA,cAEA,0BACA,6BAEA,wBACA,4BAEA,0CACArB,6BAGA,SAFAuB,sBAGA,CAGA,YACA,EAsBAC,mDACA,uCACA,iBACA,+BAEA,cACA,4BAEA,cACA,4BAEA,YACA,0BAEA,aACA,2BAEA,YACA,0BAEA,WACA,yBAEA,mBACA,kCAEA,eACA,6BAEA,WACA,yBAEA,UACA,wBAEA,sBACA,sCAEA,aACA,2BAEA,UACA,wBAEA,WACA,yBAEA,QACA,UAEA,EASAC,sEACAC,YACAxB,wBACA,aAFAwB,WAIAxB,qBACA,UALAwB,WAOAxB,qBACA,UARAwB,WAUAxB,mBACA,QAXAwB,WAaAxB,oBACA,SAdAwB,WAgBAxB,mBACA,QAjBAwB,WAmBAxB,kBACA,OApBAwB,WAsBAxB,2BACA,eAvBAwB,WAyBAxB,sBACA,WA1BAwB,WA4BAxB,kBACA,OA7BAwB,WA+BAxB,iBACA,MAhCAwB,WAkCAxB,+BACA,kBAnCAwB,WAqCAxB,qBArCAwB,WAsCAxB,iBACAyB,SACA,MAEA,WA1CAD,WA6CAxB,oBACA,MAKA,CACA,gCAEA,qBACA,GC3WAH,4DACA,SACA,aACA,gBAMA6B,kBAMAC,SAOAC,WAOAC,UAOAC,SAQAf,OAQAD,KACA,EAEAiB,qBAKAd,2BACA,sBACA,EAQAC,2DAiBA,MAhBApB,yBACA,oDACAkC,gBAGAA,6DAGA,mBACAlC,+EAGAkC,mCAFAA,mFAMAA,WACA,EAQAV,mDACA,uCACA,aACA,8BAEA,eACA,gCAEA,cACA,+BAEA,aACA,8BAEA,WACA,4BAEA,UACA,2BAEA,QACA,UAEA,EAQAC,6DACAC,YACAE,uBACA,SAFAF,WAIAE,yBACA,WALAF,WAOAE,wBACA,UARAF,WAUAE,uBACA,SAXAF,WAaAE,qBACA,SAdAF,WAgBAE,sBACA,KAKA,CACA,mCAEA,wBACA,GCnLA7B,iEACA,SACA,aACA,aAaA,wCACA,kFACA,CASA,kEACAoC,gFACAC,kFAOA,MALAC,gBACAA,iEACAF,2EAGA,iCACA,CASA,iEACA,8BAOA,MANAnC,sCACA,0DAEA,MADAsC,YAGA,GACAA,KACA,CA9CA,sDAgDA,OACAC,sCACAC,kCACAC,+BACA,CACA,GC9DA1C,2DACA,SACA,yDACA,8BACA,aAmHA,2BAOA,MANA2C,SACAA,UAEA1C,mBACA0C,eAEAA,KACA,CAzHA,sBASAC,mDACA,aACA,eACA,EAEA,8CACA,0DAQA,GALA3C,8BACA4C,sBACAC,iBAGAA,YACA,oDACA,4DAEAC,6BACA,CAEA,uCACA,EASAC,oCAIA,+BACA,4CAEA,MADAC,oBAGA,IARAA,YACAC,uBAeA,MANAjD,uBACAA,gCACAkD,YACAC,sBAGAH,KACA,EAUAI,gEACAC,4CACAC,+BACAC,iBACAC,IACAC,IAEAC,aACAA,4CACAA,cACAD,4CACAC,qBAIA,iBACA,EAUAC,8DACAC,aACAP,2CACAQ,yBACAf,4DAMA,MAJAA,kCACAc,8BAGAA,MACA,CACA,EAiBA,uBACA,GChIA7D,wDACA,SACA,yDACA,mDACA,iDACA,oDACA,gFACA,aAEA,mBAQA+D,mFACA,oFACA,EASAC,+EACA,qFACA,EAOAC,4DACA,yDACA,EAOAC,kEACA,sDACA,mBACA,EAOAC,wEACA,2FACA,gBACA,EAOAC,uEACAnE,6DACA,EAOAoE,6EACApE,gEACA,EAOAqE,iGACA,2CACArE,0BACAA,4CACAsE,iBACAC,mBAEAD,0BACAE,6CACAF,4BACAE,8CAGA,CAEAA,qEACA,EAQAC,iDACA,gBASA,MARAzE,oBACAuE,SAEAG,mEACA,mBACAC,0DAEA,GACAA,QACA,EAQAC,8DACAC,8DACAC,yDACAC,MAEA/E,uBACA+E,gBAEAJ,6EACA,cACA,GAEAI,wCACA,6DACA,GAGAF,cACA7E,6BAGA8E,OACA9E,qBAEA,EAUAgF,kEAEA,oDACA,4DAGA,gEACAC,SACAC,kBACAC,sBACAC,iBACAC,iBACAC,gBACApD,6EACAqD,2DACA,EACA,EAUAC,gFACA,kDAEA,qDACA,yEAaA,MAVAC,mBAKA,kCACAA,oCALAA,+DACAC,eACA,GACAC,+CAKAF,oDACAnB,cACA,EAUAsB,iEACA,+CAEA,sEACA,yDAGA,mBACA,qGACA,yEAGAlB,4DACA,CAQA,MANAG,gBACAA,gBACAc,4CAGAd,2BACAgB,OACA,EAQAC,6DACA,8BACA,4CACA,4FACA,wDAGAH,qEACA,CACA,mFACA,+EACA,gEAGAA,8BACAA,0EAEAA,2FACA,CACA,CACA,CACA,EAEA,oBACA,GC7QA5F,+EACA,aAQA,oCACA,+CACA,CAOA,sCACA,yCACAC,qDACA,+CACAA,oDACAuE,wBACA,GAEA,+CACAwB,qCAEA,EACA,EACA/F,iDACAA,kEACA+F,2CACA,EACA,EACA,CAEA,OAOAC,8BASAC,kDACA,kBAMA,MALAD,6CACAE,6BACAC,wBAEA,GACAnG,kBACA,EAQAoG,4CACA,eAMA,MALAJ,6CACAE,6BACAG,qBAEA,GACArG,eACA,CACA,CACA,GClFAD,sDACA,SACA,kBACA,iBACA,iDACA,oDACA,gDACA,iDACA,gGACA,aAQA,4CACA,mBAKAuG,6BACA,YACA,EASAC,qCAQA,MAPAC,gBAMAC,wCACA,IACA,EAMAC,+BACA,aACA,EAaAC,2CACA,+EACA,OACAC,4BACAvF,4DACAa,wEACA,CACA,EACA,EAMA2E,6CACA,+EACA,6BACA,EACA,EAMAC,uCACA,2CACA,EAMAC,iCACA,wCACA,CACA,EAIA,MAFAC,2DAEAC,oCACA,CAEA,2BACA,GCxGAlH,uDACA,SACA,kBACA,2CACA,8CACA,+DACA,aAOA,sDAYA,6BAIA,MAHA,wBACA0G,kDAEAA,aACA,CAMA,0BAUA,MATAS,cACAA,yCACA,mBACA,yDACA,6DACA,qDACA,8DACA,IAEAA,UACA,IAjCAC,aAEAC,WACAZ,MACAU,WACAT,cA8BA,qCACA,wDAuBA,MApBAW,8BAEAD,aACAE,sCACAb,OACA,EAEAc,uCACA,uBACA,EAEAC,6CACA,0BACA,EAEAC,2CACA,kFACA,CACA,EAEAC,uBACA,CAEA,yBACA,GC7EA1H,wCACA,SACA,OACA,WACA,4BACA,gCACA,aAKA,mBACA2H,gBACAC,wDACA,EACAC,UACAD,kDACA,EACAE,wBACAF,qDACA,CACA,EAQA,gDAOA,MALAX,6CAKA,CAMAU,yCACA,yCACA,EAOAE,mCACA,0CACA,EAOAC,iEACA,qEACA,CACA,CACA,CACA,GCnEA9H,mDACA,SACA,SACA,OACA,cACA,gCACA,uBACA,cACA,8FACA,kBAKA+H,uDAKAC,2CAMAC,8BACAF,kBACAG,4EACA,EAEAC,yDAOA,0CACAC,iCACAC,gBACA/G,iBACAgH,8DACAC,kEACAC,kBACAC,SACA,CACAC,aACAC,aACArH,YACA,EACA,CACA8G,OACA,EAGAQ,oEACAC,uBACAxB,0CACA,qBACA,EACA,GACAwB,4BAEAb,iBACAH,iBACAiB,aAEA,yBACA,GACAC,cACA,GACAF,oCAGAb,iBACAF,iCACAgB,eACA,2BACA,GACAC,cACA,GACAF,6BASAxB,iDACA,GAGAW,iBACAL,iBACAmB,uBACAT,+BACAA,sCACA,GACAS,gBAEA,uEACA,GACAA,uBAEAT,8BACA,GACAS,gBAEAT,kDACAO,2DACA,GACAA,sDACA,GACAG,cACA,CACA,GCzIA/I,+JACA,iCACAgJ,6DACA,uIAkCA,MA/BAC,uBACAC,2EACAA,mHACAD,iCACA,wHACAC,2EACAA,mHACAD,iCACA,+CACAC,2EACAA,mHACAD,iCACA,2dACAE,8MACA,oPACAA,oLACA,sKACAA,wLACA,uOACAA,sMACA,sBACAD,2EACAA,mHACAD,iCACA,oVACAC,2EACAA,mHACAD,iCACA,oYACAE,4LACA,0CACAF,MACA,KCtCAjJ,8JACA,iCACAgJ,6DACA,uIA0CA,MAvCAC,uBACAC,2EACAA,mHACAD,iCACA,6JACAC,sEACAA,8GACAD,iCACA,0dACAE,kMACA,wPACAA,8MACA,+BACAD,2EACAA,mHACAD,iCACA,2PACAE,0MACA,2PACAA,oLACA,6KACAA,wLACA,8OACAA,kMACA,6WACAD,2EACAA,mHACAD,iCACA,4FACAE,gMACA,kRACAA,sMACA,0HACAA,gLACA,gLACAA,0NACA,gGACAA,0NACA,8KACAF,MACA,KC9CAjJ,kKACA,iCACAgJ,6DACA,wGAQA,MALAC,yaACAE,oNACA,uOACAA,4MACA,yWACAF,MACA,KCZAjJ,8JACA,iCACAgJ,6DACA,uIAuBA,MApBAC,qBACAC,2EACAA,mHACAD,iCACA,gBACAC,qEACAA,6GACAD,iCACA,wDACAG,qNACAA,sCACAH,gcACAE,wNACA,uOACAA,oLACA,6JACAA,wLACA,0NACAA,gNACA,8SACAF,MACA,KC3BAjJ,+JAKA,+BAEA,4BAcA,MAbAiJ,+EACAC,qEACAA,6GACAD,iCACA,8CACAC,qEACAA,6GACAD,iCACA,8CACAC,4EACAA,oHACAD,iCACA,uBACAA,MACA,CAEA,+BAEA,6BAIA,MAHAA,+EACAE,8NACA,uBACAF,MACA,CA9BA,iCACAD,6DACA,iJAuCA,MATAC,sEACAE,0LACA,2CACAA,8KACA,2CACAA,4LACA,mBACAC,qJACAA,sCACAH,MACA,KC3CAjJ,iKAKA,+BAEA,6BAYA,MAXAiJ,uFACAE,4LACA,8RACAA,sMACA,wgBACAA,0OACA,6PACAA,oNACA,qnBACAA,8VACA,iGACAF,MACA,CAEA,+BAEA,oCAwBA,MAvBAA,yOACAE,0MACA,gQACAC,8HACAA,sCACAH,4RACAE,gSACA,8PACAA,kMACA,qkBACAA,gPACA,+OACAA,kNACA,4dACAA,4WACA,uQACAA,kLACA,6eACAA,4QACA,sMACAC,8HACAA,sCACAH,6DACAA,MACA,CACA,+BAEA,4BAaA,MAZAA,mDACAC,oEACAA,4GACAD,iCACA,MACAG,kIACAA,sCACAH,aACAC,sEACAA,8GACAD,iCACA,8BACAA,MACA,CACA,+BAGA,6BACA,CAEA,+BAEA,4BAUA,MATAA,2GACAC,oEACAA,4GACAD,iCACA,oFACAC,4EACAA,oHACAD,iCACA,6CACAA,MACA,CArFA,iCACAD,6DACA,iJA0HA,MArCAC,wQACAE,0LACA,kBACAA,kMACA,sDACAD,2EACAA,mHACAD,iCACA,yDACAC,2EACAA,mHACAD,iCACA,iNACAC,2EACAA,mHACAD,iCACA,wOACAE,gPACA,yLACAA,gLACA,kBACAA,kMACA,4YACAA,oMACA,2DACAC,wIACAA,sCACAH,gFACAE,oLACA,6CACAC,mIACAA,sCACAH,6FACAE,8MACA,6VACAA,0LACA,0HACAF,MACA,KC9HAjJ,qKAKA,+BAGA,eACA,CAEA,+BAEA,6BAMA,MALAiJ,kLACAE,gMACA,sqBACAA,gVACA,qGACAF,MACA,CAEA,+BAEA,6BAMA,MALAA,kLACAE,gMACA,sqBACAA,kfACA,qGACAF,MACA,CAEA,+BAEA,6BAMA,MALAA,mLACAE,kMACA,+rBACAA,8UACA,qGACAF,MACA,CAEA,+BAEA,6BAYA,MAXAA,+FACAE,4LACA,gVACAA,sMACA,ikBACAA,8OACA,4UACAA,oNACA,4qBACAA,gYACA,qHACAF,MACA,CA1DA,iCACAD,6DACA,iJAmJA,MAzFAC,sCACAC,2EACAA,mHACAD,iCACA,yDACAC,2EACAA,mHACAD,iCACA,oNACAE,0LACA,kBACAA,kMACA,0DACAD,2EACAA,mHACAD,iCACA,yDACAC,2EACAA,mHACAD,iCACA,+NACAC,2EACAA,mHACAD,iCACA,4PACAE,wNACA,yNACAA,0LACA,kBACAA,kMACA,mQACAC,kRACAA,sCACAH,8QACAE,kLACA,2NACAC,kRACAA,sCACAH,0JACAE,0LACA,+QACAA,khBACA,0NACAA,0LACA,kBACAA,kMACA,mQACAC,kRACAA,sCACAH,8QACAE,0LACA,2NACAC,kRACAA,sCACAH,0JACAE,8LACA,+QACAA,4wBACA,uOACAA,0LACA,gbACAA,omBACA,4XACAA,8MACA,2UACAA,8LACA,8XACAD,4EACAA,oHACAD,iCACA,uaACAE,4SACA,6JACAC,iJACAA,sCACAH,qGACAG,iJACAA,sCACAH,8FACAG,kJACAA,sCACAH,mPACAE,0MACA,soBACAA,wRACA,yGACAC,wIACAA,sCACAH,gCACAA,MACA,KCvJAjJ,oKAKA,+BAEA,6BAMA,MALAiJ,yIACAE,4MACA,4VACAA,kXACA,yDACAF,MACA,CAEA,+BAEA,6BAMA,MALAA,0OACAE,sMACA,6kBACAA,sVACA,yEACAF,MACA,CAEA,+BAEA,6BAMA,MALAA,qKACAE,gMACA,ynBACAA,gVACA,qFACAF,MACA,CAEA,+BAEA,6BAMA,MALAA,qKACAE,gMACA,0nBACAA,kfACA,qFACAF,MACA,CAEA,+BAEA,6BAMA,MALAA,sKACAE,kMACA,+oBACAA,8UACA,qFACAF,MACA,CAEA,gCAEA,6BAYA,MAXAA,0FACAE,4LACA,iUACAA,sMACA,giBACAA,0OACA,qUACAA,oNACA,goBACAA,4XACA,iGACAF,MACA,CA1EA,iCACAD,6DACA,iJAyKA,MA/FAC,qCACAC,2EACAA,mHACAD,iCACA,wDACAC,sEACAA,8GACAD,iCACA,sLACAE,0LACA,kBACAA,kMACA,sDACAD,2EACAA,mHACAD,iCACA,yDACAC,2EACAA,mHACAD,iCACA,kNACAC,2EACAA,mHACAD,iCACA,wOACAE,kOACA,uNACAA,gLACA,kBACAA,kMACA,6YACAA,0MACA,oPACAA,oLACA,kBACAA,kMACA,miBACAA,oSACA,uPACAA,gMACA,qiBACAA,0qBACA,2DACAC,sIACAA,sCACAH,mKACAE,0LACA,gZACAA,gmBACA,oVACAA,wLACA,yQACAA,sMACA,seACAA,kLACA,kBACAA,kMACA,mgBACAA,8PACA,2EACAC,qJACAA,sCACAH,gGACAE,sLACA,iUACAA,oLACA,4jBACAA,sbACA,gKACAA,8MACA,yVACAA,8LACA,yVACAD,4EACAA,oHACAD,iCACA,uYACAE,4SACA,+JACAC,iJACAA,sCACAH,mHACAG,iJACAA,sCACAH,4GACAG,kJACAA,sCACAH,wPACAE,0MACA,ulBACAA,wRACA,qFACAC,0IACAA,sCACAH,mBACAA,MACA,KC7KAjJ,oKAKA,+BAEA,6BAMA,MALAiJ,qKACAE,gMACA,ynBACAA,gVACA,qFACAF,MACA,CAEA,+BAEA,6BAMA,MALAA,qKACAE,gMACA,0nBACAA,kfACA,qFACAF,MACA,CAEA,+BAEA,6BAMA,MALAA,sKACAE,kMACA,+oBACAA,8UACA,qFACAF,MACA,CAEA,+BAEA,oCAkCA,MAjCAA,sFACAE,4LACA,2RACAA,sMACA,4gBACAA,kVACA,iCACAA,4SACA,iCACAA,oXACA,uKACAA,sQACA,yMACAA,sMACA,2bACAC,kIACAA,sCACAH,mGACAE,sMACA,weACAA,oOACA,sMACAC,mIACAA,sCACAH,2FACAE,sMACA,4eACAA,oOACA,qTACAA,oNACA,olBACAA,sXACA,iFACAF,MACA,CACA,+BAGA,cACA,CA/EA,iCACAD,6DACA,iJAiKA,MAlFAC,qCACAC,2EACAA,mHACAD,iCACA,yDACAG,qNACAA,sCACAH,0MACAE,0LACA,kBACAA,kMACA,sDACAD,2EACAA,mHACAD,iCACA,yDACAC,2EACAA,mHACAD,iCACA,oNACAC,2EACAA,mHACAD,iCACA,wOACAE,gPACA,iOACAA,wLACA,kBACAA,kMACA,uYACAA,kMACA,0PACAA,sLACA,qgBACAA,8RACA,oPACAA,gLACA,+fACAA,wPACA,2MACAA,0LACA,iHACAA,wMACA,kRACAA,4RACA,2kBACAA,oLACA,uJACAA,0LACA,6FACAA,gLACA,kQACAA,sUACA,iXACAA,sMACA,8IACAA,8MACA,iXACAA,8LACA,2VACAD,4EACAA,oHACAD,iCACA,uYACAE,4SACA,2KACAC,iJACAA,sCACAH,+HACAG,iJACAA,sCACAH,wHACAG,kJACAA,sCACAH,oQACAE,0MACA,ulBACAA,wRACA,qFACAC,wIACAA,sCACAH,mBACAA,MACA,KCrKAjJ,2KACA,iCACAgJ,6DACA,2FAgBA,MAbAC,6LACAC,2EACAA,mHACAD,iCACA,+BACAC,2EACAA,mHACAD,iCACA,kJACAC,sEACAA,8GACAD,iCACA,gPACAA,MACA,KCpBAjJ,wKAKA,+BAEA,6BAcA,MAbAiJ,2SACAE,wLACA,wjBACAA,sPACA,oTACAA,oLACA,4fACAA,oRACA,+TACAA,4LACA,mgBACAA,0SACA,iFACAF,MACA,CArBA,iCACAD,6DACA,iJA2DA,MAtCAC,wEACAE,8LACA,MACAD,2EACAA,mHACAD,iCACA,sNACAE,gLACA,kBACAA,kMACA,8LACAA,kLACA,0DACAA,wLACA,wDACAA,oLACA,uDACAA,kLACA,gEACAA,sMACA,sDACAA,gLACA,qQACAA,0QACA,iPACAA,gLACA,ycACAA,8PACA,8OACAA,0LACA,mcACAA,wQACA,oIACAA,kMACA,wGACAC,oIACAA,sCACAH,mBACAA,MACA,KC/DAjJ,uKAKA,sCAEA,4BAiBA,MAhBAiJ,mFACAC,wEACAA,gHACAD,iCACA,OACAC,2EACAA,mHACAD,iCACA,gDACAC,wEACAA,gHACAD,iCACA,oBACAG,gJACAA,sCACAH,uBACAA,MACA,CACA,sCAEA,oCA6BA,MA5BAA,gGACAC,4EACAA,oHACAD,iCACA,+KACAC,mEACAA,2GACAD,iCACA,wCACAC,4EACAA,oHACAD,iCACA,+BACAG,qUACAA,sCACAH,wMACAC,mEACAA,2GACAD,iCACA,OACAC,sEACAA,8GACAD,iCACA,8OACAC,4EACAA,oHACAD,iCACA,yDACAA,MACA,CACA,+BAGA,eACA,CA9DA,iCACAD,6DACA,8HA8DAI,qJACAA,0BACA,EACA,KCpEApJ,iKACA,iCACAgJ,6DACA,uIA0CA,MAvCAC,uBACAC,2EACAA,mHACAD,iCACA,sGACAC,sEACAA,8GACAD,iCACA,0dACAE,kMACA,wPACAA,8MACA,+BACAD,2EACAA,mHACAD,iCACA,2PACAE,0MACA,2PACAA,oLACA,gLACAA,wLACA,mQACAA,wMACA,mXACAD,2EACAA,mHACAD,iCACA,4FACAE,gMACA,kRACAA,sMACA,0HACAA,gLACA,gLACAA,0NACA,gGACAA,0NACA,4KACAF,MACA,KC1BAjJ,wDACA,gDACA,uDACA,sDACA,0DACA,sDACA,uDACA,yDACA,6DACA,4DACA,4DACA,mEACA,gEACA,+DACA,yDACA,CACA,SACAqJ,SACAC,SACAC,QACAC,YACAC,QACA7E,SACA8E,UACAC,cACAC,aACAC,aACAC,mBACAC,iBACAC,gBACAC,WACA,CACA,aAEA,8EAMA,OACAC,8CACAX,4CACAY,4CACAC,oDACAxF,8CACAqF,kDACApH,YACAwH,2CACAH,mDACAX,iDACAY,iDACAG,6DACAF,yDACAG,4DACAN,mDAEA,CACA,CACA,GC3DAjK,0JACAwK,EACAC,GACAC,WACAC,UACA,CACA,aAcA,iDAmGA,0BACAC,oDACAC,6CACAC,2DAGAC,uDACAC,0GAEA,wBACAC,4BAEAT,2CACAA,kEAEAA,8EAGAA,2CACAA,oEAEAA,+EAGA,GAEAI,wDACA,MA7HAvD,qCACA6D,wCACA,eAKAC,0BACA,qBACAR,8BACA,EACAtD,mEACAuD,+DAGAH,4BAGA,2DACAW,kBAEAC,iBAEAT,mCAMA,iEACAA,qCACA,4CACAU,kCAEA,EACA,EAMAC,+BACA,YACA,EAMAC,6BACA,mCACA,EAMAC,+BACAb,yCACAc,oBACAlH,IACA,EACA,EAMAmH,iCACAf,0CACAc,oBACAlH,IACA,EACA,EAKAoH,+BACAhB,cACA,EAKAiB,6BACAxE,8EACAmE,SACAZ,2CAEAA,yCAEA,EAmCA,OACAO,UACAI,gBACAC,cACAC,cACAE,gBACAC,gBACAC,aACA,CACA,EAEA,eACA,GCpKA7L,4FACA,aAQA,4CACA,oDACA,CAOA,yCACA,kDACA,CAOA,kCACA,gEACA,CAOA,4CACA,kEACA,CAOA,0CACA,iDACA,CAOA,uCACA,sCACA,CAOA,sCACA8L,oCACAC,8BAEAC,gCACA,CAOA,6CACA,kGACA,CAQA,mDACAC,8CACAC,4DACAC,4FACA,sCACA,0CACA,MACAC,oDACAC,kFACA,0DACA,CACA,CAEA,OACAP,sBACAQ,mBACAC,eACAC,uBACAC,sBACAT,oBACAD,iBACAW,UACAC,uBACA,CACA,GCzGA3M,sDACA,SACA,+CACA,mDACA,4CACA,aAaA,kDACA,kBACAqH,4DACAqE,mBACA,mBACAlB,kCACAoC,YAEA,iBACAvF,iCACAuF,0BAEAC,sCACAA,gBAEAA,2BACAxF,iCACAuF,yBACA,GACAC,4BACAxF,oCACAuF,4BACA,GAEA,uBACApI,cAGAqI,kBAGA,EACA,CAQA,iEACAC,uDACAzF,kDAGAmD,iDACA,0BAGAkB,sBACAoB,+DAKAC,kDACAb,gCACA,SACAY,0BACAA,SACAE,qEACAC,4CACA,GAEA,GAGAzC,mDACA,0BAGAkB,sBACAoB,+DAKAC,kDACAb,gCACAA,8CACAY,0BACAA,SACAI,oEACAD,4CACA,GAEA,EACA,CAQA,6DACA5F,gCACAuF,cACAO,mDAEAjB,6BACAkB,uCACAC,2CAGA,uBACAD,gCACAC,mCAGA,WACAD,gCACAC,sCAGAnB,4BACAmB,kCACAD,qCAIAA,mCACAC,qCAEA,EACA,CAOA,kDACAhG,gCACAuF,cACAO,mDACAG,4CAEA,mDACAA,gCAEAA,kCAEA,EACA,CAOA,gDAEA,sBACAjG,qFAEA,CAOA,+CACAA,4GACA,CAQA,2CACAuF,4CACAW,mEACAA,iBACAX,YAEAA,WAEA,CAMA,gDACA,gEACA,kDACA,kDACAY,iEACA,CACA,CASA,qIACAC,mCACAC,+DACA,2BAIA,qDACA,cACA,eACAd,YACA,MAEA,cACA,oFACAW,kBACAX,YACAc,mDAEAd,YAEA,CACA,CACA,CAMA,iCACAe,2BACAf,cACAgB,iDAEA,kCACAC,mCACA3B,wCACA0B,8BACA,KACAA,iEAEA,EACA,MA/PAE,0BACAC,qBACAC,2BAoQA,OACAnL,WACAoL,KACAC,UACAC,QACAC,QACAC,OACAC,mBACAC,qBACAC,uBACAC,gBACA,CACA,GC3RAzO,0GACA,aAMA,uCACA0O,oEACAjI,wBAEA,CAMA,2CACA,gEACAiI,6EACAjI,yBAEAiI,kGACAjI,kCAEAiI,kGACAjI,kCAEAiI,mGACAjI,kCAEA,CAMA,0CACA,+DACAiI,4EACAjI,yBAEAiI,iGACAjI,kCAEAiI,iGACAjI,kCAEAiI,kGACAjI,kCAEA,CAMA,0CACA,+DACAiI,4EACAjI,yBAEAiI,iGACAjI,kCAEAiI,iGACAjI,kCAEAiI,kGACAjI,kCAEA,CAQA,wHACAkI,4EACAC,uEACA,mBAgBA,MAfAC,qCACAC,gDACAJ,qEACA,EACAI,uBACAA,uCACA,wDACA,2DACAJ,4DACA,EACAK,qCACA,CACA,IAGAD,cACA,CAEA,OACAE,uBACAC,2BACAC,0BACAC,0BACAC,oBACA,CACA,GCtGApP,iEACA,SACA,SACA,OACA,kBACA,aACA,gDACA,0DACA,UACA,iEACA,aAKA,6CAQA,iCACAqP,2BACArM,uCACAsM,UACAC,yBACA,uBACA,GACAC,+BACAxM,6CACAsM,UACAC,yBACA,uBACA,GACAE,uCACAC,+BACA1M,+BACA2M,gBACAL,UACAC,yBACA,4DACA,GACAK,4CACAF,+BACA1M,uCACAsM,UACAC,yBACA,4DACA,GAEAM,yDACAC,yEAOA,2EACA,MA7CAC,wDACAC,kEA8CAC,kBAQAC,6DACAC,WACAC,gDACAC,yEAEAC,yEAEAP,qEAEAA,yCACA,sDACA,mBACA,sCACAQ,mCAEAtQ,mBACAuQ,2BACA,EACA,CACA,GAGAR,wBACAS,SACAC,aACAC,sBACAC,YACAC,8BACAC,2BACA,oCACA,EACAC,qBACA,GACAlI,uBACA2H,2BACA,GAGAQ,0BACA,EAOAC,iEACAC,qCAEAb,uFAIAc,6DACAA,mCACA,0BAEA,sBACA,gBAEA,qCAEA,kCACA,iBAFA,qBAIA,GAIAnB,wDAEAA,wBACAN,+BACA1M,+BACAoO,qBACAC,aACAC,+BACA,qCACAD,uBAEA,EACA,CACA,EAIA,MAFA3J,8BAEAuI,gBACA,CAgCA,wCACA,gDACA,sDACA,4BACA,KACA,CAvLA,kBACAsB,2BAwLA,MAlBAC,iEACAvR,qBACAwR,mBACAF,uDAEA,EAaAC,uBACA,GC9MAxR,iIACA,aAUA,oCACA,gGACA,CAUA,+CACA,6CAEA0R,yBAGAC,uEAGAC,0DAEAnL,yDACAA,gDAGAoL,2BACAC,gCACA,CAQA,8BACA,kBAKAC,YAJAC,OACAC,MACAC,WACAR,QAGA,+BACA,4DAGA,qGAEA,+CACA,aACAzR,mCAEAmG,mDAEA,oFACA+L,mCAEA,UAEAA,8BAEA,YACAC,qDAIAJ,4BACAC,iCAGAC,6CAGAR,uCAEAU,2CACA,CASA,yCACAC,2BACApS,gDACA,gDACA,qCACAqS,2BAEAA,mEAEA,+CACAT,qCAEA,GAEAU,oDAEA,CASA,4CACAF,2BACApS,gDACA,mFACAqS,wEAEA,+CACAR,wCAEA,GAEAS,oDAEA,CAEA,qOACA,mBACA,CACAC,QACAN,sBACAR,eACA,EACA,qBACA,wBACA,EAEA,CAnJA,6BAqJA,OACAW,wCACAI,4BACA1L,4BACA8K,4BACAC,iCACA,CACA,GC9JA9R,2DACA,0BACA,SACA,SACA,OACA,gDACA,mDACA,sDACA,0EACA,aAWA,6BACA,OACA6G,gBACA6L,0CACAC,kCACA,6BACAC,kCAEA,CACA,CACA,CAMA,iCACA,+BACA,OACA/L,oBACA6L,mEACAC,kCACA,6BACAC,sCAEA,CACA,CACA,CAOA,6CACA,OACA/L,uBACA6L,2CACAC,0CACA,4BACAjP,yBACAmP,8EAIAC,qEACA,gCACA,iDAIAF,SACA,iCACA,4DAEA,CACA,KACA,0DAEA,CACA,CACA,CAMA,2CAEAG,oDACAA,4DACAA,6EACA,CAOA,8BACA,iEACA,2BACA,4DACA,sBAUA,GARA9S,eACA+S,sBACAC,2BACA,UACAC,uBACAC,0BAEA,GACA,wBACA,qGAGAlT,6CACAA,kEACAA,+DACA,mDACAuJ,YACA,gCACA,UACA,CACA,6DACA,gBACA4J,GACA,+EACAC,gCACA,CAGA,EACA,EACA,EACA,CAQA,uEACAR,gBAEAS,kCACAvQ,uEACA,+EACA8P,kBACA/P,4CACAyQ,sCACAjS,yBACAoH,uCACA,GAGAzI,wCACAA,sDACA,yEACAqT,mBAGA,EACA,EAQA,OANArT,4CACAuT,8BACAC,wCAEAH,gBAEAT,WACA,CAQA,sDACAA,+EACAnP,wBACAgQ,2CACA,gDACA,MAhLAC,oCAGAC,2FA8KA,OACAA,qBACAC,mBACAC,mBACAC,cACAC,kBACA,CACA,GChMAhU,uJACA,aAMA,qBAQAiU,6EACA,wGACA,EAQAC,0DACA,2CACA,EAQAC,iDACA,kDACA,EAOAC,oEACAnU,mCACA,gEACAA,gBACAoU,aACA1R,8BAGAA,uBAGA1C,iBACA,mDACAA,mBACA,6BAEA,EACA,EAOAqU,kDAkDA,MAjDA7N,qDACAxG,6CACAqJ,qEACArJ,kEAGAsU,4BACA,wDACA,yCAEA,kCAIAA,2EACAtU,wDAEAuU,iBACAvU,6BACA,6DAEAuU,mBAEA,GAGAD,4EAGA,2CACA,2CACA,qDAEA,sCACA,yCAEAtU,+DACAuJ,+BAKA,GAGA,EAEA,GAEA/C,KACA,EAQAgO,iFACA,YACA,kBACA3R,YACA4R,QAEAA,QADAzU,oDACA,2DAEA,qCAGA0U,+BAEA,EACA7R,0CAEA7C,8CACAuK,4BAGA,iBACA,CACA,EAEA,oBACA,GCxJAxK,sDACA,SACA,SACA,OACA,8CACA,yDACA,wDACA,gDACA,0DACA,gDACA,UACAwK,EACAvK,EACAmT,GACAwB,QACApD,wBACAqD,gBACAC,cACAC,kBACApK,UACA,CAsBA,wEAmCA,yCACAC,0BAGAoK,gDACAC,gDACAC,sDACAC,iDACAC,+DACAC,iEACAC,iEAMAC,+CACAD,iCACAA,yDACArV,mBACAqV,+CACA,EACA,OAMAE,+CACAH,iCACAA,yDACApV,mBACAoV,+CACA,EACA,OAKAI,iCACAN,QACAO,wBACAC,mBACAC,sDAGAP,yDAGAA,gDACAC,+CACA,EAKAO,qCACAV,QACAO,sBACAC,qBACAC,qEAEAR,mCACAC,+CACA,EAKAS,qDACAC,+CACAA,4CACAf,qCACAC,qCACAC,2CACAG,6EACAI,aAEAN,+BACAzJ,mBAEAyJ,2BACAU,eAEAJ,YAEA,IACAM,mBACAb,wCACAF,wCACAC,0CAEAC,wCACAF,qCACAC,wCAEA,EAGAvO,8CACAoP,sBACA,GAEAA,uBAGAV,6CAEA,+BACA,+BACAW,wDAEAV,kFAEAA,oFAEA,EACA,CAOA,mCACA,oDACAW,yDAEA/F,0CACAA,sDAEArF,wCACAqF,qDACA,GAEAA,yDAEA+F,uCACAA,iCAEAtP,iDACA,EACA,CAKA,mCACA,6BACAuP,uDACAC,6CAEAtL,yDACA,iBACAjI,QACA,oBACAG,WACA,4BACA,SACAgS,gEACA,EACApJ,mBAEAuK,2CACAF,mCACAE,0CAEArL,sBACA,EACA,CAOA,+BACA,wEACAuL,WACAC,oBACAA,wCACAA,yCACA,CACAC,sCAEA,mDACAxJ,kBAEA,EAEAyJ,uCACAC,0BACAC,6BAEAJ,sEACA,IArOA1P,iDACAO,qCACAkG,wCAGA4I,8BACAA,gCAEA9V,wEAEA8V,+CAGAhB,sDAEAH,oEACAA,oDAKA3U,gCACAwW,4CACA/F,kDACAlG,mCACA,MA6MA,CAKA,6BACAA,+BACAoK,2DACA,GAEApK,YACA3B,2BAEA6N,qBADA7I,QAEA6I,8BACA7I,qCAEA,GACAhF,wEAEA6N,qBADA7I,SAEA6I,6DACA7I,wFACA+G,mCACAA,8CAEA,EACA,CAvRA,aAKA,2CACA+B,8BACAF,wCACA/F,+CACAlG,mCACA,OAoRA,OACAoM,YACAC,mCACA,CACA,GCvTA7W,yDACA,SACA,SACA,mDACA,iDACA,uBACA,mDACA,aAoCA,+BACA,sBACA,WACA,UACA,UACA,OACA,QACA,CACA,CAOA,+BACA,yDACAC,6EACA6W,8BAEAA,GACA,KACA,CASA,qCACA,0DACA,8CACA,MAhEAC,kBACAC,8BACAC,sCACAC,oBACAC,kCACAC,sBACAC,mCACA,EAMAC,iBACAC,iBACAzU,oDACA0U,gDACAC,mDACAvL,oDACAwL,+CACAC,2CACAC,qDACA,CACA,EAgDA,OAOAC,4BACA,cACAC,SAEA,yBACA,kDACA,gCACA,MAWA,MAVA7X,gDACA,mCACA8X,oBAEAD,yEACAC,mBACAD,6EAEAA,eAGA,aACA,EAOAE,gCACA7H,WACA8H,yDACApU,UACA,aAqCA,MAnCA5D,oCACA,WACA,iBACA,mCACA4D,+CACA,0CACA,UACA,IAEA,mBACAqU,yCAEAC,iDACA,MACA,SACA,WACA,QACA,QACA,GACAlY,+CACA,iDACAmY,WACAd,+DACAR,qFAEAA,8BAGA,IACA,0BACAqB,4CAEAtU,oBAEA,GACAA,MACA,CACA,CACA,GCjKA7D,0DACA,SACA,SACA,WACA,8CACA,+CACA,sDACA,aAEA,OACA4C,6DACAuN,WACAkI,gBACAlR,0CACAT,gDAEA4R,mGAQA,CAEAC,SACA,CACA1R,mBACA2R,iDACA,GACA3R,cACA2R,6CACA,GACA3R,aACA2R,cACA,EACA,IACA,CACA3R,cACA2R,gBACA,GACA3R,iBACA2R,kHACA,EACA,CAEAC,sEAEAC,mCACAvR,sBACAmR,4BACAC,gBACAI,UACAC,mCACA,uCACA,CACA,EACAC,8BACAC,iBACAC,YACA,GAGAC,sDACA7I,uCACA,GAEA9I,oCACA,EAKAuE,oDACA,yCAIA,MAHAyM,WACAW,iCAEAN,mCACA,CACA,CACA,GCnFA1Y,0DACA,SACA,SACA,OACA,WACA,kBACA,iBACA,8CACA,UACA,iDACA,mDACA,kDACA,WACA,sHACA,aAOA,OAQA4W,8DAYA,qCAEA,MADAqC,mEACAC,mCACA,CAOA,0BAQA,MAPAC,yBACA,0DACAA,4BAEA,mBACAA,4BAEAA,IACA,CAKA,gCACAC,4EACAC,8FACA/H,gDAEA+H,QACAA,wBAEAD,sBAEA,CAKA,4BACAA,gFACAC,8FACA/H,+CACA6H,gDAGAG,8EAEAC,+BAGAD,6DACAT,sCACAW,4BACA,CACA,EACA,EACA,CAOA,qCACAC,wCACAJ,8FAyBA,MAvBAI,YAEAJ,SASAA,2CACAA,wCATAK,oDACApI,kDACAqI,mCACA7W,+BACAwO,2BACA,GACA,IAKAsI,iBAGAP,UACAK,oCACAE,iBAIAH,SACA,CAOA,+BAQA,mCACAI,6CACAA,0CACA,CAGA,6BACAH,wBACA,iBACA7M,kBAEA,CAGA,uCACA,kEACAiN,qDAEA,CAGA,6BACAL,gEAEA7U,+DACA,OACA6I,QACAsM,SACA,CACA,GAGAC,0BACAC,2BACAvJ,aACAwJ,aACA,GAGAT,WACAU,sCAEAL,yBACA,IAlDAlP,0BACAwP,yDACAC,2DACAL,oDACAG,+CAgDA3P,gCACAyP,2BACAvJ,YACA,GAEA9F,wCACA0P,wEACAA,uEACAA,mDACAA,oDACAN,yBACAO,gBACA,GACAD,oDACAE,qCACAD,gBACA,GAEAT,qCACAS,iBACAE,cACA,CAOA,iCACA,4CAEAH,uEACA,mCACAI,0BAEA,GAEA,mCACAA,0BAEA,IA3MAhU,iDACAS,0CACAoS,2DA2MAG,gDACAA,iCACAA,sBACAD,kGACA3T,iHACA6U,4GACA,EAEAjU,cACAkU,mDACA,uEACAC,gFAEAnU,yEACAgT,kCACAA,gCACAc,qCACAM,yCAGAA,uCAEA,GACAjS,+CAEAnC,mCAEAqU,wGACArU,mCAEA,GAEAkO,uEAEAgF,gBAIAU,2CACAhB,6DACA,GAEAC,gDAEApS,yFACA,GAGAA,qFACA6T,4CACA,EACA,CACA,CACA,GCtQAhb,0DACA,SACA,oBACA,0BACA,aASA,sCACAC,8CACAgb,oCACA,EACA,CAOA,8CACAhb,0DACAib,kCACA,EACA,CAOA,4CACAjb,uDAEA,sDAEAib,2CACA,wDACA,+BACA,sBACA1W,YAEA+N,+CAEA,CACA,EACA,CA5CA,uBA8CA,OACA4I,eACAF,mBACAC,iBACA,CACA,GCvFAlb,iEACA,SACA,OACA,wDACA,kDACA,oBACA,6DACA,aAUA,qCACA,0BACA,gCACAC,qCACAwG,qFACA,CAUA,kHAEA2U,wCAKAzJ,sEAGAC,0DAEAnL,yDACAA,gDAGAoL,2BACAC,gCACA,CAeA,8BACA,gCACA,4DAGA,gBAMA,2BAOA,GANAuJ,mDACA,4CACAC,qDAEA,GAEA,WACA,2DACA,KAGArJ,6CAEAC,wDAEAR,uCAEA,iDACA,CASA,yCACA6J,4BACAF,mDACApb,gCACAwJ,uBAEAA,yDACA,GAEA8I,oDAEA,CASA,4CACAgJ,4BACAF,mDACApb,gCACAwJ,+DAEA,GAEA8I,oDAEA,CASA,qOACA,oBACAC,QACAN,sBACAR,eACA,uBACA,wBACA,EACA,CA9IA,8BAgJA,OACA6J,0CACA9I,4BACA1L,4BACA8K,4BACAC,iCACA,CACA,GChKA9R,kEACA,SACA,OACA,oBACA,6BAEA,aAYA,uCACAyG,yBACA,CASA,oCAEA,eACAmB,WACAtG,WACA4Y,MACA3Q,wBACA,EACAiS,eAEA,GACAC,gBACAlJ,oEACA,EAEA,CApCA,4BAsCA,OACAmJ,0BACAC,yBACA,CACA,GCjDA3b,yDACA,SACA,SACA,MACA,OACA,gDACA,8CACA,8CACA,kDACA,gDACA,gDACA,yDACA,wDACA,0DACA,oBACA,mDACA,mDACA,oBACA,UACAwK,EACAvK,EACA0I,IACAyK,GACA/J,SACAuL,QACAgH,YACAC,gBACAlR,UACAmK,cACAtD,wBACAqD,gBACAiH,iBACAC,cACAC,kBACAjJ,WACAkJ,iBACA,CACA,aASA,wEAmDA,+BAqDA,gDACA9F,+EAEAE,sCAOA,mDACAxJ,kBAEA,MAjEAjC,0BAGAsR,8DACAC,kDACAC,6DAIAC,2FAEAC,2CACA,yCACAH,uCACAC,4CAEAD,wCACAC,uCAEA,EACAF,gDACAA,sDACAA,qCACAC,wBACAC,qCACA,iCAEA,GAEAF,2EAGA,sDACAtR,qCACA,2EACA2R,kCAEA,GAIAC,0EAGAlG,0BAEA,kDACAmG,yBAGA7H,2CAgBA,CAKA,uBACA8H,+BACAA,iCAGAV,mEACAW,sBACAzQ,sCACAwQ,sCACAA,wCAGA9F,sFACA,EACA,CAKA,oBACA8F,+BACAA,iCAEAlS,oDACA4L,kBACAlK,kCACAwQ,sCACAA,wCAGAd,kBACA5C,eACA0D,oCACAA,gBACAE,aACAxG,SACA,CACAA,6EACA,EACA,CAOA,0BACA,2DAGAyG,+DACAC,yDACAC,wCAEA,oBACAD,aACAE,OACAC,aACApU,sBAEA,yBAGA6T,oCACA,iEAGAQ,gFAIAA,mEAEAjd,mCACA,0BACA,CACAkd,cACAzU,iBACA,8BACA,EACAwU,gBACA,CAEAjd,6BACAmd,iEAGAC,yDACA,GAEAR,4CAEAE,iCACA,GAEAA,iCAEA,GAIAvS,YACAyS,mFACApU,GACA,aACA,iEACA,qBACA,GACA,wBACAuN,8BACAA,oEACA,MACAlK,mCACAoR,iDAGA1B,qFAMAlV,8CACA,CACA,EAEA,CAQA,6CACA,qCACAwF,2BACAkR,yDACA1W,yBACA,oBACA,OACA,CACA0W,uBACA,8CACA,QACAhH,yCAEAA,4BAEAmH,2CACA,CAMA,wBACAb,+BACAA,iCAEAlS,kDACAsQ,sBACA5O,sCACAwQ,sCACAA,wCAGAb,sFACA,GAGA,uCACArR,oDAEA,CAOA,0BACAA,sCACAgT,qCACAlM,8BACAmM,0BACAjZ,IACA,yBACA0H,0CACAoF,WACApM,SACA,EACA,CACA,GAIAsF,YACAyS,+EACApU,GACA,aACA,6DACA,yBACA,GACA,wBACAiS,sCACAA,wEACA,MACA5O,uCACAwN,oDAEAlP,6DACAqR,+DAMAnV,+CACA,CACA,EAEA,CAQA,mCACA,gEACAuJ,gDAEAA,4CACAgB,kCAEArG,wCACAqG,iCACA,GAEArG,6CACA8R,sDACAzL,iCACA,GAEAhB,uEACA4E,sEAEAnO,wCACA,EACA,CAEA,2CACA,gEACAuJ,0EACA,CAOA,kCAyCA,yBACA,+CACA6L,iBACAH,mEACA+B,uBACAzd,iBACA,iCACAyc,mCACAhC,qDACAA,0BAGA,EAEA,CAOA,iCACAoB,wDACA,CA/DA,kDACApB,QACAjK,SACAkN,MACA/V,iCACA4T,gBACAoC,UACAC,cACA3D,sBACA,OACApX,iBACA,CACA,EACAgb,uBACA,WACA,CACA,EACAC,qBACArN,aACAE,YACAoN,cACAC,qCACAnN,2BACA,8BACA,EACAC,qBACA,GACAlI,wBACA6S,mBACA,GAEAwC,gBACAtT,wCACAsT,eACA,EA8BA,CAEA,yBACA,4BACAC,SACAC,QACAC,aACAC,eACA,CACA,CACA,EAEA9T,8CACAgT,4CACAlM,6BACAmM,0BAEA,oBACAjB,iCACA+B,4CACA/B,sCAEAhY,IACA,+BACA1B,gDACA4D,yBACA,oBACA,aACA,CACA8X,yCACAtS,QACAqS,aACAE,WACAC,oBACAC,uCACA,CACA,EACA,EACAC,qCACA,GACAlC,iCACA5H,iFACA,MAEAzI,wEACAwS,kBACA,uGACAxS,gBACAqQ,sBACA,uBACAoC,mCACA,CACAC,oBAEAvU,yDACAgS,sEACA,GACAwC,gBAEAxU,wCAIAkS,+CACA3J,kEACAtJ,wDACA/C,yBACA,oBACA,OACA,CAEA,GACA8V,qEACAE,gCACAuC,YACA,IACA,EACAlD,yEACAmD,SACAlc,iBACAmc,sBACAA,mCACA,KACA,yEACA3C,wEAEAyC,YAEA,CACA,GAIAzU,YACAyS,oEACApU,2FACA,GACA,wBACA8T,qCACAA,2CACA,MAEAyC,kDACAC,gEAEA7C,sCACA6C,gEACA7C,2CAIA5F,oEAEAhC,wCACAA,4CAEAA,uCAMAlO,qDACA,CACA,EACA,MA/jBAoY,4BAEAQ,2DACAC,6CACAC,6CAEA9Y,gDACAO,iCAGAyV,qCACAA,uCAEAA,6BACAA,uDAEAzc,+EAEAA,0CACAyc,iCAGAE,oEACA,4DAEA,CAEAhI,4EACAA,2DACAA,wCACAA,gDAEA6K,cACAC,WACAC,iBACAC,eACAC,iBAEA7D,mDAEAxR,wCACAA,0DAEAsV,eAqhBA,CAKA,6BACAtV,kCACA,4CAEAoK,qCACAA,gDACAA,sCACA,GAEApK,YACA3B,wBACA,0BACA,mCACA,mDACAmW,gBACApK,oCACAA,wCAEA,4CACAA,sCACA,MACA,CACA,GACA/L,yDACA,0BACA,oEACA,oEAIA,GAHA+L,qCACAA,gDAEA,yCACA/G,8CACAkS,+CACAnL,gDACAA,oCACAA,uCACA,CACA,CACA,EACA,CAMA,OACAgC,YACAC,mCACA,CACA,GClqBA7W,sDACA,SACA,SACA,MACA,OACA,gDACA,8CACA,8CACA,kDACA,gDACA,gDACA,yDACA,yDACA,wDACA,0DACA,iDACA,oBACA,mDACA,mDACA,0DACA,oBACA,UACAwK,EACAvK,EACA0I,IACAyK,GACA/J,SACAuL,QACAgH,YACAC,gBACAlR,UACAmK,cACAtD,wBACAwO,iBACAnL,gBACAiH,iBACAmE,eACAlE,cACAC,kBACAjJ,WACAgC,kBACAkH,iBACA,CAWA,+DAgDA,+BAqDA,gDACA9F,yEAEAE,sCAOA,mDACAxJ,kBAEA,MAjEAjC,0BAGAsR,8DACAC,kDACAC,6DAIAC,qFAEAC,2CACA,yCACAH,uCACAC,4CAEAD,wCACAC,uCAEA,EACAF,gDACAA,sDACAA,qCACAC,wBACAC,qCACA,8BAEA,GAEAF,2EAGA,sDACAtR,qCACA,wEACA2R,+BAEA,GAIA2D,uEAGA5J,0BAEA,+CACAmG,yBAGA7H,wCAgBA,CAMA,uBACAgI,4BACAA,8BAGAZ,gEACAQ,qBACAtQ,qCACA0Q,mCACAA,qCAGAqD,8FACA,EACA,CAKA,oBACArD,4BACAA,8BAEApS,oDACA4L,kBACAlK,kCACA0Q,mCACAA,qCAGAhB,mGACAxF,6EACA,EACA,CAOA,0BACA,2DAGAyG,+DACAC,yDACAC,wCAEA,oBACAD,aACAE,OACAC,aACApU,sBAEA,yBAGA+T,iCACA,8DAGAM,6EAIAA,mEAEAjd,mCACA,0BACA,CACAkd,cACAzU,iBACA,8BACA,EACAwU,gBACA,CAEAjd,6BACAmd,iEAGAC,yDACA,GAEAR,4CAEAE,iCACA,GAEAA,iCAEA,GAIAvS,YACAyS,gFACApU,GACA,aACA,8DACA,qBACA,GACA,wBACAuN,8BACA,wCACA,MACAlK,mCACAoR,8CAGA1B,+EAMAlV,8CACA,CACA,EAEA,CAQA,6CACA,qCACAwF,2BACAkR,yDACA1W,yBACA,oBACA,OACA,CACA0W,uBACA,8CACA,QACAhH,yCAEAA,4BAEAmH,2CACA,CAMA,wBACAX,4BACAA,8BAEApS,kDACAsQ,sBACA5O,sCACA0Q,mCACAA,qCAGAf,mFACA,GAGA,oCACArR,oDAEA,CAOA,0BACAA,sCACAgT,qCACAlM,8BACAmM,0BACAjZ,IACA,yBACA0H,0CACAoF,WACApM,SACA,EACA,CACA,GAIAsF,YACAyS,4EACApU,GACA,aACA,0DACA,yBACA,GACA,wBACAiS,sCACA,4CACA,MACA5O,uCACAwN,iDAEAlP,6DACAqR,+DAMAnV,+CACA,CACA,EAEA,CAQA,mCACA,6DACAuJ,gDAEAA,sDACAgB,kCAEArG,wCACAqG,iCACA,GAEArG,6CACAgS,mDACA3L,iCACA,GAEAhB,uEACA4E,mEAEAnO,wCACA,EACA,CAEA,2CACA,6DACAuJ,0EACA,CAOA,kCAyCA,yBACA,4CACA6L,iBACAH,gEACA+B,uBACAzd,iBACA,8BACA2c,gCACAlC,qDACAA,0BAGA,EAEA,CAOA,iCACAoB,qDACA,CA/DA,kDACApB,QACAjK,SACAkN,MACA/V,iCACA4T,gBACAoC,UACAC,cACA3D,sBACA,OACApX,iBACA,CACA,EACAgb,uBACA,WACA,CACA,EACAC,qBACArN,aACAE,YACAoN,cACAC,qCACAnN,2BACA,8BACA,EACAC,qBACA,GACAlI,wBACA6S,mBACA,GAEAwC,gBACAtT,wCACAsT,eACA,EA8BA,CAEA,yBACA,4BACAC,SACAC,QACAC,aACAC,eACA,CACA,CACA,EAEA9T,8CACAgT,yCACAlM,6BACAmM,0BAEA,oBACAyC,8BACA3B,yCACA2B,mCAEA1b,IACA,+BACA1B,gDACA4D,yBACA,oBACA,aACA,CACA8X,yCACAtS,QACAqS,aACAE,WACAC,oBACAC,uCACA,CACA,EACA,EACAC,qCACA,GACAhC,8BACA9H,8EACA,MAEAjH,uCACA3B,4CACA2S,kBACA,uGACA,cACAjC,mBACA,gBACAkC,mCACA,CACAC,oBAEAvU,yDACA0V,mEACA,GACAlB,gBAEAxU,wCAIAoS,4CACA7J,kEACAtJ,wDACA/C,yBACA,oBACA,OACA,CAEA,GACAwZ,+DACAtD,6BACAqC,YACA,IACA,EACAlD,yEACAmD,SACAlc,iBACAmc,sBACAA,mCACA,KACA,mEACAe,kEAEAjB,YAEA,CACA,GAIAzU,YACAyS,iEACApU,uFACA,GACA,wBACA2T,oCACA,2CACA,MAEAnQ,gDACAqQ,2DAEAwD,mCACAxD,4DACAwD,wCAIAD,8EAEArL,qCACAA,yCAEAA,sCAMAlO,oDACA,CACA,EACA,MAxjBAoY,4BAEAQ,wDACAC,0CACAC,0CAEA9Y,gDACAO,iCAMA,GAHA2V,kCACAA,oCAEA,0BAEA,+DACAA,sFACA,CACA3c,yEAEAA,0CACA2c,8BAGAA,oEACA,4DAEA,CAGA7H,0DAEAH,yEACAA,qDACAA,qCAEA6K,cACAC,WACAC,iBACAC,eACAC,iBACAC,eAghBA,CAKA,6BACAtV,+BACA,sCAEAoK,kCACAA,0CACAA,mCACA,GAEApK,YACA3B,wBACA,YACA,0BACA6N,8BACA7I,qCACA+G,+CACAoK,gBAEA,gEACApK,iDACA,OAEA,GACA/L,yDACA,0BACA,mEACAsX,sFACAC,2CAGA,GAFAxL,kCACAA,0CACA,oCACA,2EACAA,iDACA,yBACA,wDACAA,qCACA,CACA,CACA,GACA/L,6CACA,yBACA2B,wCAEA,GACA3B,8CACA,yBACA2B,2CAEA,EACA,CAOA,MAjoBA,aAioBA,CACAoM,YACAC,mCACA,CACA,GChrBA7W,uDACA,SACA,SACA,gDACA,8CACA,8CACA,gDACA,gDACA,yDACA,yDACA,0DACA,UACAwK,EACAvK,EACAoJ,SACAuL,QACAyL,YACA1V,UACAmK,cACAkL,iBACAxO,wBACAuD,kBACA,CAUA,mDAoBA,oCACAnK,0BAGAU,0DACAV,2CACA,yDACAU,mCAMA5E,mDAEA,GAGA4Z,8EACA,iEACAzT,kBAEA,GAGAyJ,0BAEA1B,oDACA,CAMA,oBACA2L,+BACAA,iCAEA/V,6CACA0V,kBACAhU,kCACAqU,sCACAA,wCAGAF,wFACA,EACA,CAOA,sBACA7V,qCACAgT,gCACAlM,0BACAmM,0BAEAjZ,IACA,+BACA1B,gDACA4D,yBACA,oBACAoY,gCACA,CACAN,yCACAtS,QACAqS,gBACAE,WACAC,oBACAC,uCACA,CACA,EACA,CACA,GAIAnU,YACAyS,2DACApU,8EACA,8DACAqD,mCACA0Q,iDAGAyD,kEAMA3Z,iDACA,CACA,EACA,CAQA,wCACA8Z,6DACAvQ,gDAEAA,8DACAgB,kCAEArG,wCACAqG,iCACA,GAEArG,6CACA2V,gDACAtP,iCACA,GAEAhB,uEACA+P,iEAEAtZ,wCACA,EACA,CAEA,2CACA,kEACAuJ,wFACA,MAtJA6O,4BACA3R,mCACAqS,2CACA9Y,gDAGAqO,wDAGAH,sEACAA,sDACA6L,WACAC,YA2IA,CAKA,6BACA,8BAEA9L,mCACAA,4CAEApK,gBACA3B,wBACA,0BACA6N,8BACA9B,oCAEA,GACA/L,yDACA,2BAEA6N,+DAEAiK,0BAGA/L,mCACAA,4CAEA,EACA,CAOA,MAtMA,aAsMA,CACAgC,YACAC,mCACA,CACA,GClOA7W,mDACA,SACA,SACA,OACA,WACA,cACA,oBACA,gDACA,8CACA,+CACA,gDACA,gDACA,0DACA,UACAwK,EACAvK,EACAmT,GACAyG,MACA3R,SACAwG,SACArF,SACAuL,QACAgM,aACAjW,UACAmK,cACAC,kBACA,CASA,kCAkBA,qBACAnP,sBACAA,wBAEA4E,kCACA8V,mBACApU,mCACAtG,6BACAA,+BAGAgb,uEACA,EACA,CAQA,+BAUA,mCACAC,gFACAC,wCAEAjH,yEACAA,2CACAA,8CACAA,0BACAA,iFAEAkH,gCAKAra,kDAEAqa,4BACA,CAEA,0BACA,4CAEAC,2EACA,IAjCApW,0BACAqW,mDACAC,yCACAC,yDACAC,8DACAC,yDACA9E,sDACAwE,+CA4BAvW,mDACAyP,2BACAvJ,YACA,GAEA2Q,gCACAA,mDACA3a,cACAmC,yCACAnC,8CACAwB,WACAwV,mDACA7U,wBACAwY,wDACA,EACA,GACAC,yBACA,GAEA1W,2CACA,+DACA2W,6BAGAhF,yBAEA,GAEA7V,iDAEA6a,iCACAhH,gBACA,CAOA,uBACA/P,4BACAgT,uBACAlM,2BACAmM,0BAEA,wCACAjZ,IACA,sBACA1B,gDACA4D,yBACA,WACAoY,6BACA,CACA5S,oBACAsV,8CACAC,8CACAC,oBACA,CACA,+BACA5e,gDACA4D,yBACA,oBACAoY,gCACA,CACAN,yCACAtS,QACAqS,gBACAE,WACAC,oBACAC,uCACA,CACA,EAEA,EACA,CACA,GAGAnU,YACAyS,+BACApU,yDACA,cACA,yDACA0X,2CAGAK,uDAEAhM,kDAMAlO,4CAEA,EACA,CA3KA,oCACAA,iDACAd,mCAGAmP,oDAEAH,8EACA+M,YACAC,aAmKA,CAEA,MAvLA,aAuLAC,QACA,GCjNA7hB,+DACA,SACA,yDACA,mDACA,iDACA,oDACA,gFACA,aAkUA,6DACA,uEAOA,MALAuE,8BACAA,6BAEAud,2DAEAvd,cACA,CAaA,8DACA,uEAOA,MALAA,8BACAA,6BAEAud,2DAEAvd,cACA,CASA,oEAYA,MAXAtE,2BACAuF,wDAGAvF,6BACAkC,wCAGAoC,6DACAA,sDAEAA,cACA,CASA,2EAIA,MAHAA,gEACAA,+DAEAA,cACA,CASA,gEACA,sBACA,2DACA,mEAEAA,kDACA,KACAA,qCAGA,qBACA,CASA,8DACA,qBACA,0DACA,mEAEAA,gDACA,KACAA,oCAGA,qBACA,CAQA,wCACA,oDACA,4DAEA,iBACA,CAQA,kCACA,8CACA,6DAEA,cACA,CAQA,uCACA,gDACA,sEAEA,eACA,CAOA,2BAOA,MANA5B,SACAA,UAEA1C,mBACA0C,eAEAA,KACA,CAjeA,0BAWAC,mDACA,4FAMA,MAJAmf,aACAC,8DAGAzd,cACA,EAQA0d,gEACA1d,iBACAtE,uBACAsE,4BACAA,gCAEAA,6DAEAA,6BACAA,iCAEAwd,aACAxd,wDAIA,EAQA2d,gEACA3d,6BACAA,4BACAA,iEACAA,gCAGAtE,sBACAsE,0GAEAA,2BACAA,4CAEAA,sDAIA,EAUA4d,gEACA,+FACA,EASAC,6BACA,uCACA,EASAC,6BACA,uCACA,EASAC,mCACA,0CACA,EAQAC,wBACA,6EAOA,MALAhe,8BACAA,8BACAA,qHACAA,+EAEAA,cACA,EAYAie,2GACA,gEAQA,MANAje,kEACAA,sDACAke,qDACAC,wCACA7Q,kEAEAtN,cACA,EAWAoe,wGACA,iEAQA,MANApe,iEAEAke,qDACAC,wCACA7Q,kEAEAtN,cACA,EAQAqe,8EACA,kEAKA,MAHAF,yCACA7Q,kEAEAtN,cACA,EAQAse,yCACA,4DAKA,MAHAte,4EACAA,4EAEAA,cACA,EAUAue,wDACA,0FAIA,MAFAL,sDAEAle,cACA,EASAwe,iCACA,uFACA,EAQAC,mCACA,kFACA,EASAC,kEACA,mEAEA,+DACA,mEAGA,gFACA,qEAUA,MAPA1e,oCACAA,iCAEA2e,cACA3e,wCAGAA,cACA,EASA4e,qDACA,4DASA,MAPAljB,0BACAmjB,2BAGA7e,sDACAA,6DAEAA,cACA,EAQA2e,8CACA,8DAQA,MANAjjB,0BACAmjB,2BAGA7e,6DAEAA,cACA,CAEA,EA+KA,2BACA,GC7eAvE,wDACA,SACA,OACA,cACA,iDACA,gDACA,uDACA,yEACA,0BAwwBAqjB,SApYA,4EACAvd,sEACAvB,oDACAzB,WACAkf,yBACAA,mCACAsB,wBACA,GACAC,iCACAC,SACA,CACA,CACA,CAEA7e,sDACA,CAWA,0EACAmB,sEACAvB,oDACAzB,WACAkf,yBACAA,4FACA,CACA,CACArd,sDACA,CAUA,6EACAmB,sEACAmd,uDACAjB,+BACAA,0EACAA,qCACAlf,WACAkf,uDACA,CACA,CACAA,iCACAA,qCACAlf,WACAkf,4BACAA,+CACAA,6CACA,CACA,CACA,CACA,CAEArd,wCACAA,0DACA,CAWA,2FACAmB,wEACAvB,oDACAzB,WACAkf,yBACAA,4BACAA,+CACAA,+CACA,CACAA,8DACA,CACA,CAEArd,sDACA,CAUA,iFACAmB,wEACAvB,oDACAzB,WACAkf,yBACAA,+CACAA,8DACA,CACA,CAEArd,sDACA,CAWA,iFACArD,gCACAwE,qDACAvB,qDACAyd,+BACAA,2BACAA,wCACAA,0DACA,CACAA,6FACA,CACAA,iCACAA,gGACA,CACA,CAEArd,wCACAA,wDACA,CAQA,kDACA,8CACA,CASA,0DACA,aAgBA,MAfA8e,uDACAV,SACAW,gBACAzjB,4CACA,GACA6F,iCACAA,iDACAA,gDAGA,MADAid,YAGA,IAGAA,KACA,CASA,wDAKA,+CACA,oBAmDA,MAhDAU,sDACAxjB,4CAqCA,GAlCA6F,kCACA,sEACA,oEAEAM,YAEAnG,wCAkBA,GAhBA6F,4BACAhD,wEAEA6gB,gBAEA7d,0BACAhD,sEAEA6gB,gBAEA7d,0BACAhD,wEAEA6gB,iBAGAA,aACA,QAEA,GAGAA,gBAIAA,aACA,QAEA,GAGA,gCACAA,6EAGAA,YACA,IAzDAC,yCACAb,SA6EA,MAlBA9iB,yCACA,sBAQA,GAPAA,yCACA,8BAEA,MADA4jB,qBAGA,GAEAA,iBACAd,mDAEA,QACA,QAGA,GAEAA,KACA,CAOA,mCACA,kBAGAU,QACAA,kBACAG,kCAEAH,uDAGA,iBACA,CAOA,gCACA,mBAGAA,QACAA,kBACAK,oDAEAL,uDAGA,kBACA,CAQA,uCACA3gB,wDACAihB,UAOA,MANA9jB,sDACA,+CAEA,MADA8jB,yBAGA,GACAA,IACA,CAOA,oCACA,aAOA,MANA9jB,sCACA,sCACAwjB,SACAO,qBAEA,GACA/jB,aACA,CAOA,yCACA,mCACA,oDACA,CAOA,mCACA,wBAUA,MATAA,oFACA,sEACAA,qDACA,kDAEA,MADAgkB,uBAGA,EACA,GACAA,gBACA,CAOA,4BACA,6DACA1U,sBACA,6FACA,GACA2U,UACAC,OACAxhB,QAIA,MAHA1C,qBACAmkB,0BAEA,yBACA,CAOA,oCACA,cAQA,MAPAzf,mFACA,+EACAyf,4CAEA,GACAA,0CAEAA,0BACA,CAOA,gDACA3d,gCACA4d,gEACA1e,4DAGAb,mDACAwf,0CACAC,2CACAC,2BAIA9e,2BAHA+e,SAyCA,MAnCAD,8BACAA,MAGAvkB,kBACA,UAEAwkB,YACAxkB,sCACAykB,sDACAD,4CAEA,GACAH,6CACAE,0BAGA,YAEA9e,gCAIA,aACAgf,mCACAhe,iCACAA,8BACA,GAEAhB,8BA1BAA,0BAgCAA,iBACA,CAOA,sCACA,mCACA,OACAse,sBACAW,wBACAC,4CACAC,4BACAC,wCACApf,qDACA,CACA,CAMA,8BACA,oGACA,cACA,GAEAf,qDAGA,2CACA1E,0BACAA,4CACAsE,iBACAwgB,sEACAhC,UAGA,mCACAte,6CAEA,oCACAA,8CAEA,qCACAA,+CAEA,kCACAA,4CAEA,oCACAA,8CAGA,CA1BA,aAiCA,MALA,yCACAA,2BAEAse,qEAEAA,KACA,EACA,CAOA,4BACA,OACAsB,2EACA3e,mBACAC,sEACA,CACA,CACA,IA55BAqf,oBAMAC,+BAMAC,iBACAC,MACAzhB,WACAgF,iBACA0c,0EACA,EACAC,QACA3hB,aACAgF,mBACA0c,2FACA,EACAE,OACA5hB,YACAgF,wBACA0c,wKACA,IACAhS,uLACA,EACAmS,KACA7hB,UACAgF,sBACA0c,wQACA,IACAhS,sLACA,CACA,EAMAsR,iBACAS,MACAzhB,WACA8hB,QACA,EACAH,QACA3hB,aACA8hB,QACA,EACAF,OACA5hB,YACA+hB,+CACA7gB,UACA,CACA8gB,eACA5iB,yBACA6iB,gCACAC,uCACAC,6CACA,EACA,CACAH,aACA5iB,6BACA6iB,oCACAC,2CACAC,iDACA,EACA,CACAH,eACA5iB,yBACA6iB,gCACAhB,iBACAW,oBACAQ,qBACA,EACAC,eACAT,6BACAQ,8BACA,CACA,EACA,CACAN,QACA,EACAD,KACA7hB,UACAsiB,gBACAP,8CACA7gB,UACA,CACA8gB,aACA5iB,sBACAoF,8BACA+d,oBACAC,4BACAN,sCACAO,6CACA,EACA,CACAX,QACA,CACA,EAMAY,iBAOAC,wDACAC,iBACA1hB,SACA2hB,sBACAA,iCACAA,+BACA,CACAjD,0BAEAgD,iBACA1hB,SACA2hB,oBACAA,+BACAA,6BAGA,EASAjB,mEAEAkB,iEACAD,+CACAC,+DAIAD,2CACAtmB,wCACAumB,sBACA5hB,SACA0e,QACAmD,iEAEAjD,SACA,CACA+C,uDACAC,sBACA5hB,SACA0e,QACAmD,+DAEAjD,SAGA,EAEA,EASAsC,+DAEAY,+DACAH,+CACAG,6DAIAH,2CACAtmB,wCACAymB,oBACA9hB,SACA0e,QACAmD,iEAEAjD,SACA,CACA+C,uDACAG,oBACA9hB,SACA0e,QACAmD,+DAEAjD,SAGA,EAEA,EAUA+B,kEACAV,2BACA8B,iDACArB,iDACAQ,6CACAO,iDACAO,8DACAC,oCA4CA,MAzCAC,oFAGAP,qBACAQ,yBACAniB,SACA2hB,oBACAA,sBACAA,sBACAA,0BACA,CAIAA,2CACAtmB,2CACA+mB,kFACAC,wFACAC,+EACAC,6EAEAC,oBACAxiB,SACAqiB,0BACAC,wBACAC,wBACAtC,SACA,CAEA0B,6BACAQ,yBACAniB,SACA6hB,4DACAQ,0BACAV,sBACAA,0BAGA,GAGA3hB,QACA,CACA,EAEAyiB,eAWAC,kCACA,UAEA,yDACA,8DAGA7gB,+BAGAA,2CAEAC,cACAmC,sDAQAnC,+EACA,GACAmC,yCACAlE,8CAOA+B,4CACA,GAMA4a,6BACA,EASAiG,0CACA,4DAEA,yDACA,8DAQA,GALA9gB,+BACA6c,sBACA1e,4BAGA0e,QAEA,GADAkE,yDACAA,cACAA,qBAEAC,wBAIAC,kCAGAC,kCACAvhB,0CAIAnG,mCACA,0CACAylB,0CACA,OAEA,gFAIA,eACA,CACA,EA4iBA,oBACA,GC/6BA1lB,8DACA,SACA,SACA,OACA,WACA,kBACA,YACA,cACA,UACAwK,EACAvK,EACAmT,GACAwU,KACAlgB,WACAmgB,OACA3f,SACA,CACA,aA8CA,uIAGA,qCAMA4f,WAHAC,aAQA,gCAKAT,OAGA,MAFAS,uCAEA,IACA,EAMAC,UA8DA,MA7DA,aACAF,UAGAtd,UACA3B,iCACA,8BACA,qBAEA,GAEAA,8BAGA2B,mBACA3B,yBACA,2FACA6C,6BACAA,mBAEAtE,8BACA,uCACA0B,gBACA,0BACA,iBACA4C,gBACA,GACA3C,cAEA,2BACAD,gBACA,0BACA,iBACA4C,gBACA,GAEA3C,cAGA,GAEA3B,YACAyB,sDACA+R,qCACA,0BAIA,MAHAxT,iCACA6gB,kCAEA,kDACA,yBACA,KACArN,6EACAxT,8BACAc,oJACA,IAEA,yBAEA,IACA0S,8CAEA,IACA,EAOAsN,qBACAC,+BACA/gB,2BAEA,EAOAghB,YAQA,MANA5d,oBACAyS,aACAzS,uBACAA,gCACApD,yBAEA,IACA,EAUAihB,uBAIA,MAFA3V,oCAEA,+BACA,UACA,gBAGA,sBACA,iBAGAoV,UACA,eAIAQ,WADA5V,oCACAmV,QACAnV,gBACAyL,qBACAoK,cACAC,eACAC,2CACAC,iBACAJ,kBACAK,mBACA,CACA,GAEAd,QACAnV,gBACAyL,0BACAoK,cACAC,eACAI,uCACAH,2CACAC,iBACAJ,kBACAK,mBACA,CACA,GAEAL,2CACA,EACA,EAMAO,aACA,2CACA,0EACA,EAMAC,oBACA,kBACA,IAEAC,qEAGAA,2CACA,YACAA,eACA,CACA,iBACA,CACA,GAEA,8BACA,MAtPAC,WACAC,qEACAC,0EACAC,oEACAC,0IACA,EACAC,eACA5b,cACAnM,eACAoH,gBACA4gB,QACA,GACA7b,YACAnM,eACAoH,eACA4gB,QACA,GACAC,oBACA9b,cACAnM,eACAoH,uBACA4gB,QACA,GACA7b,YACAnM,eACAoH,mBACA4gB,QACA,GACA7b,UACAnM,YACAoH,iBACA4gB,QACA,GAwNA,2BACA,GChRAtpB,gDACA,SACA,SACA,SACA,UACA,OACA,oBACA,cACA,0BACA,+CACA,2CACA,2CACA,+CACA,8CACA,8CACA,iDACA,gDACA,gDACA,gDACA,yDACA,mDACA,sDACA,0DACA,6BACA,cACA,iDACA,UACAE,OACAsK,EACAvK,EACA+I,QACAoK,GACA1E,SACAxG,SACAshB,mBACAC,sBACAC,SACA7H,SACAjB,aACAP,YACAsJ,YACAC,eACAjf,UACAmK,cACAuS,cACApX,iBACA8C,WACA8W,cACA9U,kBACA+U,iBACAC,cACA9J,eACA,CACA,kBACAlY,uDAOAiiB,YACAC,UAWAC,oBACA7iB,+BACA8iB,mBACAC,2BACAC,6BAEArR,gBACAsR,OACAC,cACA7jB,cAEA,oBAEAJ,6CACAA,kCACAA,kCACAA,gGACAA,uDAEA2J,uDAGAoa,qBACA3e,mBACAsN,sCACA,GAGAwR,oCACAJ,oDAEAA,0BACA1e,mBACA0e,iCACApR,iCAEA,GACA,8BACA3R,0DACA+iB,4DAGAA,uDAGA,CAGAV,8DAGAriB,wDACA,8CACAX,mCAEA,GAGA6jB,sCACAniB,SACAqiB,wEACAC,uEACA,EACAC,UACAC,sBACA,EACAjgB,oBACAkgB,kBAEA/V,uCAGAA,sCAGA,IACA/B,+BACA,YAGA,MAFAoX,oDACAjiB,qEAEA,CACA,QACA,CACA,GAGAoiB,4EACAtR,iDACArQ,gBACAyV,sBACA6L,sBACAa,yCACA,GACA9R,mCACAtS,gDAGA2gB,kCAGAtU,6CAGA8O,yBAGAjB,iCACAP,gCACAJ,mCACA0J,gCAEAE,kEAEA7Q,oCACAmR,8BACAA,gDACAG,YACA,WACAH,mDACAjiB,qCACA6iB,sBACA/R,+BACA,EACA,WACAmR,kDACA,EACA,CAEA,GAEAnR,uCACA,kEACAgS,6BACAC,iDACAhkB,uBACA,qEACAikB,YACAC,YACAC,oCACA,eACArjB,kBACAG,iBACAkL,2EAEA,EACA,CACA,GAEA4F,sCACAA,+BACAqS,qBACA,EACA,GAGAlB,kCACAnR,kCACAsS,uBACApjB,mBACAkL,+HACA,EAEA4F,8BAEA,EACA,CACA,EAEA,iBACA,GCnPAhZ,gIACA,aAmBA,mDAKA,MAJAsG,qBAEAA,qBAEAilB,oDACA,EAEA,OACA5kB,mBACA,CACA,GC9BA3G,oDACA,aAEA,OACAwrB,SACArM,cACAvK,SACA1I,kCACA,CACA,EACAuf,WACA7W,SACA8W,8BACA,CACA,CACA,CACA,GCpCA1rB,4DCsBAA,8CACA,SACA,SACA,OACA,UACA,SACA,cACA,cACA,qBACA,cACA,WACA,cACA,iCACA,yBACA,oCACA,UACAwK,EACAvK,EACAmT,GACAuY,QACAzrB,OACA0rB,OACA7B,cACA8B,WACA3jB,SACA4jB,QACAC,OACAC,eACAC,OACA,CACA,aAMA,uBACA,iBACA,kBACA,gBACA,YACA,UACA,YACA,CAKA,OAaA/B,cACA,oBACA7iB,yBAEAU,0CACAmkB,mCACAC,uEACA,MAEAC,oBACAC,wBAOAlD,kCACA,+BACA,gBACAmD,SACA,SACAC,iBAEAC,qBACA5kB,+BAEAyjB,mBACA,EAOApjB,6CACAwkB,sCACA,EAOAC,iDACAD,wCACA,EAQAA,6DACAE,UACAC,SACA7kB,cACAG,kBACA,EACA2kB,OACA9kB,eACAG,gBACA,CACA,EACA4kB,0CACAC,wDAEAC,4CAEA/sB,6BACA+sB,+CAEAnB,kBAEAiB,kDAEA,kDACAE,kHACA7D,mCAEA,mBACAkD,2DAEA,EAEAY,mCAEApB,oBAGArhB,6EAGA0iB,uEAKAD,oFACArB,0CAIA3kB,kEAGA+kB,gDACAljB,uBAEA,oDAGA,GAFAqkB,qCAEArP,uEACA,0DACAqP,gDACA,CAEAplB,cACAd,wBACAmmB,0BACA,uBAGAnB,gEACApjB,oBACAA,wBACAA,sBACA5I,mBACAoH,gCACA,EACA,GACAwB,0BACAqR,oBACAmT,uBAEA,GACAC,2BACA,0BAGAnE,gBACA,GAGAtgB,sCACAwjB,eACAA,sBAEAD,kBACA,GAGAvjB,qCACAujB,kBACA,GACA9E,MACA,GACAve,oBACAd,oEACA,IAjEAA,2EAkEA,CACA,CACA,GChOAjI,8CACA,SACA,SACA,oBACA,eACA,oCAEA,aAiDA,mCACA,iDACA,CAMA,iCACAutB,uBACAA,iBAEA,CAiBA,2EAYA,MATAC,iBACAC,eAEAxtB,yBACAytB,YACAC,eACA,GAGA,8BACAC,0BAEAC,4CAEAC,SACAC,aAEAC,mBAEAL,gDAGAI,mEACAA,sBACAJ,yBACAI,uBAIAJ,mBAEAF,eAGAK,sBAGAG,wCAGAH,8CAGAN,cACAU,yBAIAC,gBAEA5b,2DACA4b,cAGA,cACA5b,2EACA4b,aACA,IAGA5b,kEACA4b,cAEA,EACA,CASA,+CAGAC,6BADAC,eAGA,eAEAhnB,2BAEA,gBAEAA,6BAIA+mB,wBACAnuB,eA+CAoH,4BA7CAA,+CAEA,iBACAinB,0BAEA,mBAEAC,aACAF,gBAEA,qBAEAE,aACAF,gBAEA,CAIA,GAFAC,0BAEAruB,8BAIA,MAHAsuB,cACAF,eAGAC,eACAD,YACAC,eACAD,YACAE,aAGA,CAEA,GAEAF,OACAA,sBACAE,QACAA,wBAEAlnB,4BAQA,CAWA,2BACA,iGACA,yCACA,KACA,IApOAmnB,sBAOAC,iBAOAhB,SAOAD,gBAiNA,MA9MAiB,wEACAjB,gBACAvtB,+BACAiuB,uBACA,EACA,GAyMA,CACAjjB,sBACAyjB,uBACA,CACA,GC3PA1uB,+CACA,SACA,SACA,kBACA,eACA,sCACA,gDACA,gBAkBA2uB,iBAKAC,kCACA,4BAOA,kDAEAC,iCAOA,gDACA,EAMAC,iDACA,6BACA,EAOAC,yCACA,iDACA,EAOAC,uCACA,oDACA,EAOAC,qBACA,6BACA,EAMAC,qBACA,6BACA,EASAC,qEACAhf,WACA9I,wBACA+nB,YASA,oEAEAnvB,0DACAmvB,yFACA,GAEAC,4CACA,aACAC,YAEArvB,gCACAstB,OACAE,iBACA6B,4BAEA,GAEArvB,wBACA2S,iEAWAzC,wEACA,EACA,CACA,EAnIA0e,mBAMAS,SA+HA,iCACA,GCtKAtvB,kJAKA,+BAGA,eACA,CARA,iCACAgJ,6DACA,iJAkDA,MA1CAC,sDACAC,uEACAA,+GACAD,iCACA,qCACAG,gIACAA,sCACAH,qJACAE,wNACA,4FACAA,oNACA,8MACAA,4LACA,wQACAA,8LACA,yNACAA,kLACA,uNACAA,oMACA,2NACAA,sLACA,yNACAA,sMACA,6NACAA,0LACA,sNACAA,gOACA,0NACAA,oLACA,kdACAA,gMACA,kCACAA,4KACA,wNACAA,wOACA,qNACAA,sPACA,kSACAA,4QACA,wFACAA,4QACA,+DACAF,MACA,KCtDAjJ,sJAKA,+BAEA,4BAoBA,MAnBAiJ,6EACAG,oJACAA,sCACAH,yBACAC,mEACAA,2GACAD,iCACA,6EACAC,sEACAA,8GACAD,iCACA,wEACAC,sEACAA,8GACAD,iCACA,iMACAG,gNACAA,sCACAH,4DACAA,MACA,CACA,+BAGA,cACA,CAEA,+BAGA,iBACA,CAEA,+BAEA,qBAKA,MAJAA,iIACAG,iIACAA,sCACAH,oEACAA,MACA,CACA,+BAEA,4BA0BA,MAzBAA,wFACAG,oJACAA,sCACAH,yBACAC,mEACAA,2GACAD,iCACA,qFACAC,sEACAA,8GACAD,iCACA,gFACAC,sEACAA,8GACAD,iCACA,uFACAC,yEACAA,iHACAD,iCACA,IACAE,+JACA,qLACAC,8HACAA,sCACAH,mHACAA,MACA,CACA,+BAEA,4BAgCA,MA/BAA,gFACAG,gIACAA,sCACAA,mIACAA,sCACAA,oIACAA,sCACAA,wJACAA,sCACAH,yBACAC,mEACAA,2GACAD,iCACA,uBACAC,yEACAA,iHACAD,iCACA,sGACAC,sEACAA,8GACAD,iCACA,0FACAG,yJACAA,sCACAH,wGACAE,wHACA,yDACAD,sEACAA,8GACAD,iCACA,qIACAA,MACA,CACA,+BAGA,eACA,CAEA,gCAGA,gBACA,CAEA,gCAGA,iBACA,CAEA,gCAGA,eACA,CAEA,gCAGA,eACA,CAEA,gCAGA,eACA,CAEA,gCAEA,iBACAG,0JACAA,0BACA,EACA,CACA,gCAGA,gBACA,CAEA,gCAEA,iBACAA,wJACAA,0BACA,EACA,CACA,gCAGA,cACA,CAEA,gCAGA,cACA,CAEA,gCAEA,oCAkBA,MAjBAH,sKACAE,4QACA,gHACAA,4QACA,oLACAD,uEACAA,+GACAD,iCACA,uBACAC,yEACAA,iHACAD,iCACA,cACAE,oMACA,+EACAA,oMACA,0NACAF,MACA,CA5MA,iCACAD,6DACA,kIAgNA,MAJAC,0EACAG,8HACAA,sCACAH,kCACAA,MACA,KCjMAjJ,2CACA,SACA,SACA,OACA,0CACA,8CACA,kBACA,2DACA,gBAOAuvB,UACAC,gBACAC,sBACAC,0BACAC,gBACAC,oBACAC,kBACAC,oBACAC,gBACAC,gBACAC,0BACAC,OACA7lB,kBACAf,2BACA6mB,gCACA,CACA,EAOAC,YACAC,2BACAC,mCACA3sB,2BACA4sB,4CACAC,oCACAC,8DACAC,0DACAC,kEACAC,4DACAC,gCACAC,6DACAC,4BACAC,wDACAvQ,kCACAwQ,8DACAzY,4BACA0Y,wDACAC,sDACAC,4BACAC,gDACAC,kCACAC,kBACA7B,2BACA8B,uCACAxB,iBACAF,qBACAD,mBACA4B,4BACAC,8BACA/B,gBACA,EAQAgC,YACAnf,OACAof,+BACA/B,8BACAC,gCACA+B,0BACA,EAOAC,aACA,0BACA,sCACA,yBACA,EAOAC,eACA1nB,YACAf,oBACA6mB,yBACA,EAMA6B,YAWA1K,uCACA2K,8CACAC,wCACAC,2CAeA,GAbA,yBACA,iBACA,iCACA,yBAGA,iBACA,yBAIA,2BACAA,2CACAA,aACAA,gDACAC,iCACAzC,kBACA,QAEA,0EAQA,MAJA,iBACA,mBACA,6BAEA,IACA,EAMA0C,oBACA,2DAGA,iEACA,6DACA,qEACA,+DAGA,2DACA,yCAGA,iDAGA,8DACA,EAMAC,uBACA,cAGA,qFACAniB,gBAIAA,+CACAA,6CACAA,qBAEA,GAGA,sFACA,mBAIA,iDACAA,+CACA,GAGA,4EACA,sBAIA6Q,0CACA7V,gDAEAA,OACA6V,gCACA7Q,qBAEAA,iEAGA,GAGA,+EACA,mBAIA,gDAEAA,8CACA,GAGA,iFACA,sBAIAoiB,yCACA7b,QAEA6b,mCACA7b,wBACAA,2EACA,iCACAvG,mBAGAA,oBACAA,oBAGA,GAGA,6EACA,mBAIA,iBAEAqiB,kCACAA,gCACAriB,kBAEA,GAGA,gEACA,sBAIAqiB,cACAzO,uBAEA5T,0CACAA,+CACAqiB,8BAEAriB,mBACA,EACA,EAOAsiB,8BACAC,sEACAC,4BACAA,QACAD,+CAEA,sCACA,2BACA,EASAE,mCAEAC,6FACAC,4CAUA,MAPA3nB,OACA,0BAIA,gEACA2nB,iDACAD,QACA,EAOAE,4BAEAF,+DACAC,4CAKA,MAFA,2BAEAD,QACA,EAQAG,gCACA,4EACAC,qCACA,EAQAC,8CACA,gDAWA,MATAC,mBACA,8DAGA1D,UACA2D,qCAEAA,kCAEA3D,SACA,EAQA4D,kCACAd,qEACA,EAOAe,mCACArD,WACAsD,2BACAC,SACAjE,gBACAA,iBACAA,eACA,CAEAtvB,gCACA,uBAEA,MADAgwB,YAGA,GAEA,0CACA,EAQAwD,iCACAlB,wCACA,2BACA,EAOAmB,yBACAC,yBACAC,oCACAC,sCAEA,wBAUA,+CACA,EAOAC,yBACAH,yBACAC,oCASA,0CACA,EAMAG,6CACA5jB,WACAwiB,kDACA,wDACAzS,kBACAwS,uCACAsB,gCACA1O,oBACA2O,0BACA9jB,6DACA,EACA,EAMA+jB,oCACAC,6DACAC,sCACAC,sBACAp0B,sCACAo0B,sBACA,GACAD,YACAC,2BAEAA,+DACA,EAOAC,qCACA,yBACAr0B,qDACA,mCACAqG,2CAEA,EACA,EAKAiuB,wBACA,iCACA3C,oDAGA,4EACA,oEACA,wEACA,yEACA,EASA4C,yCACAC,4BACA,EAQAC,4CACA,OACApP,iCACAwK,wCACAC,sCACAF,oCACA,CACA,EAQA8E,gDACA,OACArP,qCACAwK,4CACAC,0CACAF,wCACA,CACA,EAQA+E,mDACA,OACAtP,wCACAwK,+CACAC,6CACAF,2CACA,CACA,EAMAgF,qCACAC,uCACAX,qCACAY,yCACAC,+CACAC,4BAIA,MAHAD,qCACAxgB,oDAEAA,WACA,EAGAsgB,eACAC,uCAEAD,kDAGA,uBACA,yBACA,kCACA/D,kBACA,IAEA,6BAEA,gEAGA,uBACA,yBACA,oBAIA,mEACA,EAOAmE,mDACA3C,0FACA4C,6BAGA,6BAGAA,+DACA,0EACA,gCACA,EAQAC,2DACAC,0BACAC,yCACAC,qDACAC,4CAEAC,0BAGAx1B,kDA0BA,GAzBAA,gDAoBA,GAnBAA,gDACA,UACAy1B,2BACAC,cACAC,oBACAJ,cAEAE,kCACAG,iBACAC,uBACAP,iBAGAC,yBACA,QAGA,GAEAA,wBACA,QAEA,GAEAA,wBACA,QAEA,GAGAA,mCACAH,iCAEAG,yCACAH,sCAIAA,iCACAA,qCAGAp1B,iCACA0X,uBACA8d,8BAEA,EACA,EAOAM,oDACA,gEACAC,+DACAD,oEACAZ,4DAEA,kBACA,EAOAc,oCAMA,MALA,mDACA,iCACA,+BACA,8BACA,wCACA,IACA,EAMA7nB,2BAGA,MAFA,kBACA,2CACA,IACA,EAMAC,yBAGA,MAFA,kBACA,8CACA,IACA,EAMA6nB,qBAIA,MAHA,kBACA,eACA,yCACA,IACA,EAMAlZ,qBAIA,MAHA,kBACA,eACA,4CACA,IACA,EAOAnR,6BAWA,MAVA,iBACAmR,kBAGAA,KACA,YAEA,YAGA,IACA,EAOAnU,0BACA,wBAKA,MAJAstB,MACAA,4BAGA,IACA,EAOAlZ,4BACA,wBAKA,MAJAkZ,MACAA,6BAGA,IACA,EAQA7U,oDACA,wBAeA,MAbA,4BACA8U,oBAEAn2B,6BACAm2B,mCAGAA,2BAEAD,KACAA,uCAGA,IACA,CACA,EAYAE,4CACA,qCACA,sCACA,EAEA,wBACA,GCr2BAr2B,gDACA,SACA,SACA,OACA,iBACA,kBACA,4BAsDAqjB,qBADAA,SA/CAiT,kBAQAhP,yCAGA,MAFA,+CACA,oCACA,IACA,EAQAiP,4BAGA,MAFA,gCACA,4CACA,IACA,EAOAN,gCACAO,+DACAC,wDACAV,yEACAZ,4DAGA,MADA,iDACAA,WACA,EAOAuB,+CACApR,4CACAe,0DACA,OACAA,YACA3d,eACA,CACA,EAOAiuB,6CACAC,kDACAC,sBACAxsB,MACAib,oBACAwR,uBACA,EACAxtB,UACAgc,wBACAwR,2BACA,EACA3G,aACA7K,2BACAwR,8BACA,CACA,EACAC,uEACAzR,8CACAwR,yCACA,OACAzQ,uCACA3d,wCACA,CACA,CACA,EAQAsuB,2DACA,yCACA,8CACA,EAEA,6BACA,GClFAh3B,6CACA,SACA,aACA,aAMA,8DAiGA,gBACAi3B,6CACAC,2CACA,CAMA,kCACA,iDACA,IACA7L,2CACA,qBACA,6CACA,sDACA,cACA5nB,6BACAC,IACAD,KACAC,wBACA,iDACA2nB,oCACA8L,WAGA,WACAC,6BAEA,mBAEA,KACA,mBAEA,CACA,CAMA,+BACA,wEACAvzB,4BAEA,aACA,CA9IA,GACAwzB,mCACAC,SAFAL,mBAIA,gDACA,4DAGA,kBACAM,mBACA,uBACA,SACA,qBACA,YACA,eACA,gBACA,SACA,EACAC,gBACA,aACA,eACA,gBACA,qBACA,cACA,iCACA,EAKAC,qDACA,yBACA,EAMAC,0DACAT,wBACA,EAMAU,+BACAL,WACAF,0CACA,EAOAQ,yCACA1d,mBACA,qBACA2d,cAGAA,UACA53B,oBAEAq3B,0BAEAF,0CACA,EAOAU,2BACA,qBACA,EAMAC,+BACAT,SACAjM,iEACA,EAEA2M,iDACA,wCACA,CACA,EAuDA,MAFA1Q,QAEA4P,YACA,EAEA,0BACA,GC7LAl3B,mDACA,SACA,SACA,OACA,SACA,uCACA,mCACA,wCACA,qCACA,wBACA,6BACA,0BACA,iBACA,UACA,cACA,aACA,SACA,WACA,iBACA,CACA,SACAwK,EACAvK,EACAmT,GACAlT,OACAyuB,eACAqD,WACAiG,gBACAC,oBACAC,WACAC,gBACAC,aACAC,eACAC,QACArwB,SACAswB,QACAC,OACAC,MACA,CAEA,6BA6/BArV,qBA3DAA,SAh8BAsV,YACAC,gBACAC,aACAC,aACAC,cAMAtK,iBACAuK,6CACAC,2DACAC,+CACAC,mDACAC,yEACAC,YAEA,qBACA,yBACA,4BACA,uBACA,oBACA,yBACA,4BACA,8BAMAC,oCAEAhB,iCAGA,kBAEAiB,4BACAA,+BACAA,+BAGA,6BACAva,sBAEA,EAKAwa,2BACA,iBAGAlB,mCACApB,sBACA,4CACA,EACA,EAMAuC,wBACA,cACAC,cACAlN,2BACA,kBAEA,qFACA,qCAEA,gCACArc,8BACA,EAEA,EAMAwpB,iCACA,aACA,cACAD,uBACAlN,2BACArc,uCACA,CACA,EAOAypB,sCACA,cAGAtB,iCAGA,kBAEA9tB,QACA5C,sCACAiyB,SACAC,SACAx4B,YACAka,gBACAtB,MACA2Z,UACAiD,iBACA,EACApZ,uBAEAvN,kBACAA,2CACAA,oEACAA,2CACAA,mCAEAA,oCAIAA,iBAGAmoB,kCACA,CACA,EACA,EAKAyB,uBAIA,yBACA5pB,mDACAA,qCAEAA,+BAEA,CATA,cACAupB,qBAUA,kBAEA,0GACA,oCACA,0CACA,kBAEAM,gBAIA,gCACA7pB,8BACA,EAEA,EAKA8pB,uCACA,iFACA,EAKAC,2CACA,aACAC,sBACAC,yBACAC,gBACA,EAEA,wBACAjnB,2IACAknB,WACAh0B,QAEA,EAKAi0B,wBACA,cACAb,sBAEA,kBAEA,2FACA,sCAEA,gCACAvpB,+BACA,EAEA,EAOAqqB,0CACA,wCACAC,wBACAC,2EAEA,oBACA,mBACA,4BAGAliB,iCACAiiB,wBACA,KACA,CACA,KAEAA,yBAGA,2BACA,EAQAE,6CACA,cAEAzD,gHACA/mB,gCACAA,8BACA,EACA,EAOAyqB,yCACA,cACAC,8BACAC,8BACAD,WACAA,0BAEA1qB,+BACA,EAEA,4DACA,0DACA,sCAEA2qB,qBAEA,wBACA1nB,iJACA0nB,mBACA,sBACA,CAGA,gBACA,EAKAC,0BACA5qB,WACA0qB,8BACAG,yBACA7qB,wEACA,EAEA0qB,WACAA,0BAGA,kDACA,wBACAznB,iJACA4nB,cACA,sBACA,CAEAA,gBAGA,gBACA,EAOAjF,kCACAkF,iBACA5wB,QACAf,gBACA6mB,qBACA,EACA+K,6CAEA,OACA5V,qDACAwK,4DACAC,0DACAF,wDACA,CACA,EAaAsL,uDACAhrB,WACA7J,oBACA4pB,oBACAiK,6CACAC,0CACAC,+EAEAe,sCACAjG,uCACAkG,uDACAC,iCAuCA,MArCAjB,iBACAgB,+CACAA,kBAGAC,8BACA5oB,uFACA2oB,2BACAC,wBACA,cAEAA,eACA5oB,0FAGA2oB,kBACA3oB,4FAKA0oB,2CACAA,wDACAA,sDACAA,+BAEAA,qFACAA,0BACA,GAEAA,wEACAA,2BACAn7B,sBACAy5B,iBAEA,GAEA0B,WACA,EAOAG,mCACArE,sBACA,MACA,oCACA,kEACA,CACA,GACA,8BACAtkB,iBAEA,kCACA,EAMA4oB,+BACA,2CACA,EAQAC,iCACA,gBAOA,MANAx7B,mDACA,iEAEA,MADA6vB,eAGA,GACAA,QACA,EAOA4L,2BACA,uDACA,EAMAC,2BACAC,+CACAC,8CACAC,oFACAC,yEACA,yDACA,EAMAC,0BACA,qDACAA,kBACA,iCACAC,uCACA,4DACAD,mBAIA,qBACA,EAMAE,kCACA,uCACAC,qCACAC,aAEA,4BACA,qCAGAD,8BAIA,mDACA,4DAGA,sDACAC,4DACA,KACA,CAIA,mBACA,EAKAC,gBACA,cACAC,kBACAnsB,kBACAA,uBACA,EAEA,mCACA,mCAEAmsB,QAEA,EAKAC,mBACA,cACA,kBACA,8BACA,mBAEA,gCACA,4CACA9G,6BACA+G,gEAEA/G,oEACAyB,oGAEAA,2FAGA/mB,iBACAssB,8BACAD,8CACAC,0BACAtsB,0BACA,EACA,EACA,EAMAusB,qCACA,6BACA,yDACA,+DACA,EAMAC,wBACAzF,kCACA0F,mDACA,EACA,EAMAC,kCAIA,MAHAC,uBACAA,0EAEAA,mBACA,EAMA7G,6BACA,cACAsD,8BAEA,yBACAwD,6BAEA,wCAEA,iCACA,qBACA,sBACA,wBACA,wBACA,yBACA,qBACA,8BACA,mBACA,wBACA,6BACA,oBAEAxD,wFACAA,qDAEA,uGACA9K,iEACAte,uBACAA,mBACAopB,gDACA,GAGA,gEAEA,IAKAppB,sBAEA,EAKA6sB,6BAEA,gCACA90B,4FAEA,sEACAA,iGAEA,EAMA+0B,iDACAC,aACAC,eACAC,2IAEA,sCACA,6BACAF,YAEAC,gBAIA5D,iCACAA,uCACAA,+CAEA5K,+DACA,EAOA0O,0BACA,4EACAC,oEACAC,4EAIA,MAFAC,uBACAA,qBACAA,MACA,EAKAC,0BACAttB,WACAutB,UACAnE,gCAEA,kCACAoE,0BAQA,GALAhF,YACAC,gBACAC,aACAC,aAEA,iCACA3oB,uEAEAutB,oDACAnE,4DAEAmE,YAIA,uDAEA,4CAEA,iCAWA,GARAnE,sDAGAX,4BACAC,sBACAC,eACA8E,aAEAztB,gFACA0tB,gBACA59B,+EACAA,gDACA49B,mBACAv8B,WACAw8B,wBACAC,sBACA,EAEA,GACA,4DACA,mEACAF,qDAEA,CAEA,0BACAlF,2DAWAtV,WAPA,GAFAyV,4EAEA,4BACA,sCACAF,kCACAE,uBACA,CAEAkF,uBACA7kB,4DAEA,6BAEAyf,2BACAqF,oCAGA1E,4BACAppB,gBAEA0oB,+BAGA,iDAEA,yFACA1oB,6BAGA,MACA,kBACA,CACA,CAEA6tB,mEACAzE,8BACA,CAEA,EAgBA2E,kCACA,eAEAV,0EACAD,qCAFAY,UAQA,OAJAZ,mFAEAY,8DAEAN,kBACA,wBACAnrB,oFACA,MACA,wBACAA,uFACA,MACA,eACAA,yFACA,MACA,qBACAA,mFACA,CAGAxK,kCAEA21B,qCACA,EAKAO,yBAGA,GAFA7E,iCAEA,mCAEAA,uDAEA,kDACAA,4DAKAA,mEAEA,MADAlO,0BAEA,GAEAkO,yDAEAhB,SACAA,qEAGA,CACA,EAKA8F,4BACA9E,uBAEA,8BACAA,8BACAA,4BAGAA,8BACAA,2BAEA,8DAEAA,gCAIAA,2BACA,sCACAA,+BAGAA,+BAGA,EAKA+E,4BACA,4DAEA,kBACA,sFACA,yCAEA,EAKAC,0BACA,4DAEAhF,6EAEAiF,kBACA,6CAEA,EAKAC,yBAEAlF,kDAGA,qGAEAA,sCACAA,4BACA,EAKAmF,4BAEAnF,uEACAA,kEACA,EAKAoF,0BACAC,yDAEAC,mHACAjD,sCAFAkD,mBAGAvF,0CACAA,4BACAA,oCACA,qBACAwF,qEACA,GAGAnD,qCACAkD,mEAEAA,iCACAF,aACAE,0EAEAA,gCAGAlD,2BACAA,4EAEArC,gDAEA,EAKAyF,sBACAzF,4CACA,iBACA,yBAEA,EAKA0F,qBACA1F,+CACA,iBACA,wBAEA,EAKA2F,mBACA3F,0CACA,iBACA,sBAEA,EAKA4F,mBACA5F,6CACA,iBACA,sBAEA,EAOA6F,qCACAC,sBACAC,gCACAC,6CACAC,mDAEA,WACAF,iBAEA,aACAC,qBAEA,aACAC,qBAGA,uCAEA,WACA,EAMAC,0CACA,cAGA,eACA,wBAGAnH,sCACA5lB,sBACAgnB,kBACAxC,cACAA,yBAEArK,qCAEA1c,yBAGAA,sBAEA,CACA,EACA,EASAuvB,wCACA,cACAlT,yDAEAmT,cACAnT,qCAEA,iCACAhiB,QACA5C,mCACAiyB,SACA3f,YACA4f,SACAte,gBACAkC,8BACAwZ,yBAEAzB,oBAGAA,2CACAtlB,yBAGAA,yBANAA,8BAQA,CACA,EACA,EACA,EAOAgZ,gBACA,cACA+N,sBACA,6DACA,+DACA,GACA,wBACA9jB,6CACA,WACAjD,gCACAA,kCACA+mB,wBACA,EACA,EACA,0DAEA,EAQA0I,uCACAnyB,KACA,gCAEA,EAKAoyB,iCACA,wBACA,EAMAC,+BACA,4BACA,CACA,EAEA74B,uBA/nCA+2B,QACAzE,UACAqE,WACA1G,aACA4F,oBAgoCA,MAJA71B,SACA0nB,yCAGA,CACAzE,4BAEAqP,WAEAwG,kDACAC,0CACAC,oDACAC,kDACAC,mCACAC,0CACAC,gEACAC,oEACAC,6CACAC,kCACAC,wCAGAC,kDACAC,sDACAC,gCAGArkB,8CACAskB,oDAGAC,iDAGAC,kCACAxH,6BACAyH,0BACAC,gCACAC,8BACAC,6BACAC,yDACAC,8DACA,EAGA9H,gEAEA9K,qCACA,oBACA6J,yCAEA,GAEAjN,gEACAgO,iCAEA5D,oBAMAA,kDACA6L,oBACApK,0BAIAmC,mDACAA,uCACAA,0BAEAA,4EACAA,+CAEAA,+BAlBAA,oCAqBA,EAEAA,8BACAA,mCAEAE,wCACA/uB,8BACA6uB,iBAEA,GAEAE,2CACA/uB,8BACA6uB,wBAEA,GAEAE,yCACA/uB,8BACA6uB,yBAEA,GAEAE,wCACA/uB,8BACA6uB,wBAEA,GAEAE,kCACA7tB,mBACA2tB,iBACA,GAEA7uB,4CACA6uB,yBACAE,4BACA,QAEA9K,6BACA6J,gCACA,GAGA7J,+BACA6J,kCACA,GAEAe,4FAEA5D,2BACA4D,0DACAjH,wCACAzC,0DACAwE,oCACAoN,kDACAC,2CACA,sCACAnI,yBACA,2CACAA,uCACA,GACAE,wCAGAF,4BACAA,8BAEAf,sCAGAe,yBAEAE,iFAEAf,4BACAE,iBAGAluB,gEACAi3B,sBACApI,8DAEA,wCACA5rB,WACA4rB,wCAEA,wCACAE,mCACA,EAEA,CACA,CACA,GC52CA,gOACA,wh5DCDAv5B,2DACAA,MCFA","names":["define","_","module","baseTypeEnum","IDENTIFIER","BOOLEAN","INTEGER","FLOAT","STRING","POINT","PAIR","DIRECTED_PAIR","DURATION","FILE","URI","INT_OR_IDENTIFIER","COORDS","ANY","SAME","baseTypeHelper","asArray","getValid","type","getValue","parseInt","getConstantByName","getNameByConstant","constant","operator","cardinalityEnum","SINGLE","MULTIPLE","ORDERED","RECORD","cardinalityHelper","cardinality","validOutcome","validIdentifier","allowedTypes","valid","validateIdentifier","validateOutcomes","validateOutcome","value","create","properties","identifier","element","find","found","types","collection","checkType","lookupElement","steps","nodeNames","len","i","key","tree","lookupProperty","result","propertyName","getProcessingRuleExpression","getProcessingRuleProperty","getOutcomeIdentifier","getOutcomeDeclarations","getOutcomeProcessingRules","eachOutcomeDeclarations","eachOutcomeProcessingRules","eachOutcomeProcessingRuleExpressions","processingRule","cb","browseExpressions","listOutcomes","outcomeHelper","outcomes","removeOutcomes","declarations","rules","check","createOutcome","views","interpretation","longInterpretation","normalMaximum","normalMinimum","masteryValue","baseType","addOutcomeProcessing","outcomeProcessing","outcomeRules","testModel","addOutcome","outcome","replaceOutcomes","getCategoriesRecursively","eachCategories","listCategories","isCategoryOption","categories","listOptions","options","getModel","setModel","model","modelOverseer","getConfig","getOutcomesList","name","getOutcomesNames","getCategories","getOptions","config","statifier","areaBroker","testCreator","$container","setTestModel","getAreaBroker","getModelOverseer","isTestHasErrors","eventifier","getItemClasses","url","getItems","getItemClassProperties","logger","testItemProvider","onError","feedback","ITEM_URI","filters","selectorConfig","selectionMode","selectAllPolicy","classUri","classes","label","uri","resourceSelector","on","then","catch","helpers","buffer","helper","escapeExpression","stack1","defaults","testPart","section","rubricBlock","itemRef","testProps","testPartProps","sectionProps","itemRefProps","itemRefPropsWeight","rubricBlockProps","categoryPresets","subsection","testpart","itemref","rubricblock","test","itemrefweight","categorypresets","$","ui","DataBinder","templates","$view","$warningIconSelector","$test","errors","currentTargetId","isValid","template","open","databinder","propValidation","$identifier","getView","isOpen","onOpen","e","onClose","destroy","toggle","isFistLevelSubsection","getParentSection","getParentSubsection","$parentSection","index","sectionIndex","$parentSubsection","subsectionIndex","isNestedSubsection","getSubsections","getSubsectionContainer","getSiblingSubsections","getParent","getSubsectionTitleIndex","$elt","propView","$element","$elements","insertBefore","fadeIn","insertAfter","$actionContainer","$moveUp","$moveDown","$delete","subsectionsCount","$deleteButton","id","$propertiesView","$list","$indexSpan","$parent","disabledClass","activeClass","btnOnClass","move","removable","movable","disable","enable","displayItemWrapper","updateDeleteSelector","displayCategoryPresets","updateTitleIndex","features","categoryGroupNamespace","categoryPresetNamespace","presetGroups","filteredGroups","filteredGroup","addTestVisibilityProps","addTestPartVisibilityProps","addSectionVisibilityProps","addItemRefVisibilityProps","filterVisiblePresets","presetSelected","toArray","map","presetIndeterminate","customSelected","siblings","not","customIndeterminate","selectedCategories","indeterminatedCategories","$presetsContainer","$customCategoriesSelect","categorySelector","createForm","self","presetsTpl","customCategories","filteredPresets","$checkbox","updateCategories","select2","width","tags","multiple","tokenSeparators","formatNoMatches","maximumInputLength","tooltip","updateFormState","indeterminate","$presetsCheckboxes","each","$li","content","allQtiCategoriesPresets","categorySelectorFactory","allPresets","partial","toRemove","toAdd","addCategories","removeCategories","itemCount","arrays","union","propagated","getCategoriesRecursive","createCategories","isValidSectionModel","sectionPart","errorHandler","all","setCategories","message","validate","callback","identifiers","$idInUI","validators","countBy","forEach","nonUniqueIdentifiers","messageDetails","__","feedbackBlock","extract","originalIdentifier","excludeTypes","includesOnlyTypes","counts","qtiIdPattern","qtiTypesForUniqueIds","extractIdentifiers","registerValidators","validateModel","checkIfItemIdValid","getIdentifiers","getIdentifiersOf","filterQtiType","addMissingQtiType","parentType","consolidateModel","assessmentSection","part","getAvailableIdentifier","current","suggestion","actions","sectionCategory","qtiTestHelper","featureVisibility","$minTimeContainer","$maxTimeContainer","$lockedTimeContainer","$locker","$durationFields","$minTimeField","$maxTimeField","minToMaxHandler","maxToMinHandler","lockTimers","removeClass","addClass","attr","unlockTimers","toggleTimeContainers","refModel","$categoryField","$weightList","weightTpl","validIds","$itemRef","deletedNodeId","categoriesProperty","weightsProperty","timeLimitsProperty","$actions","$target","$refs","setUp","listenActionState","acc","normalizedNodes","feedbackblock","outcomeidentifier","showhide","printedvariable","powerform","mappingindicator","typedAttributes","printedVariable","powerForm","base","delimiter","field","mappingIndicator","encode","startTag","modelValue","decode","$nodeValue","nodeName","object","attrName","editorId","removePlugins","toolbar","items","qtiCommonRenderer","containerEditor","metadata","getOutcomes","change","resetRenderer","autofocus","creatorContext","eventName","$el","html","rubric","wrapper","qtiContentCreator","$rubricBlockContent","editorToModel","activated","rubricModel","outcomeIdentifier","modelToEditor","hider","changeFeedback","text","$feedbackOutcome","minimumResultsForSearch","data","$feedbackActivated","$feedbackOutcomeLine","$feedbackMatchLine","bindEvent","updateOutcomes","updateFeedback","rbViews","$select","matchValue","before","feedbackOutcome","$rubricBlock","dialogAlert","CKEDITOR","eachItemInTestPart","eachItemInSection","eachItemInTest","currentCategories","testModelHelper","itemRefCategories","isValidTestPartModel","dataType","fail","setBlueprint","getBlueprint","itemRefView","rubricBlockView","sectionBlueprint","confirmDialog","subsectionsHelper","servicesFeatures","$selectionSwitcher","$selectionSelect","$selectionWithRep","isSelectionFromServer","switchSelection","$title","$subsection","blueprintProperty","subsectionModel","$sub2section","sectionModel","$itemsPanel","$placeholder","$placeholders","show","off","defaultItemData","href","itemData","addItemRef","itemRefModel","$refList","target","templateData","success","ajax","delay","method","results","minimumInputLength","allowClear","placeholder","initBlueprint","buttons","labels","ok","cancel","sectionParts","title","visible","itemSessionControl","maxAttempts","checkAndCallAdd","confirmMessage","defaultsConfigs","acceptFunction","setTimeout","executeAdd","getDom","css","sub2sectionIndex","sub2sectionModel","$itemRefsWrapper","$rubBlocks","$titleWithActions","subsections","itemRefs","acceptItemRefs","rubricBlocks","addRubricBlock","addSubsection","$AllNestedSubsections","testPartCategory","subsectionView","$section","$sectionsContainer","$sections","sectionView","$testPart","partModel","categoriesSummary","sections","addSection","$testParts","testPartView","noOptions","newScoringState","scoringState","$panel","$categoryScoreLine","$cutScoreLine","$weightIdentifierLine","$descriptions","$generate","trigger","changeScoring","navigationMode","submissionMode","assessmentSections","testParts","addTestPart","testView","addTypeAndCardinality","expression","processingRuleHelper","setExpression","addExpression","setOutcomeValue","gte","lte","divide","sum","testVariables","addWeightIdentifier","addSectionIdentifier","outcomeMaximum","numberPresented","baseValue","variable","match","isNull","outcomeCondition","outcomeElse","outcomeIf","instruction","Math","scoring","weight","category","recipe","onlyCategories","outcomeMatch","signatures","signatureMatch","descriptors","mode","modes","categoryOutcomes","compact","uniq","values","outcomeDeclarations","processing","diff","count","included","outcomesRecipes","scoreIdentifier","weightIdentifier","cutScore","categoryScore","scoringOutcomes","defaultCutScore","defaultScoreIdentifier","processingModes","none","description","custom","total","cut","clean","signature","writer","weighted","categoryIdentifier","categoryWeighted","max","scoreWeighted","include","feedbackOk","feedbackFailed","categoryFeedback","outcomesWriters","ratio","addRatioOutcomes","descriptor","addTotalScoreOutcomes","formatCategoryOutcome","addMaxScoreOutcomes","totalModeOutcomes","whichOutcome","ratioIdentifier","addGlobalCutScoreOutcomes","addFeedbackScoreOutcomes","categoryOutcome","categoryOutcomeIdentifier","categoryScoreIdentifier","categoryCountIdentifier","addCutScoreOutcomes","scoringHelper","init","generate","outcomeRecipe","removeScoring","recipes","handleCategories","uuid","dialog","asking","originalTest","install","leavingStatus","ifWantSave","whatToDo","uninstall","confirmBefore","confirmDlg","autoRender","autoDestroy","onDontsaveBtn","onCancelBtn","reject","onSaveBtn","hasChanged","getSerializedTest","serialized","messages","preview","leave","exit","leaveWhenInvalid","buttonsYesNo","close","buttonsCancelSave","DataBindController","qtiTestCreatorFactory","itemView","itemrefView","Dom2QtiEncoder","changeTracker","previewerFactory","loggerFactory","Controller","routes","start","$saver","$previewer","$back","binder","binderOptions","Object","isItemRef","isSection","encoders","dom2qti","beforeSave","guidedNavigation","isTestContainsItems","saveUrl","testUri","readOnly","fullPage","pluginsOptions","window","event","ckConfigurator","Creator","XmlEditor","edit","context","router","loadingBar","urlUtil","locale","providerLoader","runner","serviceCallId","plugins","preventFeedback","errorFeedback","reason","level","params","onFeedback","onWarning","typeMap","warning","error","loggerByType","feedbackByType","displayMessage","moduleConfig","requiredOptions","testRunnerConfig","providers","exitReason","after","tool","itemIsLoaded","tools","toolconfig","hook","isValidConfig","require","$button","$existingBtn","isValidHook","_appendInOrder","triggerItemLoaded","resolve","order","$after","_order","$before","_ns","$doc","initQtiTool","actionBarTools","register","registeredQtiTools","getRegisteredTools","getRegistered","isRegistered","get","list","render","promises","Promise","qtiTools","_cssCls","active","collapsed","collapsible","hidden","disabled","flagged","answered","viewed","unseen","icon","scope","testSection","_selectors","component","filterBar","collapseHandle","linearState","infoAnswered","infoViewed","infoUnanswered","infoFlagged","infoPanel","infoPanelLabels","parts","partLabels","sectionLabels","itemLabels","itemIcons","icons","linearStart","counters","actives","collapsiblePanels","notFlagged","notAnswered","_filterMap","unanswered","filtered","_optionsMap","_reviewScopes","testReview","initOptions","putOnRight","insertMethod","region","_loadDOM","_initEvents","$item","$btn","_filter","$items","filter","_select","selected","hierarchy","_openSelected","_openOnly","opened","_togglePanel","collapseSelector","panel","_setItemIcon","_adjustItemIcon","defaultIcon","iconCls","_toggleFlag","_mark","itemId","itemPosition","flag","_jump","_updateSectionCounters","$filtered","nb","_updateDisplayOptions","reviewScope","scopeClass","$root","_updateOptions","_updateInfos","_writeCount","$place","_getProgressionOfTest","_getProgressionOfTestPart","_getProgressionOfTestSection","_updateTree","navigatorMap","reviewScopePart","reviewScopeSection","_partsFilter","setItemFlag","progression","updateNumberFlagged","fields","currentPosition","currentFound","itemFound","testContext","item","itemPart","itemSection","currentPart","currentSection","getProgression","progressInfoMethod","update","hide","dom","extraParameters","testReviewFactory","progressUpdaters","write","progressIndicator","progressIndicatorMethod","percentageProgression","positionProgression","progressScope","progressScopeCounter","position","counter","progressUpdaterFactory","_testServiceCallId","testMetaData","removed","setLocalStorageData","_storageKeyPrefix","_data","SECTION_EXIT_CODE","TEST_EXIT_CODE","getTestServiceCallId","setTestServiceCallId","setData","addData","overwrite","getData","clearData","getLocalStorageKey","progressUpdater","testMetaDataFactory","ServiceApi","UserInfoService","StateStorage","iframeNotifier","MathJax","deleter","moment","modal","timerIds","currentTimes","lastDates","timeDiffs","waitingTime","optionNextSection","optionNextSectionWarning","optionReviewScreen","optionEndTestWarning","optionNoExitTimedSectionWarning","TestRunner","beforeTransition","$controls","afterTransition","jump","action","keepItemTimed","markForReview","cache","async","moveForward","doExitSection","shouldDisplayEndTestWarning","displayEndTestWarning","confirmLabel","cancelLabel","showItemCount","nextAction","moveBackward","isJumpOutOfSection","isJumpToOtherSection","isValidPosition","exitSection","exitTimedSection","qtiRunner","doExitTimedSection","nextSection","doNextSection","scopeSuffixMap","scopeSuffix","displayExitMessage","$confirmBox","unansweredCount","flaggedCount","killItemSession","isCurrentItemActive","isCurrentItemAnswered","hasOption","getQtiRunner","itemFrame","itemWindow","itemContainerFrame","itemContainerWindow","isTimedSection","timeConstraints","getCurrentSectionItems","navMap","sectionItems","skip","doSkip","timeout","confirmBtn","confirmBox","setTestContext","initMetadata","testServiceCallId","getSessionStateService","sessionStateService","$runner","updateInformation","updateTools","showSkip","showSkipEnd","showNextSection","createTimer","$label","$time","$timer","updateTimer","hasTimers","clearTimeout","timerIndex","cst","showed","point","$timers","clearInterval","timeWarning","remaining","updateRubrics","updateNavigation","updateTestReview","updateProgress","considerProgress","updateContext","updateExitButton","adjustFrame","rubricHeight","finalHeight","frameContentHeight","$sideBar","disableGui","enableGui","hideGui","showGui","formatTime","sec_num","hours","minutes","seconds","processError","actionCall","extraParams","setCurrentItemState","resetCurrentItemState","getCurrentItemState","$moveForward","$moveEnd","$moveBackward","$nextSection","$skip","$skipEnd","$exit","$logout","$naviButtons","$skipButtons","$forwardButtons","$progressBar","$progressLabel","$progressBox","$position","$timerWrapper","$contentPanel","$itemFrame","$rubricBlocks","$contentBox","$sideBars","$topActionBar","$bottomActionBar","serviceApi","preventsUnseen","canCollapse","responseId"],"sources":["../controller/creator/areaBroker.js","../controller/creator/config/defaults.js","../controller/creator/helpers/baseType.js","../controller/creator/helpers/cardinality.js","../controller/creator/helpers/outcomeValidator.js","../controller/creator/helpers/qtiElement.js","../controller/creator/helpers/outcome.js","../controller/creator/helpers/category.js","../controller/creator/modelOverseer.js","../controller/creator/qtiTestCreator.js","../provider/testItems.js","../controller/creator/views/item.js","../controller/creator/templates/testpart!tpl","../controller/creator/templates/section!tpl","../controller/creator/templates/rubricblock!tpl","../controller/creator/templates/itemref!tpl","../controller/creator/templates/outcomes!tpl","tpl!taoQtiTest/controller/creator/templates/test-props","tpl!taoQtiTest/controller/creator/templates/testpart-props","tpl!taoQtiTest/controller/creator/templates/section-props","tpl!taoQtiTest/controller/creator/templates/itemref-props","tpl!taoQtiTest/controller/creator/templates/itemref-props-weight","tpl!taoQtiTest/controller/creator/templates/rubricblock-props","tpl!taoQtiTest/controller/creator/templates/category-presets","../controller/creator/templates/subsection!tpl","../controller/creator/templates/index.js","../controller/creator/views/property.js","../controller/creator/helpers/subsection.js","../controller/creator/views/actions.js","../controller/creator/helpers/featureVisibility.js","../controller/creator/helpers/categorySelector.js","../controller/creator/helpers/sectionCategory.js","../controller/creator/helpers/validators.js","../controller/creator/helpers/qtiTest.js","../controller/creator/views/itemref.js","../controller/creator/encoders/dom2qti.js","../controller/creator/qtiContentCreator.js","../controller/creator/views/rubricblock.js","../controller/creator/helpers/testModel.js","../controller/creator/helpers/testPartCategory.js","../controller/creator/helpers/sectionBlueprints.js","../controller/creator/views/subsection.js","../controller/creator/views/section.js","../controller/creator/views/testpart.js","../controller/creator/views/test.js","../controller/creator/helpers/processingRule.js","../controller/creator/helpers/scoring.js","../controller/creator/helpers/changeTracker.js","../controller/creator/creator.js","../controller/creator/helpers/ckConfigurator.js","../controller/routes.js","css!taoQtiTestCss/new-test-runner","../controller/runner/runner.js","../testRunner/actionBarHook.js","../testRunner/actionBarTools.js","../testRunner/tpl/navigator!tpl","../testRunner/tpl/navigatorTree!tpl","../testRunner/testReview.js","../testRunner/progressUpdater.js","../testRunner/testMetaData.js","../controller/runtime/testRunner.js","onLayerEnd0.js","module-create.js","/github/workspace/tao/views/build/config-wrap-end-default.js"],"sourcesContent":["/*\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2017 (original work) Open Assessment Technlogies SA\n *\n */\n\n/**\n *\n * Defines the test creator areas\n *\n * @author Christophe Noël \n */\ndefine('taoQtiTest/controller/creator/areaBroker',[\n 'lodash',\n 'ui/areaBroker'\n], function (_, areaBroker) {\n 'use strict';\n\n var requireAreas = [\n 'creator',\n 'itemSelectorPanel',\n 'contentCreatorPanel',\n 'propertyPanel',\n 'elementPropertyPanel'\n ];\n\n /**\n * Creates an area broker with the required areas for the item creator\n *\n * @see ui/areaBroker\n *\n * @param {jQueryElement|HTMLElement|String} $container - the main container\n * @param {Object} mapping - keys are the area names, values are jQueryElement\n * @returns {broker} the broker\n * @throws {TypeError} without a valid container\n */\n return _.partial(areaBroker, requireAreas);\n\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2020 (original work) Open Assessment Technologies SA;\n *\n * @author Sergei Mikhailov \n */\n\ndefine('taoQtiTest/controller/creator/config/defaults',[\n 'lodash',\n 'module',\n], function(\n _,\n module\n) {\n 'use strict';\n\n return (config = {}) => _.defaults({}, module.config(), config);\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2017 (original work) Open Assessment Technologies SA ;\n */\n/**\n * The BaseType enumeration (port of \\qtism\\common\\enums\\BaseType).\n *\n * From IMS QTI:\n *\n * A base-type is simply a description of a set of atomic values (atomic to this specification).\n * Note that several of the baseTypes used to define the runtime data model have identical\n * definitions to those of the basic data types used to define the values for attributes\n * in the specification itself. The use of an enumeration to define the set of baseTypes\n * used in the runtime model, as opposed to the use of classes with similar names, is\n * designed to help distinguish between these two distinct levels of modelling.\n *\n * @author Jérôme Bogaerts \n * @author Jean-Sébastien Conan \n */\ndefine('taoQtiTest/controller/creator/helpers/baseType',[\n 'lodash'\n], function (_) {\n 'use strict';\n\n /**\n * The list of QTI base types\n * @type {Object}\n */\n var baseTypeEnum = {\n /**\n * From IMS QTI:\n *\n * The set of identifier values is the same as the set of values\n * defined by the identifier class.\n *\n * @type {Number}\n */\n IDENTIFIER: 0,\n\n /**\n * From IMS QTI:\n *\n * The set of boolean values is the same as the set of values defined\n * by the boolean class.\n *\n * @type {Number}\n */\n BOOLEAN: 1,\n\n /**\n * From IMS QTI:\n *\n * The set of integer values is the same as the set of values defined\n * by the integer class.\n *\n * @type {Number}\n */\n INTEGER: 2,\n\n /**\n * From IMS QTI:\n *\n * The set of float values is the same as the set of values defined by the\n * float class.\n *\n * @type {Number}\n */\n FLOAT: 3,\n\n /**\n * From IMS QTI:\n *\n * The set of string values is the same as the set of values defined by the\n * string class.\n *\n * @type {Number}\n */\n STRING: 4,\n\n /**\n * From IMS QTI:\n *\n * A point value represents an integer tuple corresponding to a graphic point.\n * The two integers correspond to the horizontal (x-axis) and vertical (y-axis)\n * positions respectively. The up/down and left/right senses of the axes are\n * context dependent.\n *\n * @type {Number}\n */\n POINT: 5,\n\n /**\n * From IMS QTI:\n *\n * A pair value represents a pair of identifiers corresponding to an association\n * between two objects. The association is undirected so (A,B) and (B,A) are equivalent.\n *\n * @type {Number}\n */\n PAIR: 6,\n\n /**\n * From IMS QTI:\n *\n * A directedPair value represents a pair of identifiers corresponding to a directed\n * association between two objects. The two identifiers correspond to the source\n * and destination objects.\n *\n * @type {Number}\n */\n DIRECTED_PAIR: 7,\n\n /**\n * From IMS QTI:\n *\n * A duration value specifies a distance (in time) between two time points.\n * In other words, a time period as defined by [ISO8601], but represented as\n * a single float that records time in seconds. Durations may have a fractional\n * part. Durations are represented using the xsd:double datatype rather than\n * xsd:dateTime for convenience and backward compatibility.\n *\n * @type {Number}\n */\n DURATION: 8,\n\n /**\n * From IMS QTI:\n *\n * A file value is any sequence of octets (bytes) qualified by a content-type and an\n * optional filename given to the file (for example, by the candidate when uploading\n * it as part of an interaction). The content type of the file is one of the MIME\n * types defined by [RFC2045].\n *\n * @type {Number}\n */\n FILE: 9,\n\n /**\n * From IMS QTI:\n *\n * A URI value is a Uniform Resource Identifier as defined by [URI].\n *\n * @type {Number}\n */\n URI: 10,\n\n /**\n * From IMS QTI:\n *\n * An intOrIdentifier value is the union of the integer baseType and\n * the identifier baseType.\n *\n * @type {Number}\n */\n INT_OR_IDENTIFIER: 11,\n\n /**\n * In qtism, we consider an extra 'coords' baseType.\n *\n * @type {Number}\n */\n COORDS: 12,\n\n /**\n * Express that the operands can have any BaseType from the BaseType enumeration and\n * can be different.\n *\n * @type {Number}\n */\n ANY: 12,\n\n /**\n * Express that all the operands must have the same\n * baseType.\n *\n * @type {Number}\n */\n SAME: 13\n };\n\n var baseTypeHelper = _({\n /**\n * Gets the the list of QTI base types\n * @returns {Object}\n */\n asArray: function asArray() {\n return baseTypeEnum;\n },\n\n /**\n * Gets a valid type or the default\n * @param {String|Number} type\n * @param {String|Number} [defaultType]\n * @returns {*}\n */\n getValid: function getValid(type, defaultType) {\n if (_.isFinite(type)) {\n if (!baseTypeHelper.getNameByConstant(type)) {\n type = false;\n }\n } else {\n type = baseTypeHelper.getConstantByName(type);\n }\n\n if (false === type) {\n if (!_.isUndefined(defaultType) && defaultType !== -1) {\n type = baseTypeHelper.getValid(defaultType, -1);\n } else {\n type = -1;\n }\n }\n\n return type;\n },\n\n /**\n * Adjusts a value with respect to the type\n * @param {String|Number} type\n * @param {*} value\n * @returns {*}\n */\n getValue: function getValue(type, value) {\n if (_.isString(type)) {\n type = baseTypeHelper.getConstantByName(type);\n }\n\n switch (type) {\n case baseTypeEnum.URI:\n case baseTypeEnum.STRING:\n case baseTypeEnum.IDENTIFIER:\n return value + '';\n\n case baseTypeEnum.BOOLEAN:\n if (_.isString(value)) {\n switch (value.toLowerCase()) {\n case 'true':\n return true;\n case 'false':\n return false;\n }\n }\n return !!value;\n\n case baseTypeEnum.INTEGER:\n return parseInt(value, 10) || 0;\n\n case baseTypeEnum.FLOAT:\n return parseFloat(value) || 0;\n\n case baseTypeEnum.INT_OR_IDENTIFIER:\n if (!_.isNaN(parseInt(value, 10))) {\n return parseInt(value, 10) || 0;\n } else {\n return '' + value;\n }\n }\n\n return value;\n },\n\n /**\n * Get a constant value from the BaseType enumeration by baseType name.\n *\n * * 'identifier' -> baseTypes.IDENTIFIER\n * * 'boolean' -> baseTypes.BOOLEAN\n * * 'integer' -> baseTypes.INTEGER\n * * 'float' -> baseTypes.FLOAT\n * * 'string' -> baseTypes.STRING\n * * 'point' -> baseTypes.POINT\n * * 'pair' -> baseTypes.PAIR\n * * 'directedPair' -> baseTypes.DIRECTED_PAIR\n * * 'duration' -> baseTypes.DURATION\n * * 'file' -> baseTypes.FILE\n * * 'uri' -> baseTypes.URI\n * * 'intOrIdentifier' -> baseTypes.INT_OR_IDENTIFIER\n * * extra 'coords' -> baseTypes.COORDS\n *\n * @param {String} name The baseType name.\n * @return {Number|Boolean} The related enumeration value or `false` if the name could not be resolved.\n */\n getConstantByName: function getConstantByName(name) {\n switch (String(name).trim().toLowerCase()) {\n case 'identifier':\n return baseTypeEnum.IDENTIFIER;\n\n case 'boolean':\n return baseTypeEnum.BOOLEAN;\n\n case 'integer':\n return baseTypeEnum.INTEGER;\n\n case 'float':\n return baseTypeEnum.FLOAT;\n\n case 'string':\n return baseTypeEnum.STRING;\n\n case 'point':\n return baseTypeEnum.POINT;\n\n case 'pair':\n return baseTypeEnum.PAIR;\n\n case 'directedpair':\n return baseTypeEnum.DIRECTED_PAIR;\n\n case 'duration':\n return baseTypeEnum.DURATION;\n\n case 'file':\n return baseTypeEnum.FILE;\n\n case 'uri':\n return baseTypeEnum.URI;\n\n case 'intoridentifier':\n return baseTypeEnum.INT_OR_IDENTIFIER;\n\n case 'coords':\n return baseTypeEnum.COORDS;\n\n case 'any':\n return baseTypeEnum.ANY;\n\n case 'same':\n return baseTypeEnum.SAME;\n\n default:\n return false;\n }\n },\n\n /**\n * Get the QTI name of a BaseType.\n *\n * @param {Number} constant A constant value from the BaseType enumeration.\n * @param {Boolean} [operator] A flag that allow to switch between operator an value types to prevent duplicate name issue\n * @return {String|Boolean} The QTI name or false if not match.\n */\n getNameByConstant: function getNameByConstant(constant, operator) {\n switch (constant) {\n case baseTypeEnum.IDENTIFIER:\n return 'identifier';\n\n case baseTypeEnum.BOOLEAN:\n return 'boolean';\n\n case baseTypeEnum.INTEGER:\n return 'integer';\n\n case baseTypeEnum.FLOAT:\n return 'float';\n\n case baseTypeEnum.STRING:\n return 'string';\n\n case baseTypeEnum.POINT:\n return 'point';\n\n case baseTypeEnum.PAIR:\n return 'pair';\n\n case baseTypeEnum.DIRECTED_PAIR:\n return 'directedPair';\n\n case baseTypeEnum.DURATION:\n return 'duration';\n\n case baseTypeEnum.FILE:\n return 'file';\n\n case baseTypeEnum.URI:\n return 'uri';\n\n case baseTypeEnum.INT_OR_IDENTIFIER:\n return 'intOrIdentifier';\n\n case baseTypeEnum.COORDS:\n case baseTypeEnum.ANY:\n if (operator) {\n return 'any';\n } else {\n return 'coords';\n }\n\n case baseTypeEnum.SAME:\n return 'same';\n\n default:\n return false;\n }\n }\n }).assign(baseTypeEnum).value();\n\n return baseTypeHelper;\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2017 (original work) Open Assessment Technologies SA ;\n */\n/**\n * The Cardinality enumeration (port of \\qtism\\common\\enums\\Cardinality).\n *\n * From IMS QTI:\n *\n * An expression or itemVariable can either be single-valued or multi-valued. A multi-valued\n * expression (or variable) is called a container. A container contains a list of values,\n * this list may be empty in which case it is treated as NULL. All the values in a multiple\n * or ordered container are drawn from the same value set, however, containers may contain\n * multiple occurrences of the same value. In other words, [A,B,B,C] is an acceptable value\n * for a container. A container with cardinality multiple and value [A,B,C] is equivalent\n * to a similar one with value [C,B,A] whereas these two values would be considered distinct\n * for containers with cardinality ordered. When used as the value of a response variable\n * this distinction is typified by the difference between selecting choices in a multi-response\n * multi-choice task and ranking choices in an order objects task. In the language of [ISO11404]\n * a container with multiple cardinality is a \"bag-type\", a container with ordered cardinality\n * is a \"sequence-type\" and a container with record cardinality is a \"record-type\".\n *\n * The record container type is a special container that contains a set of independent values\n * each identified by its own identifier and having its own base-type. This specification\n * does not make use of the record type directly however it is provided to enable\n * customInteractions to manipulate more complex responses and customOperators to\n * return more complex values, in addition to the use for detailed information about\n * numeric responses described in the stringInteraction abstract class.\n *\n * @author Jérôme Bogaerts \n * @author Jean-Sébastien Conan \n */\ndefine('taoQtiTest/controller/creator/helpers/cardinality',[\n 'lodash'\n], function (_) {\n 'use strict';\n\n /**\n * The list of QTI cardinalities\n * @type {Object}\n */\n var cardinalityEnum = {\n /**\n * Single term cardinality\n *\n * @type {Number}\n */\n SINGLE: 0,\n\n /**\n * Multiple terms cardinality\n *\n * @type {Number}\n */\n MULTIPLE: 1,\n\n /**\n * Ordered terms cardinality\n *\n * @type {Number}\n */\n ORDERED: 2,\n\n /**\n * Record term cardinality\n *\n * @type {Number}\n */\n RECORD: 3,\n\n /**\n * Express that all the expressions involved in an operator have\n * the same cardinality.\n *\n * @type {Number}\n */\n SAME: 4,\n\n /**\n * Express that all the expressions involved in an operator may\n * have any cardinality.\n *\n * @type {Number}\n */\n ANY: 5\n };\n\n var cardinalityHelper = _({\n /**\n * Gets the the list of QTI cardinalities\n * @returns {Object}\n */\n asArray: function asArray() {\n return cardinalityEnum;\n },\n\n /**\n * Gets a valid cardinality or the default\n * @param {String|Number} cardinality\n * @param {String|Number} [defaultCardinality]\n * @returns {*}\n */\n getValid: function getValid(cardinality, defaultCardinality) {\n if (_.isFinite(cardinality)) {\n if (!cardinalityHelper.getNameByConstant(cardinality)) {\n cardinality = false;\n }\n } else {\n cardinality = cardinalityHelper.getConstantByName(cardinality);\n }\n\n if (false === cardinality) {\n if (!_.isUndefined(defaultCardinality) && defaultCardinality !== cardinalityEnum.SINGLE) {\n cardinality = cardinalityHelper.getValid(defaultCardinality, cardinalityEnum.SINGLE);\n } else {\n cardinality = cardinalityEnum.SINGLE;\n }\n }\n\n return cardinality;\n },\n\n /**\n * Get a constant value from its name.\n *\n * @param {String} name The name of the constant, as per QTI spec.\n * @return {Number|Boolean} The constant value or `false` if not found.\n */\n getConstantByName: function getConstantByName(name) {\n switch (String(name).trim().toLowerCase()) {\n case 'single':\n return cardinalityEnum.SINGLE;\n\n case 'multiple':\n return cardinalityEnum.MULTIPLE;\n\n case 'ordered':\n return cardinalityEnum.ORDERED;\n\n case 'record':\n return cardinalityEnum.RECORD;\n\n case 'same':\n return cardinalityEnum.SAME;\n\n case 'any':\n return cardinalityEnum.ANY;\n\n default:\n return false;\n }\n },\n\n /**\n * Get the name of a constant from its value.\n *\n * @param {Number} constant The constant value to search the name for.\n * @return {String|Boolean} The name of the constant or false if not found.\n */\n getNameByConstant: function getNameByConstant(constant) {\n switch (constant) {\n case cardinalityEnum.SINGLE:\n return 'single';\n\n case cardinalityEnum.MULTIPLE:\n return 'multiple';\n\n case cardinalityEnum.ORDERED:\n return 'ordered';\n\n case cardinalityEnum.RECORD:\n return 'record';\n\n case cardinalityEnum.SAME:\n return 'same';\n\n case cardinalityEnum.ANY:\n return 'any';\n\n default:\n return false;\n }\n }\n }).assign(cardinalityEnum).value();\n\n return cardinalityHelper;\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2017 (original work) Open Assessment Technologies SA ;\n */\n/**\n * @author Jean-Sébastien Conan \n */\ndefine('taoQtiTest/controller/creator/helpers/outcomeValidator',[\n 'lodash'\n], function (_) {\n 'use strict';\n\n /**\n * The RegEx that validates outcome identifiers\n * @type {RegExp}\n */\n var identifierValidator = /^[a-zA-Z_][a-zA-Z0-9_\\.-]*$/;\n\n /**\n * Checks the validity of an identifier\n * @param {String} identifier\n * @returns {Boolean}\n */\n function validateIdentifier(identifier) {\n return !!(identifier && _.isString(identifier) && identifierValidator.test(identifier));\n }\n\n /**\n * Checks if an object is a valid outcome\n * @param {Object} outcome\n * @param {Boolean} [checkIdentifier]\n * @param {String||String[]} [allowedTypes]\n * @returns {Boolean}\n */\n function validateOutcome(outcome, checkIdentifier, allowedTypes) {\n var validOutcome = _.isPlainObject(outcome) && validateIdentifier(outcome['qti-type']);\n var validIdentifier = !checkIdentifier || (outcome && validateIdentifier(outcome.identifier));\n\n if (allowedTypes) {\n allowedTypes = !_.isArray(allowedTypes) ? [allowedTypes] : allowedTypes;\n validOutcome = validOutcome && _.indexOf(allowedTypes, outcome['qti-type']) >= 0;\n }\n\n return !!(validOutcome && validIdentifier);\n }\n\n /**\n * Checks if an array contains valid outcomes\n * @param {Array} outcomes\n * @param {Boolean} [checkIdentifier]\n * @param {String||String[]} [allowedTypes]\n * @returns {Boolean}\n */\n function validateOutcomes(outcomes, checkIdentifier, allowedTypes) {\n var valid = _.isArray(outcomes);\n _.forEach(outcomes, function(outcome) {\n if (!validateOutcome(outcome, checkIdentifier, allowedTypes)) {\n valid = false;\n return false;\n }\n });\n return valid;\n }\n\n return {\n validateIdentifier: validateIdentifier,\n validateOutcomes: validateOutcomes,\n validateOutcome: validateOutcome\n };\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2017 (original work) Open Assessment Technologies SA ;\n */\n/**\n * @author Jean-Sébastien Conan \n */\ndefine('taoQtiTest/controller/creator/helpers/qtiElement',[\n 'lodash',\n 'taoQtiTest/controller/creator/helpers/outcomeValidator'\n], function (_, outcomeValidator) {\n 'use strict';\n\n var qtiElementHelper = {\n /**\n * Creates a QTI element\n * @param {String} type - The QTI type of the element to create\n * @param {String|Object} [identifier] - An optional identifier, or a list of properties\n * @param {Object} [properties] - A list of additional properties\n * @returns {Object}\n * @throws {TypeError} if the type or the identifier is not valid\n */\n create: function create(type, identifier, properties) {\n var element = {\n 'qti-type': type\n };\n\n if (!outcomeValidator.validateIdentifier(type)) {\n throw new TypeError('You must provide a valid QTI type!');\n }\n\n if (_.isPlainObject(identifier)) {\n properties = identifier;\n identifier = null;\n }\n\n if (identifier) {\n if (!outcomeValidator.validateIdentifier(identifier)) {\n throw new TypeError('You must provide a valid identifier!');\n }\n element.identifier = identifier;\n }\n\n return _.assign(element, properties || {});\n },\n\n /**\n * Finds a QTI element in a collection, by its type.\n * The collection may also be a single object.\n * @param {Array|Object} collection\n * @param {Array|String} type\n * @returns {Object}\n */\n find: function find(collection, type) {\n var found = null;\n var types = forceArray(type);\n\n function checkType(qtiElement) {\n if (types.indexOf(qtiElement['qti-type']) >= 0) {\n found = qtiElement;\n return false;\n }\n }\n\n if (_.isArray(collection)) {\n _.forEach(collection, checkType);\n } else if (collection) {\n checkType(collection);\n }\n\n return found;\n },\n\n /**\n * Finds an element from a tree.\n * The path to the element is based on QTI types.\n * @param {Object} tree - The root of the tree from which get the property\n * @param {String|String[]} path - The path to the element, with QTI types separated by dot, like: \"setOutcomeValue.gte.baseValue\"\n * @param {String|String[]} nodeName - The name of the nodes that may contain subtrees\n * @returns {*}\n */\n lookupElement: function lookupElement(tree, path, nodeName) {\n var steps = _.isArray(path) ? path : path.split('.');\n var nodeNames = forceArray(nodeName);\n var len = steps.length;\n var i = 0;\n var key;\n\n while (tree && i < len) {\n tree = qtiElementHelper.find(tree, steps[i++]);\n if (tree && i < len) {\n key = _.find(nodeNames, _.partial(_.has, tree));\n tree = key && tree[key];\n }\n }\n\n return tree || null;\n },\n\n /**\n * Finds a property from a tree.\n * The path to the property is based on QTI types.\n * @param {Object} tree - The root of the tree from which get the property\n * @param {String|String[]} path - The path to the property, with QTI types separated by dot, like: \"setOutcomeValue.gte.baseValue.value\"\n * @param {String|String[]} nodeName - The name of the nodes that may contain subtrees\n * @returns {*}\n */\n lookupProperty: function lookupProperty(tree, path, nodeName) {\n var result = null;\n var steps = _.isArray(path) ? path : path.split('.');\n var propertyName = steps.pop();\n var element = qtiElementHelper.lookupElement(tree, steps, nodeName);\n\n if (element && element[propertyName]) {\n result = element[propertyName];\n }\n\n return result;\n }\n };\n\n /**\n * Ensures a value is an array\n * @param {*} value\n * @returns {Array}\n */\n function forceArray(value) {\n if (!value) {\n value = [];\n }\n if (!_.isArray(value)) {\n value = [value];\n }\n return value;\n }\n\n return qtiElementHelper;\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2017 (original work) Open Assessment Technologies SA ;\n */\n/**\n * Basic helper that is intended to manage outcomes inside a test model.\n *\n * @author Jean-Sébastien Conan \n */\ndefine('taoQtiTest/controller/creator/helpers/outcome',[\n 'lodash',\n 'taoQtiTest/controller/creator/helpers/outcomeValidator',\n 'taoQtiTest/controller/creator/helpers/qtiElement',\n 'taoQtiTest/controller/creator/helpers/baseType',\n 'taoQtiTest/controller/creator/helpers/cardinality'\n], function (_, outcomeValidator, qtiElementHelper, baseTypeHelper, cardinalityHelper) {\n 'use strict';\n\n var outcomeHelper = {\n /**\n * Gets a property from an outcome rule expression.\n * The path to the property is based on QTI types.\n * @param {Object} outcomeRule - The outcome rule from which get the property\n * @param {String|String[]} path - The path to the property, with QTI types separated by dot, like: \"setOutcomeValue.gte.baseValue\"\n * @returns {*}\n */\n getProcessingRuleExpression: function getProcessingRuleExpression(outcomeRule, path) {\n return qtiElementHelper.lookupElement(outcomeRule, path, ['expression', 'expressions']);\n },\n\n /**\n * Gets a property from an outcome rule expression.\n * The path to the property is based on QTI types.\n * @param {Object} outcomeRule - The outcome rule from which get the property\n * @param {String|String[]} path - The path to the property, with QTI types separated by dot, like: \"setOutcomeValue.gte.baseValue.value\"\n * @returns {*}\n */\n getProcessingRuleProperty: function getProcessingRuleProperty(outcomeRule, path) {\n return qtiElementHelper.lookupProperty(outcomeRule, path, ['expression', 'expressions']);\n },\n\n /**\n * Gets the identifier of an outcome\n * @param {Object|String} outcome\n * @returns {String}\n */\n getOutcomeIdentifier: function getOutcomeIdentifier(outcome) {\n return String(_.isObject(outcome) ? outcome.identifier : outcome);\n },\n\n /**\n * Gets the list of outcome declarations\n * @param {Object} testModel\n * @returns {Array}\n */\n getOutcomeDeclarations: function getOutcomeDeclarations(testModel) {\n var outcomes = testModel && testModel.outcomeDeclarations;\n return outcomes || [];\n },\n\n /**\n * Gets the list of outcome processing rules\n * @param {Object} testModel\n * @returns {Array}\n */\n getOutcomeProcessingRules: function getOutcomeProcessingRules(testModel) {\n var rules = testModel && testModel.outcomeProcessing && testModel.outcomeProcessing.outcomeRules;\n return rules || [];\n },\n\n /**\n * Applies a function on each outcome declarations\n * @param {Object} testModel\n * @param {Function} cb\n */\n eachOutcomeDeclarations: function eachOutcomeDeclarations(testModel, cb) {\n _.forEach(outcomeHelper.getOutcomeDeclarations(testModel), cb);\n },\n\n /**\n * Applies a function on each outcome processing rules. Does not take care of sub-expressions.\n * @param {Object} testModel\n * @param {Function} cb\n */\n eachOutcomeProcessingRules: function eachOutcomeProcessingRules(testModel, cb) {\n _.forEach(outcomeHelper.getOutcomeProcessingRules(testModel), cb);\n },\n\n /**\n * Applies a function on each outcome processing rules, take care of each sub expression.\n * @param {Object} testModel\n * @param {Function} cb\n */\n eachOutcomeProcessingRuleExpressions: function eachOutcomeProcessingRuleExpressions(testModel, cb) {\n function browseExpressions(processingRule) {\n if (_.isArray(processingRule)) {\n _.forEach(processingRule, browseExpressions);\n } else if (processingRule) {\n cb(processingRule);\n\n if (processingRule.expression) {\n browseExpressions(processingRule.expression);\n } else if (processingRule.expressions) {\n browseExpressions(processingRule.expressions);\n }\n }\n }\n\n browseExpressions(outcomeHelper.getOutcomeProcessingRules(testModel));\n },\n\n /**\n * Lists all outcomes identifiers. An optional callback allows to filter the list\n * @param {Object} testModel\n * @param {Function} [cb]\n * @returns {Array}\n */\n listOutcomes: function listOutcomes(testModel, cb) {\n var outcomes = [];\n if (!_.isFunction(cb)) {\n cb = null;\n }\n outcomeHelper.eachOutcomeDeclarations(testModel, function (outcome) {\n if (!cb || cb(outcome)) {\n outcomes.push(outcomeHelper.getOutcomeIdentifier(outcome));\n }\n });\n return outcomes;\n },\n\n /**\n * Removes the specified outcomes from the provided test model\n * @param {Object} testModel - The test model to clean up\n * @param {Function|String[]} outcomes - The list of outcomes identifiers to remove,\n * or a callback that will match each outcome to remove\n */\n removeOutcomes: function removeOutcomes(testModel, outcomes) {\n var declarations = outcomeHelper.getOutcomeDeclarations(testModel);\n var rules = outcomeHelper.getOutcomeProcessingRules(testModel);\n var check;\n\n if (_.isFunction(outcomes)) {\n check = outcomes;\n } else {\n outcomes = _.indexBy(_.isArray(outcomes) ? outcomes : [outcomes], function (outcome) {\n return outcome;\n });\n\n check = function checkIdentifier(outcome) {\n return !!outcomes[outcomeHelper.getOutcomeIdentifier(outcome)];\n };\n }\n\n if (declarations) {\n _.remove(declarations, check);\n }\n\n if (rules) {\n _.remove(rules, check);\n }\n },\n\n /**\n * Creates an outcome declaration\n * @param {String} identifier\n * @param {String|Number|Boolean} [type] - The data type of the outcome, FLOAT by default\n * @param {Number} [cardinality] - The variable cardinality, default 0\n * @returns {Object}\n * @throws {TypeError} if the identifier is empty or is not a string\n */\n createOutcome: function createOutcome(identifier, type, cardinality) {\n\n if (!outcomeValidator.validateIdentifier(identifier)) {\n throw new TypeError('You must provide a valid identifier!');\n }\n\n return qtiElementHelper.create('outcomeDeclaration', identifier, {\n views: [],\n interpretation: '',\n longInterpretation: '',\n normalMaximum: false,\n normalMinimum: false,\n masteryValue: false,\n cardinality: cardinalityHelper.getValid(cardinality, cardinalityHelper.SINGLE),\n baseType: baseTypeHelper.getValid(type, baseTypeHelper.FLOAT)\n });\n },\n\n /**\n * Adds a processing rule into the test model\n *\n * @param {Object} testModel\n * @param {Object} processingRule\n * @returns {Object}\n * @throws {TypeError} if the processing rule is not valid\n */\n addOutcomeProcessing: function createOutcomeProcessing(testModel, processingRule) {\n var outcomeProcessing = testModel.outcomeProcessing;\n\n if (!outcomeValidator.validateOutcome(processingRule)) {\n throw new TypeError('You must provide a valid outcome processing rule!');\n }\n\n if (!outcomeProcessing) {\n outcomeProcessing = qtiElementHelper.create('outcomeProcessing', {\n outcomeRules: []\n });\n testModel.outcomeProcessing = outcomeProcessing;\n } else if (!outcomeProcessing.outcomeRules) {\n outcomeProcessing.outcomeRules = [];\n }\n\n outcomeProcessing.outcomeRules.push(processingRule);\n return processingRule;\n },\n\n /**\n * Creates an outcome and adds it to the declarations\n * @param {Object} testModel\n * @param {Object} outcome - The outcome to add\n * @param {Object} [processingRule] - The processing rule attached to the outcome\n * @returns {Object}\n * @throws {TypeError} if one of the outcome or the processing rule is not valid\n */\n addOutcome: function addOutcome(testModel, outcome, processingRule) {\n var declarations = testModel.outcomeDeclarations;\n\n if (!outcomeValidator.validateOutcome(outcome, true, 'outcomeDeclaration')) {\n throw new TypeError('You must provide a valid outcome!');\n }\n\n if (processingRule) {\n if (!outcomeValidator.validateOutcome(processingRule) || processingRule.identifier !== outcome.identifier) {\n throw new TypeError('You must provide a valid outcome processing rule!');\n }\n\n outcomeHelper.addOutcomeProcessing(testModel, processingRule);\n }\n\n if (!declarations) {\n declarations = [];\n testModel.outcomeDeclarations = declarations;\n }\n\n declarations.push(outcome);\n return outcome;\n },\n\n /**\n * Replaces the outcomes in a test model\n * @param {Object} testModel\n * @param {Object} outcomes\n * @throws {TypeError} if one of the outcomes or the processing rules are not valid\n */\n replaceOutcomes: function replaceOutcomes(testModel, outcomes) {\n if (_.isPlainObject(outcomes)) {\n if (_.isArray(outcomes.outcomeDeclarations)) {\n if (!outcomeValidator.validateOutcomes(outcomes.outcomeDeclarations, true, 'outcomeDeclaration')) {\n throw new TypeError('You must provide valid outcomes!');\n }\n\n testModel.outcomeDeclarations = [].concat(outcomes.outcomeDeclarations);\n }\n if (outcomes.outcomeProcessing && _.isArray(outcomes.outcomeProcessing.outcomeRules)) {\n if (!outcomeValidator.validateOutcomes(outcomes.outcomeProcessing.outcomeRules)) {\n throw new TypeError('You must provide valid processing rules!');\n }\n\n if (!testModel.outcomeProcessing) {\n testModel.outcomeProcessing = qtiElementHelper.create('outcomeProcessing');\n }\n testModel.outcomeProcessing.outcomeRules = [].concat(outcomes.outcomeProcessing.outcomeRules);\n }\n }\n }\n };\n\n return outcomeHelper;\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2017-2023 (original work) Open Assessment Technologies SA ;\n */\n/**\n * Helper that provides a way to browse all categories attached to a test model at the item level.\n *\n * @author Jean-Sébastien Conan \n */\ndefine('taoQtiTest/controller/creator/helpers/category',['lodash'], function (_) {\n 'use strict';\n\n /**\n * Checks if a category is an option\n *\n * @param {String} category\n * @returns {Boolean}\n */\n function isCategoryOption(category) {\n return category && category.indexOf('x-tao-') === 0;\n }\n\n /**\n * Calls a function for each category in the test model\n * @param {Object} testModel\n * @param {Function} cb\n */\n function eachCategories(testModel, cb) {\n const getCategoriesRecursively = section => {\n _.forEach(section.sectionParts, function (sectionPart) {\n if (sectionPart['qti-type'] === 'assessmentItemRef') {\n _.forEach(sectionPart.categories, function (category) {\n cb(category, sectionPart);\n });\n }\n if (sectionPart['qti-type'] === 'assessmentSection') {\n getCategoriesRecursively(sectionPart);\n }\n });\n };\n _.forEach(testModel.testParts, function (testPart) {\n _.forEach(testPart.assessmentSections, function (assessmentSection) {\n getCategoriesRecursively(assessmentSection);\n });\n });\n }\n\n return {\n /**\n * Calls a function for each category in the test model\n * @function eachCategories\n * @param {Object} testModel\n * @param {Function} cb\n */\n eachCategories: eachCategories,\n\n /**\n * Gets the list of categories assigned to the items.\n * Discards special purpose categories like 'x-tao-...'\n *\n * @param {Object} testModel\n * @returns {Array}\n */\n listCategories: function listCategories(testModel) {\n var categories = {};\n eachCategories(testModel, function (category) {\n if (!isCategoryOption(category)) {\n categories[category] = true;\n }\n });\n return _.keys(categories);\n },\n\n /**\n * Gets the list of options assigned to the items (special purpose categories like 'x-tao-...').\n *\n * @param {Object} testModel\n * @returns {Array}\n */\n listOptions: function listOptions(testModel) {\n var options = {};\n eachCategories(testModel, function (category) {\n if (isCategoryOption(category)) {\n options[category] = true;\n }\n });\n return _.keys(options);\n }\n };\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2017 (original work) Open Assessment Technologies SA ;\n */\n/**\n * @author Jean-Sébastien Conan \n */\ndefine('taoQtiTest/controller/creator/modelOverseer',[\n 'lodash',\n 'core/eventifier',\n 'core/statifier',\n 'taoQtiTest/controller/creator/helpers/baseType',\n 'taoQtiTest/controller/creator/helpers/cardinality',\n 'taoQtiTest/controller/creator/helpers/outcome',\n 'taoQtiTest/controller/creator/helpers/category'\n], function (_, eventifier, statifier, baseTypeHelper, cardinalityHelper, outcomeHelper, categoryHelper) {\n 'use strict';\n\n /**\n * Wraps the test model in a manager, provides API to handle events and states\n * @param {Object} model\n * @param {Object} [config]\n * @returns {Object}\n */\n function modelOverseerFactory(model, config) {\n var modelOverseer = {\n /**\n * Gets the nested model\n * @returns {Object}\n */\n getModel: function getModel() {\n return model;\n },\n\n /**\n * Sets the nested model\n *\n * @param {Object} newModel\n * @returns {modelOverseer}\n * @fires setmodel\n */\n setModel: function setModel(newModel) {\n model = newModel;\n\n /**\n * @event modelOverseer#setmodel\n * @param {String} model\n */\n modelOverseer.trigger('setmodel', model);\n return this;\n },\n\n /**\n * Gets the config set\n * @returns {Object}\n */\n getConfig: function getConfig() {\n return config;\n },\n\n\n\n /**\n * Gets the list of defined outcomes for the nested model. A descriptor is built for each outcomes:\n * {\n * name: {String},\n * type: {String},\n * cardinality: {String}\n * }\n * @returns {Object[]}\n */\n getOutcomesList: function getOutcomesList() {\n return _.map(outcomeHelper.getOutcomeDeclarations(model), function(declaration) {\n return {\n name: declaration.identifier,\n type: baseTypeHelper.getNameByConstant(declaration.baseType),\n cardinality: cardinalityHelper.getNameByConstant(declaration.cardinality)\n };\n });\n },\n\n /**\n * Gets the names of the defined outcomes for the nested model\n * @returns {Array}\n */\n getOutcomesNames: function getOutcomesNames() {\n return _.map(outcomeHelper.getOutcomeDeclarations(model), function(declaration) {\n return declaration.identifier;\n });\n },\n\n /**\n * Gets the list of defined categories for the nested model\n * @returns {Array}\n */\n getCategories: function getCategories() {\n return categoryHelper.listCategories(model);\n },\n\n /**\n * Gets the list of defined options for the nested model\n * @returns {Array}\n */\n getOptions: function getOptions() {\n return categoryHelper.listOptions(model);\n }\n };\n\n config = _.isPlainObject(config) ? config : _.assign({}, config);\n\n return statifier(eventifier(modelOverseer));\n }\n\n return modelOverseerFactory;\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2017 (original work) Open Assessment Technologies SA;\n */\n/**\n * This object holds a shared context for all of the test creator modules and allow them to communicate via events.\n * Its lifecycle is bound to the creator controller.\n *\n * @author Christophe Noël \n */\ndefine('taoQtiTest/controller/creator/qtiTestCreator',[\n 'jquery',\n 'core/eventifier',\n 'taoQtiTest/controller/creator/areaBroker',\n 'taoQtiTest/controller/creator/modelOverseer'\n], function($, eventifier, areaBrokerFactory, modelOverseerFactory) {\n 'use strict';\n\n /**\n * @param {jQuery} $creatorContainer - root DOM element containing the creator\n * @param {Object} config - options that will be forwareded to the modelOverseer Factory\n * @returns {Object}\n */\n function testCreatorFactory($creatorContainer, config) {\n var testCreator;\n\n var $container,\n model,\n areaBroker,\n modelOverseer;\n\n /**\n * Create the model overseer with the given model\n * @returns {modelOverseer}\n */\n function loadModelOverseer() {\n if (! modelOverseer && model) {\n modelOverseer = modelOverseerFactory(model, config);\n }\n return modelOverseer;\n }\n\n /**\n * Set up the areaBroker mapping from the actual DOM\n * @returns {areaBroker} already mapped\n */\n function loadAreaBroker(){\n if (! areaBroker) {\n areaBroker = areaBrokerFactory($container, {\n 'creator': $container,\n 'itemSelectorPanel': $container.find('.test-creator-items'),\n 'contentCreatorPanel': $container.find('.test-creator-content'),\n 'propertyPanel': $container.find('.test-creator-props'),\n 'elementPropertyPanel': $container.find('.qti-widget-properties')\n });\n }\n return areaBroker;\n }\n\n if (! ($creatorContainer instanceof $)) {\n throw new TypeError('a valid $container must be given');\n }\n\n $container = $creatorContainer;\n\n testCreator = {\n setTestModel: function setTestModel(m) {\n model = m;\n },\n\n getAreaBroker: function getAreaBroker() {\n return loadAreaBroker();\n },\n\n getModelOverseer: function getModelOverseer() {\n return loadModelOverseer();\n },\n\n isTestHasErrors: function isTestHasErrors() {\n return $container.find('.test-creator-props').find('span.validate-error').length > 0;\n }\n };\n\n return eventifier(testCreator);\n }\n\n return testCreatorFactory;\n});\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2017 (original work) Open Assessment Technologies SA;\n */\n\n/**\n * The testItem data provider\n *\n * @author Bertrand Chevrier \n */\ndefine('taoQtiTest/provider/testItems',[\n 'lodash',\n 'i18n',\n 'util/url',\n 'core/dataProvider/request'\n], function (_, __, urlUtil, request) {\n 'use strict';\n\n /**\n * Per function requests configuration.\n */\n var defaultConfig = {\n getItemClasses : {\n url : urlUtil.route('getItemClasses', 'Items', 'taoQtiTest')\n },\n getItems : {\n url : urlUtil.route('getItems', 'Items', 'taoQtiTest')\n },\n getItemClassProperties : {\n url : urlUtil.route('create', 'RestFormItem', 'taoItems')\n }\n };\n\n /**\n * Creates a configured testItem provider\n *\n * @param {Object} [config] - to override the default config\n * @returns {testItemProvider} the new provider\n */\n return function testItemProviderFactory(config){\n\n config = _.defaults(config || {}, defaultConfig);\n\n /**\n * @typedef {testItemProvider}\n */\n return {\n\n /**\n * Get the list of Items classes and sub classes\n * @returns {Promise} that resolves with the classes\n */\n getItemClasses: function getItemClasses(){\n return request(config.getItemClasses.url);\n },\n\n /**\n * Get QTI Items in different formats\n * @param {Object} [params] - the parameters to pass through the request\n * @returns {Promise} that resolves with the classes\n */\n getItems : function getItems(params){\n return request(config.getItems.url, params);\n },\n\n /**\n * Get the properties of a the given item class\n * @param {String} classUri - the item class URI\n * @returns {Promise} that resolves with the classes\n */\n getItemClassProperties: function getItemClassProperties(classUri) {\n return request(config.getItemClassProperties.url, { classUri : classUri });\n }\n };\n };\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2014 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);\n */\n\n/**\n * @author Bertrand Chevrier \n */\ndefine('taoQtiTest/controller/creator/views/item',[\n 'module',\n 'jquery',\n 'i18n',\n 'core/logger',\n 'taoQtiTest/provider/testItems',\n 'ui/resource/selector',\n 'ui/feedback'\n], function (module, $, __, loggerFactory, testItemProviderFactory, resourceSelectorFactory, feedback) {\n 'use strict';\n\n /**\n * Create a dedicated logger\n */\n const logger = loggerFactory('taoQtiTest/creator/views/item');\n\n /**\n * Let's you access the data\n */\n const testItemProvider = testItemProviderFactory();\n\n /**\n * Handles errors\n * @param {Error} err\n */\n const onError = function onError(err) {\n logger.error(err);\n feedback().error(err.message || __('An error occured while retrieving items'));\n };\n\n const ITEM_URI = 'http://www.tao.lu/Ontologies/TAOItem.rdf#Item';\n\n /**\n * The ItemView setup items related components\n * @exports taoQtiTest/controller/creator/views/item\n * @param {jQueryElement} $container - where to append the view\n */\n return function itemView($container) {\n const filters = module.config().BRS || false; // feature flag BRS (search by metadata) in Test Authoring\n const selectorConfig = {\n type: __('items'),\n selectionMode: resourceSelectorFactory.selectionModes.multiple,\n selectAllPolicy: resourceSelectorFactory.selectAllPolicies.visible,\n classUri: ITEM_URI,\n classes: [\n {\n label: 'Item',\n uri: ITEM_URI,\n type: 'class'\n }\n ],\n filters\n };\n\n //set up the resource selector with one root class Item in classSelector\n const resourceSelector = resourceSelectorFactory($container, selectorConfig)\n .on('render', function () {\n $container.on('itemselected.creator', () => {\n this.clearSelection();\n });\n })\n .on('query', function (params) {\n //ask the server the item from the component query\n testItemProvider\n .getItems(params)\n .then(items => {\n //and update the item list\n this.update(items, params);\n })\n .catch(onError);\n })\n .on('classchange', function (classUri) {\n //by changing the class we need to change the\n //properties filters\n testItemProvider\n .getItemClassProperties(classUri)\n .then(filters => {\n this.updateFilters(filters);\n })\n .catch(onError);\n })\n .on('change', function (values) {\n /**\n * We've got a selection, triggered on the view container\n *\n * TODO replace jquery events by the eventifier\n *\n * @event jQuery#itemselect.creator\n * @param {Object[]} values - the selection\n */\n $container.trigger('itemselect.creator', [values]);\n });\n\n //load the classes hierarchy\n testItemProvider\n .getItemClasses()\n .then(function (classes) {\n selectorConfig.classes = classes;\n selectorConfig.classUri = classes[0].uri;\n })\n .then(function () {\n //load the class properties\n return testItemProvider.getItemClassProperties(selectorConfig.classUri);\n })\n .then(function (filters) {\n //set the filters from the properties\n selectorConfig.filters = filters;\n })\n .then(function () {\n // add classes in classSelector\n selectorConfig.classes[0].children.forEach(node => {\n resourceSelector.addClassNode(node, selectorConfig.classUri);\n });\n resourceSelector.updateFilters(selectorConfig.filters);\n })\n .catch(onError);\n };\n});\n\n","\ndefine('tpl!taoQtiTest/controller/creator/templates/testpart', ['handlebars'], function(hb){ return hb.template(function (Handlebars,depth0,helpers,partials,data) {\n this.compilerInfo = [4,'>= 1.0.0'];\nhelpers = this.merge(helpers, Handlebars.helpers); data = data || {};\n var buffer = \"\", stack1, helper, options, functionType=\"function\", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing;\n\n\n buffer += \"
\\n

\\n \\n \";\n if (helper = helpers.identifier) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.identifier); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"\\n \\n
\\n
\\n
\\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n
\\n
\\n
\\n

\\n
\\n\\n \\n
\\n\\n \\n
\\n
\";\n return buffer;\n }); });\n","\ndefine('tpl!taoQtiTest/controller/creator/templates/section', ['handlebars'], function(hb){ return hb.template(function (Handlebars,depth0,helpers,partials,data) {\n this.compilerInfo = [4,'>= 1.0.0'];\nhelpers = this.merge(helpers, Handlebars.helpers); data = data || {};\n var buffer = \"\", stack1, helper, options, functionType=\"function\", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing;\n\n\n buffer += \"
\\n\\n\\n

\";\n if (helper = helpers.title) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.title); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"\\n \\n
\\n
\\n
\\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n
\\n
\\n
\\n

\\n\\n
\\n

\\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Rubric Blocks\", options) : helperMissing.call(depth0, \"__\", \"Rubric Blocks\", options)))\n + \"\\n

\\n
    \\n \\n
    \\n
    \\n

    \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Items\", options) : helperMissing.call(depth0, \"__\", \"Items\", options)))\n + \"\\n

    \\n
      \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Add selected item(s) here.\", options) : helperMissing.call(depth0, \"__\", \"Add selected item(s) here.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n
      \";\n return buffer;\n }); });\n","\ndefine('tpl!taoQtiTest/controller/creator/templates/rubricblock', ['handlebars'], function(hb){ return hb.template(function (Handlebars,depth0,helpers,partials,data) {\n this.compilerInfo = [4,'>= 1.0.0'];\nhelpers = this.merge(helpers, Handlebars.helpers); data = data || {};\n var buffer = \"\", helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression;\n\n\n buffer += \"
    1. \\n
      \\n
      \\n
      \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n
      \\n
      \\n
      \\n
      \\n
    2. \";\n return buffer;\n }); });\n","\ndefine('tpl!taoQtiTest/controller/creator/templates/itemref', ['handlebars'], function(hb){ return hb.template(function (Handlebars,depth0,helpers,partials,data) {\n this.compilerInfo = [4,'>= 1.0.0'];\nhelpers = this.merge(helpers, Handlebars.helpers); data = data || {};\n var buffer = \"\", stack1, helper, options, functionType=\"function\", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing;\n\n\n buffer += \"
    3. \\n \";\n stack1 = (helper = helpers.dompurify || (depth0 && depth0.dompurify),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.label), options) : helperMissing.call(depth0, \"dompurify\", (depth0 && depth0.label), options));\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n \\n
      \\n
      \\n
      \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n
      \\n
      \\n
      \\n
    4. \";\n return buffer;\n }); });\n","\ndefine('tpl!taoQtiTest/controller/creator/templates/outcomes', ['handlebars'], function(hb){ return hb.template(function (Handlebars,depth0,helpers,partials,data) {\n this.compilerInfo = [4,'>= 1.0.0'];\nhelpers = this.merge(helpers, Handlebars.helpers); data = data || {};\n var buffer = \"\", stack1, helper, options, functionType=\"function\", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing, self=this;\n\nfunction program1(depth0,data) {\n \n var buffer = \"\", stack1, helper;\n buffer += \"\\n
      \\n
      \";\n if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"
      \\n
      \";\n if (helper = helpers.type) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.type); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"
      \\n
      \";\n if (helper = helpers.cardinality) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.cardinality); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"
      \\n
      \\n\";\n return buffer;\n }\n\nfunction program3(depth0,data) {\n \n var buffer = \"\", helper, options;\n buffer += \"\\n
      \\n
      \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"no outcome declaration found\", options) : helperMissing.call(depth0, \"__\", \"no outcome declaration found\", options)))\n + \"
      \\n
      \\n\";\n return buffer;\n }\n\n buffer += \"
      \\n
      \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Identifier\", options) : helperMissing.call(depth0, \"__\", \"Identifier\", options)))\n + \"
      \\n
      \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Type\", options) : helperMissing.call(depth0, \"__\", \"Type\", options)))\n + \"
      \\n
      \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Cardinality\", options) : helperMissing.call(depth0, \"__\", \"Cardinality\", options)))\n + \"
      \\n
      \\n\";\n stack1 = helpers.each.call(depth0, (depth0 && depth0.outcomes), {hash:{},inverse:self.program(3, program3, data),fn:self.program(1, program1, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n return buffer;\n }); });\n","\ndefine('tpl!taoQtiTest/controller/creator/templates/test-props', ['handlebars'], function(hb){ return hb.template(function (Handlebars,depth0,helpers,partials,data) {\n this.compilerInfo = [4,'>= 1.0.0'];\nhelpers = this.merge(helpers, Handlebars.helpers); data = data || {};\n var buffer = \"\", stack1, helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType=\"function\", self=this;\n\nfunction program1(depth0,data) {\n \n var buffer = \"\", helper, options;\n buffer += \"\\n

      \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Time Limits\", options) : helperMissing.call(depth0, \"__\", \"Time Limits\", options)))\n + \"

      \\n\\n \\n
      \\n\\n \\n\\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Maximum duration for the all test.\", options) : helperMissing.call(depth0, \"__\", \"Maximum duration for the all test.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n \\n
      \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Late submission allowed\", options) : helperMissing.call(depth0, \"__\", \"Late submission allowed\", options)))\n + \"\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Whether a candidate's response that is beyond the maximum duration should still be accepted.\", options) : helperMissing.call(depth0, \"__\", \"Whether a candidate's response that is beyond the maximum duration should still be accepted.\", options)))\n + \"\\n
      \\n
      \\n
      \\n
      \\n \";\n return buffer;\n }\n\nfunction program3(depth0,data) {\n \n var buffer = \"\", stack1, helper, options;\n buffer += \"\\n
      \\n\\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Select the way the responses of your test should be processed\", options) : helperMissing.call(depth0, \"__\", \"Select the way the responses of your test should be processed\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Also compute the score per categories\", options) : helperMissing.call(depth0, \"__\", \"Also compute the score per categories\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Set the cut score (or pass score ratio) associated to the test. It must be a float between 0 and 1.\", options) : helperMissing.call(depth0, \"__\", \"Set the cut score (or pass score ratio) associated to the test. It must be a float between 0 and 1.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Set the weight identifier used to process the score\", options) : helperMissing.call(depth0, \"__\", \"Set the weight identifier used to process the score\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n\\n
      \\n
      \\n \";\n stack1 = helpers.each.call(depth0, (depth0 && depth0.modes), {hash:{},inverse:self.noop,fn:self.program(7, program7, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n
      \\n
      \\n
      \\n\";\n return buffer;\n }\nfunction program4(depth0,data) {\n \n var buffer = \"\", stack1, helper;\n buffer += \"\\n \\n \";\n return buffer;\n }\nfunction program5(depth0,data) {\n \n \n return \"selected=\\\"selected\\\"\";\n }\n\nfunction program7(depth0,data) {\n \n var buffer = \"\", stack1, helper;\n buffer += \"\\n
      \\n \\n \";\n if (helper = helpers.description) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.description); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"\\n
      \\n \";\n return buffer;\n }\n\n buffer += \"
      \\n\\n \\n

      \\n\\n\\n
      \\n
      \\n \\n \";\n if (helper = helpers.identifier) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.identifier); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"The principle identifier of the test.\", options) : helperMissing.call(depth0, \"__\", \"The principle identifier of the test.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"The test title.\", options) : helperMissing.call(depth0, \"__\", \"The test title.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n \";\n stack1 = helpers['if'].call(depth0, (depth0 && depth0.showTimeLimits), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n\\n

      \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Scoring\", options) : helperMissing.call(depth0, \"__\", \"Scoring\", options)))\n + \"

      \\n\\n\\n\";\n stack1 = helpers['with'].call(depth0, (depth0 && depth0.scoring), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n\\n

      \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Outcome declarations\", options) : helperMissing.call(depth0, \"__\", \"Outcome declarations\", options)))\n + \"

      \\n\\n \\n
      \\n
      \\n
      \\n \\n
      \\n
      \\n
      \\n
      \\n\\n
      \";\n return buffer;\n }); });\n","\ndefine('tpl!taoQtiTest/controller/creator/templates/testpart-props', ['handlebars'], function(hb){ return hb.template(function (Handlebars,depth0,helpers,partials,data) {\n this.compilerInfo = [4,'>= 1.0.0'];\nhelpers = this.merge(helpers, Handlebars.helpers); data = data || {};\n var buffer = \"\", stack1, helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType=\"function\", self=this;\n\nfunction program1(depth0,data) {\n \n \n return \"checked\";\n }\n\nfunction program3(depth0,data) {\n \n var buffer = \"\", helper, options;\n buffer += \"\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"This constraint affects the visibility of feedback after the end of the last attempt.\", options) : helperMissing.call(depth0, \"__\", \"This constraint affects the visibility of feedback after the end of the last attempt.\", options)))\n + \"\\n
      \\n
      \\n
      \\n \";\n return buffer;\n }\n\nfunction program5(depth0,data) {\n \n var buffer = \"\", helper, options;\n buffer += \"\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"This constraint controls whether or not the candidate is allowed to provide a comment on the item during the session. Comments are not part of the assessed responses.\", options) : helperMissing.call(depth0, \"__\", \"This constraint controls whether or not the candidate is allowed to provide a comment on the item during the session. Comments are not part of the assessed responses.\", options)))\n + \"\\n
      \\n
      \\n
      \\n \";\n return buffer;\n }\n\nfunction program7(depth0,data) {\n \n var buffer = \"\", helper, options;\n buffer += \"\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"If the candidate can skip the item, without submitting a response (default is true).\", options) : helperMissing.call(depth0, \"__\", \"If the candidate can skip the item, without submitting a response (default is true).\", options)))\n + \"\\n
      \\n
      \\n
      \\n \";\n return buffer;\n }\n\nfunction program9(depth0,data) {\n \n var buffer = \"\", helper, options;\n buffer += \"\\n

      \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Time Limits\", options) : helperMissing.call(depth0, \"__\", \"Time Limits\", options)))\n + \"

      \\n\\n \\n
      \\n\\n \\n\\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Maximum duration for this test part.\", options) : helperMissing.call(depth0, \"__\", \"Maximum duration for this test part.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Whether a candidate's response that is beyond the maximum duration of the test part should still be accepted.\", options) : helperMissing.call(depth0, \"__\", \"Whether a candidate's response that is beyond the maximum duration of the test part should still be accepted.\", options)))\n + \"\\n
      \\n
      \\n
      \\n
      \\n \";\n return buffer;\n }\n\n buffer += \"
      \\n

      \";\n if (helper = helpers.identifier) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.identifier); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"

      \\n\\n
      \\n\\n\\n
      \\n
      \\n \\n \";\n if (helper = helpers.identifier) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.identifier); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"The test part identifier.\", options) : helperMissing.call(depth0, \"__\", \"The test part identifier.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n\\n
      \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Navigation\", options) : helperMissing.call(depth0, \"__\", \"Navigation\", options)))\n + \" *\\n
      \\n
      \\n \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"The navigation mode determines the general paths that the candidate may take. A linear mode restricts the candidate to attempt each item in turn. Non Linear removes this restriction.\", options) : helperMissing.call(depth0, \"__\", \"The navigation mode determines the general paths that the candidate may take. A linear mode restricts the candidate to attempt each item in turn. Non Linear removes this restriction.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n\\n
      \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Submission\", options) : helperMissing.call(depth0, \"__\", \"Submission\", options)))\n + \" *\\n
      \\n
      \\n \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"The submission mode determines when the candidate's responses are submitted for response processing. A testPart in individual mode requires the candidate to submit their responses on an item-by-item basis. In simultaneous mode the candidate's responses are all submitted together at the end of the testPart.\", options) : helperMissing.call(depth0, \"__\", \"The submission mode determines when the candidate's responses are submitted for response processing. A testPart in individual mode requires the candidate to submit their responses on an item-by-item basis. In simultaneous mode the candidate's responses are all submitted together at the end of the testPart.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n
      \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Test part level category enables configuring the categories of its composing items all at once. A category in gray means that all items have that category. A category in white means that only a few items have that category.\", options) : helperMissing.call(depth0, \"__\", \"Test part level category enables configuring the categories of its composing items all at once. A category in gray means that all items have that category. A category in white means that only a few items have that category.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n \\n
      \\n
      \\n\\n

      \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Item Session Control\", options) : helperMissing.call(depth0, \"__\", \"Item Session Control\", options)))\n + \"

      \\n\\n\\n\\n
      \\n\\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Controls the maximum number of attempts allowed. 0 means unlimited.\", options) : helperMissing.call(depth0, \"__\", \"Controls the maximum number of attempts allowed. 0 means unlimited.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n\\n \";\n stack1 = helpers['if'].call(depth0, (depth0 && depth0.itemSessionShowFeedback), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n\\n\\n\\n\\n\\n\\n \";\n stack1 = helpers['if'].call(depth0, (depth0 && depth0.itemSessionAllowComment), {hash:{},inverse:self.noop,fn:self.program(5, program5, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n\\n\\n \";\n stack1 = helpers['if'].call(depth0, (depth0 && depth0.itemSessionAllowSkipping), {hash:{},inverse:self.noop,fn:self.program(7, program7, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n\\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"The candidate is not allowed to submit invalid responses.\", options) : helperMissing.call(depth0, \"__\", \"The candidate is not allowed to submit invalid responses.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n
      \\n\\n \";\n stack1 = helpers['if'].call(depth0, (depth0 && depth0.showTimeLimits), {hash:{},inverse:self.noop,fn:self.program(9, program9, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n
      \\n
      \";\n return buffer;\n }); });\n","\ndefine('tpl!taoQtiTest/controller/creator/templates/section-props', ['handlebars'], function(hb){ return hb.template(function (Handlebars,depth0,helpers,partials,data) {\n this.compilerInfo = [4,'>= 1.0.0'];\nhelpers = this.merge(helpers, Handlebars.helpers); data = data || {};\n var buffer = \"\", stack1, helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType=\"function\", self=this;\n\nfunction program1(depth0,data) {\n \n var buffer = \"\", helper, options;\n buffer += \"\\n
      \\n
      \\n \\n
      \\n\\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Associate a blueprint to a section allow you to validate this section against the specified blueprint.\", options) : helperMissing.call(depth0, \"__\", \"Associate a blueprint to a section allow you to validate this section against the specified blueprint.\", options)))\n + \"\\n
      \\n
      \\n
      \\n \";\n return buffer;\n }\n\nfunction program3(depth0,data) {\n \n var buffer = \"\", helper, options;\n buffer += \"\\n\\n
      \\n
      \\n \\n
      \\n\\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"When selecting child elements each element is normally eligible for selection once only.\", options) : helperMissing.call(depth0, \"__\", \"When selecting child elements each element is normally eligible for selection once only.\", options)))\n + \"\\n
      \\n
      \\n
      \\n \";\n return buffer;\n }\n\nfunction program5(depth0,data) {\n \n var buffer = \"\", helper, options;\n buffer += \"\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"This constraint affects the visibility of feedback after the end of the last attempt.\", options) : helperMissing.call(depth0, \"__\", \"This constraint affects the visibility of feedback after the end of the last attempt.\", options)))\n + \"\\n
      \\n
      \\n
      \\n \";\n return buffer;\n }\n\nfunction program7(depth0,data) {\n \n var buffer = \"\", helper, options;\n buffer += \"\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"This constraint controls whether or not the candidate is allowed to provide a comment on the item during the session. Comments are not part of the assessed responses.\", options) : helperMissing.call(depth0, \"__\", \"This constraint controls whether or not the candidate is allowed to provide a comment on the item during the session. Comments are not part of the assessed responses.\", options)))\n + \"\\n
      \\n
      \\n
      \\n \";\n return buffer;\n }\n\nfunction program9(depth0,data) {\n \n var buffer = \"\", helper, options;\n buffer += \"\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"If the candidate can skip the item, without submitting a response (default is true).\", options) : helperMissing.call(depth0, \"__\", \"If the candidate can skip the item, without submitting a response (default is true).\", options)))\n + \"\\n
      \\n
      \\n
      \\n \";\n return buffer;\n }\n\nfunction program11(depth0,data) {\n \n var buffer = \"\", helper, options;\n buffer += \"\\n

      \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Time Limits\", options) : helperMissing.call(depth0, \"__\", \"Time Limits\", options)))\n + \"

      \\n\\n \\n
      \\n\\n\\n \\n\\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Maximum duration for this section.\", options) : helperMissing.call(depth0, \"__\", \"Maximum duration for this section.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Whether a candidate's response that is beyond the maximum duration of the section should still be accepted.\", options) : helperMissing.call(depth0, \"__\", \"Whether a candidate's response that is beyond the maximum duration of the section should still be accepted.\", options)))\n + \"\\n
      \\n
      \\n
      \\n
      \\n \";\n return buffer;\n }\n\n buffer += \"
      \\n

      \";\n if (helper = helpers.title) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.title); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"

      \\n\\n\\n
      \\n
      \\n \\n \";\n if (helper = helpers.identifier) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.identifier); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"The identifier of the section.\", options) : helperMissing.call(depth0, \"__\", \"The identifier of the section.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"The section title.\", options) : helperMissing.call(depth0, \"__\", \"The section title.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n\\n\\n\\n\\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"A visible section is one that is identifiable by the candidate.\", options) : helperMissing.call(depth0, \"__\", \"A visible section is one that is identifiable by the candidate.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n\\n
      \\n
      \\n \\n
      \\n\\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"An invisible section with a parent that is subject to shuffling can specify whether or not its children, which will appear to the candidate as if they were part of the parent, are shuffled as a block or mixed up with the other children of the parent section.\", options) : helperMissing.call(depth0, \"__\", \"An invisible section with a parent that is subject to shuffling can specify whether or not its children, which will appear to the candidate as if they were part of the parent, are shuffled as a block or mixed up with the other children of the parent section.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n \";\n stack1 = helpers['if'].call(depth0, (depth0 && depth0.hasBlueprint), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n\\n
      \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Section level category enables configuring the categories of its composing items all at once. A category in gray means that all items have that category. A category in white means that only a few items have that category.\", options) : helperMissing.call(depth0, \"__\", \"Section level category enables configuring the categories of its composing items all at once. A category in gray means that all items have that category. A category in white means that only a few items have that category.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n \\n
      \\n
      \\n\\n

      \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Selection\", options) : helperMissing.call(depth0, \"__\", \"Selection\", options)))\n + \"

      \\n\\n\\n
      \\n\\n
      \\n
      \\n \\n
      \\n\\n
      \\n \\n
      \\n
      \\n\\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"The number of child elements to be selected.\", options) : helperMissing.call(depth0, \"__\", \"The number of child elements to be selected.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n \";\n stack1 = helpers['if'].call(depth0, (depth0 && depth0.hasSelectionWithReplacement), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n
      \\n\\n

      \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Ordering\", options) : helperMissing.call(depth0, \"__\", \"Ordering\", options)))\n + \"

      \\n\\n\\n
      \\n\\n
      \\n
      \\n \\n
      \\n\\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"If set, it causes the order of the child elements to be randomized, otherwise it uses the order in which the child elements are defined.\", options) : helperMissing.call(depth0, \"__\", \"If set, it causes the order of the child elements to be randomized, otherwise it uses the order in which the child elements are defined.\", options)))\n + \"\\n
      \\n
      \\n
      \\n
      \\n\\n

      \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Item Session Control\", options) : helperMissing.call(depth0, \"__\", \"Item Session Control\", options)))\n + \"

      \\n\\n\\n
      \\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Controls the maximum number of attempts allowed. 0 means unlimited.\", options) : helperMissing.call(depth0, \"__\", \"Controls the maximum number of attempts allowed. 0 means unlimited.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n\\n \";\n stack1 = helpers['if'].call(depth0, (depth0 && depth0.itemSessionShowFeedback), {hash:{},inverse:self.noop,fn:self.program(5, program5, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n\\n\\n\\n\\n\\n\\n \";\n stack1 = helpers['if'].call(depth0, (depth0 && depth0.itemSessionAllowComment), {hash:{},inverse:self.noop,fn:self.program(7, program7, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n\\n\\n \";\n stack1 = helpers['if'].call(depth0, (depth0 && depth0.itemSessionAllowSkipping), {hash:{},inverse:self.noop,fn:self.program(9, program9, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n\\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"The candidate is not allowed to submit invalid responses.\", options) : helperMissing.call(depth0, \"__\", \"The candidate is not allowed to submit invalid responses.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n
      \\n\\n \";\n stack1 = helpers['if'].call(depth0, (depth0 && depth0.showTimeLimits), {hash:{},inverse:self.noop,fn:self.program(11, program11, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n
      \";\n return buffer;\n }); });\n","\ndefine('tpl!taoQtiTest/controller/creator/templates/itemref-props', ['handlebars'], function(hb){ return hb.template(function (Handlebars,depth0,helpers,partials,data) {\n this.compilerInfo = [4,'>= 1.0.0'];\nhelpers = this.merge(helpers, Handlebars.helpers); data = data || {};\n var buffer = \"\", stack1, helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, self=this, functionType=\"function\";\n\nfunction program1(depth0,data) {\n \n var buffer = \"\", helper, options;\n buffer += \"\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"This constraint affects the visibility of feedback after the end of the last attempt.\", options) : helperMissing.call(depth0, \"__\", \"This constraint affects the visibility of feedback after the end of the last attempt.\", options)))\n + \"\\n
      \\n
      \\n
      \\n \";\n return buffer;\n }\n\nfunction program3(depth0,data) {\n \n var buffer = \"\", helper, options;\n buffer += \"\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"This constraint controls whether or not the candidate is allowed to provide a comment on the item during the session. Comments are not part of the assessed responses.\", options) : helperMissing.call(depth0, \"__\", \"This constraint controls whether or not the candidate is allowed to provide a comment on the item during the session. Comments are not part of the assessed responses.\", options)))\n + \"\\n
      \\n
      \\n
      \\n \";\n return buffer;\n }\n\nfunction program5(depth0,data) {\n \n var buffer = \"\", helper, options;\n buffer += \"\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"If the candidate can skip the item, without submitting a response (default is true).\", options) : helperMissing.call(depth0, \"__\", \"If the candidate can skip the item, without submitting a response (default is true).\", options)))\n + \"\\n
      \\n
      \\n
      \\n \";\n return buffer;\n }\n\nfunction program7(depth0,data) {\n \n var buffer = \"\", stack1, helper, options;\n buffer += \"\\n

      \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Time Limits\", options) : helperMissing.call(depth0, \"__\", \"Time Limits\", options)))\n + \"

      \\n\\n\\n
      \\n\\n
      \\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Minimum duration : enforces the test taker to stay on the item for the given duration.\", options) : helperMissing.call(depth0, \"__\", \"Minimum duration : enforces the test taker to stay on the item for the given duration.\", options)))\n + \"
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Maximum duration : the items times out when the duration reaches 0.\", options) : helperMissing.call(depth0, \"__\", \"Maximum duration : the items times out when the duration reaches 0.\", options)))\n + \"
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Locked duration : guided navigation. The test transition to the next item once the duration reaches 0.\", options) : helperMissing.call(depth0, \"__\", \"Locked duration : guided navigation. The test transition to the next item once the duration reaches 0.\", options)))\n + \"
      \\n
      \\n
      \\n
      \\n
      \\n \\n
      \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n
      \\n \\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Maximum duration for this item.\", options) : helperMissing.call(depth0, \"__\", \"Maximum duration for this item.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Minimum duration for this item.\", options) : helperMissing.call(depth0, \"__\", \"Minimum duration for this item.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Whether a candidate's response that is beyond the maximum duration of the item should still be accepted.\", options) : helperMissing.call(depth0, \"__\", \"Whether a candidate's response that is beyond the maximum duration of the item should still be accepted.\", options)))\n + \"\\n
      \\n
      \\n
      \\n
      \\n \";\n return buffer;\n }\nfunction program8(depth0,data) {\n \n \n return \"hidden\";\n }\n\n buffer += \"
      \\n\\n

      \";\n stack1 = (helper = helpers.dompurify || (depth0 && depth0.dompurify),options={hash:{},data:data},helper ? helper.call(depth0, (depth0 && depth0.label), options) : helperMissing.call(depth0, \"dompurify\", (depth0 && depth0.label), options));\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"

      \\n\\n\\n
      \\n
      \\n \\n \";\n if (helper = helpers.identifier) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.identifier); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"The identifier of the item reference.\", options) : helperMissing.call(depth0, \"__\", \"The identifier of the item reference.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"The reference.\", options) : helperMissing.call(depth0, \"__\", \"The reference.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"If required it must appear (at least once) in the selection.\", options) : helperMissing.call(depth0, \"__\", \"If required it must appear (at least once) in the selection.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Not shuffled, the position remains fixed.\", options) : helperMissing.call(depth0, \"__\", \"Not shuffled, the position remains fixed.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n
      \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Items can optionally be assigned to one or more categories.\", options) : helperMissing.call(depth0, \"__\", \"Items can optionally be assigned to one or more categories.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n \\n \\n\\n \\n
      \\n
      \\n\\n

      \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Weights\", options) : helperMissing.call(depth0, \"__\", \"Weights\", options)))\n + \"

      \\n\\n
      \\n
      \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Identifier\", options) : helperMissing.call(depth0, \"__\", \"Identifier\", options)))\n + \"\\n
      \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Value\", options) : helperMissing.call(depth0, \"__\", \"Value\", options)))\n + \"\\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Controls the contribution of an individual item score to the overall test score.\", options) : helperMissing.call(depth0, \"__\", \"Controls the contribution of an individual item score to the overall test score.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n
      \\n \\n
      \\n\\n\\n

      \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Item Session Control\", options) : helperMissing.call(depth0, \"__\", \"Item Session Control\", options)))\n + \"

      \\n\\n\\n
      \\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Controls the maximum number of attempts allowed. 0 means unlimited.\", options) : helperMissing.call(depth0, \"__\", \"Controls the maximum number of attempts allowed. 0 means unlimited.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n\\n \";\n stack1 = helpers['if'].call(depth0, (depth0 && depth0.itemSessionShowFeedback), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n\\n\\n\\n\\n\\n\\n \";\n stack1 = helpers['if'].call(depth0, (depth0 && depth0.itemSessionAllowComment), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n\\n\\n \";\n stack1 = helpers['if'].call(depth0, (depth0 && depth0.itemSessionAllowSkipping), {hash:{},inverse:self.noop,fn:self.program(5, program5, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n\\n\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"The candidate is not allowed to submit invalid responses.\", options) : helperMissing.call(depth0, \"__\", \"The candidate is not allowed to submit invalid responses.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n
      \\n\\n \";\n stack1 = helpers['if'].call(depth0, (depth0 && depth0.showTimeLimits), {hash:{},inverse:self.noop,fn:self.program(7, program7, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n
      \";\n return buffer;\n }); });\n","\ndefine('tpl!taoQtiTest/controller/creator/templates/itemref-props-weight', ['handlebars'], function(hb){ return hb.template(function (Handlebars,depth0,helpers,partials,data) {\n this.compilerInfo = [4,'>= 1.0.0'];\nhelpers = this.merge(helpers, Handlebars.helpers); data = data || {};\n var buffer = \"\", stack1, helper, functionType=\"function\", escapeExpression=this.escapeExpression;\n\n\n buffer += \"
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n \\n \\n
      \\n
      \";\n return buffer;\n }); });\n","\ndefine('tpl!taoQtiTest/controller/creator/templates/rubricblock-props', ['handlebars'], function(hb){ return hb.template(function (Handlebars,depth0,helpers,partials,data) {\n this.compilerInfo = [4,'>= 1.0.0'];\nhelpers = this.merge(helpers, Handlebars.helpers); data = data || {};\n var buffer = \"\", stack1, helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType=\"function\", self=this;\n\nfunction program1(depth0,data) {\n \n var buffer = \"\", helper, options;\n buffer += \"\\n \\n \";\n return buffer;\n }\n\n buffer += \"
      \\n\\n

      \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Rubric Block\", options) : helperMissing.call(depth0, \"__\", \"Rubric Block\", options)))\n + \": \";\n if (helper = helpers.orderIndex) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.orderIndex); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"

      \\n\\n \\n \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Set the XHTML-QTI class of the rubric block.\", options) : helperMissing.call(depth0, \"__\", \"Set the XHTML-QTI class of the rubric block.\", options)))\n + \"\\n
      \\n
      \\n
      \\n\\n \\n \\n\\n

      \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Feedback block\", options) : helperMissing.call(depth0, \"__\", \"Feedback block\", options)))\n + \"

      \\n\\n \\n \";\n stack1 = helpers['with'].call(depth0, (depth0 && depth0.feedback), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n
      \";\n return buffer;\n }); });\n","\ndefine('tpl!taoQtiTest/controller/creator/templates/category-presets', ['handlebars'], function(hb){ return hb.template(function (Handlebars,depth0,helpers,partials,data) {\n this.compilerInfo = [4,'>= 1.0.0'];\nhelpers = this.merge(helpers, Handlebars.helpers); data = data || {};\n var stack1, functionType=\"function\", escapeExpression=this.escapeExpression, self=this, helperMissing=helpers.helperMissing;\n\nfunction program1(depth0,data,depth1) {\n \n var buffer = \"\", stack1, helper;\n buffer += \"\\n

      \";\n if (helper = helpers.groupLabel) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.groupLabel); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"

      \\n\\n
      \\n \";\n stack1 = helpers.each.call(depth0, (depth0 && depth0.presets), {hash:{},inverse:self.noop,fn:self.programWithDepth(2, program2, data, depth1),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n
      \\n\\n\";\n return buffer;\n }\nfunction program2(depth0,data,depth2) {\n \n var buffer = \"\", stack1, helper, options;\n buffer += \"\\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n
      \\n \\n
      \\n \";\n if (helper = helpers.description) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.description); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"\\n
      \\n
      \\n
      \\n \";\n return buffer;\n }\nfunction program3(depth0,data) {\n \n \n return \"checked\";\n }\n\n stack1 = helpers.each.call(depth0, (depth0 && depth0.presetGroups), {hash:{},inverse:self.noop,fn:self.programWithDepth(1, program1, data, depth0),data:data});\n if(stack1 || stack1 === 0) { return stack1; }\n else { return ''; }\n }); });\n","\ndefine('tpl!taoQtiTest/controller/creator/templates/subsection', ['handlebars'], function(hb){ return hb.template(function (Handlebars,depth0,helpers,partials,data) {\n this.compilerInfo = [4,'>= 1.0.0'];\nhelpers = this.merge(helpers, Handlebars.helpers); data = data || {};\n var buffer = \"\", stack1, helper, options, functionType=\"function\", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing;\n\n\n buffer += \"
      \\n\\n

      \";\n if (helper = helpers.title) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.title); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"\\n \\n
      \\n
      \\n
      \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n
      \\n
      \\n
      \\n

      \\n\\n
      \\n

      \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Rubric Blocks\", options) : helperMissing.call(depth0, \"__\", \"Rubric Blocks\", options)))\n + \"\\n

      \\n
        \\n \\n
        \\n
        \\n

        \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Items\", options) : helperMissing.call(depth0, \"__\", \"Items\", options)))\n + \"\\n

        \\n
          \\n
          \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"Add selected item(s) here.\", options) : helperMissing.call(depth0, \"__\", \"Add selected item(s) here.\", options)))\n + \"\\n
          \\n
          \\n
          \\n
          \";\n return buffer;\n }); });\n","\n/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2014-2021 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);\n */\n/**\n * @author Bertrand Chevrier \n */\ndefine('taoQtiTest/controller/creator/templates/index',[\n 'taoQtiTest/controller/creator/config/defaults',\n 'tpl!taoQtiTest/controller/creator/templates/testpart',\n 'tpl!taoQtiTest/controller/creator/templates/section',\n 'tpl!taoQtiTest/controller/creator/templates/rubricblock',\n 'tpl!taoQtiTest/controller/creator/templates/itemref',\n 'tpl!taoQtiTest/controller/creator/templates/outcomes',\n 'tpl!taoQtiTest/controller/creator/templates/test-props',\n 'tpl!taoQtiTest/controller/creator/templates/testpart-props',\n 'tpl!taoQtiTest/controller/creator/templates/section-props',\n 'tpl!taoQtiTest/controller/creator/templates/itemref-props',\n 'tpl!taoQtiTest/controller/creator/templates/itemref-props-weight',\n 'tpl!taoQtiTest/controller/creator/templates/rubricblock-props',\n 'tpl!taoQtiTest/controller/creator/templates/category-presets',\n 'tpl!taoQtiTest/controller/creator/templates/subsection'\n],\nfunction(\n defaults,\n testPart,\n section,\n rubricBlock,\n itemRef,\n outcomes,\n testProps,\n testPartProps,\n sectionProps,\n itemRefProps,\n itemRefPropsWeight,\n rubricBlockProps,\n categoryPresets,\n subsection\n){\n 'use strict';\n\n const applyTemplateConfiguration = (template) => (config) => template(defaults(config));\n\n /**\n * Expose all the templates used by the test creator\n * @exports taoQtiTest/controller/creator/templates/index\n */\n return {\n testpart : applyTemplateConfiguration(testPart),\n section : applyTemplateConfiguration(section),\n itemref : applyTemplateConfiguration(itemRef),\n rubricblock : applyTemplateConfiguration(rubricBlock),\n outcomes : applyTemplateConfiguration(outcomes),\n subsection : applyTemplateConfiguration(subsection),\n properties : {\n test : applyTemplateConfiguration(testProps),\n testpart : applyTemplateConfiguration(testPartProps),\n section : applyTemplateConfiguration(sectionProps),\n itemref : applyTemplateConfiguration(itemRefProps),\n itemrefweight : applyTemplateConfiguration(itemRefPropsWeight),\n rubricblock : applyTemplateConfiguration(rubricBlockProps),\n categorypresets : applyTemplateConfiguration(categoryPresets),\n subsection : applyTemplateConfiguration(sectionProps)\n\n }\n };\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2014-2022 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);\n */\n\n/**\n * @author Bertrand Chevrier \n */\ndefine('taoQtiTest/controller/creator/views/property',['jquery', 'uikitLoader', 'core/databinder', 'taoQtiTest/controller/creator/templates/index'], function (\n $,\n ui,\n DataBinder,\n templates\n) {\n 'use strict';\n\n /**\n * @callback PropertyViewCallback\n * @param {propertyView} propertyView - the view object\n */\n\n /**\n * The PropertyView setup the property panel component\n * @param {String} tmplName\n * @param {Object} model\n * @exports taoQtiTest/controller/creator/views/property\n * @returns {Object}\n */\n const propView = function propView(tmplName, model) {\n const $container = $('.test-creator-props');\n const template = templates.properties[tmplName];\n let $view;\n\n /**\n * Opens the view for the 1st time\n */\n const open = function propOpen() {\n const binderOptions = {\n templates: templates.properties\n };\n $container.children('.props').hide().trigger('propclose.propview');\n $view = $(template(model)).appendTo($container).filter('.props');\n\n //start listening for DOM compoenents inside the view\n ui.startDomComponent($view);\n\n //start the data binding\n const databinder = new DataBinder($view, model, binderOptions);\n databinder.bind();\n\n propValidation();\n\n $view.trigger('propopen.propview');\n\n // contains identifier from model, needed for validation on keyup for identifiers\n // jQuesy selector for Id with dots don't work\n // dots are allowed for id by default see taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier\n // need to use attr\n const $identifier = $view.find(`[id=\"props-${model.identifier}\"]`);\n $view.on('change.binder', function (e) {\n if (e.namespace === 'binder' && $identifier.length) {\n $identifier.text(model.identifier);\n }\n });\n };\n\n /**\n * Get the view container element\n * @returns {jQueryElement}\n */\n const getView = function propGetView() {\n return $view;\n };\n\n /**\n * Check wheter the view is displayed\n * @returns {boolean} true id opened\n */\n const isOpen = function propIsOpen() {\n return $view.css('display') !== 'none';\n };\n\n /**\n * Bind a callback on view open\n * @param {PropertyViewCallback} cb\n */\n const onOpen = function propOnOpen(cb) {\n $view.on('propopen.propview', function (e) {\n e.stopPropagation();\n cb();\n });\n };\n\n /**\n * Bind a callback on view close\n * @param {PropertyViewCallback} cb\n */\n const onClose = function propOnClose(cb) {\n $view.on('propclose.propview', function (e) {\n e.stopPropagation();\n cb();\n });\n };\n\n /**\n * Removes the property view\n */\n const destroy = function propDestroy() {\n $view.remove();\n };\n\n /**\n * Toggles the property view display\n */\n const toggle = function propToggle() {\n $container.children('.props').not($view).hide().trigger('propclose.propview');\n if (isOpen()) {\n $view.hide().trigger('propclose.propview');\n } else {\n $view.show().trigger('propopen.propview');\n }\n };\n\n /**\n * Set up the validation on the property view\n * @private\n */\n function propValidation() {\n $view.on('validated.group', function(e, isValid){\n const $warningIconSelector = $('span.icon-warning');\n const $test = $('.tlb-button-on').parents('.test-creator-test');\n\n // finds error current element if any\n let errors = $(e.currentTarget).find('span.validate-error');\n let currentTargetId = `[id=\"${$(e.currentTarget).find('span[data-bind=\"identifier\"]').attr('id').slice(6)}\"]`;\n \n if(e.namespace === 'group'){\n if (isValid && errors.length === 0) {\n //remove warning icon if validation fails\n if($(e.currentTarget).hasClass('test-props')){\n $($test).find($warningIconSelector).first().css('display', 'none');\n }\n $(currentTargetId).find($warningIconSelector).first().css('display', 'none');\n } else {\n //add warning icon if validation fails\n if($(e.currentTarget).hasClass('test-props')){\n $($test).find($warningIconSelector).first().css('display', 'inline');\n }\n $(currentTargetId).find($warningIconSelector).first().css('display', 'inline');\n }\n }\n });\n\n $view.groupValidator({ events: ['keyup', 'change', 'blur'] });\n }\n\n return {\n open: open,\n getView: getView,\n isOpen: isOpen,\n onOpen: onOpen,\n onClose: onClose,\n destroy: destroy,\n toggle: toggle\n };\n };\n\n return propView;\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2021-2022 (original work) Open Assessment Technologies SA;\n */\ndefine('taoQtiTest/controller/creator/helpers/subsection',['jquery', 'lodash'], function ($, _) {\n 'use strict';\n\n /**\n * Check if this is first level of subsections\n *\n * @param {JQueryElement} $subsection\n * @returns {boolean}\n */\n function isFistLevelSubsection($subsection) {\n return $subsection.parents('.subsection').length === 0;\n }\n /**\n * Check if this is nested subsections (2nd level)\n *\n * @param {JQueryElement} $subsection\n * @returns {boolean}\n */\n function isNestedSubsection($subsection) {\n return $subsection.parents('.subsection').length > 0;\n }\n /**\n * Get subsections of this section/subsection (without nesting subsection)\n *\n * @param {JQueryElement} $section\n * @returns {boolean}\n */\n function getSubsections($section) {\n return $section.children('.subsections').children('.subsection');\n }\n /**\n * Get siblings subsections of this subsection\n *\n * @param {JQueryElement} $subsection\n * @returns {boolean}\n */\n function getSiblingSubsections($subsection) {\n return getSubsectionContainer($subsection).children('.subsection');\n }\n /**\n * Get parent subsection of this nested subsection\n *\n * @param {JQueryElement} $subsection\n * @returns {boolean}\n */\n function getParentSubsection($subsection) {\n return $subsection.parents('.subsection').first();\n }\n /**\n * Get parent section of this subsection\n *\n * @param {JQueryElement} $subsection\n * @returns {boolean}\n */\n function getParentSection($subsection) {\n return $subsection.parents('.section');\n }\n /**\n * Get parent section/subsection\n *\n * @param {JQueryElement} $subsection\n * @returns {boolean}\n */\n function getParent($subsection) {\n if (isFistLevelSubsection($subsection)) {\n return getParentSection($subsection);\n }\n return getParentSubsection($subsection);\n }\n /**\n * Get parent container('.subsections') for this subsection\n *\n * @param {JQueryElement} $subsection\n * @returns {boolean}\n */\n function getSubsectionContainer($subsection) {\n return $subsection.hasClass('subsections') ? $subsection : $subsection.parents('.subsections').first();\n }\n\n /**\n * Get index for this subsection\n *\n * @param {JQueryElement} $subsection\n * @returns {boolean}\n */\n function getSubsectionTitleIndex($subsection) {\n const $parentSection = getParentSection($subsection);\n const index = getSiblingSubsections($subsection).index($subsection);\n const sectionIndex = $parentSection.parents('.sections').children('.section').index($parentSection);\n if (isFistLevelSubsection($subsection)) {\n return `${sectionIndex + 1}.${index + 1}.`;\n } else {\n const $parentSubsection = getParentSubsection($subsection);\n const subsectionIndex = getSiblingSubsections($parentSubsection).index($parentSubsection);\n return `${sectionIndex + 1}.${subsectionIndex + 1}.${index + 1}.`;\n }\n }\n\n return {\n isFistLevelSubsection,\n isNestedSubsection,\n getSubsections,\n getSubsectionContainer,\n getSiblingSubsections,\n getParentSubsection,\n getParentSection,\n getParent,\n getSubsectionTitleIndex\n };\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2014-2022 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);\n */\n\n/**\n * @author Bertrand Chevrier \n */\ndefine('taoQtiTest/controller/creator/views/actions',[\n 'jquery',\n 'taoQtiTest/controller/creator/views/property',\n 'taoQtiTest/controller/creator/helpers/subsection'\n], function ($, propertyView, subsectionsHelper) {\n 'use strict';\n\n const disabledClass = 'disabled';\n const activeClass = 'active';\n const btnOnClass = 'tlb-button-on';\n\n /**\n * Set up the property view for an element\n * @param {jQueryElement} $container - that contains the property opener\n * @param {String} template - the name of the template to give to the propertyView\n * @param {Object} model - the model to bind\n * @param {PropertyViewCallback} cb - execute at view setup phase\n */\n function properties($container, template, model, cb) {\n let propView = null;\n $container.find('.property-toggler').on('click', function (e) {\n e.preventDefault();\n const $elt = $(this);\n if (!$(this).hasClass(disabledClass)) {\n $elt.blur(); //to remove the focus\n\n if (propView === null) {\n $container.addClass(activeClass);\n $elt.addClass(btnOnClass);\n\n propView = propertyView(template, model);\n propView.open();\n\n propView.onOpen(function () {\n $container.addClass(activeClass);\n $elt.addClass(btnOnClass);\n });\n propView.onClose(function () {\n $container.removeClass(activeClass);\n $elt.removeClass(btnOnClass);\n });\n\n if (typeof cb === 'function') {\n cb(propView);\n }\n } else {\n propView.toggle();\n }\n }\n });\n }\n\n /**\n * Enable to move an element\n * @param {jQueryElement} $actionContainer - where the mover is\n * @param {String} containerClass - the cssClass of the element container\n * @param {String} elementClass - the cssClass to identify elements\n */\n function move($actionContainer, containerClass, elementClass) {\n const $element = $actionContainer.closest(`.${elementClass}`);\n const $container = $element.closest(`.${containerClass}`);\n\n //move up an element\n $('.move-up', $actionContainer).click(function (e) {\n let $elements, index;\n\n //prevent default and click during animation and on disabled icon\n e.preventDefault();\n if ($element.is(':animated') && $element.hasClass('disabled')) {\n return false;\n }\n\n //get the position\n $elements = $container.children(`.${elementClass}`);\n index = $elements.index($element);\n if (index > 0) {\n $element.fadeOut(200, () => {\n $element\n .insertBefore($container.children(`.${elementClass}:eq(${index - 1})`))\n .fadeIn(400, () => $container.trigger('change'));\n });\n }\n });\n\n //move down an element\n $('.move-down', $actionContainer).click(function (e) {\n let $elements, index;\n\n //prevent default and click during animation and on disabled icon\n e.preventDefault();\n if ($element.is(':animated') && $element.hasClass('disabled')) {\n return false;\n }\n\n //get the position\n $elements = $container.children(`.${elementClass}`);\n index = $elements.index($element);\n if (index < $elements.length - 1 && $elements.length > 1) {\n $element.fadeOut(200, () => {\n $element\n .insertAfter($container.children(`.${elementClass}:eq(${index + 1})`))\n .fadeIn(400, () => $container.trigger('change'));\n });\n }\n });\n }\n\n /**\n * Update the movable state of an element\n * @param {jQueryElement} $container - the movable elements (scopped)\n * @param {String} elementClass - the cssClass to identify elements\n * @param {String} actionContainerElt - the element name that contains the actions\n */\n function movable($container, elementClass, actionContainerElt) {\n $container.each(function () {\n const $elt = $(this);\n const $actionContainer = $elt.children(actionContainerElt);\n\n const index = $container.index($elt);\n const $moveUp = $('.move-up', $actionContainer);\n const $moveDown = $('.move-down', $actionContainer);\n\n //only one test part, no moving\n if ($container.length === 1) {\n $moveUp.addClass(disabledClass);\n $moveDown.addClass(disabledClass);\n\n //testpart is the first, only moving down\n } else if (index === 0) {\n $moveUp.addClass(disabledClass);\n $moveDown.removeClass(disabledClass);\n\n //testpart is the lasst, only moving up\n } else if (index >= $container.length - 1) {\n $moveDown.addClass(disabledClass);\n $moveUp.removeClass(disabledClass);\n\n //or enable moving top/bottom\n } else {\n $moveUp.removeClass(disabledClass);\n $moveDown.removeClass(disabledClass);\n }\n });\n }\n\n /**\n * Update the removable state of an element\n * @param {jQueryElement} $container - that contains the removable action\n * @param {String} actionContainerElt - the element name that contains the actions\n */\n function removable($container, actionContainerElt) {\n $container.each(function () {\n const $elt = $(this);\n const $actionContainer = $elt.children(actionContainerElt);\n const $delete = $('[data-delete]', $actionContainer);\n\n if ($container.length <= 1 && !$elt.hasClass('subsection')) {\n $delete.addClass(disabledClass);\n } else {\n $delete.removeClass(disabledClass);\n }\n });\n }\n\n /**\n * Disable all the actions of the target\n * @param {jQueryElement} $container - that contains the the actions\n * @param {String} actionContainerElt - the element name that contains the actions\n */\n function disable($container, actionContainerElt) {\n\n if ($container.length <= 2){\n $container.children(actionContainerElt).find('[data-delete]').addClass(disabledClass);\n }\n }\n\n /**\n * Enable all the actions of the target\n * @param {jQueryElement} $container - that contains the the actions\n * @param {String} actionContainerElt - the element name that contains the actions\n */\n function enable($container, actionContainerElt) {\n $container.children(actionContainerElt).find('[data-delete],.move-up,.move-down').removeClass(disabledClass);\n }\n\n /**\n * Hides/shows container for adding items inside a section checking if there is at least\n * one subsection inside of it. As delete subsection event is triggered before subsection\n * container is actually removed from section container, we need to have conditional flow\n * @param {jQueryElement} $section - section jquery container\n */\n function displayItemWrapper($section) {\n const $elt = $('.itemrefs-wrapper:first', $section);\n const subsectionsCount = subsectionsHelper.getSubsections($section).length;\n if (subsectionsCount) {\n $elt.hide();\n } else {\n $elt.show();\n }\n }\n\n /**\n * Update delete selector for 2nd level subsections\n *@param {jQueryElement} $actionContainer - action's container\n */\n function updateDeleteSelector($actionContainer) {\n const $deleteButton = $actionContainer.find('.delete-subsection');\n if ($deleteButton.parents('.subsection').length > 1) {\n const deleteSelector = $deleteButton.data('delete');\n $deleteButton.attr('data-delete', `${deleteSelector} .subsection`);\n }\n }\n\n /**\n * Hides/shows category-presets (Test Navigation, Navigation Warnings, Test-Taker Tools)\n * Hide category-presets for section that contains subsections\n * @param {jQueryElement} $authoringContainer\n * @param {string} [scope='section'] // can also be 'testpart'\n * @fires propertiesView#set-default-categories\n */\n function displayCategoryPresets($authoringContainer, scope = 'section') {\n const id = $authoringContainer.attr('id');\n const $propertiesView = $(`.test-creator-props #${scope}-props-${id}`);\n if (!$propertiesView.length) {\n // property view is not setup\n return;\n }\n const $elt = $propertiesView.find('.category-presets');\n switch (scope) {\n case 'testpart':\n $elt.show();\n break;\n\n case 'section':\n const subsectionsCount = subsectionsHelper.getSubsections($authoringContainer).length;\n if (subsectionsCount) {\n $elt.hide();\n $propertiesView.trigger('set-default-categories');\n } else {\n $elt.show();\n }\n break;\n }\n }\n\n /**\n * Update the index of an section/subsection\n * @param {jQueryElement} $list - list of elements\n */\n function updateTitleIndex($list) {\n $list.each(function () {\n const $elt = $(this);\n const $indexSpan = $('.title-index', $elt.children('h2'));\n\n if ($elt.hasClass('section')) {\n const $parent = $elt.parents('.sections');\n const index = $('.section', $parent).index($elt);\n $indexSpan.text(`${index + 1}.`);\n } else {\n $indexSpan.text(subsectionsHelper.getSubsectionTitleIndex($elt));\n }\n });\n }\n\n /**\n * The actions gives you shared behavior for some actions.\n *\n * @exports taoQtiTest/controller/creator/views/actions\n */\n return {\n properties,\n move,\n removable,\n movable,\n disable,\n enable,\n displayItemWrapper,\n updateDeleteSelector,\n displayCategoryPresets,\n updateTitleIndex\n };\n});\n\n","/*\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2022 (original work) Open Assessment Technologies SA\n *\n */\n\ndefine('taoQtiTest/controller/creator/helpers/featureVisibility',['services/features'], function (features) {\n 'use strict';\n\n /**\n * Adds visibility properties for test model which allow to toggle test properties presence in interface\n * @param {Object} model\n */\n function addTestVisibilityProps(model) {\n if (features.isVisible('taoQtiTest/creator/test/property/timeLimits')) {\n model.showTimeLimits = true;\n }\n }\n\n /**\n * Adds visibility properties for testPart model which allow to toggle testPart properties presence in interface\n * @param {Object} model\n */\n function addTestPartVisibilityProps(model) {\n const propertyNamespace = 'taoQtiTest/creator/testPart/property/';\n if (features.isVisible(`${propertyNamespace}timeLimits`)) {\n model.showTimeLimits = true;\n }\n if (features.isVisible(`${propertyNamespace}itemSessionControl/showFeedback`)) {\n model.itemSessionShowFeedback = true;\n }\n if (features.isVisible(`${propertyNamespace}itemSessionControl/allowComment`)) {\n model.itemSessionAllowComment = true;\n }\n if (features.isVisible(`${propertyNamespace}itemSessionControl/allowSkipping`)) {\n model.itemSessionAllowSkipping = true;\n }\n }\n\n /**\n * Adds visibility properties for section model which allow to toggle section properties presence in interface\n * @param {Object} model\n */\n function addSectionVisibilityProps(model) {\n const propertyNamespace = 'taoQtiTest/creator/section/property/';\n if (features.isVisible(`${propertyNamespace}timeLimits`)) {\n model.showTimeLimits = true;\n }\n if (features.isVisible(`${propertyNamespace}itemSessionControl/showFeedback`)) {\n model.itemSessionShowFeedback = true;\n }\n if (features.isVisible(`${propertyNamespace}itemSessionControl/allowComment`)) {\n model.itemSessionAllowComment = true;\n }\n if (features.isVisible(`${propertyNamespace}itemSessionControl/allowSkipping`)) {\n model.itemSessionAllowSkipping = true;\n }\n }\n\n /**\n * Adds visibility properties for item model which allow to toggle item properties presence in interface\n * @param {Object} model\n */\n function addItemRefVisibilityProps(model) {\n const propertyNamespace = 'taoQtiTest/creator/itemRef/property/';\n if (features.isVisible(`${propertyNamespace}timeLimits`)) {\n model.showTimeLimits = true;\n }\n if (features.isVisible(`${propertyNamespace}itemSessionControl/showFeedback`)) {\n model.itemSessionShowFeedback = true;\n }\n if (features.isVisible(`${propertyNamespace}itemSessionControl/allowComment`)) {\n model.itemSessionAllowComment = true;\n }\n if (features.isVisible(`${propertyNamespace}itemSessionControl/allowSkipping`)) {\n model.itemSessionAllowSkipping = true;\n }\n }\n\n /**\n * Filters the presets and preset groups based on visibility config\n * @param {Array} presetGroups array of presetGroups\n * @param {string} [level='all'] testPart, section of itemRef\n * @returns {Array} filtered presetGroups array\n */\n function filterVisiblePresets(presetGroups, level = 'all') {\n const categoryGroupNamespace = `taoQtiTest/creator/${level}/category/presetGroup/`;\n const categoryPresetNamespace = `taoQtiTest/creator/${level}/category/preset/`;\n let filteredGroups;\n if (presetGroups && presetGroups.length) {\n filteredGroups = presetGroups.filter(presetGroup => {\n return features.isVisible(`${categoryGroupNamespace}${presetGroup.groupId}`);\n });\n if (filteredGroups.length) {\n filteredGroups.forEach(filteredGroup => {\n if (filteredGroup.presets && filteredGroup.presets.length) {\n const filteredPresets = filteredGroup.presets.filter(preset => {\n return features.isVisible(`${categoryPresetNamespace}${preset.id}`);\n });\n filteredGroup.presets = filteredPresets;\n }\n });\n }\n }\n return filteredGroups;\n }\n\n return {\n addTestVisibilityProps,\n addTestPartVisibilityProps,\n addSectionVisibilityProps,\n addItemRefVisibilityProps,\n filterVisiblePresets\n };\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2017-2023 (original work) Open Assessment Technologies SA;\n */\n/**\n * This helper manages the category selection UI:\n * - either via a text entry field that allow to enter any custom categories\n * - either via displaying grouped checkboxes that allow to select any categories presets\n * All categories are then grouped and given to this object's listeners, as they will later end up in the same model field.\n *\n * @author Christophe Noël \n */\ndefine('taoQtiTest/controller/creator/helpers/categorySelector',[\n 'jquery',\n 'lodash',\n 'i18n',\n 'core/eventifier',\n 'ui/tooltip',\n 'taoQtiTest/controller/creator/templates/index',\n 'taoQtiTest/controller/creator/helpers/featureVisibility',\n 'select2'\n], function ($, _, __, eventifier, tooltip, templates, featureVisibility) {\n 'use strict';\n\n let allPresets = [],\n allQtiCategoriesPresets = [];\n\n function categorySelectorFactory($container) {\n const $presetsContainer = $container.find('.category-presets');\n const $customCategoriesSelect = $container.find('[name=category-custom]');\n\n /**\n * Read the form state from the DOM and trigger an event with the result, so the listeners can update the item/section model\n * @fires categorySelector#category-change\n */\n function updateCategories() {\n const presetSelected = $container\n .find('.category-preset input:checked')\n .toArray()\n .map(function (categoryEl) {\n return categoryEl.value;\n }),\n presetIndeterminate = $container\n .find('.category-preset input:indeterminate')\n .toArray()\n .map(function (categoryEl) {\n return categoryEl.value;\n }),\n customSelected = $customCategoriesSelect\n .siblings('.select2-container')\n .find('.select2-search-choice')\n .not('.partial')\n .toArray()\n .map(function (categoryEl) {\n return categoryEl.textContent && categoryEl.textContent.trim();\n }),\n customIndeterminate = $customCategoriesSelect\n .siblings('.select2-container')\n .find('.select2-search-choice.partial')\n .toArray()\n .map(function (categoryEl) {\n return categoryEl.textContent && categoryEl.textContent.trim();\n });\n\n const selectedCategories = presetSelected.concat(customSelected);\n const indeterminatedCategories = presetIndeterminate.concat(customIndeterminate);\n\n /**\n * @event categorySelector#category-change\n * @param {String[]} allCategories\n * @param {String[]} indeterminate\n */\n this.trigger('category-change', selectedCategories, indeterminatedCategories);\n }\n\n const categorySelector = {\n /**\n * Create the category selection form\n *\n * @param {Array} [currentCategories] - all categories currently associated to the item. If applied to a section,\n * contains all the categories applied to at least one item of the section.\n * @param {string} [level] one of the values `testPart`, `section` or `itemRef`\n */\n createForm: function createForm(currentCategories, level) {\n const self = this,\n presetsTpl = templates.properties.categorypresets,\n customCategories = _.difference(currentCategories, allQtiCategoriesPresets);\n\n const filteredPresets = featureVisibility.filterVisiblePresets(allPresets, level);\n // add preset checkboxes\n $presetsContainer.append(presetsTpl({ presetGroups: filteredPresets }));\n\n $presetsContainer.on('click', function (e) {\n const $preset = $(e.target).closest('.category-preset');\n if ($preset.length) {\n const $checkbox = $preset.find('input');\n $checkbox.prop('indeterminate', false);\n\n _.defer(function () {\n updateCategories.call(self);\n });\n }\n });\n\n // init custom categories field\n $customCategoriesSelect\n .select2({\n width: '100%',\n tags: customCategories,\n multiple: true,\n tokenSeparators: [',', ' ', ';'],\n formatNoMatches: function () {\n return __('Enter a custom category');\n },\n maximumInputLength: 32\n })\n .on('change', function () {\n updateCategories.call(self);\n });\n\n // enable help tooltips\n tooltip.lookup($container);\n },\n\n /**\n * Check/Uncheck boxes and fill the custom category field to match the new model\n * @param {String[]} selected - categories associated with an item, or with all the items of the same section\n * @param {String[]} [indeterminate] - categories in an indeterminate state at a section level\n */\n updateFormState: function updateFormState(selected, indeterminate) {\n indeterminate = indeterminate || [];\n\n const customCategories = _.difference(selected.concat(indeterminate), allQtiCategoriesPresets);\n\n // Preset categories\n\n const $presetsCheckboxes = $container.find('.category-preset input');\n $presetsCheckboxes.each(function () {\n const category = this.value;\n\n this.indeterminate = false;\n this.checked = false;\n\n if (indeterminate.indexOf(category) !== -1) {\n this.indeterminate = true;\n } else if (selected.indexOf(category) !== -1) {\n this.checked = true;\n }\n });\n\n // Custom categories\n\n $customCategoriesSelect.select2('val', customCategories);\n\n $customCategoriesSelect\n .siblings('.select2-container')\n .find('.select2-search-choice')\n .each(function () {\n const $li = $(this);\n const content = $li.find('div').text();\n if (indeterminate.indexOf(content) !== -1) {\n $li.addClass('partial');\n }\n });\n }\n };\n\n eventifier(categorySelector);\n\n return categorySelector;\n }\n\n /**\n * @param {Object[]} presets - expected format:\n * [\n * {\n * groupId: 'navigation',\n * groupLabel: 'Test Navigation',\n * presets: [\n * {\n * id: 'nextPartWarning',\n * label: 'Next Part Warning',\n * qtiCategory : 'x-tao-option-nextPartWarning',\n * description : 'Displays a warning before the user finishes a part'\n * },\n * ...\n * ]\n * },\n * ...\n * ]\n */\n categorySelectorFactory.setPresets = function setPresets(presets) {\n if (_.isArray(presets)) {\n allPresets = presets;\n allQtiCategoriesPresets = extractCategoriesFromPresets();\n }\n };\n\n /**\n * Extract the qtiCategory property of all presets of all groups\n * @returns {String[]}\n */\n function extractCategoriesFromPresets() {\n return allPresets.reduce(function (prev, current) {\n const groupIds = _.pluck(current.presets, 'qtiCategory');\n return prev.concat(groupIds);\n }, []);\n }\n\n return categorySelectorFactory;\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2015-2023 (original work) Open Assessment Technologies SA;\n */\ndefine('taoQtiTest/controller/creator/helpers/sectionCategory',['lodash', 'i18n', 'core/errorHandler'], function (_, __, errorHandler) {\n 'use strict';\n\n const _ns = '.sectionCategory';\n\n /**\n * Check if the given object is a valid assessmentSection model object\n *\n * @param {object} model\n * @returns {boolean}\n */\n function isValidSectionModel(model) {\n return _.isObject(model) && model['qti-type'] === 'assessmentSection' && _.isArray(model.sectionParts);\n }\n\n /**\n * Set an array of categories to the section model (affect the childen itemRef)\n *\n * @param {object} model\n * @param {array} selected - all categories active for the whole section\n * @param {array} partial - only categories in an indeterminate state\n * @returns {undefined}\n */\n function setCategories(model, selected, partial) {\n const currentCategories = getCategories(model);\n\n partial = partial || [];\n\n //the categories that are no longer in the new list of categories should be removed\n const toRemove = _.difference(currentCategories.all, selected.concat(partial));\n\n //the categories that are not in the current categories collection should be added to the children\n const toAdd = _.difference(selected, currentCategories.propagated);\n\n model.categories = _.difference(model.categories, toRemove);\n model.categories = model.categories.concat(toAdd);\n\n //process the modification\n addCategories(model, toAdd);\n removeCategories(model, toRemove);\n }\n\n /**\n * Get the categories assign to the section model, infered by its interal itemRefs\n *\n * @param {object} model\n * @returns {object}\n */\n function getCategories(model) {\n let categories= [],\n arrays,\n union,\n propagated,\n partial,\n itemCount = 0;\n\n if (!isValidSectionModel(model)) {\n return errorHandler.throw(_ns, 'invalid tool config format');\n }\n\n const getCategoriesRecursive = sectionModel => _.forEach(sectionModel.sectionParts, function (sectionPart) {\n if (\n sectionPart['qti-type'] === 'assessmentItemRef' &&\n ++itemCount &&\n _.isArray(sectionPart.categories)\n ) {\n categories.push(_.compact(sectionPart.categories));\n }\n if (sectionPart['qti-type'] === 'assessmentSection' && _.isArray(sectionPart.sectionParts)) {\n getCategoriesRecursive(sectionPart);\n }\n });\n\n getCategoriesRecursive(model);\n\n if (!itemCount) {\n return createCategories(model.categories, model.categories);\n }\n\n //array of categories\n arrays = _.values(categories);\n union = _.union.apply(null, arrays);\n\n //categories that are common to all itemRef\n propagated = _.intersection.apply(null, arrays);\n\n //the categories that are only partially covered on the section level : complementary of \"propagated\"\n partial = _.difference(union, propagated);\n\n return createCategories(union, propagated, partial);\n }\n\n /**\n * Add an array of categories to a section model (affect the childen itemRef)\n *\n * @param {object} model\n * @param {array} categories\n * @returns {undefined}\n */\n function addCategories(model, categories) {\n if (isValidSectionModel(model)) {\n _.each(model.sectionParts, function (sectionPart) {\n if (sectionPart['qti-type'] === 'assessmentItemRef') {\n if (!_.isArray(sectionPart.categories)) {\n sectionPart.categories = [];\n }\n sectionPart.categories = _.union(sectionPart.categories, categories);\n }\n if (sectionPart['qti-type'] === 'assessmentSection') {\n addCategories(sectionPart, categories);\n }\n });\n } else {\n errorHandler.throw(_ns, 'invalid tool config format');\n }\n }\n\n /**\n * Remove an array of categories from a section model (affect the childen itemRef)\n *\n * @param {object} model\n * @param {array} categories\n * @returns {undefined}\n */\n function removeCategories(model, categories) {\n if (isValidSectionModel(model)) {\n _.each(model.sectionParts, function (sectionPart) {\n if (sectionPart['qti-type'] === 'assessmentItemRef' && _.isArray(sectionPart.categories)) {\n sectionPart.categories = _.difference(sectionPart.categories, categories);\n }\n if (sectionPart['qti-type'] === 'assessmentSection') {\n removeCategories(sectionPart, categories);\n }\n });\n } else {\n errorHandler.throw(_ns, 'invalid tool config format');\n }\n }\n\n function createCategories(all = [], propagated = [], partial = []) {\n return _.mapValues(\n {\n all: all,\n propagated: propagated,\n partial: partial\n },\n function (categories) {\n return categories.sort();\n }\n );\n }\n\n return {\n isValidSectionModel: isValidSectionModel,\n setCategories: setCategories,\n getCategories: getCategories,\n addCategories: addCategories,\n removeCategories: removeCategories\n };\n});\n\n","/*\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2022 (original work) Open Assessment Technologies SA\n *\n */\ndefine('taoQtiTest/controller/creator/helpers/validators',[\n 'ui/validator/validators',\n 'jquery',\n 'lodash',\n 'i18n',\n 'taoQtiTest/controller/creator/helpers/outcome',\n 'taoQtiTest/controller/creator/helpers/qtiElement',\n 'taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier'\n], function (validators, $, _, __, outcomeHelper, qtiElementHelper, qtiIdentifier) {\n 'use strict';\n\n const qtiIdPattern = qtiIdentifier.pattern;\n //Identifiers must be unique across\n //those QTI types\n const qtiTypesForUniqueIds = ['assessmentTest', 'testPart', 'assessmentSection', 'assessmentItemRef'];\n\n /**\n * Gives you a validator that check QTI id format\n * @returns {Object} the validator\n */\n function idFormatValidator() {\n return {\n name: 'idFormat',\n message: qtiIdentifier.invalidQtiIdMessage,\n validate: function (value, callback) {\n if (typeof callback === 'function') {\n callback(qtiIdPattern.test(value));\n }\n }\n };\n }\n\n /**\n * Gives you a validator that check QTI id format of the test (it is different from the others...)\n * @returns {Object} the validator\n */\n function testidFormatValidator() {\n const qtiTestIdPattern = /^\\S+$/;\n return {\n name: 'testIdFormat',\n message: __('is not a valid identifier (everything except spaces)'),\n validate: function (value, callback) {\n if (typeof callback === 'function') {\n callback(qtiTestIdPattern.test(value));\n }\n }\n };\n }\n\n /**\n * Gives you a validator that check if a QTI id is available\n * @param {Object} modelOverseer - let's you get the data model\n * @returns {Object} the validator\n */\n function idAvailableValidator(modelOverseer) {\n return {\n name: 'testIdAvailable',\n message: __('is already used in the test.'),\n validate: function (value, callback, options) {\n if (options.identifier) {\n const key = value.toUpperCase();\n const identifiers = extractIdentifiers(modelOverseer.getModel(), qtiTypesForUniqueIds);\n // jQuesy selector for Id with dots don't work\n // dots are allowed for id by default see taoQtiItem/qtiCreator/widgets/helpers/qtiIdentifier\n // need to use attr\n const $idInUI = $(`[id=\"props-${options.identifier}\"]:contains(\"${value}\")`);\n if (typeof callback === 'function') {\n const counts = _.countBy(identifiers, 'identifier');\n //the identifier list contains itself after change on input\n //on keyup $idInUI.length === 0\n //on change and blur $idInUI.length === 1 and text equal value\n callback(\n typeof counts[key] === 'undefined' ||\n $idInUI.length === 1 && $idInUI.text() === value && counts[key] === 1\n );\n }\n } else {\n throw new Error('missing required option \"identifier\"');\n }\n }\n };\n }\n\n /**\n * Gives you a validator that check if a QTI id is available\n * @param {Object} modelOverseer - let's you get the data model\n */\n function registerValidators(modelOverseer) {\n //register validators\n validators.register('idFormat', idFormatValidator());\n validators.register('testIdFormat', testidFormatValidator());\n validators.register('testIdAvailable', idAvailableValidator(modelOverseer), true);\n }\n\n /**\n * Validates the provided model\n * @param {Object} model\n * @throws {Error} if the model is not valid\n */\n function validateModel(model) {\n const identifiers = extractIdentifiers(model, qtiTypesForUniqueIds);\n let nonUniqueIdentifiers = 0;\n const outcomes = _.indexBy(outcomeHelper.listOutcomes(model));\n let messageDetails = '';\n\n _(identifiers)\n .countBy('identifier')\n .forEach(function (count, id) {\n if (count > 1) {\n nonUniqueIdentifiers++;\n messageDetails += `\\n${id}`;\n }\n });\n if (nonUniqueIdentifiers >= 1) {\n throw new Error(__('The following identifiers are not unique accross the test : %s', messageDetails));\n }\n\n _.forEach(model.testParts, function (testPart) {\n _.forEach(testPart.assessmentSections, function (assessmentSection) {\n _.forEach(assessmentSection.rubricBlocks, function (rubricBlock) {\n const feedbackBlock = qtiElementHelper.lookupElement(\n rubricBlock,\n 'rubricBlock.div.feedbackBlock',\n 'content'\n );\n if (feedbackBlock && !outcomes[feedbackBlock.outcomeIdentifier]) {\n throw new Error(\n __(\n 'The outcome \"%s\" does not exist, but it is referenced by a feedback block!',\n feedbackBlock.outcomeIdentifier\n )\n );\n }\n });\n });\n });\n }\n /**\n * Extracts the identifiers from a QTI model\n * @param {Object|Object[]} model - the JSON QTI model\n * @param {String[]} [includesOnlyTypes] - list of qti-type to include, exclusively\n * @param {String[]} [excludeTypes] - list of qti-type to exclude, it excludes the children too\n * @returns {Object[]} a collection of identifiers (with some meta), if the id is not unique it will appear multiple times, as extracted.\n */\n function extractIdentifiers(model, includesOnlyTypes, excludeTypes) {\n const identifiers = [];\n\n const extract = function extract(element) {\n if (element && _.has(element, 'identifier') && _.isString(element.identifier)) {\n if (!includesOnlyTypes.length || _.contains(includesOnlyTypes, element['qti-type'])) {\n identifiers.push({\n identifier: element.identifier.toUpperCase(),\n originalIdentifier: element.identifier,\n type: element['qti-type'],\n label: element.title || element.identifier\n });\n }\n }\n _.forEach(element, function (subElement) {\n if (_.isPlainObject(subElement) || _.isArray(subElement)) {\n if (!excludeTypes.length || !_.contains(excludeTypes, subElement['qti-type'])) {\n extract(subElement);\n }\n }\n });\n };\n\n if (_.isPlainObject(model) || _.isArray(model)) {\n excludeTypes = excludeTypes || [];\n includesOnlyTypes = includesOnlyTypes || [];\n\n extract(model);\n }\n return identifiers;\n }\n\n /**\n * Gives you a validator that check QTI id format\n * @param {String} value of identifier\n * @param {Object} modelOverseer - let's you get the data model\n * @returns {Boolean} isValid\n */\n function checkIfItemIdValid(value, modelOverseer) {\n const identifiers = extractIdentifiers(modelOverseer.getModel(), qtiTypesForUniqueIds);\n const key = value.toUpperCase();\n const counts = _.countBy(identifiers, 'identifier');\n return qtiIdPattern.test(value) && counts[key] === 1;\n }\n return {\n qtiTypesForUniqueIds,\n extractIdentifiers,\n registerValidators,\n validateModel,\n checkIfItemIdValid\n };\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2015-2022 (original work) Open Assessment Technologies SA ;\n */\n\n/**\n * @author Bertrand Chevrier \n */\ndefine('taoQtiTest/controller/creator/helpers/qtiTest',['jquery', 'lodash', 'taoQtiTest/controller/creator/helpers/validators'], function ($, _, validators) {\n 'use strict';\n\n /**\n * Utility to manage the QTI Test model\n * @exports taoQtiTest/controller/creator/qtiTestHelper\n */\n const qtiTestHelper = {\n /**\n * Get the list of unique identifiers for the given model.\n * @param {Object|Object[]} model - the JSON QTI model\n * @param {String[]} [includesOnlyTypes] - list of qti-type to include, exclusively\n * @param {String[]} [excludeTypes] - list of qti-type to exclude, it excludes the children too\n * @returns {String[]} the list of unique identifiers\n */\n getIdentifiers: function getIdentifiers(model, includesOnlyTypes, excludeTypes) {\n return _.uniq(_.pluck(validators.extractIdentifiers(model, includesOnlyTypes, excludeTypes), 'identifier'));\n },\n\n /**\n * Get the list of identifiers for a given QTI type, only.\n * @param {Object|Object[]} model - the JSON QTI model\n * @param {String} qtiType - the type of QTI element to get the identifiers.\n * @returns {String[]} the list of unique identifiers\n */\n getIdentifiersOf: function getIdentifiersOf(model, qtiType) {\n return this.getIdentifiers(model, [qtiType]);\n },\n\n /**\n * Does the value contains the type type\n * @param {Object} value\n * @param {string} type\n * @returns {boolean}\n */\n filterQtiType: function filterQtiType(value, type) {\n return value['qti-type'] && value['qti-type'] === type;\n },\n\n /**\n * Add the 'qti-type' properties to object that miss it, using the parent key name\n * @param {Object|Array} collection\n * @param {string} parentType\n */\n addMissingQtiType: function addMissingQtiType(collection, parentType) {\n _.forEach(collection, (value, key) => {\n if (_.isObject(value) && !_.isArray(value) && !_.has(value, 'qti-type')) {\n if (_.isNumber(key)) {\n if (parentType) {\n value['qti-type'] = parentType;\n }\n } else {\n value['qti-type'] = key;\n }\n }\n if (_.isArray(value)) {\n this.addMissingQtiType(value, key.replace(/s$/, ''));\n } else if (_.isObject(value)) {\n this.addMissingQtiType(value);\n }\n });\n },\n\n /**\n * Applies consolidation rules to the model\n * @param {Object} model\n * @returns {Object}\n */\n consolidateModel: function consolidateModel(model) {\n if (model && model.testParts && _.isArray(model.testParts)) {\n _.forEach(model.testParts, function (testPart) {\n if (testPart.assessmentSections && _.isArray(testPart.assessmentSections)) {\n _.forEach(testPart.assessmentSections, function (assessmentSection) {\n //remove ordering is shuffle is false\n if (\n assessmentSection.ordering &&\n typeof assessmentSection.ordering.shuffle !== 'undefined' &&\n assessmentSection.ordering.shuffle === false\n ) {\n delete assessmentSection.ordering;\n }\n\n // clean categories (QTI identifier can't be empty string)\n if (assessmentSection.sectionParts && _.isArray(assessmentSection.sectionParts)) {\n _.forEach(assessmentSection.sectionParts, function (part) {\n if (\n part.categories &&\n _.isArray(part.categories) &&\n (part.categories.length === 0 || part.categories[0].length === 0)\n ) {\n part.categories = [];\n }\n });\n }\n\n if (assessmentSection.rubricBlocks && _.isArray(assessmentSection.rubricBlocks)) {\n //remove rubric blocks if empty\n if (\n assessmentSection.rubricBlocks.length === 0 ||\n (assessmentSection.rubricBlocks.length === 1 &&\n assessmentSection.rubricBlocks[0].content.length === 0)\n ) {\n delete assessmentSection.rubricBlocks;\n } else if (assessmentSection.rubricBlocks.length > 0) {\n //ensure the view attribute is present\n _.forEach(assessmentSection.rubricBlocks, function (rubricBlock) {\n rubricBlock.views = ['candidate'];\n //change once views are supported\n //if(rubricBlock && rubricBlock.content && (!rubricBlock.views || (_.isArray(rubricBlock.views) && rubricBlock.views.length === 0))){\n //rubricBlock.views = ['candidate'];\n //}\n });\n }\n }\n });\n }\n });\n }\n return model;\n },\n /**\n * Get a valid and available QTI identifier for the given type\n * @param {Object|Object[]} model - the JSON QTI model to check the existing IDs\n * @param {String} qtiType - the type of element you want an id for\n * @param {String} [suggestion] - the default pattern body, we use the type otherwise\n * @returns {String} the generated identifier\n */\n getAvailableIdentifier: function getAvailableIdentifier(model, qtiType, suggestion) {\n let index = 1;\n const glue = '-';\n let identifier;\n let current;\n if (_.contains(validators.qtiTypesForUniqueIds, qtiType)) {\n current = this.getIdentifiers(model, validators.qtiTypesForUniqueIds);\n } else {\n current = this.getIdentifiersOf(model, qtiType);\n }\n\n suggestion = suggestion || qtiType;\n\n do {\n identifier = suggestion + glue + index++;\n } while (\n _.contains(current, identifier.toUpperCase()) || // identifier exist in model\n $(`#${identifier}`).length // identifier was in model but still exist in DOM\n );\n\n return identifier;\n }\n };\n\n return qtiTestHelper;\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2014 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);\n */\n\n/**\n * @author Bertrand Chevrier \n */\ndefine('taoQtiTest/controller/creator/views/itemref',[\n 'jquery',\n 'lodash',\n 'i18n',\n 'taoQtiTest/controller/creator/views/actions',\n 'taoQtiTest/controller/creator/helpers/categorySelector',\n 'taoQtiTest/controller/creator/helpers/sectionCategory',\n 'taoQtiTest/controller/creator/helpers/qtiTest',\n 'taoQtiTest/controller/creator/helpers/featureVisibility',\n 'taoQtiTest/controller/creator/templates/index'\n], function (\n $,\n _,\n __,\n actions,\n categorySelectorFactory,\n sectionCategory,\n qtiTestHelper,\n featureVisibility,\n templates\n) {\n ('use strict');\n\n /**\n * We need to resize the itemref in case of long labels\n */\n var resize = _.throttle(function resize() {\n var $refs = $('.itemrefs').first();\n var $actions = $('.itemref .actions').first();\n var width = $refs.innerWidth() - $actions.outerWidth();\n $('.itemref > .title').width(width);\n }, 100);\n\n /**\n * Set up an item ref: init action behaviors. Called for each one.\n *\n * @param {Object} creatorContext\n * @param {Object} refModel - the data model to bind to the item ref\n * @param {Object} sectionModel - the parent data model to inherit\n * @param {Object} partModel - the model of the parent's test part\n * @param {jQueryElement} $itemRef - the itemRef element to set up\n */\n function setUp(creatorContext, refModel, sectionModel, partModel, $itemRef) {\n var modelOverseer = creatorContext.getModelOverseer();\n var config = modelOverseer.getConfig() || {};\n var $actionContainer = $('.actions', $itemRef);\n\n // set item session control to use test part options if section level isn't set\n if (!refModel.itemSessionControl) {\n refModel.itemSessionControl = {};\n }\n _.defaults(refModel.itemSessionControl, sectionModel.itemSessionControl);\n\n refModel.isLinear = partModel.navigationMode === 0;\n\n //add feature visibility properties to itemRef model\n featureVisibility.addItemRefVisibilityProps(refModel);\n\n actions.properties($actionContainer, 'itemref', refModel, propHandler);\n actions.move($actionContainer, 'itemrefs', 'itemref');\n\n /**\n * We need to resize the itemref in case of long labels\n */\n _.throttle(function resize() {\n var $actions = $itemRef.find('.actions').first();\n var width = $itemRef.innerWidth() - $actions.outerWidth();\n $('.itemref > .title').width(width);\n }, 100);\n\n /**\n * Set up the time limits behaviors :\n * - linear test part: display the minTime field\n * - linear + guided nav option : display the minTime field + the lock\n * - otherwise only the maxTime field\n * @param {propView} propView - the view object\n */\n function timeLimitsProperty(propView) {\n var $view = propView.getView();\n\n //target elements\n var $minTimeContainer = $('.mintime-container', $view);\n var $maxTimeContainer = $('.maxtime-container', $view);\n var $lockedTimeContainer = $('.lockedtime-container', $view);\n var $locker = $('.locker button', $lockedTimeContainer);\n var $durationFields = $(':text[data-duration]', $lockedTimeContainer);\n var $minTimeField = $(':text[name=\"min-time\"]', $lockedTimeContainer);\n var $maxTimeField = $(':text[name=\"max-time\"]', $lockedTimeContainer);\n\n /**\n * Sync min value to max value, trigger change to sync the component.\n * Need to temporally remove the other handler to prevent infinite loop\n */\n var minToMaxHandler = _.throttle(function minToMax() {\n $maxTimeField.off('change.sync');\n $maxTimeField.val($minTimeField.val()).trigger('change');\n _.defer(function () {\n $maxTimeField.on('change.sync', minToMaxHandler);\n });\n }, 200);\n\n /**\n * Sync max value to min value, trigger change to sync the component.\n * Need to temporally remove the other handler to prevent infinite loop\n */\n var maxToMinHandler = _.throttle(function maxToMin() {\n $minTimeField.off('change.sync');\n $minTimeField.val($maxTimeField.val()).trigger('change');\n _.defer(function () {\n $minTimeField.on('change.sync', minToMaxHandler);\n });\n }, 200);\n\n /**\n * Lock the timers\n */\n var lockTimers = function lockTimers() {\n $locker\n .removeClass('unlocked')\n .addClass('locked')\n .attr('title', __('Unlink to use separated durations'));\n\n //sync min to max\n $minTimeField.val($maxTimeField.val()).trigger('change');\n\n //keep both in sync\n $minTimeField.on('change.sync', minToMaxHandler);\n $maxTimeField.on('change.sync', maxToMinHandler);\n };\n\n /**\n * Unlock the timers\n */\n var unlockTimers = function unlockTimers() {\n $locker\n .removeClass('locked')\n .addClass('unlocked')\n .attr('title', __('Link durations to activate the guided navigation'));\n\n $durationFields.off('change.sync');\n $minTimeField.val('00:00:00').trigger('change');\n };\n\n /**\n * Toggle the timelimits modes max, min + max, min + max + locked\n */\n var toggleTimeContainers = function toggleTimeContainers() {\n refModel.isLinear = partModel.navigationMode === 0;\n if (refModel.isLinear && config.guidedNavigation) {\n $minTimeContainer.addClass('hidden');\n $maxTimeContainer.addClass('hidden');\n $lockedTimeContainer.removeClass('hidden');\n if ($minTimeField.val() === $maxTimeField.val() && $maxTimeField.val() !== '00:00:00') {\n lockTimers();\n }\n $locker.on('click', function (e) {\n e.preventDefault();\n\n if ($locker.hasClass('locked')) {\n unlockTimers();\n } else {\n lockTimers();\n }\n });\n } else if (refModel.isLinear) {\n $lockedTimeContainer.addClass('hidden');\n $minTimeContainer.removeClass('hidden');\n $maxTimeContainer.removeClass('hidden');\n } else {\n $lockedTimeContainer.addClass('hidden');\n $minTimeContainer.addClass('hidden');\n $maxTimeContainer.removeClass('hidden');\n }\n };\n\n //if the testpart changes it's navigation mode\n modelOverseer.on('testpart-change', function () {\n toggleTimeContainers();\n });\n\n toggleTimeContainers();\n\n //check if min <= maw\n $durationFields.on('change.check', function () {\n if (\n refModel.timeLimits.minTime > 0 &&\n refModel.timeLimits.maxTime > 0 &&\n refModel.timeLimits.minTime > refModel.timeLimits.maxTime\n ) {\n $minTimeField.parent('div').find('.duration-ctrl-wrapper').addClass('brd-danger');\n } else {\n $minTimeField.parent('div').find('.duration-ctrl-wrapper').removeClass('brd-danger');\n }\n });\n }\n\n /**\n * Set up the category property\n * @private\n * @param {jQueryElement} $view - the $view object containing the $select\n */\n function categoriesProperty($view) {\n var categorySelector = categorySelectorFactory($view),\n $categoryField = $view.find('[name=\"itemref-category\"]');\n\n categorySelector.createForm([], 'itemRef');\n categorySelector.updateFormState(refModel.categories);\n\n $view.on('propopen.propview', function () {\n categorySelector.updateFormState(refModel.categories);\n });\n\n categorySelector.on('category-change', function (selected) {\n // Let the binder update the model by going through the category hidden field\n $categoryField.val(selected.join(','));\n $categoryField.trigger('change');\n\n modelOverseer.trigger('category-change', selected);\n });\n }\n\n /**\n * Setup the weights properties\n */\n function weightsProperty(propView) {\n var $view = propView.getView(),\n $weightList = $view.find('[data-bind-each=\"weights\"]'),\n weightTpl = templates.properties.itemrefweight;\n\n $view.find('.itemref-weight-add').on('click', function (e) {\n var defaultData = {\n value: 1,\n 'qti-type': 'weight',\n identifier:\n refModel.weights.length === 0\n ? 'WEIGHT'\n : qtiTestHelper.getAvailableIdentifier(refModel, 'weight', 'WEIGHT')\n };\n e.preventDefault();\n\n $weightList.append(weightTpl(defaultData));\n refModel.weights.push(defaultData);\n $weightList.trigger('add.internalbinder'); // trigger model update\n\n $view.groupValidator();\n });\n }\n\n /**\n * Perform some binding once the property view is create\n * @private\n * @param {propView} propView - the view object\n */\n function propHandler(propView) {\n const removePropHandler = function removePropHandler(e, $deletedNode) {\n const validIds = [\n $itemRef.attr('id'),\n $itemRef.parents('.section').attr('id'),\n $itemRef.parents('.testpart').attr('id')\n ];\n const deletedNodeId = $deletedNode.attr('id');\n\n if (propView !== null && validIds.includes(deletedNodeId)) {\n propView.destroy();\n }\n };\n\n categoriesProperty(propView.getView());\n weightsProperty(propView);\n timeLimitsProperty(propView);\n\n $itemRef.parents('.testparts').on('deleted.deleter', removePropHandler);\n }\n }\n\n /**\n * Listen for state changes to enable/disable . Called globally.\n */\n function listenActionState() {\n $('.itemrefs').each(function () {\n actions.movable($('.itemref', $(this)), 'itemref', '.actions');\n });\n\n $(document)\n .on('delete', function (e) {\n var $parent;\n var $target = $(e.target);\n if ($target.hasClass('itemref')) {\n $parent = $target.parents('.itemrefs');\n }\n })\n .on('add change undo.deleter deleted.deleter', '.itemrefs', function (e) {\n var $parent;\n var $target = $(e.target);\n if ($target.hasClass('itemref') || $target.hasClass('itemrefs')) {\n $parent = $('.itemref', $target.hasClass('itemrefs') ? $target : $target.parents('.itemrefs'));\n actions.enable($parent, '.actions');\n actions.movable($parent, 'itemref', '.actions');\n }\n });\n }\n\n /**\n * The itemrefView setup itemref related components and behavior\n *\n * @exports taoQtiTest/controller/creator/views/itemref\n */\n return {\n setUp: setUp,\n listenActionState: listenActionState\n };\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2014 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);\n */\n\n/**\n * @author Bertrand Chevrier \n */\ndefine('taoQtiTest/controller/creator/encoders/dom2qti',[\n 'jquery',\n 'lodash',\n 'taoQtiTest/controller/creator/helpers/qtiElement',\n 'taoQtiTest/controller/creator/helpers/baseType',\n 'lib/dompurify/purify'\n], function ($, _, qtiElementHelper, baseType, DOMPurify) {\n 'use strict';\n\n /**\n * A mapping of QTI-XML node and attributes names in order to keep the camel case form\n * @type {Object}\n */\n const normalizedNodes = {\n feedbackblock: 'feedbackBlock',\n outcomeidentifier: 'outcomeIdentifier',\n showhide: 'showHide',\n printedvariable: 'printedVariable',\n powerform: 'powerForm',\n mappingindicator: 'mappingIndicator'\n };\n\n /**\n * Some Nodes have attributes that needs typing during decoding.\n * @type {Object}\n */\n const typedAttributes = {\n printedVariable: {\n identifier: baseType.getConstantByName('identifier'),\n powerForm: baseType.getConstantByName('boolean'),\n base: baseType.getConstantByName('intOrIdentifier'),\n index: baseType.getConstantByName('intOrIdentifier'),\n delimiter: baseType.getConstantByName('string'),\n field: baseType.getConstantByName('string'),\n mappingIndicator: baseType.getConstantByName('string')\n }\n };\n\n /**\n * Get the list of objects attributes to encode\n * @param {Object} object\n * @returns {Array}\n */\n function getAttributes(object) {\n return _.omit(object, [\n 'qti-type',\n 'content',\n 'xmlBase',\n 'lang',\n 'label'\n ]);\n }\n\n /**\n * Encode object's properties to xml/html string attributes\n * @param {Object} attributes\n * @returns {String}\n */\n function attrToStr(attributes) {\n return _.reduce(attributes, function (acc, value, key) {\n if (_.isNumber(value) || _.isBoolean(value) || (_.isString(value) && !_.isEmpty(value))) {\n return acc + ' ' + key + '=\"' + value + '\" ';\n }\n return acc;\n }, '');\n }\n\n /**\n * Ensures the nodeName has a normalized form:\n * - standard HTML tags are in lower case\n * - QTI-XML tags are in the right form\n * @param {String} nodeName\n * @returns {String}\n */\n function normalizeNodeName(nodeName) {\n const normalized = (nodeName) ? nodeName.toLocaleLowerCase() : '';\n return normalizedNodes[normalized] || normalized;\n }\n\n /**\n * This encoder is used to transform DOM to JSON QTI and JSON QTI to DOM.\n * It works now for the rubricBlocks components.\n * @exports creator/encoders/dom2qti\n */\n return {\n\n /**\n * Encode an object to a dom string\n * @param {Object} modelValue\n * @returns {String}\n */\n encode: function (modelValue) {\n let self = this,\n startTag;\n\n if (_.isArray(modelValue)) {\n return _.reduce(modelValue, function (result, value) {\n return result + self.encode(value);\n }, '');\n } else if (_.isObject(modelValue) && modelValue['qti-type']) {\n if (modelValue['qti-type'] === 'textRun') {\n return modelValue.content;\n }\n startTag = '<' + modelValue['qti-type'] + attrToStr(getAttributes(modelValue));\n if (modelValue.content) {\n return startTag + '>' + self.encode(modelValue.content) + '';\n } else {\n return startTag + '/>';\n }\n }\n return '' + modelValue;\n },\n\n /**\n * Decode a string that represents a DOM to a QTI formatted object\n * @param {String} nodeValue\n * @returns {Array}\n */\n decode: function (nodeValue) {\n const self = this;\n const $nodeValue = (nodeValue instanceof $) ? nodeValue : $(nodeValue);\n const result = [];\n let nodeName;\n\n _.forEach($nodeValue, function (elt) {\n let object;\n if (elt.nodeType === 3) {\n if (!_.isEmpty($.trim(elt.nodeValue))) {\n result.push(qtiElementHelper.create('textRun', {\n 'content': DOMPurify.sanitize(elt.nodeValue),\n 'xmlBase': ''\n }));\n }\n } else if (elt.nodeType === 1) {\n nodeName = normalizeNodeName(elt.nodeName);\n\n object = _.merge(qtiElementHelper.create(nodeName, {\n 'id': '',\n 'class': '',\n 'xmlBase': '',\n 'lang': '',\n 'label': ''\n }),\n _.transform(elt.attributes, function (acc, value) {\n const attrName = normalizeNodeName(value.nodeName);\n if (attrName) {\n if (typedAttributes[nodeName] && typedAttributes[nodeName][attrName]) {\n acc[attrName] = baseType.getValue(typedAttributes[nodeName][attrName], value.nodeValue);\n } else {\n acc[attrName] = value.nodeValue;\n }\n }\n }));\n if (elt.childNodes.length > 0) {\n object.content = self.decode(elt.childNodes);\n }\n result.push(object);\n }\n });\n return result;\n }\n };\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2017 (original work) Open Assessment Technologies SA;\n */\n/**\n * Instanciate a Wysiwyg editor to create QTI content.\n *\n * @author Christophe Noël \n */\ndefine('taoQtiTest/controller/creator/qtiContentCreator',[\n 'lodash',\n 'jquery',\n 'lib/uuid',\n 'taoQtiItem/qtiCreator/helper/commonRenderer',\n 'taoQtiItem/qtiCreator/editor/containerEditor'\n], function(_, $, uuid, qtiCommonRenderer, containerEditor) {\n 'use strict';\n\n return {\n create: function create(creatorContext, $container, options) {\n var self = this,\n editorId = uuid(),\n areaBroker = creatorContext.getAreaBroker(),\n modelOverseer = creatorContext.getModelOverseer();\n\n var removePlugins = [\n 'magicline',\n 'taoqtiimage',\n 'taoqtimedia',\n 'taoqtimaths',\n 'taoqtiinclude',\n 'taoqtitable',\n 'sharedspace' // That Ck instance still use floatingspace to position the toolbar, whereas the sharedspace plugin is used by the Item creator\n ].join(','),\n\n toolbar = [\n {\n name : 'basicstyles',\n items : ['Bold', 'Italic', 'Subscript', 'Superscript']\n }, {\n name : 'insert',\n items : ['SpecialChar', 'TaoQtiPrintedVariable']\n }, {\n name : 'links',\n items : ['Link']\n },\n '/',\n {\n name : 'styles',\n items : ['Format']\n }, {\n name : 'paragraph',\n items : ['NumberedList', 'BulletedList', '-', 'Blockquote', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock']\n }\n ];\n\n qtiCommonRenderer.setContext(areaBroker.getContentCreatorPanelArea());\n\n containerEditor.create($container, {\n areaBroker: areaBroker,\n removePlugins: removePlugins,\n toolbar: toolbar,\n metadata: {\n getOutcomes: function getOutcomes() {\n return modelOverseer.getOutcomesNames();\n }\n },\n change: options.change || _.noop,\n resetRenderer: true,\n autofocus: false\n });\n\n // destroying ckInstance on editor close\n creatorContext.on('creatorclose.' + editorId, function() {\n self.destroy(creatorContext, $container);\n });\n\n $container.data('editorId', editorId);\n },\n\n /**\n * @returns {Promise} - when editor is destroyed\n */\n destroy: function destroy(creatorContext, $container) {\n var editorId = $container.data('editorId');\n if (editorId) {\n creatorContext.off('.' + editorId);\n }\n return containerEditor.destroy($container);\n }\n };\n});\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2014 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);\n */\n\n/**\n * @author Bertrand Chevrier \n */\ndefine('taoQtiTest/controller/creator/views/rubricblock',[\n 'jquery',\n 'lodash',\n 'i18n',\n 'ui/hider',\n 'ui/dialog/alert',\n 'util/namespace',\n 'taoQtiTest/controller/creator/views/actions',\n 'helpers',\n 'taoQtiTest/controller/creator/encoders/dom2qti',\n 'taoQtiTest/controller/creator/helpers/qtiElement',\n 'taoQtiTest/controller/creator/qtiContentCreator',\n 'ckeditor',\n], function ($, _, __, hider, dialogAlert, namespaceHelper, actions, helpers, Dom2QtiEncoder, qtiElementHelper, qtiContentCreator) {\n 'use strict';\n\n /**\n * The rubriclockView setup RB related components and behavior\n *\n * @exports taoQtiTest/controller/creator/views/rubricblock\n */\n return {\n /**\n * Set up a rubric block: init action behaviors. Called for each one.\n *\n * @param {Object} creatorContext\n * @param {Object} rubricModel - the rubric block data\n * @param {jQueryElement} $rubricBlock - the rubric block to set up\n */\n setUp: function setUp(creatorContext, rubricModel, $rubricBlock) {\n var modelOverseer = creatorContext.getModelOverseer();\n var areaBroker = creatorContext.getAreaBroker();\n var $rubricBlockContent = $('.rubricblock-content', $rubricBlock);\n\n /**\n * Bind a listener only related to this rubric.\n * @param {jQuery} $el\n * @param {String} eventName\n * @param {Function} cb\n * @returns {jQuery}\n */\n function bindEvent($el, eventName, cb) {\n eventName = namespaceHelper.namespaceAll(eventName, rubricModel.uid);\n return $el.off(eventName).on(eventName, cb);\n }\n\n /**\n * Ensures an html content is wrapped by a container tag.\n * @param {String} html\n * @returns {String}\n */\n function ensureWrap(html) {\n html = (html || '').trim();\n if (html.charAt(0) !== '<' || html.charAt(html.length - 1) !== '>') {\n html = '
          ' + html + '
          ';\n }\n if ($(html).length > 1) {\n html = '
          ' + html + '
          ';\n }\n return html;\n }\n\n /**\n * Forwards the editor content into the model\n */\n function editorToModel(html) {\n var rubric = qtiElementHelper.lookupElement(rubricModel, 'rubricBlock', 'content');\n var wrapper = qtiElementHelper.lookupElement(rubricModel, 'rubricBlock.div.feedbackBlock', 'content');\n var content = Dom2QtiEncoder.decode(ensureWrap(html));\n\n if (wrapper) {\n wrapper.content = content;\n } else {\n rubric.content = content;\n }\n }\n\n /**\n * Forwards the model content into the editor\n */\n function modelToEditor() {\n var rubric = qtiElementHelper.lookupElement(rubricModel, 'rubricBlock', 'content') || {};\n var wrapper = qtiElementHelper.lookupElement(rubricModel, 'rubricBlock.div.feedbackBlock', 'content');\n var content = wrapper ? wrapper.content : rubric.content;\n var html = ensureWrap(Dom2QtiEncoder.encode(content));\n\n // Destroy any existing CKEditor instance\n qtiContentCreator.destroy(creatorContext, $rubricBlockContent).then(function() {\n // update the editor content\n $rubricBlockContent.html(html);\n\n // Re-create the Qti-ckEditor instance\n qtiContentCreator.create(creatorContext, $rubricBlockContent, {\n change: function change(editorContent) {\n editorToModel(editorContent);\n }\n });\n });\n }\n\n /**\n * Wrap/unwrap the rubric block in a feedback according to the user selection\n * @param {Object} feedback\n * @returns {Boolean}\n */\n function updateFeedback(feedback) {\n var activated = feedback && feedback.activated;\n var wrapper = qtiElementHelper.lookupElement(rubricModel, 'rubricBlock.div.feedbackBlock', 'content');\n\n if (activated) {\n // wrap the actual content into a feedbackBlock if needed\n if (!wrapper) {\n rubricModel.content = [qtiElementHelper.create('div', {\n content: [qtiElementHelper.create('feedbackBlock', {\n outcomeIdentifier: feedback.outcome,\n identifier: feedback.matchValue,\n content: rubricModel.content\n })]\n })];\n } else {\n wrapper.outcomeIdentifier = feedback.outcome;\n wrapper.identifier = feedback.matchValue;\n }\n modelToEditor();\n } else {\n // remove the feedbackBlock wrapper, just keep the actual content\n if (wrapper) {\n rubricModel.content = wrapper.content;\n modelToEditor();\n }\n }\n\n return activated;\n }\n\n /**\n * Perform some binding once the property view is created\n * @private\n * @param {propView} propView - the view object\n */\n function propHandler(propView) {\n var $view = propView.getView();\n var $feedbackOutcomeLine = $('.rubric-feedback-outcome', $view);\n var $feedbackMatchLine = $('.rubric-feedback-match-value', $view);\n var $feedbackOutcome = $('[name=feedback-outcome]', $view);\n var $feedbackActivated = $('[name=activated]', $view);\n\n // toggle the feedback panel\n function changeFeedback(activated) {\n hider.toggle($feedbackOutcomeLine, activated);\n hider.toggle($feedbackMatchLine, activated);\n }\n\n // should be called when the properties panel is removed\n function removePropHandler() {\n rubricModel.feedback = {};\n if (propView !== null) {\n propView.destroy();\n }\n }\n\n // take care of changes in the properties view\n function changeHandler(e, changedModel) {\n if (e.namespace === 'binder' && changedModel['qti-type'] === 'rubricBlock') {\n changeFeedback(updateFeedback(changedModel.feedback));\n }\n }\n\n // update the list of outcomes the feedback can target\n function updateOutcomes() {\n var activated = rubricModel.feedback && rubricModel.feedback.activated;\n // build the list of outcomes in a way select2 can understand\n var outcomes = _.map(modelOverseer.getOutcomesNames(), function(name) {\n return {\n id: name,\n text: name\n };\n });\n\n // create/update the select field\n $feedbackOutcome.select2({\n minimumResultsForSearch: -1,\n width: '100%',\n data: outcomes\n });\n\n // update the UI to reflect the data\n if (!activated) {\n $feedbackActivated.prop('checked', false);\n }\n changeFeedback(activated);\n }\n\n $('[name=type]', $view).select2({\n minimumResultsForSearch: -1,\n width: '100%'\n });\n\n $view.on('change.binder', changeHandler);\n bindEvent($rubricBlock.parents('.testpart'), 'delete', removePropHandler);\n bindEvent($rubricBlock.parents('.section'), 'delete', removePropHandler);\n bindEvent($rubricBlock, 'delete', removePropHandler);\n bindEvent($rubricBlock, 'outcome-removed', function() {\n $feedbackOutcome.val('');\n updateOutcomes();\n });\n bindEvent($rubricBlock, 'outcome-updated', function() {\n updateFeedback(rubricModel.feedback);\n updateOutcomes();\n });\n\n changeFeedback(rubricModel.feedback);\n updateOutcomes();\n rbViews($view);\n }\n\n /**\n * Set up the views select box\n * @private\n * @param {jQueryElement} $propContainer - the element container\n */\n function rbViews($propContainer) {\n var $select = $('[name=view]', $propContainer);\n\n bindEvent($select.select2({'width': '100%'}), \"select2-removed\", function () {\n if ($select.select2('val').length === 0) {\n $select.select2('val', [1]);\n }\n });\n\n if ($select.select2('val').length === 0) {\n $select.select2('val', [1]);\n }\n }\n\n rubricModel.orderIndex = (rubricModel.index || 0) + 1;\n rubricModel.uid = _.uniqueId('rb');\n rubricModel.feedback = {\n activated: !!qtiElementHelper.lookupElement(rubricModel, 'rubricBlock.div.feedbackBlock', 'content'),\n outcome: qtiElementHelper.lookupProperty(rubricModel, 'rubricBlock.div.feedbackBlock.outcomeIdentifier', 'content'),\n matchValue: qtiElementHelper.lookupProperty(rubricModel, 'rubricBlock.div.feedbackBlock.identifier', 'content')\n };\n\n modelOverseer\n .before('scoring-write.' + rubricModel.uid, function() {\n var feedbackOutcome = rubricModel.feedback && rubricModel.feedback.outcome;\n if (feedbackOutcome && _.indexOf(modelOverseer.getOutcomesNames(), feedbackOutcome) < 0) {\n // the targeted outcome has been removed, so remove the feedback\n modelOverseer.changedRubricBlock = (modelOverseer.changedRubricBlock || 0) + 1;\n rubricModel.feedback.activated = false;\n rubricModel.feedback.outcome = '';\n updateFeedback(rubricModel.feedback);\n $rubricBlock.trigger('outcome-removed');\n } else {\n // the tageted outcome is still here, just notify the properties panel to update the list\n $rubricBlock.trigger('outcome-updated');\n }\n })\n .on('scoring-write.' + rubricModel.uid, function() {\n // will notify the user of any removed feedbacks\n if (modelOverseer.changedRubricBlock) {\n /** @todo: provide a way to cancel changes */\n dialogAlert(__('Some rubric blocks have been updated to reflect the changes in the list of outcomes.'));\n modelOverseer.changedRubricBlock = 0;\n }\n });\n\n actions.properties($rubricBlock, 'rubricblock', rubricModel, propHandler);\n\n modelToEditor();\n\n // destroy CK instance on rubric bloc deletion.\n // todo: find a way to destroy CK upon destroying rubric bloc parent section/part\n bindEvent($rubricBlock, 'delete', function() {\n qtiContentCreator.destroy(creatorContext, $rubricBlockContent);\n });\n\n $rubricBlockContent.on('editorfocus', function() {\n // close all properties forms and turn off their related button\n areaBroker.getPropertyPanelArea().children('.props').hide().trigger('propclose.propview');\n });\n\n //change position of CKeditor toolbar on scroll\n areaBroker.getContentCreatorPanelArea().find('.test-content').on('scroll', function () {\n CKEDITOR.document.getWindow().fire('scroll');\n });\n }\n };\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2022 (original work) Open Assessment Technologies SA ;\n */\n\n/**\n * Helper for iterating on nested collections within a test model.\n * It's recommended to use a validator on the model before calling these functions.\n *\n * @example\nconst testModel = {\n 'qti-type': 'test',\n testParts: [{\n 'qti-type': 'testPart',\n identifier: 'testPart-1',\n assessmentSections: [{\n 'qti-type': 'assessmentSection',\n identifier: 'assessmentSection-1',\n sectionParts: [{\n 'qti-type': 'assessmentSection',\n identifier: 'subsection-1',\n sectionParts: [{\n 'qti-type': 'assessmentItemRef',\n identifier: 'item-1',\n categories: ['math', 'history']\n }]\n }]\n }]\n }]\n};\neachItemInTest(testModel, itemRef => {\n console.log(itemRef.categories);\n});\n */\ndefine('taoQtiTest/controller/creator/helpers/testModel',[\n 'lodash',\n 'core/errorHandler'\n], function (_, errorHandler) {\n 'use strict';\n\n const _ns = '.testModel';\n\n /**\n * Calls a function for each itemRef in the test model. Handles nested subsections.\n * @param {Object} testModel\n * @param {Function} cb - takes itemRef as only param\n */\n function eachItemInTest(testModel, cb) {\n _.forEach(testModel.testParts, testPartModel => {\n eachItemInTestPart(testPartModel, cb);\n });\n }\n\n /**\n * Calls a function for each itemRef in the testPart model. Handles nested subsections.\n * @param {Object} testPartModel\n * @param {Function} cb - takes itemRef as only param\n */\n function eachItemInTestPart(testPartModel, cb) {\n _.forEach(testPartModel.assessmentSections, sectionModel => {\n eachItemInSection(sectionModel, cb);\n });\n }\n\n /**\n * Calls a function for each itemRef in the section model. Handles nested subsections.\n * @param {Object} sectionModel\n * @param {Function} cb - takes itemRef as only param\n */\n function eachItemInSection(sectionModel, cb) {\n _.forEach(sectionModel.sectionParts, sectionPartModel => {\n // could be item, could be subsection\n if (sectionPartModel['qti-type'] === 'assessmentSection') {\n // recursion to handle any amount of subsection levels\n eachItemInSection(sectionPartModel, cb);\n } else if (sectionPartModel['qti-type'] === 'assessmentItemRef') {\n const itemRef = sectionPartModel;\n if (typeof cb === 'function') {\n cb(itemRef);\n } else {\n errorHandler.throw(_ns, 'cb must be a function');\n }\n }\n });\n }\n\n return {\n eachItemInTest,\n eachItemInTestPart,\n eachItemInSection\n };\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2022 (original work) Open Assessment Technologies SA;\n */\ndefine('taoQtiTest/controller/creator/helpers/testPartCategory',[\n 'lodash',\n 'i18n',\n 'taoQtiTest/controller/creator/helpers/sectionCategory',\n 'taoQtiTest/controller/creator/helpers/testModel',\n 'core/errorHandler'\n], function(_, __, sectionCategory, testModelHelper, errorHandler) {\n 'use strict';\n\n const _ns = '.testPartCategory';\n\n /**\n * Check if the given object is a valid testPart model object\n *\n * @param {object} model\n * @returns {boolean}\n */\n function isValidTestPartModel(model) {\n return _.isObject(model)\n && model['qti-type'] === 'testPart'\n && _.isArray(model.assessmentSections)\n && model.assessmentSections.every(section => sectionCategory.isValidSectionModel(section));\n }\n\n /**\n * Set an array of categories to the testPart model (affects the children itemRef, and after propagation, the section models)\n *\n * @param {object} model\n * @param {array} selected - all categories active for the whole testPart\n * @param {array} partial - only categories in an indeterminate state\n * @returns {undefined}\n */\n function setCategories(model, selected, partial = []) {\n\n const currentCategories = getCategories(model);\n\n // partial = partial || [];\n\n //the categories that are no longer in the new list of categories should be removed\n const toRemove = _.difference(currentCategories.all, selected.concat(partial));\n\n //the categories that are not in the current categories collection should be added to the children\n const toAdd = _.difference(selected, currentCategories.propagated);\n\n model.categories = _.difference(model.categories, toRemove);\n model.categories = model.categories.concat(toAdd);\n\n //process the modification\n addCategories(model, toAdd);\n removeCategories(model, toRemove);\n }\n\n /**\n * @typedef {object} CategoriesSummary\n * @property {string[]} all - array of all categories of itemRef descendents\n * @property {string[]} propagated - array of categories propagated to every itemRef descendent\n * @property {string[]} partial - array of categories propagated to a partial set of itemRef descendents\n */\n\n /**\n * Get the categories assigned to the testPart model, inferred by its internal itemRefs\n *\n * @param {object} model\n * @returns {CategoriesSummary}\n */\n function getCategories(model) {\n if (!isValidTestPartModel(model)) {\n return errorHandler.throw(_ns, 'invalid tool config format');\n }\n\n let itemCount = 0;\n\n /**\n * List of lists of categories of each itemRef in the testPart\n * @type {string[][]}\n */\n const itemRefCategories = [];\n testModelHelper.eachItemInTestPart(model, itemRef => {\n if (++itemCount && _.isArray(itemRef.categories)) {\n itemRefCategories.push(_.compact(itemRef.categories));\n }\n });\n\n if (!itemCount) {\n return createCategories(model.categories, model.categories);\n }\n\n //all item categories\n const union = _.union.apply(null, itemRefCategories);\n //categories that are common to all itemRefs\n const propagated = _.intersection.apply(null, itemRefCategories);\n //the categories that are only partially covered on the section level : complementary of \"propagated\"\n const partial = _.difference(union, propagated);\n\n return createCategories(union, propagated, partial);\n }\n\n /**\n * Add an array of categories to a testPart model (affects the children itemRef, and after propagation, the section models)\n *\n * @param {object} model\n * @param {array} categories\n * @returns {undefined}\n */\n function addCategories(model, categories) {\n if (isValidTestPartModel(model)) {\n testModelHelper.eachItemInTestPart(model, itemRef => {\n if (!_.isArray(itemRef.categories)) {\n itemRef.categories = [];\n }\n itemRef.categories = _.union(itemRef.categories, categories);\n });\n } else {\n errorHandler.throw(_ns, 'invalid tool config format');\n }\n }\n\n /**\n * Remove an array of categories from a testPart model (affects the children itemRef, and after propagation, the section models)\n *\n * @param {object} model\n * @param {array} categories\n * @returns {undefined}\n */\n function removeCategories(model, categories) {\n if (isValidTestPartModel(model)) {\n testModelHelper.eachItemInTestPart(model, itemRef => {\n if (_.isArray(itemRef.categories)) {\n itemRef.categories = _.difference(itemRef.categories, categories);\n }\n });\n } else {\n errorHandler.throw(_ns, 'invalid tool config format');\n }\n }\n\n /**\n * Assigns input category arrays to output object, while sorting each one\n * @param {string[]} all\n * @param {string[]} propagated\n * @param {string[]} partial\n * @returns {CategoriesSummary}\n */\n function createCategories(all = [], propagated = [], partial = []) {\n return _.mapValues({\n all: all,\n propagated: propagated,\n partial: partial\n }, function(categories) {\n return categories.sort();\n });\n }\n\n return {\n isValidTestPartModel : isValidTestPartModel,\n setCategories : setCategories,\n getCategories : getCategories,\n addCategories : addCategories,\n removeCategories : removeCategories\n };\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2016 (original work) Open Assessment Technologies SA;\n */\ndefine('taoQtiTest/controller/creator/helpers/sectionBlueprints',[\n 'lodash',\n 'i18n',\n 'core/errorHandler'\n], function (_, __, errorHandler){\n\n 'use strict';\n\n var _ns = '.sectionBlueprint';\n\n\n /**\n * Set an array of categories to the section model (affect the childen itemRef)\n *\n * @param {object} model\n * @param {string} blueprint\n * @returns {undefined}\n */\n function setBlueprint(model, blueprint){\n model.blueprint = blueprint;\n }\n\n /**\n * Get the categories assign to the section model, infered by its interal itemRefs\n *\n * @param {string} getUrl\n * @param {object} model\n * @returns {object}\n */\n function getBlueprint(getUrl, model){\n\n return $.ajax({\n url: getUrl,\n type: 'GET',\n data: {\n section: model.identifier\n },\n dataType: 'json'\n\n })\n .fail(function () {\n errorHandler.throw(_ns, 'invalid tool config format');\n });\n\n }\n\n return {\n setBlueprint : setBlueprint,\n getBlueprint : getBlueprint\n };\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2021-2022 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);\n */\n\ndefine('taoQtiTest/controller/creator/views/subsection',[\n 'jquery',\n 'lodash',\n 'uri',\n 'i18n',\n 'taoQtiTest/controller/creator/config/defaults',\n 'taoQtiTest/controller/creator/views/actions',\n 'taoQtiTest/controller/creator/views/itemref',\n 'taoQtiTest/controller/creator/views/rubricblock',\n 'taoQtiTest/controller/creator/templates/index',\n 'taoQtiTest/controller/creator/helpers/qtiTest',\n 'taoQtiTest/controller/creator/helpers/categorySelector',\n 'taoQtiTest/controller/creator/helpers/sectionCategory',\n 'taoQtiTest/controller/creator/helpers/sectionBlueprints',\n 'ui/dialog/confirm',\n 'taoQtiTest/controller/creator/helpers/subsection',\n 'taoQtiTest/controller/creator/helpers/validators',\n 'services/features'\n], function (\n $,\n _,\n uri,\n __,\n defaults,\n actions,\n itemRefView,\n rubricBlockView,\n templates,\n qtiTestHelper,\n categorySelectorFactory,\n sectionCategory,\n sectionBlueprint,\n confirmDialog,\n subsectionsHelper,\n validators,\n servicesFeatures\n) {\n 'use strict';\n /**\n * Set up a section: init action behaviors. Called for each section.\n *\n * @param {Object} creatorContext\n * @param {Object} subsectionModel - the data model to bind to the test section\n * @param {Object} sectionModel - the parent data model to inherit\n * @param {jQuery} $subsection - the subsection to set up\n */\n function setUp(creatorContext, subsectionModel, sectionModel, $subsection) {\n const defaultsConfigs = defaults();\n // select elements for subsection, to avoid selecting the same elements in nested subsections\n const $itemRefsWrapper = $subsection.children('.itemrefs-wrapper');\n const $rubBlocks = $subsection.children('.rublocks');\n const $titleWithActions = $subsection.children('h2');\n\n const modelOverseer = creatorContext.getModelOverseer();\n const config = modelOverseer.getConfig();\n\n // set item session control to use test part options if section level isn't set\n if (!subsectionModel.itemSessionControl) {\n subsectionModel.itemSessionControl = {};\n }\n if (!subsectionModel.categories) {\n subsectionModel.categories = defaultsConfigs.categories;\n }\n _.defaults(subsectionModel.itemSessionControl, sectionModel.itemSessionControl);\n\n if (!_.isEmpty(config.routes.blueprintsById)) {\n subsectionModel.hasBlueprint = true;\n }\n\n sectionModel.hasSelectionWithReplacement = servicesFeatures.isVisible(\n 'taoQtiTest/creator/properties/selectionWithReplacement',\n false\n );\n\n actions.properties($titleWithActions, 'section', subsectionModel, propHandler);\n actions.move($titleWithActions, 'subsections', 'subsection');\n actions.displayItemWrapper($subsection);\n actions.updateDeleteSelector($titleWithActions);\n\n subsections();\n itemRefs();\n acceptItemRefs();\n rubricBlocks();\n addRubricBlock();\n\n if (subsectionsHelper.isNestedSubsection($subsection)) {\n // prevent adding a third subsection level\n $('.add-subsection', $subsection).hide();\n $('.add-subsection + .tlb-separator', $subsection).hide();\n } else {\n addSubsection();\n }\n\n /**\n * Perform some binding once the property view is create\n * @param {propView} propView - the view object\n */\n function propHandler(propView) {\n const $view = propView.getView();\n\n //enable/disable selection\n const $selectionSwitcher = $('[name=section-enable-selection]', $view);\n const $selectionSelect = $('[name=section-select]', $view);\n const $selectionWithRep = $('[name=section-with-replacement]', $view);\n\n // subsectionModel.selection will be filled by binded values from template section-props.tpl\n // if subsectionModel.selection from server response it has 'qti-type'\n const isSelectionFromServer = !!(subsectionModel.selection && subsectionModel.selection['qti-type']);\n\n const switchSelection = function switchSelection() {\n if ($selectionSwitcher.prop('checked') === true) {\n $selectionSelect.incrementer('enable');\n $selectionWithRep.removeClass('disabled');\n } else {\n $selectionSelect.incrementer('disable');\n $selectionWithRep.addClass('disabled');\n }\n };\n $selectionSwitcher.on('change', switchSelection);\n $selectionSwitcher.on('change', function updateModel() {\n if (!$selectionSwitcher.prop('checked')) {\n $selectionSelect.val(0);\n $selectionWithRep.prop('checked', false);\n delete subsectionModel.selection;\n }\n });\n\n $selectionSwitcher.prop('checked', isSelectionFromServer).trigger('change');\n\n //listen for databinder change to update the test part title\n const $title = $('[data-bind=title]', $titleWithActions);\n $view.on('change.binder', function (e) {\n if (e.namespace === 'binder' && subsectionModel['qti-type'] === 'assessmentSection') {\n $title.text(subsectionModel.title);\n }\n });\n\n // deleted.deleter event fires only on the parent nodes (testparts, sections, etc)\n // Since it \"bubles\" we can subsctibe only to the highest parent node\n $subsection.parents('.testparts').on('deleted.deleter', removePropHandler);\n\n //section level category configuration\n categoriesProperty($view);\n\n if (typeof subsectionModel.hasBlueprint !== 'undefined') {\n blueprintProperty($view);\n }\n\n actions.displayCategoryPresets($subsection);\n\n function removePropHandler(e, $deletedNode) {\n const validIds = [$subsection.parents('.testpart').attr('id'), $subsection.attr('id')];\n\n const deletedNodeId = $deletedNode.attr('id');\n // We have to check id of a deleted node, because\n // 1. Event fires after child node was deleted, but e.stopPropagation doesn't help\n // because currentTarget is always document\n // 2. We have to subscribe to the parent node and it's posiible that another section was removed even from another testpart\n // Subscription to the .sections selector event won't help because sections element might contain several children.\n\n if (propView !== null && validIds.includes(deletedNodeId)) {\n propView.destroy();\n }\n }\n }\n /**\n * Set up subsections that already belongs to the section\n * @private\n */\n function subsections() {\n if (!subsectionModel.sectionParts) {\n subsectionModel.sectionParts = [];\n }\n\n subsectionsHelper.getSubsections($subsection).each(function () {\n const $sub2section = $(this);\n const index = $sub2section.data('bind-index');\n if (!subsectionModel.sectionParts[index]) {\n subsectionModel.sectionParts[index] = {};\n }\n\n setUp(creatorContext, subsectionModel.sectionParts[index], subsectionModel, $sub2section);\n });\n }\n /**\n * Set up the item refs that already belongs to the section\n * @private\n */\n function itemRefs() {\n if (!subsectionModel.sectionParts) {\n subsectionModel.sectionParts = [];\n }\n $('.itemref', $itemRefsWrapper).each(function () {\n const $itemRef = $(this);\n const index = $itemRef.data('bind-index');\n if (!subsectionModel.sectionParts[index]) {\n subsectionModel.sectionParts[index] = {};\n }\n\n itemRefView.setUp(\n creatorContext,\n subsectionModel.sectionParts[index],\n subsectionModel,\n sectionModel,\n $itemRef\n );\n $itemRef.find('.title').text(config.labels[uri.encode($itemRef.data('uri'))]);\n });\n }\n\n /**\n * Make the section to accept the selected items\n * @private\n * @fires modelOverseer#item-add\n */\n function acceptItemRefs() {\n const $itemsPanel = $('.test-creator-items .item-selection');\n\n //the item selector trigger a select event\n $itemsPanel.on('itemselect.creator', function (e, selection) {\n const $placeholder = $('.itemref-placeholder', $itemRefsWrapper);\n const $placeholders = $('.itemref-placeholder');\n\n if (_.size(selection) > 0) {\n $placeholder\n .show()\n .off('click')\n .on('click', function () {\n //prepare the item data\n const defaultItemData = {};\n\n if (\n subsectionModel.itemSessionControl &&\n !_.isUndefined(subsectionModel.itemSessionControl.maxAttempts)\n ) {\n //for a matter of consistency, the itemRef will \"inherit\" the itemSessionControl configuration from its parent section\n defaultItemData.itemSessionControl = _.clone(subsectionModel.itemSessionControl);\n }\n\n //the itemRef should also \"inherit\" default categories set at the item level\n defaultItemData.categories = _.clone(defaultsConfigs.categories) || [];\n\n _.forEach(selection, function (item) {\n const itemData = _.defaults(\n {\n href: item.uri,\n label: item.label,\n 'qti-type': 'assessmentItemRef'\n },\n defaultItemData\n );\n\n if (_.isArray(item.categories)) {\n itemData.categories = item.categories.concat(itemData.categories);\n }\n\n addItemRef($('.itemrefs', $itemRefsWrapper), null, itemData);\n });\n\n $itemsPanel.trigger('itemselected.creator');\n\n $placeholders.hide().off('click');\n });\n } else {\n $placeholders.hide().off('click');\n }\n });\n\n //we listen the event not from the adder but from the data binder to be sure the model is up to date\n // jquesry issue to select id with dot by '#ab.cd', should be used [id=\"ab.cd\"]\n $(document)\n .off('add.binder', `[id=\"${$subsection.attr('id')}\"] > .itemrefs-wrapper .itemrefs`)\n .on(\n 'add.binder',\n `[id=\"${$subsection.attr('id')}\"] > .itemrefs-wrapper .itemrefs`,\n function (e, $itemRef) {\n if (\n e.namespace === 'binder' &&\n $itemRef.hasClass('itemref') &&\n $itemRef.closest('.subsection').attr('id') === $subsection.attr('id')\n ) {\n const index = $itemRef.data('bind-index');\n const itemRefModel = subsectionModel.sectionParts[index];\n\n //initialize the new item ref\n itemRefView.setUp(creatorContext, itemRefModel, subsectionModel, sectionModel, $itemRef);\n\n /**\n * @event modelOverseer#item-add\n * @param {Object} itemRefModel\n */\n modelOverseer.trigger('item-add', itemRefModel);\n }\n }\n );\n }\n\n /**\n * Add a new item ref to the section\n * @param {jQuery} $refList - the element to add the item to\n * @param {Number} [index] - the position of the item to add\n * @param {Object} [itemData] - the data to bind to the new item ref\n */\n function addItemRef($refList, index, itemData) {\n const $items = $refList.children('li');\n index = index || $items.length;\n itemData.identifier = qtiTestHelper.getAvailableIdentifier(\n modelOverseer.getModel(),\n 'assessmentItemRef',\n 'item'\n );\n itemData.index = index + 1;\n const $itemRef = $(templates.itemref(itemData));\n if (index > 0) {\n $itemRef.insertAfter($items.eq(index - 1));\n } else {\n $itemRef.appendTo($refList);\n }\n $refList.trigger('add', [$itemRef, itemData]);\n }\n\n /**\n * Set up the rubric blocks that already belongs to the section\n * @private\n */\n function rubricBlocks() {\n if (!subsectionModel.rubricBlocks) {\n subsectionModel.rubricBlocks = [];\n }\n $('.rubricblock', $rubBlocks).each(function () {\n const $rubricBlock = $(this);\n const index = $rubricBlock.data('bind-index');\n if (!subsectionModel.rubricBlocks[index]) {\n subsectionModel.rubricBlocks[index] = {};\n }\n\n rubricBlockView.setUp(creatorContext, subsectionModel.rubricBlocks[index], $rubricBlock);\n });\n\n //opens the rubric blocks section if they are there.\n if (subsectionModel.rubricBlocks.length > 0) {\n $('.rub-toggler', $titleWithActions).trigger('click');\n }\n }\n\n /**\n * Enable to add new rubric block\n * @private\n * @fires modelOverseer#rubric-add\n */\n function addRubricBlock() {\n $('.rublock-adder', $rubBlocks).adder({\n target: $('.rubricblocks', $rubBlocks),\n content: templates.rubricblock,\n templateData: function (cb) {\n cb({\n 'qti-type': 'rubricBlock',\n index: $('.rubricblock', $rubBlocks).length,\n content: [],\n views: [1]\n });\n }\n });\n\n //we listen the event not from the adder but from the data binder to be sure the model is up to date\n // jquesry issue to select id with dot by '#ab.cd', should be used [id=\"ab.cd\"]\n $(document)\n .off('add.binder', `[id=\"${$subsection.attr('id')}\"] > .rublocks .rubricblocks`)\n .on(\n 'add.binder',\n `[id=\"${$subsection.attr('id')}\"] > .rublocks .rubricblocks`,\n function (e, $rubricBlock) {\n if (\n e.namespace === 'binder' &&\n $rubricBlock.hasClass('rubricblock') &&\n $rubricBlock.closest('.subsection').attr('id') === $subsection.attr('id')\n ) {\n const index = $rubricBlock.data('bind-index');\n const rubricModel = subsectionModel.rubricBlocks[index] || {};\n\n $('.rubricblock-binding', $rubricBlock).html('

           

          ');\n rubricBlockView.setUp(creatorContext, rubricModel, $rubricBlock);\n\n /**\n * @event modelOverseer#rubric-add\n * @param {Object} rubricModel\n */\n modelOverseer.trigger('rubric-add', rubricModel);\n }\n }\n );\n }\n\n /**\n * Set up the category property\n * @private\n * @param {jQuery} $view - the $view object containing the $select\n * @fires modelOverseer#category-change\n */\n function categoriesProperty($view) {\n const categories = sectionCategory.getCategories(subsectionModel),\n categorySelector = categorySelectorFactory($view);\n\n categorySelector.createForm(categories.all);\n updateFormState(categorySelector);\n\n $view.on('propopen.propview', function () {\n updateFormState(categorySelector);\n });\n\n $view.on('set-default-categories', function () {\n subsectionModel.categories = defaultsConfigs.categories;\n updateFormState(categorySelector);\n });\n\n categorySelector.on('category-change', function (selected, indeterminate) {\n sectionCategory.setCategories(subsectionModel, selected, indeterminate);\n\n modelOverseer.trigger('category-change');\n });\n }\n\n function updateFormState(categorySelector) {\n const categories = sectionCategory.getCategories(subsectionModel);\n categorySelector.updateFormState(categories.propagated, categories.partial);\n }\n\n /**\n * Set up the Blueprint property\n * @private\n * @param {jQuery} $view - the $view object containing the $select\n */\n function blueprintProperty($view) {\n const $select = $('[name=section-blueprint]', $view);\n $select\n .select2({\n ajax: {\n url: config.routes.blueprintsById,\n dataType: 'json',\n delay: 350,\n method: 'POST',\n data: function (params) {\n return {\n identifier: params // search term\n };\n },\n results: function (data) {\n return data;\n }\n },\n minimumInputLength: 3,\n width: '100%',\n multiple: false,\n allowClear: true,\n placeholder: __('Select a blueprint'),\n formatNoMatches: function () {\n return __('Enter a blueprint');\n },\n maximumInputLength: 32\n })\n .on('change', function (e) {\n setBlueprint(e.val);\n });\n\n initBlueprint();\n $view.on('propopen.propview', function () {\n initBlueprint();\n });\n\n /**\n * Start the blueprint editing\n * @private\n */\n function initBlueprint() {\n if (typeof subsectionModel.blueprint === 'undefined') {\n sectionBlueprint\n .getBlueprint(config.routes.blueprintByTestSection, subsectionModel)\n .success(function (data) {\n if (!_.isEmpty(data)) {\n if (subsectionModel.blueprint !== '') {\n subsectionModel.blueprint = data.uri;\n $select.select2('data', { id: data.uri, text: data.text });\n $select.trigger('change');\n }\n }\n });\n }\n }\n\n /**\n * save the categories into the model\n * @param {Object} blueprint\n * @private\n */\n function setBlueprint(blueprint) {\n sectionBlueprint.setBlueprint(subsectionModel, blueprint);\n }\n }\n\n function addSubsection() {\n const optionsConfirmDialog = {\n buttons: {\n labels: {\n ok: __('Yes'),\n cancel: __('No')\n }\n }\n };\n\n $('.add-subsection', $titleWithActions).adder({\n target: $subsection.children('.subsections'),\n content: templates.subsection,\n templateData: function (cb) {\n //create a new subsection model object to be bound to the template\n let sectionParts = [];\n if ($subsection.data('movedItems')) {\n sectionParts = $subsection.data('movedItems');\n $subsection.removeData('movedItems');\n }\n cb({\n 'qti-type': 'assessmentSection',\n identifier: qtiTestHelper.getAvailableIdentifier(\n modelOverseer.getModel(),\n 'assessmentSection',\n 'subsection'\n ),\n title: defaultsConfigs.sectionTitlePrefix,\n index: 0,\n sectionParts,\n visible: true,\n itemSessionControl: {\n maxAttempts: defaultsConfigs.maxAttempts\n }\n });\n },\n checkAndCallAdd: function (executeAdd) {\n if (\n subsectionModel.sectionParts[0] &&\n qtiTestHelper.filterQtiType(subsectionModel.sectionParts[0], 'assessmentItemRef')\n ) {\n // subsection has item(s)\n const subsectionIndex = subsectionsHelper.getSubsectionTitleIndex($subsection);\n const confirmMessage = __(\n 'The items contained in %s %s will be moved into the new %s %s. Do you wish to proceed?',\n subsectionIndex,\n subsectionModel.title,\n `${subsectionIndex}1.`,\n defaultsConfigs.sectionTitlePrefix\n );\n const acceptFunction = () => {\n // trigger deleted event for each itemfer to run removePropHandler and remove propView\n $('.itemrefs .itemref', $itemRefsWrapper).each(function () {\n $subsection.parents('.testparts').trigger('deleted.deleter', [$(this)]);\n });\n setTimeout(() => {\n // remove all itemrefs\n $('.itemrefs', $itemRefsWrapper).empty();\n // check itemrefs identifiers\n // because validation is build on \n // and each item should have valid and unique id\n subsectionModel.sectionParts.forEach(itemRef => {\n if (!validators.checkIfItemIdValid(itemRef.identifier, modelOverseer)) {\n itemRef.identifier = qtiTestHelper.getAvailableIdentifier(\n modelOverseer.getModel(),\n 'assessmentItemRef',\n 'item'\n );\n }\n });\n $subsection.data('movedItems', _.clone(subsectionModel.sectionParts));\n subsectionModel.sectionParts = [];\n executeAdd();\n }, 0);\n };\n confirmDialog(confirmMessage, acceptFunction, () => {}, optionsConfirmDialog)\n .getDom()\n .find('.buttons')\n .css('display', 'flex')\n .css('flex-direction', 'row-reverse');\n } else {\n if (!subsectionModel.sectionParts.length && subsectionModel.categories.length) {\n $subsection.data('movedCategories', _.clone(subsectionModel.categories));\n }\n executeAdd();\n }\n }\n });\n\n //we listen the event not from the adder but from the data binder to be sure the model is up to date\n // jquesry issue to select id with dot by '#ab.cd', should be used [id=\"ab.cd\"]\n $(document)\n .off('add.binder', `[id=\"${$subsection.attr('id')}\"] > .subsections`)\n .on('add.binder', `[id=\"${$subsection.attr('id')}\"] > .subsections`, function (e, $sub2section) {\n if (\n e.namespace === 'binder' &&\n $sub2section.hasClass('subsection') &&\n $sub2section.parents('.subsection').length\n ) {\n // second level of subsection){\n const sub2sectionIndex = $sub2section.data('bind-index');\n const sub2sectionModel = subsectionModel.sectionParts[sub2sectionIndex];\n\n if ($subsection.data('movedCategories')) {\n sub2sectionModel.categories = $subsection.data('movedCategories');\n $subsection.removeData('movedCategories');\n }\n\n // initialize the new subsection\n setUp(creatorContext, sub2sectionModel, subsectionModel, $sub2section);\n // hide 'Items' block and category-presets for current subsection\n actions.displayItemWrapper($subsection);\n actions.displayCategoryPresets($subsection);\n // set index for new subsection\n actions.updateTitleIndex($sub2section);\n\n /**\n * @event modelOverseer#section-add\n * @param {Object} sub2sectionModel\n */\n modelOverseer.trigger('section-add', sub2sectionModel);\n }\n });\n }\n }\n\n /**\n * Listen for state changes to enable/disable . Called globally.\n */\n function listenActionState() {\n $('.subsections').each(function () {\n const $subsections = $('.subsection', $(this));\n\n actions.removable($subsections, 'h2');\n actions.movable($subsections, 'subsection', 'h2');\n actions.updateTitleIndex($subsections);\n });\n\n $(document)\n .on('delete', function (e) {\n const $target = $(e.target);\n if ($target.hasClass('subsection')) {\n const $parent = subsectionsHelper.getParent($target);\n setTimeout(() => {\n actions.displayItemWrapper($parent);\n actions.displayCategoryPresets($parent);\n // element detached after event\n const $subsections = $('.subsection', $parent);\n actions.updateTitleIndex($subsections);\n }, 100);\n }\n })\n .on('add change undo.deleter deleted.deleter', function (e) {\n const $target = $(e.target);\n if ($target.hasClass('subsection') || $target.hasClass('subsections')) {\n const $subsections = subsectionsHelper.getSiblingSubsections($target);\n actions.removable($subsections, 'h2');\n actions.movable($subsections, 'subsection', 'h2');\n\n if (e.type === 'undo' || e.type === 'change') {\n const $parent = subsectionsHelper.getParent($target);\n const $AllNestedSubsections = $('.subsection', $parent);\n actions.updateTitleIndex($AllNestedSubsections);\n actions.displayItemWrapper($parent);\n actions.displayCategoryPresets($parent);\n }\n }\n });\n }\n /**\n * The sectionView setup section related components and beahvior\n *\n * @exports taoQtiTest/controller/creator/views/section\n */\n return {\n setUp: setUp,\n listenActionState: listenActionState\n };\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2014-2022 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);\n */\n\n/**\n * @author Bertrand Chevrier \n */\ndefine('taoQtiTest/controller/creator/views/section',[\n 'jquery',\n 'lodash',\n 'uri',\n 'i18n',\n 'taoQtiTest/controller/creator/config/defaults',\n 'taoQtiTest/controller/creator/views/actions',\n 'taoQtiTest/controller/creator/views/itemref',\n 'taoQtiTest/controller/creator/views/rubricblock',\n 'taoQtiTest/controller/creator/templates/index',\n 'taoQtiTest/controller/creator/helpers/qtiTest',\n 'taoQtiTest/controller/creator/helpers/categorySelector',\n 'taoQtiTest/controller/creator/helpers/testPartCategory',\n 'taoQtiTest/controller/creator/helpers/sectionCategory',\n 'taoQtiTest/controller/creator/helpers/sectionBlueprints',\n 'taoQtiTest/controller/creator/views/subsection',\n 'ui/dialog/confirm',\n 'taoQtiTest/controller/creator/helpers/subsection',\n 'taoQtiTest/controller/creator/helpers/validators',\n 'taoQtiTest/controller/creator/helpers/featureVisibility',\n 'services/features'\n], function (\n $,\n _,\n uri,\n __,\n defaults,\n actions,\n itemRefView,\n rubricBlockView,\n templates,\n qtiTestHelper,\n categorySelectorFactory,\n testPartCategory,\n sectionCategory,\n sectionBlueprint,\n subsectionView,\n confirmDialog,\n subsectionsHelper,\n validators,\n featureVisibility,\n servicesFeatures\n) {\n ('use strict');\n\n /**\n * Set up a section: init action behaviors. Called for each section.\n *\n * @param {Object} creatorContext\n * @param {Object} sectionModel - the data model to bind to the test section\n * @param {Object} partModel - the parent data model to inherit\n * @param {jQuery} $section - the section to set up\n */\n function setUp(creatorContext, sectionModel, partModel, $section) {\n const defaultsConfigs = defaults();\n // select elements for section, to avoid selecting the same elements in subsections\n const $itemRefsWrapper = $section.children('.itemrefs-wrapper');\n const $rubBlocks = $section.children('.rublocks');\n const $titleWithActions = $section.children('h2');\n\n const modelOverseer = creatorContext.getModelOverseer();\n const config = modelOverseer.getConfig();\n\n // set item session control to use test part options if section level isn't set\n if (!sectionModel.itemSessionControl) {\n sectionModel.itemSessionControl = {};\n }\n if (!sectionModel.categories) {\n // inherit the parent testPart's propagated categories\n const partCategories = testPartCategory.getCategories(partModel);\n sectionModel.categories = _.clone(partCategories.propagated || defaultsConfigs.categories);\n }\n _.defaults(sectionModel.itemSessionControl, partModel.itemSessionControl);\n\n if (!_.isEmpty(config.routes.blueprintsById)) {\n sectionModel.hasBlueprint = true;\n }\n\n sectionModel.hasSelectionWithReplacement = servicesFeatures.isVisible(\n 'taoQtiTest/creator/properties/selectionWithReplacement',\n false\n );\n\n //add feature visibility properties to sectionModel\n featureVisibility.addSectionVisibilityProps(sectionModel);\n\n actions.properties($titleWithActions, 'section', sectionModel, propHandler);\n actions.move($titleWithActions, 'sections', 'section');\n actions.displayItemWrapper($section);\n\n subsections();\n itemRefs();\n acceptItemRefs();\n rubricBlocks();\n addRubricBlock();\n addSubsection();\n\n /**\n * Perform some binding once the property view is create\n * @param {propView} propView - the view object\n */\n function propHandler(propView) {\n const $view = propView.getView();\n\n //enable/disable selection\n const $selectionSwitcher = $('[name=section-enable-selection]', $view);\n const $selectionSelect = $('[name=section-select]', $view);\n const $selectionWithRep = $('[name=section-with-replacement]', $view);\n\n // sectionModel.selection will be filled by bound values from template section-props.tpl\n // if sectionModel.selection from server response it has 'qti-type'\n const isSelectionFromServer = !!(sectionModel.selection && sectionModel.selection['qti-type']);\n\n const switchSelection = function switchSelection() {\n if ($selectionSwitcher.prop('checked') === true) {\n $selectionSelect.incrementer('enable');\n $selectionWithRep.removeClass('disabled');\n } else {\n $selectionSelect.incrementer('disable');\n $selectionWithRep.addClass('disabled');\n }\n };\n $selectionSwitcher.on('change', switchSelection);\n $selectionSwitcher.on('change', function updateModel() {\n if (!$selectionSwitcher.prop('checked')) {\n $selectionSelect.val(0);\n $selectionWithRep.prop('checked', false);\n delete sectionModel.selection;\n }\n });\n\n $selectionSwitcher.prop('checked', isSelectionFromServer).trigger('change');\n\n //listen for databinder change to update the test part title\n const $title = $('[data-bind=title]', $titleWithActions);\n $view.on('change.binder', function (e) {\n if (e.namespace === 'binder' && sectionModel['qti-type'] === 'assessmentSection') {\n $title.text(sectionModel.title);\n }\n });\n\n // deleted.deleter event fires only on the parent nodes (testparts, sections, etc)\n // Since it \"bubles\" we can subscribe only to the highest parent node\n $section.parents('.testparts').on('deleted.deleter', removePropHandler);\n\n //section level category configuration\n categoriesProperty($view);\n\n if (typeof sectionModel.hasBlueprint !== 'undefined') {\n blueprintProperty($view);\n }\n\n actions.displayCategoryPresets($section);\n\n function removePropHandler(e, $deletedNode) {\n const validIds = [$section.parents('.testpart').attr('id'), $section.attr('id')];\n\n const deletedNodeId = $deletedNode.attr('id');\n // We have to check id of a deleted node, because\n // 1. Event fires after child node was deleted, but e.stopPropagation doesn't help\n // because currentTarget is always document\n // 2. We have to subscribe to the parent node and it's possible that another section was removed even from another testpart\n // Subscription to the .sections selector event won't help because sections element might contain several children.\n\n if (propView !== null && validIds.includes(deletedNodeId)) {\n propView.destroy();\n }\n }\n }\n\n /**\n * Set up subsections that already belongs to the section\n * @private\n */\n function subsections() {\n if (!sectionModel.sectionParts) {\n sectionModel.sectionParts = [];\n }\n\n subsectionsHelper.getSubsections($section).each(function () {\n const $subsection = $(this);\n const index = $subsection.data('bind-index');\n if (!sectionModel.sectionParts[index]) {\n sectionModel.sectionParts[index] = {};\n }\n\n subsectionView.setUp(creatorContext, sectionModel.sectionParts[index], sectionModel, $subsection);\n });\n }\n /**\n * Set up the item refs that already belongs to the section\n * @private\n */\n function itemRefs() {\n if (!sectionModel.sectionParts) {\n sectionModel.sectionParts = [];\n }\n $('.itemref', $itemRefsWrapper).each(function () {\n const $itemRef = $(this);\n const index = $itemRef.data('bind-index');\n if (!sectionModel.sectionParts[index]) {\n sectionModel.sectionParts[index] = {};\n }\n\n itemRefView.setUp(creatorContext, sectionModel.sectionParts[index], sectionModel, partModel, $itemRef);\n $itemRef.find('.title').text(config.labels[uri.encode($itemRef.data('uri'))]);\n });\n }\n\n /**\n * Make the section to accept the selected items\n * @private\n * @fires modelOverseer#item-add\n */\n function acceptItemRefs() {\n const $itemsPanel = $('.test-creator-items .item-selection');\n\n //the item selector trigger a select event\n $itemsPanel.on('itemselect.creator', function (e, selection) {\n const $placeholder = $('.itemref-placeholder', $itemRefsWrapper);\n const $placeholders = $('.itemref-placeholder');\n\n if (_.size(selection) > 0) {\n $placeholder\n .show()\n .off('click')\n .on('click', function () {\n //prepare the item data\n const defaultItemData = {};\n\n if (\n sectionModel.itemSessionControl &&\n !_.isUndefined(sectionModel.itemSessionControl.maxAttempts)\n ) {\n //for a matter of consistency, the itemRef will \"inherit\" the itemSessionControl configuration from its parent section\n defaultItemData.itemSessionControl = _.clone(sectionModel.itemSessionControl);\n }\n\n //the itemRef should also \"inherit\" default categories set at the item level\n defaultItemData.categories = _.clone(defaultsConfigs.categories) || [];\n\n _.forEach(selection, function (item) {\n const itemData = _.defaults(\n {\n href: item.uri,\n label: item.label,\n 'qti-type': 'assessmentItemRef'\n },\n defaultItemData\n );\n\n if (_.isArray(item.categories)) {\n itemData.categories = item.categories.concat(itemData.categories);\n }\n\n addItemRef($('.itemrefs', $itemRefsWrapper), null, itemData);\n });\n\n $itemsPanel.trigger('itemselected.creator');\n\n $placeholders.hide().off('click');\n });\n } else {\n $placeholders.hide().off('click');\n }\n });\n\n //we listen the event not from the adder but from the data binder to be sure the model is up to date\n // jquery issue to select id with dot by '#ab.cd', should be used [id=\"ab.cd\"]\n $(document)\n .off('add.binder', `[id=\"${$section.attr('id')}\"] > .itemrefs-wrapper .itemrefs`)\n .on(\n 'add.binder',\n `[id=\"${$section.attr('id')}\"] > .itemrefs-wrapper .itemrefs`,\n function (e, $itemRef) {\n if (\n e.namespace === 'binder' &&\n $itemRef.hasClass('itemref') &&\n !$itemRef.parents('.subsection').length\n ) {\n const index = $itemRef.data('bind-index');\n const itemRefModel = sectionModel.sectionParts[index];\n\n //initialize the new item ref\n itemRefView.setUp(creatorContext, itemRefModel, sectionModel, partModel, $itemRef);\n\n /**\n * @event modelOverseer#item-add\n * @param {Object} itemRefModel\n */\n modelOverseer.trigger('item-add', itemRefModel);\n }\n }\n );\n }\n\n /**\n * Add a new item ref to the section\n * @param {jQuery} $refList - the element to add the item to\n * @param {Number} [index] - the position of the item to add\n * @param {Object} [itemData] - the data to bind to the new item ref\n */\n function addItemRef($refList, index, itemData) {\n const $items = $refList.children('li');\n index = index || $items.length;\n itemData.identifier = qtiTestHelper.getAvailableIdentifier(\n modelOverseer.getModel(),\n 'assessmentItemRef',\n 'item'\n );\n itemData.index = index + 1;\n const $itemRef = $(templates.itemref(itemData));\n if (index > 0) {\n $itemRef.insertAfter($items.eq(index - 1));\n } else {\n $itemRef.appendTo($refList);\n }\n $refList.trigger('add', [$itemRef, itemData]);\n }\n\n /**\n * Set up the rubric blocks that already belongs to the section\n * @private\n */\n function rubricBlocks() {\n if (!sectionModel.rubricBlocks) {\n sectionModel.rubricBlocks = [];\n }\n $('.rubricblock', $rubBlocks).each(function () {\n const $rubricBlock = $(this);\n const index = $rubricBlock.data('bind-index');\n if (!sectionModel.rubricBlocks[index]) {\n sectionModel.rubricBlocks[index] = {};\n }\n\n rubricBlockView.setUp(creatorContext, sectionModel.rubricBlocks[index], $rubricBlock);\n });\n\n //opens the rubric blocks section if they are there.\n if (sectionModel.rubricBlocks.length > 0) {\n $('.rub-toggler', $titleWithActions).trigger('click');\n }\n }\n\n /**\n * Enable to add new rubric block\n * @private\n * @fires modelOverseer#rubric-add\n */\n function addRubricBlock() {\n $('.rublock-adder', $rubBlocks).adder({\n target: $('.rubricblocks', $rubBlocks),\n content: templates.rubricblock,\n templateData: function (cb) {\n cb({\n 'qti-type': 'rubricBlock',\n index: $('.rubricblock', $rubBlocks).length,\n content: [],\n views: [1]\n });\n }\n });\n\n //we listen the event not from the adder but from the data binder to be sure the model is up to date\n // jquery issue to select id with dot by '#ab.cd', should be used [id=\"ab.cd\"]\n $(document)\n .off('add.binder', `[id=\"${$section.attr('id')}\"] > .rublocks .rubricblocks`)\n .on(\n 'add.binder',\n `[id=\"${$section.attr('id')}\"] > .rublocks .rubricblocks`,\n function (e, $rubricBlock) {\n if (\n e.namespace === 'binder' &&\n $rubricBlock.hasClass('rubricblock') &&\n !$rubricBlock.parents('.subsection').length\n ) {\n const index = $rubricBlock.data('bind-index');\n const rubricModel = sectionModel.rubricBlocks[index] || {};\n\n $('.rubricblock-binding', $rubricBlock).html('

           

          ');\n rubricBlockView.setUp(creatorContext, rubricModel, $rubricBlock);\n\n /**\n * @event modelOverseer#rubric-add\n * @param {Object} rubricModel\n */\n modelOverseer.trigger('rubric-add', rubricModel);\n }\n }\n );\n }\n\n /**\n * Set up the category property\n * @private\n * @param {jQuery} $view - the $view object containing the $select\n * @fires modelOverseer#category-change\n */\n function categoriesProperty($view) {\n const categories = sectionCategory.getCategories(sectionModel),\n categorySelector = categorySelectorFactory($view);\n\n categorySelector.createForm(categories.all, 'section');\n updateFormState(categorySelector);\n\n $view.on('propopen.propview', function () {\n updateFormState(categorySelector);\n });\n\n $view.on('set-default-categories', function () {\n sectionModel.categories = defaultsConfigs.categories;\n updateFormState(categorySelector);\n });\n\n categorySelector.on('category-change', function (selected, indeterminate) {\n sectionCategory.setCategories(sectionModel, selected, indeterminate);\n\n modelOverseer.trigger('category-change');\n });\n }\n\n function updateFormState(categorySelector) {\n const categories = sectionCategory.getCategories(sectionModel);\n categorySelector.updateFormState(categories.propagated, categories.partial);\n }\n\n /**\n * Set up the Blueprint property\n * @private\n * @param {jQuery} $view - the $view object containing the $select\n */\n function blueprintProperty($view) {\n const $select = $('[name=section-blueprint]', $view);\n $select\n .select2({\n ajax: {\n url: config.routes.blueprintsById,\n dataType: 'json',\n delay: 350,\n method: 'POST',\n data: function (params) {\n return {\n identifier: params // search term\n };\n },\n results: function (data) {\n return data;\n }\n },\n minimumInputLength: 3,\n width: '100%',\n multiple: false,\n allowClear: true,\n placeholder: __('Select a blueprint'),\n formatNoMatches: function () {\n return __('Enter a blueprint');\n },\n maximumInputLength: 32\n })\n .on('change', function (e) {\n setBlueprint(e.val);\n });\n\n initBlueprint();\n $view.on('propopen.propview', function () {\n initBlueprint();\n });\n\n /**\n * Start the blueprint editing\n * @private\n */\n function initBlueprint() {\n if (typeof sectionModel.blueprint === 'undefined') {\n sectionBlueprint\n .getBlueprint(config.routes.blueprintByTestSection, sectionModel)\n .success(function (data) {\n if (!_.isEmpty(data)) {\n if (sectionModel.blueprint !== '') {\n sectionModel.blueprint = data.uri;\n $select.select2('data', { id: data.uri, text: data.text });\n $select.trigger('change');\n }\n }\n });\n }\n }\n\n /**\n * save the categories into the model\n * @param {Object} blueprint\n * @private\n */\n function setBlueprint(blueprint) {\n sectionBlueprint.setBlueprint(sectionModel, blueprint);\n }\n }\n\n function addSubsection() {\n const optionsConfirmDialog = {\n buttons: {\n labels: {\n ok: __('Yes'),\n cancel: __('No')\n }\n }\n };\n\n $('.add-subsection', $titleWithActions).adder({\n target: $section.children('.subsections'),\n content: templates.subsection,\n templateData: function (cb) {\n //create a new subsection model object to be bound to the template\n let sectionParts = [];\n if ($section.data('movedItems')) {\n sectionParts = $section.data('movedItems');\n $section.removeData('movedItems');\n }\n cb({\n 'qti-type': 'assessmentSection',\n identifier: qtiTestHelper.getAvailableIdentifier(\n modelOverseer.getModel(),\n 'assessmentSection',\n 'subsection'\n ),\n title: defaultsConfigs.sectionTitlePrefix,\n index: 0,\n sectionParts,\n visible: true,\n itemSessionControl: {\n maxAttempts: defaultsConfigs.maxAttempts\n }\n });\n },\n checkAndCallAdd: function (executeAdd) {\n if (\n sectionModel.sectionParts[0] &&\n qtiTestHelper.filterQtiType(sectionModel.sectionParts[0], 'assessmentItemRef')\n ) {\n // section has item(s)\n const $parent = $section.parents('.sections');\n const index = $('.section', $parent).index($section);\n const confirmMessage = __(\n 'The items contained in %s %s will be moved into the new %s %s. Do you wish to proceed?',\n `${index + 1}.`,\n sectionModel.title,\n `${index + 1}.1.`,\n defaultsConfigs.sectionTitlePrefix\n );\n const acceptFunction = () => {\n // trigger deleted event for each itemfer to run removePropHandler and remove propView\n $('.itemrefs .itemref', $itemRefsWrapper).each(function () {\n $section.parents('.testparts').trigger('deleted.deleter', [$(this)]);\n });\n setTimeout(() => {\n // remove all itemrefs\n $('.itemrefs', $itemRefsWrapper).empty();\n // check itemrefs identifiers\n // because validation is build on \n // and each item should have valid and unique id\n sectionModel.sectionParts.forEach(itemRef => {\n if (!validators.checkIfItemIdValid(itemRef.identifier, modelOverseer)) {\n itemRef.identifier = qtiTestHelper.getAvailableIdentifier(\n modelOverseer.getModel(),\n 'assessmentItemRef',\n 'item'\n );\n }\n });\n $section.data('movedItems', _.clone(sectionModel.sectionParts));\n sectionModel.sectionParts = [];\n executeAdd();\n }, 0);\n };\n confirmDialog(confirmMessage, acceptFunction, () => {}, optionsConfirmDialog)\n .getDom()\n .find('.buttons')\n .css('display', 'flex')\n .css('flex-direction', 'row-reverse');\n } else {\n if (!sectionModel.sectionParts.length && sectionModel.categories.length) {\n $section.data('movedCategories', _.clone(sectionModel.categories));\n }\n executeAdd();\n }\n }\n });\n\n //we listen the event not from the adder but from the data binder to be sure the model is up to date\n // jquery issue to select id with dot by '#ab.cd', should be used [id=\"ab.cd\"]\n $(document)\n .off('add.binder', `[id=\"${$section.attr('id')}\"] > .subsections`)\n .on('add.binder', `[id=\"${$section.attr('id')}\"] > .subsections`, function (e, $subsection) {\n if (\n e.namespace === 'binder' &&\n $subsection.hasClass('subsection') &&\n !$subsection.parents('.subsection').length\n ) {\n // first level of subsection\n const subsectionIndex = $subsection.data('bind-index');\n const subsectionModel = sectionModel.sectionParts[subsectionIndex];\n\n if ($section.data('movedCategories')) {\n subsectionModel.categories = $section.data('movedCategories');\n $section.removeData('movedCategories');\n }\n\n //initialize the new subsection\n subsectionView.setUp(creatorContext, subsectionModel, sectionModel, $subsection);\n // hide 'Items' block and category-presets for current section\n actions.displayItemWrapper($section);\n actions.displayCategoryPresets($section);\n // set index for new subsection\n actions.updateTitleIndex($subsection);\n\n /**\n * @event modelOverseer#section-add\n * @param {Object} subsectionModel\n */\n modelOverseer.trigger('section-add', subsectionModel);\n }\n });\n }\n }\n\n /**\n * Listen for state changes to enable/disable . Called globally.\n */\n function listenActionState() {\n $('.sections').each(function () {\n const $sections = $('.section', $(this));\n\n actions.removable($sections, 'h2');\n actions.movable($sections, 'section', 'h2');\n actions.updateTitleIndex($sections);\n });\n\n $(document)\n .on('delete', function (e) {\n let $parent;\n const $target = $(e.target);\n if ($target.hasClass('section')) {\n $parent = $target.parents('.sections');\n actions.disable($parent.find('.section'), 'h2');\n setTimeout(() => {\n // element detached after event\n const $sectionsAndsubsections = $('.section,.subsection', $parent);\n actions.updateTitleIndex($sectionsAndsubsections);\n }, 100);\n }\n })\n .on('add change undo.deleter deleted.deleter', function (e) {\n const $target = $(e.target);\n if ($target.hasClass('section') || $target.hasClass('sections')) {\n const $sectionsContainer = $target.hasClass('sections') ? $target : $target.parents('.sections');\n const $sections = $('.section', $sectionsContainer);\n actions.removable($sections, 'h2');\n actions.movable($sections, 'section', 'h2');\n if (e.type === 'undo' || e.type === 'change') {\n const $sectionsAndsubsections = $('.section,.subsection', $sectionsContainer);\n actions.updateTitleIndex($sectionsAndsubsections);\n } else if (e.type === 'add') {\n const $newSection = $('.section:last', $sectionsContainer);\n actions.updateTitleIndex($newSection);\n }\n }\n })\n .on('open.toggler', '.rub-toggler', function (e) {\n if (e.namespace === 'toggler') {\n $(this).parents('h2').addClass('active');\n }\n })\n .on('close.toggler', '.rub-toggler', function (e) {\n if (e.namespace === 'toggler') {\n $(this).parents('h2').removeClass('active');\n }\n });\n }\n\n /**\n * The sectionView setup section related components and behavior\n *\n * @exports taoQtiTest/controller/creator/views/section\n */\n return {\n setUp: setUp,\n listenActionState: listenActionState\n };\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2014-2022 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);\n */\n\n/**\n * @author Bertrand Chevrier \n */\ndefine('taoQtiTest/controller/creator/views/testpart',[\n 'jquery',\n 'lodash',\n 'taoQtiTest/controller/creator/config/defaults',\n 'taoQtiTest/controller/creator/views/actions',\n 'taoQtiTest/controller/creator/views/section',\n 'taoQtiTest/controller/creator/templates/index',\n 'taoQtiTest/controller/creator/helpers/qtiTest',\n 'taoQtiTest/controller/creator/helpers/testPartCategory',\n 'taoQtiTest/controller/creator/helpers/categorySelector',\n 'taoQtiTest/controller/creator/helpers/featureVisibility'\n], function (\n $,\n _,\n defaults,\n actions,\n sectionView,\n templates,\n qtiTestHelper,\n testPartCategory,\n categorySelectorFactory,\n featureVisibility\n) {\n ('use strict');\n\n /**\n * Set up a test part: init action behaviors. Called for each test part.\n *\n * @param {Object} creatorContext\n * @param {Object} partModel - the data model to bind to the test part\n * @param {jQuery} $testPart - the testpart container to set up\n */\n function setUp(creatorContext, partModel, $testPart) {\n const defaultsConfigs = defaults();\n const $actionContainer = $('h1', $testPart);\n const $titleWithActions = $testPart.children('h1');\n const modelOverseer = creatorContext.getModelOverseer();\n\n //add feature visibility properties to testPartModel\n featureVisibility.addTestPartVisibilityProps(partModel);\n\n //run setup methods\n actions.properties($actionContainer, 'testpart', partModel, propHandler);\n actions.move($actionContainer, 'testparts', 'testpart');\n sections();\n addSection();\n\n /**\n * Perform some binding once the property view is created\n * @private\n * @param {propView} propView - the view object\n */\n function propHandler(propView) {\n const $view = propView.getView();\n\n //listen for databinder change to update the test part title\n const $identifier = $('[data-bind=identifier]', $titleWithActions);\n $view.on('change.binder', function (e, model) {\n if (e.namespace === 'binder' && model['qti-type'] === 'testPart') {\n $identifier.text(model.identifier);\n\n /**\n * @event modelOverseer#section-add\n * @param {Object} sectionModel\n */\n modelOverseer.trigger('testpart-change', partModel);\n }\n });\n\n //destroy it when it's testpart is removed\n $testPart.parents('.testparts').on('deleted.deleter', function (e, $deletedNode) {\n if (propView !== null && $deletedNode.attr('id') === $testPart.attr('id')) {\n propView.destroy();\n }\n });\n\n //testPart level category configuration\n categoriesProperty($view);\n\n actions.displayCategoryPresets($testPart, 'testpart');\n }\n\n /**\n * Set up sections that already belongs to the test part\n * @private\n */\n function sections() {\n if (!partModel.assessmentSections) {\n partModel.assessmentSections = [];\n }\n $('.section', $testPart).each(function () {\n const $section = $(this);\n const index = $section.data('bind-index');\n if (!partModel.assessmentSections[index]) {\n partModel.assessmentSections[index] = {};\n }\n\n sectionView.setUp(creatorContext, partModel.assessmentSections[index], partModel, $section);\n });\n }\n\n /**\n * Enable to add new sections\n * @private\n * @fires modelOverseer#section-add\n */\n function addSection() {\n $('.section-adder', $testPart).adder({\n target: $('.sections', $testPart),\n content: templates.section,\n templateData: function (cb) {\n //create a new section model object to be bound to the template\n cb({\n 'qti-type': 'assessmentSection',\n identifier: qtiTestHelper.getAvailableIdentifier(\n modelOverseer.getModel(),\n 'assessmentSection',\n defaultsConfigs.sectionIdPrefix\n ),\n title: defaultsConfigs.sectionTitlePrefix,\n index: 0,\n sectionParts: [],\n visible: true,\n itemSessionControl: {\n maxAttempts: defaultsConfigs.maxAttempts\n }\n });\n }\n });\n\n //we listen the event not from the adder but from the data binder to be sure the model is up to date\n // jquery issue to select id with dot by '#ab.cd', should be used [id=\"ab.cd\"]\n $(document)\n .off('add.binder', `[id=${$testPart.attr('id')}] .sections`)\n .on('add.binder', `[id=${$testPart.attr('id')}] .sections`, function (e, $section) {\n if (e.namespace === 'binder' && $section.hasClass('section')) {\n const index = $section.data('bind-index');\n const sectionModel = partModel.assessmentSections[index];\n\n //initialize the new section\n sectionView.setUp(creatorContext, sectionModel, partModel, $section);\n\n /**\n * @event modelOverseer#section-add\n * @param {Object} sectionModel\n */\n modelOverseer.trigger('section-add', sectionModel);\n }\n });\n }\n\n /**\n * Set up the category property\n * @private\n * @param {jQuery} $view - the $view object containing the $select\n * @fires modelOverseer#category-change\n */\n function categoriesProperty($view) {\n const categoriesSummary = testPartCategory.getCategories(partModel);\n const categorySelector = categorySelectorFactory($view);\n\n categorySelector.createForm(categoriesSummary.all, 'testPart');\n updateFormState(categorySelector);\n\n $view.on('propopen.propview', function () {\n updateFormState(categorySelector);\n });\n\n $view.on('set-default-categories', function () {\n partModel.categories = defaultsConfigs.categories;\n updateFormState(categorySelector);\n });\n\n categorySelector.on('category-change', function (selected, indeterminate) {\n testPartCategory.setCategories(partModel, selected, indeterminate);\n\n modelOverseer.trigger('category-change');\n });\n }\n\n function updateFormState(categorySelector) {\n const categoriesSummary = testPartCategory.getCategories(partModel);\n categorySelector.updateFormState(categoriesSummary.propagated, categoriesSummary.partial);\n }\n }\n\n /**\n * Listen for state changes to enable/disable . Called globally.\n */\n function listenActionState() {\n let $testParts = $('.testpart');\n\n actions.removable($testParts, 'h1');\n actions.movable($testParts, 'testpart', 'h1');\n\n $('.testparts')\n .on('delete', function (e) {\n const $target = $(e.target);\n if ($target.hasClass('testpart')) {\n actions.disable($(e.target.id), 'h1');\n }\n })\n .on('add change undo.deleter deleted.deleter', function (e) {\n const $target = $(e.target);\n\n if ($target.hasClass('testpart') || $target.hasClass('testparts')) {\n //refresh\n $testParts = $('.testpart');\n\n //check state\n actions.removable($testParts, 'h1');\n actions.movable($testParts, 'testpart', 'h1');\n }\n });\n }\n\n /**\n * The testPartView setup testpart related components and behavior\n *\n * @exports taoQtiTest/controller/creator/views/testpart\n */\n return {\n setUp: setUp,\n listenActionState: listenActionState\n };\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2014 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);\n */\n/**\n * @author Bertrand Chevrier \n */\ndefine('taoQtiTest/controller/creator/views/test',[\n 'jquery',\n 'lodash',\n 'i18n',\n 'ui/hider',\n 'ui/feedback',\n 'services/features',\n 'taoQtiTest/controller/creator/config/defaults',\n 'taoQtiTest/controller/creator/views/actions',\n 'taoQtiTest/controller/creator/views/testpart',\n 'taoQtiTest/controller/creator/templates/index',\n 'taoQtiTest/controller/creator/helpers/qtiTest',\n 'taoQtiTest/controller/creator/helpers/featureVisibility'\n], function (\n $,\n _,\n __,\n hider,\n feedback,\n features,\n defaults,\n actions,\n testPartView,\n templates,\n qtiTestHelper,\n featureVisibility\n) {\n ('use strict');\n\n /**\n * The TestView setup test related components and behavior\n *\n * @exports taoQtiTest/controller/creator/views/test\n * @param {Object} creatorContext\n */\n function testView(creatorContext) {\n const defaultsConfigs = defaults();\n var modelOverseer = creatorContext.getModelOverseer();\n var testModel = modelOverseer.getModel();\n\n //add feature visibility properties to testModel\n featureVisibility.addTestVisibilityProps(testModel);\n\n actions.properties($('.test-creator-test > h1'), 'test', testModel, propHandler);\n testParts();\n addTestPart();\n\n //add feature visibility props to model\n\n /**\n * set up the existing test part views\n * @private\n */\n function testParts() {\n if (!testModel.testParts) {\n testModel.testParts = [];\n }\n $('.testpart').each(function () {\n var $testPart = $(this);\n var index = $testPart.data('bind-index');\n if (!testModel.testParts[index]) {\n testModel.testParts[index] = {};\n }\n\n testPartView.setUp(creatorContext, testModel.testParts[index], $testPart);\n });\n }\n\n /**\n * Perform some binding once the property view is created\n * @private\n * @param {propView} propView - the view object\n * @fires modelOverseer#scoring-change\n */\n function propHandler(propView) {\n var $view = propView.getView();\n var $categoryScoreLine = $('.test-category-score', $view);\n var $cutScoreLine = $('.test-cut-score', $view);\n var $weightIdentifierLine = $('.test-weight-identifier', $view);\n var $descriptions = $('.test-outcome-processing-description', $view);\n var $generate = $('[data-action=\"generate-outcomes\"]', $view);\n var $title = $('.test-creator-test > h1 [data-bind=title]');\n var scoringState = JSON.stringify(testModel.scoring);\n\n function changeScoring(scoring) {\n var noOptions = !!scoring && ['none', 'custom'].indexOf(scoring.outcomeProcessing) === -1;\n var newScoringState = JSON.stringify(scoring);\n\n hider.toggle($cutScoreLine, !!scoring && scoring.outcomeProcessing === 'cut');\n hider.toggle($categoryScoreLine, noOptions);\n hider.toggle($weightIdentifierLine, noOptions);\n hider.hide($descriptions);\n hider.show($descriptions.filter('[data-key=\"' + scoring.outcomeProcessing + '\"]'));\n\n if (scoringState !== newScoringState) {\n /**\n * @event modelOverseer#scoring-change\n * @param {Object} testModel\n */\n modelOverseer.trigger('scoring-change', testModel);\n }\n scoringState = newScoringState;\n }\n\n function updateOutcomes() {\n var $panel = $('.outcome-declarations', $view);\n\n $panel.html(templates.outcomes({ outcomes: modelOverseer.getOutcomesList() }));\n }\n\n $('[name=test-outcome-processing]', $view).select2({\n minimumResultsForSearch: -1,\n width: '100%'\n });\n\n $generate.on('click', function () {\n $generate.addClass('disabled').attr('disabled', true);\n modelOverseer\n .on('scoring-write.regenerate', function () {\n modelOverseer.off('scoring-write.regenerate');\n feedback()\n .success(__('The outcomes have been regenerated!'))\n .on('destroy', function () {\n $generate.removeClass('disabled').removeAttr('disabled');\n });\n })\n .trigger('scoring-change');\n });\n\n $view.on('change.binder', function (e, model) {\n if (e.namespace === 'binder' && model['qti-type'] === 'assessmentTest') {\n changeScoring(model.scoring);\n\n //update the test part title when the databinder has changed it\n $title.text(model.title);\n }\n });\n\n modelOverseer.on('scoring-write', updateOutcomes);\n\n changeScoring(testModel.scoring);\n updateOutcomes();\n }\n\n /**\n * Enable to add new test parts\n * @private\n * @fires modelOverseer#part-add\n */\n function addTestPart() {\n $('.testpart-adder').adder({\n target: $('.testparts'),\n content: templates.testpart,\n templateData: function (cb) {\n //create an new testPart model object to be bound to the template\n var testPartIndex = $('.testpart').length;\n cb({\n 'qti-type': 'testPart',\n identifier: qtiTestHelper.getAvailableIdentifier(\n modelOverseer.getModel(),\n 'testPart',\n defaultsConfigs.partIdPrefix\n ),\n index: testPartIndex,\n navigationMode: defaultsConfigs.navigationMode,\n submissionMode: defaultsConfigs.submissionMode,\n assessmentSections: [\n {\n 'qti-type': 'assessmentSection',\n identifier: qtiTestHelper.getAvailableIdentifier(\n modelOverseer.getModel(),\n 'assessmentSection',\n defaultsConfigs.sectionIdPrefix\n ),\n title: defaultsConfigs.sectionTitlePrefix,\n index: 0,\n sectionParts: [],\n visible: true,\n itemSessionControl: {\n maxAttempts: defaultsConfigs.maxAttempts\n }\n }\n ]\n });\n }\n });\n\n //we listen the event not from the adder but from the data binder to be sure the model is up to date\n $(document)\n .off('add.binder', '.testparts')\n .on('add.binder', '.testparts', function (e, $testPart, added) {\n var partModel;\n if (e.namespace === 'binder' && $testPart.hasClass('testpart')) {\n partModel = testModel.testParts[added.index];\n\n //initialize the new test part\n testPartView.setUp(creatorContext, partModel, $testPart);\n // set index for new section\n actions.updateTitleIndex($('.section', $testPart));\n\n /**\n * @event modelOverseer#part-add\n * @param {Object} partModel\n */\n modelOverseer.trigger('part-add', partModel);\n }\n });\n }\n }\n\n return testView;\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2017 (original work) Open Assessment Technologies SA ;\n */\n/**\n * Basic helper that is intended to generate outcomes processing rules for a test model.\n *\n * @author Jean-Sébastien Conan \n */\ndefine('taoQtiTest/controller/creator/helpers/processingRule',[\n 'lodash',\n 'taoQtiTest/controller/creator/helpers/outcomeValidator',\n 'taoQtiTest/controller/creator/helpers/qtiElement',\n 'taoQtiTest/controller/creator/helpers/baseType',\n 'taoQtiTest/controller/creator/helpers/cardinality'\n], function (_, outcomeValidator, qtiElementHelper, baseTypeHelper, cardinalityHelper) {\n 'use strict';\n\n var processingRuleHelper = {\n /**\n * Creates a basic processing rule\n * @param {String} type\n * @param {String} [identifier]\n * @param {Array|Object} [expression]\n * @returns {Object}\n * @throws {TypeError} if the type is empty or is not a string\n * @throws {TypeError} if the identifier is not valid\n * @throws {TypeError} if the expression does not contain valid QTI elements\n */\n create: function create(type, identifier, expression) {\n var processingRule = qtiElementHelper.create(type, identifier && validateIdentifier(identifier));\n\n if (expression) {\n processingRuleHelper.setExpression(processingRule, expression);\n }\n\n return processingRule;\n },\n\n /**\n * Sets an expression to a processing rule\n * @param {Object} processingRule\n * @param {Object|Array} expression\n * @throws {TypeError} if the expression does not contain valid QTI elements\n */\n setExpression: function setExpression(processingRule, expression) {\n if (processingRule) {\n if (_.isArray(expression)) {\n if (processingRule.expression) {\n processingRule.expression = null;\n }\n processingRule.expressions = validateOutcomeList(expression);\n } else {\n if (processingRule.expressions) {\n processingRule.expressions = null;\n }\n if (expression) {\n processingRule.expression = validateOutcome(expression);\n }\n }\n }\n },\n\n /**\n * Adds an expression to a processing rule\n * @param {Object} processingRule\n * @param {Object|Array} expression\n * @throws {TypeError} if the expression does not contain valid QTI elements\n */\n addExpression: function addExpression(processingRule, expression) {\n if (processingRule && expression) {\n if (processingRule.expression) {\n processingRule.expressions = forceArray(processingRule.expression);\n processingRule.expression = null;\n }\n\n if (_.isArray(expression)) {\n processingRule.expressions = forceArray(processingRule.expressions).concat(validateOutcomeList(expression));\n } else {\n if (processingRule.expressions) {\n processingRule.expressions.push(expression);\n } else {\n processingRule.expression = validateOutcome(expression);\n }\n }\n }\n },\n\n /**\n * Creates a `setOutcomeValue` rule\n * @param {String} identifier\n * @param {Object|Array} [expression]\n * @returns {Object}\n * @throws {TypeError} if the identifier is empty or is not a string\n * @throws {TypeError} if the expression does not contain valid QTI elements\n */\n setOutcomeValue: function setOutcomeValue(identifier, expression) {\n return processingRuleHelper.create('setOutcomeValue', validateIdentifier(identifier), expression);\n },\n\n /**\n * Creates a `gte` rule\n * @param {Object|Array} left - the left operand\n * @param {Object|Array} right - the right operand\n * @returns {Object}\n * @throws {TypeError} if the left and right operands are not valid QTI elements\n */\n gte: function gte(left, right) {\n return binaryOperator('gte', left, right);\n },\n\n /**\n * Creates a `lte` rule\n * @param {Object|Array} left - the left operand\n * @param {Object|Array} right - the right operand\n * @returns {Object}\n * @throws {TypeError} if the left and right operands are not valid QTI elements\n */\n lte: function lte(left, right) {\n return binaryOperator('lte', left, right);\n },\n\n /**\n * Creates a `divide` rule\n * @param {Object|Array} left - the left operand\n * @param {Object|Array} right - the right operand\n * @returns {Object}\n * @throws {TypeError} if the left and right operands are not valid QTI elements\n */\n divide: function divide(left, right) {\n return binaryOperator('divide', left, right);\n },\n\n /**\n * Creates a `sum` rule\n * @param {Object|Array} terms\n * @returns {Object}\n * @throws {TypeError} if the terms are not valid QTI elements\n */\n sum: function sum(terms) {\n var processingRule = processingRuleHelper.create('sum', null, forceArray(terms));\n\n processingRule.minOperands = 1;\n processingRule.maxOperands = -1;\n processingRule.acceptedCardinalities = [cardinalityHelper.SINGLE, cardinalityHelper.MULTIPLE, cardinalityHelper.ORDERED];\n processingRule.acceptedBaseTypes = [baseTypeHelper.INTEGER, baseTypeHelper.FLOAT];\n\n return processingRule;\n },\n\n /**\n * Creates a `testVariables` rule\n * @param {String} identifier\n * @param {String|Number} [type]\n * @param {String} weightIdentifier\n * @param {String|String[]} [includeCategories]\n * @param {String|String[]} [excludeCategories]\n * @returns {Object}\n * @throws {TypeError} if the identifier is empty or is not a string\n */\n testVariables: function testVariables(identifier, type, weightIdentifier, includeCategories, excludeCategories) {\n var processingRule = processingRuleHelper.create('testVariables');\n\n processingRule.variableIdentifier = validateIdentifier(identifier);\n processingRule.baseType = baseTypeHelper.getValid(type);\n addWeightIdentifier(processingRule, weightIdentifier);\n addSectionIdentifier(processingRule, '');\n addCategories(processingRule, includeCategories, excludeCategories);\n\n return processingRule;\n },\n\n /**\n * Creates a `outcomeMaximum` rule\n * @param {String} identifier\n * @param {String} weightIdentifier\n * @param {String|String[]} [includeCategories]\n * @param {String|String[]} [excludeCategories]\n * @returns {Object}\n * @throws {TypeError} if the identifier is empty or is not a string\n */\n outcomeMaximum: function outcomeMaximum(identifier, weightIdentifier, includeCategories, excludeCategories) {\n var processingRule = processingRuleHelper.create('outcomeMaximum');\n\n processingRule.outcomeIdentifier = validateIdentifier(identifier);\n\n addWeightIdentifier(processingRule, weightIdentifier);\n addSectionIdentifier(processingRule, '');\n addCategories(processingRule, includeCategories, excludeCategories);\n\n return processingRule;\n },\n\n /**\n * Creates a `numberPresented` rule\n * @param {String|String[]} [includeCategories]\n * @param {String|String[]} [excludeCategories]\n * @returns {Object}\n */\n numberPresented: function numberPresented(includeCategories, excludeCategories) {\n var processingRule = processingRuleHelper.create('numberPresented');\n\n addSectionIdentifier(processingRule, '');\n addCategories(processingRule, includeCategories, excludeCategories);\n\n return processingRule;\n },\n\n /**\n * Creates a `baseValue` rule\n * @param {*} [value]\n * @param {String|Number} [type]\n * @returns {Object}\n */\n baseValue: function baseValue(value, type) {\n var processingRule = processingRuleHelper.create('baseValue');\n\n processingRule.baseType = baseTypeHelper.getValid(type, baseTypeHelper.FLOAT);\n processingRule.value = baseTypeHelper.getValue(processingRule.baseType, value);\n\n return processingRule;\n },\n\n /**\n ** Creates a `variable` rule\n * @param {String} identifier\n * @param {String} [weightIdentifier]\n * @returns {Object}\n * @throws {TypeError} if the identifier is not valid\n * @throws {TypeError} if the weight identifier is not valid\n */\n variable: function variable(identifier, weightIdentifier) {\n var processingRule = processingRuleHelper.create('variable', validateIdentifier(identifier));\n\n addWeightIdentifier(processingRule, weightIdentifier);\n\n return processingRule;\n },\n\n /**\n * Creates a `match` rule\n * @param {Object|Array} left - the left operand\n * @param {Object|Array} right - the right operand\n * @returns {Object}\n * @throws {TypeError} if the left and right operands are not valid QTI elements\n */\n match: function match(left, right) {\n return binaryOperator('match', left, right, cardinalityHelper.SAME, cardinalityHelper.SAME);\n },\n\n /**\n * Creates a `isNull` rule\n * @param {Object|Array} expression - the operand\n * @returns {Object}\n * @throws {TypeError} if the operand is not valid QTI element\n */\n isNull: function isNull(expression) {\n return unaryOperator('isNull', expression, baseTypeHelper.ANY, cardinalityHelper.ANY);\n },\n\n /**\n * Creates a `outcomeCondition` rule\n * @param {Object} outcomeIf\n * @param {Object} outcomeElse\n * @returns {Object}\n * @throws {TypeError} if the outcomeIf and outcomeElse operands are not valid QTI elements\n */\n outcomeCondition: function outcomeCondition(outcomeIf, outcomeElse) {\n var processingRule = processingRuleHelper.create('outcomeCondition');\n\n if (!outcomeValidator.validateOutcome(outcomeIf, false, 'outcomeIf')) {\n throw new TypeError('You must provide a valid outcomeIf element!');\n }\n\n if (outcomeElse && !outcomeValidator.validateOutcome(outcomeElse, false, 'outcomeElse')) {\n throw new TypeError('You must provide a valid outcomeElse element!');\n }\n\n processingRule.outcomeIf = outcomeIf;\n processingRule.outcomeElseIfs = [];\n\n if (outcomeElse) {\n processingRule.outcomeElse = outcomeElse;\n }\n\n return processingRule;\n },\n\n /**\n * Creates a `outcomeIf` rule\n * @param {Object} expression\n * @param {Object|Object[]} instruction\n * @returns {Object}\n * @throws {TypeError} if the expression and instruction operands are not valid QTI elements\n */\n outcomeIf: function outcomeIf(expression, instruction) {\n var processingRule = processingRuleHelper.create('outcomeIf');\n\n if (!_.isArray(instruction)) {\n instruction = [instruction];\n }\n\n processingRule.expression = validateOutcome(expression);\n processingRule.outcomeRules = validateOutcomeList(instruction);\n\n return processingRule;\n },\n\n /**\n * Creates a `outcomeElse` rule\n * @param {Object|Object[]} instruction\n * @returns {Object}\n * @throws {TypeError} if the instruction is not a valid QTI element\n */\n outcomeElse: function outcomeElse(instruction) {\n var processingRule = processingRuleHelper.create('outcomeElse');\n\n if (!_.isArray(instruction)) {\n instruction = [instruction];\n }\n\n processingRule.outcomeRules = validateOutcomeList(instruction);\n\n return processingRule;\n }\n\n };\n\n /**\n * Creates a unary operator rule\n * @param {String} type - The rule type\n * @param {Object|Array} expression - The operand\n * @param {Number|Array} [baseType] - The accepted base type\n * @param {Number|Array} [cardinality] - The accepted cardinality\n * @returns {Object}\n * @throws {TypeError} if the type is empty or is not a string\n * @throws {TypeError} if the operand is not valid QTI element\n */\n function unaryOperator(type, expression, baseType, cardinality) {\n var processingRule = processingRuleHelper.create(type, null, [expression]);\n\n processingRule.minOperands = 1;\n processingRule.maxOperands = 1;\n\n addTypeAndCardinality(processingRule, baseType, cardinality);\n\n return processingRule;\n }\n\n /**\n * Creates a binary operator rule\n * @param {String} type - The rule type\n * @param {Object|Array} left - The left operand\n * @param {Object|Array} right - The right operand\n * @param {Number|Array} [baseType] - The accepted base type\n * @param {Number|Array} [cardinality] - The accepted cardinality\n * @returns {Object}\n * @throws {TypeError} if the type is empty or is not a string\n * @throws {TypeError} if the left and right operands are not valid QTI elements\n */\n function binaryOperator(type, left, right, baseType, cardinality) {\n var processingRule = processingRuleHelper.create(type, null, [left, right]);\n\n processingRule.minOperands = 2;\n processingRule.maxOperands = 2;\n\n addTypeAndCardinality(processingRule, baseType, cardinality);\n\n return processingRule;\n }\n\n /**\n * Appends the base type and the cardinality on a processing rule\n * @param {Object} processingRule\n * @param {Number|Array} [baseType] - The accepted base type\n * @param {Number|Array} [cardinality] - The accepted cardinality\n * @returns {Object}\n */\n function addTypeAndCardinality(processingRule, baseType, cardinality) {\n if (_.isUndefined(baseType)) {\n baseType = [baseTypeHelper.INTEGER, baseTypeHelper.FLOAT];\n }\n\n if (_.isUndefined(cardinality)) {\n cardinality = [cardinalityHelper.SINGLE];\n }\n\n processingRule.acceptedCardinalities = forceArray(cardinality);\n processingRule.acceptedBaseTypes = forceArray(baseType);\n\n return processingRule;\n }\n\n /**\n * Extends a processing rule with categories\n * @param {Object} processingRule\n * @param {Array|String} [includeCategories]\n * @param {Array|String} [excludeCategories]\n * @returns {Object}\n */\n function addCategories(processingRule, includeCategories, excludeCategories) {\n processingRule.includeCategories = forceArray(includeCategories);\n processingRule.excludeCategories = forceArray(excludeCategories);\n\n return processingRule;\n }\n\n /**\n * Appends the section identifier on a processing rule\n * @param {Object} processingRule\n * @param {String} [sectionIdentifier]\n * @returns {Object}\n * @throws {TypeError} if the weight identifier is not valid\n */\n function addSectionIdentifier(processingRule, sectionIdentifier) {\n if (sectionIdentifier) {\n if (!outcomeValidator.validateIdentifier(sectionIdentifier)) {\n throw new TypeError('You must provide a valid weight identifier!');\n }\n processingRule.sectionIdentifier = sectionIdentifier;\n } else {\n processingRule.sectionIdentifier = '';\n }\n\n return processingRule;\n }\n\n /**\n * Appends the weight identifier on a processing rule\n * @param {Object} processingRule\n * @param {String} [weightIdentifier]\n * @returns {Object}\n * @throws {TypeError} if the weight identifier is not valid\n */\n function addWeightIdentifier(processingRule, weightIdentifier) {\n if (weightIdentifier) {\n if (!outcomeValidator.validateIdentifier(weightIdentifier)) {\n throw new TypeError('You must provide a valid weight identifier!');\n }\n processingRule.weightIdentifier = weightIdentifier;\n } else {\n processingRule.weightIdentifier = '';\n }\n\n return processingRule;\n }\n\n /**\n * Validates an identifier\n * @param {String} identifier\n * @returns {String}\n * @throws {TypeError} if the identifier is not valid\n */\n function validateIdentifier(identifier) {\n if (!outcomeValidator.validateIdentifier(identifier)) {\n throw new TypeError('You must provide a valid identifier!');\n }\n return identifier;\n }\n\n /**\n * Validates an outcome\n * @param {Object} outcome\n * @returns {Object}\n * @throws {TypeError} if the outcome is not valid\n */\n function validateOutcome(outcome) {\n if (!outcomeValidator.validateOutcome(outcome)) {\n throw new TypeError('You must provide a valid QTI element!');\n }\n return outcome;\n }\n\n /**\n * Validates a list of outcomes\n * @param {Array} outcomes\n * @returns {Array}\n * @throws {TypeError} if an outcome is not valid\n */\n function validateOutcomeList(outcomes) {\n if (!outcomeValidator.validateOutcomes(outcomes)) {\n throw new TypeError('You must provide a valid list of QTI elements!');\n }\n return outcomes;\n }\n\n /**\n * Ensures a value is an array\n * @param {*} value\n * @returns {Array}\n */\n function forceArray(value) {\n if (!value) {\n value = [];\n }\n if (!_.isArray(value)) {\n value = [value];\n }\n return value;\n }\n\n return processingRuleHelper;\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2017-2023 (original work) Open Assessment Technologies SA ;\n */\n/**\n * Basic helper that is intended to manage the score processing declaration in a test model.\n *\n * @author Jean-Sébastien Conan \n */\ndefine('taoQtiTest/controller/creator/helpers/scoring',[\n 'lodash',\n 'i18n',\n 'core/format',\n 'taoQtiTest/controller/creator/helpers/baseType',\n 'taoQtiTest/controller/creator/helpers/outcome',\n 'taoQtiTest/controller/creator/helpers/processingRule'\n], function (_, __, format, baseTypeHelper, outcomeHelper, processingRuleHelper) {\n 'use strict';\n\n /**\n * The default cut score\n * @todo Move this to a config file\n * @type {Number}\n */\n var defaultCutScore = 0.5;\n\n /**\n * The name of the variable containing the score\n * @type {String}\n */\n var defaultScoreIdentifier = 'SCORE';\n\n /**\n * The list of supported processing modes, indexed by mode identifier\n * @type {Object}\n */\n var processingModes = {\n none: {\n key: 'none',\n label: __('None'),\n description: __('No outcome processing. Erase the existing rules, if any.')\n },\n custom: {\n key: 'custom',\n label: __('Custom'),\n description: __('Custom outcome processing. No changes will be made to the existing rules.')\n },\n total: {\n key: 'total',\n label: __('Total score'),\n description: __('The score will be processed for the entire test. A sum of all SCORE outcomes will be computed, the result will take place in the SCORE_TOTAL outcome.')\n + ' ' +\n __('If the category option is set, the score will also be processed per categories, and each results will take place in the SCORE_xxx outcome, where xxx is the name of the category.')\n },\n cut: {\n key: 'cut',\n label: __('Cut score'),\n description: __('The score will be processed for the entire test. A sum of all SCORE outcomes will be computed and divided by the sum of MAX SCORE, the result will be compared to the cut score (or pass ratio), then the PASS_TOTAL outcome will be set accordingly.')\n + ' ' +\n __('If the category option is set, the score will also be processed per categories, and each results will take place in the PASS_xxx outcome, where xxx is the name of the category.')\n }\n };\n\n /**\n * The list of recipes to generate the outcomes\n * @type {Object}\n */\n var outcomesRecipes = {\n none: {\n key: 'none',\n clean: true\n },\n custom: {\n key: 'custom',\n clean: false\n },\n total: {\n key: 'total',\n signature: /^SCORE_([a-zA-Z][a-zA-Z0-9_\\.-]*)$/,\n outcomes: [\n {\n writer: 'total',\n identifier: 'SCORE_TOTAL',\n weighted: 'SCORE_TOTAL_WEIGHTED',\n categoryIdentifier: 'SCORE_CATEGORY_%s',\n categoryWeighted: 'SCORE_CATEGORY_WEIGHTED_%s'\n },\n {\n writer: 'max',\n identifier: 'SCORE_TOTAL_MAX',\n weighted: 'SCORE_TOTAL_MAX_WEIGHTED',\n categoryIdentifier: 'SCORE_CATEGORY_MAX_%s',\n categoryWeighted: 'SCORE_CATEGORY_WEIGHTED_MAX_%s'\n },\n {\n writer: 'ratio',\n identifier: 'SCORE_RATIO',\n weighted: 'SCORE_RATIO_WEIGHTED',\n scoreIdentifier: {\n total: 'SCORE_TOTAL',\n max: 'SCORE_TOTAL_MAX'\n },\n scoreWeighted: {\n total: 'SCORE_TOTAL_WEIGHTED',\n max: 'SCORE_TOTAL_MAX_WEIGHTED'\n }\n }\n ],\n clean: true\n },\n cut: {\n key: 'cut',\n include: 'total',\n signature: /^PASS_([a-zA-Z][a-zA-Z0-9_\\.-]*)$/,\n outcomes: [\n {\n writer: 'cut',\n identifier: 'PASS_ALL',\n feedback: 'PASS_ALL_RENDERING',\n feedbackOk: 'passed',\n feedbackFailed: 'not_passed',\n categoryIdentifier: 'PASS_CATEGORY_%s',\n categoryFeedback: 'PASS_CATEGORY_%s_RENDERING'\n }\n ],\n clean: true\n }\n };\n\n /**\n * List of writers that provide the outcomes for each score processing mode.\n * @type {Object}\n */\n var outcomesWriters = {\n /**\n * Generates the outcomes that compute the \"Score ratio\"\n * @param {Object} descriptor\n * @param {Object} scoring\n * @param {Object} outcomes\n */\n ratio: function writerRatio(descriptor, scoring, outcomes) {\n addRatioOutcomes(\n outcomes,\n descriptor.identifier,\n descriptor.scoreIdentifier.total,\n descriptor.scoreIdentifier.max\n );\n if (scoring.weightIdentifier) {\n //add weighted ratio outcome only when the scoring outcome processing rule uses a weight\n addRatioOutcomes(\n outcomes,\n descriptor.weighted,\n descriptor.scoreWeighted.total,\n descriptor.scoreWeighted.max\n );\n }\n },\n\n /**\n * Generates the outcomes that compute the \"Total score\"\n * @param {Object} descriptor\n * @param {Object} scoring\n * @param {Object} outcomes\n * @param {Array} [categories]\n */\n total: function writerTotal(descriptor, scoring, outcomes, categories) {\n // create the outcome and the rule that process the overall score\n addTotalScoreOutcomes(outcomes, scoring, descriptor.identifier, false);\n if (descriptor.weighted && scoring.weightIdentifier) {\n addTotalScoreOutcomes(outcomes, scoring, descriptor.weighted, true);\n }\n\n // create an outcome per categories\n if (descriptor.categoryIdentifier && categories) {\n _.forEach(categories, function (category) {\n addTotalScoreOutcomes(\n outcomes,\n scoring,\n formatCategoryOutcome(category, descriptor.categoryIdentifier),\n false,\n category\n );\n if (descriptor.categoryWeighted && scoring.weightIdentifier) {\n addTotalScoreOutcomes(\n outcomes,\n scoring,\n formatCategoryOutcome(category, descriptor.categoryWeighted),\n true,\n category\n );\n }\n });\n }\n },\n\n /**\n * Generates the outcomes that compute the \"Max score\"\n * @param {Object} descriptor\n * @param {Object} scoring\n * @param {Object} outcomes\n * @param {Array} [categories]\n */\n max: function writerMax(descriptor, scoring, outcomes, categories) {\n // create the outcome and the rule that process the maximum overall score\n addMaxScoreOutcomes(outcomes, scoring, descriptor.identifier, false);\n if (descriptor.weighted && scoring.weightIdentifier) {\n addMaxScoreOutcomes(outcomes, scoring, descriptor.weighted, true);\n }\n\n // create an outcome per categories\n if (descriptor.categoryIdentifier && categories) {\n _.forEach(categories, function (category) {\n addMaxScoreOutcomes(\n outcomes,\n scoring,\n formatCategoryOutcome(category, descriptor.categoryIdentifier),\n false,\n category\n );\n if (descriptor.categoryWeighted && scoring.weightIdentifier) {\n addMaxScoreOutcomes(\n outcomes,\n scoring,\n formatCategoryOutcome(category, descriptor.categoryWeighted),\n true,\n category\n );\n }\n });\n }\n },\n\n /**\n * Generates the outcomes that compute the \"Cut score\"\n * @param {Object} descriptor\n * @param {Object} scoring\n * @param {Object} outcomes\n * @param {Array} [categories]\n * @returns {Object} outcomes\n */\n cut: function writerCut(descriptor, scoring, outcomes, categories) {\n var cutScore = scoring.cutScore;\n var totalModeOutcomes = outcomesRecipes.total.outcomes;\n var total = _.find(totalModeOutcomes, { writer: 'total' });\n var max = _.find(totalModeOutcomes, { writer: 'max' });\n var ratio = _.find(totalModeOutcomes, { writer: 'ratio' });\n var whichOutcome = scoring.weightIdentifier ? 'weighted' : 'identifier';\n var ratioIdentifier = ratio[whichOutcome];\n\n // create the outcome and the rule that process the overall score\n addGlobalCutScoreOutcomes(outcomes, descriptor.identifier, ratioIdentifier, cutScore);\n\n // create the outcome and the rule that process the score feedback\n if (descriptor.feedback) {\n addFeedbackScoreOutcomes(\n outcomes,\n descriptor.feedback,\n descriptor.identifier,\n descriptor.feedbackOk,\n descriptor.feedbackFailed\n );\n }\n\n // create an outcome per category\n if (descriptor.categoryIdentifier && categories) {\n _.forEach(categories, function (category) {\n var categoryOutcome = scoring.weightIdentifier ? 'categoryWeighted' : 'categoryIdentifier';\n var categoryOutcomeIdentifier = formatCategoryOutcome(category, descriptor.categoryIdentifier);\n var categoryScoreIdentifier = formatCategoryOutcome(category, total[categoryOutcome]);\n var categoryCountIdentifier = formatCategoryOutcome(category, max[categoryOutcome]);\n\n addCutScoreOutcomes(\n outcomes,\n categoryOutcomeIdentifier,\n categoryScoreIdentifier,\n categoryCountIdentifier,\n cutScore\n );\n\n if (descriptor.categoryFeedback) {\n addFeedbackScoreOutcomes(\n outcomes,\n formatCategoryOutcome(category, descriptor.categoryFeedback),\n categoryOutcomeIdentifier,\n descriptor.feedbackOk,\n descriptor.feedbackFailed\n );\n }\n });\n }\n\n return outcomes;\n }\n };\n\n var scoringHelper = {\n /**\n * Checks the test model against outcome processing mode.\n * Initializes the scoring property accordingly.\n *\n * @param {modelOverseer} modelOverseer\n * @throws {TypeError} if the modelOverseer is invalid\n * @fires modelOverseer#scoring-init\n * @fires modelOverseer#scoring-generate\n * @fires modelOverseer#scoring-write\n */\n init: function init(modelOverseer) {\n var model;\n\n if (!modelOverseer || !_.isFunction(modelOverseer.getModel)) {\n throw new TypeError('You must provide a valid modelOverseer');\n }\n\n model = modelOverseer.getModel();\n\n // detect the score processing mode and build the descriptor used to manage the UI\n model.scoring = detectScoring(modelOverseer);\n\n modelOverseer\n .on('scoring-change category-change delete', function () {\n /**\n * Regenerates the outcomes on any significant changes.\n * After the outcomes have been generated a write is needed to actually apply the data.\n * Other component can listen to this event and eventually prevent the write to happen.\n * @event modelOverseer#scoring-generate\n * @param {Object} outcomes\n */\n modelOverseer.trigger('scoring-generate', scoringHelper.generate(modelOverseer));\n })\n .on('scoring-generate', function (outcomes) {\n outcomeHelper.replaceOutcomes(model, outcomes);\n\n /**\n * The generated outcome have just been applied on the model.\n * @event modelOverseer#scoring-write\n * @param {Object} testModel\n */\n modelOverseer.trigger('scoring-write', model);\n })\n\n /**\n * @event modelOverseer#scoring-init\n * @param {Object} testModel\n */\n .trigger('scoring-init', model);\n },\n\n /**\n * If the processing mode has been set, generates the outcomes that define the scoring.\n *\n * @param {modelOverseer} modelOverseer\n * @returns {Object}\n * @throws {TypeError} if the modelOverseer is invalid or the processing mode is unknown\n */\n generate: function generate(modelOverseer) {\n var model, scoring, outcomes, outcomeRecipe, recipes, categories;\n\n if (!modelOverseer || !_.isFunction(modelOverseer.getModel)) {\n throw new TypeError('You must provide a valid modelOverseer');\n }\n\n model = modelOverseer.getModel();\n scoring = model.scoring;\n outcomes = getOutcomes(model);\n\n // write the score processing mode by generating the outcomes variables, but only if the mode has been set\n if (scoring) {\n outcomeRecipe = outcomesRecipes[scoring.outcomeProcessing];\n if (outcomeRecipe) {\n if (outcomeRecipe.clean) {\n // erase the existing rules, they will be replaced by those that are defined here\n removeScoring(outcomes);\n }\n\n // get the recipes that define the outcomes, include sub-recipes if any\n recipes = getRecipes(outcomeRecipe);\n\n // only get the categories if requested\n if (handleCategories(modelOverseer)) {\n categories = modelOverseer.getCategories();\n }\n\n // will generate outcomes based of the defined recipe\n _.forEach(recipes, function (recipe) {\n var writer = outcomesWriters[recipe.writer];\n writer(recipe, scoring, outcomes, categories);\n });\n } else {\n throw new Error(`Unknown score processing mode: ${scoring.outcomeProcessing}`);\n }\n }\n\n return outcomes;\n }\n };\n\n /**\n * Creates an outcome and the rule that process the total score\n *\n * @param {Object} model\n * @param {Object} scoring\n * @param {String} identifier\n * @param {Boolean} [weight]\n * @param {String} [category]\n */\n function addTotalScoreOutcomes(model, scoring, identifier, weight, category) {\n var outcome = outcomeHelper.createOutcome(identifier, baseTypeHelper.FLOAT);\n var processingRule = processingRuleHelper.setOutcomeValue(\n identifier,\n processingRuleHelper.sum(\n processingRuleHelper.testVariables(\n scoring.scoreIdentifier,\n -1,\n weight && scoring.weightIdentifier,\n category\n )\n )\n );\n\n outcomeHelper.addOutcome(model, outcome, processingRule);\n }\n\n /**\n * Creates an outcome and the rule that process the maximum score\n *\n * @param {Object} model\n * @param {Object} scoring\n * @param {String} identifier\n * @param {Boolean} [weight]\n * @param {String} [category]\n */\n function addMaxScoreOutcomes(model, scoring, identifier, weight, category) {\n var outcome = outcomeHelper.createOutcome(identifier, baseTypeHelper.FLOAT);\n var processingRule = processingRuleHelper.setOutcomeValue(\n identifier,\n processingRuleHelper.sum(\n processingRuleHelper.testVariables('MAXSCORE', -1, weight && scoring.weightIdentifier, category)\n )\n );\n outcomeHelper.addOutcome(model, outcome, processingRule);\n }\n\n /**\n * Create an outcome and the rule that process the score ratio\n *\n * @param {Object} model\n * @param {String} identifier\n * @param {String} identifierTotal\n * @param {String} identifierMax\n */\n function addRatioOutcomes(model, identifier, identifierTotal, identifierMax) {\n var outcome = outcomeHelper.createOutcome(identifier, baseTypeHelper.FLOAT);\n var outcomeCondition = processingRuleHelper.outcomeCondition(\n processingRuleHelper.outcomeIf(\n processingRuleHelper.isNull(processingRuleHelper.variable(identifierMax)),\n processingRuleHelper.setOutcomeValue(\n identifier,\n processingRuleHelper.baseValue(0, baseTypeHelper.FLOAT)\n )\n ),\n processingRuleHelper.outcomeElse(\n processingRuleHelper.setOutcomeValue(\n identifier,\n processingRuleHelper.divide(\n processingRuleHelper.variable(identifierTotal),\n processingRuleHelper.variable(identifierMax)\n )\n )\n )\n );\n\n outcomeHelper.addOutcome(model, outcome);\n outcomeHelper.addOutcomeProcessing(model, outcomeCondition);\n }\n\n /**\n * Creates an outcome and the rule that process the cut score by category\n *\n * @param {Object} model\n * @param {String} identifier\n * @param {String} scoreIdentifier\n * @param {String} countIdentifier\n * @param {String|Number} cutScore\n */\n function addCutScoreOutcomes(model, identifier, scoreIdentifier, countIdentifier, cutScore) {\n var outcome = outcomeHelper.createOutcome(identifier, baseTypeHelper.BOOLEAN);\n var processingRule = processingRuleHelper.setOutcomeValue(\n identifier,\n processingRuleHelper.gte(\n processingRuleHelper.divide(\n processingRuleHelper.variable(scoreIdentifier),\n processingRuleHelper.variable(countIdentifier)\n ),\n processingRuleHelper.baseValue(cutScore, baseTypeHelper.FLOAT)\n )\n );\n\n outcomeHelper.addOutcome(model, outcome, processingRule);\n }\n\n /**\n * Creates an outcome and the rule that process the global cut score\n *\n * @param {Object} model\n * @param {String} identifier\n * @param {String} ratioIdentifier\n * @param {String|Number} cutScore\n */\n function addGlobalCutScoreOutcomes(model, identifier, ratioIdentifier, cutScore) {\n var outcome = outcomeHelper.createOutcome(identifier, baseTypeHelper.BOOLEAN);\n var processingRule = processingRuleHelper.setOutcomeValue(\n identifier,\n processingRuleHelper.gte(\n processingRuleHelper.variable(ratioIdentifier),\n processingRuleHelper.baseValue(cutScore, baseTypeHelper.FLOAT)\n )\n );\n\n outcomeHelper.addOutcome(model, outcome, processingRule);\n }\n\n /**\n * Creates an outcome and the rule that process the score feedback\n *\n * @param {Object} model\n * @param {String} identifier\n * @param {String} variable\n * @param {String} passed\n * @param {String} notPassed\n */\n function addFeedbackScoreOutcomes(model, identifier, variable, passed, notPassed) {\n var type = baseTypeHelper.IDENTIFIER;\n var outcome = outcomeHelper.createOutcome(identifier, type);\n var processingRule = processingRuleHelper.outcomeCondition(\n processingRuleHelper.outcomeIf(\n processingRuleHelper.match(\n processingRuleHelper.variable(variable),\n processingRuleHelper.baseValue(true, baseTypeHelper.BOOLEAN)\n ),\n processingRuleHelper.setOutcomeValue(identifier, processingRuleHelper.baseValue(passed, type))\n ),\n processingRuleHelper.outcomeElse(\n processingRuleHelper.setOutcomeValue(identifier, processingRuleHelper.baseValue(notPassed, type))\n )\n );\n\n outcomeHelper.addOutcome(model, outcome);\n outcomeHelper.addOutcomeProcessing(model, processingRule);\n }\n\n /**\n * Formats the identifier of a category outcome\n * @param {String} category\n * @param {String} template\n * @returns {String}\n */\n function formatCategoryOutcome(category, template) {\n return format(template, category.toUpperCase());\n }\n\n /**\n * Checks whether an identifier belongs to a particular recipe\n * @param {String} identifier\n * @param {Object} recipe\n * @param {Boolean} [onlyCategories]\n * @returns {Boolean}\n */\n function belongToRecipe(identifier, recipe, onlyCategories) {\n var match = false;\n if (recipe.signature && recipe.signature.test(identifier)) {\n match = true;\n if (onlyCategories) {\n _.forEach(recipe.outcomes, function (outcome) {\n if (\n outcome.identifier === identifier ||\n (outcome.weighted && outcome.weighted === identifier) ||\n (outcome.feedback && outcome.feedback === identifier)\n ) {\n match = false;\n return false;\n }\n });\n }\n }\n return match;\n }\n\n /**\n * Checks if all the outcomes are related to the recipe\n * @param {Object} outcomeRecipe\n * @param {Array} outcomes\n * @param {Array} categories\n * @returns {Boolean}\n */\n function matchRecipe(outcomeRecipe, outcomes, categories) {\n var signatures = getSignatures(outcomeRecipe);\n var match = true;\n\n // check the outcomes definitions against the provided identifier\n function matchRecipeOutcome(recipe, identifier) {\n var outcomeMatch = false;\n\n // first level, the signature must match\n if (recipe.signature && recipe.signature.test(identifier)) {\n _.forEach(recipe.outcomes, function (outcome) {\n // second level, the main identifier must match\n if (\n outcome.identifier !== identifier &&\n (!outcome.weighted || (outcome.weighted && outcome.weighted !== identifier)) &&\n (!outcome.feedback || (outcome.feedback && outcome.feedback !== identifier))\n ) {\n if (categories) {\n // third level, a category must match\n _.forEach(categories, function (category) {\n if (\n outcome.categoryIdentifier &&\n identifier === formatCategoryOutcome(category, outcome.categoryIdentifier)\n ) {\n outcomeMatch = true;\n } else if (\n outcome.categoryWeighted &&\n identifier === formatCategoryOutcome(category, outcome.categoryWeighted)\n ) {\n outcomeMatch = true;\n } else if (\n outcome.categoryFeedback &&\n identifier === formatCategoryOutcome(category, outcome.categoryFeedback)\n ) {\n outcomeMatch = true;\n }\n // found something?\n if (outcomeMatch) {\n return false;\n }\n });\n }\n } else {\n outcomeMatch = true;\n }\n\n // found something?\n if (outcomeMatch) {\n return false;\n }\n });\n }\n\n if (!outcomeMatch && recipe.include) {\n outcomeMatch = matchRecipeOutcome(outcomesRecipes[recipe.include], identifier);\n }\n\n return outcomeMatch;\n }\n\n // only check the outcomes that are related to the scoring mode\n _.forEach(outcomes, function (identifier) {\n var signatureMatch = false;\n _.forEach(signatures, function (signature) {\n if (signature.test(identifier)) {\n signatureMatch = true;\n return false;\n }\n });\n\n if (signatureMatch) {\n match = matchRecipeOutcome(outcomeRecipe, identifier);\n\n if (!match) {\n return false;\n }\n }\n });\n\n return match;\n }\n\n /**\n * Gets all the outcomes signatures related to a scoring mode\n * @param {Object} recipe\n * @returns {Array}\n */\n function getSignatures(recipe) {\n var signatures = [];\n\n // list the signatures for each processing mode, taking care of includes\n while (recipe) {\n if (recipe.signature) {\n signatures.push(recipe.signature);\n }\n recipe = recipe.include && outcomesRecipes[recipe.include];\n }\n\n return signatures;\n }\n\n /**\n * Gets all the outcomes recipes related to a scoring mode\n * @param {Object} recipe\n * @returns {Array}\n */\n function getRecipes(recipe) {\n var descriptors = [];\n\n // get the recipes that define the outcomes, include sub-recipes if any\n while (recipe) {\n if (recipe.outcomes) {\n descriptors = [].concat(recipe.outcomes, descriptors);\n }\n recipe = recipe.include && outcomesRecipes[recipe.include];\n }\n\n return descriptors;\n }\n\n /**\n * Checks if an outcome is related to the outcomes recipe,\n * then returns the recipe descriptor.\n * @param {Object|String} outcome\n * @returns {Object}\n */\n function getOutcomesRecipe(outcome) {\n var identifier = outcomeHelper.getOutcomeIdentifier(outcome);\n var mode = null;\n _.forEach(outcomesRecipes, function (processingRecipe) {\n if (belongToRecipe(identifier, processingRecipe)) {\n mode = processingRecipe;\n return false;\n }\n });\n return mode;\n }\n\n /**\n * Gets the score processing modes from a list of outcomes\n * @param {Array} outcomes\n * @returns {Array}\n */\n function listScoringModes(outcomes) {\n var modes = {};\n _.forEach(outcomes, function (outcome) {\n var recipe = getOutcomesRecipe(outcome);\n if (recipe) {\n modes[recipe.key] = true;\n }\n });\n return _.keys(modes);\n }\n\n /**\n * Checks whether the categories have to be taken into account\n * @param {modelOverseer} modelOverseer\n * @returns {Boolean}\n */\n function handleCategories(modelOverseer) {\n var model = modelOverseer.getModel();\n return !!(model.scoring && model.scoring.categoryScore);\n }\n\n /**\n * Checks if the test model contains outcomes for categories\n * @param {Object} model\n * @returns {Boolean}\n */\n function hasCategoryOutcome(model) {\n var categoryOutcomes = false;\n _.forEach(outcomeHelper.getOutcomeDeclarations(model), function (outcomeDeclaration) {\n var identifier = outcomeHelper.getOutcomeIdentifier(outcomeDeclaration);\n _.forEach(outcomesRecipes, function (processingRecipe) {\n if (belongToRecipe(identifier, processingRecipe, true)) {\n categoryOutcomes = true;\n return false;\n }\n });\n });\n return categoryOutcomes;\n }\n\n /**\n * Gets the defined cut score from the outcome rules\n * @param {Object} model\n * @returns {Number}\n */\n function getCutScore(model) {\n var values = _(outcomeHelper.getOutcomeProcessingRules(model))\n .map(function (outcome) {\n return outcomeHelper.getProcessingRuleProperty(outcome, 'setOutcomeValue.gte.baseValue.value');\n })\n .compact()\n .uniq()\n .value();\n if (_.isEmpty(values)) {\n values = [defaultCutScore];\n }\n return Math.max(0, _.max(values));\n }\n\n /**\n * Gets the defined weight identifier from the outcome rules\n * @param {Object} model\n * @returns {String}\n */\n function getWeightIdentifier(model) {\n var values = [];\n outcomeHelper.eachOutcomeProcessingRuleExpressions(model, function (processingRule) {\n if (processingRule['qti-type'] === 'testVariables' && processingRule.weightIdentifier) {\n values.push(processingRule.weightIdentifier);\n }\n });\n values = _(values).compact().uniq().value();\n\n return values.length ? values[0] : '';\n }\n\n /**\n * Detects the outcome processing mode for the scoring\n * @param {modelOverseer} modelOverseer\n * @returns {String}\n */\n function getOutcomeProcessing(modelOverseer) {\n var model = modelOverseer.getModel();\n var outcomeDeclarations = outcomeHelper.getOutcomeDeclarations(model);\n var outcomeRules = outcomeHelper.getOutcomeProcessingRules(model);\n\n // walk through each outcome declaration, and tries to identify the score processing mode\n var declarations = listScoringModes(outcomeDeclarations);\n var processing = listScoringModes(outcomeRules);\n var diff = _.difference(declarations, processing);\n var count = _.size(declarations);\n var included;\n\n // default fallback, applied when several modes are detected at the same time\n var outcomeProcessing = 'custom';\n\n // set the score processing mode with respect to the found outcomes\n if (count === _.size(processing)) {\n if (!count) {\n // no mode detected, set the mode to none\n outcomeProcessing = 'none';\n } else if (_.isEmpty(diff)) {\n if (count > 1) {\n // several modes detected, try to reduce the list by detecting includes\n included = [];\n _.forEach(declarations, function (mode) {\n if (outcomesRecipes[mode] && outcomesRecipes[mode].include) {\n included.push(outcomesRecipes[mode].include);\n }\n });\n processing = _.difference(processing, included);\n count = _.size(processing);\n }\n\n if (count === 1) {\n // single mode detected, keep the last got key\n outcomeProcessing = processing[0];\n\n // check if all outcomes are strictly related to the detected mode\n if (\n !matchRecipe(\n outcomesRecipes[outcomeProcessing],\n modelOverseer.getOutcomesNames(),\n modelOverseer.getCategories()\n )\n ) {\n outcomeProcessing = 'custom';\n }\n }\n }\n }\n\n return outcomeProcessing;\n }\n\n /**\n * Detects the score processing mode and builds the descriptor used to manage the UI.\n * @param {modelOverseer} modelOverseer\n * @returns {Object}\n */\n function detectScoring(modelOverseer) {\n var model = modelOverseer.getModel();\n return {\n modes: processingModes,\n scoreIdentifier: defaultScoreIdentifier,\n weightIdentifier: getWeightIdentifier(model),\n cutScore: getCutScore(model),\n categoryScore: hasCategoryOutcome(model),\n outcomeProcessing: getOutcomeProcessing(modelOverseer)\n };\n }\n\n /**\n * Removes all scoring outcomes\n * @param {Object} model\n */\n function removeScoring(model) {\n var scoringOutcomes = _.indexBy(outcomeHelper.listOutcomes(model, getOutcomesRecipe), function (outcome) {\n return outcome;\n });\n\n outcomeHelper.removeOutcomes(model, function (outcome) {\n var match = false;\n\n function browseExpressions(processingRule) {\n if (_.isArray(processingRule)) {\n _.forEach(processingRule, browseExpressions);\n } else if (processingRule) {\n if (scoringOutcomes[outcomeHelper.getOutcomeIdentifier(processingRule)]) {\n match = true;\n }\n\n if (!match && processingRule.expression) {\n browseExpressions(processingRule.expression);\n }\n if (!match && processingRule.expressions) {\n browseExpressions(processingRule.expressions);\n }\n if (!match && processingRule.outcomeRules) {\n browseExpressions(processingRule.outcomeRules);\n }\n if (!match && processingRule.outcomeIf) {\n browseExpressions(processingRule.outcomeIf);\n }\n if (!match && processingRule.outcomeElse) {\n browseExpressions(processingRule.outcomeElse);\n }\n }\n }\n\n if (outcome['qti-type'] === 'outcomeCondition') {\n browseExpressions(outcome);\n } else {\n match = !!scoringOutcomes[outcomeHelper.getOutcomeIdentifier(outcome)];\n }\n return match;\n });\n }\n\n /**\n * Gets a copy of the list of outcomes, provides the same structure as the model\n * @param {Object} model\n * @returns {Object}\n */\n function getOutcomes(model) {\n return {\n outcomeDeclarations: [].concat(outcomeHelper.getOutcomeDeclarations(model)),\n outcomeProcessing: {\n outcomeRules: [].concat(outcomeHelper.getOutcomeProcessingRules(model))\n }\n };\n }\n\n return scoringHelper;\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2020 Open Assessment Technologies SA ;\n */\n/**\n * Track the change within the testCreator\n * @author Juan Luis Gutierrez Dos Santos \n */\ndefine('taoQtiTest/controller/creator/helpers/changeTracker',[\n 'jquery',\n 'lodash',\n 'i18n',\n 'lib/uuid',\n 'core/eventifier',\n 'ui/dialog',\n 'ui/feedback',\n], function (\n $,\n _,\n __,\n uuid,\n eventifier,\n dialog,\n feedback\n) {\n 'use strict';\n\n /**\n * The messages asking to save\n */\n const messages = {\n preview: __('The test needs to be saved before it can be previewed.'),\n leave: __('The test has unsaved changes, are you sure you want to leave?'),\n exit: __('The test has unsaved changes, would you like to save it?'),\n leaveWhenInvalid: __('If you leave the test, your changes will not be saved due to invalid test settings. Are you sure you wish to leave?')\n };\n const buttonsYesNo = [{\n id: 'dontsave',\n type: 'regular',\n label: __('YES'),\n close: true\n }, {\n id: 'cancel',\n type: 'regular',\n label: __('NO'),\n close: true\n }];\n const buttonsCancelSave = [{\n id: 'dontsave',\n type: 'regular',\n label: __('Don\\'t save'),\n close: true\n }, {\n id: 'cancel',\n type: 'regular',\n label: __('Cancel'),\n close: true\n }, {\n id: 'save',\n type: 'info',\n label: __('Save'),\n close: true\n }];\n\n /**\n *\n * @param {HTMLElement} container\n * @param {testCreator} testCreator\n * @param {String} [wrapperSelector]\n * @returns {changeTracker}\n */\n function changeTrackerFactory(container, testCreator, wrapperSelector = 'body') {\n\n // internal namespace for global registered events\n const eventNS = `.ct-${uuid(8, 16)}`;\n\n // keep the value of the test before changes\n let originalTest;\n\n // are we in the middle of the confirm process?\n let asking = false;\n\n /**\n * @typedef {Object} changeTracker\n */\n const changeTracker = eventifier({\n /**\n * Initialized the changed state\n * @returns {changeTracker}\n */\n init() {\n originalTest = this.getSerializedTest();\n\n return this;\n },\n\n /**\n * Installs the change tracker, registers listeners\n * @returns {changeTracker}\n */\n install() {\n this.init();\n asking = false;\n\n // add a browser popup to prevent leaving the browser\n $(window)\n .on(`beforeunload${eventNS}`, () => {\n if (!asking && this.hasChanged()) {\n return messages.leave;\n }\n })\n // since we don't know how to prevent history based events, we just stop the handling\n .on('popstate', this.uninstall);\n\n // every click outside the authoring\n $(wrapperSelector)\n .on(`click${eventNS}`, e => {\n if (!$.contains(container, e.target) && this.hasChanged() && e.target.classList[0] !== 'icon-close') {\n e.stopImmediatePropagation();\n e.preventDefault();\n\n if (testCreator.isTestHasErrors()) {\n this.confirmBefore('leaveWhenInvalid')\n .then(whatToDo => {\n this.ifWantSave(whatToDo);\n this.uninstall();\n e.target.click();\n })\n .catch(() => {});\n } else {\n this.confirmBefore('exit')\n .then(whatToDo => {\n this.ifWantSave(whatToDo);\n this.uninstall();\n e.target.click();\n })\n //do nothing but prevent uncaught error\n .catch(() => {});\n }\n }\n });\n\n testCreator\n .on(`ready${eventNS} saved${eventNS}`, () => this.init())\n .before(`creatorclose${eventNS}`, () => {\n let leavingStatus = 'leave';\n if(testCreator.isTestHasErrors()) {\n leavingStatus ='leaveWhenInvalid';\n }\n return this.confirmBefore(leavingStatus).then(whatToDo => {\n this.ifWantSave(whatToDo);\n })})\n .before(`preview${eventNS}`, () => this.confirmBefore('preview').then(whatToDo => {\n if(testCreator.isTestHasErrors()){\n feedback().warning(`${__('The test cannot be saved because it currently contains invalid settings.\\n' +\n 'Please fix the invalid settings and try again.')}`);\n } else {\n this.ifWantSave(whatToDo);\n }\n }))\n .before(`exit${eventNS}`, () => this.uninstall());\n\n return this;\n },\n\n /**\n * Check if we need to trigger save\n * @param {Object} whatToDo\n * @fires {save}\n */\n ifWantSave(whatToDo) {\n if (whatToDo && whatToDo.ifWantSave) {\n testCreator.trigger('save');\n }\n },\n\n\n /**\n * Uninstalls the change tracker, unregisters listeners\n * @returns {changeTracker}\n */\n uninstall() {\n // remove all global handlers\n $(window.document)\n .off(eventNS);\n $(window).off(eventNS);\n $(wrapperSelector).off(eventNS);\n testCreator.off(eventNS);\n\n return this;\n },\n\n /**\n * Displays a confirmation dialog,\n * The \"ok\" button will save and resolve.\n * The \"cancel\" button will reject.\n *\n * @param {String} message - the confirm message to display\n * @returns {Promise} resolves once saved\n */\n confirmBefore(message) {\n // if a key is given load the related message\n message = messages[message] || message;\n\n return new Promise((resolve, reject) => {\n if (asking) {\n return reject();\n }\n\n if (!this.hasChanged()) {\n return resolve();\n }\n\n asking = true;\n let confirmDlg;\n\n // chosses what buttons to display depending on the message\n if(message === messages.leaveWhenInvalid) {\n confirmDlg = dialog({\n message: message,\n buttons: buttonsYesNo,\n autoRender: true,\n autoDestroy: true,\n onDontsaveBtn: () => resolve({ ifWantSave: false }),\n onCancelBtn: () => {\n confirmDlg.hide();\n reject({ cancel: true });\n }\n });\n } else {\n confirmDlg = dialog({\n message: message,\n buttons: buttonsCancelSave,\n autoRender: true,\n autoDestroy: true,\n onSaveBtn: () => resolve({ ifWantSave: true }),\n onDontsaveBtn: () => resolve({ ifWantSave: false }),\n onCancelBtn: () => {\n confirmDlg.hide();\n reject({ cancel: true });\n }\n });\n }\n confirmDlg.on('closed.modal', () => asking = false);\n });\n },\n\n /**\n * Does the test have changed?\n * @returns {Boolean}\n */\n hasChanged() {\n const currentTest = this.getSerializedTest();\n return originalTest !== currentTest || (null === currentTest && null === originalTest);\n },\n\n /**\n * Get a string representation of the current test, used for comparison\n * @returns {String} the test\n */\n getSerializedTest() {\n let serialized = '';\n try {\n // create a string from the test content\n serialized = JSON.stringify(testCreator.getModelOverseer().getModel());\n\n // sometimes the creator strip spaces between tags, so we do the same\n serialized = serialized.replace(/ {2,}/g, ' ');\n } catch (err) {\n serialized = null;\n }\n return serialized;\n }\n });\n\n return changeTracker.install();\n }\n\n return changeTrackerFactory;\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2014-2021 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);\n */\n/**\n * @author Bertrand Chevrier \n */\ndefine('taoQtiTest/controller/creator/creator',[\n 'module',\n 'jquery',\n 'lodash',\n 'helpers',\n 'i18n',\n 'services/features',\n 'ui/feedback',\n 'core/databindcontroller',\n 'taoQtiTest/controller/creator/qtiTestCreator',\n 'taoQtiTest/controller/creator/views/item',\n 'taoQtiTest/controller/creator/views/test',\n 'taoQtiTest/controller/creator/views/testpart',\n 'taoQtiTest/controller/creator/views/section',\n 'taoQtiTest/controller/creator/views/itemref',\n 'taoQtiTest/controller/creator/encoders/dom2qti',\n 'taoQtiTest/controller/creator/templates/index',\n 'taoQtiTest/controller/creator/helpers/qtiTest',\n 'taoQtiTest/controller/creator/helpers/scoring',\n 'taoQtiTest/controller/creator/helpers/categorySelector',\n 'taoQtiTest/controller/creator/helpers/validators',\n 'taoQtiTest/controller/creator/helpers/changeTracker',\n 'taoQtiTest/controller/creator/helpers/featureVisibility',\n 'taoTests/previewer/factory',\n 'core/logger',\n 'taoQtiTest/controller/creator/views/subsection'\n], function (\n module,\n $,\n _,\n helpers,\n __,\n features,\n feedback,\n DataBindController,\n qtiTestCreatorFactory,\n itemView,\n testView,\n testPartView,\n sectionView,\n itemrefView,\n Dom2QtiEncoder,\n templates,\n qtiTestHelper,\n scoringHelper,\n categorySelector,\n validators,\n changeTracker,\n featureVisibility,\n previewerFactory,\n loggerFactory,\n subsectionView\n) {\n ('use strict');\n const logger = loggerFactory('taoQtiTest/controller/creator');\n\n /**\n * The test creator controller is the main entry point\n * and orchestrates data retrieval and view/components loading.\n * @exports creator/controller\n */\n const Controller = {\n routes: {},\n\n /**\n * Start the controller, main entry method.\n * @public\n * @param {Object} options\n * @param {Object} options.labels - the list of item's labels to give to the ItemView\n * @param {Object} options.routes - action's urls\n * @param {Object} options.categoriesPresets - predefined category that can be set at the item or section level\n * @param {Boolean} [options.guidedNavigation=false] - feature flag for the guided navigation\n */\n start(options) {\n const $container = $('#test-creator');\n const $saver = $('#saver');\n const $previewer = $('#previewer');\n const $back = $('#authoringBack');\n\n let creatorContext;\n let binder;\n let binderOptions;\n let modelOverseer;\n\n this.identifiers = [];\n\n options = _.merge(module.config(), options || {});\n options.routes = options.routes || {};\n options.labels = options.labels || {};\n options.categoriesPresets = featureVisibility.filterVisiblePresets(options.categoriesPresets) || {};\n options.guidedNavigation = options.guidedNavigation === true;\n\n categorySelector.setPresets(options.categoriesPresets);\n\n //back button\n $back.on('click', e => {\n e.preventDefault();\n creatorContext.trigger('creatorclose');\n });\n\n //preview button\n if (!Object.keys(options.labels).length) {\n $previewer.attr('disabled', true).addClass('disabled');\n }\n $previewer.on('click', e => {\n e.preventDefault();\n if (!$previewer.hasClass('disabled')) {\n creatorContext.trigger('preview');\n }\n });\n const isTestContainsItems = () => {\n if ($container.find('.test-content').find('.itemref').length) {\n $previewer.attr('disabled', false).removeClass('disabled');\n return true;\n } else {\n $previewer.attr('disabled', true).addClass('disabled');\n return false;\n }\n };\n\n //set up the ItemView, give it a configured loadItems ref\n itemView($('.test-creator-items .item-selection', $container));\n\n // forwards some binder events to the model overseer\n $container.on('change.binder delete.binder', (e, model) => {\n if (e.namespace === 'binder' && model && modelOverseer) {\n modelOverseer.trigger(e.type, model);\n }\n });\n\n //Data Binding options\n binderOptions = _.merge(options.routes, {\n filters: {\n isItemRef: value => qtiTestHelper.filterQtiType(value, 'assessmentItemRef'),\n isSection: value => qtiTestHelper.filterQtiType(value, 'assessmentSection')\n },\n encoders: {\n dom2qti: Dom2QtiEncoder\n },\n templates: templates,\n beforeSave(model) {\n //ensure the qti-type is present\n qtiTestHelper.addMissingQtiType(model);\n\n //apply consolidation rules\n qtiTestHelper.consolidateModel(model);\n\n //validate the model\n try {\n validators.validateModel(model);\n } catch (err) {\n $saver.attr('disabled', false).removeClass('disabled');\n feedback().error(`${__('The test has not been saved.')} + ${err}`);\n return false;\n }\n return true;\n }\n });\n\n //set up the databinder\n binder = DataBindController.takeControl($container, binderOptions).get(model => {\n creatorContext = qtiTestCreatorFactory($container, {\n uri: options.uri,\n labels: options.labels,\n routes: options.routes,\n guidedNavigation: options.guidedNavigation\n });\n creatorContext.setTestModel(model);\n modelOverseer = creatorContext.getModelOverseer();\n\n //detect the scoring mode\n scoringHelper.init(modelOverseer);\n\n //register validators\n validators.registerValidators(modelOverseer);\n\n //once model is loaded, we set up the test view\n testView(creatorContext);\n\n //listen for changes to update available actions\n testPartView.listenActionState();\n sectionView.listenActionState();\n subsectionView.listenActionState();\n itemrefView.listenActionState();\n\n changeTracker($container.get()[0], creatorContext, '.content-wrap');\n\n creatorContext.on('save', function () {\n if (!$saver.hasClass('disabled')) {\n $saver.prop('disabled', true).addClass('disabled');\n binder.save(\n function () {\n $saver.prop('disabled', false).removeClass('disabled');\n feedback().success(__('Test Saved'));\n isTestContainsItems();\n creatorContext.trigger('saved');\n },\n function () {\n $saver.prop('disabled', false).removeClass('disabled');\n }\n );\n }\n });\n\n creatorContext.on('preview', function () {\n if (isTestContainsItems() && !creatorContext.isTestHasErrors()) {\n const saveUrl = options.routes.save;\n const testUri = saveUrl.slice(saveUrl.indexOf('uri=') + 4);\n const config = module.config();\n return previewerFactory(config.provider, decodeURIComponent(testUri), {\n readOnly: false,\n fullPage: true,\n pluginsOptions: config.pluginsOptions\n }).catch(err => {\n logger.error(err);\n feedback().error(\n __('Test Preview is not installed, please contact to your administrator.')\n );\n });\n }\n });\n\n creatorContext.on('creatorclose', () => {\n creatorContext.trigger('exit');\n window.history.back();\n });\n });\n\n //the save button triggers binder's save action.\n $saver.on('click', function (event) {\n if (creatorContext.isTestHasErrors()) {\n event.preventDefault();\n feedback().warning(\n __('The test cannot be saved because it currently contains invalid settings.\\nPlease fix the invalid settings and try again.')\n );\n } else {\n creatorContext.trigger('save');\n }\n });\n }\n };\n\n return Controller;\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2015 (original work) Open Assessment Technologies SA ;\n */\n/**\n * @author Jean-Sébastien Conan \n */\ndefine('taoQtiTest/controller/creator/helpers/ckConfigurator',['ui/ckeditor/ckConfigurator', 'mathJax'], function(ckConfigurator) {\n 'use strict';\n\n /**\n * Generate a configuration object for CKEDITOR\n *\n * @param editor instance of ckeditor\n * @param toolbarType block | inline | flow | qtiBlock | qtiInline | qtiFlow | reset to get back to normal\n * @param {Object} [options] - is based on the CKEDITOR config object with some additional sugar\n * Note that it's here you need to add parameters for the resource manager.\n * Some options are not covered in http://docs.ckeditor.com/#!/api/CKEDITOR.config\n * @param [options.dtdOverrides] - @see dtdOverrides which pre-defines them\n * @param {Object} [options.positionedPlugins] - @see ckConfig.positionedPlugins\n * @param {Boolean} [options.qtiImage] - enables the qtiImage plugin\n * @param {Boolean} [options.qtiInclude] - enables the qtiInclude plugin\n * @param {Boolean} [options.underline] - enables the underline plugin\n * @param {Boolean} [options.mathJax] - enables the mathJax plugin\n *\n * @see http://docs.ckeditor.com/#!/api/CKEDITOR.config\n */\n var getConfig = function(editor, toolbarType, options){\n options = options || {};\n\n options.underline = true;\n\n return ckConfigurator.getConfig(editor, toolbarType, options);\n };\n\n return {\n getConfig : getConfig\n };\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2014 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);\n *\n *\n */\n\n//@see http://forge.taotesting.com/projects/tao/wiki/Front_js\ndefine('taoQtiTest/controller/routes',[],function () {\n 'use strict';\n\n return {\n Creator: {\n css: 'creator',\n actions: {\n index: 'controller/creator/creator'\n }\n },\n XmlEditor: {\n actions: {\n edit: 'controller/content/edit'\n }\n }\n };\n});\n\n","\ndefine('css!taoQtiTestCss/new-test-runner',[],function(){});\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2016-2019 (original work) Open Assessment Technologies SA ;\n */\n\n/**\n * Test runner controller entry\n *\n * @author Bertrand Chevrier \n */\ndefine('taoQtiTest/controller/runner/runner',[\n 'jquery',\n 'lodash',\n 'i18n',\n 'context',\n 'module',\n 'core/router',\n 'core/logger',\n 'layout/loading-bar',\n 'ui/feedback',\n 'util/url',\n 'util/locale',\n 'taoTests/runner/providerLoader',\n 'taoTests/runner/runner',\n 'css!taoQtiTestCss/new-test-runner'\n], function (\n $,\n _,\n __,\n context,\n module,\n router,\n loggerFactory,\n loadingBar,\n feedback,\n urlUtil,\n locale,\n providerLoader,\n runner\n) {\n 'use strict';\n\n /**\n * List of options required by the controller\n * @type {String[]}\n */\n const requiredOptions = [\n 'testDefinition',\n 'testCompilation',\n 'serviceCallId',\n 'bootstrap',\n 'options',\n 'providers'\n ];\n\n /**\n * The runner controller\n */\n return {\n\n /**\n * Controller entry point\n *\n * @param {Object} config - the testRunner config\n * @param {String} config.testDefinition - the test definition id\n * @param {String} config.testCompilation - the test compilation id\n * @param {String} config.serviceCallId - the service call id\n * @param {Object} config.bootstrap - contains the extension and the controller to call\n * @param {Object} config.options - the full URL where to return at the final end of the test\n * @param {Object[]} config.providers - the collection of providers to load\n */\n start(config) {\n let exitReason;\n const $container = $('.runner');\n\n const logger = loggerFactory('controller/runner', {\n serviceCallId : config.serviceCallId,\n plugins : config && config.providers && Object.keys(config.providers.plugins)\n });\n\n let preventFeedback = false;\n let errorFeedback = null;\n\n /**\n * Exit the test runner using the configured exitUrl\n * @param {String} [reason] - to add a warning once left\n * @param {String} [level] - error level\n */\n const exit = function exit(reason, level){\n let url = config.options.exitUrl;\n const params = {};\n if (reason) {\n if (!level) {\n level = 'warning';\n }\n params[level] = reason;\n url = urlUtil.build(url, params);\n }\n window.location = url;\n };\n\n /**\n * Handles errors\n * @param {Error} err - the thrown error\n * @param {String} [displayMessage] - an alternate message to display\n */\n const onError = function onError(err, displayMessage) {\n onFeedback(err, displayMessage, \"error\");\n };\n\n /**\n * Handles warnings\n * @param {Error} err - the thrown error\n * @param {String} [displayMessage] - an alternate message to display\n */\n const onWarning = function onWarning(err, displayMessage) {\n onFeedback(err, displayMessage, \"warning\");\n };\n\n /**\n * Handles errors & warnings\n * @param {Error} err - the thrown error\n * @param {String} [displayMessage] - an alternate message to display\n * @param {String} [type] - \"error\" or \"warning\"\n */\n const onFeedback = function onFeedback(err, displayMessage, type) {\n const typeMap = {\n warning: {\n logger: \"warn\",\n feedback: \"warning\"\n },\n error: {\n logger: \"error\",\n feedback: \"error\"\n }\n };\n const loggerByType = logger[typeMap[type].logger];\n const feedbackByType = feedback()[typeMap[type].feedback];\n\n displayMessage = displayMessage || err.message;\n\n if(!_.isString(displayMessage)){\n displayMessage = JSON.stringify(displayMessage);\n }\n loadingBar.stop();\n\n loggerByType({ displayMessage : displayMessage }, err);\n\n if(type === \"error\" && (err.code === 403 || err.code === 500)) {\n displayMessage = `${__('An error occurred during the test, please content your administrator.')} ${displayMessage}`;\n return exit(displayMessage, 'error');\n }\n if (!preventFeedback) {\n errorFeedback = feedbackByType(displayMessage, {timeout: -1});\n }\n };\n\n const moduleConfig = module.config();\n\n loadingBar.start();\n\n // adding attr for RTL languages\n $('.delivery-scope').attr({dir: locale.getLanguageDirection(context.locale)});\n\n // verify required config\n if ( ! requiredOptions.every( option => typeof config[option] !== 'undefined') ) {\n return onError(new TypeError(__('Missing required configuration option %s', name)));\n }\n\n // dispatch any extra registered routes\n if (moduleConfig && _.isArray(moduleConfig.extraRoutes) && moduleConfig.extraRoutes.length) {\n router.dispatch(moduleConfig.extraRoutes);\n }\n\n //for the qti provider to be selected here\n config.provider = Object.assign( config.provider || {}, { runner: 'qti' });\n\n //load the plugins and the proxy provider\n providerLoader(config.providers, context.bundle)\n .then(function (results) {\n\n const testRunnerConfig = _.omit(config, ['providers']);\n testRunnerConfig.renderTo = $container;\n\n if (results.proxy && typeof results.proxy.getAvailableProviders === 'function') {\n const loadedProxies = results.proxy.getAvailableProviders();\n testRunnerConfig.provider.proxy = loadedProxies[0];\n }\n\n logger.debug({\n config: testRunnerConfig,\n providers : config.providers\n }, 'Start test runner');\n\n //instantiate the QtiTestRunner\n runner(config.provider.runner, results.plugins, testRunnerConfig)\n .on('error', onError)\n .on('warning', onWarning)\n .on('ready', function () {\n _.defer(function () {\n $container.removeClass('hidden');\n });\n })\n .on('pause', function(data) {\n if (data && data.reason) {\n exitReason = data.reason;\n }\n })\n .after('destroy', function () {\n this.removeAllListeners();\n\n // at the end, we are redirected to the exit URL\n exit(exitReason);\n })\n\n //FIXME this event should not be triggered on the test runner\n .on('disablefeedbackalerts', function() {\n if (errorFeedback) {\n errorFeedback.close();\n }\n preventFeedback = true;\n })\n\n //FIXME this event should not be triggered on the test runner\n .on('enablefeedbackalerts', function() {\n preventFeedback = false;\n })\n .init();\n })\n .catch(function(err){\n onError(err, __('An error occurred during the test initialization!'));\n });\n }\n };\n});\n\n","/*\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2015 (original work) Open Assessment Technologies SA ;\n *\n */\n\n/**\n * This module allows adding extra buttons in the action bar of the test runner\n *\n */\ndefine('taoQtiTest/testRunner/actionBarHook',[\n 'jquery',\n 'lodash',\n 'core/errorHandler',\n 'core/promise'\n], function ($, _, errorHandler, Promise) {\n\n 'use strict';\n\n /**\n * Events namespace\n * @type {String}\n * @private\n */\n var _ns = '.actionBarHook';\n\n /**\n * We need to access the root document to listen for some events\n * @type {jQuery}\n * @private\n */\n var $doc = $(document);\n\n /**\n * List of loaded and visible hooks\n * @type {Object}\n * @private\n */\n var tools = {};\n\n /**\n * Flag set to true when the item is loaded\n * @type {Boolean}\n * @private\n */\n var itemIsLoaded = false;\n\n // catch the item loaded event\n $doc.off(_ns).on('serviceloaded' + _ns, function() {\n itemIsLoaded = true;\n _.forEach(tools, function(tool) {\n triggerItemLoaded(tool);\n });\n });\n\n /**\n * Check that the toolConfig is correct\n *\n * @param {Object} toolconfig\n * @param {String} toolconfig.hook - the amd module to be loaded to initialize the button\n * @param {String} [toolconfig.label] - the label to be displayed in the button\n * @param {String} [toolconfig.icon] - the icon to be displayed in the button\n * @param {String} [toolconfig.title] - the title to be displayed in the button\n * @param {Array} [toolconfig.items] - an optional list of menu items\n * @returns {Boolean}\n */\n function isValidConfig(toolconfig) {\n return !!(_.isObject(toolconfig) && toolconfig.hook);\n }\n\n /**\n * Triggers the itemLoaded event inside the provided actionBar hook\n * @param {Object} tool\n */\n function triggerItemLoaded(tool) {\n if (tool && tool.itemLoaded) {\n tool.itemLoaded();\n }\n }\n\n /**\n * Init a test runner button from its config\n *\n * @param {String} id\n * @param {Object|String} toolconfig\n * @param {String} toolconfig.hook - the amd module to be loaded to initialize the button\n * @param {String} [toolconfig.label] - the label to be displayed in the button\n * @param {String} [toolconfig.icon] - the icon to be displayed in the button\n * @param {String} [toolconfig.title] - the title to be displayed in the button\n * @param {Array} [toolconfig.items] - an optional list of menu items\n * @param {Object} testContext - the complete state of the test\n * @param {Object} testRunner - the test runner instance\n * @fires ready.actionBarHook when the hook has been initialized\n * @returns {Promise}\n */\n function initQtiTool($toolsContainer, id, toolconfig, testContext, testRunner) {\n\n // the tool is always initialized before the item is loaded, so we can safely false the flag\n itemIsLoaded = false;\n tools[id] = null;\n\n if (_.isString(toolconfig)) {\n toolconfig = {\n hook: toolconfig\n };\n }\n\n return new Promise(function(resolve) {\n if (isValidConfig(toolconfig)) {\n\n require([toolconfig.hook], function (hook) {\n\n var $button;\n var $existingBtn;\n\n if (isValidHook(hook)) {\n //init the control\n hook.init(id, toolconfig, testContext, testRunner);\n\n //if an instance of the tool is already attached, remove it:\n $existingBtn = $toolsContainer.children('[data-control=\"' + id + '\"]');\n if ($existingBtn.length) {\n hook.clear($existingBtn);\n $existingBtn.remove();\n }\n\n //check if the tool is to be available\n if (hook.isVisible()) {\n //keep access to the tool\n tools[id] = hook;\n\n // renders the button from the config\n $button = hook.render();\n\n //only attach the button to the dom when everything is ready\n _appendInOrder($toolsContainer, $button);\n\n //ready !\n $button.trigger('ready' + _ns, [hook]);\n\n //fires the itemLoaded event if the item has already been loaded\n if (itemIsLoaded) {\n triggerItemLoaded(hook);\n }\n }\n\n resolve(hook);\n } else {\n errorHandler.throw(_ns, 'invalid hook format');\n resolve(null);\n }\n\n }, function (e) {\n errorHandler.throw(_ns, 'the hook amd module cannot be found');\n resolve(null);\n });\n\n } else {\n errorHandler.throw(_ns, 'invalid tool config format');\n resolve(null);\n }\n });\n }\n\n /**\n * Append a dom element $button to a $container in a specific order\n * The orders are provided by data-order attribute set to the $button\n *\n * @param {JQuery} $container\n * @param {JQuery} $button\n */\n function _appendInOrder($container, $button) {\n\n var $after, $before;\n var order = $button.data('order');\n\n if ('last' === order) {\n\n $container.append($button);\n\n } else if ('first' === order) {\n\n $container.prepend($button);\n\n } else {\n\n order = _.parseInt(order);\n if (!_.isNaN(order)) {\n\n $container.children('.action').each(function () {\n\n var $btn = $(this),\n _order = $btn.data('order');\n\n if ('last' === _order) {\n\n $before = $btn;\n $after = null;\n\n } else if ('first' === _order) {\n\n $before = null;\n $after = $btn;\n\n } else {\n\n _order = _.parseInt(_order);\n\n if (_.isNaN(_order) || _order > order) {\n $before = $btn;\n $after = null;\n //stops here because $container children returns the dom elements in the dom order\n return false;\n } else if (_order === order) {\n $after = $btn;\n } else if (_order < order) {\n $after = $btn;\n $before = null;\n }\n\n }\n\n });\n\n if ($after) {\n $after.after($button);\n } else if ($before) {\n $before.before($button);\n } else {\n $container.append($button);\n }\n\n } else {\n //unordered buttons are append at the end (including when order equals 0)\n $container.append($button);\n }\n }\n }\n\n /**\n * Check if the hook object is valid\n *\n * @param {Object} hook\n * @param {Function} hook.init\n * @param {Function} hook.clear\n * @param {Function} hook.isVisible\n * @returns {Boolean}\n */\n function isValidHook(hook) {\n return (_.isObject(hook) && _(['init', 'render', 'clear', 'isVisible']).reduce(function (result, method) {\n return result && _.isFunction(hook[method]);\n }, true));\n }\n\n return {\n isValid: isValidConfig,\n initQtiTool: initQtiTool\n };\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2015 (original work) Open Assessment Technologies SA ;\n */\n/**\n * @author Jean-Sébastien Conan \n */\ndefine('taoQtiTest/testRunner/actionBarTools',[\n 'jquery',\n 'lodash',\n 'core/eventifier',\n 'core/promise',\n 'taoQtiTest/testRunner/actionBarHook'\n], function ($, _, eventifier, Promise, actionBarHook) {\n 'use strict';\n\n /**\n * The list of registered actionBar tools\n * @type {Object}\n */\n var registeredQtiTools;\n\n /**\n * The list of actionBar tools instances\n * @type {Object}\n */\n var qtiTools;\n\n /**\n * Manages the actionBar tools\n * @type {Object}\n */\n var actionBarTools = {\n /**\n * Registers the actionBar tools\n * @param {Object} tools\n */\n register : function register(tools) {\n var registerTools = tools || {};\n\n /**\n * @event actionBarTools#beforeregister\n * @param {Object} tools\n * @param {actionBarTools} this\n */\n this.trigger('beforeregister', registerTools, this);\n\n registeredQtiTools = registerTools;\n\n /**\n * @event actionBarTools#afterregister\n * @param {Object} tools\n * @param {actionBarTools} this\n */\n this.trigger('afterregister', registerTools, this);\n },\n\n /**\n * Gets the list of registered tools\n * @returns {Object}\n */\n getRegisteredTools : function getRegisteredTools() {\n return registeredQtiTools || {};\n },\n\n /**\n * Gets a particular tool config\n * @param {String} id\n * @returns {Object}\n */\n getRegistered : function getRegistered(id) {\n return registeredQtiTools && registeredQtiTools[id];\n },\n\n /**\n * Checks if a particular tool is registered\n * @param {String} id\n * @returns {Boolean}\n */\n isRegistered : function isRegistered(id) {\n return !!(registeredQtiTools && registeredQtiTools[id]);\n },\n\n /**\n * Gets a particular tool\n * @param {String} id\n * @returns {Object}\n */\n get : function get(id) {\n return qtiTools && qtiTools[id];\n },\n\n /**\n * Gets the list of tools instances\n * @returns {Array}\n */\n list : function list() {\n return _.values(qtiTools || {});\n },\n\n /**\n * Renders the actionBar\n * @param {String|jQuery|HTMLElement} container - The container in which renders the tools\n * @param {Object} testContext - The assessment test context\n * @param {Object} testRunner - The assessment test runner\n * @param {Function} [callback] - An optional callback fired when all tools have been rendered\n */\n render : function render(container, testContext, testRunner, callback) {\n var self = this;\n var $container = $(container);\n var promises = [];\n\n /**\n * @event actionBarTools#beforerender\n * @param {jQuery} $container\n * @param {Object} testContext\n * @param {Object} testRunner\n * @param {actionBarTools} this\n */\n this.trigger('beforerender', $container, testContext, testRunner, this);\n\n _.forIn(this.getRegisteredTools(), function(toolconfig, id){\n promises.push(actionBarHook.initQtiTool($container, id, toolconfig, testContext, testRunner));\n });\n\n Promise.all(promises).then(function(values) {\n var tools = [];\n qtiTools = {};\n\n _.forEach(values, function(tool) {\n if (tool) {\n tools.push(tool);\n qtiTools[tool.getId()] = tool;\n }\n });\n\n if (_.isFunction(callback)) {\n callback.call(self, tools, $container, testContext, testRunner, self);\n }\n\n /**\n * @event actionBarTools#afterrender\n * @param {Array} tools\n * @param {jQuery} $container\n * @param {Object} testContext\n * @param {Object} testRunner\n * @param {actionBarTools} this\n */\n self.trigger('afterrender', tools, $container, testContext, testRunner, self);\n });\n }\n };\n\n return eventifier(actionBarTools);\n});\n\n","\ndefine('tpl!taoQtiTest/testRunner/tpl/navigator', ['handlebars'], function(hb){ return hb.template(function (Handlebars,depth0,helpers,partials,data) {\n this.compilerInfo = [4,'>= 1.0.0'];\nhelpers = this.merge(helpers, Handlebars.helpers); data = data || {};\n var buffer = \"\", stack1, helper, options, functionType=\"function\", escapeExpression=this.escapeExpression, self=this, helperMissing=helpers.helperMissing;\n\nfunction program1(depth0,data) {\n \n \n return \" hidden\";\n }\n\n buffer += \"\";\n return buffer;\n }); });\n","\ndefine('tpl!taoQtiTest/testRunner/tpl/navigatorTree', ['handlebars'], function(hb){ return hb.template(function (Handlebars,depth0,helpers,partials,data) {\n this.compilerInfo = [4,'>= 1.0.0'];\nhelpers = this.merge(helpers, Handlebars.helpers); data = data || {};\n var buffer = \"\", stack1, self=this, functionType=\"function\", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing;\n\nfunction program1(depth0,data) {\n \n var buffer = \"\", stack1, helper;\n buffer += \"\\n
        1. \\n \\n \";\n if (helper = helpers.label) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.label); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"\\n \\n \\n \\n \";\n stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.sections)),stack1 == null || stack1 === false ? stack1 : stack1.length), {hash:{},inverse:self.program(29, program29, data),fn:self.program(6, program6, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n
        2. \\n \";\n return buffer;\n }\nfunction program2(depth0,data) {\n \n \n return \"active\";\n }\n\nfunction program4(depth0,data) {\n \n \n return \"collapsed\";\n }\n\nfunction program6(depth0,data) {\n \n var buffer = \"\", stack1;\n buffer += \"\\n
            \\n \";\n stack1 = helpers.each.call(depth0, (depth0 && depth0.sections), {hash:{},inverse:self.noop,fn:self.program(7, program7, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n
          \\n \";\n return buffer;\n }\nfunction program7(depth0,data) {\n \n var buffer = \"\", stack1, helper;\n buffer += \"\\n
        3. \\n \\n \";\n if (helper = helpers.label) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.label); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"\\n \";\n if (helper = helpers.answered) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.answered); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"/\"\n + escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.items)),stack1 == null || stack1 === false ? stack1 : stack1.length)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))\n + \"\\n \\n
            \\n \";\n stack1 = helpers.each.call(depth0, (depth0 && depth0.items), {hash:{},inverse:self.noop,fn:self.program(8, program8, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n
          \\n
        4. \\n \";\n return buffer;\n }\nfunction program8(depth0,data) {\n \n var buffer = \"\", stack1, helper;\n buffer += \"\\n
        5. \\n \\n \\n \"\n + escapeExpression(((stack1 = (data == null || data === false ? data : data.index)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))\n + \"\\n \";\n if (helper = helpers.label) { stack1 = helper.call(depth0, {hash:{},data:data}); }\n else { helper = (depth0 && depth0.label); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }\n buffer += escapeExpression(stack1)\n + \"\\n \\n
        6. \\n \";\n return buffer;\n }\nfunction program9(depth0,data) {\n \n \n return \" active\";\n }\n\nfunction program11(depth0,data) {\n \n \n return \" flagged\";\n }\n\nfunction program13(depth0,data) {\n \n \n return \" answered\";\n }\n\nfunction program15(depth0,data) {\n \n \n return \" viewed\";\n }\n\nfunction program17(depth0,data) {\n \n \n return \" unseen\";\n }\n\nfunction program19(depth0,data) {\n \n \n return \"flagged\";\n }\n\nfunction program21(depth0,data) {\n \n var stack1;\n stack1 = helpers['if'].call(depth0, (depth0 && depth0.answered), {hash:{},inverse:self.program(24, program24, data),fn:self.program(22, program22, data),data:data});\n if(stack1 || stack1 === 0) { return stack1; }\n else { return ''; }\n }\nfunction program22(depth0,data) {\n \n \n return \"answered\";\n }\n\nfunction program24(depth0,data) {\n \n var stack1;\n stack1 = helpers['if'].call(depth0, (depth0 && depth0.viewed), {hash:{},inverse:self.program(27, program27, data),fn:self.program(25, program25, data),data:data});\n if(stack1 || stack1 === 0) { return stack1; }\n else { return ''; }\n }\nfunction program25(depth0,data) {\n \n \n return \"viewed\";\n }\n\nfunction program27(depth0,data) {\n \n \n return \"unseen\";\n }\n\nfunction program29(depth0,data) {\n \n var buffer = \"\", stack1, helper, options;\n buffer += \"\\n
          \\n \\n

          \\n \"\n + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, \"In this part of the test navigation is not allowed.\", options) : helperMissing.call(depth0, \"__\", \"In this part of the test navigation is not allowed.\", options)))\n + \"\\n

          \\n

          \\n \\n

          \\n
          \\n \";\n return buffer;\n }\n\n buffer += \"
            \\n \";\n stack1 = helpers.each.call(depth0, (depth0 && depth0.parts), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});\n if(stack1 || stack1 === 0) { buffer += stack1; }\n buffer += \"\\n
          \";\n return buffer;\n }); });\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2015 (original work) Open Assessment Technologies SA ;\n */\n/**\n * @author Jean-Sébastien Conan \n */\ndefine('taoQtiTest/testRunner/testReview',[\n 'jquery',\n 'lodash',\n 'i18n',\n 'tpl!taoQtiTest/testRunner/tpl/navigator',\n 'tpl!taoQtiTest/testRunner/tpl/navigatorTree',\n 'util/capitalize'\n], function ($, _, __, navigatorTpl, navigatorTreeTpl, capitalize) {\n 'use strict';\n\n /**\n * List of CSS classes\n * @type {Object}\n * @private\n */\n var _cssCls = {\n active : 'active',\n collapsed : 'collapsed',\n collapsible : 'collapsible',\n hidden : 'hidden',\n disabled : 'disabled',\n flagged : 'flagged',\n answered : 'answered',\n viewed : 'viewed',\n unseen : 'unseen',\n icon : 'qti-navigator-icon',\n scope : {\n test : 'scope-test',\n testPart : 'scope-test-part',\n testSection : 'scope-test-section'\n }\n };\n\n /**\n * List of common CSS selectors\n * @type {Object}\n * @private\n */\n var _selectors = {\n component : '.qti-navigator',\n filterBar : '.qti-navigator-filters',\n tree : '.qti-navigator-tree',\n collapseHandle : '.qti-navigator-collapsible',\n linearState : '.qti-navigator-linear',\n infoAnswered : '.qti-navigator-answered .qti-navigator-counter',\n infoViewed : '.qti-navigator-viewed .qti-navigator-counter',\n infoUnanswered : '.qti-navigator-unanswered .qti-navigator-counter',\n infoFlagged : '.qti-navigator-flagged .qti-navigator-counter',\n infoPanel : '.qti-navigator-info',\n infoPanelLabels : '.qti-navigator-info > .qti-navigator-label',\n parts : '.qti-navigator-part',\n partLabels : '.qti-navigator-part > .qti-navigator-label',\n sections : '.qti-navigator-section',\n sectionLabels : '.qti-navigator-section > .qti-navigator-label',\n items : '.qti-navigator-item',\n itemLabels : '.qti-navigator-item > .qti-navigator-label',\n itemIcons : '.qti-navigator-item > .qti-navigator-icon',\n icons : '.qti-navigator-icon',\n linearStart : '.qti-navigator-linear-part button',\n counters : '.qti-navigator-counter',\n actives : '.active',\n collapsible : '.collapsible',\n collapsiblePanels : '.collapsible-panel',\n unseen : '.unseen',\n answered : '.answered',\n flagged : '.flagged',\n notFlagged : ':not(.flagged)',\n notAnswered : ':not(.answered)',\n hidden : '.hidden'\n };\n\n /**\n * Maps the filter mode to filter criteria.\n * Each filter criteria is a CSS selector used to find and mask the items to be discarded by the filter.\n * @type {Object}\n * @private\n */\n var _filterMap = {\n all : \"\",\n unanswered : _selectors.answered,\n flagged : _selectors.notFlagged,\n answered : _selectors.notAnswered,\n filtered : _selectors.hidden\n };\n\n /**\n * Maps of config options translated from the context object to the local options\n * @type {Object}\n * @private\n */\n var _optionsMap = {\n 'reviewScope' : 'reviewScope',\n 'reviewPreventsUnseen' : 'preventsUnseen',\n 'canCollapse' : 'canCollapse'\n };\n\n /**\n * Maps the handled review scopes\n * @type {Object}\n * @private\n */\n var _reviewScopes = {\n test : 'test',\n testPart : 'testPart',\n testSection : 'testSection'\n };\n\n /**\n * Provides a test review manager\n * @type {Object}\n */\n var testReview = {\n /**\n * Initializes the component\n * @param {String|jQuery|HTMLElement} element The element on which install the component\n * @param {Object} [options] A list of extra options\n * @param {String} [options.region] The region on which put the component: left or right\n * @param {String} [options.reviewScope] Limit the review screen to a particular scope:\n * the whole test, the current test part or the current test section)\n * @param {Boolean} [options.preventsUnseen] Prevents the test taker to access unseen items\n * @returns {testReview}\n */\n init: function init(element, options) {\n var initOptions = _.isObject(options) && options || {};\n var putOnRight = 'right' === initOptions.region;\n var insertMethod = putOnRight ? 'append' : 'prepend';\n\n this.options = initOptions;\n this.disabled = false;\n this.hidden = !!initOptions.hidden;\n this.currentFilter = 'all';\n\n // clean the DOM if the init method is called after initialisation\n if (this.$component) {\n this.$component.remove();\n }\n\n // build the component structure and inject it into the DOM\n this.$container = $(element);\n insertMethod = this.$container[insertMethod];\n if (insertMethod) {\n insertMethod.call(this.$container, navigatorTpl({\n region: putOnRight ? 'right' : 'left',\n hidden: this.hidden\n }));\n } else {\n throw new Error(\"Unable to inject the component structure into the DOM\");\n }\n\n // install the component behaviour\n this._loadDOM();\n this._initEvents();\n this._updateDisplayOptions();\n\n return this;\n },\n\n /**\n * Links the component to the underlying DOM elements\n * @private\n */\n _loadDOM: function() {\n this.$component = this.$container.find(_selectors.component);\n\n // access to info panel displaying counters\n this.$infoAnswered = this.$component.find(_selectors.infoAnswered);\n this.$infoViewed = this.$component.find(_selectors.infoViewed);\n this.$infoUnanswered = this.$component.find(_selectors.infoUnanswered);\n this.$infoFlagged = this.$component.find(_selectors.infoFlagged);\n\n // access to filter switches\n this.$filterBar = this.$component.find(_selectors.filterBar);\n this.$filters = this.$filterBar.find('li');\n\n // access to the tree of parts/sections/items\n this.$tree = this.$component.find(_selectors.tree);\n\n // access to the panel displayed when a linear part is reached\n this.$linearState = this.$component.find(_selectors.linearState);\n },\n\n /**\n * Installs the event handlers on the underlying DOM elements\n * @private\n */\n _initEvents: function() {\n var self = this;\n\n // click on the collapse handle: collapse/expand the review panel\n this.$component.on('click' + _selectors.component, _selectors.collapseHandle, function() {\n if (self.disabled) {\n return;\n }\n\n self.$component.toggleClass(_cssCls.collapsed);\n if (self.$component.hasClass(_cssCls.collapsed)) {\n self._openSelected();\n }\n });\n\n // click on the info panel title: toggle the related panel\n this.$component.on('click' + _selectors.component, _selectors.infoPanelLabels, function() {\n if (self.disabled) {\n return;\n }\n\n var $panel = $(this).closest(_selectors.infoPanel);\n self._togglePanel($panel, _selectors.infoPanel);\n });\n\n // click on a part title: toggle the related panel\n this.$tree.on('click' + _selectors.component, _selectors.partLabels, function() {\n if (self.disabled) {\n return;\n }\n\n var $panel = $(this).closest(_selectors.parts);\n var open = self._togglePanel($panel, _selectors.parts);\n\n if (open) {\n if ($panel.hasClass(_cssCls.active)) {\n self._openSelected();\n } else {\n self._openOnly($panel.find(_selectors.sections).first(), $panel);\n }\n }\n });\n\n // click on a section title: toggle the related panel\n this.$tree.on('click' + _selectors.component, _selectors.sectionLabels, function() {\n if (self.disabled) {\n return;\n }\n\n var $panel = $(this).closest(_selectors.sections);\n\n self._togglePanel($panel, _selectors.sections);\n });\n\n // click on an item: jump to the position\n this.$tree.on('click' + _selectors.component, _selectors.itemLabels, function(event) {\n if (self.disabled) {\n return;\n }\n\n var $item = $(this).closest(_selectors.items);\n var $target;\n\n if (!$item.hasClass(_cssCls.disabled)) {\n $target = $(event.target);\n if ($target.is(_selectors.icons) && !self.$component.hasClass(_cssCls.collapsed)) {\n if (!$item.hasClass(_cssCls.unseen)) {\n self._mark($item);\n }\n } else {\n self._select($item);\n self._jump($item);\n }\n }\n });\n\n // click on the start button inside a linear part: jump to the position\n this.$tree.on('click' + _selectors.component, _selectors.linearStart, function() {\n if (self.disabled) {\n return;\n }\n\n var $btn = $(this);\n\n if (!$btn.hasClass(_cssCls.disabled)) {\n $btn.addClass(_cssCls.disabled);\n self._jump($btn);\n }\n });\n\n // click on a filter button\n this.$filterBar.on('click' + _selectors.component, 'li', function() {\n if (self.disabled) {\n return;\n }\n\n var $btn = $(this);\n var mode = $btn.data('mode');\n\n self.$filters.removeClass(_cssCls.active);\n self.$component.removeClass(_cssCls.collapsed);\n $btn.addClass(_cssCls.active);\n\n self._filter(mode);\n });\n },\n\n /**\n * Filters the items by a criteria\n * @param {String} criteria\n * @private\n */\n _filter: function(criteria) {\n var $items = this.$tree.find(_selectors.items).removeClass(_cssCls.hidden);\n var filter = _filterMap[criteria];\n if (filter) {\n $items.filter(filter).addClass(_cssCls.hidden);\n }\n this._updateSectionCounters(!!filter);\n this.currentFilter = criteria;\n },\n\n /**\n * Selects an item\n * @param {String|jQuery} position The item's position\n * @param {Boolean} [open] Forces the tree to be opened on the selected item\n * @returns {jQuery} Returns the selected item\n * @private\n */\n _select: function(position, open) {\n // find the item to select and extract its hierarchy\n var selected = position && position.jquery ? position : this.$tree.find('[data-position=' + position + ']');\n var hierarchy = selected.parentsUntil(this.$tree);\n\n // collapse the full tree and open only the hierarchy of the selected item\n if (open) {\n this._openOnly(hierarchy);\n }\n\n // select the item\n this.$tree.find(_selectors.actives).removeClass(_cssCls.active);\n hierarchy.add(selected).addClass(_cssCls.active);\n return selected;\n },\n\n /**\n * Opens the tree on the selected item only\n * @returns {jQuery} Returns the selected item\n * @private\n */\n _openSelected: function() {\n // find the selected item and extract its hierarchy\n var selected = this.$tree.find(_selectors.items + _selectors.actives);\n var hierarchy = selected.parentsUntil(this.$tree);\n\n // collapse the full tree and open only the hierarchy of the selected item\n this._openOnly(hierarchy);\n\n return selected;\n },\n\n /**\n * Collapses the full tree and opens only the provided branch\n * @param {jQuery} opened The element to be opened\n * @param {jQuery} [root] The root element from which collapse the panels\n * @private\n */\n _openOnly: function(opened, root) {\n (root || this.$tree).find(_selectors.collapsible).addClass(_cssCls.collapsed);\n opened.removeClass(_cssCls.collapsed);\n },\n\n /**\n * Toggles a panel\n * @param {jQuery} panel The panel to toggle\n * @param {String} [collapseSelector] Selector of panels to collapse\n * @returns {Boolean} Returns `true` if the panel just expanded now\n */\n _togglePanel: function(panel, collapseSelector) {\n var collapsed = panel.hasClass(_cssCls.collapsed);\n\n if (collapseSelector) {\n this.$tree.find(collapseSelector).addClass(_cssCls.collapsed);\n }\n\n if (collapsed) {\n panel.removeClass(_cssCls.collapsed);\n } else {\n panel.addClass(_cssCls.collapsed);\n }\n return collapsed;\n },\n\n /**\n * Sets the icon of a particular item\n * @param {jQuery} $item\n * @param {String} icon\n * @private\n */\n _setItemIcon: function($item, icon) {\n $item.find(_selectors.icons).attr('class', _cssCls.icon + ' icon-' + icon);\n },\n\n /**\n * Sets the icon of a particular item according to its state\n * @param {jQuery} $item\n * @private\n */\n _adjustItemIcon: function($item) {\n var icon = null;\n var defaultIcon = _cssCls.unseen;\n var iconCls = [\n _cssCls.flagged,\n _cssCls.answered,\n _cssCls.viewed\n ];\n\n _.forEach(iconCls, function(cls) {\n if ($item.hasClass(cls)) {\n icon = cls;\n return false;\n }\n });\n\n this._setItemIcon($item, icon || defaultIcon);\n },\n\n /**\n * Toggle the marked state of an item\n * @param {jQuery} $item\n * @param {Boolean} [flag]\n * @private\n */\n _toggleFlag: function($item, flag) {\n $item.toggleClass(_cssCls.flagged, flag);\n this._adjustItemIcon($item);\n },\n\n /**\n * Marks an item for later review\n * @param {jQuery} $item\n * @private\n */\n _mark: function($item) {\n var itemId = $item.data('id');\n var itemPosition = $item.data('position');\n var flag = !$item.hasClass(_cssCls.flagged);\n\n this._toggleFlag($item);\n\n /**\n * A storage of the flag is required\n * @event testReview#mark\n * @param {Boolean} flag - Tells whether the item is marked for review or not\n * @param {Number} position - The item position on which jump\n * @param {String} itemId - The identifier of the target item\n * @param {testReview} testReview - The client testReview component\n */\n this.trigger('mark', [flag, itemPosition, itemId]);\n },\n\n /**\n * Jumps to an item\n * @param {jQuery} $item\n * @private\n */\n _jump: function($item) {\n var itemId = $item.data('id');\n var itemPosition = $item.data('position');\n\n /**\n * A jump to a particular item is required\n * @event testReview#jump\n * @param {Number} position - The item position on which jump\n * @param {String} itemId - The identifier of the target item\n * @param {testReview} testReview - The client testReview component\n */\n this.trigger('jump', [itemPosition, itemId]);\n },\n\n /**\n * Updates the sections related items counters\n * @param {Boolean} filtered\n */\n _updateSectionCounters: function(filtered) {\n var self = this;\n var filter = _filterMap[filtered ? 'filtered' : 'answered'];\n this.$tree.find(_selectors.sections).each(function() {\n var $section = $(this);\n var $items = $section.find(_selectors.items);\n var $filtered = $items.filter(filter);\n var total = $items.length;\n var nb = total - $filtered.length;\n self._writeCount($section.find(_selectors.counters), nb, total);\n });\n },\n\n /**\n * Updates the display according to options\n * @private\n */\n _updateDisplayOptions: function() {\n var reviewScope = _reviewScopes[this.options.reviewScope] || 'test';\n var scopeClass = _cssCls.scope[reviewScope];\n var $root = this.$component;\n _.forEach(_cssCls.scope, function(cls) {\n $root.removeClass(cls);\n });\n if (scopeClass) {\n $root.addClass(scopeClass);\n }\n $root.toggleClass(_cssCls.collapsible, this.options.canCollapse);\n },\n\n /**\n * Updates the local options from the provided context\n * @param {Object} testContext The progression context\n * @private\n */\n _updateOptions: function(testContext) {\n var options = this.options;\n _.forEach(_optionsMap, function(optionKey, contextKey) {\n if (undefined !== testContext[contextKey]) {\n options[optionKey] = testContext[contextKey];\n }\n });\n },\n\n /**\n * Updates the info panel\n */\n _updateInfos: function() {\n var progression = this.progression,\n unanswered = Number(progression.total) - Number(progression.answered);\n\n // update the info panel\n this._writeCount(this.$infoAnswered, progression.answered, progression.total);\n this._writeCount(this.$infoUnanswered, unanswered, progression.total);\n this._writeCount(this.$infoViewed, progression.viewed, progression.total);\n this._writeCount(this.$infoFlagged, progression.flagged, progression.total);\n },\n\n /**\n * Updates a counter\n * @param {jQuery} $place\n * @param {Number} count\n * @param {Number} total\n * @private\n */\n _writeCount: function($place, count, total) {\n $place.text(count + '/' + total);\n },\n\n /**\n * Gets the progression stats for the whole test\n * @param {Object} testContext The progression context\n * @returns {{total: (Number), answered: (Number), viewed: (Number), flagged: (Number)}}\n * @private\n */\n _getProgressionOfTest: function(testContext) {\n return {\n total : testContext.numberItems || 0,\n answered : testContext.numberCompleted || 0,\n viewed : testContext.numberPresented || 0,\n flagged : testContext.numberFlagged || 0\n };\n },\n\n /**\n * Gets the progression stats for the current test part\n * @param {Object} testContext The progression context\n * @returns {{total: (Number), answered: (Number), viewed: (Number), flagged: (Number)}}\n * @private\n */\n _getProgressionOfTestPart: function(testContext) {\n return {\n total : testContext.numberItemsPart || 0,\n answered : testContext.numberCompletedPart || 0,\n viewed : testContext.numberPresentedPart || 0,\n flagged : testContext.numberFlaggedPart || 0\n };\n },\n\n /**\n * Gets the progression stats for the current test section\n * @param {Object} testContext The progression context\n * @returns {{total: (Number), answered: (Number), viewed: (Number), flagged: (Number)}}\n * @private\n */\n _getProgressionOfTestSection: function(testContext) {\n return {\n total : testContext.numberItemsSection || 0,\n answered : testContext.numberCompletedSection || 0,\n viewed : testContext.numberPresentedSection || 0,\n flagged : testContext.numberFlaggedSection || 0\n };\n },\n\n /**\n * Updates the navigation tre\n * @param {Object} testContext The progression context\n */\n _updateTree: function(testContext) {\n var navigatorMap = testContext.navigatorMap;\n var reviewScope = this.options.reviewScope;\n var reviewScopePart = reviewScope === 'testPart';\n var reviewScopeSection = reviewScope === 'testSection';\n var _partsFilter = function(part) {\n if (reviewScopeSection && part.sections) {\n part.sections = _.filter(part.sections, _partsFilter);\n }\n return part.active;\n };\n\n // rebuild the tree\n if (navigatorMap) {\n if (reviewScopePart || reviewScopeSection) {\n // display only the current section\n navigatorMap = _.filter(navigatorMap, _partsFilter);\n }\n\n this.$filterBar.show();\n this.$linearState.hide();\n this.$tree.html(navigatorTreeTpl({\n parts: navigatorMap\n }));\n\n if (this.options.preventsUnseen) {\n // disables all unseen items to prevent the test taker has access to.\n this.$tree.find(_selectors.unseen).addClass(_cssCls.disabled);\n }\n } else {\n this.$filterBar.hide();\n this.$linearState.show();\n this.$tree.empty();\n }\n\n // apply again the current filter\n this._filter(this.$filters.filter(_selectors.actives).data('mode'));\n },\n\n /**\n * Set the marked state of an item\n * @param {Number|String|jQuery} position\n * @param {Boolean} flag\n */\n setItemFlag: function setItemFlag(position, flag) {\n var $item = position && position.jquery ? position : this.$tree.find('[data-position=' + position + ']');\n var progression = this.progression;\n\n // update the item flag\n this._toggleFlag($item, flag);\n\n // update the info panel\n progression.flagged = this.$tree.find(_selectors.flagged).length;\n this._writeCount(this.$infoFlagged, progression.flagged, progression.total);\n this._filter(this.currentFilter);\n },\n\n /**\n * Update the number of flagged items in the test context\n * @param {Object} testContext The test context\n * @param {Number} position The position of the flagged item\n * @param {Boolean} flag The flag state\n */\n updateNumberFlagged: function(testContext, position, flag) {\n var fields = ['numberFlagged'];\n var currentPosition = testContext.itemPosition;\n var currentFound = false, currentSection = null, currentPart = null;\n var itemFound = false, itemSection = null, itemPart = null;\n\n if (testContext.navigatorMap) {\n // find the current item and the marked item inside the navigator map\n // check if the marked item is in the current section\n _.forEach(testContext.navigatorMap, function(part) {\n _.forEach(part && part.sections, function(section) {\n _.forEach(section && section.items, function(item) {\n if (item) {\n if (item.position === position) {\n itemPart = part;\n itemSection = section;\n itemFound = true;\n }\n if (item.position === currentPosition) {\n currentPart = part;\n currentSection = section;\n currentFound = true;\n\n }\n if (itemFound && currentFound) {\n return false;\n }\n }\n });\n\n if (itemFound && currentFound) {\n return false;\n }\n });\n\n if (itemFound && currentFound) {\n return false;\n }\n });\n\n // select the context to update\n if (itemFound && currentPart === itemPart) {\n fields.push('numberFlaggedPart');\n }\n if (itemFound && currentSection === itemSection) {\n fields.push('numberFlaggedSection');\n }\n } else {\n // no navigator map, the current the marked item is in the current section\n fields.push('numberFlaggedPart');\n fields.push('numberFlaggedSection');\n }\n\n _.forEach(fields, function(field) {\n if (field in testContext) {\n testContext[field] += flag ? 1 : -1;\n }\n });\n },\n\n /**\n * Get progression\n * @param {Object} testContext The progression context\n * @returns {object} progression\n */\n getProgression: function getProgression(testContext) {\n var reviewScope = _reviewScopes[this.options.reviewScope] || 'test',\n progressInfoMethod = '_getProgressionOf' + capitalize(reviewScope),\n getProgression = this[progressInfoMethod] || this._getProgressionOfTest,\n progression = getProgression && getProgression(testContext) || {};\n\n return progression;\n },\n\n /**\n * Updates the review screen\n * @param {Object} testContext The progression context\n * @returns {testReview}\n */\n update: function update(testContext) {\n this.progression = this.getProgression(testContext);\n this._updateOptions(testContext);\n this._updateInfos(testContext);\n this._updateTree(testContext);\n this._updateDisplayOptions(testContext);\n return this;\n },\n\n /**\n * Disables the component\n * @returns {testReview}\n */\n disable: function disable() {\n this.disabled = true;\n this.$component.addClass(_cssCls.disabled);\n return this;\n },\n\n /**\n * Enables the component\n * @returns {testReview}\n */\n enable: function enable() {\n this.disabled = false;\n this.$component.removeClass(_cssCls.disabled);\n return this;\n },\n\n /**\n * Hides the component\n * @returns {testReview}\n */\n hide: function hide() {\n this.disabled = true;\n this.hidden = true;\n this.$component.addClass(_cssCls.hidden);\n return this;\n },\n\n /**\n * Shows the component\n * @returns {testReview}\n */\n show: function show() {\n this.disabled = false;\n this.hidden = false;\n this.$component.removeClass(_cssCls.hidden);\n return this;\n },\n\n /**\n * Toggles the display state of the component\n * @param {Boolean} [show] External condition that's tells if the component must be shown or hidden\n * @returns {testReview}\n */\n toggle: function toggle(show) {\n if (undefined === show) {\n show = this.hidden;\n }\n\n if (show) {\n this.show();\n } else {\n this.hide();\n }\n\n return this;\n },\n\n /**\n * Install an event handler on the underlying DOM element\n * @param {String} eventName\n * @returns {testReview}\n */\n on: function on(eventName) {\n var dom = this.$component;\n if (dom) {\n dom.on.apply(dom, arguments);\n }\n\n return this;\n },\n\n /**\n * Uninstall an event handler from the underlying DOM element\n * @param {String} eventName\n * @returns {testReview}\n */\n off: function off(eventName) {\n var dom = this.$component;\n if (dom) {\n dom.off.apply(dom, arguments);\n }\n\n return this;\n },\n\n /**\n * Triggers an event on the underlying DOM element\n * @param {String} eventName\n * @param {Array|Object} extraParameters\n * @returns {testReview}\n */\n trigger : function trigger(eventName, extraParameters) {\n var dom = this.$component;\n\n if (undefined === extraParameters) {\n extraParameters = [];\n }\n if (!_.isArray(extraParameters)) {\n extraParameters = [extraParameters];\n }\n\n extraParameters.push(this);\n\n if (dom) {\n dom.trigger(eventName, extraParameters);\n }\n\n return this;\n }\n };\n\n /**\n * Builds an instance of testReview\n * @param {String|jQuery|HTMLElement} element The element on which install the component\n * @param {Object} [options] A list of extra options\n * @param {String} [options.region] The region on which put the component: left or right\n * @param {String} [options.reviewScope] Limit the review screen to a particular scope:\n * the whole test, the current test part or the current test section)\n * @param {Boolean} [options.preventsUnseen] Prevents the test taker to access unseen items\n * @returns {testReview}\n */\n var testReviewFactory = function(element, options) {\n var component = _.clone(testReview, true);\n return component.init(element, options);\n };\n\n return testReviewFactory;\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2015 (original work) Open Assessment Technologies SA ;\n */\n/**\n * @author Jean-Sébastien Conan \n */\ndefine('taoQtiTest/testRunner/progressUpdater',[\n 'jquery',\n 'lodash',\n 'i18n',\n 'ui/progressbar'\n], function ($, _, __) {\n 'use strict';\n\n /**\n * Provides a versatile progress bar updater\n * @type {{init: Function, update: Function}}\n */\n var progressUpdaters = {\n /**\n * Initializes the progress updater\n *\n * @param {String|jQuery|HTMLElement} progressBar The element on which put the progress bar\n * @param {String|jQuery|HTMLElement} progressLabel The element on which put the progress label\n * @returns {progressUpdaters}\n */\n init: function(progressBar, progressLabel) {\n this.progressBar = $(progressBar).progressbar();\n this.progressLabel = $(progressLabel);\n return this;\n },\n\n /**\n * Writes the progress label and update the progress by ratio\n * @param {String} label\n * @param {Number} ratio\n * @returns {progressUpdaters}\n */\n write: function(label, ratio) {\n this.progressLabel.text(label);\n this.progressBar.progressbar('value', ratio);\n return this;\n },\n\n /**\n * Updates the progress bar\n * @param {Object} testContext The progression context\n * @returns {{ratio: number, label: string}}\n */\n update: function(testContext) {\n var progressIndicator = testContext.progressIndicator || 'percentage';\n var progressIndicatorMethod = progressIndicator + 'Progression';\n var getProgression = this[progressIndicatorMethod] || this.percentageProgression;\n var progression = getProgression && getProgression(testContext) || {};\n\n this.write(progression.label, progression.ratio);\n return progression;\n },\n\n /**\n * Updates the progress bar displaying the percentage\n * @param {Object} testContext The progression context\n * @returns {{ratio: number, label: string}}\n */\n percentageProgression: function(testContext) {\n var total = Math.max(1, testContext.numberItems);\n var ratio = Math.floor(testContext.numberCompleted / total * 100);\n return {\n ratio : ratio,\n label : ratio + '%'\n };\n },\n\n /**\n * Updates the progress bar displaying the position\n * @param {Object} testContext The progression context\n * @returns {{ratio: number, label: string}}\n */\n positionProgression: function(testContext) {\n var progressScope = testContext.progressIndicatorScope;\n var progressScopeCounter = {\n test : {\n total : 'numberItems',\n position : 'itemPosition'\n },\n testPart : {\n total : 'numberItemsPart',\n position : 'itemPositionPart'\n },\n testSection : {\n total : 'numberItemsSection',\n position : 'itemPositionSection'\n }\n };\n var counter = progressScopeCounter[progressScope] || progressScopeCounter.test;\n var total = Math.max(1, testContext[counter.total]);\n var position = testContext[counter.position] + 1;\n return {\n ratio : Math.floor(position / total * 100),\n label : __('Item %d of %d', position, total)\n };\n }\n };\n\n /**\n * Builds an instance of progressUpdaters\n * @param {String|jQuery|HTMLElement} progressBar The element on which put the progress bar\n * @param {String|jQuery|HTMLElement} progressLabel The element on which put the progress label\n * @returns {progressUpdaters}\n */\n var progressUpdaterFactory = function(progressBar, progressLabel) {\n var updater = _.clone(progressUpdaters, true);\n return updater.init(progressBar, progressLabel);\n };\n\n return progressUpdaterFactory;\n});\n\n","/**\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2015 (original work) Open Assessment Technologies SA;\n *\n */\n\n/**\n * Metadata to be sent to the server. Will be saved in result storage as a trace variable.\n * Usage example:\n *
          \n * var testMetaData = testMetaDataFactory({\n *   testServiceCallId : this.itemServiceApi.serviceCallId\n * });\n *\n * testMetaData.setData({\n *   'TEST' : {\n *      'TEST_EXIT_CODE' : 'T'\n *   },\n *   'SECTION' : {\n *      'SECTION_EXIT_CODE' : 704\n *   }\n * });\n *\n * testMetaData.addData({'ITEM' : {\n *      'ITEM_START_TIME_CLIENT' : 1443596730143,\n *      'ITEM_END_TIME_CLIENT' : 1443596731301\n *    }\n * });\n * 
          \n *\n * Data for each service call id will be stored in local storage to be able get data\n * after reloading the page or resuming the test session.\n *\n * To clear all data related to current test_call_id used clearData method.\n */\ndefine('taoQtiTest/testRunner/testMetaData',[\n 'lodash'\n], function (_) {\n 'use strict';\n\n /**\n * @param {Object} options\n * @param {string} options.testServiceCallId - test call id.\n */\n var testMetaDataFactory = function testMetaDataFactory(options) {\n var _testServiceCallId,\n _storageKeyPrefix = 'testMetaData_',\n _data = {};\n\n if (!options || options.testServiceCallId === undefined) {\n throw new TypeError(\"testServiceCallId option is required\");\n }\n\n var testMetaData = {\n SECTION_EXIT_CODE : {\n 'COMPLETED_NORMALLY': 700,\n 'QUIT': 701,\n 'COMPLETE_TIMEOUT': 703,\n 'TIMEOUT': 704,\n 'FORCE_QUIT': 705,\n 'IN_PROGRESS': 706,\n 'ERROR': 300\n },\n TEST_EXIT_CODE : {\n 'COMPLETE': 'C',\n 'TERMINATED': 'T',\n 'INCOMPLETE': 'IC',\n 'INCOMPLETE_QUIT': 'IQ',\n 'INACTIVE': 'IA',\n 'CANDIDATE_DISAGREED_WITH_NDA': 'DA'\n },\n /**\n * Return test call id.\n * @returns {string}- Test call id\n */\n getTestServiceCallId : function getTestServiceCallId () {\n return _testServiceCallId;\n },\n\n /**\n * Set test call id.\n * @param {string} value\n */\n setTestServiceCallId : function setTestServiceCallId (value) {\n _testServiceCallId = value;\n },\n\n /**\n * Set meta data. Current data object will be overwritten.\n * @param {Object} data - metadata object\n */\n setData : function setData(data) {\n _data = data;\n setLocalStorageData(JSON.stringify(_data));\n },\n\n /**\n * Add data.\n * @param {Object} data - metadata object\n * @param {Boolean} overwrite - whether the same data should be overwritten. Default - false\n */\n addData : function addData(data, overwrite) {\n data = _.clone(data);\n if (overwrite === undefined) {\n overwrite = false;\n }\n\n if (overwrite) {\n _.merge(_data, data);\n } else {\n _data = _.merge(data, _data);\n }\n setLocalStorageData(JSON.stringify(_data));\n },\n\n /**\n * Get the saved data.\n * The cloned object will be returned to avoid unwanted affecting of the original data.\n * @returns {Object} - metadata object.\n */\n getData : function getData() {\n return _.clone(_data);\n },\n\n /**\n * Clear all data saved in current object and in local storage related to current test call id.\n * @returns {Object} - metadata object.\n */\n clearData : function clearData() {\n _data = {};\n window.localStorage.removeItem(testMetaData.getLocalStorageKey());\n },\n\n getLocalStorageKey : function getLocalStorageKey () {\n return _storageKeyPrefix + _testServiceCallId;\n }\n };\n\n /**\n * Initialize test meta data manager\n */\n function init() {\n _testServiceCallId = options.testServiceCallId;\n testMetaData.setData(getLocalStorageData());\n }\n\n /**\n * Set data to local storage\n * @param {string} val - data to be stored.\n */\n function setLocalStorageData(val) {\n var currentKey = testMetaData.getLocalStorageKey();\n try {\n window.localStorage.setItem(currentKey, val);\n } catch(domException) {\n if (domException.name === 'QuotaExceededError' ||\n domException.name === 'NS_ERROR_DOM_QUOTA_REACHED') {\n var removed = 0,\n i = window.localStorage.length,\n key;\n while (i--) {\n key = localStorage.key(i);\n if (/^testMetaData_.*/.test(key) && key !== currentKey) {\n window.localStorage.removeItem(key);\n removed++;\n }\n }\n if (removed) {\n setLocalStorageData(val);\n } else {\n throw domException;\n }\n } else {\n throw domException;\n }\n }\n }\n\n /**\n * Get data from local storage stored for current call id\n * @returns {*} saved data or empty object\n */\n function getLocalStorageData() {\n var data = window.localStorage.getItem(testMetaData.getLocalStorageKey()),\n result = JSON.parse(data) || {};\n\n return result;\n }\n\n init();\n\n return testMetaData;\n };\n\n return testMetaDataFactory;\n});\n","/*\n * This program is free software; you can redistribute it and/or\n * modify it under the terms of the GNU General Public License\n * as published by the Free Software Foundation; under version 2\n * of the License (non-upgradable).\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * Copyright (c) 2015 (original work) Open Assessment Technologies SA;\n *\n */\n\ndefine('taoQtiTest/controller/runtime/testRunner',[\n 'jquery',\n 'lodash',\n 'i18n',\n 'module',\n 'taoQtiTest/testRunner/actionBarTools',\n 'taoQtiTest/testRunner/testReview',\n 'taoQtiTest/testRunner/progressUpdater',\n 'taoQtiTest/testRunner/testMetaData',\n 'serviceApi/ServiceApi',\n 'serviceApi/UserInfoService',\n 'serviceApi/StateStorage',\n 'iframeNotifier',\n 'mathJax',\n 'ui/feedback',\n 'ui/deleter',\n 'moment',\n 'ui/modal',\n 'ui/progressbar'\n],\nfunction (\n $,\n _,\n __,\n module,\n actionBarTools,\n testReview,\n progressUpdater,\n testMetaDataFactory,\n ServiceApi,\n UserInfoService,\n StateStorage,\n iframeNotifier,\n MathJax,\n feedback,\n deleter,\n moment,\n modal\n) {\n\n 'use strict';\n\n var timerIds = [],\n currentTimes = [],\n lastDates = [],\n timeDiffs = [],\n waitingTime = 0,\n $timers,\n $controls,\n timerIndex,\n testMetaData,\n sessionStateService,\n $doc = $(document),\n optionNextSection = 'x-tao-option-nextSection',\n optionNextSectionWarning = 'x-tao-option-nextSectionWarning',\n optionReviewScreen = 'x-tao-option-reviewScreen',\n optionEndTestWarning = 'x-tao-option-endTestWarning',\n optionNoExitTimedSectionWarning = 'x-tao-option-noExitTimedSectionWarning',\n TestRunner = {\n // Constants\n 'TEST_STATE_INITIAL': 0,\n 'TEST_STATE_INTERACTING': 1,\n 'TEST_STATE_MODAL_FEEDBACK': 2,\n 'TEST_STATE_SUSPENDED': 3,\n 'TEST_STATE_CLOSED': 4,\n 'TEST_NAVIGATION_LINEAR': 0,\n 'TEST_NAVIGATION_NONLINEAR': 1,\n 'TEST_ITEM_STATE_INTERACTING': 1,\n\n /**\n * Prepare a transition to another item\n * @param {Function} [callback]\n */\n beforeTransition: function (callback) {\n // Ask the top window to start the loader.\n iframeNotifier.parent('loading');\n\n // Disable buttons.\n this.disableGui();\n\n $controls.$itemFrame.hide();\n $controls.$rubricBlocks.hide();\n $controls.$timerWrapper.hide();\n\n // Wait at least waitingTime ms for a better user experience.\n if (typeof callback === 'function') {\n setTimeout(callback, waitingTime);\n }\n },\n\n /**\n * Complete a transition to another item\n */\n afterTransition: function () {\n this.enableGui();\n\n //ask the top window to stop the loader\n iframeNotifier.parent('unloading');\n testMetaData.addData({\n 'ITEM' : {'ITEM_START_TIME_CLIENT' : Date.now() / 1000}\n });\n },\n\n /**\n * Jumps to a particular item in the test\n * @param {Number} position The position of the item within the test\n */\n jump: function(position) {\n var self = this,\n action = 'jump',\n params = {position: position};\n this.disableGui();\n\n if( this.isJumpOutOfSection(position) && this.isCurrentItemActive() && this.isTimedSection() ){\n this.exitTimedSection(action, params);\n } else {\n this.killItemSession(function() {\n self.actionCall(action, params);\n });\n }\n },\n\n /**\n * Push to server how long user seen that item before to track duration\n * @param {Number} duration\n */\n keepItemTimed: function(duration){\n if (duration) {\n var self = this,\n action = 'keepItemTimed',\n params = {duration: duration};\n self.actionCall(action, params);\n }\n },\n\n /**\n * Marks an item for later review\n * @param {Boolean} flag The state of the flag\n * @param {Number} position The position of the item within the test\n */\n markForReview: function(flag, position) {\n var self = this;\n\n // Ask the top window to start the loader.\n iframeNotifier.parent('loading');\n\n // Disable buttons.\n this.disableGui();\n\n $.ajax({\n url: self.testContext.markForReviewUrl,\n cache: false,\n async: true,\n type: 'POST',\n dataType: 'json',\n data: {\n flag: flag,\n position: position\n },\n success: function(data) {\n // update the item flagged state\n if (self.testReview) {\n self.testReview.setItemFlag(position, flag);\n self.testReview.updateNumberFlagged(self.testContext, position, flag);\n if (self.testContext.itemPosition === position) {\n self.testContext.itemFlagged = flag;\n }\n self.updateTools(self.testContext);\n }\n\n // Enable buttons.\n self.enableGui();\n\n //ask the top window to stop the loader\n iframeNotifier.parent('unloading');\n }\n });\n },\n\n /**\n * Move to the next available item\n */\n moveForward: function () {\n var self = this,\n action = 'moveForward';\n\n function doExitSection() {\n if( self.isTimedSection() && !self.testContext.isTimeout){\n self.exitTimedSection(action);\n } else {\n self.exitSection(action);\n }\n }\n\n this.disableGui();\n\n if( (( this.testContext.numberItemsSection - this.testContext.itemPositionSection - 1) == 0) && this.isCurrentItemActive()){\n if (this.shouldDisplayEndTestWarning()) {\n this.displayEndTestWarning(doExitSection);\n this.enableGui();\n } else {\n doExitSection();\n }\n\n } else {\n this.killItemSession(function () {\n self.actionCall(action);\n });\n }\n },\n\n /**\n * Check if necessary to display an end test warning\n */\n shouldDisplayEndTestWarning: function(){\n return (this.testContext.isLast === true && this.hasOption(optionEndTestWarning));\n },\n\n /**\n * Warns upon exiting test\n */\n displayEndTestWarning: function(nextAction){\n var options = {\n confirmLabel: __('OK'),\n cancelLabel: __('Cancel'),\n showItemCount: false\n };\n\n this.displayExitMessage(\n __('You are about to submit the test. You will not be able to access this test once submitted. Click OK to continue and submit the test.'),\n nextAction,\n options\n );\n },\n\n /**\n * Move to the previous available item\n */\n moveBackward: function () {\n var self = this,\n action = 'moveBackward';\n\n this.disableGui();\n\n if( (this.testContext.itemPositionSection == 0) && this.isCurrentItemActive() && this.isTimedSection() ){\n this.exitTimedSection(action);\n } else {\n this.killItemSession(function () {\n self.actionCall(action);\n });\n }\n },\n\n /**\n * Checks if a position is out of the current section\n * @param {Number} jumpPosition\n * @returns {Boolean}\n */\n isJumpOutOfSection: function(jumpPosition){\n var items = this.getCurrentSectionItems(),\n isJumpToOtherSection = true,\n isValidPosition = (jumpPosition >= 0) && ( jumpPosition < this.testContext.numberItems );\n\n if( isValidPosition){\n for(var i in items ) {\n if (!items.hasOwnProperty(i)) {\n continue;\n }\n if( items[i].position == jumpPosition ){\n isJumpToOtherSection = false;\n break;\n }\n }\n } else {\n isJumpToOtherSection = false;\n }\n\n return isJumpToOtherSection;\n },\n\n /**\n * Exit from the current section. Set the exit code.de\n * @param {String} action\n * @param {Object} params\n * @param {Number} [exitCode]\n */\n exitSection: function(action, params, exitCode){\n var self = this;\n\n testMetaData.addData({\"SECTION\" : {\"SECTION_EXIT_CODE\" : exitCode || testMetaData.SECTION_EXIT_CODE.COMPLETED_NORMALLY}});\n self.killItemSession(function () {\n self.actionCall(action, params);\n });\n },\n\n /**\n * Tries to exit a timed section. Display a confirm message.\n * @param {String} action\n * @param {Object} params\n */\n exitTimedSection: function(action, params){\n var self = this,\n qtiRunner = this.getQtiRunner(),\n doExitTimedSection = function() {\n if (qtiRunner) {\n qtiRunner.updateItemApi();\n }\n self.exitSection(action, params);\n };\n\n if ((action === 'moveForward' && this.shouldDisplayEndTestWarning()) // prevent duplicate warning\n || this.hasOption(optionNoExitTimedSectionWarning) // check if warning is disabled\n || this.testContext.keepTimerUpToTimeout // no need to display the message as we may be able to go back\n ) {\n doExitTimedSection();\n } else {\n this.displayExitMessage(\n __('After you complete the section it would be impossible to return to this section to make changes. Are you sure you want to end the section?'),\n doExitTimedSection,\n { scope: 'testSection' }\n );\n }\n\n this.enableGui();\n },\n\n /**\n * Tries to leave the current section and go to the next\n */\n nextSection: function(){\n var self = this;\n var qtiRunner = this.getQtiRunner();\n var doNextSection = function() {\n self.exitSection('nextSection', null, testMetaData.SECTION_EXIT_CODE.QUIT);\n };\n\n if (qtiRunner) {\n qtiRunner.updateItemApi();\n }\n\n if (this.hasOption(optionNextSectionWarning)) {\n this.displayExitMessage(\n __('After you complete the section it would be impossible to return to this section to make changes. Are you sure you want to end the section?'),\n doNextSection,\n { scope: 'testSection' }\n );\n } else {\n doNextSection();\n }\n\n this.enableGui();\n },\n\n /**\n * Gets the current progression within a particular scope\n * @param {String} [scope]\n * @returns {Object}\n */\n getProgression: function(scope) {\n var scopeSuffixMap = {\n test : '',\n testPart : 'Part',\n testSection : 'Section'\n };\n var scopeSuffix = scope && scopeSuffixMap[scope] || '';\n\n return {\n total : this.testContext['numberItems' + scopeSuffix] || 0,\n answered : this.testContext['numberCompleted' + scopeSuffix] || 0,\n viewed : this.testContext['numberPresented' + scopeSuffix] || 0,\n flagged : this.testContext['numberFlagged' + scopeSuffix] || 0\n };\n },\n\n /**\n * Displays an exit message for a particular scope\n * @param {String} message\n * @param {Function} [action]\n * @param {Object} [options]\n * @param {String} [options.scope]\n * @param {String} [options.confirmLabel] - label of confirm button\n * @param {String} [options.cancelLabel] - label of cancel button\n * @param {Boolean} [options.showItemCount] - display the number of unanswered / flagged items in modal\n * @returns {jQuery} Returns the message box\n */\n displayExitMessage: function(message, action, options) {\n var self = this,\n options = options || {},\n scope = options.scope,\n confirmLabel = options.confirmLabel || __('Yes'),\n cancelLabel = options.cancelLabel || __('No'),\n showItemCount = typeof options.showItemCount !== 'undefined' ? options.showItemCount : true;\n\n var $confirmBox = $('.exit-modal-feedback');\n var progression = this.getProgression(scope);\n var unansweredCount = (progression.total - progression.answered);\n var flaggedCount = progression.flagged;\n\n if (showItemCount) {\n if (unansweredCount && this.isCurrentItemAnswered()) {\n unansweredCount--;\n }\n\n if (flaggedCount && unansweredCount) {\n message = __('You have %s unanswered question(s) and have %s item(s) marked for review.',\n unansweredCount.toString(),\n flaggedCount.toString()\n ) + ' ' + message;\n } else {\n if (flaggedCount) {\n message = __('You have %s item(s) marked for review.', flaggedCount.toString()) + ' ' + message;\n }\n\n if (unansweredCount) {\n message = __('You have %s unanswered question(s).', unansweredCount.toString()) + ' ' + message;\n }\n }\n }\n\n $confirmBox.find('.message').html(message);\n $confirmBox.find('.js-exit-confirm').html(confirmLabel);\n $confirmBox.find('.js-exit-cancel').html(cancelLabel);\n $confirmBox.modal({ width: 500 });\n\n $confirmBox.find('.js-exit-cancel, .modal-close').off('click').on('click', function () {\n $confirmBox.modal('close');\n });\n\n $confirmBox.find('.js-exit-confirm').off('click').on('click', function () {\n $confirmBox.modal('close');\n if (_.isFunction(action)) {\n action.call(self);\n }\n });\n\n return $confirmBox;\n },\n\n /**\n * Kill current item section and execute callback function given as first parameter.\n * Item end execution time will be stored in metadata object to be sent to the server.\n * @param {function} callback\n */\n killItemSession : function (callback) {\n testMetaData.addData({\n 'ITEM' : {\n 'ITEM_END_TIME_CLIENT' : Date.now() / 1000,\n 'ITEM_TIMEZONE' : moment().utcOffset(moment().utcOffset()).format('Z')\n }\n });\n if (typeof callback !== 'function') {\n callback = _.noop;\n }\n this.itemServiceApi.kill(callback);\n },\n\n /**\n * Checks if the current item is active\n * @returns {Boolean}\n */\n isCurrentItemActive: function(){\n return (this.testContext.itemSessionState !=4);\n },\n\n /**\n * Tells is the current item has been answered or not\n * The item is considered answered when at least one response has been set to not empty {base : null}\n *\n * @returns {Boolean}\n */\n isCurrentItemAnswered: function(){\n var answered = false;\n _.each(this.getCurrentItemState(), function(state){\n if(state && _.isObject(state.response) && state.response.base !== null){\n answered = true;//at least one response is not null so consider the item answered\n return false;\n }\n });\n return answered;\n },\n\n /**\n * Checks if a particular option is enabled for the current item\n * @param {String} option\n * @returns {Boolean}\n */\n hasOption: function(option) {\n return _.indexOf(this.testContext.categories, option) >= 0;\n },\n\n /**\n * Gets access to the qtiRunner instance\n * @returns {Object}\n */\n getQtiRunner: function(){\n var itemFrame = document.getElementById('qti-item');\n var itemWindow = itemFrame && itemFrame.contentWindow;\n var itemContainerFrame = itemWindow && itemWindow.document.getElementById('item-container');\n var itemContainerWindow = itemContainerFrame && itemContainerFrame.contentWindow;\n return itemContainerWindow && itemContainerWindow.qtiRunner;\n },\n\n /**\n * Checks if the current section is timed\n * @returns {Boolean}\n */\n isTimedSection: function(){\n var timeConstraints = this.testContext.timeConstraints,\n isTimedSection = false;\n for( var index in timeConstraints ){\n if(timeConstraints.hasOwnProperty(index) &&\n timeConstraints[index].qtiClassName === 'assessmentSection' ){\n isTimedSection = true;\n }\n }\n\n return isTimedSection;\n },\n\n /**\n * Gets the list of items owned by the current section\n * @returns {Array}\n */\n getCurrentSectionItems: function(){\n var partId = this.testContext.testPartId,\n navMap = this.testContext.navigatorMap,\n sectionItems;\n\n for( var partIndex in navMap ){\n if( !navMap.hasOwnProperty(partIndex)){\n continue;\n }\n if( navMap[partIndex].id !== partId ){\n continue;\n }\n\n for(var sectionIndex in navMap[partIndex].sections){\n if( !navMap[partIndex].sections.hasOwnProperty(sectionIndex)){\n continue;\n }\n if( navMap[partIndex].sections[sectionIndex].active === true ){\n sectionItems = navMap[partIndex].sections[sectionIndex].items;\n break;\n }\n }\n }\n\n return sectionItems;\n },\n\n /**\n * Skips the current item\n */\n skip: function () {\n var self = this,\n doSkip = function() {\n self.disableGui();\n self.actionCall('skip');\n };\n\n if (this.shouldDisplayEndTestWarning()) {\n this.displayEndTestWarning(doSkip);\n } else {\n doSkip();\n }\n },\n\n /**\n * Handles the timeout state\n */\n timeout: function () {\n var self = this;\n this.disableGui();\n this.testContext.isTimeout = true;\n this.updateTimer();\n\n this.killItemSession(function () {\n var confirmBox = $('.timeout-modal-feedback'),\n testContext = self.testContext,\n confirmBtn = confirmBox.find('.js-timeout-confirm, .modal-close');\n\n if (testContext.numberCompletedSection === testContext.numberItemsSection) {\n testMetaData.addData({\"SECTION\" : {\"SECTION_EXIT_CODE\" : testMetaData.SECTION_EXIT_CODE.COMPLETE_TIMEOUT}});\n } else {\n testMetaData.addData({\"SECTION\" : {\"SECTION_EXIT_CODE\" : testMetaData.SECTION_EXIT_CODE.TIMEOUT}});\n }\n\n self.enableGui();\n confirmBox.modal({width: 500});\n confirmBtn.off('click').on('click', function () {\n confirmBox.modal('close');\n self.actionCall('timeout');\n });\n });\n },\n\n /**\n * Sets the assessment test context object\n * @param {Object} testContext\n */\n setTestContext: function(testContext) {\n this.testContext = testContext;\n this.itemServiceApi = eval(testContext.itemServiceApiCall);\n this.itemServiceApi.setHasBeenPaused(testContext.hasBeenPaused);\n },\n\n\n /**\n * Handles Metadata initialization\n */\n initMetadata: function (){\n testMetaData = testMetaDataFactory({\n testServiceCallId: this.itemServiceApi.serviceCallId\n });\n },\n\n /**\n * Retrieve service responsible for broken session tracking\n * @returns {*}\n */\n getSessionStateService: function () {\n if (!sessionStateService) {\n sessionStateService = this.testContext.sessionStateService({accuracy: 1000});\n }\n return sessionStateService;\n },\n\n /**\n * Updates the GUI\n * @param {Object} testContext\n */\n update: function (testContext) {\n var self = this;\n $controls.$itemFrame.remove();\n\n var $runner = $('#runner');\n $runner.css('height', 'auto');\n\n this.getSessionStateService().restart();\n\n this.setTestContext(testContext);\n this.updateContext();\n this.updateProgress();\n this.updateNavigation();\n this.updateTestReview();\n this.updateInformation();\n this.updateRubrics();\n this.updateTools(testContext);\n this.updateTimer();\n this.updateExitButton();\n this.resetCurrentItemState();\n this.initMetadata();\n\n $controls.$itemFrame = $('