diff --git a/.github/workflows/run-build.yml b/.github/workflows/run-build.yml new file mode 100644 index 00000000..92f51258 --- /dev/null +++ b/.github/workflows/run-build.yml @@ -0,0 +1,28 @@ +name: Run Build + +on: + push: + pull_request: + branches: + - master + +jobs: + test: + runs-on: ubuntu-latest + + defaults: + run: + working-directory: . + + strategy: + matrix: + node-version: [20.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci --legacy-peer-deps + - run: ./build.sh --force diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-ci-scripts.yml similarity index 91% rename from .github/workflows/run-tests.yml rename to .github/workflows/run-ci-scripts.yml index c5da2324..0c3a889c 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-ci-scripts.yml @@ -1,4 +1,4 @@ -name: CI +name: Run CI Scripts on: push: @@ -26,4 +26,4 @@ jobs: node-version: ${{ matrix.node-version }} - run: sudo apt install -y xsltproc - run: npm ci --legacy-peer-deps - - run: npm run build-ci + - run: npm run git-ci diff --git a/README.md b/README.md index 29f0f145..31cd6154 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ The following table shows the compatibility between the test harness and the CHT | 2.x | 3.9.x+ | `3.9`, `3.10`, `3.11`, `3.12`, `3.13`, `3.14` | | 3.x | 4.0.x-4.5.x | `4.0` | | 4.x | 4.6.x+ | `4.6` | +| 5.x | 4.11.x+ | `4.11` | ## Contributing diff --git a/build.sh b/build.sh index bf948e50..d1adccc4 100755 --- a/build.sh +++ b/build.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash declare -A cht_versions=( - ["cht-core-4-6"]="4.6.0" + ["cht-core-4-11"]="4.11.0" ) exit_on_error() { @@ -21,20 +21,19 @@ fi npm ci --legacy-peer-deps rm -Rf build -if [[ 1 == "$FORCE" ]]; then - rm -Rf dist -else - for item in `ls dist | grep -v cht-core`; do - rm -rf dist/"$item" - done -fi +for item in `ls dist | grep -v cht-core`; do + rm -rf dist/"$item" +done for version in "${!cht_versions[@]}"; do if [[ ! 1 == "$FORCE" ]] && [ -d dist/"$version" ]; then - printf "\033[0;32m== SKIPPING $version ==\n" + printf "\033[0;32m== SKIPPING $version ==\033[0m\n" continue fi + printf "\033[0;32m== BUILDING $version ==\033[0m\n" + rm -rf dist/"$version" + git clone https://github.com/medic/cht-core.git build/"$version" (cd build/"$version" && git reset --hard "${cht_versions[$version]}") (cd build/"$version" && git clean -df) @@ -43,7 +42,6 @@ for version in "${!cht_versions[@]}"; do node ./compile-ddocs.js "$version" - (cd build/"$version"/api && npm ci --legacy-peer-deps --production) (cd build/"$version" && patch -f api/src/services/generate-xform.js < ../../patches/generate-xform.patch) # 210 patch to disable db-object-widget (cd build/"$version" && patch -f webapp/src/js/enketo/widgets.js < ../../patches/210-disable-db-object-widgets.patch) @@ -58,4 +56,4 @@ for version in "${!cht_versions[@]}"; do done npx webpack -printf "\033[0;32m== BUILD SUCCESSFUL ==\n" +printf "\033[0;32m== BUILD SUCCESSFUL ==\033[0m\n" diff --git a/cht-bundles/all-chts-bundle.js b/cht-bundles/all-chts-bundle.js index 40dae3a8..2b56952e 100644 --- a/cht-bundles/all-chts-bundle.js +++ b/cht-bundles/all-chts-bundle.js @@ -1,3 +1,3 @@ module.exports = { - '4.6': require('../dist/cht-core-4-6/cht-core-bundle.dev'), + '4.11': require('../dist/cht-core-4-11/cht-core-bundle.dev'), }; diff --git a/cht-bundles/cht-core-4-11/bundle.js b/cht-bundles/cht-core-4-11/bundle.js new file mode 100644 index 00000000..4b352d31 --- /dev/null +++ b/cht-bundles/cht-core-4-11/bundle.js @@ -0,0 +1,12 @@ +module.exports = { + ddocs: require('../../build/cht-core-4-11-ddocs.json'), + RegistrationUtils: require('../../build/cht-core-4-11/shared-libs/registration-utils'), + CalendarInterval: require('../../build/cht-core-4-11/shared-libs/calendar-interval'), + RulesEngineCore: require('../../build/cht-core-4-11/shared-libs/rules-engine'), + ContactTypesUtils: require('../../build/cht-core-4-11/shared-libs/contact-types-utils'), + RulesEmitter: require('../../build/cht-core-4-11/shared-libs/rules-engine/src/rules-emitter'), + nootils: require('../../build/cht-core-4-11/node_modules/cht-nootils'), + Lineage: require('../../build/cht-core-4-11/shared-libs/lineage'), + DataSource: require('../../build/cht-core-4-11/shared-libs/cht-datasource'), + convertFormXmlToXFormModel: require('../../build/cht-core-4-11/api/src/services/generate-xform.js').generate, +}; diff --git a/cht-bundles/cht-core-4-11/xsl-paths.js b/cht-bundles/cht-core-4-11/xsl-paths.js new file mode 100644 index 00000000..eabe812e --- /dev/null +++ b/cht-bundles/cht-core-4-11/xsl-paths.js @@ -0,0 +1,6 @@ +const path = require('path'); + +module.exports = { + FORM_STYLESHEET: path.join(__dirname, '../dist/cht-core-4-11/xsl/openrosa2html5form.xsl'), + MODEL_STYLESHEET: path.join(__dirname, '../dist/cht-core-4-11/enketo-transformer/xsl/openrosa2xmlmodel.xsl'), +}; diff --git a/cht-bundles/cht-core-4-6/bundle.js b/cht-bundles/cht-core-4-6/bundle.js deleted file mode 100644 index 74593b26..00000000 --- a/cht-bundles/cht-core-4-6/bundle.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - ddocs: require('../../build/cht-core-4-6-ddocs.json'), - RegistrationUtils: require('../../build/cht-core-4-6/shared-libs/registration-utils'), - CalendarInterval: require('../../build/cht-core-4-6/shared-libs/calendar-interval'), - RulesEngineCore: require('../../build/cht-core-4-6/shared-libs/rules-engine'), - RulesEmitter: require('../../build/cht-core-4-6/shared-libs/rules-engine/src/rules-emitter'), - nootils: require('../../build/cht-core-4-6/node_modules/cht-nootils'), - Lineage: require('../../build/cht-core-4-6/shared-libs/lineage'), - ChtScriptApi: require('../../build/cht-core-4-6/shared-libs/cht-script-api'), - convertFormXmlToXFormModel: require('../../build/cht-core-4-6/api/src/services/generate-xform.js').generate, -}; diff --git a/cht-bundles/cht-core-4-6/xsl-paths.js b/cht-bundles/cht-core-4-6/xsl-paths.js deleted file mode 100644 index f6eeecd6..00000000 --- a/cht-bundles/cht-core-4-6/xsl-paths.js +++ /dev/null @@ -1,6 +0,0 @@ -const path = require('path'); - -module.exports = { - FORM_STYLESHEET: path.join(__dirname, '../dist/cht-core-4-6/xsl/openrosa2html5form.xsl'), - MODEL_STYLESHEET: path.join(__dirname, '../dist/cht-core-4-6/enketo-transformer/xsl/openrosa2xmlmodel.xsl'), -}; diff --git a/cht-bundles/webpack.config.cht-core.js b/cht-bundles/webpack.config.cht-core.js index 598aeb92..31334505 100644 --- a/cht-bundles/webpack.config.cht-core.js +++ b/cht-bundles/webpack.config.cht-core.js @@ -24,6 +24,7 @@ module.exports = env => [ ], }, { + mode: 'development', entry: [ `./build/${env.cht}/build/cht-form/main.js`, `./build/${env.cht}/build/cht-form/polyfills.js`, diff --git a/dist/all-chts-bundle.dev.js b/dist/all-chts-bundle.dev.js index ddd35aab..fa0be438 100644 --- a/dist/all-chts-bundle.dev.js +++ b/dist/all-chts-bundle.dev.js @@ -8,1030 +8,732 @@ /***/ ((module, __unused_webpack_exports, __webpack_require__) => { module.exports = { - '4.6': __webpack_require__(/*! ../dist/cht-core-4-6/cht-core-bundle.dev */ "./dist/cht-core-4-6/cht-core-bundle.dev.js"), + '4.11': __webpack_require__(/*! ../dist/cht-core-4-11/cht-core-bundle.dev */ "./dist/cht-core-4-11/cht-core-bundle.dev.js"), }; /***/ }), -/***/ "./dist/cht-core-4-6/cht-core-bundle.dev.js": -/*!**************************************************!*\ - !*** ./dist/cht-core-4-6/cht-core-bundle.dev.js ***! - \**************************************************/ +/***/ "./dist/cht-core-4-11/cht-core-bundle.dev.js": +/*!***************************************************!*\ + !*** ./dist/cht-core-4-11/cht-core-bundle.dev.js ***! + \***************************************************/ /***/ ((__unused_webpack_module, exports, __webpack_require__) => { /******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ({ -/***/ "./build/cht-core-4-6-ddocs.json": -/*!***************************************!*\ - !*** ./build/cht-core-4-6-ddocs.json ***! - \***************************************/ +/***/ "./build/cht-core-4-11/api/src/enketo-transformer/markdown.js": +/*!********************************************************************!*\ + !*** ./build/cht-core-4-11/api/src/enketo-transformer/markdown.js ***! + \********************************************************************/ /***/ ((module) => { -"use strict"; -module.exports = JSON.parse('[{"views":{"contacts_by_freetext":{"map":"function(doc) {\\n var skip = [ \'_id\', \'_rev\', \'type\', \'refid\', \'geolocation\' ];\\n\\n var usedKeys = [];\\n var emitMaybe = function(key, value) {\\n if (usedKeys.indexOf(key) === -1 && // Not already used\\n key.length > 2 // Not too short\\n ) {\\n usedKeys.push(key);\\n emit([key], value);\\n }\\n };\\n\\n var emitField = function(key, value, order) {\\n if (!key || !value) {\\n return;\\n }\\n key = key.toLowerCase();\\n if (skip.indexOf(key) !== -1 || /_date$/.test(key)) {\\n return;\\n }\\n if (typeof value === \'string\') {\\n value = value.toLowerCase();\\n value.split(/\\\\s+/).forEach(function(word) {\\n emitMaybe(word, order);\\n });\\n }\\n if (typeof value === \'number\' || typeof value === \'string\') {\\n emitMaybe(key + \':\' + value, order);\\n }\\n };\\n\\n var types = [ \'district_hospital\', \'health_center\', \'clinic\', \'person\' ];\\n var idx;\\n if (doc.type === \'contact\') {\\n idx = types.indexOf(doc.contact_type);\\n if (idx === -1) {\\n idx = doc.contact_type;\\n }\\n } else {\\n idx = types.indexOf(doc.type);\\n }\\n\\n if (idx !== -1) {\\n var dead = !!doc.date_of_death;\\n var muted = !!doc.muted;\\n var order = dead + \' \' + muted + \' \' + idx + \' \' + (doc.name && doc.name.toLowerCase());\\n Object.keys(doc).forEach(function(key) {\\n emitField(key, doc[key], order);\\n });\\n }\\n}"},"contacts_by_last_visited":{"map":"function(doc) {\\n if (doc.type === \'data_record\' &&\\n doc.form &&\\n doc.fields &&\\n doc.fields.visited_contact_uuid) {\\n\\n var date = doc.fields.visited_date ? Date.parse(doc.fields.visited_date) : doc.reported_date;\\n if (typeof date !== \'number\' || isNaN(date)) {\\n date = 0;\\n }\\n // Is a visit report about a family\\n emit(doc.fields.visited_contact_uuid, date);\\n } else if (doc.type === \'contact\' ||\\n doc.type === \'clinic\' ||\\n doc.type === \'health_center\' ||\\n doc.type === \'district_hospital\' ||\\n doc.type === \'person\') {\\n // Is a contact type\\n emit(doc._id, 0);\\n }\\n}","reduce":"_stats"},"contacts_by_parent":{"map":"function(doc) {\\n if (doc.type === \'contact\' ||\\n doc.type === \'clinic\' ||\\n doc.type === \'health_center\' ||\\n doc.type === \'district_hospital\' ||\\n doc.type === \'person\') {\\n var parentId = doc.parent && doc.parent._id;\\n var type = doc.type === \'contact\' ? doc.contact_type : doc.type;\\n if (parentId) {\\n emit([parentId, type]);\\n }\\n }\\n}"},"contacts_by_phone":{"map":"function(doc) {\\n if (doc.phone) {\\n var types = [ \'contact\', \'district_hospital\', \'health_center\', \'clinic\', \'person\' ];\\n if (types.indexOf(doc.type) !== -1) {\\n emit(doc.phone);\\n }\\n }\\n}"},"contacts_by_place":{"map":"function(doc) {\\n var types = [ \'district_hospital\', \'health_center\', \'clinic\', \'person\' ];\\n var idx;\\n if (doc.type === \'contact\') {\\n idx = types.indexOf(doc.contact_type);\\n if (idx === -1) {\\n idx = doc.contact_type;\\n }\\n } else {\\n idx = types.indexOf(doc.type);\\n }\\n if (idx !== -1) {\\n var place = doc.parent;\\n var order = idx + \' \' + (doc.name && doc.name.toLowerCase());\\n while (place) {\\n if (place._id) {\\n emit([ place._id ], order);\\n }\\n place = place.parent;\\n }\\n }\\n}"},"contacts_by_reference":{"map":"function(doc) {\\n if (doc.type === \'contact\' ||\\n doc.type === \'clinic\' ||\\n doc.type === \'health_center\' ||\\n doc.type === \'district_hospital\' ||\\n doc.type === \'national_office\' ||\\n doc.type === \'person\') {\\n\\n var emitReference = function(prefix, key) {\\n emit([ prefix, String(key) ], doc.reported_date);\\n };\\n\\n if (doc.place_id) {\\n emitReference(\'shortcode\', doc.place_id);\\n }\\n if (doc.patient_id) {\\n emitReference(\'shortcode\', doc.patient_id);\\n }\\n if (doc.rc_code) {\\n // need String because rewriter wraps everything in quotes\\n // keep refid case-insenstive since data is usually coming from SMS\\n emitReference(\'external\', String(doc.rc_code).toUpperCase());\\n }\\n }\\n}"},"contacts_by_type_freetext":{"map":"function(doc) {\\n var skip = [ \'_id\', \'_rev\', \'type\', \'refid\', \'geolocation\' ];\\n\\n var usedKeys = [];\\n var emitMaybe = function(type, key, value) {\\n if (usedKeys.indexOf(key) === -1 && // Not already used\\n key.length > 2 // Not too short\\n ) {\\n usedKeys.push(key);\\n emit([ type, key ], value);\\n }\\n };\\n\\n var emitField = function(type, key, value, order) {\\n if (!key || !value) {\\n return;\\n }\\n key = key.toLowerCase();\\n if (skip.indexOf(key) !== -1 || /_date$/.test(key)) {\\n return;\\n }\\n if (typeof value === \'string\') {\\n value = value.toLowerCase();\\n value.split(/\\\\s+/).forEach(function(word) {\\n emitMaybe(type, word, order);\\n });\\n }\\n if (typeof value === \'number\' || typeof value === \'string\') {\\n emitMaybe(type, key + \':\' + value, order);\\n }\\n };\\n\\n var types = [ \'district_hospital\', \'health_center\', \'clinic\', \'person\' ];\\n var idx;\\n var type;\\n if (doc.type === \'contact\') {\\n type = doc.contact_type;\\n idx = types.indexOf(type);\\n if (idx === -1) {\\n idx = type;\\n }\\n } else {\\n type = doc.type;\\n idx = types.indexOf(type);\\n }\\n if (idx !== -1) {\\n var dead = !!doc.date_of_death;\\n var muted = !!doc.muted;\\n var order = dead + \' \' + muted + \' \' + idx + \' \' + (doc.name && doc.name.toLowerCase());\\n Object.keys(doc).forEach(function(key) {\\n emitField(type, key, doc[key], order);\\n });\\n }\\n}"},"contacts_by_type":{"map":"function(doc) {\\n var types = [ \'district_hospital\', \'health_center\', \'clinic\', \'person\' ];\\n var idx;\\n var type;\\n if (doc.type === \'contact\') {\\n type = doc.contact_type;\\n idx = types.indexOf(type);\\n if (idx === -1) {\\n idx = type;\\n }\\n } else {\\n type = doc.type;\\n idx = types.indexOf(type);\\n }\\n if (idx !== -1) {\\n var dead = !!doc.date_of_death;\\n var muted = !!doc.muted;\\n var order = dead + \' \' + muted + \' \' + idx + \' \' + (doc.name && doc.name.toLowerCase());\\n emit([ type ], order);\\n }\\n}"},"data_records_by_type":{"reduce":"_count","map":"function(doc) {\\n if (doc.type === \'data_record\') {\\n emit(doc.form ? \'report\' : \'message\');\\n }\\n}"},"doc_by_type":{"map":"function(doc) {\\n if (doc.type === \'translations\') {\\n emit([ \'translations\', doc.enabled ], {\\n code: doc.code,\\n name: doc.name\\n });\\n return;\\n }\\n emit([ doc.type ]);\\n}"},"docs_by_id_lineage":{"map":"function(doc) {\\n\\n var emitLineage = function(contact, depth) {\\n while (contact && contact._id) {\\n emit([ doc._id, depth++ ], { _id: contact._id });\\n contact = contact.parent;\\n }\\n };\\n\\n var types = [ \'contact\', \'district_hospital\', \'health_center\', \'clinic\', \'person\' ];\\n\\n if (types.indexOf(doc.type) !== -1) {\\n // contact\\n emitLineage(doc, 0);\\n } else if (doc.type === \'data_record\' && doc.form) {\\n // report\\n emit([ doc._id, 0 ]);\\n emitLineage(doc.contact, 1);\\n }\\n}"},"messages_by_contact_date":{"map":"function(doc) {\\n\\n var emitMessage = function(doc, contact, phone) {\\n var id = (contact && contact._id) || phone || doc._id;\\n emit([ id, doc.reported_date ], {\\n id: doc._id,\\n date: doc.reported_date,\\n contact: contact && contact._id\\n });\\n };\\n\\n if (doc.type === \'data_record\' && !doc.form) {\\n if (doc.kujua_message && doc.tasks) {\\n // outgoing\\n doc.tasks.forEach(function(task) {\\n var message = task.messages && task.messages[0];\\n if(message) {\\n emitMessage(doc, message.contact, message.to);\\n }\\n });\\n } else if (doc.sms_message) {\\n // incoming\\n emitMessage(doc, doc.contact, doc.from);\\n }\\n }\\n}","reduce":"function(key, values) {\\n var latest = { date: 0 };\\n values.forEach(function(value) {\\n if (value.date > latest.date) {\\n latest = value;\\n }\\n });\\n return latest;\\n}"},"registered_patients":{"map":"// NB: This returns *registrations* for contacts. If contacts are created by\\n// means other then sending in a registration report (eg created in the UI)\\n// they will not show up in this view.\\n//\\n// For a view with all patients by their shortcode, use:\\n// medic/docs_by_shortcode\\nfunction(doc) {\\n var patientId = doc.patient_id || (doc.fields && doc.fields.patient_id);\\n var placeId = doc.place_id || (doc.fields && doc.fields.place_id);\\n\\n if (!doc.form || doc.type !== \'data_record\' || (doc.errors && doc.errors.length)) {\\n return;\\n }\\n\\n if (patientId) {\\n emit(String(patientId));\\n }\\n\\n if (placeId) {\\n emit(String(placeId));\\n }\\n}"},"reports_by_form":{"map":"function(doc) {\\n if (doc.type === \'data_record\' && doc.form) {\\n emit([doc.form], doc.reported_date);\\n }\\n}","reduce":"function() {\\n return true;\\n}"},"reports_by_date":{"map":"function(doc) {\\n if (doc.type === \'data_record\' && doc.form) {\\n emit([doc.reported_date], doc.reported_date);\\n }\\n}"},"reports_by_freetext":{"map":"function(doc) {\\n var skip = [ \'_id\', \'_rev\', \'type\', \'refid\', \'content\' ];\\n\\n var usedKeys = [];\\n var emitMaybe = function(key, value) {\\n if (usedKeys.indexOf(key) === -1 && // Not already used\\n key.length > 2 // Not too short\\n ) {\\n usedKeys.push(key);\\n emit([key], value);\\n }\\n };\\n\\n var emitField = function(key, value, reportedDate) {\\n if (!key || !value) {\\n return;\\n }\\n key = key.toLowerCase();\\n if (skip.indexOf(key) !== -1 || /_date$/.test(key)) {\\n return;\\n }\\n if (typeof value === \'string\') {\\n value = value.toLowerCase();\\n value.split(/\\\\s+/).forEach(function(word) {\\n emitMaybe(word, reportedDate);\\n });\\n }\\n if (typeof value === \'number\' || typeof value === \'string\') {\\n emitMaybe(key + \':\' + value, reportedDate);\\n }\\n };\\n\\n if (doc.type === \'data_record\' && doc.form) {\\n Object.keys(doc).forEach(function(key) {\\n emitField(key, doc[key], doc.reported_date);\\n });\\n if (doc.fields) {\\n Object.keys(doc.fields).forEach(function(key) {\\n emitField(key, doc.fields[key], doc.reported_date);\\n });\\n }\\n if (doc.contact && doc.contact._id) {\\n emitMaybe(\'contact:\' + doc.contact._id.toLowerCase(), doc.reported_date);\\n }\\n }\\n}"},"reports_by_place":{"map":"function(doc) {\\n if (doc.type === \'data_record\' && doc.form) {\\n var place = doc.contact && doc.contact.parent;\\n while (place) {\\n if (place._id) {\\n emit([ place._id ], doc.reported_date);\\n }\\n place = place.parent;\\n }\\n }\\n}"},"reports_by_subject":{"map":"function(doc) {\\n if (doc.type === \'data_record\' && doc.form) {\\n var emitField = function(obj, field) {\\n if (obj[field]) {\\n emit(obj[field], doc.reported_date);\\n }\\n };\\n\\n emitField(doc, \'patient_id\');\\n emitField(doc, \'place_id\');\\n emitField(doc, \'case_id\');\\n\\n if (doc.fields) {\\n emitField(doc.fields, \'patient_id\');\\n emitField(doc.fields, \'place_id\');\\n emitField(doc.fields, \'case_id\');\\n emitField(doc.fields, \'patient_uuid\');\\n emitField(doc.fields, \'place_uuid\');\\n }\\n }\\n}"},"reports_by_validity":{"map":"function(doc) {\\n if (doc.type === \'data_record\' && doc.form) {\\n emit([!doc.errors || doc.errors.length === 0], doc.reported_date);\\n }\\n}"},"reports_by_verification":{"map":"function(doc) {\\n if (doc.type === \'data_record\' && doc.form) {\\n emit([doc.verified], doc.reported_date);\\n }\\n}"},"tasks_by_contact":{"map":"function(doc) {\\n if (doc.type === \'task\') {\\n var isTerminalState = [\'Cancelled\', \'Completed\', \'Failed\'].indexOf(doc.state) >= 0;\\n var owner = (doc.owner || \'_unassigned\');\\n\\n if (!isTerminalState) {\\n emit(\'owner-\' + owner);\\n }\\n\\n if (doc.requester) {\\n emit(\'requester-\' + doc.requester);\\n }\\n\\n emit([\'owner\', \'all\', owner], { state: doc.state });\\n }\\n}"},"total_clinics_by_facility":{"map":"function(doc) {\\n var districtId = doc.parent && doc.parent.parent && doc.parent.parent._id;\\n if (doc.type === \'clinic\' || (doc.type === \'contact\' && districtId)) {\\n var healthCenterId = doc.parent && doc.parent._id;\\n emit([ districtId, healthCenterId, doc._id, 0 ]);\\n if (doc.contact && doc.contact._id) {\\n emit([ districtId, healthCenterId, doc._id, 1 ], { _id: doc.contact._id });\\n }\\n var index = 2;\\n var parent = doc.parent;\\n while(parent) {\\n if (parent._id) {\\n emit([ districtId, healthCenterId, doc._id, index++ ], { _id: parent._id });\\n }\\n parent = parent.parent;\\n }\\n }\\n}"},"visits_by_date":{"map":"function(doc) {\\n if (doc.type === \'data_record\' &&\\n doc.form &&\\n doc.fields &&\\n doc.fields.visited_contact_uuid) {\\n\\n var visited_date = doc.fields.visited_date ? Date.parse(doc.fields.visited_date) : doc.reported_date;\\n\\n // Is a visit report about a family\\n emit(visited_date, doc.fields.visited_contact_uuid);\\n emit([doc.fields.visited_contact_uuid, visited_date]);\\n }\\n}"}},"validate_doc_update":"function(newDoc, oldDoc, userCtx) {\\n /*\\n LOCAL DOCUMENT VALIDATION\\n\\n This is for validating document structure, irrespective of authority, so it\\n can be run both on couchdb and pouchdb (where you are technically admin).\\n\\n For validations around authority check lib/validate_doc_update.js, which is\\n only run on the server.\\n */\\n\\n var _err = function(msg) {\\n throw({ forbidden: msg });\\n };\\n\\n /**\\n * Ensure that type=\'form\' documents are created with correctly formatted _id\\n * property.\\n */\\n var validateForm = function(newDoc) {\\n var id_parts = newDoc._id.split(\':\');\\n var prefix = id_parts[0];\\n var form_id = id_parts.slice(1).join(\':\');\\n if (prefix !== \'form\') {\\n _err(\'_id property must be prefixed with \\"form:\\". e.g. \\"form:registration\\"\');\\n }\\n if (!form_id) {\\n _err(\'_id property must define a value after \\"form:\\". e.g. \\"form:registration\\"\');\\n }\\n if (newDoc._id !== newDoc._id.toLowerCase()) {\\n _err(\'_id property must be lower case. e.g. \\"form:registration\\"\');\\n }\\n };\\n\\n var validateUserSettings = function(newDoc) {\\n var id_parts = newDoc._id.split(\':\');\\n var prefix = id_parts[0];\\n var username = id_parts.slice(1).join(\':\');\\n var idExample = \' e.g. \\"org.couchdb.user:sally\\"\';\\n if (prefix !== \'org.couchdb.user\') {\\n _err(\'_id must be prefixed with \\"org.couchdb.user:\\".\' + idExample);\\n }\\n if (!username) {\\n _err(\'_id must define a value after \\"org.couchdb.user:\\".\' + idExample);\\n }\\n if (newDoc._id !== newDoc._id.toLowerCase()) {\\n _err(\'_id must be lower case.\' + idExample);\\n }\\n if (typeof newDoc.name === \'undefined\' || newDoc.name !== username) {\\n _err(\'name property must be equivalent to username.\' + idExample);\\n }\\n if (newDoc.name.toLowerCase() !== username.toLowerCase()) {\\n _err(\'name must be equivalent to username\');\\n }\\n if (typeof newDoc.known !== \'undefined\' && typeof newDoc.known !== \'boolean\') {\\n _err(\'known is not a boolean.\');\\n }\\n if (typeof newDoc.roles !== \'object\') {\\n _err(\'roles is a required array\');\\n }\\n };\\n\\n if (userCtx.facility_id === newDoc._id) {\\n _err(\'You are not authorized to edit your own place\');\\n }\\n if (newDoc.type === \'form\') {\\n validateForm(newDoc);\\n }\\n if (newDoc.type === \'user-settings\') {\\n validateUserSettings(newDoc);\\n }\\n\\n log(\\n \'medic-client validate_doc_update passed for User \\"\' + userCtx.name +\\n \'\\" changing document \\"\' + newDoc._id + \'\\"\'\\n );\\n}","_id":"_design/medic-client"}]'); +// identical copy of https://github.com/enketo/enketo-transformer/blob/2.1.5/src/markdown.js +// committed because of https://github.com/medic/cht-core/issues/7771 -/***/ }), +/** + * @module markdown + */ -/***/ "./build/cht-core-4-6/api/node_modules/@colors/colors/lib/colors.js": -/*!**************************************************************************!*\ - !*** ./build/cht-core-4-6/api/node_modules/@colors/colors/lib/colors.js ***! - \**************************************************************************/ -/***/ ((module, __unused_webpack_exports, __nested_webpack_require_17509__) => { +/** + * Transforms XForm label and hint textnode content with a subset of Markdown into HTML + * + * Supported: + * - `_`, `__`, `*`, `**`, `[]()`, `#`, `##`, `###`, `####`, `#####`, + * - span tags and html-encoded span tags, + * - single-level unordered markdown lists and single-level ordered markdown lists + * - newline characters + * + * Also HTML encodes any unsupported HTML tags for safe use inside web-based clients + * + * @static + * @param {string} text - Text content of a textnode. + * @return {string} transformed text content of a textnode. + */ +function markdownToHtml(text) { + // note: in JS $ matches end of line as well as end of string, and ^ both beginning of line and string + const html = text + // html encoding of < because libXMLJs Element.text() converts html entities + .replace(//gm, '>') + // span + .replace( + /<\s?span([^/\n]*)>((?:(?!<\/).)+)<\/\s?span\s?>/gm, + _createSpan + ) + // sup + .replace( + /<\s?sup([^/\n]*)>((?:(?!<\/).)+)<\/\s?sup\s?>/gm, + _createSup + ) + // sub + .replace( + /<\s?sub([^/\n]*)>((?:(?!<\/).)+)<\/\s?sub\s?>/gm, + _createSub + ) + // "\" will be used as escape character for *, _ + .replace(/&/gm, '&') + .replace(/\\\\/gm, '&92;') + .replace(/\\\*/gm, '&42;') + .replace(/\\_/gm, '&95;') + .replace(/\\#/gm, '&35;') + // strong + .replace(/__(.*?)__/gm, '$1') + .replace(/\*\*(.*?)\*\*/gm, '$1') + // emphasis + .replace(/_([^\s][^_\n]*)_/gm, '$1') + .replace(/\*([^\s][^*\n]*)\*/gm, '$1') + // links + .replace( + /\[([^\]]*)\]\(([^)]+)\)/gm, + '$1' + ) + // headers + .replace(/^\s*(#{1,6})\s?([^#][^\n]*)(\n|$)/gm, _createHeader) + // unordered lists + .replace(/^((\*|\+|-) (.*)(\n|$))+/gm, _createUnorderedList) + // ordered lists, which have to be preceded by a newline since numbered labels are common + .replace(/(\n([0-9]+\.) (.*))+$/gm, _createOrderedList) + // newline characters followed by