Merge pull request #2093 from moodlehq/integration

Integration
main
Juan Leyva 2019-08-30 11:30:26 +01:00 committed by GitHub
commit 1a6f6a0e3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
441 changed files with 23599 additions and 2442 deletions

View File

@ -15,8 +15,7 @@ cache:
- $HOME/.cache/electron-builder
before_script:
- npm install -g @angular/cli
- npm i npm@latest -g
- npm install npm@latest -g
- gulp
script:

View File

@ -11,17 +11,17 @@ EXPOSE 35729
# Port 53703 is the Chrome dev logger port.
EXPOSE 53703
# MoodleMobile uses Ionic and Gulp.
RUN npm i -g ionic gulp && rm -rf /root/.npm
WORKDIR /app
COPY . /app
# Install npm libraries and run gulp to initialize the project.
RUN npm install && gulp && rm -rf /root/.npm
# Install npm libraries.
RUN npm install && rm -rf /root/.npm
# Run gulp before starting.
RUN npx gulp
# Provide a Healthcheck command for easier use in CI.
HEALTHCHECK --interval=10s --timeout=3s --start-period=30s CMD curl -f http://localhost:8100 || exit 1
CMD ["ionic", "serve", "-b"]
CMD ["npm", "run", "ionic:serve"]

View File

@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<widget id="com.moodle.moodlemobile" version="3.7.0" xmlns="http://www.w3.org/ns/widgets" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<widget id="com.moodle.moodlemobile" version="3.7.1" xmlns="http://www.w3.org/ns/widgets" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>Moodle</name>
<description>Moodle official app</description>
<author email="mobile@moodle.com" href="http://moodle.com">Moodle Mobile team</author>
@ -23,7 +23,7 @@
<preference name="webviewbounce" value="false" />
<preference name="AppendUserAgent" value="MoodleMobile" />
<preference name="android-minSdkVersion" value="19" />
<preference name="android-targetSdkVersion" value="26" />
<preference name="android-targetSdkVersion" value="28" />
<preference name="UIWebViewBounce" value="false" />
<preference name="DisallowOverscroll" value="true" />
<preference name="BackupWebStorage" value="none" />
@ -113,30 +113,30 @@
<icon height="1024" src="resources/ios/icon/icon-1024.png" width="1024" />
<splash height="2732" src="resources/ios/splash/Default@2x~universal~anyany.png" width="2732" />
</platform>
<plugin name="com-darryncampbell-cordova-plugin-intent" spec="1.1.7" />
<plugin name="cordova-android-support-gradle-release" spec="3.0.0">
<plugin name="com-darryncampbell-cordova-plugin-intent" spec="1.1.8" />
<plugin name="cordova-android-support-gradle-release" spec="3.0.1">
<variable name="ANDROID_SUPPORT_VERSION" value="27.1.0" />
</plugin>
<plugin name="cordova-clipboard" spec="1.2.1" />
<plugin name="cordova-clipboard" spec="1.3.0" />
<plugin name="cordova-plugin-badge" spec="0.8.8" />
<plugin name="cordova-plugin-camera" spec="4.0.3" />
<plugin name="cordova-plugin-camera" spec="4.1.0" />
<plugin name="cordova-plugin-customurlscheme" spec="4.4.0">
<variable name="URL_SCHEME" value="moodlemobile" />
</plugin>
<plugin name="cordova-plugin-device" spec="2.0.2" />
<plugin name="cordova-plugin-file" spec="6.0.1" />
<plugin name="cordova-plugin-device" spec="2.0.3" />
<plugin name="cordova-plugin-file" spec="6.0.2" />
<plugin name="cordova-plugin-file-opener2" spec="2.0.19" />
<plugin name="cordova-plugin-file-transfer" spec="1.7.1" />
<plugin name="cordova-plugin-globalization" spec="1.11.0" />
<plugin name="cordova-plugin-inappbrowser" spec="3.0.0" />
<plugin name="cordova-plugin-inappbrowser" spec="3.1.0" />
<plugin name="cordova-plugin-ionic-keyboard" spec="2.1.3" />
<plugin name="cordova-plugin-local-notification" spec="https://github.com/moodlemobile/cordova-plugin-local-notification.git#moodle" />
<plugin name="cordova-plugin-media-capture" spec="3.0.2" />
<plugin name="cordova-plugin-network-information" spec="2.0.1" />
<plugin name="cordova-plugin-screen-orientation" spec="3.0.1" />
<plugin name="cordova-plugin-splashscreen" spec="5.0.2" />
<plugin name="cordova-plugin-statusbar" spec="2.4.2" />
<plugin name="cordova-plugin-whitelist" spec="1.3.3" />
<plugin name="cordova-plugin-media-capture" spec="3.0.3" />
<plugin name="cordova-plugin-network-information" spec="2.0.2" />
<plugin name="cordova-plugin-screen-orientation" spec="3.0.2" />
<plugin name="cordova-plugin-splashscreen" spec="5.0.3" />
<plugin name="cordova-plugin-statusbar" spec="2.4.3" />
<plugin name="cordova-plugin-whitelist" spec="1.3.4" />
<plugin name="cordova-plugin-zip" spec="3.1.0" />
<plugin name="cordova-sqlite-storage" spec="2.6.0" />
<plugin name="nl.kingsquare.cordova.background-audio" spec="1.0.1" />
@ -147,6 +147,9 @@
<edit-config file="AndroidManifest.xml" mode="merge" target="/manifest/application/activity[@android:name='MainActivity']">
<activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|screenLayout|smallestScreenSize" android:debuggable="true" />
</edit-config>
<edit-config file="AndroidManifest.xml" mode="merge" target="/manifest/application">
<application android:usesCleartextTraffic="true" />
</edit-config>
<config-file parent="/manifest/application" target="AndroidManifest.xml">
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" />
</config-file>

View File

@ -6,7 +6,7 @@
<Identity Name="3312ADB7.MoodleDesktop"
ProcessorArchitecture="x64"
Publisher="CN=33CDCDF6-1EB5-4827-9897-ED25C91A32F6"
Version="3.7.0.0" />
Version="3.7.1.0" />
<Properties>
<DisplayName>Moodle Desktop</DisplayName>
<PublisherDisplayName>Moodle Pty Ltd.</PublisherDisplayName>

5662
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "moodlemobile",
"version": "3.7.0",
"version": "3.7.1",
"description": "The official app for Moodle.",
"author": {
"name": "Moodle Pty Ltd.",
@ -23,21 +23,21 @@
}
],
"scripts": {
"setup": "npm install && cordova prepare && gulp",
"clean": "ionic-app-scripts clean",
"build": "ionic-app-scripts build",
"lint": "ionic-app-scripts lint",
"setup": "npm install && npx cordova prepare && npx gulp",
"clean": "npx ionic-app-scripts clean",
"build": "npx ionic-app-scripts build",
"lint": "npx ionic-app-scripts lint",
"ionic:build": "node --max-old-space-size=16384 ./node_modules/@ionic/app-scripts/bin/ionic-app-scripts.js build",
"ionic:serve:before": "gulp",
"ionic:serve": "gulp watch | ionic-app-scripts serve",
"ionic:build:before": "gulp",
"ionic:watch:before": "gulp",
"ionic:build:after": "gulp copy-component-templates",
"preionic:build": "gulp",
"postionic:build": "gulp copy-component-templates",
"desktop.pack": "electron-builder --dir",
"desktop.dist": "electron-builder -p never",
"windows.store": "electron-windows-store --input-directory .\\desktop\\dist\\win-unpacked --output-directory .\\desktop\\store --flatten true -a .\\resources\\desktop -m .\\desktop\\assets\\windows\\AppXManifest.xml --package-version 0.0.0.0 --package-name MoodleDesktop"
"ionic:serve:before": "npx gulp",
"ionic:serve": "npx gulp watch & npx ionic-app-scripts serve -b --devapp --address=0.0.0.0",
"ionic:build:before": "npx gulp",
"ionic:watch:before": "npx gulp",
"ionic:build:after": "npx gulp copy-component-templates",
"preionic:build": "npx gulp",
"postionic:build": "npx gulp copy-component-templates",
"desktop.pack": "npx electron-builder --dir",
"desktop.dist": "npx electron-builder -p never",
"windows.store": "npx electron-windows-store --input-directory .\\desktop\\dist\\win-unpacked --output-directory .\\desktop\\store -a .\\resources\\desktop -m .\\desktop\\assets\\windows\\AppXManifest.xml --package-version 0.0.0.0 --package-name MoodleDesktop"
},
"dependencies": {
"@angular/animations": "5.2.10",
@ -80,27 +80,28 @@
"@types/promise.prototype.finally": "2.0.2",
"chart.js": "2.7.2",
"com-darryncampbell-cordova-plugin-intent": "1.1.7",
"cordova": "8.1.2",
"cordova-android": "7.1.2",
"cordova-android-support-gradle-release": "3.0.0",
"cordova-clipboard": "1.2.1",
"cordova-android-support-gradle-release": "3.0.1",
"cordova-clipboard": "1.3.0",
"cordova-ios": "4.5.5",
"cordova-plugin-badge": "0.8.8",
"cordova-plugin-camera": "4.0.3",
"cordova-plugin-customurlscheme": "4.3.0",
"cordova-plugin-device": "2.0.2",
"cordova-plugin-file": "6.0.1",
"cordova-plugin-camera": "4.1.0",
"cordova-plugin-customurlscheme": "4.4.0",
"cordova-plugin-device": "2.0.3",
"cordova-plugin-file": "6.0.2",
"cordova-plugin-file-opener2": "2.0.19",
"cordova-plugin-file-transfer": "1.7.1",
"cordova-plugin-globalization": "1.11.0",
"cordova-plugin-inappbrowser": "3.0.0",
"cordova-plugin-inappbrowser": "3.1.0",
"cordova-plugin-ionic-keyboard": "2.1.3",
"cordova-plugin-local-notification": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#moodle",
"cordova-plugin-media-capture": "3.0.2",
"cordova-plugin-network-information": "2.0.1",
"cordova-plugin-screen-orientation": "3.0.1",
"cordova-plugin-splashscreen": "5.0.2",
"cordova-plugin-statusbar": "2.4.2",
"cordova-plugin-whitelist": "1.3.3",
"cordova-plugin-media-capture": "3.0.3",
"cordova-plugin-network-information": "2.0.2",
"cordova-plugin-screen-orientation": "3.0.2",
"cordova-plugin-splashscreen": "5.0.3",
"cordova-plugin-statusbar": "2.4.3",
"cordova-plugin-whitelist": "1.3.4",
"cordova-plugin-zip": "3.1.0",
"cordova-sqlite-storage": "2.6.0",
"cordova-support-google-services": "1.2.1",
@ -124,7 +125,7 @@
"@ionic/app-scripts": "3.2.2",
"electron-builder-lib": "20.23.1",
"electron-rebuild": "1.8.1",
"gulp": "4.0.0",
"gulp": "4.0.2",
"gulp-clip-empty-files": "0.1.2",
"gulp-concat": "2.6.1",
"gulp-flatten": "0.4.0",
@ -210,11 +211,12 @@
}
],
"compression": "maximum",
"electronVersion": "4.0.1",
"electronVersion": "4.2.5",
"mac": {
"category": "public.app-category.education",
"icon": "resources/desktop/icon.icns",
"target": "mas",
"bundleVersion": "3.7.0",
"extendInfo": {
"ElectronTeamID": "2NU57U5PAW"
}

View File

@ -1,5 +1,8 @@
#!/bin/bash
# List first level of installed libraries so we can check the installed versions.
npm list --depth=0
# Compile AOT.
if [ $TRAVIS_BRANCH == 'integration' ] || [ $TRAVIS_BRANCH == 'master' ] || [ $TRAVIS_BRANCH == 'desktop' ] || [ -z $TRAVIS_BRANCH ] ; then
cd scripts
@ -40,17 +43,34 @@ if [ ! -z $GIT_ORG ] && [ ! -z $GIT_TOKEN ] ; then
gitfolder=${PWD##*/}
git clone --depth 1 --no-single-branch https://github.com/$GIT_ORG/moodlemobile-phonegapbuild.git ../pgb
pushd ../pgb
mkdir /tmp/travistemp
cp .travis.yml /tmp/travistemp
mkdir /tmp/travistemp/scripts
cp scripts/* /tmp/travistemp/scripts
git checkout $TRAVIS_BRANCH
rm -Rf assets build index.html templates
cp -Rf ../$gitfolder/www/* ./
rm -Rf assets/countries assets/mimetypes
rm -Rf assets build index.html templates www destkop
if [ $TRAVIS_BRANCH == 'desktop' ] ; then
cp -Rf ../$gitfolder/desktop ./
cp -Rf ../$gitfolder/package.json ./
cp -Rf ../$gitfolder/www ./
rm -Rf www/assets/countries www/assets/mimetypes
else
cp -Rf ../$gitfolder/www/* ./
rm -Rf assets/countries assets/mimetypes
fi
cp /tmp/travistemp/.travis.yml .travis.yml
mkdir scripts
cp /tmp/travistemp/scripts/* scripts
git add .
git commit -m "Travis build: $TRAVIS_BUILD_NUMBER"
git push https://$GIT_TOKEN@github.com/$GIT_ORG/moodlemobile-phonegapbuild.git
popd
fi
if [ ! -z $GIT_ORG_PRIVATE ] && [ ! -z $GIT_TOKEN ] && [ $TRAVIS_BRANCH == 'desktop' ] && [ $TRAVIS_OS_NAME == 'linux' ]; then
./scripts/linux.sh
fi

View File

@ -0,0 +1,426 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Helper functions converting moodle strings to json.
*/
function detect_languages($languages, $keys) {
echo "\n\n\n";
$all_languages = glob(LANGPACKSFOLDER.'/*' , GLOB_ONLYDIR);
function get_lang_from_dir($dir) {
return str_replace('_', '-', explode('/', $dir)[3]);
}
function get_lang_not_wp($langname) {
return (substr($langname, -3) !== '-wp');
}
$all_languages = array_map('get_lang_from_dir', $all_languages);
$all_languages = array_filter($all_languages, 'get_lang_not_wp');
$detect_lang = array_diff($all_languages, $languages);
$new_langs = array();
foreach ($detect_lang as $lang) {
reset_translations_strings();
$new = detect_lang($lang, $keys);
if ($new) {
$new_langs[$lang] = $lang;
}
}
return $new_langs;
}
function build_languages($languages, $keys, $added_langs = []) {
// Process the languages.
foreach ($languages as $lang) {
reset_translations_strings();
$ok = build_lang($lang, $keys);
if ($ok) {
$added_langs[$lang] = $lang;
}
}
return $added_langs;
}
function get_langindex_keys() {
// Process the index file, just once.
$keys = file_get_contents('langindex.json');
$keys = (array) json_decode($keys);
foreach ($keys as $key => $value) {
$map = new StdClass();
if ($value == 'local_moodlemobileapp') {
$map->file = $value;
$map->string = $key;
} else {
$exp = explode('/', $value, 2);
$map->file = $exp[0];
if (count($exp) == 2) {
$map->string = $exp[1];
} else {
$exp = explode('.', $key, 3);
if (count($exp) == 3) {
$map->string = $exp[2];
} else {
$map->string = $exp[1];
}
}
}
$keys[$key] = $map;
}
$total = count($keys);
echo "Total strings to translate $total\n";
return $keys;
}
function add_langs_to_config($langs, $config) {
$changed = false;
$config_langs = get_object_vars($config['languages']);
foreach ($langs as $lang) {
if (!isset($config_langs[$lang])) {
$langfoldername = str_replace('-', '_', $lang);
$string = [];
include(LANGPACKSFOLDER.'/'.$langfoldername.'/langconfig.php');
$config['languages']->$lang = $string['thislanguage'];
$changed = true;
}
}
if ($changed) {
// Sort languages by key.
$config['languages'] = json_decode( json_encode( $config['languages'] ), true );
ksort($config['languages']);
$config['languages'] = json_decode( json_encode( $config['languages'] ), false );
file_put_contents(CONFIG, json_encode($config, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}
}
function get_langfolder($lang) {
$folder = LANGPACKSFOLDER.'/'.str_replace('-', '_', $lang);
if (!is_dir($folder) || !is_file($folder.'/langconfig.php')) {
echo "Cannot translate $folder, folder not found";
return false;
}
return $folder;
}
function get_translation_strings($langfoldername, $file, $override_folder = false) {
global $strings;
if (isset($strings[$file])) {
return $strings[$file];
}
$string = import_translation_strings($langfoldername, $file);
$string_override = $override_folder ? import_translation_strings($override_folder, $file) : false;
if ($string) {
$strings[$file] = $string;
if ($string_override) {
$strings[$file] = array_merge($strings[$file], $string_override);
}
} else if ($string_override) {
$strings[$file] = $string_override;
} else {
$strings[$file] = false;
}
return $strings[$file];
}
function import_translation_strings($langfoldername, $file) {
$path = $langfoldername.'/'.$file.'.php';
// Apply translations.
if (!file_exists($path)) {
return false;
}
$string = [];
include($path);
return $string;
}
function reset_translations_strings() {
global $strings;
$strings = [];
}
function build_lang($lang, $keys) {
$langfoldername = get_langfolder($lang);
if (!$langfoldername) {
return false;
}
if (OVERRIDE_LANG_SUFIX) {
$override_langfolder = get_langfolder($lang.OVERRIDE_LANG_SUFIX);
} else {
$override_langfolder = false;
}
$total = count ($keys);
$local = 0;
$string = get_translation_strings($langfoldername, 'langconfig');
$parent = isset($string['parentlanguage']) ? $string['parentlanguage'] : "";
echo "Processing $lang";
if ($parent != "" && $parent != $lang) {
echo " ($parent)";
}
$langFile = false;
// Not yet translated. Do not override.
if (file_exists(ASSETSPATH.$lang.'.json')) {
// Load lang files just once.
$langFile = file_get_contents(ASSETSPATH.$lang.'.json');
$langFile = (array) json_decode($langFile);
}
$translations = [];
// Add the translation to the array.
foreach ($keys as $key => $value) {
$string = get_translation_strings($langfoldername, $value->file, $override_langfolder);
// Apply translations.
if (!$string) {
if (TOTRANSLATE) {
echo "\n\t\To translate $value->string on $value->file";
}
continue;
}
if (!isset($string[$value->string]) || ($lang == 'en' && $value->file == 'local_moodlemobileapp')) {
// Not yet translated. Do not override.
if ($langFile && is_array($langFile) && isset($langFile[$key])) {
$translations[$key] = $langFile[$key];
$local++;
}
if (TOTRANSLATE) {
echo "\n\t\tTo translate $value->string on $value->file";
}
continue;
} else {
$text = $string[$value->string];
}
if ($value->file != 'local_moodlemobileapp') {
$text = str_replace('$a->', '$a.', $text);
$text = str_replace('{$a', '{{$a', $text);
$text = str_replace('}', '}}', $text);
// Prevent double.
$text = str_replace(array('{{{', '}}}'), array('{{', '}}'), $text);
} else {
$local++;
}
$translations[$key] = html_entity_decode($text);
}
// Sort and save.
ksort($translations);
file_put_contents(ASSETSPATH.$lang.'.json', str_replace('\/', '/', json_encode($translations, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)));
$success = count($translations);
$percentage = floor($success/$total * 100);
echo "\t\t$success of $total -> $percentage% ($local local)\n";
if ($lang == 'en') {
generate_local_moodlemobileapp($keys, $translations);
override_component_lang_files($keys, $translations);
}
return true;
}
function detect_lang($lang, $keys) {
$langfoldername = get_langfolder($lang);
if (!$langfoldername) {
return false;
}
$total = count ($keys);
$success = 0;
$local = 0;
$string = get_translation_strings($langfoldername, 'langconfig');
$parent = isset($string['parentlanguage']) ? $string['parentlanguage'] : "";
if (!isset($string['thislanguage'])) {
echo "Cannot translate $lang, translated name not found";
return false;
}
echo "Checking $lang";
if ($parent != "" && $parent != $lang) {
echo " ($parent)";
}
$langname = $string['thislanguage'];
echo " ".$langname." -D";
// Add the translation to the array.
foreach ($keys as $key => $value) {
$string = get_translation_strings($langfoldername, $value->file);
// Apply translations.
if (!$string) {
continue;
}
if (!isset($string[$value->string])) {
continue;
} else {
$text = $string[$value->string];
}
if ($value->file == 'local_moodlemobileapp') {
$local++;
}
$success++;
}
$percentage = floor($success/$total * 100);
echo "\t\t$success of $total -> $percentage% ($local local)";
if (($percentage > 75 && $local > 50) || ($percentage > 50 && $local > 75)) {
echo " \t DETECTED\n";
return true;
}
echo "\n";
return false;
}
function save_key($key, $value, $path) {
$filePath = $path . '/en.json';
$file = file_get_contents($filePath);
$file = (array) json_decode($file);
$value = html_entity_decode($value);
if (!isset($file[$key]) || $file[$key] != $value) {
$file[$key] = $value;
ksort($file);
file_put_contents($filePath, str_replace('\/', '/', json_encode($file, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)));
}
}
function override_component_lang_files($keys, $translations) {
echo "Override component lang files.\n";
foreach ($translations as $key => $value) {
$path = '../src/';
$exp = explode('.', $key, 3);
$type = $exp[0];
if (count($exp) == 3) {
$component = $exp[1];
$plainid = $exp[2];
} else {
$component = 'moodle';
$plainid = $exp[1];
}
switch($type) {
case 'core':
case 'addon':
switch($component) {
case 'moodle':
$path .= 'lang';
break;
default:
$path .= $type.'/'.str_replace('_', '/', $component).'/lang';
break;
}
break;
case 'assets':
$path .= $type.'/'.$component;
break;
}
if (is_file($path.'/en.json')) {
save_key($plainid, $value, $path);
}
}
}
/**
* Generates local moodle mobile app file to update languages in AMOS.
*
* @param [array] $keys Translation keys.
* @param [array] $translations English translations.
*/
function generate_local_moodlemobileapp($keys, $translations) {
echo "Generate local_moodlemobileapp.\n";
$string = '<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Version details.
*
* @package local
* @subpackage moodlemobileapp
* @copyright 2014 Juan Leyva <juanleyvadelgado@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string[\'appstoredescription\'] = \'NOTE: This official Moodle Mobile app will ONLY work with Moodle sites that have been set up to allow it. Please talk to your Moodle administrator if you have any problems connecting.
If your Moodle site has been configured correctly, you can use this app to:
- browse the content of your courses, even when offline
- receive instant notifications of messages and other events
- quickly find and contact other people in your courses
- upload images, audio, videos and other files from your mobile device
- view your course grades
- and more!
Please see http://docs.moodle.org/en/Mobile_app for all the latest information.
Wed really appreciate any good reviews about the functionality so far, and your suggestions on what else you want this app to do!
The app requires the following permissions:
Record audio - For recording audio to upload to Moodle
Read and modify the contents of your SD card - Contents are downloaded to the SD Card so you can see them offline
Network access - To be able to connect with your Moodle site and check if you are connected or not to switch to offline mode
Run at startup - So you receive local notifications even when the app is running in the background
Prevent phone from sleeping - So you can receive push notifications anytime\';'."\n";
foreach ($keys as $key => $value) {
if (isset($translations[$key]) && $value->file == 'local_moodlemobileapp') {
$string .= '$string[\''.$key.'\'] = \''.str_replace("'", "\'", $translations[$key]).'\';'."\n";
}
}
$string .= '$string[\'pluginname\'] = \'Moodle Mobile language strings\';'."\n";
file_put_contents('../../moodle-local_moodlemobileapp/lang/en/local_moodlemobileapp.php', $string."\n");
}

View File

@ -27,6 +27,17 @@
"addon.badges.version": "badges",
"addon.badges.warnexpired": "badges",
"addon.block_activitymodules.pluginname": "block_activity_modules",
"addon.block_badges.pluginname": "block_badges",
"addon.block_blogmenu.pluginname": "block_blog_menu",
"addon.block_blogrecent.nocourses": "block_blog_recent",
"addon.block_blogrecent.pluginname": "block_blog_recent",
"addon.block_blogtags.pluginname": "block_blog_tags",
"addon.block_calendarmonth.pluginname": "block_calendar_month",
"addon.block_calendarupcoming.pluginname": "block_calendar_upcoming",
"addon.block_comments.pluginname": "block_comments",
"addon.block_completionstatus.pluginname": "block_completionstatus",
"addon.block_glossaryrandom.pluginname": "block_glossary_random",
"addon.block_learningplans.pluginname": "block_lp",
"addon.block_myoverview.all": "block_myoverview",
"addon.block_myoverview.favourites": "block_myoverview",
"addon.block_myoverview.future": "block_myoverview",
@ -38,13 +49,20 @@
"addon.block_myoverview.past": "block_myoverview",
"addon.block_myoverview.pluginname": "block_myoverview",
"addon.block_myoverview.title": "block_myoverview",
"addon.block_newsitems.pluginname": "block_news_items",
"addon.block_onlineusers.pluginname": "block_online_users",
"addon.block_privatefiles.pluginname": "block_private_files",
"addon.block_recentactivity.pluginname": "block_recent_activity",
"addon.block_recentlyaccessedcourses.nocourses": "block_recentlyaccessedcourses",
"addon.block_recentlyaccessedcourses.pluginname": "block_recentlyaccessedcourses",
"addon.block_recentlyaccesseditems.noitems": "block_recentlyaccesseditems",
"addon.block_recentlyaccesseditems.pluginname": "block_recentlyaccesseditems",
"addon.block_rssclient.pluginname": "block_rss_client",
"addon.block_selfcompletion.pluginname": "block_selfcompletion",
"addon.block_sitemainmenu.pluginname": "block_site_main_menu",
"addon.block_starredcourses.nocourses": "block_starredcourses",
"addon.block_starredcourses.pluginname": "block_starredcourses",
"addon.block_tags.pluginname": "block_tags",
"addon.block_timeline.duedate": "block_timeline",
"addon.block_timeline.next30days": "block_timeline",
"addon.block_timeline.next3months": "block_timeline",
@ -66,18 +84,61 @@
"addon.blog.publishtoworld": "blog",
"addon.blog.showonlyyourentries": "local_moodlemobileapp",
"addon.blog.siteblogheading": "blog",
"addon.calendar.allday": "calendar",
"addon.calendar.calendar": "calendar",
"addon.calendar.calendarevent": "local_moodlemobileapp",
"addon.calendar.calendarevents": "local_moodlemobileapp",
"addon.calendar.calendarreminders": "local_moodlemobileapp",
"addon.calendar.confirmeventdelete": "calendar",
"addon.calendar.confirmeventseriesdelete": "calendar",
"addon.calendar.currentmonth": "local_moodlemobileapp",
"addon.calendar.daynext": "calendar",
"addon.calendar.dayprev": "calendar",
"addon.calendar.defaultnotificationtime": "local_moodlemobileapp",
"addon.calendar.deleteallevents": "calendar",
"addon.calendar.deleteevent": "calendar",
"addon.calendar.deleteoneevent": "calendar",
"addon.calendar.durationminutes": "calendar",
"addon.calendar.durationnone": "calendar",
"addon.calendar.durationuntil": "calendar",
"addon.calendar.editevent": "calendar",
"addon.calendar.errorloadevent": "local_moodlemobileapp",
"addon.calendar.errorloadevents": "local_moodlemobileapp",
"addon.calendar.eventcalendareventdeleted": "calendar",
"addon.calendar.eventduration": "calendar",
"addon.calendar.eventendtime": "calendar",
"addon.calendar.eventkind": "calendar",
"addon.calendar.eventname": "calendar",
"addon.calendar.eventstarttime": "calendar",
"addon.calendar.eventtype": "calendar",
"addon.calendar.fri": "calendar",
"addon.calendar.friday": "calendar",
"addon.calendar.gotoactivity": "calendar",
"addon.calendar.invalidtimedurationminutes": "calendar",
"addon.calendar.invalidtimedurationuntil": "calendar",
"addon.calendar.mon": "calendar",
"addon.calendar.monday": "calendar",
"addon.calendar.monthlyview": "calendar",
"addon.calendar.newevent": "calendar",
"addon.calendar.noevents": "local_moodlemobileapp",
"addon.calendar.nopermissiontoupdatecalendar": "error",
"addon.calendar.reminders": "local_moodlemobileapp",
"addon.calendar.repeatedevents": "calendar",
"addon.calendar.repeateditall": "calendar",
"addon.calendar.repeateditthis": "calendar",
"addon.calendar.repeatevent": "calendar",
"addon.calendar.repeatweeksl": "calendar",
"addon.calendar.sat": "calendar",
"addon.calendar.saturday": "calendar",
"addon.calendar.setnewreminder": "local_moodlemobileapp",
"addon.calendar.sun": "calendar",
"addon.calendar.sunday": "calendar",
"addon.calendar.thu": "calendar",
"addon.calendar.thursday": "calendar",
"addon.calendar.today": "calendar",
"addon.calendar.tomorrow": "calendar",
"addon.calendar.tue": "calendar",
"addon.calendar.tuesday": "calendar",
"addon.calendar.typecategory": "calendar",
"addon.calendar.typeclose": "calendar",
"addon.calendar.typecourse": "calendar",
@ -87,6 +148,11 @@
"addon.calendar.typeopen": "calendar",
"addon.calendar.typesite": "calendar",
"addon.calendar.typeuser": "calendar",
"addon.calendar.upcomingevents": "calendar",
"addon.calendar.wed": "calendar",
"addon.calendar.wednesday": "calendar",
"addon.calendar.when": "calendar",
"addon.calendar.yesterday": "calendar",
"addon.competency.activities": "tool_lp",
"addon.competency.competencies": "competency",
"addon.competency.competenciesmostoftennotproficientincourse": "tool_lp",
@ -355,6 +421,7 @@
"addon.mod_assign_submission_onlinetext.wordlimitexceeded": "assignsubmission_onlinetext",
"addon.mod_book.errorchapter": "book",
"addon.mod_book.modulenameplural": "book",
"addon.mod_book.tagarea_book_chapters": "book",
"addon.mod_book.toc": "book",
"addon.mod_chat.beep": "chat",
"addon.mod_chat.chatreport": "chat",
@ -414,6 +481,7 @@
"addon.mod_data.confirmdeleterecord": "data",
"addon.mod_data.descending": "data",
"addon.mod_data.disapprove": "data",
"addon.mod_data.edittagsnotsupported": "local_moodlemobileapp",
"addon.mod_data.emptyaddform": "data",
"addon.mod_data.entrieslefttoadd": "data",
"addon.mod_data.entrieslefttoaddtoview": "data",
@ -438,8 +506,10 @@
"addon.mod_data.recorddisapproved": "data",
"addon.mod_data.resetsettings": "data",
"addon.mod_data.search": "data",
"addon.mod_data.searchbytagsnotsupported": "local_moodlemobileapp",
"addon.mod_data.selectedrequired": "data",
"addon.mod_data.single": "data",
"addon.mod_data.tagarea_data_records": "data",
"addon.mod_data.timeadded": "data",
"addon.mod_data.timemodified": "data",
"addon.mod_data.usedate": "data",
@ -532,6 +602,7 @@
"addon.mod_forum.reply": "forum",
"addon.mod_forum.replyplaceholder": "forum",
"addon.mod_forum.subject": "forum",
"addon.mod_forum.tagarea_forum_posts": "forum",
"addon.mod_forum.thisforumhasduedate": "forum",
"addon.mod_forum.thisforumisdue": "forum",
"addon.mod_forum.unlockdiscussion": "forum",
@ -566,9 +637,11 @@
"addon.mod_glossary.modulenameplural": "glossary",
"addon.mod_glossary.noentriesfound": "local_moodlemobileapp",
"addon.mod_glossary.searchquery": "local_moodlemobileapp",
"addon.mod_glossary.tagarea_glossary_entries": "glossary",
"addon.mod_imscp.deploymenterror": "imscp",
"addon.mod_imscp.modulenameplural": "imscp",
"addon.mod_imscp.showmoduledescription": "local_moodlemobileapp",
"addon.mod_imscp.toc": "imscp",
"addon.mod_lesson.answer": "lesson",
"addon.mod_lesson.attempt": "lesson",
"addon.mod_lesson.attemptheader": "lesson",
@ -665,6 +738,7 @@
"addon.mod_quiz.attemptnumber": "quiz",
"addon.mod_quiz.attemptquiznow": "quiz",
"addon.mod_quiz.attemptstate": "quiz",
"addon.mod_quiz.canattemptbutnotsubmit": "local_moodlemobileapp",
"addon.mod_quiz.cannotsubmitquizdueto": "local_moodlemobileapp",
"addon.mod_quiz.clearchoice": "qtype_multichoice",
"addon.mod_quiz.comment": "quiz",
@ -737,6 +811,7 @@
"addon.mod_quiz.warningattemptfinished": "local_moodlemobileapp",
"addon.mod_quiz.warningdatadiscarded": "local_moodlemobileapp",
"addon.mod_quiz.warningdatadiscardedfromfinished": "local_moodlemobileapp",
"addon.mod_quiz.warningquestionsnotsupported": "local_moodlemobileapp",
"addon.mod_quiz.yourfinalgradeis": "quiz",
"addon.mod_resource.errorwhileloadingthecontent": "local_moodlemobileapp",
"addon.mod_resource.modifieddate": "resource",
@ -820,6 +895,7 @@
"addon.mod_wiki.pageexists": "wiki",
"addon.mod_wiki.pagename": "wiki",
"addon.mod_wiki.subwiki": "local_moodlemobileapp",
"addon.mod_wiki.tagarea_wiki_pages": "wiki",
"addon.mod_wiki.titleshouldnotbeempty": "local_moodlemobileapp",
"addon.mod_wiki.viewpage": "local_moodlemobileapp",
"addon.mod_wiki.wikipage": "local_moodlemobileapp",
@ -898,7 +974,9 @@
"addon.mod_workshop_assessment_rubric.mustchooseone": "workshopform_rubric",
"addon.notes.addnewnote": "notes",
"addon.notes.coursenotes": "notes",
"addon.notes.deleteconfirm": "notes",
"addon.notes.eventnotecreated": "notes",
"addon.notes.eventnotedeleted": "notes",
"addon.notes.nonotes": "notes",
"addon.notes.note": "notes",
"addon.notes.notes": "notes",
@ -1231,6 +1309,7 @@
"core.answered": "quiz",
"core.areyousure": "moodle",
"core.back": "moodle",
"core.block.blocks": "moodle",
"core.cancel": "moodle",
"core.cannotconnect": "local_moodlemobileapp",
"core.cannotdownloadfiles": "local_moodlemobileapp",
@ -1246,6 +1325,16 @@
"core.clicktoseefull": "local_moodlemobileapp",
"core.close": "repository",
"core.comments": "moodle",
"core.comments.addcomment": "moodle",
"core.comments.comments": "moodle",
"core.comments.commentscount": "moodle",
"core.comments.commentsnotworking": "local_moodlemobileapp",
"core.comments.deletecommentbyon": "moodle",
"core.comments.eventcommentcreated": "moodle",
"core.comments.eventcommentdeleted": "moodle",
"core.comments.nocomments": "moodle",
"core.comments.savecomment": "moodle",
"core.comments.warningcommentsnotsent": "local_moodlemobileapp",
"core.commentscount": "moodle",
"core.commentsnotworking": "local_moodlemobileapp",
"core.completion-alt-auto-fail": "completion",
@ -1260,6 +1349,8 @@
"core.completion-alt-manual-y-override": "completion",
"core.confirmcanceledit": "local_moodlemobileapp",
"core.confirmdeletefile": "repository",
"core.confirmgotabroot": "local_moodlemobileapp",
"core.confirmgotabrootdefault": "local_moodlemobileapp",
"core.confirmloss": "local_moodlemobileapp",
"core.confirmopeninbrowser": "local_moodlemobileapp",
"core.considereddigitalminor": "moodle",
@ -1306,6 +1397,7 @@
"core.course.warningmanualcompletionmodified": "local_moodlemobileapp",
"core.course.warningofflinemanualcompletiondeleted": "local_moodlemobileapp",
"core.coursedetails": "moodle",
"core.coursenogroups": "local_moodlemobileapp",
"core.courses.addtofavourites": "block_myoverview",
"core.courses.allowguests": "enrol_guest",
"core.courses.availablecourses": "moodle",
@ -1323,6 +1415,7 @@
"core.courses.filtermycourses": "local_moodlemobileapp",
"core.courses.frontpage": "admin",
"core.courses.hidecourse": "block_myoverview",
"core.courses.ignore": "admin",
"core.courses.mycourses": "moodle",
"core.courses.mymoodle": "admin",
"core.courses.nocourses": "my",
@ -1333,6 +1426,7 @@
"core.courses.password": "local_moodlemobileapp",
"core.courses.paymentrequired": "moodle",
"core.courses.paypalaccepted": "enrol_paypal",
"core.courses.reload": "moodle",
"core.courses.removefromfavourites": "block_myoverview",
"core.courses.search": "moodle",
"core.courses.searchcourses": "moodle",
@ -1383,6 +1477,7 @@
"core.erroropenfilenoextension": "local_moodlemobileapp",
"core.erroropenpopup": "local_moodlemobileapp",
"core.errorrenamefile": "local_moodlemobileapp",
"core.errorsomedatanotdownloaded": "local_moodlemobileapp",
"core.errorsync": "local_moodlemobileapp",
"core.errorsyncblocked": "local_moodlemobileapp",
"core.explanationdigitalminor": "moodle",
@ -1435,6 +1530,7 @@
"core.grades.range": "grades",
"core.grades.rank": "grades",
"core.grades.weight": "grades",
"core.group": "moodle",
"core.groupsseparate": "moodle",
"core.groupsvisible": "moodle",
"core.hasdatatosync": "local_moodlemobileapp",
@ -1446,6 +1542,7 @@
"core.image": "local_moodlemobileapp",
"core.imageviewer": "local_moodlemobileapp",
"core.info": "moodle",
"core.invalidformdata": "error",
"core.ios": "local_moodlemobileapp",
"core.labelsep": "langconfig",
"core.lastaccess": "moodle",
@ -1465,6 +1562,8 @@
"core.login.confirmdeletesite": "local_moodlemobileapp",
"core.login.connect": "local_moodlemobileapp",
"core.login.connecttomoodle": "local_moodlemobileapp",
"core.login.connecttomoodleapp": "local_moodlemobileapp",
"core.login.connecttoworkplaceapp": "local_moodlemobileapp",
"core.login.contactyouradministrator": "local_moodlemobileapp",
"core.login.contactyouradministratorissue": "local_moodlemobileapp",
"core.login.createaccount": "moodle",
@ -1602,12 +1701,14 @@
"core.nopermissionerror": "local_moodlemobileapp",
"core.nopermissions": "error",
"core.noresults": "moodle",
"core.noselection": "form",
"core.notapplicable": "local_moodlemobileapp",
"core.notenrolledprofile": "moodle",
"core.notice": "moodle",
"core.notingroup": "moodle",
"core.notsent": "local_moodlemobileapp",
"core.now": "moodle",
"core.nummore": "local_moodlemobileapp",
"core.numwords": "moodle",
"core.offline": "message",
"core.ok": "moodle",
@ -1670,6 +1771,9 @@
"core.sec": "moodle",
"core.secs": "moodle",
"core.seemoredetail": "survey",
"core.selectacategory": "moodle",
"core.selectacourse": "moodle",
"core.selectagroup": "moodle",
"core.send": "message",
"core.sending": "chat",
"core.serverconnection": "error",
@ -1695,6 +1799,8 @@
"core.settings.disabled": "lesson",
"core.settings.displayformat": "local_moodlemobileapp",
"core.settings.enabledownloadsection": "local_moodlemobileapp",
"core.settings.enablefirebaseanalytics": "local_moodlemobileapp",
"core.settings.enablefirebaseanalyticsdescription": "local_moodlemobileapp",
"core.settings.enablerichtexteditor": "local_moodlemobileapp",
"core.settings.enablerichtexteditordescription": "local_moodlemobileapp",
"core.settings.enablesyncwifi": "local_moodlemobileapp",
@ -1703,6 +1809,8 @@
"core.settings.errorsyncsite": "local_moodlemobileapp",
"core.settings.estimatedfreespace": "local_moodlemobileapp",
"core.settings.filesystemroot": "local_moodlemobileapp",
"core.settings.fontsize": "local_moodlemobileapp",
"core.settings.fontsizecharacter": "block_accessibility/char",
"core.settings.general": "moodle",
"core.settings.language": "moodle",
"core.settings.license": "moodle",
@ -1738,6 +1846,7 @@
"core.sharedfiles.sharedfiles": "local_moodlemobileapp",
"core.sharedfiles.successstorefile": "local_moodlemobileapp",
"core.show": "moodle",
"core.showless": "form",
"core.showmore": "form",
"core.site": "moodle",
"core.sitehome.sitehome": "moodle",
@ -1770,6 +1879,20 @@
"core.submit": "moodle",
"core.success": "moodle",
"core.tablet": "local_moodlemobileapp",
"core.tag.defautltagcoll": "moodle",
"core.tag.errorareanotsupported": "local_moodlemobileapp",
"core.tag.inalltagcoll": "moodle",
"core.tag.itemstaggedwith": "moodle",
"core.tag.notagsfound": "moodle",
"core.tag.searchtags": "moodle",
"core.tag.showingfirsttags": "moodle",
"core.tag.tag": "moodle",
"core.tag.tagarea_course": "moodle",
"core.tag.tagarea_course_modules": "moodle",
"core.tag.tagarea_post": "moodle",
"core.tag.tagarea_user": "moodle",
"core.tag.tags": "moodle",
"core.tag.warningareasnotsupported": "local_moodlemobileapp",
"core.teachers": "moodle",
"core.thereisdatatosync": "local_moodlemobileapp",
"core.thisdirection": "langconfig",
@ -1786,6 +1909,7 @@
"core.unlimited": "moodle",
"core.unzipping": "local_moodlemobileapp",
"core.upgraderunning": "error",
"core.user": "moodle",
"core.user.address": "moodle",
"core.user.city": "moodle",
"core.user.contact": "local_moodlemobileapp",

View File

@ -1,38 +0,0 @@
#!/bin/bash
#
# Script for generating the Desktop builds
#
sudo apt-get install -y libnss3-dev
npm install -g electron-builder electron
electron-builder install-app-deps
jq -s '.[0] + {"name": "moodledesktop"}' package.json > package_new.json
mv package_new.json package.json
rm -Rf desktop/dist
npm run desktop.dist -- -l --x64 --ia32
if [ ! -z $GIT_ORG_PRIVATE ] && [ ! -z $GIT_TOKEN ] ; then
git clone -q https://$GIT_TOKEN@github.com/moodlemobile/bma-apps-builds.git ../apps
mv desktop/dist/*.AppImage ../apps
cd ../apps
chmod +x *.AppImage
mv *i386.AppImage linux-ia32.AppImage
mv Moodle*.AppImage linux-x64.AppImage
ls
tar -czvf MoodleDesktop32.tar.gz linux-ia32.AppImage
tar -czvf MoodleDesktop64.tar.gz linux-x64.AppImage
rm *.AppImage
git add .
git commit -m "Linux desktop versions from Travis build $TRAVIS_BUILD_NUMBER"
git push
fi

View File

@ -26,6 +26,10 @@ define('MOODLE_INTERNAL', 1);
define('LANGPACKSFOLDER', '../../moodle-langpacks');
define('ASSETSPATH', '../src/assets/lang/');
define('CONFIG', '../src/config.json');
define('OVERRIDE_LANG_SUFIX', false);
global $strings;
require_once('lang_functions.php');
$config = file_get_contents(CONFIG);
$config = (array) json_decode($config);
@ -42,355 +46,17 @@ if (isset($argv[1]) && !empty($argv[1])) {
$languages = $config_langs;
}
// Process the index file, just once.
$keys = file_get_contents('langindex.json');
$keys = (array) json_decode($keys);
$keys = get_langindex_keys();
foreach ($keys as $key => $value) {
$map = new StdClass();
if ($value == 'local_moodlemobileapp') {
$map->file = $value;
$map->string = $key;
} else {
$exp = explode('/', $value, 2);
$map->file = $exp[0];
if (count($exp) == 2) {
$map->string = $exp[1];
} else {
$exp = explode('.', $key, 3);
if (count($exp) == 3) {
$map->string = $exp[2];
} else {
$map->string = $exp[1];
}
}
}
$keys[$key] = $map;
}
$total = count ($keys);
echo "Total strings to translate $total\n";
$add_langs = array();
// Process the languages.
foreach ($languages as $lang) {
$ok = build_lang($lang, $keys, $total);
if ($ok) {
$add_langs[$lang] = $lang;
}
}
$added_langs = build_languages($languages, $keys);
if ($forcedetect) {
echo "\n\n\n";
$all_languages = glob(LANGPACKSFOLDER.'/*' , GLOB_ONLYDIR);
function get_lang_from_dir($dir) {
return str_replace('_', '-', explode('/', $dir)[3]);
}
$all_languages = array_map('get_lang_from_dir', $all_languages);
$detect_lang = array_diff($all_languages, $languages);
$new_langs = array();
foreach ($detect_lang as $lang) {
$new = detect_lang($lang, $keys, $total);
if ($new) {
$new_langs[$lang] = $lang;
}
}
$new_langs = detect_languages($languages, $keys);
if (!empty($new_langs)) {
echo "\n\n\nThe following languages are going to be added\n\n\n";
foreach ($new_langs as $lang) {
$ok = build_lang($lang, $keys, $total);
if ($ok) {
$add_langs[$lang] = $lang;
}
}
add_langs_to_config($add_langs, $config);
}
} else {
add_langs_to_config($add_langs, $config);
}
function add_langs_to_config($langs, $config) {
$changed = false;
$config_langs = get_object_vars($config['languages']);
foreach ($langs as $lang) {
if (!isset($config_langs[$lang])) {
$langfoldername = str_replace('-', '_', $lang);
$string = [];
include(LANGPACKSFOLDER.'/'.$langfoldername.'/langconfig.php');
$config['languages']->$lang = $string['thislanguage'];
$changed = true;
}
}
if ($changed) {
// Sort languages by key.
$config['languages'] = json_decode( json_encode( $config['languages'] ), true );
ksort($config['languages']);
$config['languages'] = json_decode( json_encode( $config['languages'] ), false );
file_put_contents(CONFIG, json_encode($config, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
$added_langs = build_languages($new_langs, $keys, $added_langs);
}
}
function build_lang($lang, $keys, $total) {
$local = 0;
$langFile = false;
$translations = [];
$langfoldername = str_replace('-', '_', $lang);
if (!is_dir(LANGPACKSFOLDER.'/'.$langfoldername) || !is_file(LANGPACKSFOLDER.'/'.$langfoldername.'/langconfig.php')) {
echo "Cannot translate $langfoldername, folder not found";
return false;
}
$string = [];
include(LANGPACKSFOLDER.'/'.$langfoldername.'/langconfig.php');
$parent = isset($string['parentlanguage']) ? $string['parentlanguage'] : "";
echo "Processing $lang";
if ($parent != "" && $parent != $lang) {
echo "($parent)";
}
// Add the translation to the array.
foreach ($keys as $key => $value) {
$file = LANGPACKSFOLDER.'/'.$langfoldername.'/'.$value->file.'.php';
// Apply translations.
if (!file_exists($file)) {
if (TOTRANSLATE) {
echo "\n\t\To translate $value->string on $value->file";
}
continue;
}
$string = [];
include($file);
if (!isset($string[$value->string]) || ($lang == 'en' && $value->file == 'local_moodlemobileapp')) {
// Not yet translated. Do not override.
if (!$langFile) {
// Load lang files just once.
$langFile = file_get_contents(ASSETSPATH.$lang.'.json');
$langFile = (array) json_decode($langFile);
}
if (is_array($langFile) && isset($langFile[$key])) {
$translations[$key] = $langFile[$key];
$local++;
}
if (TOTRANSLATE) {
echo "\n\t\tTo translate $value->string on $value->file";
}
continue;
} else {
$text = $string[$value->string];
}
if ($value->file != 'local_moodlemobileapp') {
$text = str_replace('$a->', '$a.', $text);
$text = str_replace('{$a', '{{$a', $text);
$text = str_replace('}', '}}', $text);
// Prevent double.
$text = str_replace(array('{{{', '}}}'), array('{{', '}}'), $text);
} else {
$local++;
}
$translations[$key] = html_entity_decode($text);
}
// Sort and save.
ksort($translations);
file_put_contents(ASSETSPATH.$lang.'.json', str_replace('\/', '/', json_encode($translations, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)));
$success = count($translations);
$percentage = floor($success/$total *100);
echo "\t\t$success of $total -> $percentage% ($local local)\n";
if ($lang == 'en') {
generate_local_moodlemobileapp($keys, $translations);
override_component_lang_files($keys, $translations);
}
return true;
}
function detect_lang($lang, $keys, $total) {
$success = 0;
$local = 0;
$langfoldername = str_replace('-', '_', $lang);
if (!is_dir(LANGPACKSFOLDER.'/'.$langfoldername) || !is_file(LANGPACKSFOLDER.'/'.$langfoldername.'/langconfig.php')) {
echo "Cannot translate $langfoldername, folder not found";
return false;
}
$string = [];
include(LANGPACKSFOLDER.'/'.$langfoldername.'/langconfig.php');
$parent = isset($string['parentlanguage']) ? $string['parentlanguage'] : "";
if (!isset($string['thislanguage'])) {
echo "Cannot translate $langfoldername, name not found";
return false;
}
echo "Checking $lang";
if ($parent != "" && $parent != $lang) {
echo "($parent)";
}
$langname = $string['thislanguage'];
echo " ".$langname." -D";
// Add the translation to the array.
foreach ($keys as $key => $value) {
$file = LANGPACKSFOLDER.'/'.$langfoldername.'/'.$value->file.'.php';
// Apply translations.
if (!file_exists($file)) {
continue;
}
$string = [];
include($file);
if (!isset($string[$value->string])) {
continue;
} else {
$text = $string[$value->string];
}
if ($value->file == 'local_moodlemobileapp') {
$local++;
}
$success++;
}
$percentage = floor($success/$total *100);
echo "\t\t$success of $total -> $percentage% ($local local)";
if (($percentage > 75 && $local > 50) || ($percentage > 50 && $local > 75)) {
echo " \t DETECTED\n";
return true;
}
echo "\n";
return false;
}
function save_key($key, $value, $path) {
$filePath = $path . '/en.json';
$file = file_get_contents($filePath);
$file = (array) json_decode($file);
$value = html_entity_decode($value);
if ($file[$key] != $value) {
$file[$key] = $value;
ksort($file);
file_put_contents($filePath, str_replace('\/', '/', json_encode($file, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)));
}
}
function override_component_lang_files($keys, $translations) {
echo "Override component lang files.\n";
foreach ($translations as $key => $value) {
$path = '../src/';
$exp = explode('.', $key, 3);
$type = $exp[0];
if (count($exp) == 3) {
$component = $exp[1];
$plainid = $exp[2];
} else {
$component = 'moodle';
$plainid = $exp[1];
}
switch($type) {
case 'core':
case 'addon':
switch($component) {
case 'moodle':
$path .= 'lang';
break;
default:
$path .= $type.'/'.str_replace('_', '/', $component).'/lang';
break;
}
break;
case 'assets':
$path .= $type.'/'.$component;
break;
}
if (is_file($path.'/en.json')) {
save_key($plainid, $value, $path);
}
}
}
/**
* Generates local moodle mobile app file to update languages in AMOS.
*
* @param [array] $keys Translation keys.
* @param [array] $translations English translations.
*/
function generate_local_moodlemobileapp($keys, $translations) {
echo "Generate local_moodlemobileapp.\n";
$string = '<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Version details.
*
* @package local
* @subpackage moodlemobileapp
* @copyright 2014 Juan Leyva <juanleyvadelgado@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string[\'appstoredescription\'] = \'NOTE: This official Moodle Mobile app will ONLY work with Moodle sites that have been set up to allow it. Please talk to your Moodle administrator if you have any problems connecting.
If your Moodle site has been configured correctly, you can use this app to:
- browse the content of your courses, even when offline
- receive instant notifications of messages and other events
- quickly find and contact other people in your courses
- upload images, audio, videos and other files from your mobile device
- view your course grades
- and more!
Please see http://docs.moodle.org/en/Mobile_app for all the latest information.
Wed really appreciate any good reviews about the functionality so far, and your suggestions on what else you want this app to do!
The app requires the following permissions:
Record audio - For recording audio to upload to Moodle
Read and modify the contents of your SD card - Contents are downloaded to the SD Card so you can see them offline
Network access - To be able to connect with your Moodle site and check if you are connected or not to switch to offline mode
Run at startup - So you receive local notifications even when the app is running in the background
Prevent phone from sleeping - So you can receive push notifications anytime\';'."\n";
foreach ($keys as $key => $value) {
if (isset($translations[$key]) && $value->file == 'local_moodlemobileapp') {
$string .= '$string[\''.$key.'\'] = \''.str_replace("'", "\'", $translations[$key]).'\';'."\n";
}
}
$string .= '$string[\'pluginname\'] = \'Moodle Mobile language strings\';'."\n";
file_put_contents('../../moodle-local_moodlemobileapp/lang/en/local_moodlemobileapp.php', $string."\n");
}
add_langs_to_config($added_langs, $config);

View File

@ -15,7 +15,7 @@
import { Injectable } from '@angular/core';
import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
import { CoreLoginHelperProvider } from '@core/login/providers/helper';
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
import { AddonBadgesProvider } from './badges';
/**
@ -26,7 +26,7 @@ export class AddonBadgesBadgeLinkHandler extends CoreContentLinksHandlerBase {
name = 'AddonBadgesBadgeLinkHandler';
pattern = /\/badges\/badge\.php.*([\?\&]hash=)/;
constructor(private badgesProvider: AddonBadgesProvider, private loginHelper: CoreLoginHelperProvider) {
constructor(private badgesProvider: AddonBadgesProvider, private linkHelper: CoreContentLinksHelperProvider) {
super();
}
@ -44,8 +44,7 @@ export class AddonBadgesBadgeLinkHandler extends CoreContentLinksHandlerBase {
return [{
action: (siteId, navCtrl?): void => {
// Always use redirect to make it the new history root (to avoid "loops" in history).
this.loginHelper.redirect('AddonBadgesIssuedBadgePage', {courseId: 0, badgeHash: params.hash}, siteId);
this.linkHelper.goInSite(navCtrl, 'AddonBadgesIssuedBadgePage', {courseId: 0, badgeHash: params.hash}, siteId);
}
}];
}

View File

@ -15,7 +15,7 @@
import { Injectable } from '@angular/core';
import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
import { CoreLoginHelperProvider } from '@core/login/providers/helper';
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
import { AddonBadgesProvider } from './badges';
/**
@ -27,7 +27,7 @@ export class AddonBadgesMyBadgesLinkHandler extends CoreContentLinksHandlerBase
featureName = 'CoreUserDelegate_AddonBadges';
pattern = /\/badges\/mybadges\.php/;
constructor(private badgesProvider: AddonBadgesProvider, private loginHelper: CoreLoginHelperProvider) {
constructor(private badgesProvider: AddonBadgesProvider, private linkHelper: CoreContentLinksHelperProvider) {
super();
}
@ -45,8 +45,7 @@ export class AddonBadgesMyBadgesLinkHandler extends CoreContentLinksHandlerBase
return [{
action: (siteId, navCtrl?): void => {
// Always use redirect to make it the new history root (to avoid "loops" in history).
this.loginHelper.redirect('AddonBadgesUserBadgesPage', {}, siteId);
this.linkHelper.goInSite(navCtrl, 'AddonBadgesUserBadgesPage', {}, siteId);
}
}];
}

View File

@ -38,7 +38,7 @@ export class AddonBlockActivityModulesHandler extends CoreBlockBaseHandler {
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number)
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {

View File

@ -0,0 +1,38 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockBadgesHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockBadgesHandler
]
})
export class AddonBlockBadgesModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockBadgesHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -0,0 +1,23 @@
.addon-block-badges core-block-pre-rendered {
.core-block-content {
ul.badges {
list-style: none;
@include margin-horizontal(0);
-webkit-padding-start: 0;
li {
position: relative;
display: inline-block;
padding-top: 1em;
text-align: center;
vertical-align: top;
width: 150px;
.badge-name {
display: block;
padding: 5px;
}
}
}
}
}

View File

@ -0,0 +1,3 @@
{
"pluginname": "Latest badges"
}

View File

@ -0,0 +1,51 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreBlockPreRenderedComponent } from '@core/block/components/pre-rendered-block/pre-rendered-block';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockBadgesHandler extends CoreBlockBaseHandler {
name = 'AddonBlockBadges';
blockName = 'badges';
constructor() {
super();
}
/**
* Returns the data needed to render the block.
*
* @param {Injector} injector Injector.
* @param {any} block The block to render.
* @param {string} contextLevel The context where the block will be used.
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {
title: 'addon.block_badges.pluginname',
class: 'addon-block-badges',
component: CoreBlockPreRenderedComponent
};
}
}

View File

@ -0,0 +1,38 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockBlogMenuHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockBlogMenuHandler
]
})
export class AddonBlockBlogMenuModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockBlogMenuHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -0,0 +1,16 @@
.addon-block-blog-menu core-block-pre-rendered {
.core-block-content {
ul.list {
list-style: none;
@include margin-horizontal(0);
-webkit-padding-start: 0;
li {
padding-bottom: 8px;
}
}
}
.core-block-footer {
display: none;
}
}

View File

@ -0,0 +1,3 @@
{
"pluginname": "Blog menu"
}

View File

@ -0,0 +1,51 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreBlockPreRenderedComponent } from '@core/block/components/pre-rendered-block/pre-rendered-block';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockBlogMenuHandler extends CoreBlockBaseHandler {
name = 'AddonBlockBlogMenu';
blockName = 'blog_menu';
constructor() {
super();
}
/**
* Returns the data needed to render the block.
*
* @param {Injector} injector Injector.
* @param {any} block The block to render.
* @param {string} contextLevel The context where the block will be used.
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {
title: 'addon.block_blogmenu.pluginname',
class: 'addon-block-blog-menu',
component: CoreBlockPreRenderedComponent
};
}
}

View File

@ -0,0 +1,38 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockBlogRecentHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockBlogRecentHandler
]
})
export class AddonBlockBlogRecentModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockBlogRecentHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -0,0 +1,13 @@
.addon-block-blog-recent core-block-pre-rendered {
.core-block-content {
ul.list {
list-style: none;
@include margin-horizontal(0);
-webkit-padding-start: 0;
li {
padding-bottom: 8px;
}
}
}
}

View File

@ -0,0 +1,3 @@
{
"pluginname": "Recent blog entries"
}

View File

@ -0,0 +1,51 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreBlockPreRenderedComponent } from '@core/block/components/pre-rendered-block/pre-rendered-block';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockBlogRecentHandler extends CoreBlockBaseHandler {
name = 'AddonBlockBlogRecent';
blockName = 'blog_recent';
constructor() {
super();
}
/**
* Returns the data needed to render the block.
*
* @param {Injector} injector Injector.
* @param {any} block The block to render.
* @param {string} contextLevel The context where the block will be used.
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {
title: 'addon.block_blogrecent.pluginname',
class: 'addon-block-blog-recent',
component: CoreBlockPreRenderedComponent
};
}
}

View File

@ -0,0 +1,38 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockBlogTagsHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockBlogTagsHandler
]
})
export class AddonBlockBlogTagsModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockBlogTagsHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -0,0 +1,88 @@
.addon-block-blog-tags core-block-pre-rendered {
.core-block-content {
ul.inline-list {
list-style: none;
@include margin-horizontal(0);
-webkit-padding-start: 0;
li {
padding: .2em;
display: inline-block;
a {
@extend ion-badge;
@extend .badge-md;
text-decoration: none;
}
.s20 {
font-size: 1.5em;
font-weight: bold;
}
.s19 {
font-size: 1.5em;
}
.s18 {
font-size: 1.4em;
font-weight: bold;
}
.s17 {
font-size: 1.4em;
}
.s16 {
font-size: 1.3em;
font-weight: bold;
}
.s15 {
font-size: 1.3em;
}
.s14 {
font-size: 1.2em;
font-weight: bold;
}
.s13 {
font-size: 1.2em;
}
.s12,
.s11 {
font-size: 1.1em;
font-weight: bold;
}
.s10,
.s9 {
font-size: 1.1em;
}
.s8,
.s7 {
font-size: 1em;
font-weight: bold;
}
.s6,
.s5 {
font-size: 1em;
}
.s4,
.s3 {
font-size: 0.9em;
font-weight: bold;
}
.s2,
.s1 {
font-size: 0.9em;
}
}
}
}
}

View File

@ -0,0 +1,3 @@
{
"pluginname": "Blog tags"
}

View File

@ -0,0 +1,51 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreBlockPreRenderedComponent } from '@core/block/components/pre-rendered-block/pre-rendered-block';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockBlogTagsHandler extends CoreBlockBaseHandler {
name = 'AddonBlockBlogTags';
blockName = 'blog_tags';
constructor() {
super();
}
/**
* Returns the data needed to render the block.
*
* @param {Injector} injector Injector.
* @param {any} block The block to render.
* @param {string} contextLevel The context where the block will be used.
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {
title: 'addon.block_blogtags.pluginname',
class: 'addon-block-blog-tags',
component: CoreBlockPreRenderedComponent
};
}
}

View File

@ -0,0 +1,38 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockCalendarMonthHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockCalendarMonthHandler
]
})
export class AddonBlockCalendarMonthModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockCalendarMonthHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -0,0 +1,3 @@
{
"pluginname": "Calendar"
}

View File

@ -0,0 +1,60 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreBlockOnlyTitleComponent } from '@core/block/components/only-title-block/only-title-block';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
import { AddonCalendarProvider } from '@addon/calendar/providers/calendar';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockCalendarMonthHandler extends CoreBlockBaseHandler {
name = 'AddonBlockCalendarMonth';
blockName = 'calendar_month';
constructor(private calendarProvider: AddonCalendarProvider) {
super();
}
/**
* Returns the data needed to render the block.
*
* @param {Injector} injector Injector.
* @param {any} block The block to render.
* @param {string} contextLevel The context where the block will be used.
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
let link = 'AddonCalendarListPage';
const linkParams: any = contextLevel == 'course' ? { courseId: instanceId } : {};
if (this.calendarProvider.canViewMonthInSite()) {
link = 'AddonCalendarIndexPage';
}
return {
title: 'addon.block_calendarmonth.pluginname',
class: 'addon-block-calendar-month',
component: CoreBlockOnlyTitleComponent,
link: link,
linkParams: linkParams
};
}
}

View File

@ -0,0 +1,38 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockCalendarUpcomingHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockCalendarUpcomingHandler
]
})
export class AddonBlockCalendarUpcomingModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockCalendarUpcomingHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -0,0 +1,3 @@
{
"pluginname": " Upcoming events"
}

View File

@ -0,0 +1,61 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreBlockOnlyTitleComponent } from '@core/block/components/only-title-block/only-title-block';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
import { AddonCalendarProvider } from '@addon/calendar/providers/calendar';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockCalendarUpcomingHandler extends CoreBlockBaseHandler {
name = 'AddonBlockCalendarUpcoming';
blockName = 'calendar_upcoming';
constructor(private calendarProvider: AddonCalendarProvider) {
super();
}
/**
* Returns the data needed to render the block.
*
* @param {Injector} injector Injector.
* @param {any} block The block to render.
* @param {string} contextLevel The context where the block will be used.
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
let link = 'AddonCalendarListPage';
const linkParams: any = contextLevel == 'course' ? { courseId: instanceId } : {};
if (this.calendarProvider.canViewMonthInSite()) {
link = 'AddonCalendarIndexPage';
linkParams.upcoming = true;
}
return {
title: 'addon.block_calendarupcoming.pluginname',
class: 'addon-block-calendar-upcoming',
component: CoreBlockOnlyTitleComponent,
link: link,
linkParams: linkParams
};
}
}

View File

@ -0,0 +1,38 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockCommentsHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockCommentsHandler
]
})
export class AddonBlockCommentsModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockCommentsHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -0,0 +1,3 @@
{
"pluginname": "Comments"
}

View File

@ -0,0 +1,53 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreBlockOnlyTitleComponent } from '@core/block/components/only-title-block/only-title-block';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockCommentsHandler extends CoreBlockBaseHandler {
name = 'AddonBlockComments';
blockName = 'comments';
constructor() {
super();
}
/**
* Returns the data needed to render the block.
*
* @param {Injector} injector Injector.
* @param {any} block The block to render.
* @param {string} contextLevel The context where the block will be used.
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {
title: 'addon.block_comments.pluginname',
class: 'addon-block-comments',
component: CoreBlockOnlyTitleComponent,
link: 'CoreCommentsViewerPage',
linkParams: { contextLevel: contextLevel, instanceId: instanceId,
componentName: 'block_comments', area: 'page_comments', itemId: 0 }
};
}
}

View File

@ -0,0 +1,38 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockCompletionStatusHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockCompletionStatusHandler
]
})
export class AddonBlockCompletionStatusModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockCompletionStatusHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -0,0 +1,3 @@
{
"pluginname": "Course completion status"
}

View File

@ -0,0 +1,52 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreBlockOnlyTitleComponent } from '@core/block/components/only-title-block/only-title-block';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockCompletionStatusHandler extends CoreBlockBaseHandler {
name = 'AddonBlockCompletionStatus';
blockName = 'completionstatus';
constructor() {
super();
}
/**
* Returns the data needed to render the block.
*
* @param {Injector} injector Injector.
* @param {any} block The block to render.
* @param {string} contextLevel The context where the block will be used.
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {
title: 'addon.block_completionstatus.pluginname',
class: 'addon-block-completion-status',
component: CoreBlockOnlyTitleComponent,
link: 'AddonCourseCompletionReportPage',
linkParams: { courseId: instanceId }
};
}
}

View File

@ -0,0 +1,38 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockGlossaryRandomHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockGlossaryRandomHandler
]
})
export class AddonBlockGlossaryRandomModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockGlossaryRandomHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -0,0 +1,3 @@
{
"pluginname": "Random glossary entry"
}

View File

@ -0,0 +1,49 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreBlockPreRenderedComponent } from '@core/block/components/pre-rendered-block/pre-rendered-block';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockGlossaryRandomHandler extends CoreBlockBaseHandler {
name = 'AddonBlockGlossaryRandom';
blockName = 'glossary_random';
constructor() {
super();
}
/**
* Returns the data needed to render the block.
*
* @param {Injector} injector Injector.
* @param {any} block The block to render.
* @param {string} contextLevel The context where the block will be used.
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {
title: block.contents.title || 'addon.block_glossaryrandom.pluginname',
class: 'addon-block-glossary-random',
component: CoreBlockPreRenderedComponent
};
}
}

View File

@ -0,0 +1,36 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockHtmlHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule
],
exports: [
],
providers: [
AddonBlockHtmlHandler
]
})
export class AddonBlockHtmlModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockHtmlHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -0,0 +1,50 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreBlockPreRenderedComponent } from '@core/block/components/pre-rendered-block/pre-rendered-block';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockHtmlHandler extends CoreBlockBaseHandler {
name = 'AddonBlockHtml';
blockName = 'html';
constructor() {
super();
}
/**
* Returns the data needed to render the block.
*
* @param {Injector} injector Injector.
* @param {any} block The block to render.
* @param {string} contextLevel The context where the block will be used.
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {
title: block.contents.title,
class: 'addon-block-html',
component: CoreBlockPreRenderedComponent
};
}
}

View File

@ -0,0 +1,3 @@
{
"pluginname": "Learning plans"
}

View File

@ -0,0 +1,40 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockLearningPlansHandler } from './providers/block-handler';
import { CoreBlockComponentsModule } from '@core/block/components/components.module';
@NgModule({
declarations: [
],
imports: [
IonicModule,
CoreBlockComponentsModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockLearningPlansHandler
]
})
export class AddonBlockLearningPlansModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockLearningPlansHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -0,0 +1,51 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreBlockOnlyTitleComponent } from '@core/block/components/only-title-block/only-title-block';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockLearningPlansHandler extends CoreBlockBaseHandler {
name = 'AddonBlockLearningPlans';
blockName = 'lp';
constructor() {
super();
}
/**
* Returns the data needed to render the block.
*
* @param {Injector} injector Injector.
* @param {any} block The block to render.
* @param {string} contextLevel The context where the block will be used.
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {
title: 'addon.block_learningplans.pluginname',
class: 'addon-block-learning-plans',
component: CoreBlockOnlyTitleComponent,
link: 'AddonCompetencyPlanListPage'
};
}
}

View File

@ -19,11 +19,11 @@
<!-- "Time" selector. -->
<ion-select text-start [title]="'core.show' | translate" [(ngModel)]="selectedFilter" (ngModelChange)="selectedChanged()" interface="popover" class="core-button-select">
<ion-option value="all">{{ 'addon.block_myoverview.all' | translate }}</ion-option>
<ion-option value="inprogress">{{ 'addon.block_myoverview.inprogress' | translate }}</ion-option>
<ion-option value="future">{{ 'addon.block_myoverview.future' | translate }}</ion-option>
<ion-option value="past">{{ 'addon.block_myoverview.past' | translate }}</ion-option>
<ion-option value="favourite" *ngIf="showFavourite">{{ 'addon.block_myoverview.favourites' | translate }}</ion-option>
<ion-option value="hidden" *ngIf="showHidden">{{ 'addon.block_myoverview.hiddencourses' | translate }}</ion-option>
<ion-option value="inprogress" [disabled]="disableInProgress">{{ 'addon.block_myoverview.inprogress' | translate }}</ion-option>
<ion-option value="future" [disabled]="disableFuture">{{ 'addon.block_myoverview.future' | translate }}</ion-option>
<ion-option value="past" [disabled]="disablePast">{{ 'addon.block_myoverview.past' | translate }}</ion-option>
<ion-option value="favourite" *ngIf="showFavourite" [disabled]="disableFavourite">{{ 'addon.block_myoverview.favourites' | translate }}</ion-option>
<ion-option value="hidden" *ngIf="showHidden" [disabled]="disableHidden">{{ 'addon.block_myoverview.hiddencourses' | translate }}</ion-option>
</ion-select>
</div>
<core-empty-box *ngIf="courses[selectedFilter].length == 0" image="assets/img/icons/courses.svg" [message]="'addon.block_myoverview.nocourses' | translate"></core-empty-box>

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit, Input, OnDestroy, ViewChild, Injector } from '@angular/core';
import { Component, OnInit, Input, OnDestroy, ViewChild, Injector, OnChanges, SimpleChange } from '@angular/core';
import { Searchbar } from 'ionic-angular';
import { CoreEventsProvider } from '@providers/events';
import { CoreUtilsProvider } from '@providers/utils/utils';
@ -32,7 +32,7 @@ import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component
selector: 'addon-block-myoverview',
templateUrl: 'addon-block-myoverview.html'
})
export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implements OnInit, OnDestroy {
export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implements OnInit, OnChanges, OnDestroy {
@ViewChild('searchbar') searchbar: Searchbar;
@Input() downloadEnabled: boolean;
@ -64,10 +64,14 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
showSortFilter = false;
downloadCourseEnabled: boolean;
downloadCoursesEnabled: boolean;
disableInProgress = false;
disablePast = false;
disableFuture = false;
disableFavourite = false;
disableHidden = false;
protected prefetchIconsInitialized = false;
protected isDestroyed;
protected downloadButtonObserver;
protected coursesObserver;
protected updateSiteObserver;
protected courseIds = [];
@ -87,18 +91,6 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
*/
ngOnInit(): void {
// Refresh the enabled flags if enabled.
this.downloadButtonObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED,
(data) => {
const wasEnabled = this.downloadEnabled;
this.downloadEnabled = data.enabled;
if (!wasEnabled && this.downloadEnabled && this.loaded) {
// Download all courses is enabled now, initialize it.
this.initPrefetchCoursesIcons();
}
});
this.downloadCourseEnabled = !this.coursesProvider.isDownloadCourseDisabledInSite();
this.downloadCoursesEnabled = !this.coursesProvider.isDownloadCoursesDisabledInSite();
@ -128,6 +120,16 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
});
}
/**
* Detect changes on input properties.
*/
ngOnChanges(changes: {[name: string]: SimpleChange}): void {
if (changes.downloadEnabled && !changes.downloadEnabled.previousValue && this.downloadEnabled && this.loaded) {
// Download all courses is enabled now, initialize it.
this.initPrefetchCoursesIcons();
}
}
/**
* Perform the invalidate content function.
*
@ -173,12 +175,17 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
this.courses.filter = '';
this.showFilter = false;
this.disableInProgress = this.courses.inprogress.length === 0;
this.disablePast = this.courses.past.length === 0;
this.disableFuture = this.courses.future.length === 0;
this.showSelectorFilter = courses.length > 0 && (this.courses.past.length > 0 || this.courses.future.length > 0 ||
typeof courses[0].enddate != 'undefined');
typeof courses[0].enddate != 'undefined');
this.showHidden = this.showSelectorFilter && typeof courses[0].hidden != 'undefined';
this.disableHidden = this.courses.hidden.length === 0;
this.showFavourite = this.showSelectorFilter && typeof courses[0].isfavourite != 'undefined';
if (!this.showSelectorFilter) {
// No selector, show all.
this.disableFavourite = this.courses.favourite.length === 0;
if (!this.showSelectorFilter || (this.selectedFilter === 'inprogress' && this.disableInProgress)) {
// No selector, or the default option is disabled, show all.
this.selectedFilter = 'all';
}
this.filteredCourses = this.courses[this.selectedFilter];
@ -350,6 +357,5 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
this.isDestroyed = true;
this.coursesObserver && this.coursesObserver.off();
this.updateSiteObserver && this.updateSiteObserver.off();
this.downloadButtonObserver && this.downloadButtonObserver.off();
}
}

View File

@ -50,7 +50,7 @@ export class AddonBlockMyOverviewHandler extends CoreBlockBaseHandler {
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number)
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {

View File

@ -0,0 +1,3 @@
{
"pluginname": "Latest announcements"
}

View File

@ -0,0 +1,38 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockNewsItemsHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockNewsItemsHandler
]
})
export class AddonBlockNewsItemsModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockNewsItemsHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -0,0 +1,26 @@
.addon-block-news-items core-block-pre-rendered {
.core-block-content {
.unlist {
list-style-type: none;
@include margin-horizontal(0);
-webkit-padding-start: 0;
li.post {
padding-bottom: 16px;
}
li.post:last-child {
padding-bottom: 0;
}
}
}
// Hide RSS link.
.core-block-footer {
a {
display: none;
}
a:first-child {
display: inline;
}
}
}

View File

@ -0,0 +1,49 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreBlockPreRenderedComponent } from '@core/block/components/pre-rendered-block/pre-rendered-block';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockNewsItemsHandler extends CoreBlockBaseHandler {
name = 'AddonBlockNewsItems';
blockName = 'news_items';
constructor() {
super();
}
/**
* Returns the data needed to render the block.
*
* @param {Injector} injector Injector.
* @param {any} block The block to render.
* @param {string} contextLevel The context where the block will be used.
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {
title: 'addon.block_newsitems.pluginname',
class: 'addon-block-news-items',
component: CoreBlockPreRenderedComponent
};
}
}

View File

@ -0,0 +1,3 @@
{
"pluginname": "Online users"
}

View File

@ -0,0 +1,38 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockOnlineUsersHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockOnlineUsersHandler
]
})
export class AddonBlockOnlineUsersModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockOnlineUsersHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -0,0 +1,48 @@
.addon-block-online-users core-block-pre-rendered .core-block-content {
max-height: 200px;
overflow-y: auto;
.item-inner,
.input-wrapper {
overflow-y: visible;
align-self: start;
}
.list {
@include margin-horizontal(0);
-webkit-padding-start: 0;
li.listentry {
clear: both;
list-style-type: none;
.user {
@include float(start);
position: relative;
padding-bottom: 16px;
.core-adapted-img-container {
display: inline;
@include margin-horizontal(0, 8px);
}
.userpicture {
vertical-align: text-bottom;
}
}
.message {
@include float(end);
margin-top: 3px;
}
.uservisibility { // No support on the app.
display: none;
}
}
}
.info {
text-align: center;
}
}

View File

@ -0,0 +1,49 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreBlockPreRenderedComponent } from '@core/block/components/pre-rendered-block/pre-rendered-block';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockOnlineUsersHandler extends CoreBlockBaseHandler {
name = 'AddonBlockOnlineUsers';
blockName = 'online_users';
constructor() {
super();
}
/**
* Returns the data needed to render the block.
*
* @param {Injector} injector Injector.
* @param {any} block The block to render.
* @param {string} contextLevel The context where the block will be used.
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {
title: 'addon.block_onlineusers.pluginname',
class: 'addon-block-online-users',
component: CoreBlockPreRenderedComponent
};
}
}

View File

@ -0,0 +1,3 @@
{
"pluginname": "Private files"
}

View File

@ -0,0 +1,38 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockPrivateFilesHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockPrivateFilesHandler
]
})
export class AddonBlockPrivateFilesModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockPrivateFilesHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -0,0 +1,52 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreBlockOnlyTitleComponent } from '@core/block/components/only-title-block/only-title-block';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockPrivateFilesHandler extends CoreBlockBaseHandler {
name = 'AddonBlockPrivateFiles';
blockName = 'private_files';
constructor() {
super();
}
/**
* Returns the data needed to render the block.
*
* @param {Injector} injector Injector.
* @param {any} block The block to render.
* @param {string} contextLevel The context where the block will be used.
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {
title: 'addon.block_privatefiles.pluginname',
class: 'addon-block-private-files',
component: CoreBlockOnlyTitleComponent,
link: 'AddonFilesListPage',
linkParams: {root: 'my'}
};
}
}

View File

@ -0,0 +1,3 @@
{
"pluginname": "Recent activity"
}

View File

@ -0,0 +1,51 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreBlockPreRenderedComponent } from '@core/block/components/pre-rendered-block/pre-rendered-block';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockRecentActivityHandler extends CoreBlockBaseHandler {
name = 'AddonBlockRecentActivity';
blockName = 'recent_activity';
constructor() {
super();
}
/**
* Returns the data needed to render the block.
*
* @param {Injector} injector Injector.
* @param {any} block The block to render.
* @param {string} contextLevel The context where the block will be used.
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {
title: 'addon.block_recentactivity.pluginname',
class: 'addon-block-recent-activity',
component: CoreBlockPreRenderedComponent
};
}
}

View File

@ -0,0 +1,38 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockRecentActivityHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockRecentActivityHandler
]
})
export class AddonBlockRecentActivityModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockRecentActivityHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -0,0 +1,20 @@
.addon-block-recent-activity core-block-pre-rendered {
.core-block-content {
.activitydate, .activityhead {
text-align: center;
}
.unlist {
list-style: none;
@include margin-horizontal(0);
-webkit-padding-start: 0;
li {
margin-bottom: 1em;
.head .date {
@include float(end);
}
}
}
}
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit, OnDestroy, Injector, Input } from '@angular/core';
import { Component, OnInit, OnDestroy, Injector, Input, OnChanges, SimpleChange } from '@angular/core';
import { CoreEventsProvider } from '@providers/events';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreSitesProvider } from '@providers/sites';
@ -30,7 +30,7 @@ import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component
selector: 'addon-block-recentlyaccessedcourses',
templateUrl: 'addon-block-recentlyaccessedcourses.html'
})
export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseComponent implements OnInit, OnDestroy {
export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseComponent implements OnInit, OnChanges, OnDestroy {
@Input() downloadEnabled: boolean;
courses = [];
@ -41,7 +41,6 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
protected prefetchIconsInitialized = false;
protected isDestroyed;
protected downloadButtonObserver;
protected coursesObserver;
protected courseIds = [];
protected fetchContentDefaultError = 'Error getting recent courses data.';
@ -59,18 +58,6 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
* Component being initialized.
*/
ngOnInit(): void {
// Refresh the enabled flags if enabled.
this.downloadButtonObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED,
(data) => {
const wasEnabled = this.downloadEnabled;
this.downloadEnabled = data.enabled;
if (!wasEnabled && this.downloadEnabled && this.loaded) {
// Download all courses is enabled now, initialize it.
this.initPrefetchCoursesIcons();
}
});
this.coursesObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, () => {
this.refreshContent();
@ -79,6 +66,16 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
super.ngOnInit();
}
/**
* Detect changes on input properties.
*/
ngOnChanges(changes: {[name: string]: SimpleChange}): void {
if (changes.downloadEnabled && !changes.downloadEnabled.previousValue && this.downloadEnabled && this.loaded) {
// Download all courses is enabled now, initialize it.
this.initPrefetchCoursesIcons();
}
}
/**
* Perform the invalidate content function.
*
@ -155,6 +152,5 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
ngOnDestroy(): void {
this.isDestroyed = true;
this.coursesObserver && this.coursesObserver.off();
this.downloadButtonObserver && this.downloadButtonObserver.off();
}
}

View File

@ -38,7 +38,7 @@ export class AddonBlockRecentlyAccessedCoursesHandler extends CoreBlockBaseHandl
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number)
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {

View File

@ -38,7 +38,7 @@ export class AddonBlockRecentlyAccessedItemsHandler extends CoreBlockBaseHandler
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number)
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {

View File

@ -0,0 +1,3 @@
{
"pluginname": "Remote RSS feeds"
}

View File

@ -0,0 +1,51 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreBlockPreRenderedComponent } from '@core/block/components/pre-rendered-block/pre-rendered-block';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockRssClientHandler extends CoreBlockBaseHandler {
name = 'AddonBlockRssClient';
blockName = 'rss_client';
constructor() {
super();
}
/**
* Returns the data needed to render the block.
*
* @param {Injector} injector Injector.
* @param {any} block The block to render.
* @param {string} contextLevel The context where the block will be used.
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {
title: block.contents.title || 'addon.block_rssclient.pluginname',
class: 'addon-block-rss-client',
component: CoreBlockPreRenderedComponent
};
}
}

View File

@ -0,0 +1,38 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockRssClientHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockRssClientHandler
]
})
export class AddonBlockRssClientModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockRssClientHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -0,0 +1,19 @@
.addon-block-rss-client core-block-pre-rendered {
.core-block-content {
.list {
list-style: none;
@include margin-horizontal(0);
-webkit-padding-start: 0;
li {
border-top: 1px solid $gray;
padding: 5px;
padding-bottom: 8px;
}
li:first-child {
border-top-width: 0;
}
}
}
}

View File

@ -0,0 +1,3 @@
{
"pluginname": "Self completion"
}

View File

@ -0,0 +1,52 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreBlockOnlyTitleComponent } from '@core/block/components/only-title-block/only-title-block';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockSelfCompletionHandler extends CoreBlockBaseHandler {
name = 'AddonBlockSelfCompletion';
blockName = 'selfcompletion';
constructor() {
super();
}
/**
* Returns the data needed to render the block.
*
* @param {Injector} injector Injector.
* @param {any} block The block to render.
* @param {string} contextLevel The context where the block will be used.
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {
title: 'addon.block_selfcompletion.pluginname',
class: 'addon-block-self-completion',
component: CoreBlockOnlyTitleComponent,
link: 'AddonCourseCompletionReportPage',
linkParams: { courseId: instanceId }
};
}
}

View File

@ -0,0 +1,38 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockSelfCompletionHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockSelfCompletionHandler
]
})
export class AddonBlockSelfCompletionModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockSelfCompletionHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -2,9 +2,11 @@
<h2>{{ 'addon.block_sitemainmenu.pluginname' | translate }}</h2>
</ion-item-divider>
<core-loading [hideUntil]="loaded" class="core-loading-center">
<ion-item text-wrap *ngIf="block.summary">
<core-format-text [text]="block.summary"></core-format-text>
</ion-item>
<ng-container *ngIf="mainMenuBlock">
<ion-item text-wrap *ngIf="mainMenuBlock.summary">
<core-format-text [text]="mainMenuBlock.summary"></core-format-text>
</ion-item>
<core-course-module *ngFor="let module of block.modules" [module]="module" [courseId]="siteHomeId" [downloadEnabled]="true" [section]="block"></core-course-module>
<core-course-module *ngFor="let module of mainMenuBlock.modules" [module]="module" [courseId]="siteHomeId" [downloadEnabled]="downloadEnabled" [section]="mainMenuBlock"></core-course-module>
</ng-container>
</core-loading>

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit, Injector } from '@angular/core';
import { Component, OnInit, Injector, Input } from '@angular/core';
import { CoreSitesProvider } from '@providers/sites';
import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
@ -28,7 +28,9 @@ import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component
templateUrl: 'addon-block-sitemainmenu.html'
})
export class AddonBlockSiteMainMenuComponent extends CoreBlockBaseComponent implements OnInit {
block: any;
@Input() downloadEnabled: boolean;
mainMenuBlock: any;
siteHomeId: number;
protected fetchContentDefaultError = 'Error getting main menu data.';
@ -60,9 +62,9 @@ export class AddonBlockSiteMainMenuComponent extends CoreBlockBaseComponent impl
promises.push(this.courseProvider.invalidateSections(this.siteHomeId));
promises.push(this.siteHomeProvider.invalidateNewsForum(this.siteHomeId));
if (this.block && this.block.modules) {
if (this.mainMenuBlock && this.mainMenuBlock.modules) {
// Invalidate modules prefetch data.
promises.push(this.prefetchDelegate.invalidateModules(this.block.modules, this.siteHomeId));
promises.push(this.prefetchDelegate.invalidateModules(this.mainMenuBlock.modules, this.siteHomeId));
}
return Promise.all(promises);
@ -75,11 +77,11 @@ export class AddonBlockSiteMainMenuComponent extends CoreBlockBaseComponent impl
*/
protected fetchContent(): Promise<any> {
return this.courseProvider.getSections(this.siteHomeId, false, true).then((sections) => {
this.block = sections.find((section) => section.section == 0);
this.mainMenuBlock = sections.find((section) => section.section == 0);
if (this.block) {
this.block.hasContent = this.courseHelper.sectionHasContent(this.block);
this.courseHelper.addHandlerDataForModules([this.block], this.siteHomeId);
if (this.mainMenuBlock) {
this.mainMenuBlock.hasContent = this.courseHelper.sectionHasContent(this.mainMenuBlock);
this.courseHelper.addHandlerDataForModules([this.mainMenuBlock], this.siteHomeId);
// Check if Site Home displays announcements. If so, remove it from the main menu block.
const currentSite = this.sitesProvider.getCurrentSite(),
@ -92,15 +94,15 @@ export class AddonBlockSiteMainMenuComponent extends CoreBlockBaseComponent impl
hasNewsItem = items.find((item) => { return item == '0'; });
}
if (hasNewsItem && this.block.modules) {
if (hasNewsItem && this.mainMenuBlock.modules) {
// Remove forum activity (news one only) from the main menu block to prevent duplicates.
return this.siteHomeProvider.getNewsForum(this.siteHomeId).then((forum) => {
// Search the module that belongs to site news.
for (let i = 0; i < this.block.modules.length; i++) {
const module = this.block.modules[i];
for (let i = 0; i < this.mainMenuBlock.modules.length; i++) {
const module = this.mainMenuBlock.modules[i];
if (module.modname == 'forum' && module.instance == forum.id) {
this.block.modules.splice(i, 1);
this.mainMenuBlock.modules.splice(i, 1);
break;
}
}

View File

@ -38,7 +38,7 @@ export class AddonBlockSiteMainMenuHandler extends CoreBlockBaseHandler {
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number)
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnInit, OnDestroy, Injector, Input } from '@angular/core';
import { Component, OnInit, OnDestroy, Injector, Input, OnChanges, SimpleChange } from '@angular/core';
import { CoreEventsProvider } from '@providers/events';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreSitesProvider } from '@providers/sites';
@ -30,7 +30,7 @@ import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component
selector: 'addon-block-starredcourses',
templateUrl: 'addon-block-starredcourses.html'
})
export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent implements OnInit, OnDestroy {
export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent implements OnInit, OnChanges, OnDestroy {
@Input() downloadEnabled: boolean;
courses = [];
@ -41,7 +41,6 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im
protected prefetchIconsInitialized = false;
protected isDestroyed;
protected downloadButtonObserver;
protected coursesObserver;
protected courseIds = [];
protected fetchContentDefaultError = 'Error getting starred courses data.';
@ -59,18 +58,6 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im
* Component being initialized.
*/
ngOnInit(): void {
// Refresh the enabled flags if enabled.
this.downloadButtonObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_DASHBOARD_DOWNLOAD_ENABLED_CHANGED,
(data) => {
const wasEnabled = this.downloadEnabled;
this.downloadEnabled = data.enabled;
if (!wasEnabled && this.downloadEnabled && this.loaded) {
// Download all courses is enabled now, initialize it.
this.initPrefetchCoursesIcons();
}
});
this.coursesObserver = this.eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, () => {
this.refreshContent();
@ -79,6 +66,16 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im
super.ngOnInit();
}
/**
* Detect changes on input properties.
*/
ngOnChanges(changes: {[name: string]: SimpleChange}): void {
if (changes.downloadEnabled && !changes.downloadEnabled.previousValue && this.downloadEnabled && this.loaded) {
// Download all courses is enabled now, initialize it.
this.initPrefetchCoursesIcons();
}
}
/**
* Perform the invalidate content function.
*
@ -155,6 +152,5 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im
ngOnDestroy(): void {
this.isDestroyed = true;
this.coursesObserver && this.coursesObserver.off();
this.downloadButtonObserver && this.downloadButtonObserver.off();
}
}

View File

@ -38,7 +38,7 @@ export class AddonBlockStarredCoursesHandler extends CoreBlockBaseHandler {
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number)
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {

View File

@ -0,0 +1,3 @@
{
"pluginname": "Tags"
}

View File

@ -0,0 +1,51 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable, Injector } from '@angular/core';
import { CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreBlockPreRenderedComponent } from '@core/block/components/pre-rendered-block/pre-rendered-block';
import { CoreBlockBaseHandler } from '@core/block/classes/base-block-handler';
/**
* Block handler.
*/
@Injectable()
export class AddonBlockTagsHandler extends CoreBlockBaseHandler {
name = 'AddonBlockTags';
blockName = 'tags';
constructor() {
super();
}
/**
* Returns the data needed to render the block.
*
* @param {Injector} injector Injector.
* @param {any} block The block to render.
* @param {string} contextLevel The context where the block will be used.
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {
title: 'addon.block_tags.pluginname',
class: 'addon-block-tags',
component: CoreBlockPreRenderedComponent
};
}
}

View File

@ -0,0 +1,38 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { AddonBlockTagsHandler } from './providers/block-handler';
@NgModule({
declarations: [
],
imports: [
IonicModule,
TranslateModule.forChild()
],
exports: [
],
providers: [
AddonBlockTagsHandler
]
})
export class AddonBlockTagsModule {
constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockTagsHandler) {
blockDelegate.registerHandler(blockHandler);
}
}

View File

@ -0,0 +1,106 @@
.addon-block-tags core-block-pre-rendered {
.core-block-content {
.tag_cloud {
text-align: center;
ul.inline-list {
list-style: none;
@include margin-horizontal(0);
-webkit-padding-start: 0;
li {
padding: .2em;
display: inline-block;
a {
@extend ion-badge;
@extend .badge-md;
text-decoration: none;
}
.s20 {
font-size: 2.7em;
}
.s19 {
font-size: 2.6em;
}
.s18 {
font-size: 2.5em;
}
.s17 {
font-size: 2.4em;
}
.s16 {
font-size: 2.3em;
}
.s15 {
font-size: 2.2em;
}
.s14 {
font-size: 2.1em;
}
.s13 {
font-size: 2em;
}
.s12 {
font-size: 1.9em;
}
.s11 {
font-size: 1.8em;
}
.s10 {
font-size: 1.7em;
}
.s9 {
font-size: 1.6em;
}
.s8 {
font-size: 1.5em;
}
.s7 {
font-size: 1.4em;
}
.s6 {
font-size: 1.3em;
}
.s5 {
font-size: 1.2em;
}
.s4 {
font-size: 1.1em;
}
.s3 {
font-size: 1em;
}
.s2 {
font-size: 0.9em;
}
.s1 {
font-size: 0.8em;
}
.s0 {
font-size: 0.7em;
}
}
}
}
}
}

View File

@ -55,7 +55,7 @@ export class AddonBlockTimelineHandler extends CoreBlockBaseHandler {
* @param {number} instanceId The instance ID associated with the context level.
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number)
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {

View File

@ -17,12 +17,14 @@ import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate';
import { CoreUserDelegate } from '@core/user/providers/user-delegate';
import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate';
import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate';
import { CoreTagAreaDelegate } from '@core/tag/providers/area-delegate';
import { AddonBlogProvider } from './providers/blog';
import { AddonBlogMainMenuHandler } from './providers/mainmenu-handler';
import { AddonBlogUserHandler } from './providers/user-handler';
import { AddonBlogCourseOptionHandler } from './providers/course-option-handler';
import { AddonBlogComponentsModule } from './components/components.module';
import { AddonBlogIndexLinkHandler } from './providers/index-link-handler';
import { AddonBlogTagAreaHandler } from './providers/tag-area-handler';
@NgModule({
declarations: [
@ -35,17 +37,20 @@ import { AddonBlogIndexLinkHandler } from './providers/index-link-handler';
AddonBlogMainMenuHandler,
AddonBlogUserHandler,
AddonBlogCourseOptionHandler,
AddonBlogIndexLinkHandler
AddonBlogIndexLinkHandler,
AddonBlogTagAreaHandler
]
})
export class AddonBlogModule {
constructor(mainMenuDelegate: CoreMainMenuDelegate, menuHandler: AddonBlogMainMenuHandler,
userHandler: AddonBlogUserHandler, userDelegate: CoreUserDelegate,
courseOptionHandler: AddonBlogCourseOptionHandler, courseOptionsDelegate: CoreCourseOptionsDelegate,
linkHandler: AddonBlogIndexLinkHandler, contentLinksDelegate: CoreContentLinksDelegate) {
linkHandler: AddonBlogIndexLinkHandler, contentLinksDelegate: CoreContentLinksDelegate,
tagAreaDelegate: CoreTagAreaDelegate, tagAreaHandler: AddonBlogTagAreaHandler) {
mainMenuDelegate.registerHandler(menuHandler);
userDelegate.registerHandler(userHandler);
courseOptionsDelegate.registerHandler(courseOptionHandler);
contentLinksDelegate.registerHandler(linkHandler);
tagAreaDelegate.registerHandler(tagAreaHandler);
}
}

View File

@ -20,6 +20,7 @@ import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CorePipesModule } from '@pipes/pipes.module';
import { CoreCommentsComponentsModule } from '@core/comments/components/components.module';
import { CoreTagComponentsModule } from '@core/tag/components/components.module';
import { AddonBlogEntriesComponent } from './entries/entries';
@NgModule({
@ -33,7 +34,8 @@ import { AddonBlogEntriesComponent } from './entries/entries';
CoreComponentsModule,
CoreDirectivesModule,
CorePipesModule,
CoreCommentsComponentsModule
CoreCommentsComponentsModule,
CoreTagComponentsModule
],
providers: [
],

View File

@ -4,9 +4,9 @@
</ion-refresher>
<core-loading [hideUntil]="loaded" class="core-loading-center">
<div class="safe-padding-horizontal">
<ion-item *ngIf="showMyIssuesToggle">
<ion-item *ngIf="showMyEntriesToggle">
<ion-label>{{ 'addon.blog.showonlyyourentries' | translate }}</ion-label>
<ion-toggle [(ngModel)]="onlyMyEntries"></ion-toggle>
<ion-toggle [(ngModel)]="onlyMyEntries" (ionChange)="onlyMyEntriesToggleChanged(onlyMyEntries)">></ion-toggle>
</ion-item>
</div>
<core-empty-box *ngIf="entries && entries.length == 0" icon="fa-newspaper-o" [message]="'addon.blog.noentriesyet' | translate"></core-empty-box>
@ -29,6 +29,10 @@
</ion-item>
<ion-card-content>
<core-format-text [text]="entry.summary" [component]="this.component" [componentId]="entry.id"></core-format-text>
<ion-item text-wrap *ngIf="tagsEnabled && entry.tags && entry.tags.length > 0">
<div item-start>{{ 'core.tag.tags' | translate }}:</div>
<core-tag-list [tags]="entry.tags"></core-tag-list>
</ion-item>
<ion-item *ngIf="commentsEnabled">
<core-comments [component]="this.component" [itemId]="entry.id" area="format_blog" [instanceId]="entry.userid" contextLevel="user"></core-comments>
</ion-item>

View File

@ -15,10 +15,12 @@
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { Content } from 'ionic-angular';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreSitesProvider } from '@providers/sites';
import { CoreUserProvider } from '@core/user/providers/user';
import { AddonBlogProvider } from '../../providers/blog';
import { CoreCommentsProvider } from '@core/comments/providers/comments';
import { CoreTagProvider } from '@core/tag/providers/tag';
/**
* Component that displays the blog entries.
@ -37,6 +39,9 @@ export class AddonBlogEntriesComponent implements OnInit {
protected filter = {};
protected pageLoaded = 0;
protected userPageLoaded = 0;
protected canLoadMoreEntries = false;
protected canLoadMoreUserEntries = true;
@ViewChild(Content) content: Content;
@ -45,14 +50,15 @@ export class AddonBlogEntriesComponent implements OnInit {
loadMoreError = false;
entries = [];
currentUserId: number;
showMyIssuesToggle = false;
showMyEntriesToggle = false;
onlyMyEntries = false;
component = AddonBlogProvider.COMPONENT;
commentsEnabled: boolean;
tagsEnabled: boolean;
constructor(protected blogProvider: AddonBlogProvider, protected domUtils: CoreDomUtilsProvider,
protected userProvider: CoreUserProvider, sitesProvider: CoreSitesProvider,
protected commentsProvider: CoreCommentsProvider) {
protected userProvider: CoreUserProvider, sitesProvider: CoreSitesProvider, protected utils: CoreUtilsProvider,
protected commentsProvider: CoreCommentsProvider, private tagProvider: CoreTagProvider) {
this.currentUserId = sitesProvider.getCurrentSiteUserId();
}
@ -63,6 +69,7 @@ export class AddonBlogEntriesComponent implements OnInit {
if (this.userId) {
this.filter['userid'] = this.userId;
}
this.showMyEntriesToggle = !this.userId;
if (this.courseId) {
this.filter['courseid'] = this.courseId;
@ -85,6 +92,7 @@ export class AddonBlogEntriesComponent implements OnInit {
}
this.commentsEnabled = !this.commentsProvider.areCommentsDisabledInSite();
this.tagsEnabled = this.tagProvider.areTagsAvailableInSite();
this.fetchEntries().then(() => {
this.blogProvider.logView(this.filter).catch(() => {
@ -104,9 +112,12 @@ export class AddonBlogEntriesComponent implements OnInit {
if (refresh) {
this.pageLoaded = 0;
this.userPageLoaded = 0;
}
return this.blogProvider.getEntries(this.filter, this.pageLoaded).then((result) => {
const loadPage = this.onlyMyEntries ? this.userPageLoaded : this.pageLoaded;
return this.blogProvider.getEntries(this.filter, loadPage).then((result) => {
const promises = result.entries.map((entry) => {
switch (entry.publishstate) {
case 'draft':
@ -131,16 +142,25 @@ export class AddonBlogEntriesComponent implements OnInit {
});
if (refresh) {
this.showMyIssuesToggle = false;
this.entries = result.entries;
} else {
this.entries = this.entries.concat(result.entries);
this.entries = this.utils.uniqueArray(this.entries.concat(result.entries), 'id').sort((a, b) => {
return b.created - a.created;
});
}
this.canLoadMore = result.totalentries > this.entries.length;
this.pageLoaded++;
this.showMyIssuesToggle = !this.userId;
if (this.onlyMyEntries) {
const count = this.entries.filter((entry) => {
return entry.userid == this.currentUserId;
}).length;
this.canLoadMoreUserEntries = result.totalentries > count;
this.canLoadMore = this.canLoadMoreUserEntries;
this.userPageLoaded++;
} else {
this.canLoadMoreEntries = result.totalentries > this.entries.length;
this.canLoadMore = this.canLoadMoreEntries;
this.pageLoaded++;
}
return Promise.all(promises);
}).catch((message) => {
@ -151,6 +171,30 @@ export class AddonBlogEntriesComponent implements OnInit {
});
}
/**
* Toggle between showing only my entries or not.
*
* @param {boolean} enabled If true, filter my entries. False otherwise.
*/
onlyMyEntriesToggleChanged(enabled: boolean): void {
if (enabled) {
const count = this.entries.filter((entry) => {
return entry.userid == this.currentUserId;
}).length;
this.userPageLoaded = Math.floor(count / AddonBlogProvider.ENTRIES_PER_PAGE);
this.filter['userid'] = this.currentUserId;
if (count == 0 && this.canLoadMoreUserEntries) {
// First time but no entry loaded. Try to load some.
this.loadMore();
}
} else {
delete this.filter['userid'];
}
this.canLoadMore = enabled ? this.canLoadMoreUserEntries : this.canLoadMoreEntries;
}
/**
* Function to load more entries.
*
@ -169,7 +213,23 @@ export class AddonBlogEntriesComponent implements OnInit {
* @param {any} refresher Refresher instance.
*/
refresh(refresher?: any): void {
this.blogProvider.invalidateEntries(this.filter).finally(() => {
const promises = this.entries.map((entry) => {
return this.commentsProvider.invalidateCommentsData('user', entry.userid, this.component, entry.id, 'format_blog');
});
promises.push(this.blogProvider.invalidateEntries(this.filter));
if (this.showMyEntriesToggle) {
this.filter['userid'] = this.currentUserId;
promises.push(this.blogProvider.invalidateEntries(this.filter));
if (!this.onlyMyEntries) {
delete this.filter['userid'];
}
}
this.utils.allPromises(promises).finally(() => {
this.fetchEntries(true).finally(() => {
if (refresher) {
refresher.complete();

View File

@ -78,10 +78,10 @@ export class AddonBlogCourseOptionHandler implements CoreCourseOptionsHandler {
* Returns the data needed to render the handler.
*
* @param {Injector} injector Injector.
* @param {number} courseId The course ID.
* @param {number} course The course.
* @return {CoreCourseOptionsHandlerData|Promise<CoreCourseOptionsHandlerData>} Data or promise resolved with the data.
*/
getDisplayData(injector: Injector, courseId: number): CoreCourseOptionsHandlerData | Promise<CoreCourseOptionsHandlerData> {
getDisplayData(injector: Injector, course: any): CoreCourseOptionsHandlerData | Promise<CoreCourseOptionsHandlerData> {
return {
title: 'addon.blog.blog',
class: 'addon-blog-handler',

View File

@ -15,7 +15,7 @@
import { Injectable } from '@angular/core';
import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
import { CoreLoginHelperProvider } from '@core/login/providers/helper';
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
import { AddonBlogProvider } from './blog';
/**
@ -27,7 +27,7 @@ export class AddonBlogIndexLinkHandler extends CoreContentLinksHandlerBase {
featureName = 'CoreUserDelegate_AddonBlog:blogs';
pattern = /\/blog\/index\.php/;
constructor(private blogProvider: AddonBlogProvider, private loginHelper: CoreLoginHelperProvider) {
constructor(private blogProvider: AddonBlogProvider, private linkHelper: CoreContentLinksHelperProvider) {
super();
}
@ -53,8 +53,7 @@ export class AddonBlogIndexLinkHandler extends CoreContentLinksHandlerBase {
return [{
action: (siteId, navCtrl?): void => {
// Always use redirect to make it the new history root (to avoid "loops" in history).
this.loginHelper.redirect('AddonBlogEntriesPage', pageParams, siteId);
this.linkHelper.goInSite(navCtrl, 'AddonBlogEntriesPage', pageParams, siteId, !Object.keys(pageParams).length);
}
}];
}

View File

@ -0,0 +1,58 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { Injectable, Injector } from '@angular/core';
import { CoreTagAreaHandler } from '@core/tag/providers/area-delegate';
import { CoreTagHelperProvider } from '@core/tag/providers/helper';
import { CoreTagFeedComponent } from '@core/tag/components/feed/feed';
import { AddonBlogProvider } from './blog';
/**
* Handler to support tags.
*/
@Injectable()
export class AddonBlogTagAreaHandler implements CoreTagAreaHandler {
name = 'AddonBlogTagAreaHandler';
type = 'core/post';
constructor(private tagHelper: CoreTagHelperProvider, private blogProvider: AddonBlogProvider) {}
/**
* Whether or not the handler is enabled on a site level.
* @return {boolean|Promise<boolean>} Whether or not the handler is enabled on a site level.
*/
isEnabled(): boolean | Promise<boolean> {
return this.blogProvider.isPluginEnabled();
}
/**
* Parses the rendered content of a tag index and returns the items.
*
* @param {string} content Rendered content.
* @return {any[]|Promise<any[]>} Area items (or promise resolved with the items).
*/
parseContent(content: string): any[] | Promise<any[]> {
return this.tagHelper.parseFeedContent(content);
}
/**
* Get the component to use to display items.
*
* @param {Injector} injector Injector.
* @return {any|Promise<any>} The component (or promise resolved with component) to use, undefined if not found.
*/
getComponent(injector: Injector): any | Promise<any> {
return CoreTagFeedComponent;
}
}

View File

@ -63,7 +63,7 @@ export class AddonBlogUserHandler implements CoreUserProfileHandler {
action: (event, navCtrl, user, courseId): void => {
event.preventDefault();
event.stopPropagation();
// Always use redirect to make it the new history root (to avoid "loops" in history).
this.linkHelper.goInSite(navCtrl, 'AddonBlogEntriesPage', { userId: user.id, courseId: courseId });
}
};

View File

@ -14,18 +14,26 @@
import { NgModule } from '@angular/core';
import { AddonCalendarProvider } from './providers/calendar';
import { AddonCalendarOfflineProvider } from './providers/calendar-offline';
import { AddonCalendarHelperProvider } from './providers/helper';
import { AddonCalendarSyncProvider } from './providers/calendar-sync';
import { AddonCalendarMainMenuHandler } from './providers/mainmenu-handler';
import { AddonCalendarSyncCronHandler } from './providers/sync-cron-handler';
import { AddonCalendarViewLinkHandler } from './providers/view-link-handler';
import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate';
import { CoreCronDelegate } from '@providers/cron';
import { CoreInitDelegate } from '@providers/init';
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
import { CoreLoginHelperProvider } from '@core/login/providers/helper';
import { CoreUpdateManagerProvider } from '@providers/update-manager';
import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate';
// List of providers (without handlers).
export const ADDON_CALENDAR_PROVIDERS: any[] = [
AddonCalendarProvider,
AddonCalendarHelperProvider
AddonCalendarOfflineProvider,
AddonCalendarHelperProvider,
AddonCalendarSyncProvider
];
@NgModule({
@ -35,15 +43,24 @@ export const ADDON_CALENDAR_PROVIDERS: any[] = [
],
providers: [
AddonCalendarProvider,
AddonCalendarOfflineProvider,
AddonCalendarHelperProvider,
AddonCalendarMainMenuHandler
AddonCalendarSyncProvider,
AddonCalendarMainMenuHandler,
AddonCalendarSyncCronHandler,
AddonCalendarViewLinkHandler
]
})
export class AddonCalendarModule {
constructor(mainMenuDelegate: CoreMainMenuDelegate, calendarHandler: AddonCalendarMainMenuHandler,
initDelegate: CoreInitDelegate, calendarProvider: AddonCalendarProvider, loginHelper: CoreLoginHelperProvider,
localNotificationsProvider: CoreLocalNotificationsProvider, updateManager: CoreUpdateManagerProvider) {
localNotificationsProvider: CoreLocalNotificationsProvider, updateManager: CoreUpdateManagerProvider,
cronDelegate: CoreCronDelegate, syncHandler: AddonCalendarSyncCronHandler,
contentLinksDelegate: CoreContentLinksDelegate, viewLinkHandler: AddonCalendarViewLinkHandler) {
mainMenuDelegate.registerHandler(calendarHandler);
cronDelegate.register(syncHandler);
contentLinksDelegate.registerHandler(viewLinkHandler);
initDelegate.ready().then(() => {
calendarProvider.scheduleAllSitesEventsNotifications();
@ -58,7 +75,13 @@ export class AddonCalendarModule {
return;
}
loginHelper.redirect('AddonCalendarListPage', {eventId: data.eventid}, data.siteId);
// Check which page we should load.
calendarProvider.canViewMonth(data.siteId).then((canView) => {
const pageName = canView ? 'AddonCalendarIndexPage' : 'AddonCalendarListPage';
loginHelper.redirect(pageName, {eventId: data.eventid}, data.siteId);
});
});
});
}

View File

@ -0,0 +1,67 @@
<!-- Add buttons to the nav bar. -->
<core-navbar-buttons end prepend>
<core-context-menu>
<core-context-menu-item *ngIf="canNavigate && !isCurrentMonth && displayNavButtons" [priority]="900" [content]="'addon.calendar.currentmonth' | translate" [iconAction]="'fa-calendar-times-o'" (action)="goToCurrentMonth()"></core-context-menu-item>
</core-context-menu>
</core-navbar-buttons>
<core-loading [hideUntil]="loaded" class="core-loading-center">
<!-- Period name and arrows to navigate. -->
<ion-grid no-padding class="addon-calendar-navigation">
<ion-row align-items-center>
<ion-col text-start *ngIf="canNavigate">
<a ion-button icon-only clear (click)="loadPrevious()" [title]="'core.previous' | translate">
<ion-icon name="arrow-back" md="ios-arrow-back"></ion-icon>
</a>
</ion-col>
<ion-col text-center class="addon-calendar-period">
<h3>{{ periodName }}</h3>
</ion-col>
<ion-col text-end *ngIf="canNavigate">
<a ion-button icon-only clear (click)="loadNext()" [title]="'core.next' | translate">
<ion-icon name="arrow-forward" md="ios-arrow-forward"></ion-icon>
</a>
</ion-col>
</ion-row>
</ion-grid>
<!-- Calendar view. -->
<ion-grid class="addon-calendar-months">
<!-- List of days. -->
<ion-row>
<ion-col text-center *ngFor="let day of weekDays" class="addon-calendar-weekday">
<span class="hidden-tablet" [title]="day.fullname | translate">{{ day.shortname | translate }}</span>
<span class="hidden-phone">{{ day.fullname | translate }}</span>
</ion-col>
</ion-row>
<!-- Weeks. -->
<ion-row *ngFor="let week of weeks" class="addon-calendar-week">
<ion-col *ngFor="let value of week.prepadding" class="dayblank addon-calendar-day"></ion-col> <!-- Empty slots (first week). -->
<ion-col text-center *ngFor="let day of week.days" (click)="dayClicked(day.mday)" [ngClass]='{"hasevents": day.hasevents, "today": isCurrentMonth && day.istoday, "weekend": day.isweekend, "duration_finish": day.haslastdayofevent}' class="addon-calendar-day" [class.addon-calendar-event-past-day]="isPastMonth || day.ispast">
<p class="addon-calendar-day-number"><span>{{ day.mday }}</span></p>
<!-- In phone, display some dots to indicate the type of events. -->
<p class="hidden-tablet addon-calendar-dot-types"><span *ngFor="let type of day.calendareventtypes" class="calendar_event_type calendar_event_{{type}}"></span></p>
<!-- In tablet, display list of events. -->
<div class="hidden-phone addon-calendar-day-events">
<ng-container *ngFor="let event of day.filteredEvents | slice:0:4; let index = index">
<p *ngIf="index < 3 || day.filteredEvents.length == 4" class="addon-calendar-event" (click)="eventClicked(event, $event)" [class.addon-calendar-event-past]="event.ispast">
<span class="calendar_event_type calendar_event_{{event.formattedType}}"></span>
<ion-icon *ngIf="event.offline && !event.deleted" name="time"></ion-icon>
<ion-icon *ngIf="event.deleted" name="trash"></ion-icon>
<span class="addon-calendar-event-time">{{ event.timestart * 1000 | coreFormatDate: timeFormat }}</span>
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" alt="" role="presentation" class="core-module-icon">
<span class="addon-calendar-event-name">{{event.name}}</span>
</p>
</ng-container>
<p *ngIf="day.filteredEvents.length > 4" class="addon-calendar-day-more"><b>{{ 'core.nummore' | translate:{$a: day.filteredEvents.length - 3} }}</b></p>
</div>
</ion-col>
<ion-col *ngFor="let value of week.postpadding" class="dayblank addon-calendar-day"></ion-col> <!-- Empty slots (last week). -->
</ion-row>
</ion-grid>
</core-loading>

View File

@ -0,0 +1,196 @@
$calendar-event-category-color: $purple !default; // Purple.
$calendar-event-course-color: $red !default; // Red.
$calendar-event-group-color: $yellow !default; // Yellow.
$calendar-event-user-color: $blue !default; // Blue.
$calendar-event-site-color: $green !default; // Green.
$calendar-today-bgcolor: $core-color !default;
$calendar-today-color: $white !default;
$calendar-border-color: $gray !default;
ion-app.app-root page-addon-calendar-list,
ion-app.app-root page-addon-calendar-day,
ion-app.app-root addon-calendar-upcoming-events {
.item.addon-calendar-event {
> .icon {
color: white;
border-radius: 50%;
width: 36px;
height: 36px;
line-height: 36px;
&.fa {
font-size: 20px;
&::before {
width: 1.9em;
}
}
}
> .core-module-icon {
margin: 9px 8px 9px 8px;
}
&.addon-calendar-eventtype-category > .icon {
background-color: $calendar-event-category-color;
}
&.addon-calendar-eventtype-course > .icon {
background-color: $calendar-event-course-color;
}
&.addon-calendar-eventtype-group > .icon {
background-color: $calendar-event-group-color;
}
&.addon-calendar-eventtype-user > .icon {
background-color: $calendar-event-user-color;
}
&.addon-calendar-eventtype-site > .icon {
background-color: $calendar-event-site-color;
}
}
}
ion-app.app-root addon-calendar-calendar {
.addon-calendar-navigation {
@include padding(5px, 10px, null, 10px);
}
.addon-calendar-months {
background-color: white;
padding: 0;
}
.addon-calendar-day {
border-bottom: 1px solid $calendar-border-color;
@include border-end(1px, solid, $calendar-border-color);
overflow: hidden;
min-height: 60px;
&:first-child {
@include padding(null, null, null, 10px);
}
&:last-child {
@include border-end(0, null, null);
@include padding(null, 8px, null, null);
}
&.addon-calendar-event-past-day > .addon-calendar-dot-types,
&.addon-calendar-event-past-day > .addon-calendar-day-events {
opacity: 0.5;
}
.addon-calendar-day-number {
margin: 0;
span {
line-height: 24px;
font-weight: 500;
display: inline-block;
margin: 3px;
width: max-content;
width: 24px;
height: 24px;
text-align: center;
}
}
@include media-breakpoint-up(md) {
.addon-calendar-day-number {
text-align: left;
}
}
&.today .addon-calendar-day-number span {
background-color: $calendar-today-bgcolor;
color: $calendar-today-color;
border-radius: 50%;
}
&.dayblank {
background-color: $gray-lighter;
}
.addon-calendar-event {
margin-top: 0.6em;
margin-bottom: 0.6em;
overflow: hidden;
white-space: nowrap;
&.addon-calendar-event-past {
opacity: 0.5;
}
.addon-calendar-event-name {
font-weight: 500;
}
}
.addon-calendar-day-more {
@include margin(0.6em, null, 0.6em, 4px);
}
.addon-calendar-dot-types {
margin: 0;
}
}
.addon-calendar-period {
flex-grow: 3;
h3 {
margin-top: 10px;
font-size: 1.6rem;
}
}
.addon-calendar-weekday {
border-bottom: 1px solid $list-md-border-color;
}
.addon-calendar-day-events {
@include text-align('start');
ion-icon {
@include margin-horizontal(null, 2px);
font-size: 1em;
}
}
.addon-calendar-event, .addon-calendar-day-number, .addon-calendar-day-more {
cursor: pointer;
}
.calendar_event_type {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
border: 1px solid white;
@include margin-horizontal(1px, 1px);
&.calendar_event_category {
background-color: $calendar-event-category-color;
}
&.calendar_event_course {
background-color: $calendar-event-course-color;
}
&.calendar_event_group {
background-color: $calendar-event-group-color;
}
&.calendar_event_user {
background-color: $calendar-event-user-color;
}
&.calendar_event_site {
background-color: $calendar-event-site-color;
}
}
.core-module-icon {
@include margin-horizontal(1px, 1px);
width: 16px;
height: 16px;
display: inline-block;
vertical-align: bottom;
}
}

View File

@ -0,0 +1,515 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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, OnDestroy, OnInit, Input, OnChanges, SimpleChange, Output, EventEmitter } from '@angular/core';
import { CoreEventsProvider } from '@providers/events';
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { AddonCalendarProvider } from '../../providers/calendar';
import { AddonCalendarHelperProvider } from '../../providers/helper';
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
import { CoreAppProvider } from '@providers/app';
/**
* Component that displays a calendar.
*/
@Component({
selector: 'addon-calendar-calendar',
templateUrl: 'addon-calendar-calendar.html',
})
export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDestroy {
@Input() initialYear: number | string; // Initial year to load.
@Input() initialMonth: number | string; // Initial month to load.
@Input() courseId: number | string;
@Input() categoryId: number | string; // Category ID the course belongs to.
@Input() canNavigate?: string | boolean; // Whether to include arrows to change the month. Defaults to true.
@Input() displayNavButtons?: string | boolean; // Whether to display nav buttons created by this component. Defaults to true.
@Output() onEventClicked = new EventEmitter<number>();
@Output() onDayClicked = new EventEmitter<{day: number, month: number, year: number}>();
periodName: string;
weekDays: any[];
weeks: any[];
loaded = false;
timeFormat: string;
isCurrentMonth: boolean;
isPastMonth: boolean;
protected year: number;
protected month: number;
protected categoriesRetrieved = false;
protected categories = {};
protected currentSiteId: string;
protected offlineEvents: {[monthId: string]: {[day: number]: any[]}} = {}; // Offline events classified in month & day.
protected offlineEditedEventsIds = []; // IDs of events edited in offline.
protected deletedEvents = []; // Events deleted in offline.
protected currentTime: number;
// Observers.
protected undeleteEventObserver: any;
protected obsDefaultTimeChange: any;
constructor(eventsProvider: CoreEventsProvider,
sitesProvider: CoreSitesProvider,
localNotificationsProvider: CoreLocalNotificationsProvider,
private calendarProvider: AddonCalendarProvider,
private calendarHelper: AddonCalendarHelperProvider,
private calendarOffline: AddonCalendarOfflineProvider,
private domUtils: CoreDomUtilsProvider,
private timeUtils: CoreTimeUtilsProvider,
private utils: CoreUtilsProvider,
private coursesProvider: CoreCoursesProvider,
private appProvider: CoreAppProvider) {
this.currentSiteId = sitesProvider.getCurrentSiteId();
if (localNotificationsProvider.isAvailable()) {
// Re-schedule events if default time changes.
this.obsDefaultTimeChange = eventsProvider.on(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, () => {
this.weeks.forEach((week) => {
week.days.forEach((day) => {
calendarProvider.scheduleEventsNotifications(day.events);
});
});
}, this.currentSiteId);
}
// Listen for events "undeleted" (offline).
this.undeleteEventObserver = eventsProvider.on(AddonCalendarProvider.UNDELETED_EVENT_EVENT, (data) => {
if (data && data.eventId) {
// Mark it as undeleted, no need to refresh.
this.undeleteEvent(data.eventId);
// Remove it from the list of deleted events if it's there.
const index = this.deletedEvents.indexOf(data.eventId);
if (index != -1) {
this.deletedEvents.splice(index, 1);
}
}
}, this.currentSiteId);
}
/**
* Component loaded.
*/
ngOnInit(): void {
const now = new Date();
this.year = this.initialYear ? Number(this.initialYear) : now.getFullYear();
this.month = this.initialMonth ? Number(this.initialMonth) : now.getMonth() + 1;
this.calculateIsCurrentMonth();
this.fetchData();
}
/**
* Detect changes on input properties.
*/
ngOnChanges(changes: {[name: string]: SimpleChange}): void {
this.canNavigate = typeof this.canNavigate == 'undefined' ? true : this.utils.isTrueOrOne(this.canNavigate);
this.displayNavButtons = typeof this.displayNavButtons == 'undefined' ? true :
this.utils.isTrueOrOne(this.displayNavButtons);
if ((changes.courseId || changes.categoryId) && this.weeks) {
this.filterEvents();
}
}
/**
* Fetch contacts.
*
* @param {boolean} [refresh=false] True if we are refreshing events.
* @return {Promise<any>} Promise resolved when done.
*/
fetchData(refresh: boolean = false): Promise<any> {
const promises = [];
promises.push(this.loadCategories());
// Get offline events.
promises.push(this.calendarOffline.getAllEditedEvents().then((events) => {
// Format data.
events.forEach((event) => {
event.offline = true;
this.calendarHelper.formatEventData(event);
});
// Classify them by month.
this.offlineEvents = this.calendarHelper.classifyIntoMonths(events);
// Get the IDs of events edited in offline.
const filtered = events.filter((event) => {
return event.id > 0;
});
this.offlineEditedEventsIds = filtered.map((event) => {
return event.id;
});
}));
// Get events deleted in offline.
promises.push(this.calendarOffline.getAllDeletedEventsIds().then((ids) => {
this.deletedEvents = ids;
}));
// Get time format to use.
promises.push(this.calendarProvider.getCalendarTimeFormat().then((value) => {
this.timeFormat = value;
}));
return Promise.all(promises).then(() => {
return this.fetchEvents();
}).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
}).finally(() => {
this.loaded = true;
});
}
/**
* Fetch the events for current month.
*
* @return {Promise<any>} Promise resolved when done.
*/
fetchEvents(): Promise<any> {
// Don't pass courseId and categoryId, we'll filter them locally.
return this.calendarProvider.getMonthlyEvents(this.year, this.month).catch((error) => {
if (!this.appProvider.isOnline()) {
// Allow navigating to non-cached months in offline (behave as if using emergency cache).
return this.calendarHelper.getOfflineMonthWeeks(this.year, this.month);
} else {
return Promise.reject(error);
}
}).then((result) => {
// Calculate the period name. We don't use the one in result because it's in server's language.
this.periodName = this.timeUtils.userDate(new Date(this.year, this.month - 1).getTime(), 'core.strftimemonthyear');
this.weekDays = this.calendarProvider.getWeekDays(result.daynames[0].dayno);
this.weeks = result.weeks;
this.calculateIsCurrentMonth();
if (this.isCurrentMonth) {
const currentDay = new Date().getDate();
let isPast = true;
this.weeks.forEach((week) => {
week.days.some((day) => {
day.istoday = day.mday == currentDay;
day.ispast = isPast && !day.istoday;
isPast = day.ispast;
if (day.istoday) {
day.events.forEach((event) => {
event.ispast = this.isEventPast(event);
});
return true;
}
return day.istoday;
});
});
}
// Merge the online events with offline data.
this.mergeEvents();
// Filter events by course.
this.filterEvents();
});
}
/**
* Load categories to be able to filter events.
*
* @return {Promise<any>} Promise resolved when done.
*/
protected loadCategories(): Promise<any> {
if (this.categoriesRetrieved) {
// Already retrieved, stop.
return Promise.resolve();
}
return this.coursesProvider.getCategories(0, true).then((cats) => {
this.categoriesRetrieved = true;
this.categories = {};
// Index categories by ID.
cats.forEach((category) => {
this.categories[category.id] = category;
});
}).catch(() => {
// Ignore errors.
});
}
/**
* Filter events to only display events belonging to a certain course.
*/
filterEvents(): void {
const courseId = this.courseId ? Number(this.courseId) : undefined,
categoryId = this.categoryId ? Number(this.categoryId) : undefined;
this.weeks.forEach((week) => {
week.days.forEach((day) => {
if (!courseId || courseId < 0) {
day.filteredEvents = day.events;
} else {
day.filteredEvents = day.events.filter((event) => {
return this.calendarHelper.shouldDisplayEvent(event, courseId, categoryId, this.categories);
});
}
// Re-calculate some properties.
this.calendarHelper.calculateDayData(day, day.filteredEvents);
});
});
}
/**
* Refresh events.
*
* @param {boolean} [afterChange] Whether the refresh is done after an event has changed or has been synced.
* @return {Promise<any>} Promise resolved when done.
*/
refreshData(afterChange?: boolean): Promise<any> {
const promises = [];
// Don't invalidate monthly events after a change, it has already been handled.
if (!afterChange) {
promises.push(this.calendarProvider.invalidateMonthlyEvents(this.year, this.month));
}
promises.push(this.coursesProvider.invalidateCategories(0, true));
promises.push(this.calendarProvider.invalidateTimeFormat());
this.categoriesRetrieved = false; // Get categories again.
return Promise.all(promises).then(() => {
return this.fetchData(true);
});
}
/**
* Load next month.
*/
loadNext(): void {
this.increaseMonth();
this.loaded = false;
this.fetchEvents().catch((error) => {
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
this.decreaseMonth();
}).finally(() => {
this.loaded = true;
});
}
/**
* Load previous month.
*/
loadPrevious(): void {
this.decreaseMonth();
this.loaded = false;
this.fetchEvents().catch((error) => {
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
this.increaseMonth();
}).finally(() => {
this.loaded = true;
});
}
/**
* An event was clicked.
*
* @param {any} calendarEvent Calendar event..
* @param {MouseEvent} event Mouse event.
*/
eventClicked(calendarEvent: any, event: MouseEvent): void {
this.onEventClicked.emit(calendarEvent.id);
event.stopPropagation();
}
/**
* A day was clicked.
*
* @param {number} day Day.
*/
dayClicked(day: number): void {
this.onDayClicked.emit({day: day, month: this.month, year: this.year});
}
/**
* Check if user is viewing the current month.
*/
calculateIsCurrentMonth(): void {
const now = new Date();
this.currentTime = this.timeUtils.timestamp();
this.isCurrentMonth = this.year == now.getFullYear() && this.month == now.getMonth() + 1;
this.isPastMonth = this.year < now.getFullYear() || (this.year == now.getFullYear() && this.month < now.getMonth() + 1);
}
/**
* Go to current month.
*/
goToCurrentMonth(): void {
const now = new Date(),
initialMonth = this.month,
initialYear = this.year;
this.month = now.getMonth() + 1;
this.year = now.getFullYear();
this.loaded = false;
this.fetchEvents().then(() => {
this.isCurrentMonth = true;
}).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'addon.calendar.errorloadevents', true);
this.year = initialYear;
this.month = initialMonth;
}).finally(() => {
this.loaded = true;
});
}
/**
* Decrease the current month.
*/
protected decreaseMonth(): void {
if (this.month === 1) {
this.month = 12;
this.year--;
} else {
this.month--;
}
}
/**
* Increase the current month.
*/
protected increaseMonth(): void {
if (this.month === 12) {
this.month = 1;
this.year++;
} else {
this.month++;
}
}
/**
* Merge online events with the offline events of that period.
*/
protected mergeEvents(): void {
const monthOfflineEvents = this.offlineEvents[this.calendarHelper.getMonthId(this.year, this.month)];
this.weeks.forEach((week) => {
week.days.forEach((day) => {
// Format online events.
day.events.forEach(this.calendarHelper.formatEventData.bind(this.calendarHelper));
// Schedule notifications for the events retrieved (only future events will be scheduled).
this.calendarProvider.scheduleEventsNotifications(day.events);
if (monthOfflineEvents || this.deletedEvents.length) {
// There is offline data, merge it.
if (this.deletedEvents.length) {
// Mark as deleted the events that were deleted in offline.
day.events.forEach((event) => {
event.deleted = this.deletedEvents.indexOf(event.id) != -1;
});
}
if (this.offlineEditedEventsIds.length) {
// Remove the online events that were modified in offline.
day.events = day.events.filter((event) => {
return this.offlineEditedEventsIds.indexOf(event.id) == -1;
});
}
if (monthOfflineEvents && monthOfflineEvents[day.mday]) {
// Add the offline events (either new or edited).
day.events = this.sortEvents(day.events.concat(monthOfflineEvents[day.mday]));
}
}
});
});
}
/**
* Sort events by timestart.
*
* @param {any[]} events List to sort.
*/
protected sortEvents(events: any[]): any[] {
return events.sort((a, b) => {
if (a.timestart == b.timestart) {
return a.timeduration - b.timeduration;
}
return a.timestart - b.timestart;
});
}
/**
* Undelete a certain event.
*
* @param {number} eventId Event ID.
*/
protected undeleteEvent(eventId: number): void {
if (!this.weeks) {
return;
}
this.weeks.forEach((week) => {
week.days.forEach((day) => {
const event = day.events.find((event) => {
return event.id == eventId;
});
if (event) {
event.deleted = false;
}
});
});
}
/**
* Returns if the event is in the past or not.
* @param {any} event Event object.
* @return {boolean} True if it's in the past.
*/
isEventPast(event: any): boolean {
return (event.timestart + event.timeduration) < this.currentTime;
}
/**
* Component destroyed.
*/
ngOnDestroy(): void {
this.undeleteEventObserver && this.undeleteEventObserver.off();
this.obsDefaultTimeChange && this.obsDefaultTimeChange.off();
}
}

Some files were not shown because too many files have changed in this diff Show More