Merge pull request #2753 from crazyserver/MOBILE-3749

Mobile 3749
main
Dani Palou 2021-05-11 11:49:15 +02:00 committed by GitHub
commit f1423183c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 911 additions and 582 deletions

25
.github/scripts/prepare.sh vendored 100755
View File

@ -0,0 +1,25 @@
#!/bin/bash
source "./.github/scripts/functions.sh"
BRANCH=${GITHUB_REF##*/}
if [ -z $GIT_TOKEN ] || [ -z $BRANCH ] || [ $GITHUB_REPOSITORY != 'moodlehq/moodleapp' ]; then
print_error "Env vars not correctly defined"
exit 1
fi
print_title "Run prepare scripts"
# TODO Change branch name.
git clone --depth 1 --single-branch --branch ionic5 https://$GIT_TOKEN@github.com/moodlemobile/apps-scripts.git ../scripts
cp ../scripts/*.sh scripts/
if [ ! -f scripts/prepare.sh ]; then
print_error "Prepare file not found"
exit 1
fi
print_title 'Prepare Build'
./scripts/prepare.sh
if [ $? -ne 0 ]; then
exit 1
fi

20
.github/workflows/prepare.yml vendored 100644
View File

@ -0,0 +1,20 @@
name: Prepare
on:
push:
branches: [ master, integration, ionic5, workplace, freemium-master ]
jobs:
prepare:
if: github.repository == 'moodlemobile/moodleapp'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
persist-credentials: false
fetch-depth: 0
- name: Prepare builds
env:
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
run: ./.github/scripts/prepare.sh

436
package-lock.json generated
View File

@ -2280,9 +2280,9 @@
} }
}, },
"@ionic-native/badge": { "@ionic-native/badge": {
"version": "5.30.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/badge/-/badge-5.30.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/badge/-/badge-5.33.0.tgz",
"integrity": "sha512-9bz8AsdiJVAPEGGRLihoWWkikJf38xjCBgshBpgMxlXGmaDKMQQ2PHl8EeSlzjDCZy3POLbtAQnLTfYp+zqXEg==", "integrity": "sha512-g/E2HLB53csZq5hWeyWUFj6r7CrllSPuOkV88XotIFTQFNuY7TaLtiFUVFSvWXRh0Kvu+w7TI5Hys67d/v7F/g==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
}, },
@ -2295,9 +2295,9 @@
} }
}, },
"@ionic-native/camera": { "@ionic-native/camera": {
"version": "5.29.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/camera/-/camera-5.29.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/camera/-/camera-5.33.0.tgz",
"integrity": "sha512-JOmFb2eWeh8zZWu2JlNVRbhcSvOcwiTSdoabEfGtw0ITXs0FzuRmzAQgF2PQGyPA8844wkr3T5IUhcMpYxW6UQ==", "integrity": "sha512-cLAxM4e8IrTECvlszyUe9FnUjE4PbWR99NNDwkOuB85yJ3diyKxiQhHu89Qh9A6qzHTJQLa93iknY2Q40VM/5Q==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
}, },
@ -2310,9 +2310,9 @@
} }
}, },
"@ionic-native/chooser": { "@ionic-native/chooser": {
"version": "5.29.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/chooser/-/chooser-5.29.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/chooser/-/chooser-5.33.0.tgz",
"integrity": "sha512-1/+zr+SbijWqd0FomOh83aQb8vqH2qO2CAlgX2FyjJuK4fgt3BF9GMXpzTjkd/qrHO9rbxUMFAcrQAv/HAVNiA==", "integrity": "sha512-hz2OtB8UsJpKdXZByDzGS6+U1FK8toIjKmIVCDQAuOXxjbw9dU2Ef+MSUKXOpK6MYnP56oP80JdSX9LDEFJnRA==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
}, },
@ -2325,9 +2325,9 @@
} }
}, },
"@ionic-native/clipboard": { "@ionic-native/clipboard": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/clipboard/-/clipboard-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/clipboard/-/clipboard-5.33.0.tgz",
"integrity": "sha512-RrnssToxCM0oWcDmrkzhac98+Z8BmUiWI7ME1qIdHqL2MB6sdU+a9es0vSVv2bWIdd19xdwXDTp0j97OSCSACg==", "integrity": "sha512-HFZN4tsAjFJ9Se9ik0/IOUvApL4zNPEKQAtUsjqP7vCnTsdrgS+FDMln1UDEKXCQ/dEEwU1N1ndFmPBGxjkGzQ==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
}, },
@ -2340,17 +2340,24 @@
} }
}, },
"@ionic-native/core": { "@ionic-native/core": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/core/-/core-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/core/-/core-5.33.0.tgz",
"integrity": "sha512-YmR70U+bIk1e4V36bke8HCol2XwuplAr4OTok4Jdx6MaAfJ1xR1AWt2PiQOIgM8bAq8qkVlbgsYi2rEOzKVc7A==", "integrity": "sha512-dnZHu7SadvQvliGJPndk2ohdoPvwiyBWJjsJw33BKyhadnmLlmkYKkRgPPI+i22BZofDKq4YiAfIIqhvfHU6gw==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
},
"dependencies": {
"@types/cordova": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
}
} }
}, },
"@ionic-native/device": { "@ionic-native/device": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/device/-/device-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/device/-/device-5.33.0.tgz",
"integrity": "sha512-9uPnXz4McQnrEDjGLRUJg133maPCDMRjpXSMJmeO2pizKTeeQGadfp63EDUPDCromBXzkhF7GqDxDbPCgP3TWg==", "integrity": "sha512-KYQvVsN98bGTEomI193Jf9r1vaXBFJQXuhkhwBI6lajynAlf2SIVJwthNUXnK43kNXy1cOelYxScUuKyPwqudw==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
}, },
@ -2363,49 +2370,84 @@
} }
}, },
"@ionic-native/diagnostic": { "@ionic-native/diagnostic": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/diagnostic/-/diagnostic-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/diagnostic/-/diagnostic-5.33.0.tgz",
"integrity": "sha512-Xw4+aYp8A0VFQMv4FIeggioBrB2giXXlXy2BwU69Is4vb5HZGx6bR49EM/cw7C4ar2biLtyZBdawgTWvT3Nt9w==", "integrity": "sha512-zBDv/yNMvUkXfxy17rgrGVU3+XKREXRI7cqy/DyvpjdomOnmx2pnFvhszgMEgeD03LkXXKiFOR5yUAVNgYirmg==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
},
"dependencies": {
"@types/cordova": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
}
} }
}, },
"@ionic-native/file": { "@ionic-native/file": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/file/-/file-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/file/-/file-5.33.0.tgz",
"integrity": "sha512-8Luf0uY/RX/JAzMDDYk1oWyUJ4u0JmC334HQJjFeKRouBn1AV1Zge8ODQbMhOHHpyJz2zHHRc+cZlSNv6FKd/A==", "integrity": "sha512-RisSGJl2t8JrBgw30Vn5hJYYKEDTHR9f3KbQDfe1MQg+VrZo7vnbml/vk4l55u65c1TYZDH+F6i7s9nyif1QVQ==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
},
"dependencies": {
"@types/cordova": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
}
} }
}, },
"@ionic-native/file-opener": { "@ionic-native/file-opener": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/file-opener/-/file-opener-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/file-opener/-/file-opener-5.33.0.tgz",
"integrity": "sha512-oQTfkLH5gnfDZ8TELzhy/iyYzChBKXS78/N0wQ0lB7Xe4rkJnD9J9A6dtGksIWNcEt22jOre5YgboA/mMjyh5g==", "integrity": "sha512-v7t1ndalAPYT9gvMAhxAup8tg1NPV415cfG6tzs1foPFP91UZC3MQny0tup8AgFivugT1GRdoSk1NMuK1jy8Cw==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
},
"dependencies": {
"@types/cordova": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
}
} }
}, },
"@ionic-native/file-transfer": { "@ionic-native/file-transfer": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/file-transfer/-/file-transfer-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/file-transfer/-/file-transfer-5.33.0.tgz",
"integrity": "sha512-f+BgasUIVXA1vvdl7VocEuQugVPb1WO1fv7YM/NNRZ1Mf+Ry/wdByXm3vXUzBgggy96IShtqhIPeXpl081ZtLA==", "integrity": "sha512-xDUpdJVgTBP6aEiU08iZ/pa9aMoGSMB9391dQKk+VSDs0hdbIwk+Cx4V40GIE4QF3TI06sUMdKXD2sIhVsr2QA==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
},
"dependencies": {
"@types/cordova": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
}
} }
}, },
"@ionic-native/geolocation": { "@ionic-native/geolocation": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/geolocation/-/geolocation-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/geolocation/-/geolocation-5.33.0.tgz",
"integrity": "sha512-SwzZdLhcX8aZ2oV5yInGDaCYkTV/bQMPuv5Ug1HC6kPj11BnMmHWcHdhIpXaxBYz5rtadkZJEsJRmWnFnBE3YQ==", "integrity": "sha512-oE6pi6l8/Wsv94vpJmPlPqE1LheP6cQTJrC78WYQp9WVDOOCGZnFznNoH8BlQfnfjuDx/kUARxzEJBO/gbUg1A==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
},
"dependencies": {
"@types/cordova": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
}
} }
}, },
"@ionic-native/http": { "@ionic-native/http": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/http/-/http-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/http/-/http-5.33.0.tgz",
"integrity": "sha512-b8mmUw8GIAMdbH3Ycnz8tp2vFcVO59u1a3pVcJQ2l1fttFxsAGHTM8xSyeeZPvG+tiMoSCRGCiVelG3vvVltXA==", "integrity": "sha512-dqcPu3igSLlfDUK8Mvma/6o6aq7WtKBlcR8xXwE8tVsO/r4fEB6NevLLN4S4zCr381Jy+RxIIjn3CZx5XHF6JQ==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
}, },
@ -2418,17 +2460,24 @@
} }
}, },
"@ionic-native/in-app-browser": { "@ionic-native/in-app-browser": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/in-app-browser/-/in-app-browser-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/in-app-browser/-/in-app-browser-5.33.0.tgz",
"integrity": "sha512-HxzfbbybYv8xGnS1CvCAKyRuQ9aQ7QDcA0nnRspgmDD+hU196rUzvS2F0Lnhm9G9j9+952ypgFL2ftCcpvUVxg==", "integrity": "sha512-GGIvnYHf8FgaIae9yfRzxqxj0ZAxrYhOJpsHVSq1YKjxnDpRe6ImWWaHHfaeXE5IdUUMUXVuf75VTSBN00VBvA==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
},
"dependencies": {
"@types/cordova": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
}
} }
}, },
"@ionic-native/ionic-webview": { "@ionic-native/ionic-webview": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/ionic-webview/-/ionic-webview-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/ionic-webview/-/ionic-webview-5.33.0.tgz",
"integrity": "sha512-Ex/IH/LIa+4X4yGFgo4/W00IWaVsF6KkZuwIG2s3zZQEgXU3tvcgxAOEzkNCbcDC5dXcFH0z/41twZ+YC6gu+A==", "integrity": "sha512-Jbr4M6z/Fya51F+QXuPLm/crFlZ9T6YvRM1cygGmFwZ7SgDAimYnjgSO8SEGJmvO2ZekYmFuF56qiVPSvx2wUA==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
}, },
@ -2441,25 +2490,39 @@
} }
}, },
"@ionic-native/keyboard": { "@ionic-native/keyboard": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/keyboard/-/keyboard-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/keyboard/-/keyboard-5.33.0.tgz",
"integrity": "sha512-qoTm/PClwANP08TFbEMye6z6ocEorfNZlkxOZq7cjUV2Vd2oCNOYMfT7saNUnsyv2pDlBgxs/x8XE2EDVd33qQ==", "integrity": "sha512-TIYU3LC+Yz/pcpBuHcxLThMNuN9y1E1wBu4SlmS6VlO3/D3R3At2WOg2UK3iA66G2kTJubed5haqTYZjeoA6Uw==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
},
"dependencies": {
"@types/cordova": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
}
} }
}, },
"@ionic-native/local-notifications": { "@ionic-native/local-notifications": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/local-notifications/-/local-notifications-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/local-notifications/-/local-notifications-5.33.0.tgz",
"integrity": "sha512-cJDlRbYG+r1kzFEJEu7yU9kMgyWeh/K1cdGE4n6ff9I+Dcp9pBsL5owGnNj/4dg88pErHUmPzhbvzwbnuWvzbQ==", "integrity": "sha512-ljn2uq0rFWpjG1I1qK+mPVX8T3T+09fsF8sZ9nK7uvD9YWShCVz2e2ctf8qPRFq+TWDeDqlnwXz3wCvWmbBUWw==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
},
"dependencies": {
"@types/cordova": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
}
} }
}, },
"@ionic-native/media": { "@ionic-native/media": {
"version": "5.29.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/media/-/media-5.29.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/media/-/media-5.33.0.tgz",
"integrity": "sha512-XC8MtrbeR0X0I6B0FABStc2mSAmgIQidaRjFqP4jBAElAwjZC7PHwaDyyVJUOR1Rx5Nest46hZAU6jpAPZ8+pw==", "integrity": "sha512-nUu7/FSH41j9+BqHXYVMBd3EifNsTNOufD3NEiMVpHVDWLWqG7tG5h5cFlsVACxUaP4kNpSEke2mjom6CKyjxQ==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
}, },
@ -2472,9 +2535,9 @@
} }
}, },
"@ionic-native/media-capture": { "@ionic-native/media-capture": {
"version": "5.29.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/media-capture/-/media-capture-5.29.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/media-capture/-/media-capture-5.33.0.tgz",
"integrity": "sha512-5NdTXQGbrpXLeeLbI+cGQaeNmpmOrPC9vgX4jvUT6whUdDXGZ93wLT1/eeRj208czNiqbdetjG8Dji3OJZ5MKA==", "integrity": "sha512-aQTnonVSeijpuZ9B7oYJCjIDqgFVhNdvqkx9vUoaO359BtJGpGS1rNPjEovt4DsqV7oe7TkbmBd8DMEwnB3A2g==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
}, },
@ -2487,75 +2550,131 @@
} }
}, },
"@ionic-native/network": { "@ionic-native/network": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/network/-/network-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/network/-/network-5.33.0.tgz",
"integrity": "sha512-tkYCJd9R2t2gTo+0Ds2zyOgDjggbL7lZFWJ/MN7K3KIuSLRLvfsob0IC9hblY3giwP1kPTBi62KhjcBXtotHQg==", "integrity": "sha512-J+3+rbDTMVNUspa/nV+P8a0hFKDicJK1rVIrm5aLBQdmN9n7WaqJmeMYTptyWOnZxQpktJAJDdhJTEvVmKQjjw==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
},
"dependencies": {
"@types/cordova": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
}
} }
}, },
"@ionic-native/push": { "@ionic-native/push": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/push/-/push-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/push/-/push-5.33.0.tgz",
"integrity": "sha512-vF6oxtZpJylv2PxGHKzBR/OIZhp2p4wEmWyaDNMdT2xiv6goXHvLnuSnGko5alvZqCo8qxWjYirrvLk4vzWz7g==", "integrity": "sha512-H4gOkMbOaNvi97cKKJDggWvAR1BVCUbtdLyWInA0q3cZBHWSc6KcRJGz+hcdDndCQJUnHofs1Tpw8LN3zhjYyQ==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
},
"dependencies": {
"@types/cordova": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
}
} }
}, },
"@ionic-native/qr-scanner": { "@ionic-native/qr-scanner": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/qr-scanner/-/qr-scanner-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/qr-scanner/-/qr-scanner-5.33.0.tgz",
"integrity": "sha512-TRWO/e7HsSyLtQ+ucOmBT03bnBEVz9xNLIY0TcWrkItQ4RIvtH+NW66BNJ27TVQim4/F4ToB2dkD6VSQz6BRVQ==", "integrity": "sha512-cyiQv0Rje/fN9/JYoDXP1+ILJkRkjvPtoSx4vd2FsKAuJB60U2DpQfmVEE/KMF4iceTpx4MK/kGzr2WE2AYnIg==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
},
"dependencies": {
"@types/cordova": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
}
} }
}, },
"@ionic-native/splash-screen": { "@ionic-native/splash-screen": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/splash-screen/-/splash-screen-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/splash-screen/-/splash-screen-5.33.0.tgz",
"integrity": "sha512-qhr/i320BgFNFvzNOoPe2G3bDF4YN7LjpKPp9JoP+Tr1JjWwok02gT896q5GgUxYMuTCRKVQMTkp4C3J1Q8/Qg==", "integrity": "sha512-6PHk5WJeUoc3zru6wTvUmd9DCASvRQoQq1dysYI3JCECaAJ6X0x1RbzM8dBFs3JPLfWtDn+P8eTp+KaA0Xf58w==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
},
"dependencies": {
"@types/cordova": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
}
} }
}, },
"@ionic-native/sqlite": { "@ionic-native/sqlite": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/sqlite/-/sqlite-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/sqlite/-/sqlite-5.33.0.tgz",
"integrity": "sha512-tDanTJmBwkb2UlH9vjZJKVtOyTD9TcQBjkk1oMh8PMsTdWlUVo4hrojMrL9K03X59Z8oQE0aX1C/K/3RPCPdOw==", "integrity": "sha512-JIdQJr6bcksotF/3ZMJQZo9lHgaHXvHOOK9R30mM/5Ds5fFu+rUoUf0wvyqaGzEFu94pGw2uPGnfD+WI7x7+1A==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
},
"dependencies": {
"@types/cordova": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
}
} }
}, },
"@ionic-native/status-bar": { "@ionic-native/status-bar": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/status-bar/-/status-bar-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/status-bar/-/status-bar-5.33.0.tgz",
"integrity": "sha512-efY1PGYHi6WSab1EvoLAMNnjrKtX1nQEkJHBMahHozZ0draBRkSfTjCYM8hRKQ/cD3SxZvcficBEj+js8OAsJg==", "integrity": "sha512-cnyfd42N9gGfhyDtF7wbWoacKg/jfsoJQHOHltfhS4/EjxsVu4bjkwq1YBLMcMY3OIqFDSn2aFcejHn8wVNkDQ==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
},
"dependencies": {
"@types/cordova": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
}
} }
}, },
"@ionic-native/web-intent": { "@ionic-native/web-intent": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/web-intent/-/web-intent-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/web-intent/-/web-intent-5.33.0.tgz",
"integrity": "sha512-2uZKsSmoRj5s73fln3nXDs7unazrDux7h1UcrxhlUa2qI20LML/Ye3/ZFc5Wy8GB+mQOwQtJjTQPT6Gpl4fQTw==", "integrity": "sha512-3CIAofrg9nkJQbSftFdMKYOduXy9Ra/a1Q0it6ysm9NFeAQn7iZPThSexBeZ9xiKKj/QTPZtnqAJ0MQ+4mkGag==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
},
"dependencies": {
"@types/cordova": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
}
} }
}, },
"@ionic-native/zip": { "@ionic-native/zip": {
"version": "5.28.0", "version": "5.33.0",
"resolved": "https://registry.npmjs.org/@ionic-native/zip/-/zip-5.28.0.tgz", "resolved": "https://registry.npmjs.org/@ionic-native/zip/-/zip-5.33.0.tgz",
"integrity": "sha512-K5I0TSwCEW1XrdrD3x8NjJbheWD5AEMEzRWLqVvO7IKbuDySRuINW4h6fzOWgrpiP11uNW7r2mMUqBt0TNeEPw==", "integrity": "sha512-hErISQ6/xqVErmVnnha5BR+3GWRNQIVfMTXzprEpNZxQl3iDe1UTIVQdFnMd3Nasz/naY+61Vq/y91PZ1Rrajw==",
"requires": { "requires": {
"@types/cordova": "^0.0.34" "@types/cordova": "^0.0.34"
},
"dependencies": {
"@types/cordova": {
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz",
"integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ="
}
} }
}, },
"@ionic/angular": { "@ionic/angular": {
"version": "5.6.3", "version": "5.6.6",
"resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-5.6.3.tgz", "resolved": "https://registry.npmjs.org/@ionic/angular/-/angular-5.6.6.tgz",
"integrity": "sha512-6MUQV+K0xrrdTHZle+HXIOEk5TIAsFt5r6hbhfzknfZT1IMNtoEgh1xpvoEjOpjvPa84mQo7oe6Hy4kM7TQmIQ==", "integrity": "sha512-0psh2n4Y/3sx0e2Yj4WmNcukz0nrETEiJE6Fl4CldoB8QHZcXGi0hKrPAcbGSAF0+lAJ58+Z5Gp/HQVm/MQOMA==",
"requires": { "requires": {
"@ionic/core": "5.6.3", "@ionic/core": "5.6.6",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
@ -2595,9 +2714,9 @@
} }
}, },
"@ionic/cli": { "@ionic/cli": {
"version": "6.12.3", "version": "6.14.1",
"resolved": "https://registry.npmjs.org/@ionic/cli/-/cli-6.12.3.tgz", "resolved": "https://registry.npmjs.org/@ionic/cli/-/cli-6.14.1.tgz",
"integrity": "sha512-o4gm4ZoKrlzL8Gs1XZR+kVaMYghvKX8t/XJrQKr4EN/DQAM03KNMxsfm+RV3wFTnM8dgFywHzCuiw+5hY1oxEg==", "integrity": "sha512-QGmA5hjW4PvS8o0f+ptJ2bOknu4dPKzlECFApBiR7ayDgGTDMwmbxrtNpRhDnjdsVlMAzWq8eo9l76831+DBjQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@ionic/cli-framework": "5.1.0", "@ionic/cli-framework": "5.1.0",
@ -2629,9 +2748,9 @@
}, },
"dependencies": { "dependencies": {
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@ -2698,9 +2817,9 @@
}, },
"dependencies": { "dependencies": {
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@ -2765,12 +2884,12 @@
}, },
"dependencies": { "dependencies": {
"ansi-escapes": { "ansi-escapes": {
"version": "4.3.1", "version": "4.3.2",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
"integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"type-fest": "^0.11.0" "type-fest": "^0.21.3"
} }
}, },
"ansi-regex": { "ansi-regex": {
@ -2780,9 +2899,9 @@
"dev": true "dev": true
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@ -2872,9 +2991,9 @@
} }
}, },
"rxjs": { "rxjs": {
"version": "6.6.3", "version": "6.6.7",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
"integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"tslib": "^1.9.0" "tslib": "^1.9.0"
@ -2889,9 +3008,9 @@
} }
}, },
"string-width": { "string-width": {
"version": "4.2.0", "version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"dev": true, "dev": true,
"requires": { "requires": {
"emoji-regex": "^8.0.0", "emoji-regex": "^8.0.0",
@ -2909,17 +3028,17 @@
} }
}, },
"type-fest": { "type-fest": {
"version": "0.11.0", "version": "0.21.3",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
"integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
"dev": true "dev": true
} }
} }
}, },
"@ionic/core": { "@ionic/core": {
"version": "5.6.3", "version": "5.6.6",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.6.3.tgz", "resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.6.6.tgz",
"integrity": "sha512-RPugxDcCwB5rgEh6yR2QDTzblT8BRBktsW6y+VBt62yHRzgEAENEfVyvkADz+CkGAsmZuPmC8OQC2jJrx/fJFA==", "integrity": "sha512-EbVIXOTVVPxBo7hsarBpRSFNsQ22wBFtWkKmrmliieknG5LUkf5WZBpj4EENQhzYA6c+//7/nfhcD9pWgtAofA==",
"requires": { "requires": {
"@stencil/core": "^2.4.0", "@stencil/core": "^2.4.0",
"ionicons": "^5.5.1", "ionicons": "^5.5.1",
@ -2927,9 +3046,9 @@
}, },
"dependencies": { "dependencies": {
"tslib": { "tslib": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w=="
} }
} }
}, },
@ -3155,12 +3274,6 @@
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"dev": true "dev": true
}, },
"astral-regex": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
"integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
"dev": true
},
"debug": { "debug": {
"version": "4.3.1", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
@ -3188,21 +3301,10 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true "dev": true
}, },
"slice-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
"integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
"dev": true,
"requires": {
"ansi-styles": "^4.0.0",
"astral-regex": "^2.0.0",
"is-fullwidth-code-point": "^3.0.0"
}
},
"string-width": { "string-width": {
"version": "4.2.0", "version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"dev": true, "dev": true,
"requires": { "requires": {
"emoji-regex": "^8.0.0", "emoji-regex": "^8.0.0",
@ -3870,9 +3972,9 @@
} }
}, },
"@stencil/core": { "@stencil/core": {
"version": "2.5.1", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.5.1.tgz", "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.5.2.tgz",
"integrity": "sha512-SHVX/XaMYEzZJr7ttFSU9a1GmZRMUS9l7f/hbWnKYRn4S9zl1CqGf2iR/ofJ7B+vKaHLjapCAVrzCrkciVIpXA==" "integrity": "sha512-bgjPXkSzzg1WnTgVUm6m5ZzpKt602WmA/QljODAW1xVN40OHJdbGblzF/F6MFzqv2c5Cy30CB41arc8qADIdcQ=="
}, },
"@szmarczak/http-timer": { "@szmarczak/http-timer": {
"version": "1.1.2", "version": "1.1.2",
@ -15844,9 +15946,9 @@
"dev": true "dev": true
}, },
"netmask": { "netmask": {
"version": "1.0.6", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
"integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=", "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==",
"dev": true "dev": true
}, },
"next-tick": { "next-tick": {
@ -16787,14 +16889,14 @@
} }
}, },
"pac-resolver": { "pac-resolver": {
"version": "4.1.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-4.1.0.tgz", "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-4.2.0.tgz",
"integrity": "sha512-d6lf2IrZJJ7ooVHr7BfwSjRO1yKSJMaiiWYSHcrxSIUtZrCa4KKGwcztdkZ/E9LFleJfjoi1yl+XLR7AX24nbQ==", "integrity": "sha512-rPACZdUyuxT5Io/gFKUeeZFfE5T7ve7cAkE5TUZRRfuKP0u5Hocwe48X7ZEm6mYB+bTB0Qf+xlVlA/RM/i6RCQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"degenerator": "^2.2.0", "degenerator": "^2.2.0",
"ip": "^1.1.5", "ip": "^1.1.5",
"netmask": "^1.0.6" "netmask": "^2.0.1"
} }
}, },
"package-json": { "package-json": {
@ -19951,6 +20053,25 @@
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"dev": true,
"requires": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
},
"dependencies": {
"object-inspect": {
"version": "1.10.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz",
"integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==",
"dev": true
}
}
},
"signal-exit": { "signal-exit": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
@ -21022,9 +21143,9 @@
} }
}, },
"form-data": { "form-data": {
"version": "3.0.0", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
"integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
"dev": true, "dev": true,
"requires": { "requires": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
@ -21033,9 +21154,9 @@
} }
}, },
"mime": { "mime": {
"version": "2.4.7", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.7.tgz", "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz",
"integrity": "sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA==", "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==",
"dev": true "dev": true
}, },
"ms": { "ms": {
@ -21045,10 +21166,13 @@
"dev": true "dev": true
}, },
"qs": { "qs": {
"version": "6.9.6", "version": "6.10.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz",
"integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==", "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==",
"dev": true "dev": true,
"requires": {
"side-channel": "^1.0.4"
}
}, },
"readable-stream": { "readable-stream": {
"version": "3.6.0", "version": "3.6.0",
@ -21926,9 +22050,9 @@
} }
}, },
"typescript": { "typescript": {
"version": "3.9.7", "version": "3.9.9",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz",
"integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==",
"dev": true "dev": true
}, },
"uglify-js": { "uglify-js": {

View File

@ -40,33 +40,33 @@
"@angular/platform-browser": "~10.0.0", "@angular/platform-browser": "~10.0.0",
"@angular/platform-browser-dynamic": "~10.0.0", "@angular/platform-browser-dynamic": "~10.0.0",
"@angular/router": "~10.0.0", "@angular/router": "~10.0.0",
"@ionic-native/badge": "^5.30.0", "@ionic-native/badge": "^5.33.0",
"@ionic-native/camera": "^5.29.0", "@ionic-native/camera": "^5.33.0",
"@ionic-native/chooser": "^5.29.0", "@ionic-native/chooser": "^5.33.0",
"@ionic-native/clipboard": "^5.28.0", "@ionic-native/clipboard": "^5.33.0",
"@ionic-native/core": "^5.0.0", "@ionic-native/core": "^5.33.0",
"@ionic-native/device": "^5.28.0", "@ionic-native/device": "^5.33.0",
"@ionic-native/diagnostic": "^5.28.0", "@ionic-native/diagnostic": "^5.33.0",
"@ionic-native/file": "^5.28.0", "@ionic-native/file": "^5.33.0",
"@ionic-native/file-opener": "^5.28.0", "@ionic-native/file-opener": "^5.33.0",
"@ionic-native/file-transfer": "^5.28.0", "@ionic-native/file-transfer": "^5.33.0",
"@ionic-native/geolocation": "^5.28.0", "@ionic-native/geolocation": "^5.33.0",
"@ionic-native/http": "^5.28.0", "@ionic-native/http": "^5.33.0",
"@ionic-native/in-app-browser": "^5.28.0", "@ionic-native/in-app-browser": "^5.33.0",
"@ionic-native/ionic-webview": "^5.28.0", "@ionic-native/ionic-webview": "^5.33.0",
"@ionic-native/keyboard": "^5.28.0", "@ionic-native/keyboard": "^5.33.0",
"@ionic-native/local-notifications": "^5.28.0", "@ionic-native/local-notifications": "^5.33.0",
"@ionic-native/media": "^5.29.0", "@ionic-native/media": "^5.33.0",
"@ionic-native/media-capture": "^5.29.0", "@ionic-native/media-capture": "^5.33.0",
"@ionic-native/network": "^5.28.0", "@ionic-native/network": "^5.33.0",
"@ionic-native/push": "^5.28.0", "@ionic-native/push": "^5.33.0",
"@ionic-native/qr-scanner": "^5.28.0", "@ionic-native/qr-scanner": "^5.33.0",
"@ionic-native/splash-screen": "^5.28.0", "@ionic-native/splash-screen": "^5.33.0",
"@ionic-native/sqlite": "^5.28.0", "@ionic-native/sqlite": "^5.33.0",
"@ionic-native/status-bar": "^5.0.0", "@ionic-native/status-bar": "^5.33.0",
"@ionic-native/web-intent": "^5.28.0", "@ionic-native/web-intent": "^5.33.0",
"@ionic-native/zip": "^5.28.0", "@ionic-native/zip": "^5.33.0",
"@ionic/angular": "^5.6.3", "@ionic/angular": "^5.6.6",
"@ngx-translate/core": "^13.0.0", "@ngx-translate/core": "^13.0.0",
"@ngx-translate/http-loader": "^6.0.0", "@ngx-translate/http-loader": "^6.0.0",
"@types/chart.js": "^2.9.31", "@types/chart.js": "^2.9.31",
@ -136,7 +136,7 @@
"@angular/compiler-cli": "~10.0.0", "@angular/compiler-cli": "~10.0.0",
"@angular/language-service": "~10.0.0", "@angular/language-service": "~10.0.0",
"@ionic/angular-toolkit": "^2.3.0", "@ionic/angular-toolkit": "^2.3.0",
"@ionic/cli": "^6.12.3", "@ionic/cli": "^6.14.1",
"@ionic/v4-migration-tslint": "^1.7.1", "@ionic/v4-migration-tslint": "^1.7.1",
"@types/faker": "^5.1.3", "@types/faker": "^5.1.3",
"@types/node": "^12.12.64", "@types/node": "^12.12.64",
@ -166,7 +166,7 @@
"jsonc-parser": "^2.3.1", "jsonc-parser": "^2.3.1",
"ts-jest": "^26.4.1", "ts-jest": "^26.4.1",
"ts-node": "~8.3.0", "ts-node": "~8.3.0",
"typescript": "~3.9.5" "typescript": "^3.9.9"
}, },
"engines": { "engines": {
"node": ">=12.x" "node": ">=12.x"

View File

@ -121,7 +121,7 @@
</p> </p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<!-- Criteria (not yet avalaible) --> <!-- Criteria (not yet available) -->
</ion-item-group> </ion-item-group>
<ion-item-group> <ion-item-group>
@ -147,7 +147,7 @@
</p> </p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<!-- Evidence (not yet avalaible) --> <!-- Evidence (not yet available) -->
</ion-item-group> </ion-item-group>
<!-- Endorsement --> <!-- Endorsement -->

View File

@ -347,7 +347,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
if (!newValue || this.courses.allincludinghidden.length <= 0) { if (!newValue || this.courses.allincludinghidden.length <= 0) {
this.filteredCourses = this.courses.allincludinghidden; this.filteredCourses = this.courses.allincludinghidden;
} else { } else {
// Use displayname if avalaible, or fullname if not. // Use displayname if available, or fullname if not.
if (this.courses.allincludinghidden.length > 0 && if (this.courses.allincludinghidden.length > 0 &&
typeof this.courses.allincludinghidden[0].displayname != 'undefined') { typeof this.courses.allincludinghidden[0].displayname != 'undefined') {
this.filteredCourses = this.courses.allincludinghidden.filter((course) => this.filteredCourses = this.courses.allincludinghidden.filter((course) =>

View File

@ -4,4 +4,8 @@
.core-horizontal-scroll > div { .core-horizontal-scroll > div {
@include horizontal_scroll_item(80%, 250px, 300px); @include horizontal_scroll_item(80%, 250px, 300px);
} }
.core-course-module-handler {
--inner-border-width: 0;
}
} }

View File

@ -146,8 +146,7 @@
.core-module-icon { .core-module-icon {
margin-right: 1px; margin-right: 1px;
margin-left: 1px; margin-left: 1px;
width: 16px; --size: 16px;
height: 16px;
display: inline-block; display: inline-block;
vertical-align: bottom; vertical-align: bottom;
} }

View File

@ -4,9 +4,7 @@
<ion-label>{{ 'addon.calendar.' + type + 'events' | translate}}</ion-label> <ion-label>{{ 'addon.calendar.' + type + 'events' | translate}}</ion-label>
<ion-toggle [(ngModel)]="filter[type]" (ionChange)="onChange()" slot="end"></ion-toggle> <ion-toggle [(ngModel)]="filter[type]" (ionChange)="onChange()" slot="end"></ion-toggle>
</ion-item> </ion-item>
<ion-item-divider *ngIf="filter.course || filter.category || filter.group"> <core-spacer *ngIf="filter.course || filter.category || filter.group"></core-spacer>
<ion-label></ion-label>
</ion-item-divider>
<ng-container *ngIf="filter.course || filter.category || filter.group"> <ng-container *ngIf="filter.course || filter.category || filter.group">
<ion-radio-group [(ngModel)]="courseId" (ionChange)="onChange()"> <ion-radio-group [(ngModel)]="courseId" (ionChange)="onChange()">
<ion-item class="ion-text-wrap" *ngFor="let course of courses"> <ion-item class="ion-text-wrap" *ngFor="let course of courses">

View File

@ -43,8 +43,7 @@
</ng-container> </ng-container>
&nbsp;/&nbsp; &nbsp;/&nbsp;
<span *ngFor="let ancestor of competency.competency.comppath.ancestors"> <span *ngFor="let ancestor of competency.competency.comppath.ancestors">
<a *ngIf="competency.competency.comppath.showlinks" (click)="openCompetencySummary(ancestor.id)" <a *ngIf="competency.competency.comppath.showlinks" (click)="openCompetencySummary(ancestor.id)">
class="core-clickable">
{{ ancestor.name }} {{ ancestor.name }}
</a> </a>
<ng-container *ngIf="!competency.competency.comppath.showlinks">{{ ancestor.name }}</ng-container> <ng-container *ngIf="!competency.competency.comppath.showlinks">{{ ancestor.name }}</ng-container>
@ -60,7 +59,7 @@
</div> </div>
<div *ngIf="competency.competency.hasrelatedcompetencies"> <div *ngIf="competency.competency.hasrelatedcompetencies">
<p *ngFor="let relatedcomp of competency.competency.relatedcompetencies"> <p *ngFor="let relatedcomp of competency.competency.relatedcompetencies">
<a (click)="openCompetencySummary(relatedcomp.id)" class="core-clickable"> <a (click)="openCompetencySummary(relatedcomp.id)">
{{ relatedcomp.shortname }} - {{ relatedcomp.idnumber }} {{ relatedcomp.shortname }} - {{ relatedcomp.idnumber }}
</a> </a>
</p> </p>

View File

@ -31,7 +31,7 @@
<core-loading [hideUntil]="loaded" class="core-loading-center"> <core-loading [hideUntil]="loaded" class="core-loading-center">
<!-- Description and intro attachments. --> <!-- Description and intro attachments. -->
<ion-card *ngIf="description" (click)="expandDescription($event)" class="core-clickable"> <ion-card *ngIf="description">
<ion-item class="ion-text-wrap"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<core-format-text [text]="description" [component]="component" [componentId]="componentId" maxHeight="120" <core-format-text [text]="description" [component]="component" [componentId]="componentId" maxHeight="120"
@ -119,8 +119,8 @@
<!-- Summary of submissions that need grading. --> <!-- Summary of submissions that need grading. -->
<ion-item class="ion-text-wrap" *ngIf="summary && summary.submissionsenabled && !assign.teamsubmission && showNumbers" <ion-item class="ion-text-wrap" *ngIf="summary && summary.submissionsenabled && !assign.teamsubmission && showNumbers"
[detail]="needsGradingAvalaible" [button]="needsGradingAvalaible" [detail]="needsGradingAvailable" [button]="needsGradingAvailable"
(click)="goToSubmissionList(needGrading, needsGradingAvalaible)"> (click)="goToSubmissionList(needGrading, needsGradingAvailable)">
<ion-label><h2>{{ 'addon.mod_assign.numberofsubmissionsneedgrading' | translate }}</h2></ion-label> <ion-label><h2>{{ 'addon.mod_assign.numberofsubmissionsneedgrading' | translate }}</h2></ion-label>
<ion-badge slot="end" color="primary"> <ion-badge slot="end" color="primary">
{{ summary.submissionsneedgradingcount }} {{ summary.submissionsneedgradingcount }}

View File

@ -65,7 +65,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
lateSubmissions?: string; // Message about late submissions. lateSubmissions?: string; // Message about late submissions.
showNumbers = true; // Whether to show number of submissions with each status. showNumbers = true; // Whether to show number of submissions with each status.
summary?: AddonModAssignSubmissionGradingSummary; // The grading summary. summary?: AddonModAssignSubmissionGradingSummary; // The grading summary.
needsGradingAvalaible = false; // Whether we can see the submissions that need grading. needsGradingAvailable = false; // Whether we can see the submissions that need grading.
groupInfo: CoreGroupInfo = { groupInfo: CoreGroupInfo = {
groups: [], groups: [],
@ -275,7 +275,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
this.summary = submissionStatus.gradingsummary; this.summary = submissionStatus.gradingsummary;
if (!this.summary) { if (!this.summary) {
this.needsGradingAvalaible = false; this.needsGradingAvailable = false;
return; return;
} }
@ -296,7 +296,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
} }
} }
this.needsGradingAvalaible = this.needsGradingAvailable =
(submissionStatus.gradingsummary?.submissionsneedgradingcount || 0) > 0 && (submissionStatus.gradingsummary?.submissionsneedgradingcount || 0) > 0 &&
this.currentSite!.isVersionGreaterEqualThan('3.2'); this.currentSite!.isVersionGreaterEqualThan('3.2');
} }

View File

@ -1123,7 +1123,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
) { ) {
if (this.previousAttempt && this.previousAttempt.submission!.plugins && if (this.previousAttempt && this.previousAttempt.submission!.plugins &&
this.userSubmission.status == this.statusReopened) { this.userSubmission.status == this.statusReopened) {
// Get latest attempt if avalaible. // Get latest attempt if available.
this.submissionPlugins = this.previousAttempt.submission!.plugins; this.submissionPlugins = this.previousAttempt.submission!.plugins;
} else { } else {
this.submissionPlugins = this.userSubmission.plugins!; this.submissionPlugins = this.userSubmission.plugins!;

View File

@ -849,13 +849,13 @@ export class AddonModAssignProvider {
return this.gradingOfflineEnabled[siteId]; return this.gradingOfflineEnabled[siteId];
} }
this.gradingOfflineEnabled[siteId] = await CoreGrades.isGradeItemsAvalaible(siteId); this.gradingOfflineEnabled[siteId] = await CoreGrades.isGradeItemsAvailable(siteId);
return this.gradingOfflineEnabled[siteId]; return this.gradingOfflineEnabled[siteId];
} }
/** /**
* Outcomes only can be edited if mod_assign_submit_grading_form is avalaible. * Outcomes only can be edited if mod_assign_submit_grading_form is available.
* *
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @return Promise resolved with true if outcomes edit is enabled, rejected or resolved with false otherwise. * @return Promise resolved with true if outcomes edit is enabled, rejected or resolved with false otherwise.

View File

@ -33,9 +33,7 @@
</ion-item > </ion-item >
<ng-container *ngIf="items && items.length"> <ng-container *ngIf="items && items.length">
<ng-container *ngFor="let item of items"> <ng-container *ngFor="let item of items">
<ion-item-divider *ngIf="item.typ == 'pagebreak'"> <core-spacer *ngIf="item.typ == 'pagebreak'"></core-spacer>
<ion-label></ion-label>
</ion-item-divider>
<ion-item class="ion-text-wrap" *ngIf="item.typ != 'pagebreak'" [color]="item.dependitem > 0 ? 'light' : ''"> <ion-item class="ion-text-wrap" *ngIf="item.typ != 'pagebreak'" [color]="item.dependitem > 0 ? 'light' : ''">
<ion-label> <ion-label>
<h2 *ngIf="item.name" [core-mark-required]="item.required"> <h2 *ngIf="item.name" [core-mark-required]="item.required">

View File

@ -21,9 +21,7 @@
</ion-label> </ion-label>
</ion-item> </ion-item>
<ng-container *ngFor="let item of items"> <ng-container *ngFor="let item of items">
<ion-item-divider *ngIf="item.typ == 'pagebreak'"> <core-spacer *ngIf="item.typ == 'pagebreak'"></core-spacer>
<ion-label></ion-label>
</ion-item-divider>
<ng-container *ngIf="item.typ != 'pagebreak'"> <ng-container *ngIf="item.typ != 'pagebreak'">
<ion-item class="ion-text-wrap addon-mod_feedback-item" [color]="item.dependitem > 0 ? 'light' : ''" <ion-item class="ion-text-wrap addon-mod_feedback-item" [color]="item.dependitem > 0 ? 'light' : ''"
[class.core-danger-item]="item.isEmpty || item.hasError"> [class.core-danger-item]="item.isEmpty || item.hasError">

View File

@ -125,7 +125,7 @@ export class AddonModFolderProvider {
/** /**
* Returns whether or not getFolder WS available or not. * Returns whether or not getFolder WS available or not.
* *
* @return If WS is avalaible. * @return If WS is available.
* @since 3.3 * @since 3.3
*/ */
isGetFolderWSAvailable(): boolean { isGetFolderWSAvailable(): boolean {

View File

@ -101,7 +101,7 @@
<ion-card *ngIf="sort != 'nested'"> <ion-card *ngIf="sort != 'nested'">
<ng-container *ngFor="let post of posts; first as first"> <ng-container *ngFor="let post of posts; first as first">
<ion-item-divider *ngIf="!first"><ion-label></ion-label></ion-item-divider> <core-spacer *ngIf="!first"></core-spacer>
<addon-mod-forum-post <addon-mod-forum-post
[post]="post" [courseId]="courseId" [discussionId]="discussionId" [post]="post" [courseId]="courseId" [discussionId]="discussionId"
[component]="component" [componentId]="cmId" [replyData]="replyData" [component]="component" [componentId]="cmId" [replyData]="replyData"

View File

@ -321,7 +321,7 @@ export class AddonModForumProvider {
/** /**
* Returns whether or not getDiscussionPost WS available or not. * Returns whether or not getDiscussionPost WS available or not.
* *
* @return If WS is avalaible. * @return If WS is available.
* @since 3.8 * @since 3.8
*/ */
isGetDiscussionPostAvailable(): boolean { isGetDiscussionPostAvailable(): boolean {
@ -332,7 +332,7 @@ export class AddonModForumProvider {
* Returns whether or not getDiscussionPost WS available or not. * Returns whether or not getDiscussionPost WS available or not.
* *
* @param site Site. If not defined, current site. * @param site Site. If not defined, current site.
* @return If WS is avalaible. * @return If WS is available.
* @since 3.7 * @since 3.7
*/ */
isGetDiscussionPostsAvailable(site?: CoreSite): boolean { isGetDiscussionPostsAvailable(site?: CoreSite): boolean {
@ -344,7 +344,7 @@ export class AddonModForumProvider {
/** /**
* Returns whether or not deletePost WS available or not. * Returns whether or not deletePost WS available or not.
* *
* @return If WS is avalaible. * @return If WS is available.
* @since 3.8 * @since 3.8
*/ */
isDeletePostAvailable(): boolean { isDeletePostAvailable(): boolean {
@ -354,7 +354,7 @@ export class AddonModForumProvider {
/** /**
* Returns whether or not updatePost WS available or not. * Returns whether or not updatePost WS available or not.
* *
* @return If WS is avalaible. * @return If WS is available.
* @since 3.8 * @since 3.8
*/ */
isUpdatePostAvailable(): boolean { isUpdatePostAvailable(): boolean {

View File

@ -58,7 +58,7 @@
<ion-label position="stacked">{{ 'addon.mod_lesson.enterpassword' | translate }}</ion-label> <ion-label position="stacked">{{ 'addon.mod_lesson.enterpassword' | translate }}</ion-label>
<core-show-password name="password"> <core-show-password name="password">
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}" <ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}"
[autofocus]="true" #passwordinput [clearOnEdit]="false"> core-auto-focus #passwordinput [clearOnEdit]="false">
</ion-input> </ion-input>
</core-show-password> </core-show-password>
</ion-item> </ion-item>

View File

@ -15,7 +15,7 @@
<ion-label>{{ 'addon.mod_lesson.enterpassword' | translate }}</ion-label> <ion-label>{{ 'addon.mod_lesson.enterpassword' | translate }}</ion-label>
<core-show-password name="password"> <core-show-password name="password">
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}" <ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}"
[autofocus]="true" #passwordinput [clearOnEdit]="false"> core-auto-focus #passwordinput [clearOnEdit]="false">
</ion-input> </ion-input>
</core-show-password> </core-show-password>
</ion-item> </ion-item>

View File

@ -127,7 +127,7 @@ export class AddonModPageProvider {
/** /**
* Returns whether or not getPage WS available or not. * Returns whether or not getPage WS available or not.
* *
* @return If WS is avalaible. * @return If WS is available.
* @since 3.3 * @since 3.3
*/ */
isGetPageWSAvailable(): boolean { isGetPageWSAvailable(): boolean {

View File

@ -18,7 +18,7 @@
<core-dynamic-component [component]="data.component" [data]="data.data"> <core-dynamic-component [component]="data.component" [data]="data.data">
<p class="ion-padding">Couldn't find the directive to render this access rule.</p> <p class="ion-padding">Couldn't find the directive to render this access rule.</p>
</core-dynamic-component> </core-dynamic-component>
<ion-item-divider *ngIf="!last"><ion-label></ion-label></ion-item-divider> <core-spacer *ngIf="!last"></core-spacer>
</ng-container> </ng-container>
<ion-button expand="block" type="submit" class="ion-margin"> <ion-button expand="block" type="submit" class="ion-margin">

View File

@ -198,7 +198,7 @@ export class AddonModResourceModuleHandlerService implements CoreCourseModuleHan
} }
if (options.showtype) { if (options.showtype) {
// We should take it from options.filedetails.size if avalaible but it's already translated. // We should take it from options.filedetails.size if available but it's already translated.
extra.push(CoreMimetypeUtils.getMimetypeDescription(file)); extra.push(CoreMimetypeUtils.getMimetypeDescription(file));
} }

View File

@ -21,7 +21,7 @@ export abstract class CoreAriaRoleButton<T = unknown> {
} }
/** /**
* A11y key functionallity that prevents keyDown events. * A11y key functionality that prevents keyDown events.
* *
* @param event Event. * @param event Event.
*/ */
@ -33,7 +33,7 @@ export abstract class CoreAriaRoleButton<T = unknown> {
} }
/** /**
* A11y key functionallity that translates space and enter keys to click action. * A11y key functionality that translates space and enter keys to click action.
* *
* @param event Event. * @param event Event.
*/ */
@ -47,7 +47,7 @@ export abstract class CoreAriaRoleButton<T = unknown> {
} }
/** /**
* A11y click functionallity. * A11y click functionality.
* *
* @param event Event. * @param event Event.
*/ */

View File

@ -21,7 +21,7 @@ export class CoreAriaRoleTab<T = unknown> {
} }
/** /**
* A11y key functionallity that prevents keyDown events. * A11y key functionality that prevents keyDown events.
* *
* @param e Event. * @param e Event.
*/ */
@ -39,7 +39,7 @@ export class CoreAriaRoleTab<T = unknown> {
} }
/** /**
* A11y key functionallity. * A11y key functionality.
* *
* Enter or Space: When a tab has focus, activates the tab, causing its associated panel to be displayed. * Enter or Space: When a tab has focus, activates the tab, causing its associated panel to be displayed.
* Right Arrow: When a tab has focus: Moves focus to the next tab. If focus is on the last tab, moves focus to the first tab. * Right Arrow: When a tab has focus: Moves focus to the next tab. If focus is on the last tab, moves focus to the first tab.

View File

@ -56,6 +56,7 @@ import { CoreTabsOutletComponent } from './tabs-outlet/tabs-outlet';
import { CoreTimerComponent } from './timer/timer'; import { CoreTimerComponent } from './timer/timer';
import { CoreUserAvatarComponent } from './user-avatar/user-avatar'; import { CoreUserAvatarComponent } from './user-avatar/user-avatar';
import { CoreComboboxComponent } from './combobox/combobox'; import { CoreComboboxComponent } from './combobox/combobox';
import { CoreSpacerComponent } from './spacer/spacer';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -94,6 +95,7 @@ import { CoreComboboxComponent } from './combobox/combobox';
CoreTimerComponent, CoreTimerComponent,
CoreUserAvatarComponent, CoreUserAvatarComponent,
CoreComboboxComponent, CoreComboboxComponent,
CoreSpacerComponent,
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -139,6 +141,7 @@ import { CoreComboboxComponent } from './combobox/combobox';
CoreTimerComponent, CoreTimerComponent,
CoreUserAvatarComponent, CoreUserAvatarComponent,
CoreComboboxComponent, CoreComboboxComponent,
CoreSpacerComponent,
], ],
}) })
export class CoreComponentsModule {} export class CoreComponentsModule {}

View File

@ -5,11 +5,15 @@
[attr.allowfullscreen]="allowFullscreen ? 'allowfullscreen' : null"> [attr.allowfullscreen]="allowFullscreen ? 'allowfullscreen' : null">
</iframe> </iframe>
<div [hidden]="loading" class="ion-text-center ion-text-wrap core-iframe-help"> <ion-button
<ion-button fill="clear" (click)="openIframeHelpModal()" aria-haspopup="dialog"> *ngIf="!loading"
{{ 'core.iframehelp' | translate }} color="dark" expand="block" fill="clear"
(click)="openIframeHelpModal()"
aria-haspopup="dialog"
class="core-button-as-link"
>
<ion-label>{{ 'core.iframehelp' | translate }}</ion-label>
</ion-button> </ion-button>
</div>
<span class="core-loading-spinner"> <span class="core-loading-spinner">
<ion-spinner *ngIf="loading"></ion-spinner> <ion-spinner *ngIf="loading"></ion-spinner>

View File

@ -14,7 +14,7 @@
<!-- Form to edit the file's name. --> <!-- Form to edit the file's name. -->
<ion-input type="text" name="filename" [placeholder]="'core.filename' | translate" autocapitalize="none" autocorrect="off" <ion-input type="text" name="filename" [placeholder]="'core.filename' | translate" autocapitalize="none" autocorrect="off"
(click)="$event.stopPropagation()" [autofocus]="true" [(ngModel)]="newFileName" *ngIf="editMode"> (click)="$event.stopPropagation()" core-auto-focus [(ngModel)]="newFileName" *ngIf="editMode">
</ion-input> </ion-input>
<div class="buttons" slot="end" *ngIf="manage"> <div class="buttons" slot="end" *ngIf="manage">

View File

@ -1,7 +1,7 @@
<ion-grid class="ion-no-padding ion-padding-bottom" *ngIf="previous || info || next"> <ion-grid class="ion-no-padding ion-padding-bottom" *ngIf="previous || info || next">
<ion-row> <ion-row>
<ion-col class="ion-text-start" size="4"> <ion-col class="ion-text-start" size="4">
<ion-button *ngIf="previous" class="core-navigation-bar-arrow" color="light" <ion-button *ngIf="previous" class="core-navigation-bar-arrow"
[title]="previousTitle || ('core.previous' | translate)" (click)="action?.emit(previous)"> [title]="previousTitle || ('core.previous' | translate)" (click)="action?.emit(previous)">
<ion-icon name="fas-arrow-left" [slot]="previousTitle ? 'start' : 'icon-only'" aria-hidden="true"></ion-icon> <ion-icon name="fas-arrow-left" [slot]="previousTitle ? 'start' : 'icon-only'" aria-hidden="true"></ion-icon>
<core-format-text *ngIf="previousTitle" [text]="previousTitle" [component]="component" [componentId]="componentId" <core-format-text *ngIf="previousTitle" [text]="previousTitle" [component]="component" [componentId]="componentId"

View File

@ -1,5 +1,5 @@
<form #messageForm> <form #messageForm>
<textarea class="core-send-message-input" [autofocus]="showKeyboard" [placeholder]="placeholder" rows="1" core-auto-rows <textarea class="core-send-message-input" [core-auto-focus]="showKeyboard" [placeholder]="placeholder" rows="1" core-auto-rows
[(ngModel)]="message" name="message" (onResize)="textareaResized()" (keyup.enter)="enterClicked($event)" [(ngModel)]="message" name="message" (onResize)="textareaResized()" (keyup.enter)="enterClicked($event)"
(keyup.control.enter)="enterClicked($event, 'control')" (keyup.meta.enter)="enterClicked($event, 'meta')"></textarea> (keyup.control.enter)="enterClicked($event, 'control')" (keyup.meta.enter)="enterClicked($event, 'meta')"></textarea>
<ion-button fill="clear" size="large" type="submit" [disabled]="!message || sendDisabled" <ion-button fill="clear" size="large" type="submit" [disabled]="!message || sendDisabled"

View File

@ -6,7 +6,6 @@
padding: 0 calc(var(--padding-start) / 2); padding: 0 calc(var(--padding-start) / 2);
position: absolute; position: absolute;
right: 0; right: 0;
bottom: 8px;
margin-top: 0; margin-top: 0;
margin-bottom: 0; margin-bottom: 0;
z-index: 3; z-index: 3;

View File

@ -0,0 +1,30 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component } from '@angular/core';
/**
* Component to display an empty spacer using item divider.
*
* Example usage:
* <core-spacer></core-spacer>
*/
@Component({
selector: 'core-spacer',
template: '<ion-item-divider><ion-label></ion-label></ion-item-divider>',
styles: [':host {--item-divider-min-height: 30px;}'],
})
export class CoreSpacerComponent {
}

View File

@ -1,8 +1,17 @@
<img *ngIf="avatarUrl" [src]="avatarUrl" [alt]="'core.pictureof' | translate:{$a: fullname}" core-external-content <img
onError="this.src='assets/img/user-avatar.png'" (click)="gotoProfile($event)" [attr.aria-hidden]="!linkProfile" *ngIf="avatarUrl"
[attr.role]="linkProfile ? 'button' : null" (keydown)="buttonAction.keyDown($event)" [src]="avatarUrl"
[alt]="'core.pictureof' | translate:{$a: fullname}"
core-external-content
onError="this.src='assets/img/user-avatar.png'"
(click)="gotoProfile($event)"
[attr.aria-hidden]="!linkProfile"
[attr.role]="linkProfile ? 'button' : null"
(keydown)="buttonAction.keyDown($event)"
(keyup)="buttonAction.keyUp($event)" (keyup)="buttonAction.keyUp($event)"
[attr.tabindex]="linkProfile ? 0 : null"> [attr.tabindex]="linkProfile ? 0 : null"
[class.clickable]="linkProfile"
>
<img *ngIf="!avatarUrl" src="assets/img/user-avatar.png" [alt]="'core.pictureof' | translate:{$a: fullname}" <img *ngIf="!avatarUrl" src="assets/img/user-avatar.png" [alt]="'core.pictureof' | translate:{$a: fullname}"
(click)="gotoProfile($event)" [attr.aria-hidden]="!linkProfile" [attr.role]="linkProfile ? 'button' : null" (click)="gotoProfile($event)" [attr.aria-hidden]="!linkProfile" [attr.role]="linkProfile ? 'button' : null"

View File

@ -1,6 +1,9 @@
:host { :host {
position: relative; position: relative;
.clickable {
cursor: pointer; cursor: pointer;
}
img { img {
border-radius: 50%; border-radius: 50%;
width: var(--core-avatar-size); width: var(--core-avatar-size);

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Directive, Input, OnInit, ElementRef } from '@angular/core'; import { Directive, Input, ElementRef, AfterViewInit } from '@angular/core';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
@ -20,16 +20,17 @@ import { CoreUtils } from '@services/utils/utils';
/** /**
* Directive to auto focus an element when a view is loaded. * Directive to auto focus an element when a view is loaded.
* *
* You can apply it conditionallity assigning it a boolean value: <ion-input [core-auto-focus]="{{showKeyboard}}"> * The value of the input will decide if show keyboard when focusing the element (only on Android).
* In case value is nofocus, the directive is disabled.
* *
* @deprecated since 3.9.5. ion-input now supports an [autofocus] attribute, please use that one instead. * <ion-input [core-auto-focus]="showKeyboard">
*/ */
@Directive({ @Directive({
selector: '[core-auto-focus]', selector: '[core-auto-focus]',
}) })
export class CoreAutoFocusDirective implements OnInit { export class CoreAutoFocusDirective implements AfterViewInit {
@Input('core-auto-focus') coreAutoFocus: boolean | string = true; @Input('core-auto-focus') showKeyboard: boolean | string = true;
protected element: HTMLElement; protected element: HTMLElement;
@ -38,31 +39,54 @@ export class CoreAutoFocusDirective implements OnInit {
} }
/** /**
* Component being initialized. * @inheritdoc
*/ */
ngOnInit(): void { ngAfterViewInit(): void {
this.autoFocus(); if (this.showKeyboard === 'nofocus') {
return;
}
this.setFocus();
} }
/** /**
* Function after the view is initialized. * Function to focus the element.
*
* @param retries Internal param to stop retrying then 0.
*/ */
protected autoFocus(): void { protected setFocus(retries = 10): void {
const autoFocus = CoreUtils.isTrueOrOne(this.coreAutoFocus); if (retries == 0) {
if (autoFocus) { return;
}
// Wait a bit to make sure the view is loaded. // Wait a bit to make sure the view is loaded.
setTimeout(() => { setTimeout(() => {
// If it's a ion-input or ion-textarea, search the right input to use. // If it's a ion-input or ion-textarea, search the right input to use.
let element = this.element; let element: HTMLElement | null = null;
if (this.element.tagName == 'ION-INPUT') { if (this.element.tagName == 'ION-INPUT') {
element = this.element.querySelector('input') || element; element = this.element.querySelector('input');
} else if (this.element.tagName == 'ION-TEXTAREA') { } else if (this.element.tagName == 'ION-TEXTAREA') {
element = this.element.querySelector('textarea') || element; element = this.element.querySelector('textarea');
} else {
element = this.element;
} }
CoreDomUtils.focusElement(element); if (!element) {
}, 200); this.setFocus(retries - 1);
return;
} }
const showKeyboard = this.showKeyboard === '' || CoreUtils.isTrueOrOne(this.showKeyboard);
CoreDomUtils.focusElement(element, showKeyboard);
if (element != document.activeElement) {
this.setFocus(retries - 1);
return;
}
}, 200);
} }
} }

View File

@ -57,7 +57,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
loaded = false; // If the component has been loaded. loaded = false; // If the component has been loaded.
component?: string; // Component name. component?: string; // Component name.
componentId?: number; // Component ID. componentId?: number; // Component ID.
blog?: boolean; // If blog is avalaible. blog?: boolean; // If blog is available.
// Data for context menu. // Data for context menu.
externalUrl?: string; // External URL to open in browser. externalUrl?: string; // External URL to open in browser.

View File

@ -92,7 +92,7 @@
<ion-buttons class="ion-padding core-course-section-nav-buttons safe-padding-horizontal" <ion-buttons class="ion-padding core-course-section-nav-buttons safe-padding-horizontal"
*ngIf="displaySectionSelector && sections?.length"> *ngIf="displaySectionSelector && sections?.length">
<ion-button *ngIf="previousSection" color="light" (click)="sectionChanged(previousSection)" <ion-button *ngIf="previousSection" (click)="sectionChanged(previousSection)"
[attr.aria-label]="'core.previous' | translate"> [attr.aria-label]="'core.previous' | translate">
<ion-icon name="fas-chevron-left" slot="icon-only" aria-hidden="true"></ion-icon> <ion-icon name="fas-chevron-left" slot="icon-only" aria-hidden="true"></ion-icon>
<core-format-text class="accesshide" [text]="previousSection.name" contextLevel="course" <core-format-text class="accesshide" [text]="previousSection.name" contextLevel="course"

View File

@ -1,3 +1,9 @@
<button *ngIf="completion" (click)="completionClicked($event)" [title]="completionDescription"> <img *ngIf="completion && completion.tracking !== 1" [src]="completionImage" [alt]="completionDescription">
<ion-button
fill="clear"
*ngIf="completion && completion.tracking === 1"
(click)="completionClicked($event)"
[title]="completionDescription">
<img [src]="completionImage" role="presentation" alt=""> <img [src]="completionImage" role="presentation" alt="">
</button> </ion-button>

View File

@ -1,13 +1,20 @@
:host { :host {
button { min-width: var(--a11y-min-target-size);
display: block; min-height: var(--a11y-min-target-size);
background-color: transparent; --size: 30px;
img { img {
padding: 5px; padding: 5px;
width: 30px; width: var(--size);
vertical-align: middle; vertical-align: middle;
max-width: none; max-width: none;
margin: 7px;
} }
ion-button {
--padding-top: 0;
--padding-start: 0;
--padding-end: 0;
--padding-bottom: 0;
} }
} }

View File

@ -1,22 +1,57 @@
<ion-item *ngIf="module && module.handlerData && module.visibleoncoursepage !== 0 && !module.handlerData.loading" <ng-container *ngIf="module.handlerData && module.visibleoncoursepage !== 0">
id="core-course-module-{{module.id}}" class="ion-text-wrap core-course-module-handler {{module.handlerData.class}}" <ng-container *ngIf="!module.handlerData.loading">
(click)="moduleClicked($event)" [attr.aria-label]="module.handlerData.a11yTitle" detail="false" <ion-item
[ngClass]="{'item-media': module.handlerData.icon, 'item-dimmed': module.visible === 0 || module.uservisible === false, id="core-course-module-{{module.id}}"
'core-not-clickable': !module.handlerData.action || module.uservisible === false}" class="ion-text-wrap core-course-module-handler core-module-main-item {{module.handlerData.class}}"
[button]="module.handlerData.action || module.uservisible"> (click)="moduleClicked($event)"
[attr.aria-label]="module.handlerData.a11yTitle"
[ngClass]="{
'has-module-description': module.description,
'item-media': module.handlerData.icon,
'item-dimmed': module.visible === 0 || module.uservisible === false
}"
[button]="module.handlerData.action && module.uservisible"
detail="false">
<img slot="start" *ngIf="module.handlerData.icon" [src]="module.handlerData.icon" [alt]="modNameTranslated" <img slot="start" *ngIf="module.handlerData.icon" [src]="module.handlerData.icon" [alt]="modNameTranslated"
class="core-module-icon"> class="core-module-icon">
<ion-label> <ion-label class="core-module-title">
<div class="core-module-title"> <h2>
<core-format-text [text]="module.handlerData.title" contextLevel="module" [contextInstanceId]="module.id" <core-format-text [text]="module.handlerData.title" contextLevel="module" [contextInstanceId]="module.id"
[courseId]="courseId" [attr.aria-label]="module.handlerData.a11yTitle + ', ' + modNameTranslated"> [courseId]="courseId" [attr.aria-label]="module.handlerData.a11yTitle + ', ' + modNameTranslated">
</core-format-text> </core-format-text>
</h2>
<ion-badge
*ngIf="module.handlerData.extraBadge"
[color]="module.handlerData.extraBadgeColor"
class="ion-text-wrap ion-text-start"
>
<span [innerHTML]="module.handlerData.extraBadge"></span>
</ion-badge>
<ion-badge *ngIf="module.visible === 0 && (!section || section.visible)" class="ion-text-wrap">
{{ 'core.course.hiddenfromstudents' | translate }}
</ion-badge>
<ion-badge *ngIf="module.visible !== 0 && module.isStealth" class="ion-text-wrap">
{{ 'core.course.hiddenoncoursepage' | translate }}
</ion-badge>
<div class="core-module-availabilityinfo" *ngIf="module.availabilityinfo">
<ion-badge class="ion-text-wrap">{{ 'core.restricted' | translate }}</ion-badge>
<core-format-text [text]="module.availabilityinfo" contextLevel="module" [contextInstanceId]="module.id"
[courseId]="courseId" class="ion-text-wrap">
</core-format-text>
</div>
<ion-badge *ngIf="module.completiondata?.offline" color="warning" class="ion-text-wrap">
{{ 'core.course.manualcompletionnotsynced' | translate }}
</ion-badge>
</ion-label>
<!-- Buttons. --> <!-- Buttons. -->
<div slot="end" *ngIf="module.uservisible !== false" class="buttons core-module-buttons" <div
[ngClass]="{'core-button-completion': module.completiondata}"> slot="end"
*ngIf="module.uservisible !== false"
class="buttons core-module-buttons"
[ngClass]="{'core-button-completion': module.completiondata}"
>
<!-- Module completion. --> <!-- Module completion. -->
<core-course-module-completion *ngIf="module.completiondata" [completion]="module.completiondata" <core-course-module-completion *ngIf="module.completiondata" [completion]="module.completiondata"
[moduleName]="module.name" [moduleId]="module.id" (completionChanged)="completionChanged.emit($event)"> [moduleName]="module.name" [moduleId]="module.id" (completionChanged)="completionChanged.emit($event)">
@ -37,39 +72,34 @@
</ion-button> </ion-button>
</div> </div>
</div> </div>
</div> </ion-item>
<ion-item
<div class="core-module-more-info"> *ngIf="module.description"
<ion-badge slot="end" *ngIf="module.handlerData.extraBadge" [color]="module.handlerData.extraBadgeColor" id="core-course-module-{{module.id}}-info"
class="ion-text-wrap ion-text-start"> class="ion-text-wrap core-course-module-handler core-module-module-description {{module.handlerData.class}}"
<span [innerHTML]="module.handlerData.extraBadge"></span> [ngClass]="{
</ion-badge> 'item-media': module.handlerData.icon,
<ion-badge slot="end" *ngIf="module.visible === 0 && (!section || section.visible)" class="ion-text-wrap"> 'item-dimmed': module.visible === 0 || module.uservisible === false
{{ 'core.course.hiddenfromstudents' | translate }} }"
</ion-badge> detail="false"
<ion-badge slot="end" *ngIf="module.visible !== 0 && module.isStealth" class="ion-text-wrap"> >
{{ 'core.course.hiddenoncoursepage' | translate }} <ion-label>
</ion-badge>
<div class="core-module-availabilityinfo" *ngIf="module.availabilityinfo" slot="end">
<ion-badge class="ion-text-wrap">{{ 'core.restricted' | translate }}</ion-badge>
<core-format-text [text]="module.availabilityinfo" contextLevel="module" [contextInstanceId]="module.id"
[courseId]="courseId" class="ion-text-wrap">
</core-format-text>
</div>
<ion-badge slot="end" *ngIf="module.completiondata?.offline" color="warning" class="ion-text-wrap">
{{ 'core.course.manualcompletionnotsynced' | translate }}
</ion-badge>
</div>
<core-format-text class="core-module-description" *ngIf="module.description" maxHeight="80" [text]="module.description" <core-format-text class="core-module-description" *ngIf="module.description" maxHeight="80" [text]="module.description"
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"> contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
</core-format-text> </core-format-text>
</ion-label> </ion-label>
</ion-item> </ion-item>
</ng-container>
<!-- Loading. --> <!-- Loading. -->
<ion-item *ngIf="module && module.handlerData && module.visibleoncoursepage !== 0 && module.handlerData.loading" role="status" <ion-item *ngIf="module.handlerData.loading"
class="ion-text-wrap" id="core-course-module-{{module.id}}" [attr.aria-label]="module.handlerData.a11yTitle" role="status"
[ngClass]="['core-course-module-handler', 'core-module-loading', module.handlerData.class]" detail="false"> class="ion-text-wrap"
id="core-course-module-{{module.id}}"
[attr.aria-label]="module.handlerData.a11yTitle"
[ngClass]="['core-course-module-handler', 'core-module-loading', module.handlerData.class]"
detail="false"
>
<ion-label><ion-spinner></ion-spinner></ion-label> <ion-label><ion-spinner></ion-spinner></ion-label>
</ion-item> </ion-item>
</ng-container>

View File

@ -1,45 +1,10 @@
:host { :host {
// @todo Review commented styles.
background: white; .item.core-module-main-item {
display: block; --min-height: 52px;
.item.core-course-module-handler {
align-items: flex-start;
min-height: 52px;
cursor: pointer;
// &.item .item-inner {
// @include safe-area-padding(null, 0px, null, null);
// }
// .label {
// @include margin(0, 0, 0, null);
// }
.core-module-icon {
align-items: flex-start;
width: 24px;
height: 24px;
margin-top: 11px;
} }
// &.item-ios:active, .core-module-main-item {
// &.item-ios.activated {
// background-color: $list-ios-activated-background-color;
// }
// &.item-md:active,
// &.item-md.activated {
// background-color: $list-md-activated-background-color;
// }
}
.core-module-title {
display: flex;
flex-flow: row;
align-items: flex-start;
core-format-text {
flex-grow: 2;
}
.core-module-buttons, .core-module-buttons,
.buttons.core-module-buttons { .buttons.core-module-buttons {
margin: 0; margin: 0;
@ -66,10 +31,10 @@
} }
} }
.core-module-more-info { .core-module-module-description {
// ion-badge { ion-badge {
// @include text-align('start'); text-align: start;
// } }
.core-module-availabilityinfo { .core-module-availabilityinfo {
font-size: 90%; font-size: 90%;
@ -79,86 +44,23 @@
} }
} }
.core-not-clickable {
cursor: initial;
// &:active,
// &.activated {
// background-color: $list-background-color;
// }
}
.core-module-loading { .core-module-loading {
width: 100%; width: 100%;
text-align: center; text-align: center;
padding-top: 10px; padding-top: 10px;
clear: both; clear: both;
// @include darkmode() {
// color: $core-dark-text-color;
// }
} }
// @include darkmode() { .core-module-main-item + .core-module-module-description ion-label {
// .item.core-course-module-handler { margin-top: 0px;
// background: $core-dark-item-bg-color; }
// &.item-ios:active,
// &.item-ios.activated, .core-module-main-item.has-module-description {
// &.item-md:active, --inner-border-width: 0;
// &.item-md.activated { }
// background-color: $core-dark-background-color;
// } .core-module-module-description ion-label {
// } margin-inline-start: 50px;
}
// .core-not-clickable:active,
// .core-not-clickable.activated {
// background-color: $core-dark-item-bg-color;
// }
// }
} }
// ion-app.app-root.md core-course-module {
// .core-module-description {
// @include padding(null, $label-md-margin-end, null, null);
// margin-bottom: $label-md-margin-bottom;
// .core-show-more {
// @include padding(null, $label-md-margin-end, null, null);
// }
// }
// .core-module-title core-format-text {
// padding-top: $label-md-margin-top + 3;
// }
// .button-md {
// margin-top: 8px;
// margin-bottom: 8px;
// }
// .core-module-buttons-more {
// min-height: 52px;
// min-width: 53px;
// }
// }
// ion-app.app-root.ios core-course-module {
// .core-module-description {
// @include padding(null, $label-ios-margin-end, null, null);
// margin-bottom: $label-md-margin-bottom;
// .core-show-more {
// @include padding(null, $label-ios-margin-end, null, null);
// }
// }
// .core-module-title core-format-text {
// padding-top: $label-ios-margin-top + 3;
// }
// .core-module-buttons-more {
// min-height: 53px;
// min-width: 58px;
// }
// }
// ion-app.app-root .core-course-module-handler.item [item-start] + .item-inner {
// @include margin-horizontal(4px, null);
// }

View File

@ -44,14 +44,14 @@ import {
}) })
export class CoreCourseModuleComponent implements OnInit, OnDestroy { export class CoreCourseModuleComponent implements OnInit, OnDestroy {
@Input() module?: CoreCourseModule; // The module to render. @Input() module!: CoreCourseModule; // The module to render.
@Input() courseId?: number; // The course the module belongs to. @Input() courseId?: number; // The course the module belongs to.
@Input() section?: CoreCourseSection; // The section the module belongs to. @Input() section?: CoreCourseSection; // The section the module belongs to.
// eslint-disable-next-line @angular-eslint/no-input-rename // eslint-disable-next-line @angular-eslint/no-input-rename
@Input('downloadEnabled') set enabled(value: boolean) { @Input('downloadEnabled') set enabled(value: boolean) {
this.downloadEnabled = value; this.downloadEnabled = value;
if (!this.module?.handlerData?.showDownloadButton || !this.downloadEnabled || this.statusCalculated) { if (!this.module.handlerData?.showDownloadButton || !this.downloadEnabled || this.statusCalculated) {
return; return;
} }
@ -81,10 +81,6 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
* Component being initialized. * Component being initialized.
*/ */
ngOnInit(): void { ngOnInit(): void {
if (!this.module) {
return;
}
this.courseId = this.courseId || this.module.course; this.courseId = this.courseId || this.module.course;
this.modNameTranslated = CoreCourse.translateModuleName(this.module.modname) || ''; this.modNameTranslated = CoreCourse.translateModuleName(this.module.modname) || '';
@ -125,7 +121,7 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
* @param event Click event. * @param event Click event.
*/ */
moduleClicked(event: Event): void { moduleClicked(event: Event): void {
if (this.module?.uservisible !== false && this.module?.handlerData?.action) { if (this.module.uservisible !== false && this.module.handlerData?.action) {
this.module.handlerData.action(event, this.module, this.courseId!); this.module.handlerData.action(event, this.module, this.courseId!);
} }
} }
@ -195,7 +191,7 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
this.spinner = false; this.spinner = false;
this.downloadStatus = status; this.downloadStatus = status;
this.module?.handlerData?.updateStatus?.(status); this.module.handlerData?.updateStatus?.(status);
} }
/** /**
@ -218,7 +214,7 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
// this.statusObserver?.off(); // this.statusObserver?.off();
this.module?.handlerData?.onDestroy?.(); this.module.handlerData?.onDestroy?.();
this.isDestroyed = true; this.isDestroyed = true;
} }

View File

@ -58,7 +58,7 @@
<h2>{{contact.fullname}}</h2> <h2>{{contact.fullname}}</h2>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item-divider><ion-label></ion-label></ion-item-divider> <core-spacer></core-spacer>
</ng-container> </ng-container>
<ion-item class="ion-text-wrap" *ngIf="course.customfields"> <ion-item class="ion-text-wrap" *ngIf="course.customfields">

View File

@ -20,7 +20,7 @@
type="password" type="password"
placeholder="{{ 'core.courses.password' | translate }}" placeholder="{{ 'core.courses.password' | translate }}"
[(ngModel)]="password" [(ngModel)]="password"
[autofocus]="true" core-auto-focus
[clearOnEdit]="false"> [clearOnEdit]="false">
</ion-input> </ion-input>
</core-show-password> </core-show-password>

View File

@ -39,9 +39,8 @@
</ion-label> </ion-label>
</ion-item-divider> </ion-item-divider>
<section *ngFor="let category of categories"> <section *ngFor="let category of categories">
<ion-item class="ion-text-wrap" router-direction="forward" <ion-item button class="ion-text-wrap" (click)="openCategory(category.id)" [attr.aria-label]="category.name"
[routerLink]="['/main/home/courses/categories', category.id]" detail="true">
[attr.aria-label]="category.name" detail="true">
<ion-icon name="fas-folder" slot="start" [attr.aria-label]="'core.category' | translate"></ion-icon> <ion-icon name="fas-folder" slot="start" [attr.aria-label]="'core.category' | translate"></ion-icon>
<ion-label> <ion-label>
<h2> <h2>

View File

@ -117,4 +117,13 @@ export class CoreCoursesCategoriesPage implements OnInit {
}); });
} }
/**
* Open a category.
*
* @param categoryId Category Id.
*/
openCategory(categoryId: number): void {
CoreNavigator.navigateToSitePath('courses/categories/' + categoryId);
}
} }

View File

@ -163,7 +163,7 @@ export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy {
if (!newValue || !this.courses) { if (!newValue || !this.courses) {
this.filteredCourses = this.courses; this.filteredCourses = this.courses;
} else { } else {
// Use displayname if avalaible, or fullname if not. // Use displayname if available, or fullname if not.
if (this.courses.length > 0 && typeof this.courses[0].displayname != 'undefined') { if (this.courses.length > 0 && typeof this.courses[0].displayname != 'undefined') {
this.filteredCourses = this.courses.filter((course) => course.displayname!.toLowerCase().indexOf(newValue) > -1); this.filteredCourses = this.courses.filter((course) => course.displayname!.toLowerCase().indexOf(newValue) > -1);
} else { } else {

View File

@ -526,7 +526,7 @@ export class CoreCoursesProvider {
response.courses = response.courses.filter((course) => courseIds.indexOf(course.id) >= 0); response.courses = response.courses.filter((course) => courseIds.indexOf(course.id) >= 0);
} }
// Courses will be sorted using sortorder if avalaible. // Courses will be sorted using sortorder if available.
return response.courses.sort((a, b) => { return response.courses.sort((a, b) => {
if (typeof a.sortorder == 'undefined' && typeof b.sortorder == 'undefined') { if (typeof a.sortorder == 'undefined' && typeof b.sortorder == 'undefined') {
return b.id - a.id; return b.id - a.id;

View File

@ -16,98 +16,120 @@
<div #toolbar class="core-rte-toolbar" [class.toolbar-hidden]="toolbarHidden"> <div #toolbar class="core-rte-toolbar" [class.toolbar-hidden]="toolbarHidden">
<button *ngIf="toolbarArrows" class="toolbar-arrow" [class.toolbar-arrow-hidden]="toolbarPrevHidden" <button *ngIf="toolbarArrows" class="toolbar-arrow" [class.toolbar-arrow-hidden]="toolbarPrevHidden"
(click)="toolbarPrev($event)" (mousedown)="mouseDownAction($event)" [attr.aria-label]="'core.previous' | translate"> (click)="toolbarPrev($event)" (keyup)="toolbarPrev($event)"
(mousedown)="downAction($event)" (keydown)="downAction($event)"
[attr.aria-label]="'core.previous' | translate">
<ion-icon name="fas-chevron-left" aria-hidden="true"></ion-icon> <ion-icon name="fas-chevron-left" aria-hidden="true"></ion-icon>
</button> </button>
<ion-slides [options]="slidesOpts" [dir]="direction" (ionSlideDidChange)="updateToolbarArrows()"> <ion-slides [options]="slidesOpts" [dir]="direction" (ionSlideDidChange)="updateToolbarArrows()">
<!-- https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand --> <!-- https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand -->
<ion-slide> <ion-slide>
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.strong" [title]="'core.editor.bold' | translate" <button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.strong" [title]="'core.editor.bold' | translate"
(click)="buttonAction($event, 'bold', 'strong')" (mousedown)="mouseDownAction($event)"> (click)="buttonAction($event, 'bold', 'strong')" (keyup)="buttonAction($event, 'bold', 'strong')"
(mousedown)="downAction($event)" (keydown)="downAction($event)">
<ion-icon name="fas-bold" aria-hidden="true"></ion-icon> <ion-icon name="fas-bold" aria-hidden="true"></ion-icon>
</button> </button>
</ion-slide> </ion-slide>
<ion-slide> <ion-slide>
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.em" (click)="buttonAction($event, 'italic', 'em')" <button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.em" [title]=" 'core.editor.italic' | translate"
(mousedown)="mouseDownAction($event)" [title]=" 'core.editor.italic' | translate"> (click)="buttonAction($event, 'italic', 'em')" (keyup)="buttonAction($event, 'italic', 'em')"
(mousedown)="downAction($event)" (keydown)="downAction($event)">
<ion-icon name="fas-italic" aria-hidden="true"></ion-icon> <ion-icon name="fas-italic" aria-hidden="true"></ion-icon>
</button> </button>
</ion-slide> </ion-slide>
<ion-slide> <ion-slide>
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.u" (click)="buttonAction($event, 'underline', 'u')" <button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.u" [title]="'core.editor.underline' | translate"
(mousedown)="mouseDownAction($event)" [title]="'core.editor.underline' | translate"> (click)="buttonAction($event, 'underline', 'u')" (keyup)="buttonAction($event, 'underline', 'u')"
(mousedown)="downAction($event)" (keydown)="downAction($event)"
>
<ion-icon name="fas-underline" aria-hidden="true"></ion-icon> <ion-icon name="fas-underline" aria-hidden="true"></ion-icon>
</button> </button>
</ion-slide> </ion-slide>
<ion-slide> <ion-slide>
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.strike" [title]="'core.editor.strike' | translate" <button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.strike" [title]="'core.editor.strike' | translate"
(click)="buttonAction($event, 'strikethrough', 'strike')" (mousedown)="mouseDownAction($event)"> (click)="buttonAction($event, 'strikethrough', 'strike')" (keyup)="buttonAction($event, 'strikethrough', 'strike')"
(mousedown)="downAction($event)" (keydown)="downAction($event)">
<ion-icon name="fas-strikethrough" aria-hidden="true"></ion-icon> <ion-icon name="fas-strikethrough" aria-hidden="true"></ion-icon>
</button> </button>
</ion-slide> </ion-slide>
<ion-slide> <ion-slide>
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.p" (click)="buttonAction($event, 'p', 'block')" <button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.p" [title]="'core.editor.p' | translate"
(mousedown)="mouseDownAction($event)" [title]="'core.editor.p' | translate"> (click)="buttonAction($event, 'p', 'block')" (keyup)="buttonAction($event, 'p', 'block')"
(mousedown)="downAction($event)" (keydown)="downAction($event)">
<ion-icon name="fas-paragraph" aria-hidden="true"></ion-icon> <ion-icon name="fas-paragraph" aria-hidden="true"></ion-icon>
</button> </button>
</ion-slide> </ion-slide>
<ion-slide> <ion-slide>
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.h3" (click)="buttonAction($event, 'h3', 'block')" <button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.h3" [title]="'core.editor.h3' | translate"
(mousedown)="mouseDownAction($event)" [title]="'core.editor.h3' | translate"> (click)="buttonAction($event, 'h3', 'block')" (keyup)="buttonAction($event, 'h3', 'block')"
(mousedown)="downAction($event)" (keydown)="downAction($event)" >
<ion-icon name="fas-heading" aria-hidden="true"></ion-icon><span aria-hidden="true">3</span> <ion-icon name="fas-heading" aria-hidden="true"></ion-icon><span aria-hidden="true">3</span>
</button> </button>
</ion-slide> </ion-slide>
<ion-slide> <ion-slide>
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.h4" (click)="buttonAction($event, 'h4', 'block')" <button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.h4" [title]="'core.editor.h4' | translate"
(mousedown)="mouseDownAction($event)" [title]="'core.editor.h4' | translate"> (click)="buttonAction($event, 'h4', 'block')" (keyup)="buttonAction($event, 'h4', 'block')"
(mousedown)="downAction($event)" (keydown)="downAction($event)">
<ion-icon name="fas-heading" aria-hidden="true"></ion-icon><span aria-hidden="true">4</span> <ion-icon name="fas-heading" aria-hidden="true"></ion-icon><span aria-hidden="true">4</span>
</button> </button>
</ion-slide> </ion-slide>
<ion-slide> <ion-slide>
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.h5" (click)="buttonAction($event, 'h5', 'block')" <button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.h5" [title]="'core.editor.h5' | translate"
(mousedown)="mouseDownAction($event)" [title]="'core.editor.h5' | translate"> (click)="buttonAction($event, 'h5', 'block')" (keyup)="buttonAction($event, 'h5', 'block')"
(mousedown)="downAction($event)" (keydown)="downAction($event)">
<ion-icon name="fas-heading" aria-hidden="true"></ion-icon><span aria-hidden="true">5</span> <ion-icon name="fas-heading" aria-hidden="true"></ion-icon><span aria-hidden="true">5</span>
</button> </button>
</ion-slide> </ion-slide>
<ion-slide> <ion-slide>
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.ul" (mousedown)="mouseDownAction($event)" <button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.ul"
(click)="buttonAction($event, 'insertUnorderedList')" [title]="'core.editor.unorderedlist' | translate"> [title]="'core.editor.unorderedlist' | translate"
(click)="buttonAction($event, 'insertUnorderedList')" (click)="buttonAction($event, 'insertUnorderedList')"
(mousedown)="downAction($event)" (keydown)="downAction($event)">
<ion-icon name="fas-list-ul" aria-hidden="true"></ion-icon> <ion-icon name="fas-list-ul" aria-hidden="true"></ion-icon>
</button> </button>
</ion-slide> </ion-slide>
<ion-slide> <ion-slide>
<button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.ol" (mousedown)="mouseDownAction($event)" <button [disabled]="!rteEnabled" [attr.aria-pressed]="toolbarStyles.ol" [title]="'core.editor.orderedlist' | translate"
(click)="buttonAction($event, 'insertOrderedList')" [title]="'core.editor.orderedlist' | translate"> (click)="buttonAction($event, 'insertOrderedList')" (keyup)="buttonAction($event, 'insertOrderedList')"
(mousedown)="downAction($event)" (keydown)="downAction($event)">
<ion-icon name="fas-list-ol" aria-hidden="true"></ion-icon> <ion-icon name="fas-list-ol" aria-hidden="true"></ion-icon>
</button> </button>
</ion-slide> </ion-slide>
<ion-slide> <ion-slide>
<button [disabled]="!rteEnabled" (click)="buttonAction($event, 'removeFormat')" (mousedown)="mouseDownAction($event)" <button [disabled]="!rteEnabled"
(click)="buttonAction($event, 'removeFormat')" (keyup)="buttonAction($event, 'removeFormat')"
(mousedown)="downAction($event)" (keydown)="downAction($event)"
[title]="'core.editor.clear' | translate"> [title]="'core.editor.clear' | translate">
<ion-icon name="fas-eraser" aria-hidden="true"></ion-icon> <ion-icon name="fas-eraser" aria-hidden="true"></ion-icon>
</button> </button>
</ion-slide> </ion-slide>
<ion-slide *ngIf="canScanQR"> <ion-slide *ngIf="canScanQR">
<button [disabled]="!rteEnabled" (click)="scanQR($event)" (mousedown)="stopBubble($event)" <button [disabled]="!rteEnabled"
(click)="scanQR($event)" (keyup)="scanQR($event)"
(mousedown)="stopBubble($event)" (keydown)="stopBubble($event)"
[title]="'core.scanqr' | translate"> [title]="'core.scanqr' | translate">
<ion-icon name="fas-qrcode" aria-hidden="true"></ion-icon> <ion-icon name="fas-qrcode" aria-hidden="true"></ion-icon>
</button> </button>
</ion-slide> </ion-slide>
<ion-slide> <ion-slide>
<button [attr.aria-pressed]="!rteEnabled" (click)="toggleEditor($event)" (mousedown)="mouseDownAction($event)" <button [attr.aria-pressed]="!rteEnabled" [title]=" 'core.editor.toggle' | translate"
[title]=" 'core.editor.toggle' | translate"> (click)="toggleEditor($event)" (keyup)="toggleEditor($event)"
(mousedown)="downAction($event)" (keydown)="downAction($event)">
<ion-icon name="fas-code" aria-hidden="true"></ion-icon> <ion-icon name="fas-code" aria-hidden="true"></ion-icon>
</button> </button>
</ion-slide> </ion-slide>
<ion-slide *ngIf="isPhone"> <ion-slide *ngIf="isPhone">
<button (click)="hideToolbar($event)" (mousedown)="mouseDownAction($event)" <button [title]=" 'core.editor.hidetoolbar' | translate"
[title]=" 'core.editor.hidetoolbar' | translate"> (click)="hideToolbar($event)" (keyup)="hideToolbar($event)"
(mousedown)="downAction($event)" (keydown)="downAction($event)">
<ion-icon name="fas-times" aria-hidden="true"></ion-icon> <ion-icon name="fas-times" aria-hidden="true"></ion-icon>
</button> </button>
</ion-slide> </ion-slide>
</ion-slides> </ion-slides>
<button *ngIf="toolbarArrows" class="toolbar-arrow" [class.toolbar-arrow-hidden]="toolbarNextHidden" <button *ngIf="toolbarArrows" class="toolbar-arrow" [class.toolbar-arrow-hidden]="toolbarNextHidden"
(click)="toolbarNext($event)" (mousedown)="mouseDownAction($event)" [attr.aria-label]="'core.next' | translate"> [attr.aria-label]="'core.next' | translate"
(click)="toolbarNext($event)" (keyup)="toolbarNext($event)"
(mousedown)="downAction($event)" (keydown)="downAction($event)" >
<ion-icon name="fas-chevron-right" aria-hidden="true"></ion-icon> <ion-icon name="fas-chevron-right" aria-hidden="true"></ion-icon>
</button> </button>
</div> </div>

View File

@ -511,6 +511,10 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
* @param event The event. * @param event The event.
*/ */
async toggleEditor(event: Event): Promise<void> { async toggleEditor(event: Event): Promise<void> {
if (event.type == 'keyup' && !this.isValidKeyboardKey(<KeyboardEvent>event)) {
return;
}
this.stopBubble(event); this.stopBubble(event);
this.setContent(this.control?.value || ''); this.setContent(this.control?.value || '');
@ -654,6 +658,10 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
* toolbar styles button when set. * toolbar styles button when set.
*/ */
buttonAction(event: Event, command: string, parameters?: string): void { buttonAction(event: Event, command: string, parameters?: string): void {
if (event.type == 'keyup' && !this.isValidKeyboardKey(<KeyboardEvent>event)) {
return;
}
this.stopBubble(event); this.stopBubble(event);
if (!command) { if (!command) {
@ -725,6 +733,10 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
* Hide the toolbar in phone mode. * Hide the toolbar in phone mode.
*/ */
hideToolbar(event: Event): void { hideToolbar(event: Event): void {
if (event.type == 'keyup' && !this.isValidKeyboardKey(<KeyboardEvent>event)) {
return;
}
this.element.classList.remove('has-focus'); this.element.classList.remove('has-focus');
this.stopBubble(event); this.stopBubble(event);
@ -734,6 +746,16 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
} }
} }
/**
* Checks if Space or Enter have been pressed.
*
* @param event Keyboard Event.
* @returns Wether space or enter have been pressed.
*/
protected isValidKeyboardKey(event: KeyboardEvent): boolean {
return event.key == ' ' || event.key == 'Enter';
}
/** /**
* Show the toolbar. * Show the toolbar.
*/ */
@ -754,7 +776,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
* @param event Event. * @param event Event.
*/ */
stopBubble(event: Event): void { stopBubble(event: Event): void {
if (event.type != 'mouseup') { if (event.type != 'mouseup' && event.type != 'keyup') {
event.preventDefault(); event.preventDefault();
} }
event.stopPropagation(); event.stopPropagation();
@ -765,7 +787,11 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
* *
* @param event Event. * @param event Event.
*/ */
mouseDownAction(event: Event): void { downAction(event: Event): void {
if (event.type == 'keydown' && !this.isValidKeyboardKey(<KeyboardEvent>event)) {
return;
}
const selection = window.getSelection()?.toString(); const selection = window.getSelection()?.toString();
// When RTE is focused with a whole paragraph in desktop the stopBubble will not fire click. // When RTE is focused with a whole paragraph in desktop the stopBubble will not fire click.
@ -778,6 +804,10 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
* Method that shows the next toolbar buttons. * Method that shows the next toolbar buttons.
*/ */
async toolbarNext(event: Event): Promise<void> { async toolbarNext(event: Event): Promise<void> {
if (event.type == 'keyup' && !this.isValidKeyboardKey(<KeyboardEvent>event)) {
return;
}
this.stopBubble(event); this.stopBubble(event);
if (!this.toolbarNextHidden) { if (!this.toolbarNextHidden) {
@ -792,6 +822,10 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
* Method that shows the previous toolbar buttons. * Method that shows the previous toolbar buttons.
*/ */
async toolbarPrev(event: Event): Promise<void> { async toolbarPrev(event: Event): Promise<void> {
if (event.type == 'keyup' && !this.isValidKeyboardKey(<KeyboardEvent>event)) {
return;
}
this.stopBubble(event); this.stopBubble(event);
if (!this.toolbarPrevHidden) { if (!this.toolbarPrevHidden) {

View File

@ -471,7 +471,7 @@ export class CoreGradesHelperProvider {
} }
// Try to open the module grade directly. Check if it's possible. // Try to open the module grade directly. Check if it's possible.
const grades = await CoreGrades.isGradeItemsAvalaible(siteId); const grades = await CoreGrades.isGradeItemsAvailable(siteId);
if (!grades) { if (!grades) {
throw new CoreError('No grades found.'); throw new CoreError('No grades found.');
@ -543,7 +543,7 @@ export class CoreGradesHelperProvider {
const site = await CoreSites.getSite(siteId); const site = await CoreSites.getSite(siteId);
userId = userId || site.getUserId(); userId = userId || site.getUserId();
const enabled = await CoreGrades.isGradeItemsAvalaible(siteId); const enabled = await CoreGrades.isGradeItemsAvailable(siteId);
return enabled return enabled
? CoreGrades.invalidateCourseGradesItemsData(courseId, userId, groupId, siteId) ? CoreGrades.invalidateCourseGradesItemsData(courseId, userId, groupId, siteId)

View File

@ -87,7 +87,7 @@ export class CoreGradesProvider {
/** /**
* Get the grade items for a certain module. Keep in mind that may have more than one item to include outcomes and scales. * Get the grade items for a certain module. Keep in mind that may have more than one item to include outcomes and scales.
* Fallback function only used if 'gradereport_user_get_grade_items' WS is not avalaible Moodle < 3.2. * Fallback function only used if 'gradereport_user_get_grade_items' WS is not available Moodle < 3.2.
* *
* @param courseId ID of the course to get the grades from. * @param courseId ID of the course to get the grades from.
* @param userId ID of the user to get the grades from. If not defined use site's current user. * @param userId ID of the user to get the grades from. If not defined use site's current user.
@ -109,7 +109,7 @@ export class CoreGradesProvider {
userId = userId || site.getUserId(); userId = userId || site.getUserId();
const enabled = await this.isGradeItemsAvalaible(siteId); const enabled = await this.isGradeItemsAvailable(siteId);
if (enabled) { if (enabled) {
try { try {
@ -340,13 +340,13 @@ export class CoreGradesProvider {
} }
/** /**
* Returns whether or not WS Grade Items is avalaible. * Returns whether or not WS Grade Items is available.
* *
* @param siteId Site ID. If not defined, current site. * @param siteId Site ID. If not defined, current site.
* @return True if ws is avalaible, false otherwise. * @return True if ws is available, false otherwise.
* @since Moodle 3.2 * @since Moodle 3.2
*/ */
async isGradeItemsAvalaible(siteId?: string): Promise<boolean> { async isGradeItemsAvailable(siteId?: string): Promise<boolean> {
const site = await CoreSites.getSite(siteId); const site = await CoreSites.getSite(siteId);
return site.wsAvailable('gradereport_user_get_grade_items'); return site.wsAvailable('gradereport_user_get_grade_items');

View File

@ -7,8 +7,7 @@
<ion-title>{{ 'core.login.login' | translate }}</ion-title> <ion-title>{{ 'core.login.login' | translate }}</ion-title>
<ion-buttons slot="end"> <ion-buttons slot="end">
<ion-button fill="clear" router-direction="forward" routerLink="/settings" <ion-button fill="clear" (click)="openSettings()" [attr.aria-label]="'core.settings.appsettings' | translate">
[attr.aria-label]="'core.settings.appsettings' | translate">
<ion-icon slot="icon-only" name="fas-cog" aria-hidden="true"></ion-icon> <ion-icon slot="icon-only" name="fas-cog" aria-hidden="true"></ion-icon>
</ion-button> </ion-button>
</ion-buttons> </ion-buttons>
@ -34,7 +33,7 @@
<ion-label></ion-label> <ion-label></ion-label>
<ion-input type="text" name="username" placeholder="{{ 'core.login.username' | translate }}" <ion-input type="text" name="username" placeholder="{{ 'core.login.username' | translate }}"
formControlName="username" autocapitalize="none" autocorrect="off" autocomplete="username" enterkeyhint="next" formControlName="username" autocapitalize="none" autocorrect="off" autocomplete="username" enterkeyhint="next"
required="true"> required="true" core-auto-focus>
</ion-input> </ion-input>
</ion-item> </ion-item>
<ion-item *ngIf="siteChecked && !isBrowserSSO" class="ion-margin-bottom"> <ion-item *ngIf="siteChecked && !isBrowserSSO" class="ion-margin-bottom">
@ -63,11 +62,16 @@
</form> </form>
<!-- Forgotten password option. --> <!-- Forgotten password option. -->
<ion-list lines="none" *ngIf="showForgottenPassword" class="core-login-forgotten-password ion-no-padding"> <ion-button
<ion-item button class="ion-text-center ion-text-wrap" (click)="forgottenPassword()" detail="false"> *ngIf="showForgottenPassword"
expand="block"
fill="clear"
color="dark"
class="core-login-forgotten-password core-button-as-link"
(click)="forgottenPassword()"
>
<ion-label>{{ 'core.login.forgotten' | translate }}</ion-label> <ion-label>{{ 'core.login.forgotten' | translate }}</ion-label>
</ion-item> </ion-button>
</ion-list>
<ion-list *ngIf="identityProviders && identityProviders.length" class="ion-padding-top core-login-identity-providers"> <ion-list *ngIf="identityProviders && identityProviders.length" class="ion-padding-top core-login-identity-providers">
<ion-item class="ion-text-wrap" lines="none"> <ion-item class="ion-text-wrap" lines="none">
@ -87,8 +91,7 @@
<ion-item class="ion-text-wrap" lines="none" *ngIf="authInstructions"> <ion-item class="ion-text-wrap" lines="none" *ngIf="authInstructions">
<ion-label><p><core-format-text [text]="authInstructions" [filter]="false"></core-format-text></p></ion-label> <ion-label><p><core-format-text [text]="authInstructions" [filter]="false"></core-format-text></p></ion-label>
</ion-item> </ion-item>
<ion-button expand="block" class="ion-margin" color="light" router-direction="forward" routerLink="/login/emailsignup" <ion-button expand="block" class="ion-margin" color="light" (click)="openEmailSignup()">
[queryParams]="{siteUrl: siteUrl}">
{{ 'core.login.startsignup' | translate }} {{ 'core.login.startsignup' | translate }}
</ion-button> </ion-button>
</ion-list> </ion-list>

View File

@ -287,6 +287,20 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
} }
} }
/**
* Open email signup page.
*/
openEmailSignup(): void {
CoreNavigator.navigate('/login/emailsignup', { params: { siteUrl: this.siteUrl } });
}
/**
* Open settings page.
*/
openSettings(): void {
CoreNavigator.navigate('/settings');
}
/** /**
* View destroyed. * View destroyed.
*/ */

View File

@ -31,7 +31,7 @@
<ion-item> <ion-item>
<ion-label></ion-label> <ion-label></ion-label>
<ion-input type="text" name="value" placeholder="{{ 'core.login.usernameoremail' | translate }}" <ion-input type="text" name="value" placeholder="{{ 'core.login.usernameoremail' | translate }}"
formControlName="value" autocapitalize="none" autocorrect="off" [autofocus]="autoFocus"> formControlName="value" autocapitalize="none" autocorrect="off" [core-auto-focus]="showKeyboard">
</ion-input> </ion-input>
</ion-item> </ion-item>
<ion-button type="submit" class="ion-margin" expand="block" [disabled]="!myForm.valid"> <ion-button type="submit" class="ion-margin" expand="block" [disabled]="!myForm.valid">

View File

@ -35,7 +35,7 @@ export class CoreLoginForgottenPasswordPage implements OnInit {
myForm!: FormGroup; myForm!: FormGroup;
siteUrl!: string; siteUrl!: string;
autoFocus!: boolean; showKeyboard!: boolean;
constructor( constructor(
protected formBuilder: FormBuilder, protected formBuilder: FormBuilder,
@ -55,7 +55,7 @@ export class CoreLoginForgottenPasswordPage implements OnInit {
} }
this.siteUrl = siteUrl; this.siteUrl = siteUrl;
this.autoFocus = Platform.is('tablet'); this.showKeyboard = Platform.is('tablet');
this.myForm = this.formBuilder.group({ this.myForm = this.formBuilder.group({
field: ['username', Validators.required], field: ['username', Validators.required],
value: [CoreNavigator.getRouteParam<string>('username') || '', Validators.required], value: [CoreNavigator.getRouteParam<string>('username') || '', Validators.required],

View File

@ -7,7 +7,7 @@
<ion-title>{{ 'core.login.reconnect' | translate }}</ion-title> <ion-title>{{ 'core.login.reconnect' | translate }}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content class="ion-padding"> <ion-content class="ion-padding" (keydown)="keyDown($event)" (keyup)="keyUp($event)">
<div class="ion-text-wrap ion-text-center ion-margin-bottom" [ngClass]="{'item-avatar-center': showSiteAvatar}"> <div class="ion-text-wrap ion-text-center ion-margin-bottom" [ngClass]="{'item-avatar-center': showSiteAvatar}">
<!-- Show user avatar. --> <!-- Show user avatar. -->
<img *ngIf="showSiteAvatar" [src]="userAvatar" class="large-avatar" core-external-content [siteId]="siteId" <img *ngIf="showSiteAvatar" [src]="userAvatar" class="large-avatar" core-external-content [siteId]="siteId"
@ -42,7 +42,7 @@
<core-show-password name="password"> <core-show-password name="password">
<ion-input class="core-ioninput-password" name="password" type="password" <ion-input class="core-ioninput-password" name="password" type="password"
placeholder="{{ 'core.login.password' | translate }}" formControlName="password" [clearOnEdit]="false" placeholder="{{ 'core.login.password' | translate }}" formControlName="password" [clearOnEdit]="false"
autocomplete="current-password" enterkeyhint="go" required="true"> autocomplete="current-password" enterkeyhint="go" required="true" core-auto-focus>
</ion-input> </ion-input>
</core-show-password> </core-show-password>
</ion-item> </ion-item>
@ -71,13 +71,16 @@
</form> </form>
<!-- Forgotten password option. --> <!-- Forgotten password option. -->
<ion-list lines="none" *ngIf="showForgottenPassword && !isOAuth" class="core-login-forgotten-password ion-no-padding"> <ion-button
<ion-item button class="ion-text-center ion-text-wrap" (click)="forgottenPassword()" detail="false"> *ngIf="showForgottenPassword && !isOAuth"
<ion-label> expand="block"
{{ 'core.login.forgotten' | translate }} fill="clear"
</ion-label> color="dark"
</ion-item> class="core-login-forgotten-password core-button-as-link"
</ion-list> (click)="forgottenPassword()"
>
<ion-label>{{ 'core.login.forgotten' | translate }}</ion-label>
</ion-button>
<!-- Identity providers. --> <!-- Identity providers. -->
<ion-list *ngIf="identityProviders && identityProviders.length" class="ion-padding-top core-login-identity-providers"> <ion-list *ngIf="identityProviders && identityProviders.length" class="ion-padding-top core-login-identity-providers">
@ -85,8 +88,8 @@
<ion-label><h3 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h3></ion-label> <ion-label><h3 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h3></ion-label>
</ion-item> </ion-item>
<ion-item button *ngFor="let provider of identityProviders" class="ion-text-wrap core-oauth-icon" <ion-item button *ngFor="let provider of identityProviders" class="ion-text-wrap core-oauth-icon"
(click)="oauthClicked(provider)" [attr.aria-label]="provider.name"> (click)="oauthClicked(provider)">
<img [src]="provider.iconurl" alt="" width="32" height="32" slot="start"> <img [src]="provider.iconurl" alt="" role="presentation" width="32" height="32" slot="start">
<ion-label>{{provider.name}}</ion-label> <ion-label>{{provider.name}}</ion-label>
</ion-item> </ion-item>
</ion-list> </ion-list>

View File

@ -271,4 +271,27 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
} }
} }
/**
* A11y key functionality that prevents keyDown events.
*
* @param e Event.
*/
keyDown(e: KeyboardEvent): void {
if (e.key == 'Escape') {
e.preventDefault();
e.stopPropagation();
}
}
/**
* Cancel reconnect.
*
* @param e Event.
*/
keyUp(e: KeyboardEvent): void {
if (e.key == 'Escape') {
this.cancel(e);
}
}
} }

View File

@ -7,8 +7,7 @@
<ion-title>{{ 'core.login.connecttomoodle' | translate }}</ion-title> <ion-title>{{ 'core.login.connecttomoodle' | translate }}</ion-title>
<ion-buttons slot="end"> <ion-buttons slot="end">
<ion-button fill="clear" router-direction="forward" routerLink="/settings" <ion-button fill="clear" (click)="openSettings()" [attr.aria-label]="'core.settings.appsettings' | translate">
[attr.aria-label]="'core.settings.appsettings' | translate">
<ion-icon slot="icon-only" name="fas-cog" aria-hidden="true"></ion-icon> <ion-icon slot="icon-only" name="fas-cog" aria-hidden="true"></ion-icon>
</ion-button> </ion-button>
</ion-buttons> </ion-buttons>
@ -27,7 +26,7 @@
<h2>{{ 'core.login.siteaddress' | translate }}</h2> <h2>{{ 'core.login.siteaddress' | translate }}</h2>
</ion-label> </ion-label>
<ion-input name="url" type="url" placeholder="{{ 'core.login.siteaddressplaceholder' | translate }}" <ion-input name="url" type="url" placeholder="{{ 'core.login.siteaddressplaceholder' | translate }}"
formControlName="siteUrl" [autofocus]="showKeyboard && !showScanQR"> formControlName="siteUrl" [core-auto-focus]="showKeyboard">
</ion-input> </ion-input>
</ion-item> </ion-item>
</ng-container> </ng-container>
@ -37,7 +36,7 @@
<h2>{{ 'core.login.siteaddress' | translate }}</h2> <h2>{{ 'core.login.siteaddress' | translate }}</h2>
</ion-label> </ion-label>
<ion-input name="url" placeholder="{{ 'core.login.siteaddressplaceholder' | translate }}" formControlName="siteUrl" <ion-input name="url" placeholder="{{ 'core.login.siteaddressplaceholder' | translate }}" formControlName="siteUrl"
[autofocus]="showKeyboard && !showScanQR" (ionChange)="searchSite($event, siteForm.value.siteUrl)"> [core-auto-focus]="showKeyboard" (ionChange)="searchSite($event, siteForm.value.siteUrl)">
</ion-input> </ion-input>
</ion-item> </ion-item>
@ -109,12 +108,16 @@
</ng-container> </ng-container>
<!-- Help. --> <!-- Help. -->
<ion-list lines="none" class="ion-margin-top"> <ion-button
<ion-item button class="ion-text-center ion-text-wrap core-login-need-help" (click)="showHelp()" detail="false" class="ion-margin-top core-login-need-help core-button-as-link"
aria-haspopup="dialog"> (click)="showHelp()"
aria-haspopup="dialog"
expand="block"
fill="clear"
color="dark"
>
<ion-label>{{ 'core.needhelp' | translate }}</ion-label> <ion-label>{{ 'core.needhelp' | translate }}</ion-label>
</ion-item> </ion-button>
</ion-list>
</ion-content> </ion-content>
<!-- Template site selector. --> <!-- Template site selector. -->

View File

@ -12,7 +12,6 @@
.item { .item {
&.core-login-need-help { &.core-login-need-help {
text-decoration: underline;
margin-top: 16px; margin-top: 16px;
} }
&.core-login-site-qrcode { &.core-login-site-qrcode {

View File

@ -567,6 +567,13 @@ export class CoreLoginSitePage implements OnInit {
CoreCustomURLSchemes.treatHandleCustomURLError(error); CoreCustomURLSchemes.treatHandleCustomURLError(error);
} }
/**
* Open settings page.
*/
openSettings(): void {
CoreNavigator.navigate('/settings');
}
} }
/** /**

View File

@ -11,8 +11,7 @@
[attr.aria-label]="'core.delete' | translate"> [attr.aria-label]="'core.delete' | translate">
<ion-icon slot="icon-only" name="fas-pen" aria-hidden="true"></ion-icon> <ion-icon slot="icon-only" name="fas-pen" aria-hidden="true"></ion-icon>
</ion-button> </ion-button>
<ion-button router-direction="forward" routerLink="/settings" <ion-button (click)="openSettings()" [attr.aria-label]="'core.settings.appsettings' | translate">
[attr.aria-label]="'core.settings.appsettings' | translate">
<ion-icon slot="icon-only" name="fas-cog" aria-hidden="true"></ion-icon> <ion-icon slot="icon-only" name="fas-cog" aria-hidden="true"></ion-icon>
</ion-button> </ion-button>
</ion-buttons> </ion-buttons>

View File

@ -139,4 +139,11 @@ export class CoreLoginSitesPage implements OnInit {
this.showDelete = !this.showDelete; this.showDelete = !this.showDelete;
} }
/**
* Open settings page.
*/
openSettings(): void {
CoreNavigator.navigate('/settings');
}
} }

View File

@ -1,9 +1,10 @@
@import "~theme/globals";
:host{ :host{
--menutabbar-size: 60px; --menutabbar-size: 60px;
ion-tabs { ion-tab-bar {
-webkit-filter: drop-shadow(0px 3px 3px rgba(var(--drop-shadow))); box-shadow: 0px -3px 3px rgba(var(--drop-shadow));
filter: drop-shadow(0px 3px 3px rgba(var(--drop-shadow)));
} }
ion-tab-button ion-icon { ion-tab-button ion-icon {
@ -28,6 +29,10 @@
width: var(--menutabbar-size); width: var(--menutabbar-size);
height: 100%; height: 100%;
flex-direction: column; flex-direction: column;
@include border-end(var(--border));
box-shadow: 3px 0 3px rgba(var(--drop-shadow));
border-top: 0;
ion-tab-button { ion-tab-button {
width: 100%; width: 100%;
ion-badge { ion-badge {

View File

@ -20,7 +20,7 @@
<p>{{ siteUrl }}</p> <p>{{ siteUrl }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item-divider><ion-label></ion-label></ion-item-divider> <core-spacer></core-spacer>
<ion-item class="ion-text-center" *ngIf="(!handlers || !handlers.length) && !handlersLoaded"> <ion-item class="ion-text-center" *ngIf="(!handlers || !handlers.length) && !handlersLoaded">
<ion-label><ion-spinner></ion-spinner></ion-label> <ion-label><ion-spinner></ion-spinner></ion-label>
</ion-item> </ion-item>
@ -82,7 +82,7 @@
<h2>{{ logoutLabel | translate }}</h2> <h2>{{ logoutLabel | translate }}</h2>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item-divider><ion-label></ion-label></ion-item-divider> <core-spacer></core-spacer>
<ion-item button (click)="openSettings()" [attr.aria-label]="'core.settings.appsettings' | translate" detail="true"> <ion-item button (click)="openSettings()" [attr.aria-label]="'core.settings.appsettings' | translate" detail="true">
<ion-icon name="fas-cogs" slot="start" aria-hidden="true"></ion-icon> <ion-icon name="fas-cogs" slot="start" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>

View File

@ -3,7 +3,7 @@
<ion-item> <ion-item>
<ion-label></ion-label> <ion-label></ion-label>
<ion-input type="search" name="search" [(ngModel)]="searchText" [placeholder]="placeholder" <ion-input type="search" name="search" [(ngModel)]="searchText" [placeholder]="placeholder"
[autocorrect]="autocorrect" [spellcheck]="spellcheck" [autofocus]="autoFocus" [autocorrect]="autocorrect" [spellcheck]="spellcheck" [core-auto-focus]="autoFocus || 'nofocus'"
[disabled]="disabled" role="searchbox" (ionFocus)="focus($event)"> [disabled]="disabled" role="searchbox" (ionFocus)="focus($event)">
</ion-input> </ion-input>
<ion-button slot="end" fill="clear" type="submit" size="small" [attr.aria-label]="searchLabel" <ion-button slot="end" fill="clear" type="submit" size="small" [attr.aria-label]="searchLabel"

View File

@ -25,7 +25,7 @@
<p>{{ siteUrl }}</p> <p>{{ siteUrl }}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item-divider><ion-label></ion-label></ion-item-divider> <core-spacer></core-spacer>
<ion-item *ngFor="let handler of handlers.items" [ngClass]="['core-settings-handler', handler.class]" <ion-item *ngFor="let handler of handlers.items" [ngClass]="['core-settings-handler', handler.class]"
[attr.aria-label]="handler.title | translate" detail="true" (click)="handlers.select(handler)" button [attr.aria-label]="handler.title | translate" detail="true" (click)="handlers.select(handler)" button

View File

@ -449,7 +449,7 @@ export class CoreSettingsHelperProvider {
* Check if device can detect color scheme system preference. * Check if device can detect color scheme system preference.
* https://caniuse.com/prefers-color-scheme * https://caniuse.com/prefers-color-scheme
* *
* @returns if the color scheme system preference is avalaible. * @returns if the color scheme system preference is available.
*/ */
canIUsePrefersColorScheme(): boolean { canIUsePrefersColorScheme(): boolean {
// The following check will check browser support but system may differ from that. // The following check will check browser support but system may differ from that.

View File

@ -31,9 +31,7 @@
<!-- Site home items: news, categories, courses, etc. --> <!-- Site home items: news, categories, courses, etc. -->
<ng-container *ngIf="items.length > 0"> <ng-container *ngIf="items.length > 0">
<ion-item-divider *ngIf="section && section!.hasContent"> <core-spacer *ngIf="section && section!.hasContent"></core-spacer>
<ion-label></ion-label>
</ion-item-divider>
<ng-container *ngFor="let item of items"> <ng-container *ngFor="let item of items">
<ng-container [ngSwitch]="item"> <ng-container [ngSwitch]="item">
<ng-container *ngSwitchCase="'LIST_OF_COURSE'"> <ng-container *ngSwitchCase="'LIST_OF_COURSE'">
@ -63,7 +61,7 @@
</ion-content> </ion-content>
<ng-template #allCourseList> <ng-template #allCourseList>
<ion-item button class="ion-text-wrap" router-direction="forward" routerLink="/main/home/courses/all" detail="true"> <ion-item button class="ion-text-wrap" (click)="openAvailableCourses()" detail="true">
<ion-icon name="fas-graduation-cap" fixed-width slot="start" aria-hidden="true"></ion-icon> <ion-icon name="fas-graduation-cap" fixed-width slot="start" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<h2>{{ 'core.courses.availablecourses' | translate}}</h2> <h2>{{ 'core.courses.availablecourses' | translate}}</h2>
@ -77,7 +75,7 @@
</ng-template> </ng-template>
<ng-template #categories> <ng-template #categories>
<ion-item button class="ion-text-wrap" router-direction="forward" routerLink="/main/home/courses/categories" detail="true"> <ion-item button class="ion-text-wrap" (click)="openCourseCategories()" detail="true">
<ion-icon name="fas-folder" slot="start" aria-hidden="true"></ion-icon> <ion-icon name="fas-folder" slot="start" aria-hidden="true"></ion-icon>
<ion-label> <ion-label>
<h2>{{ 'core.courses.categories' | translate}}</h2> <h2>{{ 'core.courses.categories' | translate}}</h2>
@ -86,7 +84,7 @@
</ng-template> </ng-template>
<ng-template #enrolledCourseList> <ng-template #enrolledCourseList>
<ion-item button class="ion-text-wrap" router-direction="forward" routerLink="/main/home/courses/my" detail="true"> <ion-item button class="ion-text-wrap" (click)="openMyCourses()" detail="true">
<ion-icon name="fas-graduation-cap" fixed-width slot="start" aria-hidden="true"> <ion-icon name="fas-graduation-cap" fixed-width slot="start" aria-hidden="true">
</ion-icon> </ion-icon>
<ion-label><h2>{{ 'core.courses.mycourses' | translate}}</h2></ion-label> <ion-label><h2>{{ 'core.courses.mycourses' | translate}}</h2></ion-label>
@ -94,7 +92,7 @@
</ng-template> </ng-template>
<ng-template #courseSearch> <ng-template #courseSearch>
<ion-item button class="ion-text-wrap" router-direction="forward" routerLink="/main/home/courses/search" detail="true"> <ion-item button class="ion-text-wrap" (click)="openSearch()" detail="true">
<ion-icon name="fas-search" slot="start" aria-hidden="true"></ion-icon> <ion-icon name="fas-search" slot="start" aria-hidden="true"></ion-icon>
<ion-label><h2>{{ 'core.courses.searchcourses' | translate}}</h2></ion-label> <ion-label><h2>{{ 'core.courses.searchcourses' | translate}}</h2></ion-label>
</ion-item> </ion-item>

View File

@ -221,6 +221,27 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
CoreNavigator.navigateToSitePath('courses/search'); CoreNavigator.navigateToSitePath('courses/search');
} }
/**
* Go to available courses.
*/
openAvailableCourses(): void {
CoreNavigator.navigateToSitePath('courses/all');
}
/**
* Go to my courses.
*/
openMyCourses(): void {
CoreNavigator.navigateToSitePath('courses/my');
}
/**
* Go to course categories.
*/
openCourseCategories(): void {
CoreNavigator.navigateToSitePath('courses/categories');
}
/** /**
* Component being destroyed. * Component being destroyed.
*/ */

View File

@ -14,11 +14,16 @@
<ion-list *ngIf="user && !isDeleted && isEnrolled"> <ion-list *ngIf="user && !isDeleted && isEnrolled">
<ion-item class="ion-text-center core-user-profile-maininfo"> <ion-item class="ion-text-center core-user-profile-maininfo">
<core-user-avatar [user]="user" [userId]="user.id" [linkProfile]="false" [checkOnline]="true"> <core-user-avatar [user]="user" [userId]="user.id" [linkProfile]="false" [checkOnline]="true">
<div class="core-icon-foreground" *ngIf="canChangeProfilePicture"> <ion-button
<ion-icon name="fa-pen" (click)="changeProfilePicture()" class="edit-avatar"
[attr.aria-label]="'core.user.newpicture' | translate"> *ngIf="canChangeProfilePicture"
</ion-icon> (click)="changeProfilePicture()"
</div> [attr.aria-label]="'core.user.newpicture' | translate"
fill="clear"
color="dark"
>
<ion-icon slot="icon-only" name="fa-pen" aria-hidden="true"></ion-icon>
</ion-button>
</core-user-avatar> </core-user-avatar>
<ion-label> <ion-label>
<h2>{{ user.fullname }}</h2> <h2>{{ user.fullname }}</h2>

View File

@ -17,16 +17,12 @@
height: 24px !important; height: 24px !important;
} }
.core-icon-foreground { .edit-avatar {
position: absolute; position: absolute;
right: 0; right: -24px;
bottom: 0; bottom: -12px;
line-height: 30px; --border-radius: 50%;
text-align: center; --background: var(--ion-item-background);
width: 30px;
height: 30px;
border-radius: 50%;
background-color:var(--background);
:host-context([dir="rtl"]) & { :host-context([dir="rtl"]) & {
left: 0; left: 0;
@ -38,8 +34,7 @@
} }
:host-context([dir="rtl"]) ::ng-deep core-user-avatar .core-icon-foreground { :host-context([dir="rtl"]) ::ng-deep core-user-avatar .edit-avatar {
left: 0; left: -24px;
right: unset; right: unset;
} }

View File

@ -412,5 +412,5 @@ export type CorePluginFileDownloadableResult = {
*/ */
export type CoreFileSizeSum = { export type CoreFileSizeSum = {
size: number; // Sum of file sizes. size: number; // Sum of file sizes.
total: boolean; // False if any file size is not avalaible. total: boolean; // False if any file size is not available.
}; };

View File

@ -391,11 +391,12 @@ export class CoreDomUtilsProvider {
* Focus an element and open keyboard. * Focus an element and open keyboard.
* *
* @param el HTML element to focus. * @param el HTML element to focus.
* @param showKeyboard Show keyboard when focusing the element.
*/ */
focusElement(el: HTMLElement): void { focusElement(el: HTMLElement, showKeyboard = true): void {
if (el?.focus) { if (el?.focus) {
el.focus(); el.focus();
if (CoreApp.isAndroid() && this.supportsInputKeyboard(el)) { if (showKeyboard && CoreApp.isAndroid() && this.supportsInputKeyboard(el)) {
// On some Android versions the keyboard doesn't open automatically. // On some Android versions the keyboard doesn't open automatically.
CoreApp.openKeyboard(); CoreApp.openKeyboard();
} }

View File

@ -107,6 +107,7 @@
$tint: map-get($value, tint); $tint: map-get($value, tint);
--ion-color-#{$color-name}: #{$base}; --ion-color-#{$color-name}: #{$base};
--ion-color-#{$color-name}-base: #{$base};
--ion-color-#{$color-name}-rgb: #{color-to-rgb-list($base)}; --ion-color-#{$color-name}-rgb: #{color-to-rgb-list($base)};
--ion-color-#{$color-name}-contrast: #{$contrast}; --ion-color-#{$color-name}-contrast: #{$contrast};
--ion-color-#{$color-name}-contrast-rgb: #{color-to-rgb-list($contrast)}; --ion-color-#{$color-name}-contrast-rgb: #{color-to-rgb-list($contrast)};
@ -115,6 +116,7 @@
.ion-color-#{$color-name} { .ion-color-#{$color-name} {
--ion-color: #{$base}; --ion-color: #{$base};
--ion-color-base: #{$base};
--ion-color-rgb: #{color-to-rgb-list($base)}; --ion-color-rgb: #{color-to-rgb-list($base)};
--ion-color-contrast: #{$contrast}; --ion-color-contrast: #{$contrast};
--ion-color-contrast-rgb: #{color-to-rgb-list($contrast)}; --ion-color-contrast-rgb: #{color-to-rgb-list($contrast)};

View File

@ -30,7 +30,7 @@
} }
} }
@mixin border-start($px, $type, $color) { @mixin border-start($px, $type: null, $color: null) {
@include ltr() { @include ltr() {
border-left: $px $type $color; border-left: $px $type $color;
} }
@ -41,7 +41,7 @@
} }
@mixin border-end($px, $type, $color) { @mixin border-end($px, $type: null, $color: null) {
@include ltr() { @include ltr() {
border-right: $px $type $color; border-right: $px $type $color;
} }

View File

@ -79,6 +79,7 @@ ion-toolbar .in-toolbar.button-clear {
ion-header ion-toolbar .button.button-clear, ion-header ion-toolbar .button.button-clear,
ion-header ion-toolbar .button.button-solid { ion-header ion-toolbar .button.button-solid {
--background: transparent;
--color: var(--core-header-toolbar-color); --color: var(--core-header-toolbar-color);
--ion-color-primary: var(--core-header-toolbar-color); --ion-color-primary: var(--core-header-toolbar-color);
} }
@ -130,7 +131,7 @@ ion-button.button-small ion-icon.faicon[slot] {
} }
// Buttons. // Buttons.
ion-button { ion-button, button, [role="button"] {
min-height: var(--a11y-min-target-size); min-height: var(--a11y-min-target-size);
min-width: var(--a11y-min-target-size); min-width: var(--a11y-min-target-size);
} }
@ -144,10 +145,13 @@ ion-button {
} }
} }
.core-iframe-help ion-button { ion-button.core-button-as-link {
text-transform: none; text-transform: none;
text-decoration: underline; text-decoration: underline;
--color: initial; font-size: inherit;
font-weight: normal;
letter-spacing: normal;
white-space: break-spaces;
} }
// Ionic alert. // Ionic alert.
@ -189,11 +193,6 @@ ion-item-divider {
opacity: var(--ion-item-detail-icon-opacity); opacity: var(--ion-item-detail-icon-opacity);
padding-inline-end: 16px; padding-inline-end: 16px;
} }
&.ios {
padding-top: 10px;
padding-bottom: 10px;
}
} }
// Ionic list. // Ionic list.
@ -363,7 +362,7 @@ img[alt] {
// Activity modules // Activity modules
.core-module-icon { .core-module-icon {
--size: 24px; --size: var(--module-icon-size);
width: var(--size); width: var(--size);
height: var(--size); height: var(--size);
max-width: var(--size); max-width: var(--size);

View File

@ -65,6 +65,8 @@
// Accessibility vars. // Accessibility vars.
--a11y-min-target-size: 44px; --a11y-min-target-size: 44px;
--module-icon-size: 24px;
--ion-text-color: #{$text-color}; --ion-text-color: #{$text-color};
--ion-text-color-rgb: 58,58,58; --ion-text-color-rgb: 58,58,58;
--ion-card-color: var(--ion-text-color); --ion-card-color: var(--ion-text-color);
@ -169,10 +171,11 @@
--detail-icon-opacity: var(--ion-item-detail-icon-opacity); --detail-icon-opacity: var(--ion-item-detail-icon-opacity);
} }
--item-divider-min-height: calc(var(--a11y-min-target-size) + 8px);
ion-item-divider { ion-item-divider {
--background: var(--gray-lighter); --background: var(--gray-lighter);
--color: inherit; --color: inherit;
min-height: calc(var(--a11y-min-target-size) + 8px); min-height: var(--item-divider-min-height);
} }
--core-combobox-background: var(--ion-item-background); --core-combobox-background: var(--ion-item-background);
@ -215,10 +218,10 @@
--addon-forum-border-color: #{$addon-forum-border-color}; --addon-forum-border-color: #{$addon-forum-border-color};
--addon-forum-highlight-color: #{$addon-forum-highlight-color}; --addon-forum-highlight-color: #{$addon-forum-highlight-color};
--drop-shadow: 0, 0, 0, 0.2; --drop-shadow: 0, 0, 0, 0.18;
--core-menu-box-shadow-end: -4px 0px 16px rgba(0, 0, 0, 0.18); --core-menu-box-shadow-end: -4px 0px 16px rgba(var(--drop-shadow));
--core-menu-box-shadow-start: 4px 0px 16px rgba(0, 0, 0, 0.18); --core-menu-box-shadow-start: 4px 0px 16px rgba(var(--drop-shadow));
--core-question-correct-color: var(--green-dark); --core-question-correct-color: var(--green-dark);
--core-question-correct-color-bg: var(--green-light); --core-question-correct-color-bg: var(--green-light);