diff --git a/gulp/dev-config.js b/gulp/dev-config.js
deleted file mode 100644
index b270bf51f..000000000
--- a/gulp/dev-config.js
+++ /dev/null
@@ -1,69 +0,0 @@
-// (C) Copyright 2015 Moodle Pty Ltd.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-const fs = require('fs');
-
-const DEV_CONFIG_FILE = '.moodleapp-dev-config';
-
-/**
- * Class to read and write dev-config data from a file.
- */
-class DevConfig {
-
- constructor() {
- this.loadFileData();
- }
-
- /**
- * Get a setting.
- *
- * @param name Name of the setting to get.
- * @param defaultValue Value to use if not found.
- */
- get(name, defaultValue) {
- return typeof this.config[name] != 'undefined' ? this.config[name] : defaultValue;
- }
-
- /**
- * Load file data to memory.
- */
- loadFileData() {
- if (!fs.existsSync(DEV_CONFIG_FILE)) {
- this.config = {};
-
- return;
- }
-
- try {
- this.config = JSON.parse(fs.readFileSync(DEV_CONFIG_FILE));
- } catch (error) {
- console.error('Error reading dev config file.', error);
- this.config = {};
- }
- }
-
- /**
- * Save some settings.
- *
- * @param settings Object with the settings to save.
- */
- save(settings) {
- this.config = Object.assign(this.config, settings);
-
- // Save the data in the dev file.
- fs.writeFileSync(DEV_CONFIG_FILE, JSON.stringify(this.config, null, 4));
- }
-}
-
-module.exports = new DevConfig();
diff --git a/gulp/git.js b/gulp/git.js
deleted file mode 100644
index e7eacb000..000000000
--- a/gulp/git.js
+++ /dev/null
@@ -1,237 +0,0 @@
-// (C) Copyright 2015 Moodle Pty Ltd.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-const exec = require('child_process').exec;
-const fs = require('fs');
-const DevConfig = require('./dev-config');
-const Utils = require('./utils');
-
-/**
- * Class to run git commands.
- */
-class Git {
-
- /**
- * Create a patch.
- *
- * @param range Show only commits in the specified revision range.
- * @param saveTo Path to the file to save the patch to. If not defined, the patch contents will be returned.
- * @return Promise resolved when done. If saveTo not provided, it will return the patch contents.
- */
- createPatch(range, saveTo) {
- return new Promise((resolve, reject) => {
- exec(`git format-patch ${range} --stdout`, (err, result) => {
- if (err) {
- reject(err);
- return;
- }
-
- if (!saveTo) {
- resolve(result);
- return;
- }
-
- // Save it to a file.
- const directory = saveTo.substring(0, saveTo.lastIndexOf('/'));
- if (directory && directory != '.' && directory != '..' && !fs.existsSync(directory)) {
- fs.mkdirSync(directory);
- }
- fs.writeFileSync(saveTo, result);
-
- resolve();
- });
- });
- }
-
- /**
- * Get current branch.
- *
- * @return Promise resolved with the branch name.
- */
- getCurrentBranch() {
- return new Promise((resolve, reject) => {
- exec('git branch --show-current', (err, branch) => {
- if (branch) {
- resolve(branch.replace('\n', ''));
- } else {
- reject (err || 'Current branch not found.');
- }
- });
- });
- }
-
- /**
- * Get the HEAD commit for a certain branch.
- *
- * @param branch Name of the branch.
- * @param branchData Parsed branch data. If not provided it will be calculated.
- * @return HEAD commit.
- */
- async getHeadCommit(branch, branchData) {
- if (!branchData) {
- // Parse the branch to get the project and issue number.
- branchData = Utils.parseBranch(branch);
- }
-
- // Loop over the last commits to find the first commit messages that doesn't belong to the issue.
- const commitsString = await this.log(50, branch, '%s_____%H');
- const commits = commitsString.split('\n');
- commits.pop(); // Remove last element, it's an empty string.
-
- for (let i = 0; i < commits.length; i++) {
- const commit = commits[i];
- const match = Utils.getIssueFromCommitMessage(commit) == branchData.issue;
-
- if (i === 0 && !match) {
- // Most recent commit doesn't belong to the issue. Stop looking.
- break;
- }
-
- if (!match) {
- // The commit does not match any more, we found it!
- return commit.split('_____')[1];
- }
- }
-
- // Couldn't find the commit using the commit names, get the last commit in the integration branch.
- const remote = DevConfig.get('upstreamRemote', 'origin');
- console.log(`Head commit not found using commit messages. Get last commit from ${remote}/integration`);
- const hashes = await this.hashes(1, `${remote}/integration`);
-
- return hashes[0];
- }
-
- /**
- * Get the URL of a certain remote.
- *
- * @param remote Remote name.
- * @return Promise resolved with the remote URL.
- */
- getRemoteUrl(remote) {
- return new Promise((resolve, reject) => {
- exec(`git remote get-url ${remote}`, (err, url) => {
- if (url) {
- resolve(url.replace('\n', ''));
- } else {
- reject (err || 'Remote not found.');
- }
- });
- });
- }
-
- /**
- * Return the latest hashes from git log.
- *
- * @param count Number of commits to display.
- * @param range Show only commits in the specified revision range.
- * @param format Pretty-print the contents of the commit logs in a given format.
- * @return Promise resolved with the list of hashes.
- */
- async hashes(count, range, format) {
- format = format || '%H';
-
- const hashList = await this.log(count, range, format);
-
- const hashes = hashList.split('\n');
- hashes.pop(); // Remove last element, it's an empty string.
-
- return hashes;
- }
-
- /**
- * Calls the log command and returns the raw output.
- *
- * @param count Number of commits to display.
- * @param range Show only commits in the specified revision range.
- * @param format Pretty-print the contents of the commit logs in a given format.
- * @param path Show only commits that are enough to explain how the files that match the specified paths came to be.
- * @return Promise resolved with the result.
- */
- log(count, range, format, path) {
- if (typeof count == 'undefined') {
- count = 10;
- }
-
- let command = 'git log';
-
- if (count > 0) {
- command += ` -n ${count} `;
- }
- if (format) {
- command += ` --format=${format} `;
- }
- if (range){
- command += ` ${range} `;
- }
- if (path) {
- command += ` -- ${path}`;
- }
-
- return new Promise((resolve, reject) => {
- exec(command, (err, result, stderr) => {
- if (err) {
- reject(err);
- } else {
- resolve(result);
- }
- });
- });
- }
-
- /**
- * Return the latest titles of the commit messages.
- *
- * @param count Number of commits to display.
- * @param range Show only commits in the specified revision range.
- * @param path Show only commits that are enough to explain how the files that match the specified paths came to be.
- * @return Promise resolved with the list of titles.
- */
- async messages(count, range, path) {
- count = typeof count != 'undefined' ? count : 10;
-
- const messageList = await this.log(count, range, '%s', path);
-
- const messages = messageList.split('\n');
- messages.pop(); // Remove last element, it's an empty string.
-
- return messages;
- }
-
- /**
- * Push a branch.
- *
- * @param remote Remote to use.
- * @param branch Branch to push.
- * @param force Whether to force the push.
- * @return Promise resolved when done.
- */
- push(remote, branch, force) {
- return new Promise((resolve, reject) => {
- let command = `git push ${remote} ${branch}`;
- if (force) {
- command += ' -f';
- }
-
- exec(command, (err, result, stderr) => {
- if (err) {
- reject(err);
- } else {
- resolve();
- }
- });
- });
- }
-}
-
-module.exports = new Git();
diff --git a/gulp/jira.js b/gulp/jira.js
deleted file mode 100644
index 5e3474973..000000000
--- a/gulp/jira.js
+++ /dev/null
@@ -1,476 +0,0 @@
-// (C) Copyright 2015 Moodle Pty Ltd.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-const exec = require('child_process').exec;
-const https = require('https');
-const inquirer = require('inquirer');
-const fs = require('fs');
-const request = require('request'); // This lib is deprecated, but it was the only way I found to make upload files work.
-const DevConfig = require('./dev-config');
-const Git = require('./git');
-const Url = require('./url');
-const Utils = require('./utils');
-
-const apiVersion = 2;
-
-/**
- * Class to interact with Jira.
- */
-class Jira {
-
- /**
- * Ask the password to the user.
- *
- * @return Promise resolved with the password.
- */
- async askPassword() {
- const data = await inquirer.prompt([
- {
- type: 'password',
- name: 'password',
- message: `Please enter the password for the username ${this.username}.`,
- },
- ]);
-
- return data.password;
- }
-
- /**
- * Ask the user the tracker data.
- *
- * @return Promise resolved with the data, rejected if cannot get.
- */
- async askTrackerData() {
- const data = await inquirer.prompt([
- {
- type: 'input',
- name: 'url',
- message: 'Please enter the tracker URL.',
- default: 'https://tracker.moodle.org/',
- },
- {
- type: 'input',
- name: 'username',
- message: 'Please enter your tracker username.',
- },
- ]);
-
- DevConfig.save({
- 'tracker.url': data.url,
- 'tracker.username': data.username,
- });
-
- return data;
- }
-
- /**
- * Build URL to perform requests to Jira.
- *
- * @param uri URI to add the the Jira URL.
- * @return URL.
- */
- buildRequestUrl(uri) {
- return Utils.concatenatePaths([this.url, this.uri, '/rest/api/', apiVersion, uri]);
- }
-
- /**
- * Delete an attachment.
- *
- * @param attachmentId Attachment ID.
- * @return Promise resolved when done.
- */
- async deleteAttachment(attachmentId) {
- const response = await this.request(`attachment/${attachmentId}`, 'DELETE');
-
- if (response.status != 204) {
- throw new Error('Could not delete the attachment');
- }
- }
-
- /**
- * Load the issue info from jira server using a REST API call.
- *
- * @param key Key to identify the issue. E.g. MOBILE-1234.
- * @param fields Fields to get.
- * @return Promise resolved with the issue data.
- */
- async getIssue(key, fields) {
- fields = fields || '*all,-comment';
-
- await this.init(); // Initialize data if needed.
-
- const response = await this.request(`issue/${key}`, 'GET', {'fields': fields, 'expand': 'names'});
-
- if (response.status == 404) {
- throw new Error('Issue could not be found.');
- } else if (response.status != 200) {
- throw new Error('The tracker is not available.')
- }
-
- const issue = response.data;
- issue.named = {};
-
- // Populate the named fields in a separate key. Allows us to easily find them without knowing the field ID.
- const nameList = issue.names || {};
- for (const fieldKey in issue.fields) {
- if (nameList[fieldKey]) {
- issue.named[nameList[fieldKey]] = issue.fields[fieldKey];
- }
- }
-
- return issue
- }
-
- /**
- * Load the version info from the jira server using a rest api call.
- *
- * @return Promise resolved when done.
- */
- async getServerInfo() {
- const response = await this.request('serverInfo');
-
- if (response.status != 200) {
- throw new Error(`Unexpected response code: ${response.status}`, response);
- }
-
- this.version = response.data;
- }
-
- /**
- * Get tracker data to push an issue.
- *
- * @return Promise resolved with the data.
- */
- async getTrackerData() {
- // Check dev-config file first.
- let data = this.getTrackerDataFromDevConfig();
-
- if (data) {
- console.log('Using tracker data from dev-config file');
- return data;
- }
-
- // Try to use mdk now.
- try {
- data = await this.getTrackerDataFromMdk();
-
- console.log('Using tracker data from mdk');
-
- return data;
- } catch (error) {
- // MDK not available or not configured. Ask for the data.
- const trackerData = await this.askTrackerData();
-
- trackerData.fromInput = true;
-
- return trackerData;
- }
- }
-
- /**
- * Get tracker data from dev config file.
- *
- * @return Data, undefined if cannot get.
- */
- getTrackerDataFromDevConfig() {
- const url = DevConfig.get('tracker.url');
- const username = DevConfig.get('tracker.username');
-
- if (url && username) {
- return {
- url,
- username,
- };
- }
- }
-
- /**
- * Get tracker URL and username from mdk.
- *
- * @return Promise resolved with the data, rejected if cannot get.
- */
- getTrackerDataFromMdk() {
- return new Promise((resolve, reject) => {
- exec('mdk config show tracker.url', (err, url) => {
- if (!url) {
- reject(err || 'URL not found.');
- return;
- }
-
- exec('mdk config show tracker.username', (error, username) => {
- if (username) {
- resolve({
- url: url.replace('\n', ''),
- username: username.replace('\n', ''),
- });
- } else {
- reject(error || 'Username not found.');
- }
- });
- });
- });
- }
-
- /**
- * Initialize some data.
- *
- * @return Promise resolved when done.
- */
- async init() {
- if (this.initialized) {
- // Already initialized.
- return;
- }
-
- // Get tracker URL and username.
- let trackerData = await this.getTrackerData();
-
- this.url = trackerData.url;
- this.username = trackerData.username;
-
- const parsed = Url.parse(this.url);
- this.ssl = parsed.protocol == 'https';
- this.host = parsed.domain;
- this.uri = parsed.path;
-
- // Get the password.
- const keytar = require('keytar');
-
- this.password = await keytar.getPassword('mdk-jira-password', this.username); // Use same service name as mdk.
-
- if (!this.password) {
- // Ask the user.
- this.password = await this.askPassword();
- }
-
- while (!this.initialized) {
- try {
- await this.getServerInfo();
-
- this.initialized = true;
- keytar.setPassword('mdk-jira-password', this.username, this.password);
- } catch (error) {
- console.log('Error connecting to the server. Please make sure you entered the data correctly.', error);
- if (trackerData.fromInput) {
- // User entered the data manually, ask him again.
- trackerData = await this.askTrackerData();
-
- this.url = trackerData.url;
- this.username = trackerData.username;
- }
-
- this.password = await this.askPassword();
- }
- }
- }
-
- /**
- * Check if a certain issue could be a security issue.
- *
- * @param key Key to identify the issue. E.g. MOBILE-1234.
- * @return Promise resolved with boolean: whether it's a security issue.
- */
- async isSecurityIssue(key) {
- const issue = await this.getIssue(key, 'security');
-
- return issue.fields && !!issue.fields.security;
- }
-
- /**
- * Sends a request to the server and returns the data.
- *
- * @param uri URI to add the the Jira URL.
- * @param method Method to use. Defaults to 'GET'.
- * @param params Params to send as GET params (in the URL).
- * @param data JSON string with the data to send as POST/PUT params.
- * @param headers Headers to send.
- * @return Promise resolved with the result.
- */
- request(uri, method, params, data, headers) {
- uri = uri || '';
- method = (method || 'GET').toUpperCase();
- data = data || '';
- params = params || {};
- headers = headers || {};
- headers['Content-Type'] = 'application/json';
-
- return new Promise((resolve, reject) => {
-
- // Build the request URL.
- const url = Url.addParamsToUrl(this.buildRequestUrl(uri), params);
-
- // Initialize the request.
- const options = {
- method: method,
- auth: `${this.username}:${this.password}`,
- headers: headers,
- };
- const buildRequest = https.request(url, options);
-
- // Add data.
- if (data) {
- buildRequest.write(data);
- }
-
- // Treat response.
- buildRequest.on('response', (response) => {
- // Read the result.
- let result = '';
- response.on('data', (chunk) => {
- result += chunk;
- });
- response.on('end', () => {
- try {
- result = JSON.parse(result);
- } catch (error) {
- // Leave it as text.
- }
-
- resolve({
- status: response.statusCode,
- data: result,
- });
- });
- });
-
- buildRequest.on('error', (e) => {
- reject(e);
- });
-
- // Send the request.
- buildRequest.end();
- });
- }
-
- /**
- * Sets a set of fields for a certain issue in Jira.
- *
- * @param issueId Key to identify the issue. E.g. MOBILE-1234.
- * @param updates Object with the fields to update.
- * @return Promise resolved when done.
- */
- async setCustomFields(issueId, updates) {
- const issue = await this.getIssue(issueId);
- const update = {'fields': {}};
-
- // Detect which fields have changed.
- for (const updateName in updates) {
- const updateValue = updates[updateName];
- const remoteValue = issue.named[updateName];
-
- if (!remoteValue || remoteValue != updateValue) {
- // Map the label of the field with the field code.
- let fieldKey;
-
- for (const id in issue.names) {
- if (issue.names[id] == updateName) {
- fieldKey = id;
- break;
- }
- }
-
- if (!fieldKey) {
- throw new Error(`Could not find the field named ${updateName}.`);
- }
-
- update.fields[fieldKey] = updateValue;
- }
- }
-
- if (!Object.keys(update.fields).length) {
- // No fields to update.
- console.log('No updates required.')
- return;
- }
-
- const response = await this.request(`issue/${key}`, 'PUT', null, JSON.stringify(update));
-
- if (response.status != 204) {
- throw new Error(`Issue was not updated: ${response.status}`, response.data);
- }
-
- console.log('Issue updated successfully.');
- }
-
- /**
- * Upload a new attachment to an issue.
- *
- * @param key Key to identify the issue. E.g. MOBILE-1234.
- * @param filePath Path to the file to upload.
- * @return Promise resolved when done.
- */
- async upload(key, filePath) {
-
- const uri = `issue/${key}/attachments`;
- const headers = {
- 'X-Atlassian-Token': 'nocheck',
- }
-
- const response = await this.uploadFile(uri, 'file', filePath, headers);
-
- if (response.status != 200) {
- throw new Error('Could not upload file to Jira issue');
- }
-
- console.log('File successfully uploaded.')
- }
-
- /**
- * Upload a file to Jira.
- *
- * @param uri URI to add the the Jira URL.
- * @param fieldName Name of the form field where to put the file.
- * @param filePath Path to the file.
- * @param headers Headers.
- * @return Promise resolved with the result.
- */
- async uploadFile(uri, fieldName, filePath, headers) {
- uri = uri || '';
- headers = headers || {};
- headers['Content-Type'] = 'multipart/form-data';
-
- return new Promise((resolve) => {
- // Add the file to the form data.
- const formData = {};
- formData[fieldName] = {
- value: fs.createReadStream(filePath),
- options: {
- filename: filePath.substr(filePath.lastIndexOf('/') + 1),
- contentType: 'multipart/form-data',
- },
- };
-
- // Perform the request.
- const options = {
- url: this.buildRequestUrl(uri),
- method: 'POST',
- headers: headers,
- auth: {
- user: this.username,
- pass: this.password,
- },
- formData: formData,
- };
-
- request(options, (_err, httpResponse, body) => {
- resolve({
- status: httpResponse.statusCode,
- data: body,
- });
- });
- });
- }
-}
-
-module.exports = new Jira();
diff --git a/gulp/task-push.js b/gulp/task-push.js
deleted file mode 100644
index 54d151dde..000000000
--- a/gulp/task-push.js
+++ /dev/null
@@ -1,280 +0,0 @@
-// (C) Copyright 2015 Moodle Pty Ltd.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-const gulp = require('gulp');
-const inquirer = require('inquirer');
-const DevConfig = require('./dev-config');
-const Git = require('./git');
-const Jira = require('./jira');
-const Utils = require('./utils');
-
-/**
- * Task to push a git branch and update tracker issue.
- */
-class PushTask {
-
- /**
- * Ask the user whether he wants to continue.
- *
- * @return Promise resolved with boolean: true if he wants to continue.
- */
- async askConfirmContinue() {
- const answer = await inquirer.prompt([
- {
- type: 'input',
- name: 'confirm',
- message: 'Are you sure you want to continue?',
- default: 'n',
- },
- ]);
-
- return answer.confirm == 'y';
- }
-
- /**
- * Push a patch to the tracker and remove the previous one.
- *
- * @param branch Branch name.
- * @param branchData Parsed branch data.
- * @param remote Remote used.
- * @return Promise resolved when done.
- */
- async pushPatch(branch, branchData, remote) {
- const headCommit = await Git.getHeadCommit(branch, branchData);
-
- if (!headCommit) {
- throw new Error('Head commit not resolved, abort pushing patch.');
- }
-
- // Create the patch file.
- const fileName = branch + '.patch';
- const tmpPatchPath = `./tmp/${fileName}`;
-
- await Git.createPatch(`${headCommit}...${branch}`, tmpPatchPath);
- console.log('Git patch created');
-
- // Check if there is an attachment with same name in the issue.
- const issue = await Jira.getIssue(branchData.issue, 'attachment');
-
- let existingAttachmentId;
- const attachments = (issue.fields && issue.fields.attachment) || [];
- for (const i in attachments) {
- if (attachments[i].filename == fileName) {
- // Found an existing attachment with the same name, we keep track of it.
- existingAttachmentId = attachments[i].id;
- break
- }
- }
-
- // Push the patch to the tracker.
- console.log(`Uploading patch ${fileName} to the tracker...`);
- await Jira.upload(branchData.issue, tmpPatchPath);
-
- if (existingAttachmentId) {
- // On success, deleting file that was there before.
- try {
- console.log('Deleting older patch...')
- await Jira.deleteAttachment(existingAttachmentId);
- } catch (error) {
- console.log('Could not delete older attachment.');
- }
- }
- }
-
- /**
- * Run the task.
- *
- * @param args Command line arguments.
- * @param done Function to call when done.
- */
- async run(args, done) {
- try {
- const remote = args.remote || DevConfig.get('upstreamRemote', 'origin');
- let branch = args.branch;
- const force = !!args.force;
-
- if (!branch) {
- branch = await Git.getCurrentBranch();
- }
-
- if (!branch) {
- throw new Error('Cannot determine the current branch. Please make sure youu aren\'t in detached HEAD state');
- } else if (branch == 'HEAD') {
- throw new Error('Cannot push HEAD branch');
- }
-
- // Parse the branch to get the project and issue number.
- const branchData = Utils.parseBranch(branch);
- const keepRunning = await this.validateCommitMessages(branchData);
-
- if (!keepRunning) {
- // Last commit not valid, stop.
- console.log('Exiting...');
- done();
- return;
- }
-
- if (!args.patch) {
- // Check if it's a security issue to force patch mode.
- try {
- args.patch = await Jira.isSecurityIssue(branchData.issue);
-
- if (args.patch) {
- console.log(`${branchData.issue} appears to be a security issue, switching to patch mode...`);
- }
- } catch (error) {
- console.log(`Could not check if ${branchData.issue} is a security issue.`);
- }
- }
-
- if (args.patch) {
- // Create and upload a patch file.
- await this.pushPatch(branch, branchData, remote);
- } else {
- // Push the branch.
- console.log(`Pushing branch ${branch} to remote ${remote}...`);
- await Git.push(remote, branch, force);
-
- // Update tracker info.
- console.log(`Branch pushed, update tracker info...`);
- await this.updateTrackerGitInfo(branch, branchData, remote);
- }
- } catch (error) {
- console.error(error);
- }
-
- done();
- }
-
- /**
- * Update git info in the tracker issue.
- *
- * @param branch Branch name.
- * @param branchData Parsed branch data.
- * @param remote Remote used.
- * @return Promise resolved when done.
- */
- async updateTrackerGitInfo(branch, branchData, remote) {
- // Get the repository data for the project.
- let repositoryUrl = DevConfig.get(branchData.project + '.repositoryUrl');
- let diffUrlTemplate = DevConfig.get(branchData.project + '.diffUrlTemplate', '');
-
- if (!repositoryUrl) {
- // Calculate the repositoryUrl based on the remote URL.
- repositoryUrl = await Git.getRemoteUrl(remote);
- }
-
- // Make sure the repository URL uses the regular format.
- repositoryUrl = repositoryUrl.replace(/^(git@|git:\/\/)/, 'https://')
- .replace(/\.git$/, '')
- .replace('github.com:', 'github.com/');
-
- if (!diffUrlTemplate) {
- diffUrlTemplate = Utils.concatenatePaths([repositoryUrl, 'compare/%headcommit%...%branch%']);
- }
-
- // Now create the git URL for the repository.
- const repositoryGitUrl = repositoryUrl.replace(/^https?:\/\//, 'git://') + '.git';
-
- // Search HEAD commit to put in the diff URL.
- console.log ('Searching for head commit...');
- let headCommit = await Git.getHeadCommit(branch, branchData);
-
- if (!headCommit) {
- throw new Error('Head commit not resolved, aborting update of tracker fields');
- }
-
- headCommit = headCommit.substr(0, 10);
- console.log(`Head commit resolved to ${headCommit}`);
-
- // Calculate last properties needed.
- const diffUrl = diffUrlTemplate.replace('%branch%', branch).replace('%headcommit%', headCommit);
- const fieldRepositoryUrl = DevConfig.get('tracker.fieldnames.repositoryurl', 'Pull from Repository');
- const fieldBranch = DevConfig.get('tracker.fieldnames.branch', 'Pull Master Branch');
- const fieldDiffUrl = DevConfig.get('tracker.fieldnames.diffurl', 'Pull Master Diff URL');
-
- // Update tracker fields.
- const updates = {};
- updates[fieldRepositoryUrl] = repositoryGitUrl;
- updates[fieldBranch] = branch;
- updates[fieldDiffUrl] = diffUrl;
-
- console.log('Setting tracker fields...');
- await Jira.setCustomFields(branchData.issue, updates);
- }
-
- /**
- * Validate commit messages comparing them with the branch name.
- *
- * @param branchData Parsed branch data.
- * @return True if value is ok or the user wants to continue anyway, false to stop.
- */
- async validateCommitMessages(branchData) {
- const messages = await Git.messages(30);
-
- let numConsecutive = 0;
- let wrongCommitCandidate = null;
-
- for (let i = 0; i < messages.length; i++) {
- const message = messages[i];
- const issue = Utils.getIssueFromCommitMessage(message);
-
- if (!issue || issue != branchData.issue) {
- if (i === 0) {
- // Last commit is wrong, it shouldn't happen. Ask the user if he wants to continue.
- if (!issue) {
- console.log('The issue number could not be found in the last commit message.');
- console.log(`Commit: ${message}`);
- } else if (issue != branchData.issue) {
- console.log('The issue number in the last commit does not match the branch being pushed to.');
- console.log(`Branch: ${branchData.issue} vs. commit: ${issue}`);
- }
-
- return this.askConfirmContinue();
- }
-
- numConsecutive++;
- if (numConsecutive > 2) {
- // 3 consecutive commits with different branch, probably the branch commits are over. Everything OK.
- return true;
-
- // Don't treat a merge pull request commit as a wrong commit between right commits.
- // The current push could be a quick fix after a merge.
- } else if (!wrongCommitCandidate && message.indexOf('Merge pull request') == -1) {
- wrongCommitCandidate = {
- message: message,
- issue: issue,
- index: i,
- };
- }
- } else if (wrongCommitCandidate) {
- // We've found a commit with the branch name after a commit with a different branch. Probably wrong commit.
- if (!wrongCommitCandidate.issue) {
- console.log('The issue number could not be found in one of the commit messages.');
- console.log(`Commit: ${wrongCommitCandidate.message}`);
- } else {
- console.log('The issue number in a certain commit does not match the branch being pushed to.');
- console.log(`Branch: ${branchData.issue} vs. commit: ${wrongCommitCandidate.issue}`);
- console.log(`Commit message: ${wrongCommitCandidate.message}`);
- }
-
- return this.askConfirmContinue();
- }
- }
-
- return true;
- }
-}
-
-module.exports = PushTask;
diff --git a/gulp/url.js b/gulp/url.js
deleted file mode 100644
index 8b46409f6..000000000
--- a/gulp/url.js
+++ /dev/null
@@ -1,79 +0,0 @@
-// (C) Copyright 2015 Moodle Pty Ltd.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-/**
- * Class with helper functions for urls.
- */
-class Url {
-
- /**
- * Add params to a URL.
- *
- * @param url URL to add the params to.
- * @param params Object with the params to add.
- * @return URL with params.
- */
- static addParamsToUrl(url, params) {
- let separator = url.indexOf('?') != -1 ? '&' : '?';
-
- for (const key in params) {
- let value = params[key];
-
- // Ignore objects.
- if (typeof value != 'object') {
- url += separator + key + '=' + value;
- separator = '&';
- }
- }
-
- return url;
- }
-
- /**
- * Parse parts of a url, using an implicit protocol if it is missing from the url.
- *
- * @param url Url.
- * @return Url parts.
- */
- static parse(url) {
- // Parse url with regular expression taken from RFC 3986: https://tools.ietf.org/html/rfc3986#appendix-B.
- const match = url.trim().match(/^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/);
-
- if (!match) {
- return null;
- }
-
- const host = match[4] || '';
-
- // Get the credentials and the port from the host.
- const [domainAndPort, credentials] = host.split('@').reverse();
- const [domain, port] = domainAndPort.split(':');
- const [username, password] = credentials ? credentials.split(':') : [];
-
- // Prepare parts replacing empty strings with undefined.
- return {
- protocol: match[2] || undefined,
- domain: domain || undefined,
- port: port || undefined,
- credentials: credentials || undefined,
- username: username || undefined,
- password: password || undefined,
- path: match[5] || undefined,
- query: match[7] || undefined,
- fragment: match[9] || undefined,
- };
- }
-}
-
-module.exports = Url;
diff --git a/gulp/utils.js b/gulp/utils.js
deleted file mode 100644
index 1e8783337..000000000
--- a/gulp/utils.js
+++ /dev/null
@@ -1,120 +0,0 @@
-// (C) Copyright 2015 Moodle Pty Ltd.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-const DevConfig = require('./dev-config');
-const DEFAULT_ISSUE_REGEX = '^(MOBILE)[-_]([0-9]+)';
-
-/**
- * Class with some utility functions.
- */
-class Utils {
- /**
- * Concatenate several paths, adding a slash between them if needed.
- *
- * @param paths List of paths.
- * @return Concatenated path.
- */
- static concatenatePaths(paths) {
- if (!paths.length) {
- return '';
- }
-
- // Remove all slashes between paths.
- for (let i = 0; i < paths.length; i++) {
- if (!paths[i]) {
- continue;
- }
-
- if (i === 0) {
- paths[i] = String(paths[i]).replace(/\/+$/g, '');
- } else if (i === paths.length - 1) {
- paths[i] = String(paths[i]).replace(/^\/+/g, '');
- } else {
- paths[i] = String(paths[i]).replace(/^\/+|\/+$/g, '');
- }
- }
-
- // Remove empty paths.
- paths = paths.filter(path => !!path);
-
- return paths.join('/');
- }
-
- /**
- * Get command line arguments.
- *
- * @return Object with command line arguments.
- */
- static getCommandLineArguments() {
-
- let args = {};
- let curOpt;
-
- for (const argument of process.argv) {
- const thisOpt = argument.trim();
- const option = thisOpt.replace(/^\-+/, '');
-
- if (option === thisOpt) {
- // argument value
- if (curOpt) {
- args[curOpt] = option;
- }
- curOpt = null;
- }
- else {
- // Argument name.
- curOpt = option;
- args[curOpt] = true;
- }
- }
-
- return args;
- }
-
- /**
- * Given a commit message, return the issue name (e.g. MOBILE-1234).
- *
- * @param commit Commit message.
- * @return Issue name.
- */
- static getIssueFromCommitMessage(commit) {
- const regex = new RegExp(DevConfig.get('wording.branchRegex', DEFAULT_ISSUE_REGEX), 'i');
- const matches = commit.match(regex);
-
- return matches && matches[0];
- }
-
- /**
- * Parse a branch name to extract some data.
- *
- * @param branch Branch name to parse.
- * @return Data.
- */
- static parseBranch(branch) {
- const regex = new RegExp(DevConfig.get('wording.branchRegex', DEFAULT_ISSUE_REGEX), 'i');
-
- const matches = branch.match(regex);
- if (!matches || matches.length < 3) {
- throw new Error(`Error parsing branch ${branch}`);
- }
-
- return {
- issue: matches[0],
- project: matches[1],
- issueNumber: matches[2],
- };
- }
-}
-
-module.exports = Utils;
diff --git a/gulpfile.js b/gulpfile.js
index 75ec00a39..ec4a3f831 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -16,9 +16,7 @@ const BuildLangTask = require('./gulp/task-build-lang');
const BuildBehatPluginTask = require('./gulp/task-build-behat-plugin');
const BuildEnvTask = require('./gulp/task-build-env');
const BuildIconsJsonTask = require('./gulp/task-build-icons-json');
-const PushTask = require('./gulp/task-push');
const OverrideLangTask = require('./gulp/task-override-lang');
-const Utils = require('./gulp/utils');
const gulp = require('gulp');
const paths = {
@@ -31,8 +29,6 @@ const paths = {
],
};
-const args = Utils.getCommandLineArguments();
-
// Build the language files into a single file per language.
gulp.task('lang', (done) => {
new BuildLangTask().run(paths.lang, done);
@@ -59,10 +55,6 @@ if (BuildBehatPluginTask.isBehatConfigured()) {
});
}
-gulp.task('push', (done) => {
- new PushTask().run(args, done);
-});
-
gulp.task(
'default',
gulp.parallel([
diff --git a/package-lock.json b/package-lock.json
index 841cc1fb7..ead02a7ee 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -141,7 +141,6 @@
"jest": "^29.7.0",
"jest-preset-angular": "^13.1.4",
"jsonc-parser": "^2.3.1",
- "keytar": "^7.2.0",
"minimatch": "^9.0.3",
"native-run": "^2.0.0",
"patch-package": "^6.5.0",
@@ -11688,21 +11687,6 @@
"node": ">=0.10"
}
},
- "node_modules/decompress-response": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
- "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
- "dev": true,
- "dependencies": {
- "mimic-response": "^3.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/dedent": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
@@ -12029,15 +12013,6 @@
"node": ">=8"
}
},
- "node_modules/detect-libc": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
- "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/detect-newline": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@@ -13338,15 +13313,6 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"dev": true
},
- "node_modules/expand-template": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
- "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/expand-tilde": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
@@ -14500,12 +14466,6 @@
"assert-plus": "^1.0.0"
}
},
- "node_modules/github-from-package": {
- "version": "0.0.0",
- "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
- "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
- "dev": true
- },
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -19897,17 +19857,6 @@
"resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.1.tgz",
"integrity": "sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg=="
},
- "node_modules/keytar": {
- "version": "7.9.0",
- "resolved": "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz",
- "integrity": "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ==",
- "dev": true,
- "hasInstallScript": true,
- "dependencies": {
- "node-addon-api": "^4.3.0",
- "prebuild-install": "^7.0.1"
- }
- },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -21178,18 +21127,6 @@
"node": ">=8"
}
},
- "node_modules/mimic-response": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
- "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
- "dev": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/min-document": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
@@ -21472,12 +21409,6 @@
"node": ">=10"
}
},
- "node_modules/mkdirp-classic": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
- "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
- "dev": true
- },
"node_modules/moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
@@ -21652,12 +21583,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/napi-build-utils": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
- "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==",
- "dev": true
- },
"node_modules/native-run": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/native-run/-/native-run-2.0.0.tgz",
@@ -21845,24 +21770,6 @@
"lower-case": "^1.1.1"
}
},
- "node_modules/node-abi": {
- "version": "3.51.0",
- "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.51.0.tgz",
- "integrity": "sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA==",
- "dev": true,
- "dependencies": {
- "semver": "^7.3.5"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/node-addon-api": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz",
- "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==",
- "dev": true
- },
"node_modules/node-forge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
@@ -24054,32 +23961,6 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
- "node_modules/prebuild-install": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
- "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==",
- "dev": true,
- "dependencies": {
- "detect-libc": "^2.0.0",
- "expand-template": "^2.0.3",
- "github-from-package": "0.0.0",
- "minimist": "^1.2.3",
- "mkdirp-classic": "^0.5.3",
- "napi-build-utils": "^1.0.1",
- "node-abi": "^3.3.0",
- "pump": "^3.0.0",
- "rc": "^1.2.7",
- "simple-get": "^4.0.0",
- "tar-fs": "^2.0.0",
- "tunnel-agent": "^0.6.0"
- },
- "bin": {
- "prebuild-install": "bin.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -25938,51 +25819,6 @@
"tail": "^0.4.0"
}
},
- "node_modules/simple-concat": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
- "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ]
- },
- "node_modules/simple-get": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
- "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "dependencies": {
- "decompress-response": "^6.0.0",
- "once": "^1.3.1",
- "simple-concat": "^1.0.0"
- }
- },
"node_modules/simple-plist": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.1.tgz",
@@ -26948,24 +26784,6 @@
"node": ">=10"
}
},
- "node_modules/tar-fs": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
- "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
- "dev": true,
- "dependencies": {
- "chownr": "^1.1.1",
- "mkdirp-classic": "^0.5.2",
- "pump": "^3.0.0",
- "tar-stream": "^2.1.4"
- }
- },
- "node_modules/tar-fs/node_modules/chownr": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
- "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
- "dev": true
- },
"node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
diff --git a/package.json b/package.json
index 7e739e549..f63a48dc4 100644
--- a/package.json
+++ b/package.json
@@ -176,7 +176,6 @@
"jest": "^29.7.0",
"jest-preset-angular": "^13.1.4",
"jsonc-parser": "^2.3.1",
- "keytar": "^7.2.0",
"minimatch": "^9.0.3",
"native-run": "^2.0.0",
"patch-package": "^6.5.0",
diff --git a/src/addons/block/myoverview/components/myoverview/addon-block-myoverview.html b/src/addons/block/myoverview/components/myoverview/addon-block-myoverview.html
index 1c55aafb3..8852c7a00 100644
--- a/src/addons/block/myoverview/components/myoverview/addon-block-myoverview.html
+++ b/src/addons/block/myoverview/components/myoverview/addon-block-myoverview.html
@@ -71,7 +71,7 @@
+ icon="fas-arrow-down-short-wide" class="no-border">
{{'addon.block_myoverview.title' | translate}}
diff --git a/src/addons/block/myoverview/components/myoverview/myoverview.scss b/src/addons/block/myoverview/components/myoverview/myoverview.scss
index 9592ab859..563fadfbd 100644
--- a/src/addons/block/myoverview/components/myoverview/myoverview.scss
+++ b/src/addons/block/myoverview/components/myoverview/myoverview.scss
@@ -10,24 +10,19 @@
}
ion-button,
- core-combobox ::ng-deep ion-button {
- --border-width: 0;
+ core-combobox ::ng-deep ion-select {
--a11y-min-target-size: 40px;
margin: 0;
+ }
+
+ ion-button {
+ --border-width: 0;
- .select-icon {
- display: none;
- }
ion-icon {
font-size: 20px;
}
}
- core-combobox ::ng-deep ion-select {
- margin: 0;
- --a11y-min-target-size: 40px;
- }
-
ion-searchbar {
padding: 0;
--height: 40px;
diff --git a/src/addons/block/tags/components/tags/tags.scss b/src/addons/block/tags/components/tags/tags.scss
index 5580a6bdd..fcdc93511 100644
--- a/src/addons/block/tags/components/tags/tags.scss
+++ b/src/addons/block/tags/components/tags/tags.scss
@@ -1,14 +1,20 @@
:host .core-block-content ::ng-deep {
+ ion-label {
+ max-width: 100%;
+ }
.tag_cloud {
- text-align: center;
ul.inline-list {
list-style: none;
margin: 0;
-webkit-padding-start: 0;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: center;
+
li {
padding: .2em;
- display: inline-block;
a {
background: var(--primary);
diff --git a/src/addons/block/timeline/components/timeline/addon-block-timeline.html b/src/addons/block/timeline/components/timeline/addon-block-timeline.html
index 4aca795d0..0b7d03d0d 100644
--- a/src/addons/block/timeline/components/timeline/addon-block-timeline.html
+++ b/src/addons/block/timeline/components/timeline/addon-block-timeline.html
@@ -36,7 +36,7 @@
+ icon="fas-arrow-down-short-wide" class="no-border">
{{ option.name | translate }}
diff --git a/src/addons/mod/forum/tests/behat/snapshots/test-basic-usage-of-forum-activity-in-app-reply-a-post_9.png b/src/addons/mod/forum/tests/behat/snapshots/test-basic-usage-of-forum-activity-in-app-reply-a-post_9.png
index 87aa8ca9c..e801c2db6 100644
Binary files a/src/addons/mod/forum/tests/behat/snapshots/test-basic-usage-of-forum-activity-in-app-reply-a-post_9.png and b/src/addons/mod/forum/tests/behat/snapshots/test-basic-usage-of-forum-activity-in-app-reply-a-post_9.png differ
diff --git a/src/core/components/combobox/combobox.scss b/src/core/components/combobox/combobox.scss
index 80c9379ad..9e61dfed6 100644
--- a/src/core/components/combobox/combobox.scss
+++ b/src/core/components/combobox/combobox.scss
@@ -5,30 +5,94 @@
display: block;
@include margin-horizontal(var(--ion-safe-area-left), var(--ion-safe-area-right));
+ &.no-border {
+ --core-combobox-border-width: 0px;
+
+ ion-select.combobox-icon-only {
+ --padding-start: 8px;
+
+ &::part(icon) {
+ display: none;
+ }
+
+ &::part(label) {
+ position: static;
+ }
+
+ ion-icon {
+ margin: var(--icon-margin);
+ font-size: 20px;
+ }
+ }
+ }
+
+
ion-select,
ion-button {
--icon-margin: 0 4px;
- --background: var(--core-combobox-background);
- --background-hover: black;
- --background-activated: black;
- --background-focused: black;
- --background-hover-opacity: .04;
+ --background: var(--core-combobox-background);
+
+ --border-color: var(--core-combobox-border-color);
+ --border-style: solid;
+ --border-width: var(--core-combobox-border-width);
+ --border-radius: var(--core-combobox-radius);
+
+ --box-shadow: var(--core-combobox-box-shadow);
+
+ --padding-start: 16px;
+ --padding-end: 8px;
+ --padding-top: 8px;
+ --padding-bottom: 8px;
+
+ background: var(--background);
+ color: var(--color);
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ min-height: var(--a11y-min-target-size);
+ overflow: hidden;
+ box-shadow: var(--box-shadow);
+
+ &:focus,
+ &:focus-within {
+ @include core-focus-style();
+ }
+ }
+
+ ion-select {
+ border-color: var(--border-color);
+ border-style: var(--border-style);
+ border-width: var(--border-width);
+ border-radius: var(--core-combobox-radius);
+ margin: 8px;
+
+ &.combobox-icon-only {
+ &::part(text) {
+ display: none;
+ }
+ }
+
+ &::part(label) {
+ position: absolute;
+ margin-inline: 0px;
+ }
+
+ &::part(icon) {
+ margin: var(--icon-margin);
+ opacity: 1;
+ }
+ }
+
+ ion-button {
--color: var(--core-combobox-color);
--color-activated: var(--core-combobox-color);
--color-focused: currentcolor;
--color-hover: currentcolor;
- --border-color: var(--core-combobox-border-color);
- --border-style: solid;
- --border-width: var(--core-combobox-border-width);
- --border-radius: var(--radius-xs);
-
- --box-shadow: var(--core-combobox-box-shadow);
-
- --padding-top: 8px;
- --padding-end: 8px;
- --padding-bottom: 8px;
+ --background-hover: black;
+ --background-activated: black;
+ --background-focused: black;
+ --background-hover-opacity: .04;
&.md {
--background-activated-opacity: 0;
@@ -39,97 +103,36 @@
--background-activated-opacity: .12;
--background-focused-opacity: .15;
}
- }
- ion-button {
- --padding-start: 8px;
- }
-
- ion-select {
- --padding-start: 16px;
- }
-}
-
-ion-select,
-ion-button {
- background: var(--background);
- color: var(--color);
- text-overflow: ellipsis;
- white-space: nowrap;
- min-height: var(--a11y-min-target-size);
- overflow: hidden;
- box-shadow: var(--box-shadow);
-
- &:focus,
- &:focus-within {
- @include core-focus-style();
- }
-}
-
-ion-select {
- border-color: var(--border-color);
- border-style: var(--border-style);
- border-width: var(--border-width);
- border-radius: var(--core-combobox-radius);
- margin: 8px;
-
- &::part(icon) {
- margin: var(--icon-margin);
- opacity: 1;
- }
-
- &::after {
- @include button-state();
- transition: var(--transition);
- z-index: -1;
- }
-
- &:hover::after {
- color: var(--color-hover);
- background: var(--background-hover);
- opacity: var(--background-hover-opacity);
- }
-
- &:focus::after,
- &:focus-within::after {
- color: var(--color-focused);
- background: var(--background-focused);
- opacity: var(--background-focused-opacity);
- }
-
- &[hidden] {
- display: none !important;
- }
-}
-
-ion-button {
- border-radius: var(--core-combobox-radius);
- margin: 4px 8px;
-
- flex: 1;
-
- &::part(native) {
- text-transform: none;
- font-weight: 400;
- font-size: 16px;
- line-height: 20px;
border-radius: var(--core-combobox-radius);
+ margin: 4px 8px;
+
+ flex: 1;
+
+ &::part(native) {
+ text-transform: none;
+ font-weight: 400;
+ font-size: 16px;
+ line-height: 20px;
+ border-radius: var(--core-combobox-radius);
+ }
+
+ .select-text {
+ @include margin-horizontal(null, auto);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ .sr-only {
+ @include sr-only();
+ }
+
+ &.ion-activated {
+ --color: var(--color-activated);
+ }
+
+ ion-icon {
+ margin: var(--icon-margin);
+ }
}
- .select-text {
- @include margin-horizontal(null, auto);
- overflow: hidden;
- text-overflow: ellipsis;
- }
- .sr-only {
- @include sr-only();
- }
-
- &.ion-activated {
- --color: var(--color-activated);
- }
-
- ion-icon {
- margin: var(--icon-margin);
- }
}
diff --git a/src/core/components/combobox/combobox.ts b/src/core/components/combobox/combobox.ts
index 97414ae21..ddf7e75e8 100644
--- a/src/core/components/combobox/combobox.ts
+++ b/src/core/components/combobox/combobox.ts
@@ -12,11 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
+import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Translate } from '@singletons';
import { ModalOptions } from '@ionic/core';
import { CoreDomUtils } from '@services/utils/dom';
-import { IonSelect } from '@ionic/angular';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
/**
@@ -51,8 +50,6 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
})
export class CoreComboboxComponent implements ControlValueAccessor {
- @ViewChild(IonSelect) select?: IonSelect;
-
@Input() interface: 'popover' | 'modal' = 'popover';
@Input() label = Translate.instant('core.show'); // Aria label.
@Input() disabled = false;
@@ -112,30 +109,25 @@ export class CoreComboboxComponent implements ControlValueAccessor {
/**
* Shows combobox modal.
*
- * @param event Event.
* @returns Promise resolved when done.
*/
- async openSelect(event?: UIEvent): Promise {
+ async openModal(): Promise {
this.touch();
- if (this.interface === 'modal') {
- if (this.expanded || !this.modalOptions) {
- return;
- }
- this.expanded = true;
+ if (this.expanded || !this.modalOptions) {
+ return;
+ }
+ this.expanded = true;
- if (this.listboxId) {
- this.modalOptions.id = this.listboxId;
- }
+ if (this.listboxId) {
+ this.modalOptions.id = this.listboxId;
+ }
- const data = await CoreDomUtils.openModal(this.modalOptions);
- this.expanded = false;
+ const data = await CoreDomUtils.openModal(this.modalOptions);
+ this.expanded = false;
- if (data) {
- this.onValueChanged(data);
- }
- } else if (this.select) {
- this.select.open(event);
+ if (data) {
+ this.onValueChanged(data);
}
}
diff --git a/src/core/components/combobox/core-combobox.html b/src/core/components/combobox/core-combobox.html
index 8d0868bc5..212de4608 100644
--- a/src/core/components/combobox/core-combobox.html
+++ b/src/core/components/combobox/core-combobox.html
@@ -1,18 +1,17 @@
-
-
-
-
+ [interface]="interface" [disabled]="disabled" [class.combobox-icon-only]="icon" [interfaceOptions]="{alignment: 'start', arrow: false}">
+
+ {{ label }}
+
+
+
+ [attr.aria-expanded]="expanded" (click)="openModal()" [disabled]="disabled" expand="block" role="combobox">
- {{ label }}:
+ {{ label }},
{{selection}}
diff --git a/src/core/features/fileuploader/services/handlers/album.ts b/src/core/features/fileuploader/services/handlers/album.ts
index 26eb45fe6..e61ddf6de 100644
--- a/src/core/features/fileuploader/services/handlers/album.ts
+++ b/src/core/features/fileuploader/services/handlers/album.ts
@@ -30,19 +30,14 @@ export class CoreFileUploaderAlbumHandlerService implements CoreFileUploaderHand
priority = 2000;
/**
- * Whether or not the handler is enabled on a site level.
- *
- * @returns Promise resolved with true if enabled.
+ * @inheritdoc
*/
async isEnabled(): Promise {
return CorePlatform.isMobile();
}
/**
- * Given a list of mimetypes, return the ones that are supported by the handler.
- *
- * @param mimetypes List of mimetypes.
- * @returns Supported mimetypes.
+ * @inheritdoc
*/
getSupportedMimetypes(mimetypes: string[]): string[] {
// Album allows picking images and videos.
@@ -50,15 +45,13 @@ export class CoreFileUploaderAlbumHandlerService implements CoreFileUploaderHand
}
/**
- * Get the data to display the handler.
- *
- * @returns Data.
+ * @inheritdoc
*/
getData(): CoreFileUploaderHandlerData {
return {
title: 'core.fileuploader.photoalbums',
class: 'core-fileuploader-album-handler',
- icon: 'images', // Cannot use font-awesome in action sheet.
+ icon: 'fas-images',
action: async (
maxSize?: number,
upload?: boolean,
diff --git a/src/core/features/fileuploader/services/handlers/audio.ts b/src/core/features/fileuploader/services/handlers/audio.ts
index cdce9f086..2a2e93725 100644
--- a/src/core/features/fileuploader/services/handlers/audio.ts
+++ b/src/core/features/fileuploader/services/handlers/audio.ts
@@ -30,19 +30,14 @@ export class CoreFileUploaderAudioHandlerService implements CoreFileUploaderHand
priority = 1600;
/**
- * Whether or not the handler is enabled on a site level.
- *
- * @returns Promise resolved with true if enabled.
+ * @inheritdoc
*/
async isEnabled(): Promise {
return CorePlatform.isMobile() || (CoreApp.canGetUserMedia() && CoreApp.canRecordMedia());
}
/**
- * Given a list of mimetypes, return the ones that are supported by the handler.
- *
- * @param mimetypes List of mimetypes.
- * @returns Supported mimetypes.
+ * @inheritdoc
*/
getSupportedMimetypes(mimetypes: string[]): string[] {
if (CorePlatform.isIOS()) {
@@ -66,15 +61,13 @@ export class CoreFileUploaderAudioHandlerService implements CoreFileUploaderHand
}
/**
- * Get the data to display the handler.
- *
- * @returns Data.
+ * @inheritdoc
*/
getData(): CoreFileUploaderHandlerData {
return {
title: 'core.fileuploader.audio',
class: 'core-fileuploader-audio-handler',
- icon: 'mic', // Cannot use font-awesome in action sheet.
+ icon: 'fas-microphone',
action: async (
maxSize?: number,
upload?: boolean,
diff --git a/src/core/features/fileuploader/services/handlers/camera.ts b/src/core/features/fileuploader/services/handlers/camera.ts
index 84c9e3ff9..80cae2cdd 100644
--- a/src/core/features/fileuploader/services/handlers/camera.ts
+++ b/src/core/features/fileuploader/services/handlers/camera.ts
@@ -31,19 +31,14 @@ export class CoreFileUploaderCameraHandlerService implements CoreFileUploaderHan
priority = 1800;
/**
- * Whether or not the handler is enabled on a site level.
- *
- * @returns Promise resolved with true if enabled.
+ * @inheritdoc
*/
async isEnabled(): Promise {
return CorePlatform.isMobile() || CoreApp.canGetUserMedia();
}
/**
- * Given a list of mimetypes, return the ones that are supported by the handler.
- *
- * @param mimetypes List of mimetypes.
- * @returns Supported mimetypes.
+ * @inheritdoc
*/
getSupportedMimetypes(mimetypes: string[]): string[] {
// Camera only supports JPEG and PNG.
@@ -51,15 +46,13 @@ export class CoreFileUploaderCameraHandlerService implements CoreFileUploaderHan
}
/**
- * Get the data to display the handler.
- *
- * @returns Data.
+ * @inheritdoc
*/
getData(): CoreFileUploaderHandlerData {
return {
title: 'core.fileuploader.camera',
class: 'core-fileuploader-camera-handler',
- icon: 'camera', // Cannot use font-awesome in action sheet.
+ icon: 'fas-camera',
action: async (
maxSize?: number,
upload?: boolean,
diff --git a/src/core/features/fileuploader/services/handlers/file.ts b/src/core/features/fileuploader/services/handlers/file.ts
index 0ed301042..4e63a0aa8 100644
--- a/src/core/features/fileuploader/services/handlers/file.ts
+++ b/src/core/features/fileuploader/services/handlers/file.ts
@@ -31,34 +31,27 @@ export class CoreFileUploaderFileHandlerService implements CoreFileUploaderHandl
priority = 1200;
/**
- * Whether or not the handler is enabled on a site level.
- *
- * @returns Promise resolved with true if enabled.
+ * @inheritdoc
*/
async isEnabled(): Promise {
return true;
}
/**
- * Given a list of mimetypes, return the ones that are supported by the handler.
- *
- * @param mimetypes List of mimetypes.
- * @returns Supported mimetypes.
+ * @inheritdoc
*/
getSupportedMimetypes(mimetypes: string[]): string[] {
return mimetypes;
}
/**
- * Get the data to display the handler.
- *
- * @returns Data.
+ * @inheritdoc
*/
getData(): CoreFileUploaderHandlerData {
const handler: CoreFileUploaderHandlerData = {
title: 'core.fileuploader.file',
class: 'core-fileuploader-file-handler',
- icon: 'folder', // Cannot use font-awesome in action sheet.
+ icon: 'fas-file-lines',
};
if (CorePlatform.isMobile()) {
diff --git a/src/core/features/fileuploader/services/handlers/video.ts b/src/core/features/fileuploader/services/handlers/video.ts
index 0472f2248..7d5b72476 100644
--- a/src/core/features/fileuploader/services/handlers/video.ts
+++ b/src/core/features/fileuploader/services/handlers/video.ts
@@ -30,19 +30,14 @@ export class CoreFileUploaderVideoHandlerService implements CoreFileUploaderHand
priority = 1400;
/**
- * Whether or not the handler is enabled on a site level.
- *
- * @returns Promise resolved with true if enabled.
+ * @inheritdoc
*/
async isEnabled(): Promise {
return CorePlatform.isMobile() || (CoreApp.canGetUserMedia() && CoreApp.canRecordMedia());
}
/**
- * Given a list of mimetypes, return the ones that are supported by the handler.
- *
- * @param mimetypes List of mimetypes.
- * @returns Supported mimetypes.
+ * @inheritdoc
*/
getSupportedMimetypes(mimetypes: string[]): string[] {
if (CorePlatform.isIOS()) {
@@ -66,15 +61,13 @@ export class CoreFileUploaderVideoHandlerService implements CoreFileUploaderHand
}
/**
- * Get the data to display the handler.
- *
- * @returns Data.
+ * @inheritdoc
*/
getData(): CoreFileUploaderHandlerData {
return {
title: 'core.fileuploader.video',
class: 'core-fileuploader-video-handler',
- icon: 'videocam', // Cannot use font-awesome in action sheet.
+ icon: 'fas-video',
action: async (
maxSize?: number,
upload?: boolean,
diff --git a/src/core/features/sharedfiles/services/handlers/upload.ts b/src/core/features/sharedfiles/services/handlers/upload.ts
index c0ff37195..b2e3ad45c 100644
--- a/src/core/features/sharedfiles/services/handlers/upload.ts
+++ b/src/core/features/sharedfiles/services/handlers/upload.ts
@@ -32,34 +32,27 @@ export class CoreSharedFilesUploadHandlerService implements CoreFileUploaderHand
priority = 1300;
/**
- * Whether or not the handler is enabled on a site level.
- *
- * @returns True or promise resolved with true if enabled.
+ * @inheritdoc
*/
async isEnabled(): Promise {
return CorePlatform.isIOS();
}
/**
- * Given a list of mimetypes, return the ones that are supported by the handler.
- *
- * @param mimetypes List of mimetypes.
- * @returns Supported mimetypes.
+ * @inheritdoc
*/
getSupportedMimetypes(mimetypes: string[]): string[] {
return mimetypes;
}
/**
- * Get the data to display the handler.
- *
- * @returns Data.
+ * @inheritdoc
*/
getData(): CoreFileUploaderHandlerData {
return {
title: 'core.sharedfiles.sharedfiles',
class: 'core-sharedfiles-fileuploader-handler',
- icon: 'folder', // Cannot use font-awesome in action sheet.
+ icon: 'fas-folder',
action: (
maxSize?: number,
upload?: boolean,
diff --git a/src/core/features/user/services/handlers/profile-mail.ts b/src/core/features/user/services/handlers/profile-mail.ts
index d848f6307..daba6ac66 100644
--- a/src/core/features/user/services/handlers/profile-mail.ts
+++ b/src/core/features/user/services/handlers/profile-mail.ts
@@ -49,7 +49,7 @@ export class CoreUserProfileMailHandlerService implements CoreUserProfileHandler
*/
getDisplayData(): CoreUserProfileHandlerData {
return {
- icon: 'mail',
+ icon: 'fas-envelope',
title: 'core.user.sendemail',
class: 'core-user-profile-mail',
action: (event, user): void => {
diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss
index 37c3e2adf..a1543e5b2 100644
--- a/src/theme/theme.base.scss
+++ b/src/theme/theme.base.scss
@@ -1027,7 +1027,6 @@ input[type=radio],
--border-width: 2px;
--outer-border-width: 2px;
--border-style: solid;
- --inner-border-radius: 50%;
--size: 20px;
&:not(.ion-color) {
@@ -1045,10 +1044,10 @@ input[type=radio],
}
.ios ion-radio {
- width: var(--size);
- height: var(--size);
-
&::part(container) {
+ width: var(--size);
+ height: var(--size);
+
margin: 0px;
border-radius: var(--border-radius);
border-width: var(--outer-border-width);
@@ -1155,6 +1154,13 @@ ion-select {
}
ion-select-popover {
+ ion-list ion-radio-group ion-item.select-interface-option ion-radio.hydrated::part(container) {
+ opacity: 1;
+ }
+
+ ion-item {
+ font-size: 14px;
+ }
ion-item.core-select-option-border-bottom {
border-bottom: 1px solid var(--stroke);
}
@@ -1645,6 +1651,7 @@ ion-item.item {
// Change default outline.
:focus-visible {
@include core-focus-style();
+ border-radius: inherit;
outline: none;
}