diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5cf4ae5..7aeb7db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,8 +30,8 @@ jobs: strategy: fail-fast: false matrix: - php: ['7.3', '7.4', '8.0'] - moodle-branch: ['MOODLE_39_STABLE', 'MOODLE_310_STABLE', 'MOODLE_311_STABLE', 'MOODLE_400_STABLE', 'MOODLE_401_STABLE'] + php: ['7.3', '7.4', '8.0', '8.1'] + moodle-branch: ['MOODLE_39_STABLE', 'MOODLE_310_STABLE', 'MOODLE_311_STABLE', 'MOODLE_400_STABLE', 'MOODLE_401_STABLE', 'MOODLE_402_STABLE'] database: [pgsql, mariadb] steps: @@ -93,13 +93,13 @@ jobs: if: ${{ always() }} run: moodle-plugin-ci savepoints - - name: Mustache Lint - if: ${{ always() }} - run: moodle-plugin-ci mustache + # - name: Mustache Lint + # if: ${{ always() }} + # run: moodle-plugin-ci mustache - name: Grunt if: ${{ always() }} - run: moodle-plugin-ci grunt --max-lint-warnings 0 + run: moodle-plugin-ci grunt --max-lint-warnings 1 - name: PHPUnit tests if: ${{ always() }} diff --git a/CHANGES.md b/CHANGES.md index 3a9f6f9..361c5f9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,18 @@ ## Changelog ## +- [1.2.9]: + - Ensured compatibility with Moodle 4.2. + - [Layout]: Minor layout fixes because of the new versions of the bootstrap and fontawesome libraries. + - [Icon]: Added monologo version of the activity icon for current Moodle versions. + - [Bugfix]: Minor code changes for new php version. + - [Bugfix]: Removed deprecated legacy function from some events. + - [Bugfix]: Cancel button on edit.php now working as intended. + - [Bugfix]: Now hiding the edit and delete annotation buttons for annotations shown on edit.php. + - [Bugfix]: Fixed a bug that the overview page of a margic was not displayed if it has been restored and a teacher who has graded an entry there does not exist anymore. + - [Bugfix]: When editing an existing entry where the date can be set manually, the current date is now pre-filled. Also, the date set for the edited version must be newer than the date of the original entry. + - [Bugfix]: Small adjustment to the print view of the overview page: Removing unnecessary margin at the top of the page. Also the background colors of the annotations are now always printed in all browsers. + + - tl;dr: The update ensures compatibility of the Margic plugin with Moodle 4.2 and contains a few minor fixes for existing bugs. It should be installed when updating your moodle to 4.2, otherwise its recommended but optional. + - [1.2.8]: - [Bugfix]: Renamed start and end field in table margic_annotations because those names are forbidden in postgreSQL databases. - [Bugfix]: Fixed wrong default value in line 43 edit.php to prevent an error in moodle installations using postgreSQL databases. diff --git a/amd/build/annotations.min.js b/amd/build/annotations.min.js index 83c258b..245842b 100644 --- a/amd/build/annotations.min.js +++ b/amd/build/annotations.min.js @@ -5,6 +5,6 @@ define("mod_margic/annotations",["exports","jquery","./highlighting"],(function( * @module mod_margic/annotations * @copyright 2022 coactum GmbH * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj};_exports.init=(cmid,canmakeannotations,myuserid,focusannotation)=>{var edited=!1,annotations=Array(),newannotation=!1;function editAnnotation(annotationid){if(edited==annotationid)(0,_highlighting.removeAllTempHighlights)(),resetForms(),edited=!1;else if(canmakeannotations&&myuserid==annotations[annotationid].userid){(0,_highlighting.removeAllTempHighlights)(),resetForms(),edited=annotationid;var entry=annotations[annotationid].entry;(0,_jquery.default)(".annotation-box-"+annotationid).hide(),(0,_jquery.default)(".annotation-form-"+entry+' input[name="startcontainer"]').val(annotations[annotationid].startcontainer),(0,_jquery.default)(".annotation-form-"+entry+' input[name="endcontainer"]').val(annotations[annotationid].endcontainer),(0,_jquery.default)(".annotation-form-"+entry+' input[name="startoffset"]').val(annotations[annotationid].startoffset),(0,_jquery.default)(".annotation-form-"+entry+' input[name="endoffset"]').val(annotations[annotationid].endoffset),(0,_jquery.default)(".annotation-form-"+entry+' input[name="annotationstart"]').val(annotations[annotationid].annotationstart),(0,_jquery.default)(".annotation-form-"+entry+' input[name="annotationend"]').val(annotations[annotationid].annotationend),(0,_jquery.default)(".annotation-form-"+entry+' input[name="exact"]').val(annotations[annotationid].exact),(0,_jquery.default)(".annotation-form-"+entry+' input[name="prefix"]').val(annotations[annotationid].prefix),(0,_jquery.default)(".annotation-form-"+entry+' input[name="suffix"]').val(annotations[annotationid].suffix),(0,_jquery.default)(".annotation-form-"+entry+' input[name="annotationid"]').val(annotationid),(0,_jquery.default)(".annotation-form-"+entry+' textarea[name="text"]').val(annotations[annotationid].text),(0,_jquery.default)(".annotation-form-"+entry+" select").val(annotations[annotationid].type),(0,_jquery.default)("#annotationpreview-temp-"+entry).html(annotations[annotationid].exact.replaceAll("<","<").replaceAll(">",">")),(0,_jquery.default)("#annotationpreview-temp-"+entry).css("border-color","#"+annotations[annotationid].color),(0,_jquery.default)(".annotationarea-"+entry+" .annotation-form").insertBefore(".annotation-box-"+annotationid),(0,_jquery.default)(".annotationarea-"+entry+" .annotation-form").show(),(0,_jquery.default)(".annotationarea-"+entry+" #id_text").focus()}else(0,_jquery.default)(".annotation-box-"+annotationid).focus()}function resetForms(){(0,_jquery.default)(".annotation-form").hide(),(0,_jquery.default)('.annotation-form input[name^="annotationid"]').val(null),(0,_jquery.default)('.annotation-form input[name^="startcontainer"]').val(-1),(0,_jquery.default)('.annotation-form input[name^="endcontainer"]').val(-1),(0,_jquery.default)('.annotation-form input[name^="startoffset"]').val(-1),(0,_jquery.default)('.annotation-form input[name^="endoffset"]').val(-1),(0,_jquery.default)('.annotation-form textarea[name^="text"]').val(""),(0,_jquery.default)(".annotation-box").not(".annotation-form").show()}(0,_jquery.default)(".annotation-form div.col-md-3").removeClass("col-md-3"),(0,_jquery.default)(".annotation-form div.col-md-9").removeClass("col-md-9"),(0,_jquery.default)(".annotation-form div.form-group").removeClass("form-group"),(0,_jquery.default)(".annotation-form div.row").removeClass("row"),(0,_jquery.default)(document).on("click","#id_cancel",(function(e){e.preventDefault(),(0,_highlighting.removeAllTempHighlights)(),resetForms(),edited=!1})),(0,_jquery.default)("textarea").keypress((function(e){13==e.which&&((0,_jquery.default)(this).parents(":eq(2)").submit(),e.preventDefault())})),(0,_jquery.default)(document).on("mouseup",".originaltext",(function(){if(""!==window.getSelection().getRangeAt(0).cloneContents().textContent&&canmakeannotations){(0,_highlighting.removeAllTempHighlights)(),resetForms(),newannotation=function(root){const ranges=[window.getSelection().getRangeAt(0)];if(ranges.collapsed)return null;const rangeSelectors=ranges.map((range=>(0,_highlighting.describe)(root,range))),annotation={target:rangeSelectors.map((selectors=>({selector:selectors})))};return(0,_highlighting.anchor)(annotation,root),annotation}(this);var entry=this.id.replace(/entry-/,"");(0,_jquery.default)(".annotation-form-"+entry+' input[name="startcontainer"]').val(newannotation.target[0].selector[0].startContainer),(0,_jquery.default)(".annotation-form-"+entry+' input[name="endcontainer"]').val(newannotation.target[0].selector[0].endContainer),(0,_jquery.default)(".annotation-form-"+entry+' input[name="startoffset"]').val(newannotation.target[0].selector[0].startOffset),(0,_jquery.default)(".annotation-form-"+entry+' input[name="endoffset"]').val(newannotation.target[0].selector[0].endOffset),(0,_jquery.default)(".annotation-form-"+entry+' input[name="annotationstart"]').val(newannotation.target[0].selector[1].start),(0,_jquery.default)(".annotation-form-"+entry+' input[name="annotationend"]').val(newannotation.target[0].selector[1].end),(0,_jquery.default)(".annotation-form-"+entry+' input[name="exact"]').val(newannotation.target[0].selector[2].exact),(0,_jquery.default)(".annotation-form-"+entry+' input[name="prefix"]').val(newannotation.target[0].selector[2].prefix),(0,_jquery.default)(".annotation-form-"+entry+' input[name="suffix"]').val(newannotation.target[0].selector[2].suffix),(0,_jquery.default)(".annotation-form-"+entry+" select").val(1),(0,_jquery.default)("#annotationpreview-temp-"+entry).html(newannotation.target[0].selector[2].exact.replaceAll("<","<").replaceAll(">",">")),(0,_jquery.default)(".annotationarea-"+entry+" .annotation-form").show(),(0,_jquery.default)(".annotation-form-"+entry+" #id_text").focus()}})),_jquery.default.ajax({url:"./annotations.php",data:{id:cmid,getannotations:1},success:function(response){annotations=JSON.parse(response),function(){for(let annotation of Object.values(annotations)){const newannotation={annotation:annotation,target:[[{type:"RangeSelector",startContainer:annotation.startcontainer,startOffset:parseInt(annotation.startoffset),endContainer:annotation.endcontainer,endOffset:parseInt(annotation.endoffset)},{type:"TextPositionSelector",start:parseInt(annotation.annotationstart),end:parseInt(annotation.annotationend)},{type:"TextQuoteSelector",exact:annotation.exact,prefix:annotation.prefix,suffix:annotation.suffix}]].map((selectors=>({selector:selectors})))};(0,_highlighting.anchor)(newannotation,(0,_jquery.default)("#entry-"+annotation.entry)[0])}}(),(0,_jquery.default)(".annotated").mouseenter((function(){var id=this.id.replace("annotated-","");(0,_jquery.default)(".annotation-box-"+id).addClass("hovered"),(0,_jquery.default)(".annotated-"+id).css("background-color","lightblue")})),(0,_jquery.default)(".annotated").mouseleave((function(){var id=this.id.replace("annotated-","");(0,_jquery.default)(".annotation-box-"+id).removeClass("hovered"),(0,_jquery.default)(".annotated-"+id).css("background-color",(0,_jquery.default)(".annotated-"+id).css("textDecorationColor"))})),(0,_jquery.default)(document).on("mouseover",".annotated_temp",(function(){(0,_jquery.default)(".annotated_temp").addClass("hovered")})),(0,_jquery.default)(document).on("mouseleave",".annotated_temp",(function(){(0,_jquery.default)(".annotated_temp").removeClass("hovered")})),(0,_jquery.default)(document).on("click",".annotated",(function(){editAnnotation(this.id.replace("annotated-",""))})),(0,_jquery.default)(document).on("click",".edit-annotation",(function(){editAnnotation(this.id.replace("edit-annotation-",""))})),(0,_jquery.default)(document).on("mouseover",".hoverannotation",(function(){var id=this.id.replace("hoverannotation-","");(0,_jquery.default)(".annotated-"+id).css("background-color","lightblue")})),(0,_jquery.default)(document).on("mouseleave",".hoverannotation",(function(){var id=this.id.replace("hoverannotation-","");(0,_jquery.default)(".annotated-"+id).css("background-color",(0,_jquery.default)(".annotated-"+id).css("textDecorationColor"))})),0!=focusannotation&&((0,_jquery.default)(".annotated-"+focusannotation).attr("tabindex",-1),(0,_jquery.default)(".annotated-"+focusannotation).focus())},complete:function(){(0,_jquery.default)("#overlay").hide()},error:function(){}})}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=(obj=_jquery)&&obj.__esModule?obj:{default:obj};_exports.init=(cmid,canmakeannotations,myuserid,focusannotation)=>{var edited=!1,annotations=Array(),newannotation=!1;function editAnnotation(annotationid){if(edited==annotationid)(0,_highlighting.removeAllTempHighlights)(),resetForms(),edited=!1;else if(canmakeannotations&&myuserid==annotations[annotationid].userid){(0,_highlighting.removeAllTempHighlights)(),resetForms(),edited=annotationid;var entry=annotations[annotationid].entry;(0,_jquery.default)(".annotation-box-"+annotationid).hide(),(0,_jquery.default)(".annotation-form-"+entry+' input[name="startcontainer"]').val(annotations[annotationid].startcontainer),(0,_jquery.default)(".annotation-form-"+entry+' input[name="endcontainer"]').val(annotations[annotationid].endcontainer),(0,_jquery.default)(".annotation-form-"+entry+' input[name="startoffset"]').val(annotations[annotationid].startoffset),(0,_jquery.default)(".annotation-form-"+entry+' input[name="endoffset"]').val(annotations[annotationid].endoffset),(0,_jquery.default)(".annotation-form-"+entry+' input[name="annotationstart"]').val(annotations[annotationid].annotationstart),(0,_jquery.default)(".annotation-form-"+entry+' input[name="annotationend"]').val(annotations[annotationid].annotationend),(0,_jquery.default)(".annotation-form-"+entry+' input[name="exact"]').val(annotations[annotationid].exact),(0,_jquery.default)(".annotation-form-"+entry+' input[name="prefix"]').val(annotations[annotationid].prefix),(0,_jquery.default)(".annotation-form-"+entry+' input[name="suffix"]').val(annotations[annotationid].suffix),(0,_jquery.default)(".annotation-form-"+entry+' input[name="annotationid"]').val(annotationid),(0,_jquery.default)(".annotation-form-"+entry+' textarea[name="text"]').val(annotations[annotationid].text),(0,_jquery.default)(".annotation-form-"+entry+" select").val(annotations[annotationid].type),(0,_jquery.default)("#annotationpreview-temp-"+entry).html(annotations[annotationid].exact.replaceAll("<","<").replaceAll(">",">")),(0,_jquery.default)("#annotationpreview-temp-"+entry).css("border-color","#"+annotations[annotationid].color),(0,_jquery.default)(".annotationarea-"+entry+" .annotation-form").insertBefore(".annotation-box-"+annotationid),(0,_jquery.default)(".annotationarea-"+entry+" .annotation-form").show(),(0,_jquery.default)(".annotationarea-"+entry+" #id_text").focus()}else(0,_jquery.default)(".annotation-box-"+annotationid).focus()}function resetForms(){(0,_jquery.default)(".annotation-form").hide(),(0,_jquery.default)('.annotation-form input[name^="annotationid"]').val(null),(0,_jquery.default)('.annotation-form input[name^="startcontainer"]').val(-1),(0,_jquery.default)('.annotation-form input[name^="endcontainer"]').val(-1),(0,_jquery.default)('.annotation-form input[name^="startoffset"]').val(-1),(0,_jquery.default)('.annotation-form input[name^="endoffset"]').val(-1),(0,_jquery.default)('.annotation-form textarea[name^="text"]').val(""),(0,_jquery.default)(".annotation-box").not(".annotation-form").show()}(0,_jquery.default)(".annotation-form div.col-md-3").removeClass("col-md-3"),(0,_jquery.default)(".annotation-form div.col-md-9").removeClass("col-md-9"),(0,_jquery.default)(".annotation-form div.form-group").removeClass("form-group"),(0,_jquery.default)(".annotation-form div.row").removeClass("row"),(0,_jquery.default)(document).on("click",".margic_entry #id_cancel",(function(e){e.preventDefault(),(0,_highlighting.removeAllTempHighlights)(),resetForms(),edited=!1})),(0,_jquery.default)(".margic_entry textarea").keypress((function(e){13==e.which&&((0,_jquery.default)(this).parents(":eq(2)").submit(),e.preventDefault())})),(0,_jquery.default)(document).on("mouseup",".originaltext",(function(){if(""!==window.getSelection().getRangeAt(0).cloneContents().textContent&&canmakeannotations){(0,_highlighting.removeAllTempHighlights)(),resetForms(),newannotation=function(root){const ranges=[window.getSelection().getRangeAt(0)];if(ranges.collapsed)return null;const rangeSelectors=ranges.map((range=>(0,_highlighting.describe)(root,range))),annotation={target:rangeSelectors.map((selectors=>({selector:selectors})))};return(0,_highlighting.anchor)(annotation,root),annotation}(this);var entry=this.id.replace(/entry-/,"");(0,_jquery.default)(".annotation-form-"+entry+' input[name="startcontainer"]').val(newannotation.target[0].selector[0].startContainer),(0,_jquery.default)(".annotation-form-"+entry+' input[name="endcontainer"]').val(newannotation.target[0].selector[0].endContainer),(0,_jquery.default)(".annotation-form-"+entry+' input[name="startoffset"]').val(newannotation.target[0].selector[0].startOffset),(0,_jquery.default)(".annotation-form-"+entry+' input[name="endoffset"]').val(newannotation.target[0].selector[0].endOffset),(0,_jquery.default)(".annotation-form-"+entry+' input[name="annotationstart"]').val(newannotation.target[0].selector[1].start),(0,_jquery.default)(".annotation-form-"+entry+' input[name="annotationend"]').val(newannotation.target[0].selector[1].end),(0,_jquery.default)(".annotation-form-"+entry+' input[name="exact"]').val(newannotation.target[0].selector[2].exact),(0,_jquery.default)(".annotation-form-"+entry+' input[name="prefix"]').val(newannotation.target[0].selector[2].prefix),(0,_jquery.default)(".annotation-form-"+entry+' input[name="suffix"]').val(newannotation.target[0].selector[2].suffix),(0,_jquery.default)(".annotation-form-"+entry+" select").val(1),(0,_jquery.default)("#annotationpreview-temp-"+entry).html(newannotation.target[0].selector[2].exact.replaceAll("<","<").replaceAll(">",">")),(0,_jquery.default)(".annotationarea-"+entry+" .annotation-form").show(),(0,_jquery.default)(".annotation-form-"+entry+" #id_text").focus()}})),_jquery.default.ajax({url:"./annotations.php",data:{id:cmid,getannotations:1},success:function(response){annotations=JSON.parse(response),function(){for(let annotation of Object.values(annotations)){const newannotation={annotation:annotation,target:[[{type:"RangeSelector",startContainer:annotation.startcontainer,startOffset:parseInt(annotation.startoffset),endContainer:annotation.endcontainer,endOffset:parseInt(annotation.endoffset)},{type:"TextPositionSelector",start:parseInt(annotation.annotationstart),end:parseInt(annotation.annotationend)},{type:"TextQuoteSelector",exact:annotation.exact,prefix:annotation.prefix,suffix:annotation.suffix}]].map((selectors=>({selector:selectors})))};(0,_highlighting.anchor)(newannotation,(0,_jquery.default)("#entry-"+annotation.entry)[0])}}(),(0,_jquery.default)(".annotated").mouseenter((function(){var id=this.id.replace("annotated-","");(0,_jquery.default)(".annotation-box-"+id).addClass("hovered"),(0,_jquery.default)(".annotated-"+id).css("background-color","lightblue")})),(0,_jquery.default)(".annotated").mouseleave((function(){var id=this.id.replace("annotated-","");(0,_jquery.default)(".annotation-box-"+id).removeClass("hovered"),(0,_jquery.default)(".annotated-"+id).css("background-color",(0,_jquery.default)(".annotated-"+id).css("textDecorationColor"))})),(0,_jquery.default)(document).on("mouseover",".annotated_temp",(function(){(0,_jquery.default)(".annotated_temp").addClass("hovered")})),(0,_jquery.default)(document).on("mouseleave",".annotated_temp",(function(){(0,_jquery.default)(".annotated_temp").removeClass("hovered")})),(0,_jquery.default)(document).on("click",".annotated",(function(){editAnnotation(this.id.replace("annotated-",""))})),(0,_jquery.default)(document).on("click",".edit-annotation",(function(){editAnnotation(this.id.replace("edit-annotation-",""))})),(0,_jquery.default)(document).on("mouseover",".hoverannotation",(function(){var id=this.id.replace("hoverannotation-","");(0,_jquery.default)(".annotated-"+id).css("background-color","lightblue")})),(0,_jquery.default)(document).on("mouseleave",".hoverannotation",(function(){var id=this.id.replace("hoverannotation-","");(0,_jquery.default)(".annotated-"+id).css("background-color",(0,_jquery.default)(".annotated-"+id).css("textDecorationColor"))})),0!=focusannotation&&((0,_jquery.default)(".annotated-"+focusannotation).attr("tabindex",-1),(0,_jquery.default)(".annotated-"+focusannotation).focus())},complete:function(){(0,_jquery.default)("#overlay").hide()},error:function(){}})}})); //# sourceMappingURL=annotations.min.js.map \ No newline at end of file diff --git a/amd/build/annotations.min.js.map b/amd/build/annotations.min.js.map index a36170f..48f7162 100644 --- a/amd/build/annotations.min.js.map +++ b/amd/build/annotations.min.js.map @@ -1 +1 @@ -{"version":3,"file":"annotations.min.js","sources":["../src/annotations.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle 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 Moodle. If not, see .\n\n/**\n * Module for the annotation functions of the margic.\n *\n * @module mod_margic/annotations\n * @copyright 2022 coactum GmbH\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport {removeAllTempHighlights, anchor, describe} from './highlighting';\n\nexport const init = (cmid, canmakeannotations, myuserid, focusannotation) => {\n\n var edited = false;\n var annotations = Array();\n\n var newannotation = false;\n\n // Remove col-mds from moodle form.\n $('.annotation-form div.col-md-3').removeClass('col-md-3');\n $('.annotation-form div.col-md-9').removeClass('col-md-9');\n $('.annotation-form div.form-group').removeClass('form-group');\n $('.annotation-form div.row').removeClass('row');\n\n // Onclick listener if form is canceled.\n $(document).on('click', '#id_cancel', function(e) {\n e.preventDefault();\n\n removeAllTempHighlights(); // Remove other temporary highlights.\n\n resetForms(); // Remove old form contents.\n\n edited = false;\n });\n\n // Listen for return key pressed to submit annotation form.\n $('textarea').keypress(function(e) {\n if (e.which == 13) {\n $(this).parents(':eq(2)').submit();\n e.preventDefault();\n }\n });\n\n // If user selects text for new annotation\n $(document).on('mouseup', '.originaltext', function() {\n var selectedrange = window.getSelection().getRangeAt(0);\n\n if (selectedrange.cloneContents().textContent !== '' && canmakeannotations) {\n\n removeAllTempHighlights(); // Remove other temporary highlights.\n\n resetForms(); // Reset the annotation forms.\n\n // Create new annotation.\n newannotation = createAnnotation(this);\n\n var entry = this.id.replace(/entry-/, '');\n\n // RangeSelector.\n $('.annotation-form-' + entry + ' input[name=\"startcontainer\"]').val(\n newannotation.target[0].selector[0].startContainer);\n $('.annotation-form-' + entry + ' input[name=\"endcontainer\"]').val(\n newannotation.target[0].selector[0].endContainer);\n $('.annotation-form-' + entry + ' input[name=\"startoffset\"]').val(\n newannotation.target[0].selector[0].startOffset);\n $('.annotation-form-' + entry + ' input[name=\"endoffset\"]').val(\n newannotation.target[0].selector[0].endOffset);\n\n // TextPositionSelector.\n $('.annotation-form-' + entry + ' input[name=\"annotationstart\"]').val(\n newannotation.target[0].selector[1].start);\n $('.annotation-form-' + entry + ' input[name=\"annotationend\"]').val(\n newannotation.target[0].selector[1].end);\n\n // TextQuoteSelector.\n $('.annotation-form-' + entry + ' input[name=\"exact\"]').val(\n newannotation.target[0].selector[2].exact);\n $('.annotation-form-' + entry + ' input[name=\"prefix\"]').val(\n newannotation.target[0].selector[2].prefix);\n $('.annotation-form-' + entry + ' input[name=\"suffix\"]').val(\n newannotation.target[0].selector[2].suffix);\n\n $('.annotation-form-' + entry + ' select').val(1);\n\n // Prevent JavaScript injection (if annotated text in original entry is JavaScript code in script tags).\n $('#annotationpreview-temp-' + entry).html(\n newannotation.target[0].selector[2].exact.replaceAll('<', '<').replaceAll('>', '>'));\n\n $('.annotationarea-' + entry + ' .annotation-form').show();\n $('.annotation-form-' + entry + ' #id_text').focus();\n }\n });\n\n // Fetch and recreate annotations.\n $.ajax({\n url: './annotations.php',\n data: {'id': cmid, 'getannotations': 1},\n success: function(response) {\n annotations = JSON.parse(response);\n recreateAnnotations();\n\n // Highlight annotation and all annotated text if annotated text is hovered\n $('.annotated').mouseenter(function() {\n var id = this.id.replace('annotated-', '');\n $('.annotation-box-' + id).addClass('hovered');\n $('.annotated-' + id).css(\"background-color\", 'lightblue');\n });\n\n $('.annotated').mouseleave(function() {\n var id = this.id.replace('annotated-', '');\n $('.annotation-box-' + id).removeClass('hovered');\n $('.annotated-' + id).css(\"background-color\", $('.annotated-' + id).css('textDecorationColor'));\n });\n\n // Highlight whole temp annotation if part of temp annotation is hovered\n $(document).on('mouseover', '.annotated_temp', function() {\n $('.annotated_temp').addClass('hovered');\n });\n\n $(document).on('mouseleave', '.annotated_temp', function() {\n $('.annotated_temp').removeClass('hovered');\n });\n\n // Onclick listener for editing annotation.\n $(document).on('click', '.annotated', function() {\n var id = this.id.replace('annotated-', '');\n editAnnotation(id);\n });\n\n // Onclick listener for editing annotation.\n $(document).on('click', '.edit-annotation', function() {\n var id = this.id.replace('edit-annotation-', '');\n editAnnotation(id);\n });\n\n // Highlight annotation if hoverannotation button is hovered\n $(document).on('mouseover', '.hoverannotation', function() {\n var id = this.id.replace('hoverannotation-', '');\n $('.annotated-' + id).css(\"background-color\", 'lightblue');\n });\n\n $(document).on('mouseleave', '.hoverannotation', function() {\n var id = this.id.replace('hoverannotation-', '');\n $('.annotated-' + id).css(\"background-color\", $('.annotated-' + id).css('textDecorationColor'));\n });\n\n\n // Focus annotation if needed.\n if (focusannotation != 0) {\n $('.annotated-' + focusannotation).attr('tabindex', -1);\n $('.annotated-' + focusannotation).focus();\n }\n\n },\n complete: function() {\n $('#overlay').hide();\n },\n error: function() {\n // For output: alert('Error fetching annotations');\n }\n });\n\n /**\n * Recreate annotations.\n *\n */\n function recreateAnnotations() {\n\n for (let annotation of Object.values(annotations)) {\n\n const rangeSelectors = [[\n {type: \"RangeSelector\", startContainer: annotation.startcontainer, startOffset: parseInt(annotation.startoffset),\n endContainer: annotation.endcontainer, endOffset: parseInt(annotation.endoffset)},\n {type: \"TextPositionSelector\", start: parseInt(annotation.annotationstart),\n end: parseInt(annotation.annotationend)},\n {type: \"TextQuoteSelector\", exact: annotation.exact, prefix: annotation.prefix, suffix: annotation.suffix}\n ]];\n\n const target = rangeSelectors.map(selectors => ({\n selector: selectors,\n }));\n\n /** @type {AnnotationData} */\n const newannotation = {\n annotation: annotation,\n target: target,\n };\n\n anchor(newannotation, $(\"#entry-\" + annotation.entry)[0]);\n }\n }\n\n /**\n * Edit annotation.\n *\n * @param {int} annotationid\n */\n function editAnnotation(annotationid) {\n\n if (edited == annotationid) {\n removeAllTempHighlights(); // Remove other temporary highlights.\n resetForms(); // Remove old form contents.\n edited = false;\n } else if (canmakeannotations && myuserid == annotations[annotationid].userid) {\n removeAllTempHighlights(); // Remove other temporary highlights.\n resetForms(); // Remove old form contents.\n\n edited = annotationid;\n\n var entry = annotations[annotationid].entry;\n\n $('.annotation-box-' + annotationid).hide(); // Hide edited annotation-box.\n\n $('.annotation-form-' + entry + ' input[name=\"startcontainer\"]').val(annotations[annotationid].startcontainer);\n $('.annotation-form-' + entry + ' input[name=\"endcontainer\"]').val(annotations[annotationid].endcontainer);\n $('.annotation-form-' + entry + ' input[name=\"startoffset\"]').val(annotations[annotationid].startoffset);\n $('.annotation-form-' + entry + ' input[name=\"endoffset\"]').val(annotations[annotationid].endoffset);\n $('.annotation-form-' + entry + ' input[name=\"annotationstart\"]').val(annotations[annotationid].annotationstart);\n $('.annotation-form-' + entry + ' input[name=\"annotationend\"]').val(annotations[annotationid].annotationend);\n $('.annotation-form-' + entry + ' input[name=\"exact\"]').val(annotations[annotationid].exact);\n $('.annotation-form-' + entry + ' input[name=\"prefix\"]').val(annotations[annotationid].prefix);\n $('.annotation-form-' + entry + ' input[name=\"suffix\"]').val(annotations[annotationid].suffix);\n\n $('.annotation-form-' + entry + ' input[name=\"annotationid\"]').val(annotationid);\n\n $('.annotation-form-' + entry + ' textarea[name=\"text\"]').val(annotations[annotationid].text);\n\n $('.annotation-form-' + entry + ' select').val(annotations[annotationid].type);\n\n // Prevent JavaScript injection (if annotated text in original entry is JavaScript code in script tags).\n $('#annotationpreview-temp-' + entry).html(\n annotations[annotationid].exact.replaceAll('<', '<').replaceAll('>', '>'));\n $('#annotationpreview-temp-' + entry).css('border-color', '#' + annotations[annotationid].color);\n\n $('.annotationarea-' + entry + ' .annotation-form').insertBefore('.annotation-box-' + annotationid);\n $('.annotationarea-' + entry + ' .annotation-form').show();\n $('.annotationarea-' + entry + ' #id_text').focus();\n } else {\n $('.annotation-box-' + annotationid).focus();\n }\n }\n\n /**\n * Reset all annotation forms\n */\n function resetForms() {\n $('.annotation-form').hide();\n\n $('.annotation-form input[name^=\"annotationid\"]').val(null);\n\n $('.annotation-form input[name^=\"startcontainer\"]').val(-1);\n $('.annotation-form input[name^=\"endcontainer\"]').val(-1);\n $('.annotation-form input[name^=\"startoffset\"]').val(-1);\n $('.annotation-form input[name^=\"endoffset\"]').val(-1);\n\n $('.annotation-form textarea[name^=\"text\"]').val('');\n\n $('.annotation-box').not('.annotation-form').show(); // To show again edited annotation.\n }\n};\n\n/**\n * Create a new annotation that is associated with the selected region of\n * the current document.\n *\n * @param {object} root - The root element\n * @return {object} - The new annotation\n */\nfunction createAnnotation(root) {\n const ranges = [window.getSelection().getRangeAt(0)];\n\n if (ranges.collapsed) {\n return null;\n }\n\n const rangeSelectors = ranges.map(range => describe(root, range));\n\n const target = rangeSelectors.map(selectors => ({\n selector: selectors,\n }));\n\n /** @type {AnnotationData} */\n const annotation = {\n target,\n };\n\n anchor(annotation, root);\n\n return annotation;\n}"],"names":["obj","_jquery","__esModule","default","_exports","init","cmid","canmakeannotations","myuserid","focusannotation","edited","annotations","Array","newannotation","editAnnotation","annotationid","removeAllTempHighlights","resetForms","userid","entry","$","hide","val","startcontainer","endcontainer","startoffset","endoffset","annotationstart","annotationend","exact","prefix","suffix","text","type","html","replaceAll","css","color","insertBefore","show","focus","not","removeClass","document","on","e","preventDefault","keypress","which","this","parents","submit","window","getSelection","getRangeAt","cloneContents","textContent","root","ranges","collapsed","rangeSelectors","map","range","describe","annotation","target","selectors","selector","anchor","createAnnotation","id","replace","startContainer","endContainer","startOffset","endOffset","start","end","ajax","url","data","getannotations","success","response","JSON","parse","Object","values","parseInt","recreateAnnotations","mouseenter","addClass","mouseleave","attr","complete","error"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"annotations.min.js","sources":["../src/annotations.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle 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 Moodle. If not, see .\n\n/**\n * Module for the annotation functions of the margic.\n *\n * @module mod_margic/annotations\n * @copyright 2022 coactum GmbH\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport {removeAllTempHighlights, anchor, describe} from './highlighting';\n\nexport const init = (cmid, canmakeannotations, myuserid, focusannotation) => {\n\n var edited = false;\n var annotations = Array();\n\n var newannotation = false;\n\n // Remove col-mds from moodle form.\n $('.annotation-form div.col-md-3').removeClass('col-md-3');\n $('.annotation-form div.col-md-9').removeClass('col-md-9');\n $('.annotation-form div.form-group').removeClass('form-group');\n $('.annotation-form div.row').removeClass('row');\n\n // Onclick listener if form is canceled.\n $(document).on('click', '.margic_entry #id_cancel', function(e) {\n e.preventDefault();\n\n removeAllTempHighlights(); // Remove other temporary highlights.\n\n resetForms(); // Remove old form contents.\n\n edited = false;\n });\n\n // Listen for return key pressed to submit annotation form.\n $('.margic_entry textarea').keypress(function(e) {\n if (e.which == 13) {\n $(this).parents(':eq(2)').submit();\n e.preventDefault();\n }\n });\n\n // If user selects text for new annotation\n $(document).on('mouseup', '.originaltext', function() {\n var selectedrange = window.getSelection().getRangeAt(0);\n\n if (selectedrange.cloneContents().textContent !== '' && canmakeannotations) {\n\n removeAllTempHighlights(); // Remove other temporary highlights.\n\n resetForms(); // Reset the annotation forms.\n\n // Create new annotation.\n newannotation = createAnnotation(this);\n\n var entry = this.id.replace(/entry-/, '');\n\n // RangeSelector.\n $('.annotation-form-' + entry + ' input[name=\"startcontainer\"]').val(\n newannotation.target[0].selector[0].startContainer);\n $('.annotation-form-' + entry + ' input[name=\"endcontainer\"]').val(\n newannotation.target[0].selector[0].endContainer);\n $('.annotation-form-' + entry + ' input[name=\"startoffset\"]').val(\n newannotation.target[0].selector[0].startOffset);\n $('.annotation-form-' + entry + ' input[name=\"endoffset\"]').val(\n newannotation.target[0].selector[0].endOffset);\n\n // TextPositionSelector.\n $('.annotation-form-' + entry + ' input[name=\"annotationstart\"]').val(\n newannotation.target[0].selector[1].start);\n $('.annotation-form-' + entry + ' input[name=\"annotationend\"]').val(\n newannotation.target[0].selector[1].end);\n\n // TextQuoteSelector.\n $('.annotation-form-' + entry + ' input[name=\"exact\"]').val(\n newannotation.target[0].selector[2].exact);\n $('.annotation-form-' + entry + ' input[name=\"prefix\"]').val(\n newannotation.target[0].selector[2].prefix);\n $('.annotation-form-' + entry + ' input[name=\"suffix\"]').val(\n newannotation.target[0].selector[2].suffix);\n\n $('.annotation-form-' + entry + ' select').val(1);\n\n // Prevent JavaScript injection (if annotated text in original entry is JavaScript code in script tags).\n $('#annotationpreview-temp-' + entry).html(\n newannotation.target[0].selector[2].exact.replaceAll('<', '<').replaceAll('>', '>'));\n\n $('.annotationarea-' + entry + ' .annotation-form').show();\n $('.annotation-form-' + entry + ' #id_text').focus();\n }\n });\n\n // Fetch and recreate annotations.\n $.ajax({\n url: './annotations.php',\n data: {'id': cmid, 'getannotations': 1},\n success: function(response) {\n annotations = JSON.parse(response);\n recreateAnnotations();\n\n // Highlight annotation and all annotated text if annotated text is hovered\n $('.annotated').mouseenter(function() {\n var id = this.id.replace('annotated-', '');\n $('.annotation-box-' + id).addClass('hovered');\n $('.annotated-' + id).css(\"background-color\", 'lightblue');\n });\n\n $('.annotated').mouseleave(function() {\n var id = this.id.replace('annotated-', '');\n $('.annotation-box-' + id).removeClass('hovered');\n $('.annotated-' + id).css(\"background-color\", $('.annotated-' + id).css('textDecorationColor'));\n });\n\n // Highlight whole temp annotation if part of temp annotation is hovered\n $(document).on('mouseover', '.annotated_temp', function() {\n $('.annotated_temp').addClass('hovered');\n });\n\n $(document).on('mouseleave', '.annotated_temp', function() {\n $('.annotated_temp').removeClass('hovered');\n });\n\n // Onclick listener for editing annotation.\n $(document).on('click', '.annotated', function() {\n var id = this.id.replace('annotated-', '');\n editAnnotation(id);\n });\n\n // Onclick listener for editing annotation.\n $(document).on('click', '.edit-annotation', function() {\n var id = this.id.replace('edit-annotation-', '');\n editAnnotation(id);\n });\n\n // Highlight annotation if hoverannotation button is hovered\n $(document).on('mouseover', '.hoverannotation', function() {\n var id = this.id.replace('hoverannotation-', '');\n $('.annotated-' + id).css(\"background-color\", 'lightblue');\n });\n\n $(document).on('mouseleave', '.hoverannotation', function() {\n var id = this.id.replace('hoverannotation-', '');\n $('.annotated-' + id).css(\"background-color\", $('.annotated-' + id).css('textDecorationColor'));\n });\n\n\n // Focus annotation if needed.\n if (focusannotation != 0) {\n $('.annotated-' + focusannotation).attr('tabindex', -1);\n $('.annotated-' + focusannotation).focus();\n }\n\n },\n complete: function() {\n $('#overlay').hide();\n },\n error: function() {\n // For output: alert('Error fetching annotations');\n }\n });\n\n /**\n * Recreate annotations.\n *\n */\n function recreateAnnotations() {\n\n for (let annotation of Object.values(annotations)) {\n\n const rangeSelectors = [[\n {type: \"RangeSelector\", startContainer: annotation.startcontainer, startOffset: parseInt(annotation.startoffset),\n endContainer: annotation.endcontainer, endOffset: parseInt(annotation.endoffset)},\n {type: \"TextPositionSelector\", start: parseInt(annotation.annotationstart),\n end: parseInt(annotation.annotationend)},\n {type: \"TextQuoteSelector\", exact: annotation.exact, prefix: annotation.prefix, suffix: annotation.suffix}\n ]];\n\n const target = rangeSelectors.map(selectors => ({\n selector: selectors,\n }));\n\n /** @type {AnnotationData} */\n const newannotation = {\n annotation: annotation,\n target: target,\n };\n\n anchor(newannotation, $(\"#entry-\" + annotation.entry)[0]);\n }\n }\n\n /**\n * Edit annotation.\n *\n * @param {int} annotationid\n */\n function editAnnotation(annotationid) {\n\n if (edited == annotationid) {\n removeAllTempHighlights(); // Remove other temporary highlights.\n resetForms(); // Remove old form contents.\n edited = false;\n } else if (canmakeannotations && myuserid == annotations[annotationid].userid) {\n removeAllTempHighlights(); // Remove other temporary highlights.\n resetForms(); // Remove old form contents.\n\n edited = annotationid;\n\n var entry = annotations[annotationid].entry;\n\n $('.annotation-box-' + annotationid).hide(); // Hide edited annotation-box.\n\n $('.annotation-form-' + entry + ' input[name=\"startcontainer\"]').val(annotations[annotationid].startcontainer);\n $('.annotation-form-' + entry + ' input[name=\"endcontainer\"]').val(annotations[annotationid].endcontainer);\n $('.annotation-form-' + entry + ' input[name=\"startoffset\"]').val(annotations[annotationid].startoffset);\n $('.annotation-form-' + entry + ' input[name=\"endoffset\"]').val(annotations[annotationid].endoffset);\n $('.annotation-form-' + entry + ' input[name=\"annotationstart\"]').val(annotations[annotationid].annotationstart);\n $('.annotation-form-' + entry + ' input[name=\"annotationend\"]').val(annotations[annotationid].annotationend);\n $('.annotation-form-' + entry + ' input[name=\"exact\"]').val(annotations[annotationid].exact);\n $('.annotation-form-' + entry + ' input[name=\"prefix\"]').val(annotations[annotationid].prefix);\n $('.annotation-form-' + entry + ' input[name=\"suffix\"]').val(annotations[annotationid].suffix);\n\n $('.annotation-form-' + entry + ' input[name=\"annotationid\"]').val(annotationid);\n\n $('.annotation-form-' + entry + ' textarea[name=\"text\"]').val(annotations[annotationid].text);\n\n $('.annotation-form-' + entry + ' select').val(annotations[annotationid].type);\n\n // Prevent JavaScript injection (if annotated text in original entry is JavaScript code in script tags).\n $('#annotationpreview-temp-' + entry).html(\n annotations[annotationid].exact.replaceAll('<', '<').replaceAll('>', '>'));\n $('#annotationpreview-temp-' + entry).css('border-color', '#' + annotations[annotationid].color);\n\n $('.annotationarea-' + entry + ' .annotation-form').insertBefore('.annotation-box-' + annotationid);\n $('.annotationarea-' + entry + ' .annotation-form').show();\n $('.annotationarea-' + entry + ' #id_text').focus();\n } else {\n $('.annotation-box-' + annotationid).focus();\n }\n }\n\n /**\n * Reset all annotation forms\n */\n function resetForms() {\n $('.annotation-form').hide();\n\n $('.annotation-form input[name^=\"annotationid\"]').val(null);\n\n $('.annotation-form input[name^=\"startcontainer\"]').val(-1);\n $('.annotation-form input[name^=\"endcontainer\"]').val(-1);\n $('.annotation-form input[name^=\"startoffset\"]').val(-1);\n $('.annotation-form input[name^=\"endoffset\"]').val(-1);\n\n $('.annotation-form textarea[name^=\"text\"]').val('');\n\n $('.annotation-box').not('.annotation-form').show(); // To show again edited annotation.\n }\n};\n\n/**\n * Create a new annotation that is associated with the selected region of\n * the current document.\n *\n * @param {object} root - The root element\n * @return {object} - The new annotation\n */\nfunction createAnnotation(root) {\n const ranges = [window.getSelection().getRangeAt(0)];\n\n if (ranges.collapsed) {\n return null;\n }\n\n const rangeSelectors = ranges.map(range => describe(root, range));\n\n const target = rangeSelectors.map(selectors => ({\n selector: selectors,\n }));\n\n /** @type {AnnotationData} */\n const annotation = {\n target,\n };\n\n anchor(annotation, root);\n\n return annotation;\n}"],"names":["cmid","canmakeannotations","myuserid","focusannotation","edited","annotations","Array","newannotation","editAnnotation","annotationid","resetForms","userid","entry","hide","val","startcontainer","endcontainer","startoffset","endoffset","annotationstart","annotationend","exact","prefix","suffix","text","type","html","replaceAll","css","color","insertBefore","show","focus","not","removeClass","document","on","e","preventDefault","keypress","which","this","parents","submit","window","getSelection","getRangeAt","cloneContents","textContent","root","ranges","collapsed","rangeSelectors","map","range","annotation","target","selectors","selector","createAnnotation","id","replace","startContainer","endContainer","startOffset","endOffset","start","end","ajax","url","data","success","response","JSON","parse","Object","values","parseInt","recreateAnnotations","mouseenter","addClass","mouseleave","attr","complete","error"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/highlighting.min.js b/amd/build/highlighting.min.js index b1eae32..18539b4 100644 --- a/amd/build/highlighting.min.js +++ b/amd/build/highlighting.min.js @@ -1,3 +1,3 @@ -define("mod_margic/highlighting",["exports","jquery","./types","./text-range"],(function(_exports,_jquery,_types,_textRange){var obj;function highlightRange(range){let annotationid=arguments.length>1&&void 0!==arguments[1]&&arguments[1],cssClass=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"annotated",color=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"FFFF00";const textNodes=function(range){if(range.collapsed)return[];let root=range.commonAncestorContainer;root.nodeType!==Node.ELEMENT_NODE&&(root=root.parentElement);if(!root)return[];const textNodes=[],nodeIter=root.ownerDocument.createNodeIterator(root,NodeFilter.SHOW_TEXT);let node;for(;node=nodeIter.nextNode();){if(!isNodeInRange(range,node))continue;let text=node;text===range.startContainer&&range.startOffset>0?text.splitText(range.startOffset):(text===range.endContainer&&range.endOffset{prevNode&&prevNode.nextSibling===node?currentSpan.push(node):(currentSpan=[node],textNodeSpans.push(currentSpan)),prevNode=node}));const whitespace=/^\s*$/;textNodeSpans=textNodeSpans.filter((span=>span.some((node=>!whitespace.test(node.nodeValue)))));const highlights=[];return textNodeSpans.forEach((nodes=>{const highlightEl=document.createElement("margic-highlight");highlightEl.className=cssClass,annotationid&&(highlightEl.className+=" "+cssClass+"-"+annotationid,highlightEl.style="text-decoration:underline; text-decoration-color: #"+color,highlightEl.id=cssClass+"-"+annotationid,highlightEl.style.backgroundColor="#"+color);nodes[0].parentNode.replaceChild(highlightEl,nodes[0]),nodes.forEach((node=>highlightEl.appendChild(node))),highlights.push(highlightEl)})),highlights}function isNodeInRange(range,node){try{var _node$nodeValue$lengt,_node$nodeValue;const length=null!==(_node$nodeValue$lengt=null===(_node$nodeValue=node.nodeValue)||void 0===_node$nodeValue?void 0:_node$nodeValue.length)&&void 0!==_node$nodeValue$lengt?_node$nodeValue$lengt:node.childNodes.length;return range.comparePoint(node,0)<=0&&range.comparePoint(node,length)>=0}catch(e){return!1}}function querySelector(anchor){let options=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return anchor.toRange(options)}function replaceWith(node,replacements){const parent=node.parentNode;replacements.forEach((r=>parent.insertBefore(r,node))),node.remove()}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.anchor=function(annotation,root){const highlight=anchor=>{const range=function(anchor){if(!anchor.range)return null;try{return anchor.range.toRange()}catch{return null}}(anchor);if(!range)return;let highlights=[];highlights=annotation.annotation?highlightRange(range,annotation.annotation.id,"annotated",annotation.annotation.color):highlightRange(range,!1,"annotated_temp"),highlights.forEach((h=>{h._annotation=anchor.annotation})),anchor.highlights=highlights};annotation.target||(annotation.target=[]);const anchors=annotation.target.map((target=>{if(!target.selector||!target.selector.some((s=>"TextQuoteSelector"===s.type)))return{annotation:annotation,target:target};let anchor;try{const range=function(root,selectors){let options=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},position=null,quote=null,range=null;for(let selector of selectors)switch(selector.type){case"TextPositionSelector":position=selector,options.hint=position.start;break;case"TextQuoteSelector":quote=selector;break;case"RangeSelector":range=selector}const maybeAssertQuote=range=>{var _quote;if(null!==(_quote=quote)&&void 0!==_quote&&_quote.exact&&range.toString()!==quote.exact)throw new Error("quote mismatch");return range};let queryselector=!1;try{if(range)return queryselector=querySelector(_types.RangeAnchor.fromSelector(root,range),options),queryselector||maybeAssertQuote}catch(error){try{if(position)return queryselector=querySelector(_types.TextPositionAnchor.fromSelector(root,position),options),queryselector||maybeAssertQuote}catch(error){try{if(quote)return queryselector=querySelector(_types.TextQuoteAnchor.fromSelector(root,quote),options),queryselector}catch(error){return!1}}}return!1}(root,target.selector),textRange=_textRange.TextRange.fromRange(range);anchor={annotation:annotation,target:target,range:textRange}}catch(err){anchor={annotation:annotation,target:target}}return anchor}));for(let anchor of anchors)highlight(anchor);return annotation.$orphan=anchors.length>0&&anchors.every((anchor=>anchor.target.selector&&!anchor.range)),anchors},_exports.describe=function(root,range){const types=[_types.RangeAnchor,_types.TextPositionAnchor,_types.TextQuoteAnchor],result=[];for(let type of types)try{const anchor=type.fromRange(root,range);result.push(anchor.toSelector())}catch(error){continue}return result},_exports.removeAllTempHighlights=function(){const highlights=Array.from((0,_jquery.default)("body")[0].querySelectorAll(".annotated_temp"));void 0!==highlights&&0!=highlights.length&&function(highlights){for(var i=0;i1&&void 0!==arguments[1]&&arguments[1],cssClass=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"annotated",color=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"FFFF00";const textNodes=wholeTextNodesInRange(range);let textNodeSpans=[],prevNode=null,currentSpan=null;textNodes.forEach((node=>{prevNode&&prevNode.nextSibling===node?currentSpan.push(node):(currentSpan=[node],textNodeSpans.push(currentSpan)),prevNode=node}));const whitespace=/^\s*$/;textNodeSpans=textNodeSpans.filter((span=>span.some((node=>!whitespace.test(node.nodeValue)))));const highlights=[];return textNodeSpans.forEach((nodes=>{const highlightEl=document.createElement("margic-highlight");highlightEl.className=cssClass,annotationid&&(highlightEl.className+=" "+cssClass+"-"+annotationid,highlightEl.style="text-decoration:underline; text-decoration-color: #"+color,highlightEl.id=cssClass+"-"+annotationid,highlightEl.style.backgroundColor="#"+color);nodes[0].parentNode.replaceChild(highlightEl,nodes[0]),nodes.forEach((node=>highlightEl.appendChild(node))),highlights.push(highlightEl)})),highlights}function wholeTextNodesInRange(range){if(range.collapsed)return[];let root=range.commonAncestorContainer;if(root.nodeType!==Node.ELEMENT_NODE&&(root=root.parentElement),!root)return[];const textNodes=[],nodeIter=root.ownerDocument.createNodeIterator(root,NodeFilter.SHOW_TEXT);let node;for(;node=nodeIter.nextNode();){if(!isNodeInRange(range,node))continue;let text=node;text===range.startContainer&&range.startOffset>0?text.splitText(range.startOffset):(text===range.endContainer&&range.endOffset=0}catch(e){return!1}}function querySelector(anchor){let options=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return anchor.toRange(options)}function replaceWith(node,replacements){const parent=node.parentNode;replacements.forEach((r=>parent.insertBefore(r,node))),node.remove()}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.anchor=function(annotation,root){const highlight=anchor=>{const range=function(anchor){if(!anchor.range)return null;try{return anchor.range.toRange()}catch{return null}}(anchor);if(!range)return;let highlights=[];highlights=annotation.annotation?highlightRange(range,annotation.annotation.id,"annotated",annotation.annotation.color):highlightRange(range,!1,"annotated_temp"),highlights.forEach((h=>{h._annotation=anchor.annotation})),anchor.highlights=highlights};annotation.target||(annotation.target=[]);const anchors=annotation.target.map((target=>{if(!target.selector||!target.selector.some((s=>"TextQuoteSelector"===s.type)))return{annotation:annotation,target:target};let anchor;try{const range=function(root,selectors){let options=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},position=null,quote=null,range=null;for(let selector of selectors)switch(selector.type){case"TextPositionSelector":position=selector,options.hint=position.start;break;case"TextQuoteSelector":quote=selector;break;case"RangeSelector":range=selector}const maybeAssertQuote=range=>{var _quote;if(null!==(_quote=quote)&&void 0!==_quote&&_quote.exact&&range.toString()!==quote.exact)throw new Error("quote mismatch");return range};let queryselector=!1;try{if(range)return queryselector=querySelector(_types.RangeAnchor.fromSelector(root,range),options),queryselector||maybeAssertQuote}catch(error){try{if(position)return queryselector=querySelector(_types.TextPositionAnchor.fromSelector(root,position),options),queryselector||maybeAssertQuote}catch(error){try{if(quote)return queryselector=querySelector(_types.TextQuoteAnchor.fromSelector(root,quote),options),queryselector}catch(error){return!1}}}return!1}(root,target.selector),textRange=_textRange.TextRange.fromRange(range);anchor={annotation:annotation,target:target,range:textRange}}catch(err){anchor={annotation:annotation,target:target}}return anchor}));for(let anchor of anchors)highlight(anchor);return annotation.$orphan=anchors.length>0&&anchors.every((anchor=>anchor.target.selector&&!anchor.range)),anchors},_exports.describe=function(root,range){const types=[_types.RangeAnchor,_types.TextPositionAnchor,_types.TextQuoteAnchor],result=[];for(let type of types)try{const anchor=type.fromRange(root,range);result.push(anchor.toSelector())}catch(error){continue}return result},_exports.removeAllTempHighlights=function(){const highlights=Array.from((0,_jquery.default)("body")[0].querySelectorAll(".annotated_temp"));void 0!==highlights&&0!=highlights.length&&function(highlights){for(var i=0;i {\n\n // Only annotations with an associated quote can currently be anchored.\n // This is because the quote is used to verify anchoring with other selector\n // types.\n if (\n !target.selector ||\n !target.selector.some(s => s.type === 'TextQuoteSelector')\n ) {\n return {annotation, target};\n }\n\n /** @type {Anchor} */\n let anchor;\n try {\n const range = htmlAnchor(root, target.selector);\n // Convert the `Range` to a `TextRange` which can be converted back to\n // a `Range` later. The `TextRange` representation allows for highlights\n // to be inserted during anchoring other annotations without \"breaking\"\n // this anchor.\n\n\n const textRange = TextRange.fromRange(range);\n\n anchor = {annotation, target, range: textRange};\n\n } catch (err) {\n\n anchor = {annotation, target};\n }\n\n return anchor;\n };\n\n /**\n * Highlight the text range that `anchor` refers to.\n *\n * @param {Anchor} anchor\n */\n const highlight = anchor => {\n\n const range = resolveAnchor(anchor);\n\n if (!range) {\n return;\n }\n\n let highlights = [];\n\n if (annotation.annotation) {\n highlights = highlightRange(range, annotation.annotation.id, 'annotated', annotation.annotation.color);\n } else {\n highlights = highlightRange(range, false, 'annotated_temp');\n }\n\n highlights.forEach(h => {\n h._annotation = anchor.annotation;\n });\n anchor.highlights = highlights;\n\n };\n\n // Remove existing anchors for this annotation.\n // this.detach(annotation, false /* notify */); // To be replaced by own method\n\n // Resolve selectors to ranges and insert highlights.\n if (!annotation.target) {\n annotation.target = [];\n }\n const anchors = annotation.target.map(locate);\n\n for (let anchor of anchors) {\n\n highlight(anchor);\n }\n\n // Set flag indicating whether anchoring succeeded. For each target,\n // anchoring is successful either if there are no selectors (ie. this is a\n // Page Note) or we successfully resolved the selectors to a range.\n annotation.$orphan =\n anchors.length > 0 &&\n anchors.every(anchor => anchor.target.selector && !anchor.range);\n\n return anchors;\n}\n\n/**\n * Resolve an anchor's associated document region to a concrete `Range`.\n *\n * This may fail if anchoring failed or if the document has been mutated since\n * the anchor was created in a way that invalidates the anchor.\n *\n * @param {Anchor} anchor\n * @return {Range|null}\n */\nfunction resolveAnchor(anchor) {\n\n if (!anchor.range) {\n return null;\n }\n try {\n return anchor.range.toRange();\n } catch {\n return null;\n }\n}\n\n/**\n * Wraps the DOM Nodes within the provided range with a highlight\n * element of the specified class and returns the highlight Elements.\n *\n * Modified for handling annotations.\n *\n * @param {Range} range - Range to be highlighted\n * @param {int} annotationid - ID of annotation\n * @param {string} cssClass - A CSS class to use for the highlight\n * @param {string} color - Color of the highlighting\n * @return {HighlightElement[]} - Elements wrapping text in `normedRange` to add a highlight effect\n */\n function highlightRange(range, annotationid = false, cssClass = 'annotated', color = 'FFFF00') {\n\n const textNodes = wholeTextNodesInRange(range);\n\n // Group text nodes into spans of adjacent nodes. If a group of text nodes are\n // adjacent, we only need to create one highlight element for the group.\n let textNodeSpans = [];\n let prevNode = null;\n let currentSpan = null;\n\n textNodes.forEach(node => {\n if (prevNode && prevNode.nextSibling === node) {\n currentSpan.push(node);\n } else {\n currentSpan = [node];\n textNodeSpans.push(currentSpan);\n }\n prevNode = node;\n });\n\n // Filter out text node spans that consist only of white space. This avoids\n // inserting highlight elements in places that can only contain a restricted\n // subset of nodes such as table rows and lists.\n const whitespace = /^\\s*$/;\n textNodeSpans = textNodeSpans.filter(span =>\n // Check for at least one text node with non-space content.\n span.some(node => !whitespace.test(node.nodeValue))\n );\n\n // Wrap each text node span with a `` element.\n const highlights = /** @type {HighlightElement[]} */ ([]);\n\n textNodeSpans.forEach(nodes => {\n const highlightEl = document.createElement('margic-highlight');\n highlightEl.className = cssClass;\n\n if (annotationid) {\n highlightEl.className += ' ' + cssClass + '-' + annotationid;\n highlightEl.style = \"text-decoration:underline; text-decoration-color: #\" + color;\n highlightEl.id = cssClass + '-' + annotationid;\n highlightEl.style.backgroundColor = '#' + color;\n }\n\n const parent = /** @type {Node} */ (nodes[0].parentNode);\n parent.replaceChild(highlightEl, nodes[0]);\n nodes.forEach(node => highlightEl.appendChild(node));\n\n highlights.push(highlightEl);\n\n });\n\n return highlights;\n}\n\n/**\n * Return text nodes which are entirely inside `range`.\n *\n * If a range starts or ends part-way through a text node, the node is split\n * and the part inside the range is returned.\n *\n * @param {Range} range\n * @return {Text[]}\n */\n function wholeTextNodesInRange(range) {\n if (range.collapsed) {\n // Exit early for an empty range to avoid an edge case that breaks the algorithm\n // below. Splitting a text node at the start of an empty range can leave the\n // range ending in the left part rather than the right part.\n return [];\n }\n\n /** @type {Node|null} */\n let root = range.commonAncestorContainer;\n if (root.nodeType !== Node.ELEMENT_NODE) {\n // If the common ancestor is not an element, set it to the parent element to\n // ensure that the loop below visits any text nodes generated by splitting\n // the common ancestor.\n //\n // Note that `parentElement` may be `null`.\n root = root.parentElement;\n }\n\n if (!root) {\n // If there is no root element then we won't be able to insert highlights,\n // so exit here.\n return [];\n }\n\n const textNodes = [];\n const nodeIter = /** @type {Document} */ (\n root.ownerDocument\n ).createNodeIterator(\n root,\n NodeFilter.SHOW_TEXT // Only return `Text` nodes.\n );\n let node;\n while ((node = nodeIter.nextNode())) {\n if (!isNodeInRange(range, node)) {\n continue;\n }\n let text = /** @type {Text} */ (node);\n\n if (text === range.startContainer && range.startOffset > 0) {\n // Split `text` where the range starts. The split will create a new `Text`\n // node which will be in the range and will be visited in the next loop iteration.\n text.splitText(range.startOffset);\n continue;\n }\n\n if (text === range.endContainer && range.endOffset < text.data.length) {\n // Split `text` where the range ends, leaving it as the part in the range.\n text.splitText(range.endOffset);\n }\n\n textNodes.push(text);\n }\n\n return textNodes;\n}\n\n/**\n * Returns true if any part of `node` lies within `range`.\n *\n * @param {Range} range\n * @param {Node} node\n * @return {bool} - If node is in range\n */\nfunction isNodeInRange(range, node) {\n try {\n const length = node.nodeValue?.length ?? node.childNodes.length;\n return (\n // Check start of node is before end of range.\n range.comparePoint(node, 0) <= 0 &&\n // Check end of node is after start of range.\n range.comparePoint(node, length) >= 0\n );\n } catch (e) {\n // `comparePoint` may fail if the `range` and `node` do not share a common\n // ancestor or `node` is a doctype.\n return false;\n }\n}\n\n/**\n * @param {RangeAnchor|TextPositionAnchor|TextQuoteAnchor} anchor\n * @param {Object} [options]\n * @return {obj} - range\n */\n function querySelector(anchor, options = {}) {\n\n return anchor.toRange(options);\n}\n\n/**\n * Anchor a set of selectors.\n *\n * This function converts a set of selectors into a document range.\n * It encapsulates the core anchoring algorithm, using the selectors alone or\n * in combination to establish the best anchor within the document.\n *\n * @param {Element} root - The root element of the anchoring context.\n * @param {Selector[]} selectors - The selectors to try.\n * @param {Object} [options]\n * @return {object} the query selector\n */\n function htmlAnchor(root, selectors, options = {}) {\n let position = null;\n let quote = null;\n let range = null;\n\n // Collect all the selectors\n for (let selector of selectors) {\n switch (selector.type) {\n case 'TextPositionSelector':\n position = selector;\n options.hint = position.start; // TextQuoteAnchor hint\n break;\n case 'TextQuoteSelector':\n quote = selector;\n break;\n case 'RangeSelector':\n range = selector;\n break;\n }\n }\n\n /**\n * Assert the quote matches the stored quote, if applicable\n * @param {Range} range\n * @return {Range} range\n */\n const maybeAssertQuote = range => {\n\n if (quote?.exact && range.toString() !== quote.exact) {\n throw new Error('quote mismatch');\n } else {\n return range;\n }\n };\n\n let queryselector = false;\n\n try {\n if (range) {\n\n let anchor = RangeAnchor.fromSelector(root, range);\n\n queryselector = querySelector(anchor, options);\n\n if (queryselector) {\n return queryselector;\n } else {\n return maybeAssertQuote;\n }\n }\n } catch (error) {\n try {\n if (position) {\n\n let anchor = TextPositionAnchor.fromSelector(root, position);\n\n queryselector = querySelector(anchor, options);\n if (queryselector) {\n return queryselector;\n } else {\n return maybeAssertQuote;\n }\n }\n } catch (error) {\n try {\n if (quote) {\n\n let anchor = TextQuoteAnchor.fromSelector(root, quote);\n\n queryselector = querySelector(anchor, options);\n\n return queryselector;\n }\n } catch (error) {\n return false;\n }\n }\n }\n return false;\n}\n\n/**\n * Remove all temporary highlights under a given root element.\n */\n export function removeAllTempHighlights() {\n const highlights = Array.from($('body')[0].querySelectorAll('.annotated_temp'));\n if (highlights !== undefined && highlights.length != 0) {\n removeHighlights(highlights);\n }\n}\n\n/**\n * Remove highlights from a range previously highlighted with `highlightRange`.\n *\n * @param {HighlightElement[]} highlights - The highlight elements returned by `highlightRange`\n */\n function removeHighlights(highlights) {\n\n for (var i = 0; i < highlights.length; i++) {\n if (highlights[i].parentNode) {\n const children = Array.from(highlights[i].childNodes);\n replaceWith(highlights[i], children);\n }\n }\n}\n\n/**\n * Replace a child `node` with `replacements`.\n *\n * nb. This is like `ChildNode.replaceWith` but it works in older browsers.\n *\n * @param {ChildNode} node\n * @param {Node[]} replacements\n */\nfunction replaceWith(node, replacements) {\n const parent = /** @type {Node} */ (node.parentNode);\n\n replacements.forEach(r => parent.insertBefore(r, node));\n node.remove();\n}"],"names":["obj","highlightRange","range","annotationid","arguments","length","undefined","cssClass","color","textNodes","collapsed","root","commonAncestorContainer","nodeType","Node","ELEMENT_NODE","parentElement","nodeIter","ownerDocument","createNodeIterator","NodeFilter","SHOW_TEXT","node","nextNode","isNodeInRange","text","startContainer","startOffset","splitText","endContainer","endOffset","data","push","wholeTextNodesInRange","textNodeSpans","prevNode","currentSpan","forEach","nextSibling","whitespace","filter","span","some","test","nodeValue","highlights","nodes","highlightEl","document","createElement","className","style","id","backgroundColor","parentNode","replaceChild","appendChild","_node$nodeValue$lengt","_node$nodeValue","childNodes","comparePoint","e","querySelector","anchor","options","toRange","replaceWith","replacements","parent","r","insertBefore","remove","annotation","highlight","resolveAnchor","h","_annotation","target","anchors","map","selector","s","type","selectors","position","quote","hint","start","maybeAssertQuote","_quote","exact","toString","Error","queryselector","RangeAnchor","fromSelector","error","TextPositionAnchor","TextQuoteAnchor","htmlAnchor","textRange","TextRange","fromRange","err","$orphan","every","types","result","toSelector","Array","from","$","default","querySelectorAll","i","children","removeHighlights","_jquery","__esModule"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"highlighting.min.js","sources":["../src/highlighting.js"],"sourcesContent":["/**\n * Functions for the highlighting and anchoring of annotations.\n *\n * This code originaly is from the Hypothesis project (https://github.com/hypothesis/client)\n * which is released under the 2-Clause BSD License (https://opensource.org/licenses/BSD-2-Clause),\n * sometimes referred to as the \"Simplified BSD License\".\n */\n\nimport $ from 'jquery';\nimport {RangeAnchor, TextPositionAnchor, TextQuoteAnchor} from './types';\nimport {TextRange} from './text-range';\n\n/**\n * Get anchors for new annnotation.\n *\n * @param {Element} root\n * @param {Range} range\n * @return {object} - Array with the anchors.\n */\nexport function describe(root, range) {\n const types = [RangeAnchor, TextPositionAnchor, TextQuoteAnchor];\n const result = [];\n\n for (let type of types) {\n try {\n const anchor = type.fromRange(root, range);\n\n result.push(anchor.toSelector());\n } catch (error) {\n continue;\n }\n }\n return result;\n}\n\n/**\n * Anchor an annotation's selectors in the document.\n *\n * _Anchoring_ resolves a set of selectors to a concrete region of the document\n * which is then highlighted.\n *\n * Any existing anchors associated with `annotation` will be removed before\n * re-anchoring the annotation.\n *\n * @param {AnnotationData} annotation\n * @param {obj} root\n * @return {obj} achor object\n */\n export function anchor(annotation, root) {\n /**\n * Resolve an annotation's selectors to a concrete range.\n *\n * @param {Target} target\n * @return {obj}\n */\n const locate = target => {\n\n // Only annotations with an associated quote can currently be anchored.\n // This is because the quote is used to verify anchoring with other selector\n // types.\n if (\n !target.selector ||\n !target.selector.some(s => s.type === 'TextQuoteSelector')\n ) {\n return {annotation, target};\n }\n\n /** @type {Anchor} */\n let anchor;\n try {\n const range = htmlAnchor(root, target.selector);\n // Convert the `Range` to a `TextRange` which can be converted back to\n // a `Range` later. The `TextRange` representation allows for highlights\n // to be inserted during anchoring other annotations without \"breaking\"\n // this anchor.\n\n\n const textRange = TextRange.fromRange(range);\n\n anchor = {annotation, target, range: textRange};\n\n } catch (err) {\n\n anchor = {annotation, target};\n }\n\n return anchor;\n };\n\n /**\n * Highlight the text range that `anchor` refers to.\n *\n * @param {Anchor} anchor\n */\n const highlight = anchor => {\n\n const range = resolveAnchor(anchor);\n\n if (!range) {\n return;\n }\n\n let highlights = [];\n\n if (annotation.annotation) {\n highlights = highlightRange(range, annotation.annotation.id, 'annotated', annotation.annotation.color);\n } else {\n highlights = highlightRange(range, false, 'annotated_temp');\n }\n\n highlights.forEach(h => {\n h._annotation = anchor.annotation;\n });\n anchor.highlights = highlights;\n\n };\n\n // Remove existing anchors for this annotation.\n // this.detach(annotation, false /* notify */); // To be replaced by own method\n\n // Resolve selectors to ranges and insert highlights.\n if (!annotation.target) {\n annotation.target = [];\n }\n const anchors = annotation.target.map(locate);\n\n for (let anchor of anchors) {\n\n highlight(anchor);\n }\n\n // Set flag indicating whether anchoring succeeded. For each target,\n // anchoring is successful either if there are no selectors (ie. this is a\n // Page Note) or we successfully resolved the selectors to a range.\n annotation.$orphan =\n anchors.length > 0 &&\n anchors.every(anchor => anchor.target.selector && !anchor.range);\n\n return anchors;\n}\n\n/**\n * Resolve an anchor's associated document region to a concrete `Range`.\n *\n * This may fail if anchoring failed or if the document has been mutated since\n * the anchor was created in a way that invalidates the anchor.\n *\n * @param {Anchor} anchor\n * @return {Range|null}\n */\nfunction resolveAnchor(anchor) {\n\n if (!anchor.range) {\n return null;\n }\n try {\n return anchor.range.toRange();\n } catch {\n return null;\n }\n}\n\n/**\n * Wraps the DOM Nodes within the provided range with a highlight\n * element of the specified class and returns the highlight Elements.\n *\n * Modified for handling annotations.\n *\n * @param {Range} range - Range to be highlighted\n * @param {int} annotationid - ID of annotation\n * @param {string} cssClass - A CSS class to use for the highlight\n * @param {string} color - Color of the highlighting\n * @return {HighlightElement[]} - Elements wrapping text in `normedRange` to add a highlight effect\n */\n function highlightRange(range, annotationid = false, cssClass = 'annotated', color = 'FFFF00') {\n\n const textNodes = wholeTextNodesInRange(range);\n\n // Group text nodes into spans of adjacent nodes. If a group of text nodes are\n // adjacent, we only need to create one highlight element for the group.\n let textNodeSpans = [];\n let prevNode = null;\n let currentSpan = null;\n\n textNodes.forEach(node => {\n if (prevNode && prevNode.nextSibling === node) {\n currentSpan.push(node);\n } else {\n currentSpan = [node];\n textNodeSpans.push(currentSpan);\n }\n prevNode = node;\n });\n\n // Filter out text node spans that consist only of white space. This avoids\n // inserting highlight elements in places that can only contain a restricted\n // subset of nodes such as table rows and lists.\n const whitespace = /^\\s*$/;\n textNodeSpans = textNodeSpans.filter(span =>\n // Check for at least one text node with non-space content.\n span.some(node => !whitespace.test(node.nodeValue))\n );\n\n // Wrap each text node span with a `` element.\n const highlights = /** @type {HighlightElement[]} */ ([]);\n\n textNodeSpans.forEach(nodes => {\n const highlightEl = document.createElement('margic-highlight');\n highlightEl.className = cssClass;\n\n if (annotationid) {\n highlightEl.className += ' ' + cssClass + '-' + annotationid;\n highlightEl.style = \"text-decoration:underline; text-decoration-color: #\" + color;\n highlightEl.id = cssClass + '-' + annotationid;\n highlightEl.style.backgroundColor = '#' + color;\n }\n\n const parent = /** @type {Node} */ (nodes[0].parentNode);\n parent.replaceChild(highlightEl, nodes[0]);\n nodes.forEach(node => highlightEl.appendChild(node));\n\n highlights.push(highlightEl);\n\n });\n\n return highlights;\n}\n\n/**\n * Return text nodes which are entirely inside `range`.\n *\n * If a range starts or ends part-way through a text node, the node is split\n * and the part inside the range is returned.\n *\n * @param {Range} range\n * @return {Text[]}\n */\n function wholeTextNodesInRange(range) {\n if (range.collapsed) {\n // Exit early for an empty range to avoid an edge case that breaks the algorithm\n // below. Splitting a text node at the start of an empty range can leave the\n // range ending in the left part rather than the right part.\n return [];\n }\n\n /** @type {Node|null} */\n let root = range.commonAncestorContainer;\n if (root.nodeType !== Node.ELEMENT_NODE) {\n // If the common ancestor is not an element, set it to the parent element to\n // ensure that the loop below visits any text nodes generated by splitting\n // the common ancestor.\n //\n // Note that `parentElement` may be `null`.\n root = root.parentElement;\n }\n\n if (!root) {\n // If there is no root element then we won't be able to insert highlights,\n // so exit here.\n return [];\n }\n\n const textNodes = [];\n const nodeIter = /** @type {Document} */ (\n root.ownerDocument\n ).createNodeIterator(\n root,\n NodeFilter.SHOW_TEXT // Only return `Text` nodes.\n );\n let node;\n while ((node = nodeIter.nextNode())) {\n if (!isNodeInRange(range, node)) {\n continue;\n }\n let text = /** @type {Text} */ (node);\n\n if (text === range.startContainer && range.startOffset > 0) {\n // Split `text` where the range starts. The split will create a new `Text`\n // node which will be in the range and will be visited in the next loop iteration.\n text.splitText(range.startOffset);\n continue;\n }\n\n if (text === range.endContainer && range.endOffset < text.data.length) {\n // Split `text` where the range ends, leaving it as the part in the range.\n text.splitText(range.endOffset);\n }\n\n textNodes.push(text);\n }\n\n return textNodes;\n}\n\n/**\n * Returns true if any part of `node` lies within `range`.\n *\n * @param {Range} range\n * @param {Node} node\n * @return {bool} - If node is in range\n */\nfunction isNodeInRange(range, node) {\n try {\n const length = node.nodeValue?.length ?? node.childNodes.length;\n return (\n // Check start of node is before end of range.\n range.comparePoint(node, 0) <= 0 &&\n // Check end of node is after start of range.\n range.comparePoint(node, length) >= 0\n );\n } catch (e) {\n // `comparePoint` may fail if the `range` and `node` do not share a common\n // ancestor or `node` is a doctype.\n return false;\n }\n}\n\n/**\n * @param {RangeAnchor|TextPositionAnchor|TextQuoteAnchor} anchor\n * @param {Object} [options]\n * @return {obj} - range\n */\n function querySelector(anchor, options = {}) {\n\n return anchor.toRange(options);\n}\n\n/**\n * Anchor a set of selectors.\n *\n * This function converts a set of selectors into a document range.\n * It encapsulates the core anchoring algorithm, using the selectors alone or\n * in combination to establish the best anchor within the document.\n *\n * @param {Element} root - The root element of the anchoring context.\n * @param {Selector[]} selectors - The selectors to try.\n * @param {Object} [options]\n * @return {object} the query selector\n */\n function htmlAnchor(root, selectors, options = {}) {\n let position = null;\n let quote = null;\n let range = null;\n\n // Collect all the selectors\n for (let selector of selectors) {\n switch (selector.type) {\n case 'TextPositionSelector':\n position = selector;\n options.hint = position.start; // TextQuoteAnchor hint\n break;\n case 'TextQuoteSelector':\n quote = selector;\n break;\n case 'RangeSelector':\n range = selector;\n break;\n }\n }\n\n /**\n * Assert the quote matches the stored quote, if applicable\n * @param {Range} range\n * @return {Range} range\n */\n const maybeAssertQuote = range => {\n\n if (quote?.exact && range.toString() !== quote.exact) {\n throw new Error('quote mismatch');\n } else {\n return range;\n }\n };\n\n let queryselector = false;\n\n try {\n if (range) {\n\n let anchor = RangeAnchor.fromSelector(root, range);\n\n queryselector = querySelector(anchor, options);\n\n if (queryselector) {\n return queryselector;\n } else {\n return maybeAssertQuote;\n }\n }\n } catch (error) {\n try {\n if (position) {\n\n let anchor = TextPositionAnchor.fromSelector(root, position);\n\n queryselector = querySelector(anchor, options);\n if (queryselector) {\n return queryselector;\n } else {\n return maybeAssertQuote;\n }\n }\n } catch (error) {\n try {\n if (quote) {\n\n let anchor = TextQuoteAnchor.fromSelector(root, quote);\n\n queryselector = querySelector(anchor, options);\n\n return queryselector;\n }\n } catch (error) {\n return false;\n }\n }\n }\n return false;\n}\n\n/**\n * Remove all temporary highlights under a given root element.\n */\n export function removeAllTempHighlights() {\n const highlights = Array.from($('body')[0].querySelectorAll('.annotated_temp'));\n if (highlights !== undefined && highlights.length != 0) {\n removeHighlights(highlights);\n }\n}\n\n/**\n * Remove highlights from a range previously highlighted with `highlightRange`.\n *\n * @param {HighlightElement[]} highlights - The highlight elements returned by `highlightRange`\n */\n function removeHighlights(highlights) {\n\n for (var i = 0; i < highlights.length; i++) {\n if (highlights[i].parentNode) {\n const children = Array.from(highlights[i].childNodes);\n replaceWith(highlights[i], children);\n }\n }\n}\n\n/**\n * Replace a child `node` with `replacements`.\n *\n * nb. This is like `ChildNode.replaceWith` but it works in older browsers.\n *\n * @param {ChildNode} node\n * @param {Node[]} replacements\n */\nfunction replaceWith(node, replacements) {\n const parent = /** @type {Node} */ (node.parentNode);\n\n replacements.forEach(r => parent.insertBefore(r, node));\n node.remove();\n}"],"names":["highlightRange","range","annotationid","cssClass","color","textNodes","wholeTextNodesInRange","textNodeSpans","prevNode","currentSpan","forEach","node","nextSibling","push","whitespace","filter","span","some","test","nodeValue","highlights","nodes","highlightEl","document","createElement","className","style","id","backgroundColor","parentNode","replaceChild","appendChild","collapsed","root","commonAncestorContainer","nodeType","Node","ELEMENT_NODE","parentElement","nodeIter","ownerDocument","createNodeIterator","NodeFilter","SHOW_TEXT","nextNode","isNodeInRange","text","startContainer","startOffset","splitText","endContainer","endOffset","data","length","_node$nodeValue","childNodes","comparePoint","e","querySelector","anchor","options","toRange","replaceWith","replacements","parent","r","insertBefore","remove","annotation","highlight","resolveAnchor","h","_annotation","target","anchors","map","selector","s","type","selectors","position","quote","hint","start","maybeAssertQuote","exact","toString","Error","queryselector","RangeAnchor","fromSelector","error","TextPositionAnchor","TextQuoteAnchor","htmlAnchor","textRange","TextRange","fromRange","err","$orphan","every","types","result","toSelector","Array","from","querySelectorAll","undefined","i","children","removeHighlights"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/match-quote.min.js.map b/amd/build/match-quote.min.js.map index b583708..b560094 100644 --- a/amd/build/match-quote.min.js.map +++ b/amd/build/match-quote.min.js.map @@ -1 +1 @@ -{"version":3,"file":"match-quote.min.js","sources":["../src/match-quote.js"],"sourcesContent":["/**\n * Functions for quote matching for the annotations and highlighting.\n *\n * This code originaly is from the Hypothesis project (https://github.com/hypothesis/client)\n * which is released under the 2-Clause BSD License (https://opensource.org/licenses/BSD-2-Clause),\n * sometimes referred to as the \"Simplified BSD License\".\n */\n\nimport approxSearch from './string-match';\n\n/**\n * @typedef {import('approx-string-match').Match} StringMatch\n */\n\n/**\n * @typedef Match\n * @prop {number} start - Start offset of match in text\n * @prop {number} end - End offset of match in text\n * @prop {number} score -\n * Score for the match between 0 and 1.0, where 1.0 indicates a perfect match\n * for the quote and context.\n */\n\n/**\n * Find the best approximate matches for `str` in `text` allowing up to `maxErrors` errors.\n *\n * @param {string} text\n * @param {string} str\n * @param {number} maxErrors\n * @return {StringMatch[]}\n */\nfunction search(text, str, maxErrors) {\n // Do a fast search for exact matches. The `approx-string-match` library\n // doesn't currently incorporate this optimization itself.\n let matchPos = 0;\n let exactMatches = [];\n while (matchPos !== -1) {\n matchPos = text.indexOf(str, matchPos);\n if (matchPos !== -1) {\n exactMatches.push({\n start: matchPos,\n end: matchPos + str.length,\n errors: 0,\n });\n matchPos += 1;\n }\n }\n if (exactMatches.length > 0) {\n return exactMatches;\n }\n\n // If there are no exact matches, do a more expensive search for matches\n // with errors.\n return approxSearch(text, str, maxErrors);\n}\n\n/**\n * Compute a score between 0 and 1.0 for the similarity between `text` and `str`.\n *\n * @param {string} text\n * @param {string} str\n * @return {int}\n */\nfunction textMatchScore(text, str) {\n // `search` will return no matches if either the text or pattern is empty,\n // otherwise it will return at least one match if the max allowed error count\n // is at least `str.length`.\n if (str.length === 0 || text.length === 0) {\n return 0.0;\n }\n\n const matches = search(text, str, str.length);\n\n // Prettier-ignore.\n return 1 - (matches[0].errors / str.length);\n}\n\n/**\n * Find the best approximate match for `quote` in `text`.\n *\n * Returns `null` if no match exceeding the minimum quality threshold was found.\n *\n * @param {string} text - Document text to search\n * @param {string} quote - String to find within `text`\n * @param {Object} context -\n * Context in which the quote originally appeared. This is used to choose the\n * best match.\n * @param {string} [context.prefix] - Expected text before the quote\n * @param {string} [context.suffix] - Expected text after the quote\n * @param {number} [context.hint] - Expected offset of match within text\n * @return {Match|null}\n */\nexport function matchQuote(text, quote, context = {}) {\n if (quote.length === 0) {\n return null;\n }\n\n // Choose the maximum number of errors to allow for the initial search.\n // This choice involves a tradeoff between:\n //\n // - Recall (proportion of \"good\" matches found)\n // - Precision (proportion of matches found which are \"good\")\n // - Cost of the initial search and of processing the candidate matches [1]\n //\n // [1] Specifically, the expected-time complexity of the initial search is\n // `O((maxErrors / 32) * text.length)`. See `approx-string-match` docs.\n const maxErrors = Math.min(256, quote.length / 2);\n\n // Find closest matches for `quote` in `text` based on edit distance.\n const matches = search(text, quote, maxErrors);\n\n if (matches.length === 0) {\n return null;\n }\n\n /**\n * Compute a score between 0 and 1.0 for a match candidate.\n *\n * @param {StringMatch} match\n * @return {int}\n */\n const scoreMatch = match => {\n const quoteWeight = 50; // Similarity of matched text to quote.\n const prefixWeight = 20; // Similarity of text before matched text to `context.prefix`.\n const suffixWeight = 20; // Similarity of text after matched text to `context.suffix`.\n const posWeight = 2; // Proximity to expected location. Used as a tie-breaker.\n\n const quoteScore = 1 - match.errors / quote.length;\n\n const prefixScore = context.prefix\n ? textMatchScore(\n text.slice(\n Math.max(0, match.start - context.prefix.length),\n match.start\n ),\n context.prefix\n )\n : 1.0;\n const suffixScore = context.suffix\n ? textMatchScore(\n text.slice(match.end, match.end + context.suffix.length),\n context.suffix\n )\n : 1.0;\n\n let posScore = 1.0;\n if (typeof context.hint === 'number') {\n const offset = Math.abs(match.start - context.hint);\n posScore = 1.0 - offset / text.length;\n }\n\n const rawScore =\n quoteWeight * quoteScore +\n prefixWeight * prefixScore +\n suffixWeight * suffixScore +\n posWeight * posScore;\n const maxScore = quoteWeight + prefixWeight + suffixWeight + posWeight;\n const normalizedScore = rawScore / maxScore;\n\n return normalizedScore;\n };\n\n // Rank matches based on similarity of actual and expected surrounding text\n // and actual/expected offset in the document text.\n const scoredMatches = matches.map(m => ({\n start: m.start,\n end: m.end,\n score: scoreMatch(m),\n }));\n\n // Choose match with highest score.\n scoredMatches.sort((a, b) => b.score - a.score);\n return scoredMatches[0];\n}\n"],"names":["obj","search","text","str","maxErrors","matchPos","exactMatches","indexOf","push","start","end","length","errors","approxSearch","textMatchScore","quote","context","arguments","undefined","Math","min","matches","scoreMatch","match","quoteScore","prefixScore","prefix","slice","max","suffixScore","suffix","posScore","hint","abs","quoteWeight","scoredMatches","map","m","score","sort","a","b","_stringMatch","__esModule","default"],"mappings":"8FAQ0C,IAAAA,IAuB1C,SAASC,OAAOC,KAAMC,IAAKC,WAGzB,IAAIC,SAAW,EACXC,aAAe,GACnB,MAAqB,IAAdD,UACLA,SAAWH,KAAKK,QAAQJ,IAAKE,WACX,IAAdA,WACFC,aAAaE,KAAK,CAChBC,MAAOJ,SACPK,IAAKL,SAAWF,IAAIQ,OACpBC,OAAQ,IAEVP,UAAY,GAGhB,OAAIC,aAAaK,OAAS,EACjBL,cAKF,EAAAO,sBAAaX,KAAMC,IAAKC,UACjC,CASA,SAASU,eAAeZ,KAAMC,KAI5B,GAAmB,IAAfA,IAAIQ,QAAgC,IAAhBT,KAAKS,OAC3B,OAAO,EAMT,OAAO,EAHSV,OAAOC,KAAMC,IAAKA,IAAIQ,QAGlB,GAAGC,OAAST,IAAIQ,MACtC,6EAiBO,SAAoBT,KAAMa,OAAqB,IAAdC,QAAOC,UAAAN,OAAA,QAAAO,IAAAD,UAAA,GAAAA,UAAA,GAAG,CAAA,EAChD,GAAqB,IAAjBF,MAAMJ,OACR,OAAO,KAYT,MAAMP,UAAYe,KAAKC,IAAI,IAAKL,MAAMJ,OAAS,GAGzCU,QAAUpB,OAAOC,KAAMa,MAAOX,WAEpC,GAAuB,IAAnBiB,QAAQV,OACV,OAAO,KAST,MAAMW,WAAaC,QACjB,MAKMC,WAAa,EAAID,MAAMX,OAASG,MAAMJ,OAEtCc,YAAcT,QAAQU,OACxBZ,eACEZ,KAAKyB,MACHR,KAAKS,IAAI,EAAGL,MAAMd,MAAQO,QAAQU,OAAOf,QACzCY,MAAMd,OAERO,QAAQU,QAEV,EACEG,YAAcb,QAAQc,OACxBhB,eACEZ,KAAKyB,MAAMJ,MAAMb,IAAKa,MAAMb,IAAMM,QAAQc,OAAOnB,QACjDK,QAAQc,QAEV,EAEJ,IAAIC,SAAW,EACf,GAA4B,iBAAjBf,QAAQgB,KAAmB,CAEpCD,SAAW,EADIZ,KAAKc,IAAIV,MAAMd,MAAQO,QAAQgB,MACpB9B,KAAKS,MACjC,CAUA,OArCoB,GA8BJa,WA7BK,GA8BJC,YA7BI,GA8BJI,YA7BC,EA8BJE,UACGG,EAGK,EAKlBC,cAAgBd,QAAQe,KAAIC,IAAM,CACtC5B,MAAO4B,EAAE5B,MACTC,IAAK2B,EAAE3B,IACP4B,MAAOhB,WAAWe,OAKpB,OADAF,cAAcI,MAAK,CAACC,EAAGC,IAAMA,EAAEH,MAAQE,EAAEF,QAClCH,cAAc,EACvB,EArKAO,cAA0C1C,IAA1C0C,eAA0C1C,IAAA2C,WAAA3C,IAAA4C,CAAAA,QAAA5C,IAqKzC"} \ No newline at end of file +{"version":3,"file":"match-quote.min.js","sources":["../src/match-quote.js"],"sourcesContent":["/**\n * Functions for quote matching for the annotations and highlighting.\n *\n * This code originaly is from the Hypothesis project (https://github.com/hypothesis/client)\n * which is released under the 2-Clause BSD License (https://opensource.org/licenses/BSD-2-Clause),\n * sometimes referred to as the \"Simplified BSD License\".\n */\n\nimport approxSearch from './string-match';\n\n/**\n * @typedef {import('approx-string-match').Match} StringMatch\n */\n\n/**\n * @typedef Match\n * @prop {number} start - Start offset of match in text\n * @prop {number} end - End offset of match in text\n * @prop {number} score -\n * Score for the match between 0 and 1.0, where 1.0 indicates a perfect match\n * for the quote and context.\n */\n\n/**\n * Find the best approximate matches for `str` in `text` allowing up to `maxErrors` errors.\n *\n * @param {string} text\n * @param {string} str\n * @param {number} maxErrors\n * @return {StringMatch[]}\n */\nfunction search(text, str, maxErrors) {\n // Do a fast search for exact matches. The `approx-string-match` library\n // doesn't currently incorporate this optimization itself.\n let matchPos = 0;\n let exactMatches = [];\n while (matchPos !== -1) {\n matchPos = text.indexOf(str, matchPos);\n if (matchPos !== -1) {\n exactMatches.push({\n start: matchPos,\n end: matchPos + str.length,\n errors: 0,\n });\n matchPos += 1;\n }\n }\n if (exactMatches.length > 0) {\n return exactMatches;\n }\n\n // If there are no exact matches, do a more expensive search for matches\n // with errors.\n return approxSearch(text, str, maxErrors);\n}\n\n/**\n * Compute a score between 0 and 1.0 for the similarity between `text` and `str`.\n *\n * @param {string} text\n * @param {string} str\n * @return {int}\n */\nfunction textMatchScore(text, str) {\n // `search` will return no matches if either the text or pattern is empty,\n // otherwise it will return at least one match if the max allowed error count\n // is at least `str.length`.\n if (str.length === 0 || text.length === 0) {\n return 0.0;\n }\n\n const matches = search(text, str, str.length);\n\n // Prettier-ignore.\n return 1 - (matches[0].errors / str.length);\n}\n\n/**\n * Find the best approximate match for `quote` in `text`.\n *\n * Returns `null` if no match exceeding the minimum quality threshold was found.\n *\n * @param {string} text - Document text to search\n * @param {string} quote - String to find within `text`\n * @param {Object} context -\n * Context in which the quote originally appeared. This is used to choose the\n * best match.\n * @param {string} [context.prefix] - Expected text before the quote\n * @param {string} [context.suffix] - Expected text after the quote\n * @param {number} [context.hint] - Expected offset of match within text\n * @return {Match|null}\n */\nexport function matchQuote(text, quote, context = {}) {\n if (quote.length === 0) {\n return null;\n }\n\n // Choose the maximum number of errors to allow for the initial search.\n // This choice involves a tradeoff between:\n //\n // - Recall (proportion of \"good\" matches found)\n // - Precision (proportion of matches found which are \"good\")\n // - Cost of the initial search and of processing the candidate matches [1]\n //\n // [1] Specifically, the expected-time complexity of the initial search is\n // `O((maxErrors / 32) * text.length)`. See `approx-string-match` docs.\n const maxErrors = Math.min(256, quote.length / 2);\n\n // Find closest matches for `quote` in `text` based on edit distance.\n const matches = search(text, quote, maxErrors);\n\n if (matches.length === 0) {\n return null;\n }\n\n /**\n * Compute a score between 0 and 1.0 for a match candidate.\n *\n * @param {StringMatch} match\n * @return {int}\n */\n const scoreMatch = match => {\n const quoteWeight = 50; // Similarity of matched text to quote.\n const prefixWeight = 20; // Similarity of text before matched text to `context.prefix`.\n const suffixWeight = 20; // Similarity of text after matched text to `context.suffix`.\n const posWeight = 2; // Proximity to expected location. Used as a tie-breaker.\n\n const quoteScore = 1 - match.errors / quote.length;\n\n const prefixScore = context.prefix\n ? textMatchScore(\n text.slice(\n Math.max(0, match.start - context.prefix.length),\n match.start\n ),\n context.prefix\n )\n : 1.0;\n const suffixScore = context.suffix\n ? textMatchScore(\n text.slice(match.end, match.end + context.suffix.length),\n context.suffix\n )\n : 1.0;\n\n let posScore = 1.0;\n if (typeof context.hint === 'number') {\n const offset = Math.abs(match.start - context.hint);\n posScore = 1.0 - offset / text.length;\n }\n\n const rawScore =\n quoteWeight * quoteScore +\n prefixWeight * prefixScore +\n suffixWeight * suffixScore +\n posWeight * posScore;\n const maxScore = quoteWeight + prefixWeight + suffixWeight + posWeight;\n const normalizedScore = rawScore / maxScore;\n\n return normalizedScore;\n };\n\n // Rank matches based on similarity of actual and expected surrounding text\n // and actual/expected offset in the document text.\n const scoredMatches = matches.map(m => ({\n start: m.start,\n end: m.end,\n score: scoreMatch(m),\n }));\n\n // Choose match with highest score.\n scoredMatches.sort((a, b) => b.score - a.score);\n return scoredMatches[0];\n}\n"],"names":["search","text","str","maxErrors","matchPos","exactMatches","indexOf","push","start","end","length","errors","textMatchScore","quote","context","Math","min","matches","scoreMatch","match","quoteScore","prefixScore","prefix","slice","max","suffixScore","suffix","posScore","hint","abs","quoteWeight","scoredMatches","map","m","score","sort","a","b"],"mappings":"+GA+BSA,OAAOC,KAAMC,IAAKC,eAGrBC,SAAW,EACXC,aAAe,SACE,IAAdD,UACLA,SAAWH,KAAKK,QAAQJ,IAAKE,WACX,IAAdA,WACFC,aAAaE,KAAK,CAChBC,MAAOJ,SACPK,IAAKL,SAAWF,IAAIQ,OACpBC,OAAQ,IAEVP,UAAY,UAGZC,aAAaK,OAAS,EACjBL,cAKF,wBAAaJ,KAAMC,IAAKC,oBAUxBS,eAAeX,KAAMC,QAIT,IAAfA,IAAIQ,QAAgC,IAAhBT,KAAKS,cACpB,SAMF,EAHSV,OAAOC,KAAMC,IAAKA,IAAIQ,QAGlB,GAAGC,OAAST,IAAIQ,4FAkBXT,KAAMY,WAAOC,+DAAU,MAC3B,IAAjBD,MAAMH,cACD,WAYHP,UAAYY,KAAKC,IAAI,IAAKH,MAAMH,OAAS,GAGzCO,QAAUjB,OAAOC,KAAMY,MAAOV,cAEb,IAAnBc,QAAQP,cACH,WASHQ,WAAaC,cAMXC,WAAa,EAAID,MAAMR,OAASE,MAAMH,OAEtCW,YAAcP,QAAQQ,OACxBV,eACEX,KAAKsB,MACHR,KAAKS,IAAI,EAAGL,MAAMX,MAAQM,QAAQQ,OAAOZ,QACzCS,MAAMX,OAERM,QAAQQ,QAEV,EACEG,YAAcX,QAAQY,OACxBd,eACEX,KAAKsB,MAAMJ,MAAMV,IAAKU,MAAMV,IAAMK,QAAQY,OAAOhB,QACjDI,QAAQY,QAEV,MAEAC,SAAW,KACa,iBAAjBb,QAAQc,KAAmB,CAEpCD,SAAW,EADIZ,KAAKc,IAAIV,MAAMX,MAAQM,QAAQc,MACpB3B,KAAKS,cA1Bb,GA8BJU,WA7BK,GA8BJC,YA7BI,GA8BJI,YA7BC,EA8BJE,UACGG,IAQbC,cAAgBd,QAAQe,KAAIC,KAChCzB,MAAOyB,EAAEzB,MACTC,IAAKwB,EAAExB,IACPyB,MAAOhB,WAAWe,cAIpBF,cAAcI,MAAK,CAACC,EAAGC,IAAMA,EAAEH,MAAQE,EAAEF,QAClCH,cAAc"} \ No newline at end of file diff --git a/amd/build/string-match.min.js.map b/amd/build/string-match.min.js.map index 943986e..a413799 100644 --- a/amd/build/string-match.min.js.map +++ b/amd/build/string-match.min.js.map @@ -1 +1 @@ -{"version":3,"file":"string-match.min.js","sources":["../src/string-match.js"],"sourcesContent":["/**\n * Functions for string matching used by the other methods.\n *\n * This code originaly is from the approx-string-match project (https://github.com/robertknight/approx-string-match-js)\n * by Robert Knight wich is released under the MIT License (https://opensource.org/licenses/MIT).\n */\n\n/**\n * Represents a match returned by a call to `search`.\n * @param {string} s - Document text to search\n * @return {string}\n */\n function reverse(s) {\n return s.split(\"\").reverse().join(\"\");\n }\n\n /**\n * Given the ends of approximate matches for `pattern` in `text`, find\n * the start of the matches.\n *\n * @param {string} text\n * @param {string} pattern\n * @param {array} matches\n * @return {obj} Matches with the `start` property set.\n */\n function findMatchStarts(text, pattern, matches) {\n const patRev = reverse(pattern);\n\n return matches.map((m) => {\n // Find start of each match by reversing the pattern and matching segment\n // of text and searching for an approx match with the same number of\n // errors.\n const minStart = Math.max(0, m.end - pattern.length - m.errors);\n const textRev = reverse(text.slice(minStart, m.end));\n\n // If there are multiple possible start points, choose the one that\n // maximizes the length of the match.\n const start = findMatchEnds(textRev, patRev, m.errors).reduce((min, rm) => {\n if (m.end - rm.end < min) {\n return m.end - rm.end;\n }\n return min;\n }, m.end);\n\n return {\n start,\n end: m.end,\n errors: m.errors,\n };\n });\n }\n\n /**\n * Internal context used when calculating blocks of a column.\n */\n // interface Context {\n // /**\n // * Bit-arrays of positive vertical deltas.\n // *\n // * ie. `P[b][i]` is set if the vertical delta for the i'th row in the b'th\n // * block is positive.\n // */\n // P: Uint32Array;\n // /** Bit-arrays of negative vertical deltas. */\n // M: Uint32Array;\n // /** Bit masks with a single bit set indicating the last row in each block. */\n // lastRowMask: Uint32Array;\n // }\n\n /**\n * Return 1 if a number is non-zero or zero otherwise, without using\n * conditional operators.\n *\n * This should get inlined into `advanceBlock` below by the JIT.\n *\n * Adapted from https://stackoverflow.com/a/3912218/434243\n * @param {int} n\n * @return {bool}\n */\n function oneIfNotZero(n) {\n return ((n | -n) >> 31) & 1;\n }\n\n /**\n * Block calculation step of the algorithm.\n *\n * From Fig 8. on p. 408 of [1], additionally optimized to replace conditional\n * checks with bitwise operations as per Section 4.2.3 of [2].\n *\n * @param {obj} ctx - The pattern context object\n * @param {array} peq - The `peq` array for the current character (`ctx.peq.get(ch)`)\n * @param {int} b - The block level\n * @param {obj} hIn - Horizontal input delta ∈ {1,0,-1}\n * @return {obj} Horizontal output delta ∈ {1,0,-1}\n */\n function advanceBlock(ctx, peq, b, hIn) {\n let pV = ctx.P[b];\n let mV = ctx.M[b];\n const hInIsNegative = hIn >>> 31; // 1 if hIn < 0 or 0 otherwise.\n const eq = peq[b] | hInIsNegative;\n\n // Step 1: Compute horizontal deltas.\n const xV = eq | mV;\n const xH = (((eq & pV) + pV) ^ pV) | eq;\n\n let pH = mV | ~(xH | pV);\n let mH = pV & xH;\n\n // Step 2: Update score (value of last row of this block).\n const hOut =\n oneIfNotZero(pH & ctx.lastRowMask[b]) -\n oneIfNotZero(mH & ctx.lastRowMask[b]);\n\n // Step 3: Update vertical deltas for use when processing next char.\n pH <<= 1;\n mH <<= 1;\n\n mH |= hInIsNegative;\n pH |= oneIfNotZero(hIn) - hInIsNegative; // Set pH[0] if hIn > 0.\n\n pV = mH | ~(xV | pH);\n mV = pH & xV;\n\n ctx.P[b] = pV;\n ctx.M[b] = mV;\n\n return hOut;\n }\n\n /**\n * Find the ends and error counts for matches of `pattern` in `text`.\n *\n * Only the matches with the lowest error count are reported. Other matches\n * with error counts <= maxErrors are discarded.\n *\n * This is the block-based search algorithm from Fig. 9 on p.410 of [1].\n *\n * @param {string} text\n * @param {string} pattern\n * @param {array} maxErrors\n * @return {obj} Matches with the `start` property set.\n */\n function findMatchEnds(text, pattern, maxErrors) {\n if (pattern.length === 0) {\n return [];\n }\n\n // Clamp error count so we can rely on the `maxErrors` and `pattern.length`\n // rows being in the same block below.\n maxErrors = Math.min(maxErrors, pattern.length);\n\n const matches = [];\n\n // Word size.\n const w = 32;\n\n // Index of maximum block level.\n const bMax = Math.ceil(pattern.length / w) - 1;\n\n // Context used across block calculations.\n const ctx = {\n P: new Uint32Array(bMax + 1),\n M: new Uint32Array(bMax + 1),\n lastRowMask: new Uint32Array(bMax + 1),\n };\n ctx.lastRowMask.fill(1 << 31);\n ctx.lastRowMask[bMax] = 1 << (pattern.length - 1) % w;\n\n // Dummy \"peq\" array for chars in the text which do not occur in the pattern.\n const emptyPeq = new Uint32Array(bMax + 1);\n\n // Map of UTF-16 character code to bit vector indicating positions in the\n // pattern that equal that character.\n const peq = new Map();\n\n // Version of `peq` that only stores mappings for small characters. This\n // allows faster lookups when iterating through the text because a simple\n // array lookup can be done instead of a hash table lookup.\n const asciiPeq = [];\n for (let i = 0; i < 256; i++) {\n asciiPeq.push(emptyPeq);\n }\n\n // Calculate `ctx.peq` - a map of character values to bitmasks indicating\n // positions of that character within the pattern, where each bit represents\n // a position in the pattern.\n for (let c = 0; c < pattern.length; c += 1) {\n const val = pattern.charCodeAt(c);\n if (peq.has(val)) {\n // Duplicate char in pattern.\n continue;\n }\n\n const charPeq = new Uint32Array(bMax + 1);\n peq.set(val, charPeq);\n if (val < asciiPeq.length) {\n asciiPeq[val] = charPeq;\n }\n\n for (let b = 0; b <= bMax; b += 1) {\n charPeq[b] = 0;\n\n // Set all the bits where the pattern matches the current char (ch).\n // For indexes beyond the end of the pattern, always set the bit as if the\n // pattern contained a wildcard char in that position.\n for (let r = 0; r < w; r += 1) {\n const idx = b * w + r;\n if (idx >= pattern.length) {\n continue;\n }\n\n const match = pattern.charCodeAt(idx) === val;\n if (match) {\n charPeq[b] |= 1 << r;\n }\n }\n }\n }\n\n // Index of last-active block level in the column.\n let y = Math.max(0, Math.ceil(maxErrors / w) - 1);\n\n // Initialize maximum error count at bottom of each block.\n const score = new Uint32Array(bMax + 1);\n for (let b = 0; b <= y; b += 1) {\n score[b] = (b + 1) * w;\n }\n score[bMax] = pattern.length;\n\n // Initialize vertical deltas for each block.\n for (let b = 0; b <= y; b += 1) {\n ctx.P[b] = ~0;\n ctx.M[b] = 0;\n }\n\n // Process each char of the text, computing the error count for `w` chars of\n // the pattern at a time.\n for (let j = 0; j < text.length; j += 1) {\n // Lookup the bitmask representing the positions of the current char from\n // the text within the pattern.\n const charCode = text.charCodeAt(j);\n let charPeq;\n\n if (charCode < asciiPeq.length) {\n // Fast array lookup.\n charPeq = asciiPeq[charCode];\n } else {\n // Slower hash table lookup.\n charPeq = peq.get(charCode);\n if (typeof charPeq === \"undefined\") {\n charPeq = emptyPeq;\n }\n }\n\n // Calculate error count for blocks that we definitely have to process for\n // this column.\n let carry = 0;\n for (let b = 0; b <= y; b += 1) {\n carry = advanceBlock(ctx, charPeq, b, carry);\n score[b] += carry;\n }\n\n // Check if we also need to compute an additional block, or if we can reduce\n // the number of blocks processed for the next column.\n if (\n score[y] - carry <= maxErrors &&\n y < bMax &&\n (charPeq[y + 1] & 1 || carry < 0)\n ) {\n // Error count for bottom block is under threshold, increase the number of\n // blocks processed for this column & next by 1.\n y += 1;\n\n ctx.P[y] = ~0;\n ctx.M[y] = 0;\n\n let maxBlockScore;\n if (y === bMax) {\n const remainder = pattern.length % w;\n maxBlockScore = remainder === 0 ? w : remainder;\n } else {\n maxBlockScore = w;\n }\n\n score[y] =\n score[y - 1] +\n maxBlockScore -\n carry +\n advanceBlock(ctx, charPeq, y, carry);\n } else {\n // Error count for bottom block exceeds threshold, reduce the number of\n // blocks processed for the next column.\n while (y > 0 && score[y] >= maxErrors + w) {\n y -= 1;\n }\n }\n\n // If error count is under threshold, report a match.\n if (y === bMax && score[y] <= maxErrors) {\n if (score[y] < maxErrors) {\n // Discard any earlier, worse matches.\n matches.splice(0, matches.length);\n }\n\n matches.push({\n start: -1,\n end: j + 1,\n errors: score[y],\n });\n\n // Because `search` only reports the matches with the lowest error count,\n // we can \"ratchet down\" the max error threshold whenever a match is\n // encountered and thereby save a small amount of work for the remainder\n // of the text.\n maxErrors = score[y];\n }\n }\n\n return matches;\n }\n\n /**\n * Search for matches for `pattern` in `text` allowing up to `maxErrors` errors.\n *\n * Returns the start, and end positions and error counts for each lowest-cost\n * match. Only the \"best\" matches are returned.\n * @param {string} text\n * @param {string} pattern\n * @param {array} maxErrors\n * @return {obj} Matches with the `start` property set.\n */\n export default function search(\n text,\n pattern,\n maxErrors\n ) {\n const matches = findMatchEnds(text, pattern, maxErrors);\n return findMatchStarts(text, pattern, matches);\n }"],"names":["reverse","s","split","join","oneIfNotZero","n","advanceBlock","ctx","peq","b","hIn","pV","P","mV","M","hInIsNegative","eq","xV","xH","pH","mH","hOut","lastRowMask","findMatchEnds","text","pattern","maxErrors","length","Math","min","matches","w","bMax","ceil","Uint32Array","fill","emptyPeq","Map","asciiPeq","i","push","c","val","charCodeAt","has","charPeq","set","r","idx","y","max","score","j","charCode","get","carry","maxBlockScore","remainder","splice","start","end","errors","patRev","map","m","minStart","slice","reduce","rm","findMatchStarts","_exports","default"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"string-match.min.js","sources":["../src/string-match.js"],"sourcesContent":["/**\n * Functions for string matching used by the other methods.\n *\n * This code originaly is from the approx-string-match project (https://github.com/robertknight/approx-string-match-js)\n * by Robert Knight wich is released under the MIT License (https://opensource.org/licenses/MIT).\n */\n\n/**\n * Represents a match returned by a call to `search`.\n * @param {string} s - Document text to search\n * @return {string}\n */\n function reverse(s) {\n return s.split(\"\").reverse().join(\"\");\n }\n\n /**\n * Given the ends of approximate matches for `pattern` in `text`, find\n * the start of the matches.\n *\n * @param {string} text\n * @param {string} pattern\n * @param {array} matches\n * @return {obj} Matches with the `start` property set.\n */\n function findMatchStarts(text, pattern, matches) {\n const patRev = reverse(pattern);\n\n return matches.map((m) => {\n // Find start of each match by reversing the pattern and matching segment\n // of text and searching for an approx match with the same number of\n // errors.\n const minStart = Math.max(0, m.end - pattern.length - m.errors);\n const textRev = reverse(text.slice(minStart, m.end));\n\n // If there are multiple possible start points, choose the one that\n // maximizes the length of the match.\n const start = findMatchEnds(textRev, patRev, m.errors).reduce((min, rm) => {\n if (m.end - rm.end < min) {\n return m.end - rm.end;\n }\n return min;\n }, m.end);\n\n return {\n start,\n end: m.end,\n errors: m.errors,\n };\n });\n }\n\n /**\n * Internal context used when calculating blocks of a column.\n */\n // interface Context {\n // /**\n // * Bit-arrays of positive vertical deltas.\n // *\n // * ie. `P[b][i]` is set if the vertical delta for the i'th row in the b'th\n // * block is positive.\n // */\n // P: Uint32Array;\n // /** Bit-arrays of negative vertical deltas. */\n // M: Uint32Array;\n // /** Bit masks with a single bit set indicating the last row in each block. */\n // lastRowMask: Uint32Array;\n // }\n\n /**\n * Return 1 if a number is non-zero or zero otherwise, without using\n * conditional operators.\n *\n * This should get inlined into `advanceBlock` below by the JIT.\n *\n * Adapted from https://stackoverflow.com/a/3912218/434243\n * @param {int} n\n * @return {bool}\n */\n function oneIfNotZero(n) {\n return ((n | -n) >> 31) & 1;\n }\n\n /**\n * Block calculation step of the algorithm.\n *\n * From Fig 8. on p. 408 of [1], additionally optimized to replace conditional\n * checks with bitwise operations as per Section 4.2.3 of [2].\n *\n * @param {obj} ctx - The pattern context object\n * @param {array} peq - The `peq` array for the current character (`ctx.peq.get(ch)`)\n * @param {int} b - The block level\n * @param {obj} hIn - Horizontal input delta ∈ {1,0,-1}\n * @return {obj} Horizontal output delta ∈ {1,0,-1}\n */\n function advanceBlock(ctx, peq, b, hIn) {\n let pV = ctx.P[b];\n let mV = ctx.M[b];\n const hInIsNegative = hIn >>> 31; // 1 if hIn < 0 or 0 otherwise.\n const eq = peq[b] | hInIsNegative;\n\n // Step 1: Compute horizontal deltas.\n const xV = eq | mV;\n const xH = (((eq & pV) + pV) ^ pV) | eq;\n\n let pH = mV | ~(xH | pV);\n let mH = pV & xH;\n\n // Step 2: Update score (value of last row of this block).\n const hOut =\n oneIfNotZero(pH & ctx.lastRowMask[b]) -\n oneIfNotZero(mH & ctx.lastRowMask[b]);\n\n // Step 3: Update vertical deltas for use when processing next char.\n pH <<= 1;\n mH <<= 1;\n\n mH |= hInIsNegative;\n pH |= oneIfNotZero(hIn) - hInIsNegative; // Set pH[0] if hIn > 0.\n\n pV = mH | ~(xV | pH);\n mV = pH & xV;\n\n ctx.P[b] = pV;\n ctx.M[b] = mV;\n\n return hOut;\n }\n\n /**\n * Find the ends and error counts for matches of `pattern` in `text`.\n *\n * Only the matches with the lowest error count are reported. Other matches\n * with error counts <= maxErrors are discarded.\n *\n * This is the block-based search algorithm from Fig. 9 on p.410 of [1].\n *\n * @param {string} text\n * @param {string} pattern\n * @param {array} maxErrors\n * @return {obj} Matches with the `start` property set.\n */\n function findMatchEnds(text, pattern, maxErrors) {\n if (pattern.length === 0) {\n return [];\n }\n\n // Clamp error count so we can rely on the `maxErrors` and `pattern.length`\n // rows being in the same block below.\n maxErrors = Math.min(maxErrors, pattern.length);\n\n const matches = [];\n\n // Word size.\n const w = 32;\n\n // Index of maximum block level.\n const bMax = Math.ceil(pattern.length / w) - 1;\n\n // Context used across block calculations.\n const ctx = {\n P: new Uint32Array(bMax + 1),\n M: new Uint32Array(bMax + 1),\n lastRowMask: new Uint32Array(bMax + 1),\n };\n ctx.lastRowMask.fill(1 << 31);\n ctx.lastRowMask[bMax] = 1 << (pattern.length - 1) % w;\n\n // Dummy \"peq\" array for chars in the text which do not occur in the pattern.\n const emptyPeq = new Uint32Array(bMax + 1);\n\n // Map of UTF-16 character code to bit vector indicating positions in the\n // pattern that equal that character.\n const peq = new Map();\n\n // Version of `peq` that only stores mappings for small characters. This\n // allows faster lookups when iterating through the text because a simple\n // array lookup can be done instead of a hash table lookup.\n const asciiPeq = [];\n for (let i = 0; i < 256; i++) {\n asciiPeq.push(emptyPeq);\n }\n\n // Calculate `ctx.peq` - a map of character values to bitmasks indicating\n // positions of that character within the pattern, where each bit represents\n // a position in the pattern.\n for (let c = 0; c < pattern.length; c += 1) {\n const val = pattern.charCodeAt(c);\n if (peq.has(val)) {\n // Duplicate char in pattern.\n continue;\n }\n\n const charPeq = new Uint32Array(bMax + 1);\n peq.set(val, charPeq);\n if (val < asciiPeq.length) {\n asciiPeq[val] = charPeq;\n }\n\n for (let b = 0; b <= bMax; b += 1) {\n charPeq[b] = 0;\n\n // Set all the bits where the pattern matches the current char (ch).\n // For indexes beyond the end of the pattern, always set the bit as if the\n // pattern contained a wildcard char in that position.\n for (let r = 0; r < w; r += 1) {\n const idx = b * w + r;\n if (idx >= pattern.length) {\n continue;\n }\n\n const match = pattern.charCodeAt(idx) === val;\n if (match) {\n charPeq[b] |= 1 << r;\n }\n }\n }\n }\n\n // Index of last-active block level in the column.\n let y = Math.max(0, Math.ceil(maxErrors / w) - 1);\n\n // Initialize maximum error count at bottom of each block.\n const score = new Uint32Array(bMax + 1);\n for (let b = 0; b <= y; b += 1) {\n score[b] = (b + 1) * w;\n }\n score[bMax] = pattern.length;\n\n // Initialize vertical deltas for each block.\n for (let b = 0; b <= y; b += 1) {\n ctx.P[b] = ~0;\n ctx.M[b] = 0;\n }\n\n // Process each char of the text, computing the error count for `w` chars of\n // the pattern at a time.\n for (let j = 0; j < text.length; j += 1) {\n // Lookup the bitmask representing the positions of the current char from\n // the text within the pattern.\n const charCode = text.charCodeAt(j);\n let charPeq;\n\n if (charCode < asciiPeq.length) {\n // Fast array lookup.\n charPeq = asciiPeq[charCode];\n } else {\n // Slower hash table lookup.\n charPeq = peq.get(charCode);\n if (typeof charPeq === \"undefined\") {\n charPeq = emptyPeq;\n }\n }\n\n // Calculate error count for blocks that we definitely have to process for\n // this column.\n let carry = 0;\n for (let b = 0; b <= y; b += 1) {\n carry = advanceBlock(ctx, charPeq, b, carry);\n score[b] += carry;\n }\n\n // Check if we also need to compute an additional block, or if we can reduce\n // the number of blocks processed for the next column.\n if (\n score[y] - carry <= maxErrors &&\n y < bMax &&\n (charPeq[y + 1] & 1 || carry < 0)\n ) {\n // Error count for bottom block is under threshold, increase the number of\n // blocks processed for this column & next by 1.\n y += 1;\n\n ctx.P[y] = ~0;\n ctx.M[y] = 0;\n\n let maxBlockScore;\n if (y === bMax) {\n const remainder = pattern.length % w;\n maxBlockScore = remainder === 0 ? w : remainder;\n } else {\n maxBlockScore = w;\n }\n\n score[y] =\n score[y - 1] +\n maxBlockScore -\n carry +\n advanceBlock(ctx, charPeq, y, carry);\n } else {\n // Error count for bottom block exceeds threshold, reduce the number of\n // blocks processed for the next column.\n while (y > 0 && score[y] >= maxErrors + w) {\n y -= 1;\n }\n }\n\n // If error count is under threshold, report a match.\n if (y === bMax && score[y] <= maxErrors) {\n if (score[y] < maxErrors) {\n // Discard any earlier, worse matches.\n matches.splice(0, matches.length);\n }\n\n matches.push({\n start: -1,\n end: j + 1,\n errors: score[y],\n });\n\n // Because `search` only reports the matches with the lowest error count,\n // we can \"ratchet down\" the max error threshold whenever a match is\n // encountered and thereby save a small amount of work for the remainder\n // of the text.\n maxErrors = score[y];\n }\n }\n\n return matches;\n }\n\n /**\n * Search for matches for `pattern` in `text` allowing up to `maxErrors` errors.\n *\n * Returns the start, and end positions and error counts for each lowest-cost\n * match. Only the \"best\" matches are returned.\n * @param {string} text\n * @param {string} pattern\n * @param {array} maxErrors\n * @return {obj} Matches with the `start` property set.\n */\n export default function search(\n text,\n pattern,\n maxErrors\n ) {\n const matches = findMatchEnds(text, pattern, maxErrors);\n return findMatchStarts(text, pattern, matches);\n }"],"names":["reverse","s","split","join","oneIfNotZero","n","advanceBlock","ctx","peq","b","hIn","pV","P","mV","M","hInIsNegative","eq","xV","xH","pH","mH","hOut","lastRowMask","findMatchEnds","text","pattern","maxErrors","length","Math","min","matches","w","bMax","ceil","Uint32Array","fill","emptyPeq","Map","asciiPeq","i","push","c","val","charCodeAt","has","charPeq","set","r","idx","y","max","score","j","charCode","get","carry","maxBlockScore","remainder","splice","start","end","errors","patRev","map","m","minStart","slice","reduce","rm","findMatchStarts"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/text-range.min.js.map b/amd/build/text-range.min.js.map index 20b72c0..44708c6 100644 --- a/amd/build/text-range.min.js.map +++ b/amd/build/text-range.min.js.map @@ -1 +1 @@ -{"version":3,"file":"text-range.min.js","sources":["../src/text-range.js"],"sourcesContent":["/**\n * Functions for handling text-ranges used by the other methods.\n *\n * This code originaly is from the Hypothesis project (https://github.com/hypothesis/client)\n * which is released under the 2-Clause BSD License (https://opensource.org/licenses/BSD-2-Clause),\n * sometimes referred to as the \"Simplified BSD License\".\n */\n\n/**\n * Return the combined length of text nodes contained in `node`.\n *\n * @param {Node} node\n * @return {string}\n */\nfunction nodeTextLength(node) {\n switch (node.nodeType) {\n case Node.ELEMENT_NODE:\n case Node.TEXT_NODE:\n // Nb. `textContent` excludes text in comments and processing instructions\n // when called on a parent element, so we don't need to subtract that here.\n\n return /** @type {string} */ (node.textContent).length;\n default:\n return 0;\n }\n}\n\n/**\n * Return the total length of the text of all previous siblings of `node`.\n *\n * @param {Node} node\n * @return {int}\n */\nfunction previousSiblingsTextLength(node) {\n let sibling = node.previousSibling;\n let length = 0;\n\n while (sibling) {\n length += nodeTextLength(sibling);\n sibling = sibling.previousSibling;\n }\n\n return length;\n}\n\n/**\n * Resolve one or more character offsets within an element to (text node, position)\n * pairs.\n *\n * @param {Element} element\n * @param {number[]} offsets - Offsets, which must be sorted in ascending order\n * @return {{ node: Text, offset: number }[]}\n */\nfunction resolveOffsets(element, ...offsets) {\n\n let nextOffset = offsets.shift();\n const nodeIter = /** @type {Document} */ (\n element.ownerDocument\n ).createNodeIterator(element, NodeFilter.SHOW_TEXT);\n const results = [];\n\n let currentNode = nodeIter.nextNode();\n let textNode;\n let length = 0;\n\n // Find the text node containing the `nextOffset`th character from the start\n // of `element`.\n while (nextOffset !== undefined && currentNode) {\n textNode = /** @type {Text} */ (currentNode);\n\n if (length + textNode.data.length > nextOffset) {\n results.push({node: textNode, offset: nextOffset - length});\n nextOffset = offsets.shift();\n } else {\n currentNode = nodeIter.nextNode();\n length += textNode.data.length;\n }\n }\n\n // Boundary case.\n while (nextOffset !== undefined && length === nextOffset) {\n results.push({node: textNode, offset: textNode.data.length});\n nextOffset = offsets.shift();\n }\n\n if (nextOffset !== undefined) {\n throw new RangeError('Offset exceeds text length');\n }\n\n return results;\n}\n\nexport let RESOLVE_FORWARDS = 1;\nexport let RESOLVE_BACKWARDS = 2;\n\n/**\n * Represents an offset within the text content of an element.\n *\n * This position can be resolved to a specific descendant node in the current\n * DOM subtree of the element using the `resolve` method.\n */\nexport class TextPosition {\n /**\n * Construct a `TextPosition` that refers to the text position `offset` within\n * the text content of `element`.\n *\n * @param {Element} element\n * @param {number} offset\n */\n constructor(element, offset) {\n if (offset < 0) {\n throw new Error('Offset is invalid');\n }\n\n /** Element that `offset` is relative to. */\n this.element = element;\n\n /** Character offset from the start of the element's `textContent`. */\n this.offset = offset;\n }\n\n /**\n * Return a copy of this position with offset relative to a given ancestor\n * element.\n *\n * @param {Element} parent - Ancestor of `this.element`\n * @return {TextPosition}\n */\n relativeTo(parent) {\n if (!parent.contains(this.element)) {\n throw new Error('Parent is not an ancestor of current element');\n }\n\n let el = this.element;\n let offset = this.offset;\n while (el !== parent) {\n offset += previousSiblingsTextLength(el);\n el = /** @type {Element} */ (el.parentElement);\n }\n\n return new TextPosition(el, offset);\n }\n\n /**\n * Resolve the position to a specific text node and offset within that node.\n *\n * Throws if `this.offset` exceeds the length of the element's text. In the\n * case where the element has no text and `this.offset` is 0, the `direction`\n * option determines what happens.\n *\n * Offsets at the boundary between two nodes are resolved to the start of the\n * node that begins at the boundary.\n *\n * @param {Object} [options]\n * @param {RESOLVE_FORWARDS|RESOLVE_BACKWARDS} [options.direction] -\n * Specifies in which direction to search for the nearest text node if\n * `this.offset` is `0` and `this.element` has no text. If not specified\n * an error is thrown.\n * @return {{ node: Text, offset: number }}\n * @throws {RangeError}\n */\n resolve(options = {}) {\n try {\n return resolveOffsets(this.element, this.offset)[0];\n } catch (err) {\n if (this.offset === 0 && options.direction !== undefined) {\n const tw = document.createTreeWalker(\n this.element.getRootNode(),\n NodeFilter.SHOW_TEXT\n );\n tw.currentNode = this.element;\n const forwards = options.direction === RESOLVE_FORWARDS;\n const text = /** @type {Text|null} */ (\n forwards ? tw.nextNode() : tw.previousNode()\n );\n if (!text) {\n throw err;\n }\n return {node: text, offset: forwards ? 0 : text.data.length};\n } else {\n throw err;\n }\n }\n }\n\n /**\n * Construct a `TextPosition` that refers to the `offset`th character within\n * `node`.\n *\n * @param {Node} node\n * @param {number} offset\n * @return {TextPosition}\n */\n static fromCharOffset(node, offset) {\n switch (node.nodeType) {\n case Node.TEXT_NODE:\n return TextPosition.fromPoint(node, offset);\n case Node.ELEMENT_NODE:\n return new TextPosition(/** @type {Element} */ (node), offset);\n default:\n throw new Error('Node is not an element or text node');\n }\n }\n\n /**\n * Construct a `TextPosition` representing the range start or end point (node, offset).\n *\n * @param {Node} node - Text or Element node\n * @param {number} offset - Offset within the node.\n * @return {TextPosition}\n */\n static fromPoint(node, offset) {\n\n switch (node.nodeType) {\n case Node.TEXT_NODE: {\n if (offset < 0 || offset > /** @type {Text} */ (node).data.length) {\n throw new Error('Text node offset is out of range');\n }\n\n if (!node.parentElement) {\n throw new Error('Text node has no parent');\n }\n\n // Get the offset from the start of the parent element.\n const textOffset = previousSiblingsTextLength(node) + offset;\n\n return new TextPosition(node.parentElement, textOffset);\n }\n case Node.ELEMENT_NODE: {\n if (offset < 0 || offset > node.childNodes.length) {\n throw new Error('Child node offset is out of range');\n }\n\n // Get the text length before the `offset`th child of element.\n let textOffset = 0;\n for (let i = 0; i < offset; i++) {\n textOffset += nodeTextLength(node.childNodes[i]);\n }\n\n return new TextPosition(/** @type {Element} */ (node), textOffset);\n }\n default:\n throw new Error('Point is not in an element or text node');\n }\n }\n}\n\n/**\n * Represents a region of a document as a (start, end) pair of `TextPosition` points.\n *\n * Representing a range in this way allows for changes in the DOM content of the\n * range which don't affect its text content, without affecting the text content\n * of the range itself.\n */\nexport class TextRange {\n /**\n * Construct an immutable `TextRange` from a `start` and `end` point.\n *\n * @param {TextPosition} start\n * @param {TextPosition} end\n */\n constructor(start, end) {\n this.start = start;\n this.end = end;\n }\n\n /**\n * Return a copy of this range with start and end positions relative to a\n * given ancestor. See `TextPosition.relativeTo`.\n *\n * @param {Element} element\n * @return {Range}\n */\n relativeTo(element) {\n return new TextRange(\n this.start.relativeTo(element),\n this.end.relativeTo(element)\n );\n }\n\n /**\n * Resolve the `TextRange` to a DOM range.\n *\n * The resulting DOM Range will always start and end in a `Text` node.\n * Hence `TextRange.fromRange(range).toRange()` can be used to \"shrink\" a\n * range to the text it contains.\n *\n * May throw if the `start` or `end` positions cannot be resolved to a range.\n *\n * @return {Range}\n */\n toRange() {\n let start;\n let end;\n\n if (\n this.start.element === this.end.element &&\n this.start.offset <= this.end.offset\n ) {\n // Fast path for start and end points in same element.\n [start, end] = resolveOffsets(\n this.start.element,\n this.start.offset,\n this.end.offset\n );\n } else {\n start = this.start.resolve({direction: RESOLVE_FORWARDS});\n end = this.end.resolve({direction: RESOLVE_BACKWARDS});\n }\n\n const range = new Range();\n range.setStart(start.node, start.offset);\n range.setEnd(end.node, end.offset);\n return range;\n }\n\n /**\n * Convert an existing DOM `Range` to a `TextRange`\n *\n * @param {Range} range\n * @return {TextRange}\n */\n static fromRange(range) {\n const start = TextPosition.fromPoint(\n range.startContainer,\n range.startOffset\n );\n const end = TextPosition.fromPoint(range.endContainer, range.endOffset);\n return new TextRange(start, end);\n }\n\n /**\n * Return a `TextRange` from the `start`th to `end`th characters in `root`.\n *\n * @param {Element} root\n * @param {number} start\n * @param {number} end\n * @return {Range}\n */\n static fromOffsets(root, start, end) {\n return new TextRange(\n new TextPosition(root, start),\n new TextPosition(root, end)\n );\n }\n}\n"],"names":["nodeTextLength","node","nodeType","Node","ELEMENT_NODE","TEXT_NODE","textContent","length","previousSiblingsTextLength","sibling","previousSibling","resolveOffsets","element","_len","arguments","offsets","Array","_key","nextOffset","shift","nodeIter","ownerDocument","createNodeIterator","NodeFilter","SHOW_TEXT","results","textNode","currentNode","nextNode","undefined","data","push","offset","RangeError","_exports","RESOLVE_FORWARDS","RESOLVE_BACKWARDS","TextPosition","constructor","Error","this","relativeTo","parent","contains","el","parentElement","resolve","options","err","direction","tw","document","createTreeWalker","getRootNode","forwards","text","previousNode","static","fromPoint","textOffset","childNodes","i","TextRange","start","end","toRange","range","Range","setStart","setEnd","startContainer","startOffset","endContainer","endOffset","root"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"text-range.min.js","sources":["../src/text-range.js"],"sourcesContent":["/**\n * Functions for handling text-ranges used by the other methods.\n *\n * This code originaly is from the Hypothesis project (https://github.com/hypothesis/client)\n * which is released under the 2-Clause BSD License (https://opensource.org/licenses/BSD-2-Clause),\n * sometimes referred to as the \"Simplified BSD License\".\n */\n\n/**\n * Return the combined length of text nodes contained in `node`.\n *\n * @param {Node} node\n * @return {string}\n */\nfunction nodeTextLength(node) {\n switch (node.nodeType) {\n case Node.ELEMENT_NODE:\n case Node.TEXT_NODE:\n // Nb. `textContent` excludes text in comments and processing instructions\n // when called on a parent element, so we don't need to subtract that here.\n\n return /** @type {string} */ (node.textContent).length;\n default:\n return 0;\n }\n}\n\n/**\n * Return the total length of the text of all previous siblings of `node`.\n *\n * @param {Node} node\n * @return {int}\n */\nfunction previousSiblingsTextLength(node) {\n let sibling = node.previousSibling;\n let length = 0;\n\n while (sibling) {\n length += nodeTextLength(sibling);\n sibling = sibling.previousSibling;\n }\n\n return length;\n}\n\n/**\n * Resolve one or more character offsets within an element to (text node, position)\n * pairs.\n *\n * @param {Element} element\n * @param {number[]} offsets - Offsets, which must be sorted in ascending order\n * @return {{ node: Text, offset: number }[]}\n */\nfunction resolveOffsets(element, ...offsets) {\n\n let nextOffset = offsets.shift();\n const nodeIter = /** @type {Document} */ (\n element.ownerDocument\n ).createNodeIterator(element, NodeFilter.SHOW_TEXT);\n const results = [];\n\n let currentNode = nodeIter.nextNode();\n let textNode;\n let length = 0;\n\n // Find the text node containing the `nextOffset`th character from the start\n // of `element`.\n while (nextOffset !== undefined && currentNode) {\n textNode = /** @type {Text} */ (currentNode);\n\n if (length + textNode.data.length > nextOffset) {\n results.push({node: textNode, offset: nextOffset - length});\n nextOffset = offsets.shift();\n } else {\n currentNode = nodeIter.nextNode();\n length += textNode.data.length;\n }\n }\n\n // Boundary case.\n while (nextOffset !== undefined && length === nextOffset) {\n results.push({node: textNode, offset: textNode.data.length});\n nextOffset = offsets.shift();\n }\n\n if (nextOffset !== undefined) {\n throw new RangeError('Offset exceeds text length');\n }\n\n return results;\n}\n\nexport let RESOLVE_FORWARDS = 1;\nexport let RESOLVE_BACKWARDS = 2;\n\n/**\n * Represents an offset within the text content of an element.\n *\n * This position can be resolved to a specific descendant node in the current\n * DOM subtree of the element using the `resolve` method.\n */\nexport class TextPosition {\n /**\n * Construct a `TextPosition` that refers to the text position `offset` within\n * the text content of `element`.\n *\n * @param {Element} element\n * @param {number} offset\n */\n constructor(element, offset) {\n if (offset < 0) {\n throw new Error('Offset is invalid');\n }\n\n /** Element that `offset` is relative to. */\n this.element = element;\n\n /** Character offset from the start of the element's `textContent`. */\n this.offset = offset;\n }\n\n /**\n * Return a copy of this position with offset relative to a given ancestor\n * element.\n *\n * @param {Element} parent - Ancestor of `this.element`\n * @return {TextPosition}\n */\n relativeTo(parent) {\n if (!parent.contains(this.element)) {\n throw new Error('Parent is not an ancestor of current element');\n }\n\n let el = this.element;\n let offset = this.offset;\n while (el !== parent) {\n offset += previousSiblingsTextLength(el);\n el = /** @type {Element} */ (el.parentElement);\n }\n\n return new TextPosition(el, offset);\n }\n\n /**\n * Resolve the position to a specific text node and offset within that node.\n *\n * Throws if `this.offset` exceeds the length of the element's text. In the\n * case where the element has no text and `this.offset` is 0, the `direction`\n * option determines what happens.\n *\n * Offsets at the boundary between two nodes are resolved to the start of the\n * node that begins at the boundary.\n *\n * @param {Object} [options]\n * @param {RESOLVE_FORWARDS|RESOLVE_BACKWARDS} [options.direction] -\n * Specifies in which direction to search for the nearest text node if\n * `this.offset` is `0` and `this.element` has no text. If not specified\n * an error is thrown.\n * @return {{ node: Text, offset: number }}\n * @throws {RangeError}\n */\n resolve(options = {}) {\n try {\n return resolveOffsets(this.element, this.offset)[0];\n } catch (err) {\n if (this.offset === 0 && options.direction !== undefined) {\n const tw = document.createTreeWalker(\n this.element.getRootNode(),\n NodeFilter.SHOW_TEXT\n );\n tw.currentNode = this.element;\n const forwards = options.direction === RESOLVE_FORWARDS;\n const text = /** @type {Text|null} */ (\n forwards ? tw.nextNode() : tw.previousNode()\n );\n if (!text) {\n throw err;\n }\n return {node: text, offset: forwards ? 0 : text.data.length};\n } else {\n throw err;\n }\n }\n }\n\n /**\n * Construct a `TextPosition` that refers to the `offset`th character within\n * `node`.\n *\n * @param {Node} node\n * @param {number} offset\n * @return {TextPosition}\n */\n static fromCharOffset(node, offset) {\n switch (node.nodeType) {\n case Node.TEXT_NODE:\n return TextPosition.fromPoint(node, offset);\n case Node.ELEMENT_NODE:\n return new TextPosition(/** @type {Element} */ (node), offset);\n default:\n throw new Error('Node is not an element or text node');\n }\n }\n\n /**\n * Construct a `TextPosition` representing the range start or end point (node, offset).\n *\n * @param {Node} node - Text or Element node\n * @param {number} offset - Offset within the node.\n * @return {TextPosition}\n */\n static fromPoint(node, offset) {\n\n switch (node.nodeType) {\n case Node.TEXT_NODE: {\n if (offset < 0 || offset > /** @type {Text} */ (node).data.length) {\n throw new Error('Text node offset is out of range');\n }\n\n if (!node.parentElement) {\n throw new Error('Text node has no parent');\n }\n\n // Get the offset from the start of the parent element.\n const textOffset = previousSiblingsTextLength(node) + offset;\n\n return new TextPosition(node.parentElement, textOffset);\n }\n case Node.ELEMENT_NODE: {\n if (offset < 0 || offset > node.childNodes.length) {\n throw new Error('Child node offset is out of range');\n }\n\n // Get the text length before the `offset`th child of element.\n let textOffset = 0;\n for (let i = 0; i < offset; i++) {\n textOffset += nodeTextLength(node.childNodes[i]);\n }\n\n return new TextPosition(/** @type {Element} */ (node), textOffset);\n }\n default:\n throw new Error('Point is not in an element or text node');\n }\n }\n}\n\n/**\n * Represents a region of a document as a (start, end) pair of `TextPosition` points.\n *\n * Representing a range in this way allows for changes in the DOM content of the\n * range which don't affect its text content, without affecting the text content\n * of the range itself.\n */\nexport class TextRange {\n /**\n * Construct an immutable `TextRange` from a `start` and `end` point.\n *\n * @param {TextPosition} start\n * @param {TextPosition} end\n */\n constructor(start, end) {\n this.start = start;\n this.end = end;\n }\n\n /**\n * Return a copy of this range with start and end positions relative to a\n * given ancestor. See `TextPosition.relativeTo`.\n *\n * @param {Element} element\n * @return {Range}\n */\n relativeTo(element) {\n return new TextRange(\n this.start.relativeTo(element),\n this.end.relativeTo(element)\n );\n }\n\n /**\n * Resolve the `TextRange` to a DOM range.\n *\n * The resulting DOM Range will always start and end in a `Text` node.\n * Hence `TextRange.fromRange(range).toRange()` can be used to \"shrink\" a\n * range to the text it contains.\n *\n * May throw if the `start` or `end` positions cannot be resolved to a range.\n *\n * @return {Range}\n */\n toRange() {\n let start;\n let end;\n\n if (\n this.start.element === this.end.element &&\n this.start.offset <= this.end.offset\n ) {\n // Fast path for start and end points in same element.\n [start, end] = resolveOffsets(\n this.start.element,\n this.start.offset,\n this.end.offset\n );\n } else {\n start = this.start.resolve({direction: RESOLVE_FORWARDS});\n end = this.end.resolve({direction: RESOLVE_BACKWARDS});\n }\n\n const range = new Range();\n range.setStart(start.node, start.offset);\n range.setEnd(end.node, end.offset);\n return range;\n }\n\n /**\n * Convert an existing DOM `Range` to a `TextRange`\n *\n * @param {Range} range\n * @return {TextRange}\n */\n static fromRange(range) {\n const start = TextPosition.fromPoint(\n range.startContainer,\n range.startOffset\n );\n const end = TextPosition.fromPoint(range.endContainer, range.endOffset);\n return new TextRange(start, end);\n }\n\n /**\n * Return a `TextRange` from the `start`th to `end`th characters in `root`.\n *\n * @param {Element} root\n * @param {number} start\n * @param {number} end\n * @return {Range}\n */\n static fromOffsets(root, start, end) {\n return new TextRange(\n new TextPosition(root, start),\n new TextPosition(root, end)\n );\n }\n}\n"],"names":["nodeTextLength","node","nodeType","Node","ELEMENT_NODE","TEXT_NODE","textContent","length","previousSiblingsTextLength","sibling","previousSibling","resolveOffsets","element","offsets","nextOffset","shift","nodeIter","ownerDocument","createNodeIterator","NodeFilter","SHOW_TEXT","results","textNode","currentNode","nextNode","undefined","data","push","offset","RangeError","TextPosition","constructor","Error","relativeTo","parent","contains","this","el","parentElement","resolve","options","err","direction","tw","document","createTreeWalker","getRootNode","forwards","text","previousNode","fromPoint","textOffset","childNodes","i","TextRange","start","end","toRange","range","Range","setStart","setEnd","startContainer","startOffset","endContainer","endOffset","root"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/types.min.js.map b/amd/build/types.min.js.map index 51b6a10..dab80b0 100644 --- a/amd/build/types.min.js.map +++ b/amd/build/types.min.js.map @@ -1 +1 @@ -{"version":3,"file":"types.min.js","sources":["../src/types.js"],"sourcesContent":["/**\n * This module exports a set of classes for converting between DOM `Range`\n * objects and different types of selectors. It is mostly a thin wrapper around a\n * set of anchoring libraries. It serves two main purposes:\n *\n * 1. Providing a consistent interface across different types of anchors.\n * 2. Insulating the rest of the code from API changes in the underlying anchoring\n * libraries.\n *\n * This code originaly is from the Hypothesis project (https://github.com/hypothesis/client)\n * which is released under the 2-Clause BSD License (https://opensource.org/licenses/BSD-2-Clause),\n * sometimes referred to as the \"Simplified BSD License\".\n */\n\nimport {matchQuote} from './match-quote';\nimport {TextRange, TextPosition} from './text-range';\nimport {nodeFromXPath, xpathFromNode} from './xpath';\n\n/**\n * @typedef {import('../../types/api').RangeSelector} RangeSelector\n * @typedef {import('../../types/api').TextPositionSelector} TextPositionSelector\n * @typedef {import('../../types/api').TextQuoteSelector} TextQuoteSelector\n */\n\n/**\n * Converts between `RangeSelector` selectors and `Range` objects.\n */\nexport class RangeAnchor {\n /**\n * @param {Node} root - A root element from which to anchor.\n * @param {Range} range - A range describing the anchor.\n */\n constructor(root, range) {\n this.root = root;\n this.range = range;\n }\n\n /**\n * @param {Node} root - A root element from which to anchor.\n * @param {Range} range - A range describing the anchor.\n * @return {RangeAnchor}\n */\n static fromRange(root, range) {\n return new RangeAnchor(root, range);\n }\n\n /**\n * Create an anchor from a serialized `RangeSelector` selector.\n *\n * @param {Element} root - A root element from which to anchor.\n * @param {RangeSelector} selector\n * @return {RangeAnchor}\n */\n static fromSelector(root, selector) {\n\n const startContainer = nodeFromXPath(selector.startContainer, root);\n\n if (!startContainer) {\n throw new Error('Failed to resolve startContainer XPath');\n }\n\n const endContainer = nodeFromXPath(selector.endContainer, root);\n if (!endContainer) {\n throw new Error('Failed to resolve endContainer XPath');\n }\n\n const startPos = TextPosition.fromCharOffset(\n startContainer,\n selector.startOffset\n );\n const endPos = TextPosition.fromCharOffset(\n endContainer,\n selector.endOffset\n );\n\n const range = new TextRange(startPos, endPos).toRange();\n return new RangeAnchor(root, range);\n }\n\n toRange() {\n return this.range;\n }\n\n /**\n * @return {RangeSelector}\n */\n toSelector() {\n // \"Shrink\" the range so that it tightly wraps its text. This ensures more\n // predictable output for a given text selection.\n\n const normalizedRange = TextRange.fromRange(this.range).toRange();\n\n const textRange = TextRange.fromRange(normalizedRange);\n const startContainer = xpathFromNode(textRange.start.element, this.root);\n const endContainer = xpathFromNode(textRange.end.element, this.root);\n\n return {\n type: 'RangeSelector',\n startContainer,\n startOffset: textRange.start.offset,\n endContainer,\n endOffset: textRange.end.offset,\n };\n }\n}\n\n/**\n * Converts between `TextPositionSelector` selectors and `Range` objects.\n */\nexport class TextPositionAnchor {\n /**\n * @param {Element} root\n * @param {number} start\n * @param {number} end\n */\n constructor(root, start, end) {\n this.root = root;\n this.start = start;\n this.end = end;\n }\n\n /**\n * @param {Element} root\n * @param {Range} range\n * @return {TextPositionAnchor}\n */\n static fromRange(root, range) {\n const textRange = TextRange.fromRange(range).relativeTo(root);\n return new TextPositionAnchor(\n root,\n textRange.start.offset,\n textRange.end.offset\n );\n }\n /**\n * @param {Element} root\n * @param {TextPositionSelector} selector\n * @return {TextPositionAnchor}\n */\n static fromSelector(root, selector) {\n return new TextPositionAnchor(root, selector.start, selector.end);\n }\n\n /**\n * @return {TextPositionSelector}\n */\n toSelector() {\n return {\n type: 'TextPositionSelector',\n start: this.start,\n end: this.end,\n };\n }\n\n toRange() {\n return TextRange.fromOffsets(this.root, this.start, this.end).toRange();\n }\n}\n\n/**\n * @typedef QuoteMatchOptions\n * @prop {number} [hint] - Expected position of match in text. See `matchQuote`.\n */\n\n/**\n * Converts between `TextQuoteSelector` selectors and `Range` objects.\n */\nexport class TextQuoteAnchor {\n /**\n * @param {Element} root - A root element from which to anchor.\n * @param {string} exact\n * @param {Object} context\n * @param {string} [context.prefix]\n * @param {string} [context.suffix]\n */\n constructor(root, exact, context = {}) {\n this.root = root;\n this.exact = exact;\n this.context = context;\n }\n\n /**\n * Create a `TextQuoteAnchor` from a range.\n *\n * Will throw if `range` does not contain any text nodes.\n *\n * @param {Element} root\n * @param {Range} range\n * @return {TextQuoteAnchor}\n */\n static fromRange(root, range) {\n const text = /** @type {string} */ (root.textContent);\n const textRange = TextRange.fromRange(range).relativeTo(root);\n\n const start = textRange.start.offset;\n const end = textRange.end.offset;\n\n // Number of characters around the quote to capture as context. We currently\n // always use a fixed amount, but it would be better if this code was aware\n // of logical boundaries in the document (paragraph, article etc.) to avoid\n // capturing text unrelated to the quote.\n //\n // In regular prose the ideal content would often be the surrounding sentence.\n // This is a natural unit of meaning which enables displaying quotes in\n // context even when the document is not available. We could use `Intl.Segmenter`\n // for this when available.\n const contextLen = 32;\n\n return new TextQuoteAnchor(root, text.slice(start, end), {\n prefix: text.slice(Math.max(0, start - contextLen), start),\n suffix: text.slice(end, Math.min(text.length, end + contextLen)),\n });\n }\n\n /**\n * @param {Element} root\n * @param {TextQuoteSelector} selector\n * @return {TextQuoteAnchor}\n */\n static fromSelector(root, selector) {\n const {prefix, suffix} = selector;\n return new TextQuoteAnchor(root, selector.exact, {prefix, suffix});\n }\n\n /**\n * @return {TextQuoteSelector}\n */\n toSelector() {\n return {\n type: 'TextQuoteSelector',\n exact: this.exact,\n prefix: this.context.prefix,\n suffix: this.context.suffix,\n };\n }\n\n /**\n * @param {QuoteMatchOptions} [options]\n * @return {TextQuoteAnchor}\n */\n toRange(options = {}) {\n return this.toPositionAnchor(options).toRange();\n }\n\n /**\n * @param {QuoteMatchOptions} [options]\n * @return {TextPositionAnchor}\n */\n toPositionAnchor(options = {}) {\n const text = /** @type {string} */ (this.root.textContent);\n const match = matchQuote(text, this.exact, {\n ...this.context,\n hint: options.hint,\n });\n\n if (!match) {\n throw new Error('Quote not found');\n }\n\n return new TextPositionAnchor(this.root, match.start, match.end);\n }\n}\n"],"names":["RangeAnchor","constructor","root","range","this","static","selector","startContainer","nodeFromXPath","Error","endContainer","startPos","TextPosition","fromCharOffset","startOffset","endPos","endOffset","TextRange","toRange","toSelector","normalizedRange","fromRange","textRange","xpathFromNode","start","element","end","type","offset","_exports","TextPositionAnchor","relativeTo","fromOffsets","TextQuoteAnchor","exact","context","arguments","length","undefined","text","textContent","slice","prefix","Math","max","suffix","min","options","toPositionAnchor","match","matchQuote","hint"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"types.min.js","sources":["../src/types.js"],"sourcesContent":["/**\n * This module exports a set of classes for converting between DOM `Range`\n * objects and different types of selectors. It is mostly a thin wrapper around a\n * set of anchoring libraries. It serves two main purposes:\n *\n * 1. Providing a consistent interface across different types of anchors.\n * 2. Insulating the rest of the code from API changes in the underlying anchoring\n * libraries.\n *\n * This code originaly is from the Hypothesis project (https://github.com/hypothesis/client)\n * which is released under the 2-Clause BSD License (https://opensource.org/licenses/BSD-2-Clause),\n * sometimes referred to as the \"Simplified BSD License\".\n */\n\nimport {matchQuote} from './match-quote';\nimport {TextRange, TextPosition} from './text-range';\nimport {nodeFromXPath, xpathFromNode} from './xpath';\n\n/**\n * @typedef {import('../../types/api').RangeSelector} RangeSelector\n * @typedef {import('../../types/api').TextPositionSelector} TextPositionSelector\n * @typedef {import('../../types/api').TextQuoteSelector} TextQuoteSelector\n */\n\n/**\n * Converts between `RangeSelector` selectors and `Range` objects.\n */\nexport class RangeAnchor {\n /**\n * @param {Node} root - A root element from which to anchor.\n * @param {Range} range - A range describing the anchor.\n */\n constructor(root, range) {\n this.root = root;\n this.range = range;\n }\n\n /**\n * @param {Node} root - A root element from which to anchor.\n * @param {Range} range - A range describing the anchor.\n * @return {RangeAnchor}\n */\n static fromRange(root, range) {\n return new RangeAnchor(root, range);\n }\n\n /**\n * Create an anchor from a serialized `RangeSelector` selector.\n *\n * @param {Element} root - A root element from which to anchor.\n * @param {RangeSelector} selector\n * @return {RangeAnchor}\n */\n static fromSelector(root, selector) {\n\n const startContainer = nodeFromXPath(selector.startContainer, root);\n\n if (!startContainer) {\n throw new Error('Failed to resolve startContainer XPath');\n }\n\n const endContainer = nodeFromXPath(selector.endContainer, root);\n if (!endContainer) {\n throw new Error('Failed to resolve endContainer XPath');\n }\n\n const startPos = TextPosition.fromCharOffset(\n startContainer,\n selector.startOffset\n );\n const endPos = TextPosition.fromCharOffset(\n endContainer,\n selector.endOffset\n );\n\n const range = new TextRange(startPos, endPos).toRange();\n return new RangeAnchor(root, range);\n }\n\n toRange() {\n return this.range;\n }\n\n /**\n * @return {RangeSelector}\n */\n toSelector() {\n // \"Shrink\" the range so that it tightly wraps its text. This ensures more\n // predictable output for a given text selection.\n\n const normalizedRange = TextRange.fromRange(this.range).toRange();\n\n const textRange = TextRange.fromRange(normalizedRange);\n const startContainer = xpathFromNode(textRange.start.element, this.root);\n const endContainer = xpathFromNode(textRange.end.element, this.root);\n\n return {\n type: 'RangeSelector',\n startContainer,\n startOffset: textRange.start.offset,\n endContainer,\n endOffset: textRange.end.offset,\n };\n }\n}\n\n/**\n * Converts between `TextPositionSelector` selectors and `Range` objects.\n */\nexport class TextPositionAnchor {\n /**\n * @param {Element} root\n * @param {number} start\n * @param {number} end\n */\n constructor(root, start, end) {\n this.root = root;\n this.start = start;\n this.end = end;\n }\n\n /**\n * @param {Element} root\n * @param {Range} range\n * @return {TextPositionAnchor}\n */\n static fromRange(root, range) {\n const textRange = TextRange.fromRange(range).relativeTo(root);\n return new TextPositionAnchor(\n root,\n textRange.start.offset,\n textRange.end.offset\n );\n }\n /**\n * @param {Element} root\n * @param {TextPositionSelector} selector\n * @return {TextPositionAnchor}\n */\n static fromSelector(root, selector) {\n return new TextPositionAnchor(root, selector.start, selector.end);\n }\n\n /**\n * @return {TextPositionSelector}\n */\n toSelector() {\n return {\n type: 'TextPositionSelector',\n start: this.start,\n end: this.end,\n };\n }\n\n toRange() {\n return TextRange.fromOffsets(this.root, this.start, this.end).toRange();\n }\n}\n\n/**\n * @typedef QuoteMatchOptions\n * @prop {number} [hint] - Expected position of match in text. See `matchQuote`.\n */\n\n/**\n * Converts between `TextQuoteSelector` selectors and `Range` objects.\n */\nexport class TextQuoteAnchor {\n /**\n * @param {Element} root - A root element from which to anchor.\n * @param {string} exact\n * @param {Object} context\n * @param {string} [context.prefix]\n * @param {string} [context.suffix]\n */\n constructor(root, exact, context = {}) {\n this.root = root;\n this.exact = exact;\n this.context = context;\n }\n\n /**\n * Create a `TextQuoteAnchor` from a range.\n *\n * Will throw if `range` does not contain any text nodes.\n *\n * @param {Element} root\n * @param {Range} range\n * @return {TextQuoteAnchor}\n */\n static fromRange(root, range) {\n const text = /** @type {string} */ (root.textContent);\n const textRange = TextRange.fromRange(range).relativeTo(root);\n\n const start = textRange.start.offset;\n const end = textRange.end.offset;\n\n // Number of characters around the quote to capture as context. We currently\n // always use a fixed amount, but it would be better if this code was aware\n // of logical boundaries in the document (paragraph, article etc.) to avoid\n // capturing text unrelated to the quote.\n //\n // In regular prose the ideal content would often be the surrounding sentence.\n // This is a natural unit of meaning which enables displaying quotes in\n // context even when the document is not available. We could use `Intl.Segmenter`\n // for this when available.\n const contextLen = 32;\n\n return new TextQuoteAnchor(root, text.slice(start, end), {\n prefix: text.slice(Math.max(0, start - contextLen), start),\n suffix: text.slice(end, Math.min(text.length, end + contextLen)),\n });\n }\n\n /**\n * @param {Element} root\n * @param {TextQuoteSelector} selector\n * @return {TextQuoteAnchor}\n */\n static fromSelector(root, selector) {\n const {prefix, suffix} = selector;\n return new TextQuoteAnchor(root, selector.exact, {prefix, suffix});\n }\n\n /**\n * @return {TextQuoteSelector}\n */\n toSelector() {\n return {\n type: 'TextQuoteSelector',\n exact: this.exact,\n prefix: this.context.prefix,\n suffix: this.context.suffix,\n };\n }\n\n /**\n * @param {QuoteMatchOptions} [options]\n * @return {TextQuoteAnchor}\n */\n toRange(options = {}) {\n return this.toPositionAnchor(options).toRange();\n }\n\n /**\n * @param {QuoteMatchOptions} [options]\n * @return {TextPositionAnchor}\n */\n toPositionAnchor(options = {}) {\n const text = /** @type {string} */ (this.root.textContent);\n const match = matchQuote(text, this.exact, {\n ...this.context,\n hint: options.hint,\n });\n\n if (!match) {\n throw new Error('Quote not found');\n }\n\n return new TextPositionAnchor(this.root, match.start, match.end);\n }\n}\n"],"names":["RangeAnchor","constructor","root","range","selector","startContainer","Error","endContainer","startPos","TextPosition","fromCharOffset","startOffset","endPos","endOffset","TextRange","toRange","this","toSelector","normalizedRange","fromRange","textRange","start","element","end","type","offset","TextPositionAnchor","relativeTo","fromOffsets","TextQuoteAnchor","exact","context","text","textContent","slice","prefix","Math","max","suffix","min","length","options","toPositionAnchor","match","hint"],"mappings":"gRA2BaA,YAKXC,YAAYC,KAAMC,YACXD,KAAOA,UACPC,MAAQA,uBAQED,KAAMC,cACd,IAAIH,YAAYE,KAAMC,2BAUXD,KAAME,gBAElBC,gBAAiB,wBAAcD,SAASC,eAAgBH,UAEzDG,qBACG,IAAIC,MAAM,gDAGZC,cAAe,wBAAcH,SAASG,aAAcL,UACrDK,mBACG,IAAID,MAAM,8CAGZE,SAAWC,wBAAaC,eAC5BL,eACAD,SAASO,aAELC,OAASH,wBAAaC,eAC1BH,aACAH,SAASS,WAGLV,MAAQ,IAAIW,qBAAUN,SAAUI,QAAQG,iBACvC,IAAIf,YAAYE,KAAMC,OAG/BY,iBACSC,KAAKb,MAMdc,mBAIQC,gBAAkBJ,qBAAUK,UAAUH,KAAKb,OAAOY,UAElDK,UAAYN,qBAAUK,UAAUD,iBAChCb,gBAAiB,wBAAce,UAAUC,MAAMC,QAASN,KAAKd,MAC7DK,cAAe,wBAAca,UAAUG,IAAID,QAASN,KAAKd,YAExD,CACLsB,KAAM,gBACNnB,eAAAA,eACAM,YAAaS,UAAUC,MAAMI,OAC7BlB,aAAAA,aACAM,UAAWO,UAAUG,IAAIE,gDAQlBC,mBAMXzB,YAAYC,KAAMmB,MAAOE,UAClBrB,KAAOA,UACPmB,MAAQA,WACRE,IAAMA,qBAQIrB,KAAMC,aACfiB,UAAYN,qBAAUK,UAAUhB,OAAOwB,WAAWzB,aACjD,IAAIwB,mBACTxB,KACAkB,UAAUC,MAAMI,OAChBL,UAAUG,IAAIE,4BAQEvB,KAAME,iBACjB,IAAIsB,mBAAmBxB,KAAME,SAASiB,MAAOjB,SAASmB,KAM/DN,mBACS,CACLO,KAAM,uBACNH,MAAOL,KAAKK,MACZE,IAAKP,KAAKO,KAIdR,iBACSD,qBAAUc,YAAYZ,KAAKd,KAAMc,KAAKK,MAAOL,KAAKO,KAAKR,gEAYrDc,gBAQX5B,YAAYC,KAAM4B,WAAOC,+DAAU,QAC5B7B,KAAOA,UACP4B,MAAQA,WACRC,QAAUA,yBAYA7B,KAAMC,aACf6B,KAA8B9B,KAAK+B,YACnCb,UAAYN,qBAAUK,UAAUhB,OAAOwB,WAAWzB,MAElDmB,MAAQD,UAAUC,MAAMI,OACxBF,IAAMH,UAAUG,IAAIE,cAanB,IAAII,gBAAgB3B,KAAM8B,KAAKE,MAAMb,MAAOE,KAAM,CACvDY,OAAQH,KAAKE,MAAME,KAAKC,IAAI,EAAGhB,MAHd,IAGmCA,OACpDiB,OAAQN,KAAKE,MAAMX,IAAKa,KAAKG,IAAIP,KAAKQ,OAAQjB,IAJ7B,2BAaDrB,KAAME,gBAClB+B,OAACA,OAADG,OAASA,QAAUlC,gBAClB,IAAIyB,gBAAgB3B,KAAME,SAAS0B,MAAO,CAACK,OAAAA,OAAQG,OAAAA,SAM5DrB,mBACS,CACLO,KAAM,oBACNM,MAAOd,KAAKc,MACZK,OAAQnB,KAAKe,QAAQI,OACrBG,OAAQtB,KAAKe,QAAQO,QAQzBvB,cAAQ0B,+DAAU,UACTzB,KAAK0B,iBAAiBD,SAAS1B,UAOxC2B,uBAAiBD,+DAAU,SACnBT,KAA8BhB,KAAKd,KAAK+B,YACxCU,OAAQ,0BAAWX,KAAMhB,KAAKc,MAAO,IACtCd,KAAKe,QACRa,KAAMH,QAAQG,WAGXD,YACG,IAAIrC,MAAM,0BAGX,IAAIoB,mBAAmBV,KAAKd,KAAMyC,MAAMtB,MAAOsB,MAAMpB"} \ No newline at end of file diff --git a/amd/build/xpath.min.js b/amd/build/xpath.min.js index 4cd3bb3..fb26f06 100644 --- a/amd/build/xpath.min.js +++ b/amd/build/xpath.min.js @@ -1,3 +1,3 @@ -define("mod_margic/xpath",["exports"],(function(_exports){function getPathSegment(node){const name=function(node){const nodeName=node.nodeName.toLowerCase();let result=nodeName;return"#text"===nodeName&&(result="text()"),result}(node),pos=function(node){let pos=0,tmp=node;for(;tmp;)tmp.nodeName===node.nodeName&&(pos+=1),tmp=tmp.previousSibling;return pos}(node);return"".concat(name,"[").concat(pos,"]")}function nthChildOfType(element,nodeName,index){nodeName=nodeName.toUpperCase();let matchIndex=-1;for(let i=0;i1&&void 0!==arguments[1]?arguments[1]:document.body;try{return function(xpath,root){const isSimpleXPath=null!==xpath.match(/^(\/[A-Za-z0-9-]+(\[[0-9]+\])?)+$/);if(!isSimpleXPath)throw new Error("Expression is not a simple XPath");const segments=xpath.split("/");let element=root;segments.shift();for(let segment of segments){let elementName,elementIndex;const separatorPos=segment.indexOf("[");if(-1!==separatorPos){elementName=segment.slice(0,separatorPos);const indexStr=segment.slice(separatorPos+1,segment.indexOf("]"));if(elementIndex=parseInt(indexStr)-1,elementIndex<0)return null}else elementName=segment,elementIndex=0;const child=nthChildOfType(element,elementName,elementIndex);if(!child)return null;element=child}return element}(xpath,root)}catch(err){return document.evaluate("."+xpath,root,null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue}},_exports.xpathFromNode=function(node,root){let xpath="",elem=node;for(;elem!==root;){if(!elem)throw new Error("Node is not a descendant of root");xpath=getPathSegment(elem)+"/"+xpath,elem=elem.parentNode}return xpath="/"+xpath,xpath=xpath.replace(/\/$/,""),xpath}})); +define("mod_margic/xpath",["exports"],(function(_exports){function getPathSegment(node){const name=function(node){const nodeName=node.nodeName.toLowerCase();let result=nodeName;return"#text"===nodeName&&(result="text()"),result}(node),pos=function(node){let pos=0,tmp=node;for(;tmp;)tmp.nodeName===node.nodeName&&(pos+=1),tmp=tmp.previousSibling;return pos}(node);return"".concat(name,"[").concat(pos,"]")}function nthChildOfType(element,nodeName,index){nodeName=nodeName.toUpperCase();let matchIndex=-1;for(let i=0;i1&&void 0!==arguments[1]?arguments[1]:document.body;try{return evaluateSimpleXPath(xpath,root)}catch(err){return document.evaluate("."+xpath,root,null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue}},_exports.xpathFromNode=function(node,root){let xpath="",elem=node;for(;elem!==root;){if(!elem)throw new Error("Node is not a descendant of root");xpath=getPathSegment(elem)+"/"+xpath,elem=elem.parentNode}return xpath="/"+xpath,xpath=xpath.replace(/\/$/,""),xpath}})); //# sourceMappingURL=xpath.min.js.map \ No newline at end of file diff --git a/amd/build/xpath.min.js.map b/amd/build/xpath.min.js.map index 21ed853..6317652 100644 --- a/amd/build/xpath.min.js.map +++ b/amd/build/xpath.min.js.map @@ -1 +1 @@ -{"version":3,"file":"xpath.min.js","sources":["../src/xpath.js"],"sourcesContent":["/**\n * XPATH and DOM functions used for anchoring and highlighting.\n *\n * This code originaly is from the Hypothesis project (https://github.com/hypothesis/client)\n * which is released under the 2-Clause BSD License (https://opensource.org/licenses/BSD-2-Clause),\n * sometimes referred to as the \"Simplified BSD License\".\n */\n\n/**\n * Get the node name for use in generating an xpath expression.\n *\n * @param {Node} node\n * @return {string} - Name of the node\n */\nfunction getNodeName(node) {\n const nodeName = node.nodeName.toLowerCase();\n let result = nodeName;\n if (nodeName === '#text') {\n result = 'text()';\n }\n return result;\n}\n\n/**\n * Get the index of the node as it appears in its parent's child list\n *\n * @param {Node} node\n * @return {int} - Position of the node\n */\nfunction getNodePosition(node) {\n let pos = 0;\n /** @type {Node|null} */\n let tmp = node;\n while (tmp) {\n if (tmp.nodeName === node.nodeName) {\n pos += 1;\n }\n tmp = tmp.previousSibling;\n }\n return pos;\n}\n\n/**\n * Get the path segments to the node\n *\n * @param {Node} node\n * @return {array} - Path segments\n */\nfunction getPathSegment(node) {\n const name = getNodeName(node);\n const pos = getNodePosition(node);\n return `${name}[${pos}]`;\n}\n\n/**\n * A simple XPath generator which can generate XPaths of the form\n * /tag[index]/tag[index].\n *\n * @param {Node} node - The node to generate a path to\n * @param {Node} root - Root node to which the returned path is relative\n * @return {string} - The xpath of a node\n */\nexport function xpathFromNode(node, root) {\n let xpath = '';\n\n /** @type {Node|null} */\n let elem = node;\n while (elem !== root) {\n if (!elem) {\n throw new Error('Node is not a descendant of root');\n }\n xpath = getPathSegment(elem) + '/' + xpath;\n elem = elem.parentNode;\n }\n xpath = '/' + xpath;\n xpath = xpath.replace(/\\/$/, ''); // Remove trailing slash\n\n return xpath;\n}\n\n/**\n * Return the `index`'th immediate child of `element` whose tag name is\n * `nodeName` (case insensitive).\n *\n * @param {Element} element\n * @param {string} nodeName\n * @param {number} index\n * @return {Element} - The child element or null\n */\nfunction nthChildOfType(element, nodeName, index) {\n nodeName = nodeName.toUpperCase();\n\n let matchIndex = -1;\n for (let i = 0; i < element.children.length; i++) {\n const child = element.children[i];\n if (child.nodeName.toUpperCase() === nodeName) {\n ++matchIndex;\n if (matchIndex === index) {\n return child;\n }\n }\n }\n\n return null;\n}\n\n/**\n * Evaluate a _simple XPath_ relative to a `root` element and return the\n * matching element.\n *\n * A _simple XPath_ is a sequence of one or more `/tagName[index]` strings.\n *\n * Unlike `document.evaluate` this function:\n *\n * - Only supports simple XPaths\n * - Is not affected by the document's _type_ (HTML or XML/XHTML)\n * - Ignores element namespaces when matching element names in the XPath against\n * elements in the DOM tree\n * - Is case insensitive for all elements, not just HTML elements\n *\n * The matching element is returned or `null` if no such element is found.\n * An error is thrown if `xpath` is not a simple XPath.\n *\n * @param {string} xpath\n * @param {Element} root\n * @return {Element|null}\n */\nfunction evaluateSimpleXPath(xpath, root) {\n const isSimpleXPath =\n xpath.match(/^(\\/[A-Za-z0-9-]+(\\[[0-9]+\\])?)+$/) !== null;\n if (!isSimpleXPath) {\n throw new Error('Expression is not a simple XPath');\n }\n\n const segments = xpath.split('/');\n let element = root;\n\n // Remove leading empty segment. The regex above validates that the XPath\n // has at least two segments, with the first being empty and the others non-empty.\n segments.shift();\n\n for (let segment of segments) {\n let elementName;\n let elementIndex;\n\n const separatorPos = segment.indexOf('[');\n if (separatorPos !== -1) {\n elementName = segment.slice(0, separatorPos);\n\n const indexStr = segment.slice(separatorPos + 1, segment.indexOf(']'));\n elementIndex = parseInt(indexStr) - 1;\n if (elementIndex < 0) {\n return null;\n }\n } else {\n elementName = segment;\n elementIndex = 0;\n }\n\n const child = nthChildOfType(element, elementName, elementIndex);\n if (!child) {\n return null;\n }\n\n element = child;\n }\n\n return element;\n}\n\n/**\n * Finds an element node using an XPath relative to `root`\n *\n * Example:\n * node = nodeFromXPath('/main/article[1]/p[3]', document.body)\n *\n * @param {string} xpath\n * @param {Element} [root]\n * @return {Node|null}\n */\nexport function nodeFromXPath(xpath, root = document.body) {\n try {\n return evaluateSimpleXPath(xpath, root);\n } catch (err) {\n return document.evaluate(\n '.' + xpath,\n root,\n\n // Nb. The `namespaceResolver` and `result` arguments are optional in the spec but required in Edge Legacy.\n null /* NamespaceResolver */,\n XPathResult.FIRST_ORDERED_NODE_TYPE,\n null /* Result */\n ).singleNodeValue;\n }\n}\n"],"names":["getPathSegment","node","name","nodeName","toLowerCase","result","getNodeName","pos","tmp","previousSibling","getNodePosition","concat","nthChildOfType","element","index","toUpperCase","matchIndex","i","children","length","child","xpath","root","arguments","undefined","document","body","isSimpleXPath","match","Error","segments","split","shift","segment","elementName","elementIndex","separatorPos","indexOf","slice","indexStr","parseInt","evaluateSimpleXPath","err","evaluate","XPathResult","FIRST_ORDERED_NODE_TYPE","singleNodeValue","elem","parentNode","replace"],"mappings":"0DAgDA,SAASA,eAAeC,MACtB,MAAMC,KAnCR,SAAqBD,MACnB,MAAME,SAAWF,KAAKE,SAASC,cAC/B,IAAIC,OAASF,SAIb,MAHiB,UAAbA,WACFE,OAAS,UAEJA,MACT,CA4BeC,CAAYL,MACnBM,IArBR,SAAyBN,MACvB,IAAIM,IAAM,EAENC,IAAMP,KACV,KAAOO,KACDA,IAAIL,WAAaF,KAAKE,WACxBI,KAAO,GAETC,IAAMA,IAAIC,gBAEZ,OAAOF,GACT,CAUcG,CAAgBT,MAC5B,MAAA,GAAAU,OAAUT,KAAIS,KAAAA,OAAIJ,IAAG,IACvB,CAqCA,SAASK,eAAeC,QAASV,SAAUW,OACzCX,SAAWA,SAASY,cAEpB,IAAIC,YAAc,EAClB,IAAK,IAAIC,EAAI,EAAGA,EAAIJ,QAAQK,SAASC,OAAQF,IAAK,CAChD,MAAMG,MAAQP,QAAQK,SAASD,GAC/B,GAAIG,MAAMjB,SAASY,gBAAkBZ,aACjCa,WACEA,aAAeF,OACjB,OAAOM,KAGb,CAEA,OAAO,IACT,gFA4EO,SAAuBC,OAA6B,IAAtBC,KAAIC,UAAAJ,OAAAI,QAAAC,IAAAD,UAAAC,GAAAD,UAAGE,GAAAA,SAASC,KACnD,IACE,OAvDJ,SAA6BL,MAAOC,MAClC,MAAMK,cACiD,OAArDN,MAAMO,MAAM,qCACd,IAAKD,cACH,MAAM,IAAIE,MAAM,oCAGlB,MAAMC,SAAWT,MAAMU,MAAM,KAC7B,IAAIlB,QAAUS,KAIdQ,SAASE,QAET,IAAK,IAAIC,WAAWH,SAAU,CAC5B,IAAII,YACAC,aAEJ,MAAMC,aAAeH,QAAQI,QAAQ,KACrC,IAAsB,IAAlBD,aAAqB,CACvBF,YAAcD,QAAQK,MAAM,EAAGF,cAE/B,MAAMG,SAAWN,QAAQK,MAAMF,aAAe,EAAGH,QAAQI,QAAQ,MAEjE,GADAF,aAAeK,SAASD,UAAY,EAChCJ,aAAe,EACjB,OAAO,IAEX,MACED,YAAcD,QACdE,aAAe,EAGjB,MAAMf,MAAQR,eAAeC,QAASqB,YAAaC,cACnD,IAAKf,MACH,OAAO,KAGTP,QAAUO,KACZ,CAEA,OAAOP,OACT,CAcW4B,CAAoBpB,MAAOC,KACnC,CAAC,MAAOoB,KACP,OAAOjB,SAASkB,SACd,IAAMtB,MACNC,KAGA,KACAsB,YAAYC,wBACZ,MACAC,eACJ,CACF,yBApIO,SAAuB7C,KAAMqB,MAClC,IAAID,MAAQ,GAGR0B,KAAO9C,KACX,KAAO8C,OAASzB,MAAM,CACpB,IAAKyB,KACH,MAAM,IAAIlB,MAAM,oCAElBR,MAAQrB,eAAe+C,MAAQ,IAAM1B,MACrC0B,KAAOA,KAAKC,UACd,CAIA,OAHA3B,MAAQ,IAAMA,MACdA,MAAQA,MAAM4B,QAAQ,MAAO,IAEtB5B,KACT,CAoHC"} \ No newline at end of file +{"version":3,"file":"xpath.min.js","sources":["../src/xpath.js"],"sourcesContent":["/**\n * XPATH and DOM functions used for anchoring and highlighting.\n *\n * This code originaly is from the Hypothesis project (https://github.com/hypothesis/client)\n * which is released under the 2-Clause BSD License (https://opensource.org/licenses/BSD-2-Clause),\n * sometimes referred to as the \"Simplified BSD License\".\n */\n\n/**\n * Get the node name for use in generating an xpath expression.\n *\n * @param {Node} node\n * @return {string} - Name of the node\n */\nfunction getNodeName(node) {\n const nodeName = node.nodeName.toLowerCase();\n let result = nodeName;\n if (nodeName === '#text') {\n result = 'text()';\n }\n return result;\n}\n\n/**\n * Get the index of the node as it appears in its parent's child list\n *\n * @param {Node} node\n * @return {int} - Position of the node\n */\nfunction getNodePosition(node) {\n let pos = 0;\n /** @type {Node|null} */\n let tmp = node;\n while (tmp) {\n if (tmp.nodeName === node.nodeName) {\n pos += 1;\n }\n tmp = tmp.previousSibling;\n }\n return pos;\n}\n\n/**\n * Get the path segments to the node\n *\n * @param {Node} node\n * @return {array} - Path segments\n */\nfunction getPathSegment(node) {\n const name = getNodeName(node);\n const pos = getNodePosition(node);\n return `${name}[${pos}]`;\n}\n\n/**\n * A simple XPath generator which can generate XPaths of the form\n * /tag[index]/tag[index].\n *\n * @param {Node} node - The node to generate a path to\n * @param {Node} root - Root node to which the returned path is relative\n * @return {string} - The xpath of a node\n */\nexport function xpathFromNode(node, root) {\n let xpath = '';\n\n /** @type {Node|null} */\n let elem = node;\n while (elem !== root) {\n if (!elem) {\n throw new Error('Node is not a descendant of root');\n }\n xpath = getPathSegment(elem) + '/' + xpath;\n elem = elem.parentNode;\n }\n xpath = '/' + xpath;\n xpath = xpath.replace(/\\/$/, ''); // Remove trailing slash\n\n return xpath;\n}\n\n/**\n * Return the `index`'th immediate child of `element` whose tag name is\n * `nodeName` (case insensitive).\n *\n * @param {Element} element\n * @param {string} nodeName\n * @param {number} index\n * @return {Element} - The child element or null\n */\nfunction nthChildOfType(element, nodeName, index) {\n nodeName = nodeName.toUpperCase();\n\n let matchIndex = -1;\n for (let i = 0; i < element.children.length; i++) {\n const child = element.children[i];\n if (child.nodeName.toUpperCase() === nodeName) {\n ++matchIndex;\n if (matchIndex === index) {\n return child;\n }\n }\n }\n\n return null;\n}\n\n/**\n * Evaluate a _simple XPath_ relative to a `root` element and return the\n * matching element.\n *\n * A _simple XPath_ is a sequence of one or more `/tagName[index]` strings.\n *\n * Unlike `document.evaluate` this function:\n *\n * - Only supports simple XPaths\n * - Is not affected by the document's _type_ (HTML or XML/XHTML)\n * - Ignores element namespaces when matching element names in the XPath against\n * elements in the DOM tree\n * - Is case insensitive for all elements, not just HTML elements\n *\n * The matching element is returned or `null` if no such element is found.\n * An error is thrown if `xpath` is not a simple XPath.\n *\n * @param {string} xpath\n * @param {Element} root\n * @return {Element|null}\n */\nfunction evaluateSimpleXPath(xpath, root) {\n const isSimpleXPath =\n xpath.match(/^(\\/[A-Za-z0-9-]+(\\[[0-9]+\\])?)+$/) !== null;\n if (!isSimpleXPath) {\n throw new Error('Expression is not a simple XPath');\n }\n\n const segments = xpath.split('/');\n let element = root;\n\n // Remove leading empty segment. The regex above validates that the XPath\n // has at least two segments, with the first being empty and the others non-empty.\n segments.shift();\n\n for (let segment of segments) {\n let elementName;\n let elementIndex;\n\n const separatorPos = segment.indexOf('[');\n if (separatorPos !== -1) {\n elementName = segment.slice(0, separatorPos);\n\n const indexStr = segment.slice(separatorPos + 1, segment.indexOf(']'));\n elementIndex = parseInt(indexStr) - 1;\n if (elementIndex < 0) {\n return null;\n }\n } else {\n elementName = segment;\n elementIndex = 0;\n }\n\n const child = nthChildOfType(element, elementName, elementIndex);\n if (!child) {\n return null;\n }\n\n element = child;\n }\n\n return element;\n}\n\n/**\n * Finds an element node using an XPath relative to `root`\n *\n * Example:\n * node = nodeFromXPath('/main/article[1]/p[3]', document.body)\n *\n * @param {string} xpath\n * @param {Element} [root]\n * @return {Node|null}\n */\nexport function nodeFromXPath(xpath, root = document.body) {\n try {\n return evaluateSimpleXPath(xpath, root);\n } catch (err) {\n return document.evaluate(\n '.' + xpath,\n root,\n\n // Nb. The `namespaceResolver` and `result` arguments are optional in the spec but required in Edge Legacy.\n null /* NamespaceResolver */,\n XPathResult.FIRST_ORDERED_NODE_TYPE,\n null /* Result */\n ).singleNodeValue;\n }\n}\n"],"names":["getPathSegment","node","name","nodeName","toLowerCase","result","getNodeName","pos","tmp","previousSibling","getNodePosition","nthChildOfType","element","index","toUpperCase","matchIndex","i","children","length","child","evaluateSimpleXPath","xpath","root","match","Error","segments","split","shift","segment","elementName","elementIndex","separatorPos","indexOf","slice","indexStr","parseInt","document","body","err","evaluate","XPathResult","FIRST_ORDERED_NODE_TYPE","singleNodeValue","elem","parentNode","replace"],"mappings":"mEAgDSA,eAAeC,YAChBC,cAnCaD,YACbE,SAAWF,KAAKE,SAASC,kBAC3BC,OAASF,eACI,UAAbA,WACFE,OAAS,UAEJA,OA6BMC,CAAYL,MACnBM,aArBiBN,UACnBM,IAAM,EAENC,IAAMP,UACHO,KACDA,IAAIL,WAAaF,KAAKE,WACxBI,KAAO,GAETC,IAAMA,IAAIC,uBAELF,IAWKG,CAAgBT,sBAClBC,iBAAQK,kBAsCXI,eAAeC,QAAST,SAAUU,OACzCV,SAAWA,SAASW,kBAEhBC,YAAc,MACb,IAAIC,EAAI,EAAGA,EAAIJ,QAAQK,SAASC,OAAQF,IAAK,OAC1CG,MAAQP,QAAQK,SAASD,MAC3BG,MAAMhB,SAASW,gBAAkBX,aACjCY,WACEA,aAAeF,cACVM,aAKN,cAwBAC,oBAAoBC,MAAOC,WAEqB,OAArDD,MAAME,MAAM,4CAEN,IAAIC,MAAM,0CAGZC,SAAWJ,MAAMK,MAAM,SACzBd,QAAUU,KAIdG,SAASE,YAEJ,IAAIC,WAAWH,SAAU,KACxBI,YACAC,mBAEEC,aAAeH,QAAQI,QAAQ,SACf,IAAlBD,aAAqB,CACvBF,YAAcD,QAAQK,MAAM,EAAGF,oBAEzBG,SAAWN,QAAQK,MAAMF,aAAe,EAAGH,QAAQI,QAAQ,SACjEF,aAAeK,SAASD,UAAY,EAChCJ,aAAe,SACV,UAGTD,YAAcD,QACdE,aAAe,QAGXX,MAAQR,eAAeC,QAASiB,YAAaC,kBAC9CX,aACI,KAGTP,QAAUO,aAGLP,gGAaqBS,WAAOC,4DAAOc,SAASC,gBAE1CjB,oBAAoBC,MAAOC,MAClC,MAAOgB,YACAF,SAASG,SACd,IAAMlB,MACNC,KAGA,KACAkB,YAAYC,wBACZ,MACAC,kDAlIwBzC,KAAMqB,UAC9BD,MAAQ,GAGRsB,KAAO1C,UACJ0C,OAASrB,MAAM,KACfqB,WACG,IAAInB,MAAM,oCAElBH,MAAQrB,eAAe2C,MAAQ,IAAMtB,MACrCsB,KAAOA,KAAKC,kBAEdvB,MAAQ,IAAMA,MACdA,MAAQA,MAAMwB,QAAQ,MAAO,IAEtBxB"} \ No newline at end of file diff --git a/amd/src/annotations.js b/amd/src/annotations.js index 36c8047..7900473 100644 --- a/amd/src/annotations.js +++ b/amd/src/annotations.js @@ -38,7 +38,7 @@ export const init = (cmid, canmakeannotations, myuserid, focusannotation) => { $('.annotation-form div.row').removeClass('row'); // Onclick listener if form is canceled. - $(document).on('click', '#id_cancel', function(e) { + $(document).on('click', '.margic_entry #id_cancel', function(e) { e.preventDefault(); removeAllTempHighlights(); // Remove other temporary highlights. @@ -49,7 +49,7 @@ export const init = (cmid, canmakeannotations, myuserid, focusannotation) => { }); // Listen for return key pressed to submit annotation form. - $('textarea').keypress(function(e) { + $('.margic_entry textarea').keypress(function(e) { if (e.which == 13) { $(this).parents(':eq(2)').submit(); e.preventDefault(); diff --git a/classes/event/annotation_created.php b/classes/event/annotation_created.php index 3416fe1..0049944 100644 --- a/classes/event/annotation_created.php +++ b/classes/event/annotation_created.php @@ -71,25 +71,6 @@ public function get_url() { )); } - /** - * replace add_to_log() statement. - * - * @return array of parameters to be passed to legacy add_to_log() function. - */ - protected function get_legacy_logdata() { - $url = new \moodle_url('view.php', array( - 'id' => $this->contextinstanceid - )); - return array( - $this->courseid, - 'margic', - 'add annotation', - $url->out(), - $this->objectid, - $this->contextinstanceid - ); - } - /** * Get objectid mapping for restore. */ diff --git a/classes/event/annotation_deleted.php b/classes/event/annotation_deleted.php index 031c727..da1f750 100644 --- a/classes/event/annotation_deleted.php +++ b/classes/event/annotation_deleted.php @@ -71,25 +71,6 @@ public function get_url() { )); } - /** - * replace add_to_log() statement. - * - * @return array of parameters to be passed to legacy add_to_log() function. - */ - protected function get_legacy_logdata() { - $url = new \moodle_url('view.php', array( - 'id' => $this->contextinstanceid - )); - return array( - $this->courseid, - 'margic', - 'delete annotation', - $url->out(), - $this->objectid, - $this->contextinstanceid - ); - } - /** * Get objectid mapping for restore. */ diff --git a/classes/event/annotation_updated.php b/classes/event/annotation_updated.php index f669b3a..b0d99b3 100644 --- a/classes/event/annotation_updated.php +++ b/classes/event/annotation_updated.php @@ -71,25 +71,6 @@ public function get_url() { )); } - /** - * replace add_to_log() statement. - * - * @return array of parameters to be passed to legacy add_to_log() function. - */ - protected function get_legacy_logdata() { - $url = new \moodle_url('view.php', array( - 'id' => $this->contextinstanceid - )); - return array( - $this->courseid, - 'margic', - 'update annotation', - $url->out(), - $this->objectid, - $this->contextinstanceid - ); - } - /** * Get objectid mapping for restore. */ diff --git a/classes/event/course_module_viewed.php b/classes/event/course_module_viewed.php index c8a4d75..e814d9a 100644 --- a/classes/event/course_module_viewed.php +++ b/classes/event/course_module_viewed.php @@ -43,25 +43,6 @@ protected function init() { $this->data['objecttable'] = 'margic'; } - /** - * Return the legacy event log data. - * - * @return array|null - */ - protected function get_legacy_logdata() { - $url = new \moodle_url('view.php', array( - 'id' => $this->contextinstanceid - )); - return array( - $this->courseid, - 'margic', - 'view', - $url->out(), - $this->objectid, - $this->contextinstanceid - ); - } - /** * Get objectid mapping for restore. */ diff --git a/classes/event/download_margic_entries.php b/classes/event/download_margic_entries.php index 1747ed1..7518ba1 100644 --- a/classes/event/download_margic_entries.php +++ b/classes/event/download_margic_entries.php @@ -70,23 +70,4 @@ public function get_url() { 'id' => $this->contextinstanceid )); } - - /** - * replace add_to_log() statement. - * - * @return array of parameters to be passed to legacy add_to_log() function. - */ - protected function get_legacy_logdata() { - $url = new \moodle_url('view.php', array( - 'id' => $this->contextinstanceid - )); - return array( - $this->courseid, - 'margic', - 'download entries', - $url->out(), - $this->objectid, - $this->contextinstanceid - ); - } } diff --git a/classes/event/entry_created.php b/classes/event/entry_created.php index a6cb524..044dcd7 100644 --- a/classes/event/entry_created.php +++ b/classes/event/entry_created.php @@ -71,25 +71,6 @@ public function get_url() { )); } - /** - * replace add_to_log() statement. - * - * @return array of parameters to be passed to legacy add_to_log() function. - */ - protected function get_legacy_logdata() { - $url = new \moodle_url('edit.php', array( - 'id' => $this->contextinstanceid - )); - return array( - $this->courseid, - 'margic', - 'add entry', - $url->out(), - $this->objectid, - $this->contextinstanceid - ); - } - /** * Get objectid mapping for restore. */ diff --git a/classes/event/entry_updated.php b/classes/event/entry_updated.php index c3887cd..5367513 100644 --- a/classes/event/entry_updated.php +++ b/classes/event/entry_updated.php @@ -71,25 +71,6 @@ public function get_url() { )); } - /** - * replace add_to_log() statement. - * - * @return array of parameters to be passed to legacy add_to_log() function. - */ - protected function get_legacy_logdata() { - $url = new \moodle_url('edit.php', array( - 'id' => $this->contextinstanceid - )); - return array( - $this->courseid, - 'margic', - 'update entry', - $url->out(), - $this->objectid, - $this->contextinstanceid - ); - } - /** * Get objectid mapping for restore. */ diff --git a/classes/event/feedback_updated.php b/classes/event/feedback_updated.php index 6ab4ed3..24c6f29 100644 --- a/classes/event/feedback_updated.php +++ b/classes/event/feedback_updated.php @@ -71,25 +71,6 @@ public function get_url() { )); } - /** - * replace add_to_log() statement. - * - * @return array of parameters to be passed to legacy add_to_log() function. - */ - protected function get_legacy_logdata() { - $url = new \moodle_url('view.php', array( - 'id' => $this->contextinstanceid - )); - return array( - $this->courseid, - 'margic', - 'update feedback', - $url->out(), - $this->objectid, - $this->contextinstanceid - ); - } - /** * Get objectid mapping for restore. */ diff --git a/classes/event/invalid_access_attempt.php b/classes/event/invalid_access_attempt.php index 40504a4..28d2232 100644 --- a/classes/event/invalid_access_attempt.php +++ b/classes/event/invalid_access_attempt.php @@ -70,14 +70,4 @@ public function get_description() { public function get_url() { return new \moodle_url('/mod/margic/view.php', array('id' => $this->contextinstanceid)); } - - /** - * replace add_to_log() statement. - * - * @return array of parameters to be passed to legacy add_to_log() function. - */ - protected function get_legacy_logdata() { - $url = new \moodle_url('view.php', array('id' => $this->contextinstanceid)); - return array($this->courseid, 'margic', 'invalid access attempt', $url->out(), $this->objectid, $this->contextinstanceid); - } } diff --git a/classes/local/helper.php b/classes/local/helper.php index a20e90a..4f1c476 100644 --- a/classes/local/helper.php +++ b/classes/local/helper.php @@ -465,8 +465,12 @@ public static function margic_return_feedback_area_for_entry($cmid, $context, $c if ($entry->teacher) { $teacher = $DB->get_record('user', array('id' => $entry->teacher)); - $teacherimage = $OUTPUT->user_picture($teacher, + if ($teacher) { + $teacherimage = $OUTPUT->user_picture($teacher, array('courseid' => $course->id, 'link' => true, 'includefullname' => true, 'size' => 30)); + } else { + $teacherimage = false; + } } else { $teacherimage = false; } diff --git a/edit.php b/edit.php index 0d5c2e7..5dd5c3b 100644 --- a/edit.php +++ b/edit.php @@ -130,7 +130,7 @@ } $data->entryid = $entry->id; - $data->timecreated = $entry->timecreated; + $data->timecreated = time(); $data->text = $entry->text; $data->textformat = $entry->format; @@ -152,7 +152,8 @@ 'mod_margic', 'attachment', $data->entryid); // Create form. -$form = new mod_margic_entry_form(null, array('margic' => $moduleinstance->editentrydates, 'editoroptions' => $editoroptions)); +$form = new mod_margic_entry_form(null, + array('editentrydates' => $moduleinstance->editentrydates, 'editoroptions' => $editoroptions)); // Set existing data for this entry. $form->set_data($data); @@ -191,7 +192,7 @@ $baseentry = $DB->get_record('margic_entries', array('margic' => $moduleinstance->id, "id" => $newentry->baseentry)); - if ($newentry->timecreated < $baseentry->timemodified) { + if ($newentry->timecreated <= $baseentry->timemodified) { redirect(new moodle_url('/mod/margic/view.php?id=' . $cm->id), get_string('timecreatedinvalid', 'mod_margic'), null, notification::NOTIFY_ERROR); } @@ -199,7 +200,7 @@ $connectedentries = $DB->get_records('margic_entries', array('margic' => $moduleinstance->id, 'baseentry' => $newentry->baseentry), 'timecreated DESC'); - if ($connectedentries && $newentry->timecreated < $connectedentries[array_key_first($connectedentries)]->timecreated) { + if ($connectedentries && $newentry->timecreated <= $connectedentries[array_key_first($connectedentries)]->timecreated) { redirect(new moodle_url('/mod/margic/view.php?id=' . $cm->id), get_string('timecreatedinvalid', 'mod_margic'), null, notification::NOTIFY_ERROR); } diff --git a/edit_form.php b/edit_form.php index 3f016bc..818d076 100644 --- a/edit_form.php +++ b/edit_form.php @@ -46,9 +46,9 @@ public function definition() { $mform->addElement('hidden', 'id'); $mform->setType('id', PARAM_INT); - $mform->addElement('hidden', 'margic'); - $mform->setType('margic', PARAM_INT); - $mform->setDefault('margic', $this->_customdata['margic']); + $mform->addElement('hidden', 'editentrydates'); + $mform->setType('editentrydates', PARAM_INT); + $mform->setDefault('editentrydates', $this->_customdata['editentrydates']); $mform->addElement('hidden', 'entryid'); $mform->setType('entryid', PARAM_INT); @@ -59,8 +59,8 @@ public function definition() { // Add date selector if entry dates can be edited. $mform->addElement('date_time_selector', 'timecreated', get_string('margicentrydate', 'margic')); $mform->setType('timecreated', PARAM_INT); - $mform->hideIf('timecreated', 'margic', 'neq', '1'); - $mform->disabledIf('timecreated', 'margic', 'neq', '1'); + $mform->hideIf('timecreated', 'editentrydates', 'neq', '1'); + $mform->disabledIf('timecreated', 'editentrydates', 'neq', '1'); } else { $mform->addElement('hidden', 'timecreated'); $mform->setType('timecreated', PARAM_INT); diff --git a/locallib.php b/locallib.php index e9d12a2..4659f7e 100644 --- a/locallib.php +++ b/locallib.php @@ -94,10 +94,10 @@ public function __construct($id, $m, $userid, $action, $pagecount, $page) { */ function sortannotation($a, $b) { if ($a->annotationstart === $b->annotationstart) { - return $a->annotationend > $b->annotationend; + return $a->annotationend <=> $b->annotationend; } - return $a->annotationstart > $b->annotationstart; + return $a->annotationstart <=> $b->annotationstart; } global $DB, $USER; @@ -620,7 +620,7 @@ private function prepare_entry_annotations($entry, $strmanager, $annotationmode } } - if (has_capability('mod/margic:makeannotations', $this->context) && $annotation->userid == $USER->id) { + if (has_capability('mod/margic:makeannotations', $this->context) && $annotation->userid == $USER->id && !$readonly) { $entry->annotations[$key]->canbeedited = true; } else { $entry->annotations[$key]->canbeedited = false; diff --git a/pix/icon.png b/pix/icon.png index 11e0027..24a15f9 100644 Binary files a/pix/icon.png and b/pix/icon.png differ diff --git a/pix/icon.svg b/pix/icon.svg index 53c0e2f..69f2d0c 100644 --- a/pix/icon.svg +++ b/pix/icon.svg @@ -1,76 +1,187 @@ - + - + Canvas 1 Layer 1 - - - polygon4575 - - - - polygon4582 - - - - polygon4589 - - - - path4598 - - - - path4613 - - - - path29 - - - - path57-4 - - - - path64-9 - - - - rect73-0 - - - - rect32-5 - - - - rect5488-3 - - - - rect46-9 - - - - rect11-1 - - - - rect11-0-2 - - - - path2960-8 - - - - - path2982-1 - + + Canvas_1 + + Canvas_1_Layer_1 + + Group_2 + + Group_3 + + Group_35 + + Graphic_36 + + + + + + + Group_33 + + Graphic_34 + + + + + + + Group_31 + + Graphic_32 + + + + + + + Group_29 + + Graphic_30 + + + + + + + Group_27 + + Graphic_28 + + + + + + + Group_25 + + Graphic_26 + + + + + + + Group_23 + + Graphic_24 + + + + + + + Group_21 + + Graphic_22 + + + + + + + Group_19 + + Graphic_20 + + + + + + + Group_17 + + Graphic_18 + + + + + + + Group_15 + + Graphic_16 + + + + + + + Group_13 + + Graphic_14 + + + + + + + Group_11 + + Graphic_12 + + + + + + + Group_9 + + Graphic_10 + + + + + + + Group_6 + + Graphic_8 + + + + + + Graphic_7 + + + + + + + Group_4 + + Graphic_5 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pix/monologo.png b/pix/monologo.png new file mode 100644 index 0000000..f4d1a10 Binary files /dev/null and b/pix/monologo.png differ diff --git a/pix/monologo.svg b/pix/monologo.svg new file mode 100644 index 0000000..a67d21a --- /dev/null +++ b/pix/monologo.svg @@ -0,0 +1,105 @@ + + + + + + Canvas 1 + + Layer 1 + + Canvas_1_Layer_1 + + Graphic_3 + + Bézier + + + + + Graphic_6 + + Bézier + + + + + Graphic_8 + + + + + + Graphic_20 + + + + + + Graphic_19 + + + + Graphic_18 + + + + Graphic_17 + + + + + + Graphic_16 + + + + + + Graphic_15 + + + + + + Graphic_14 + + + + + + Graphic_13 + + + + + + Graphic_12 + + + + + + + + Graphic_11 + + + + + + + + + + + + + + + + + + + + + diff --git a/styles.css b/styles.css index 0f7c8a1..22ec8ce 100644 --- a/styles.css +++ b/styles.css @@ -122,6 +122,7 @@ .path-mod-margic .annotated_temp { background-color: yellow; cursor: pointer; + -webkit-print-color-adjust: exact; } .path-mod-margic .hovered .annotated, @@ -225,7 +226,9 @@ display: none; } - .path-mod-margic #page { + .path-mod-margic #page, + .path-mod-margic #page.drawers, + .path-mod-margic #page.drawers .main-inner { margin-top: 0; } diff --git a/templates/margic_childentry.mustache b/templates/margic_childentry.mustache index 664e9d2..e60ae70 100644 --- a/templates/margic_childentry.mustache +++ b/templates/margic_childentry.mustache @@ -41,7 +41,7 @@ {{#userdate}}{{timecreated}} ,{{#str}} strftimedatetimeshort, core_langconfig {{/str}}{{/userdate}} {{#canmanageentries}}{{^singleuser}}{{/singleuser}}{{/canmanageentries}} {{^edittimehasended}}{{#caneditentries}}{{#entrycanbeedited}}{{/entrycanbeedited}}{{/caneditentries}}{{/edittimehasended}} - {{#newestentry}}{{/newestentry}} + {{#newestentry}}{{/newestentry}} {{#text}} @@ -67,8 +67,8 @@
{{type}} - - + +
@@ -88,7 +88,7 @@ {{^text}}-{{/text}} {{#canbeedited}} - + {{/canbeedited}}
diff --git a/templates/margic_entry.mustache b/templates/margic_entry.mustache index faf13e3..cc1b599 100644 --- a/templates/margic_entry.mustache +++ b/templates/margic_entry.mustache @@ -96,8 +96,8 @@
{{type}} - - + +
@@ -117,7 +117,7 @@ {{^text}}-{{/text}} {{#canbeedited}} - + {{/canbeedited}}
diff --git a/templates/margic_view.mustache b/templates/margic_view.mustache index f6c7102..c683fc4 100644 --- a/templates/margic_view.mustache +++ b/templates/margic_view.mustache @@ -42,8 +42,8 @@ }
- {{#edittimehasended}}{{#edittimeends}}{{/edittimeends}}{{/edittimehasended}} {{#edittimenotstarted}}{{#edittimestarts}}{{/edittimestarts}}{{/edittimenotstarted}} + {{#edittimehasended}}{{#edittimeends}}{{/edittimeends}}{{/edittimehasended}}

{{#str}}overview, mod_margic{{/str}}

@@ -56,7 +56,7 @@ {{/currentuserrating}} {{/entries.0}}{{/ratingaggregationmode}} {{^ratingaggregationmode}}{{#str}}norating, mod_margic{{/str}}{{/ratingaggregationmode}} - {{#entries.0}} + {{#entries.0}} {{#str}}sorting, mod_margic{{/str}}{{#sortmode}}: {{sortmode}}{{/sortmode}} {{/entries.0}}
@@ -76,7 +76,7 @@ {{/entries.0}} {{#str}}errorsummary, mod_margic{{/str}} - {{#entries.0}} + {{#entries.0}} @@ -123,7 +123,7 @@

{{#str}} annotations, mod_margic {{/str}} {{#annotationmode}} - + {{/annotationmode}}

{{#str}}annotationsarefetched, mod_margic{{/str}}
diff --git a/version.php b/version.php index 7c0d616..672a2b3 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'mod_margic'; -$plugin->release = '1.2.8'; // User-friendly version number. -$plugin->version = 2023030700; // The current module version (Date: YYYYMMDDXX). +$plugin->release = '1.2.9'; // User-friendly version number. +$plugin->version = 2023050400; // The current module version (Date: YYYYMMDDXX). $plugin->requires = 2020061507; // Requires Moodle 3.9. $plugin->maturity = MATURITY_STABLE;