Merge branch 'wallet/sprint/16' into wallet/task/322
This commit is contained in:
commit
397b782017
16 changed files with 41 additions and 123 deletions
|
|
@ -13,10 +13,6 @@ angular.module('copayApp.controllers').controller('advancedSettingsController',
|
||||||
$scope.hideNextSteps = {
|
$scope.hideNextSteps = {
|
||||||
value: config.hideNextSteps.enabled
|
value: config.hideNextSteps.enabled
|
||||||
};
|
};
|
||||||
$scope.displayBitcoinCoreEnabled = {
|
|
||||||
value: config.displayBitcoinCore.enabled
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.spendUnconfirmedChange = function() {
|
$scope.spendUnconfirmedChange = function() {
|
||||||
|
|
@ -52,17 +48,6 @@ angular.module('copayApp.controllers').controller('advancedSettingsController',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.displayBitcoinCoreChange = function() {
|
|
||||||
var opts = {
|
|
||||||
displayBitcoinCore: {
|
|
||||||
enabled: $scope.displayBitcoinCoreEnabled.value
|
|
||||||
}
|
|
||||||
};
|
|
||||||
configService.set(opts, function(err) {
|
|
||||||
if (err) $log.debug(err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.$on("$ionicView.beforeEnter", function(event, data) {
|
$scope.$on("$ionicView.beforeEnter", function(event, data) {
|
||||||
$scope.isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
|
$scope.isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
|
||||||
updateConfig();
|
updateConfig();
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,11 @@ angular.module('copayApp.controllers').controller('communityController', functio
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.open = function(url) {
|
$scope.open = function(url) {
|
||||||
window.open(url, '_system');
|
if (platformInfo.isNW) {
|
||||||
|
require('nw.gui').Shell.openExternal( url );
|
||||||
|
} else {
|
||||||
|
window.open(url, '_system');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('copayApp.controllers').controller('searchController', function($scope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $state, $stateParams, $ionicScrollDelegate, bwcError, profileService, lodash, configService, gettext, gettextCatalog, platformInfo, walletService, externalLinkService) {
|
angular.module('copayApp.controllers').controller('searchController', function($scope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $state, $stateParams, $ionicScrollDelegate, bwcError, profileService, lodash, configService, gettext, gettextCatalog, platformInfo, walletService, externalLinkService, bitcoinCashJsService) {
|
||||||
|
|
||||||
var HISTORY_SHOW_LIMIT = 10;
|
var HISTORY_SHOW_LIMIT = 10;
|
||||||
var currentTxHistoryPage = 0;
|
var currentTxHistoryPage = 0;
|
||||||
|
|
@ -31,6 +31,19 @@ angular.module('copayApp.controllers').controller('searchController', function($
|
||||||
var message = tx.message ? tx.message : '';
|
var message = tx.message ? tx.message : '';
|
||||||
var comment = tx.note ? tx.note.body : '';
|
var comment = tx.note ? tx.note.body : '';
|
||||||
var addressTo = tx.addressTo ? tx.addressTo : '';
|
var addressTo = tx.addressTo ? tx.addressTo : '';
|
||||||
|
|
||||||
|
if ($scope.wallet.coin === 'bch') {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For each address
|
||||||
|
* I translate the legacy address and add in the searchable string the 3 kind of addresses
|
||||||
|
*/
|
||||||
|
lodash.each(tx.outputs, function(output) {
|
||||||
|
var addr = bitcoinCashJsService.translateAddresses(output.address);
|
||||||
|
addressTo += addr.legacy + addr.bitpay + 'bitcoincash:' + addr.cashaddr
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var txid = tx.txid ? tx.txid : '';
|
var txid = tx.txid ? tx.txid : '';
|
||||||
return ((tx.amountStr + message + addressTo + addrbook + searchableDate + comment + txid).toString()).toLowerCase();
|
return ((tx.amountStr + message + addressTo + addrbook + searchableDate + comment + txid).toString()).toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('copayApp.controllers').controller('nextStepsController', function($scope, nextStepsService, $ionicScrollDelegate, $timeout, configService) {
|
angular.module('copayApp.controllers').controller('nextStepsController', function($scope, nextStepsService, $ionicScrollDelegate, $timeout, platformInfo, configService) {
|
||||||
|
|
||||||
$scope.hide = false;
|
$scope.hide = false;
|
||||||
|
|
||||||
|
|
@ -22,6 +22,10 @@ angular.module('copayApp.controllers').controller('nextStepsController', functio
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.open = function(url) {
|
$scope.open = function(url) {
|
||||||
window.open(url, '_system');
|
if (platformInfo.isNW) {
|
||||||
|
require('nw.gui').Shell.openExternal( url );
|
||||||
|
} else {
|
||||||
|
window.open(url, '_system');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -83,9 +83,7 @@ angular.module('copayApp.controllers').controller('tabHomeController',
|
||||||
|
|
||||||
$scope.$on("$ionicView.enter", function(event, data) {
|
$scope.$on("$ionicView.enter", function(event, data) {
|
||||||
$ionicNavBarDelegate.showBar(true);
|
$ionicNavBarDelegate.showBar(true);
|
||||||
updateAllWallets(function() {
|
updateAllWallets();
|
||||||
profileService.initBitcoinCoreDisplay();
|
|
||||||
});
|
|
||||||
|
|
||||||
addressbookService.list(function(err, ab) {
|
addressbookService.list(function(err, ab) {
|
||||||
if (err) $log.error(err);
|
if (err) $log.error(err);
|
||||||
|
|
@ -126,8 +124,6 @@ angular.module('copayApp.controllers').controller('tabHomeController',
|
||||||
$scope.nextStepsItems = nextStepsService.get();
|
$scope.nextStepsItems = nextStepsService.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.displayBitcoinCore = config.displayBitcoinCore.enabled;
|
|
||||||
|
|
||||||
$scope.showServices = true;
|
$scope.showServices = true;
|
||||||
pushNotificationsService.init();
|
pushNotificationsService.init();
|
||||||
firebaseEventsService.init();
|
firebaseEventsService.init();
|
||||||
|
|
@ -281,8 +277,6 @@ angular.module('copayApp.controllers').controller('tabHomeController',
|
||||||
|
|
||||||
var txIdList = [];
|
var txIdList = [];
|
||||||
|
|
||||||
var notificationsBeforeCheck = notifications.length;
|
|
||||||
|
|
||||||
for (var i=0; i<notifications.length; i++) {
|
for (var i=0; i<notifications.length; i++) {
|
||||||
var txId = notifications[i].txid;
|
var txId = notifications[i].txid;
|
||||||
if (txIdList.includes(txId)) {
|
if (txIdList.includes(txId)) {
|
||||||
|
|
@ -293,15 +287,7 @@ angular.module('copayApp.controllers').controller('tabHomeController',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var notificationsAfterCheck = notifications.length;
|
|
||||||
var removedNotifications = notificationsBeforeCheck - notificationsAfterCheck;
|
|
||||||
|
|
||||||
if (notificationsBeforeCheck != notificationsAfterCheck) {
|
|
||||||
total = total - removedNotifications;
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.notifications = notifications;
|
$scope.notifications = notifications;
|
||||||
$scope.notificationsN = total;
|
|
||||||
$timeout(function() {
|
$timeout(function() {
|
||||||
$ionicScrollDelegate.resize();
|
$ionicScrollDelegate.resize();
|
||||||
$scope.$apply();
|
$scope.$apply();
|
||||||
|
|
@ -326,9 +312,4 @@ angular.module('copayApp.controllers').controller('tabHomeController',
|
||||||
updateAllWallets();
|
updateAllWallets();
|
||||||
};
|
};
|
||||||
|
|
||||||
$rootScope.$on('Local/SettingsUpdated', function(e, walletId) {
|
|
||||||
configService.whenAvailable(function(config) {
|
|
||||||
$scope.displayBitcoinCore = config.displayBitcoinCore.enabled;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,6 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
|
||||||
coin: v.coin,
|
coin: v.coin,
|
||||||
network: v.network,
|
network: v.network,
|
||||||
balanceString: v.cachedBalance,
|
balanceString: v.cachedBalance,
|
||||||
displayWallet: v.coin == 'btc' ? config.displayBitcoinCore.enabled : true,
|
|
||||||
getAddress: function(cb) {
|
getAddress: function(cb) {
|
||||||
walletService.getAddress(v, false, cb);
|
walletService.getAddress(v, false, cb);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,9 @@ angular.module('copayApp.directives')
|
||||||
show: '=walletSelectorShow',
|
show: '=walletSelectorShow',
|
||||||
wallets: '=walletSelectorWallets',
|
wallets: '=walletSelectorWallets',
|
||||||
selectedWallet: '=walletSelectorSelectedWallet',
|
selectedWallet: '=walletSelectorSelectedWallet',
|
||||||
onSelect: '=walletSelectorOnSelect',
|
onSelect: '=walletSelectorOnSelect'
|
||||||
alwaysDisplayBitcoinCore: '=walletSelectorAlwaysDisplayBitcoinCore'
|
|
||||||
},
|
},
|
||||||
link: function(scope, element, attrs) {
|
link: function(scope, element, attrs) {
|
||||||
scope.displayWallet = true;
|
|
||||||
scope.hide = function() {
|
scope.hide = function() {
|
||||||
scope.show = false;
|
scope.show = false;
|
||||||
};
|
};
|
||||||
|
|
@ -28,19 +26,6 @@ angular.module('copayApp.directives')
|
||||||
scope.$watch('wallets', function(newValue, oldValue) {
|
scope.$watch('wallets', function(newValue, oldValue) {
|
||||||
scope.wallets = newValue;
|
scope.wallets = newValue;
|
||||||
});
|
});
|
||||||
scope.initDisplayBitcoinCoreConfig = function() {
|
|
||||||
configService.whenAvailable(function(config) {
|
|
||||||
scope.displayBitcoinCore = config.displayBitcoinCore.enabled;
|
|
||||||
scope.initWalletDisplay();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
scope.initWalletDisplay = function() {
|
|
||||||
scope.displayWallet = scope.alwaysDisplayBitcoinCore ? true : scope.displayBitcoinCore;
|
|
||||||
};
|
|
||||||
scope.initDisplayBitcoinCoreConfig();
|
|
||||||
$rootScope.$on('Local/SettingsUpdated', function(e, walletId) {
|
|
||||||
scope.initDisplayBitcoinCoreConfig();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -85,10 +85,6 @@ angular.module('copayApp.services').factory('configService', function(storageSer
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
displayBitcoinCore: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
hideNextSteps: {
|
hideNextSteps: {
|
||||||
enabled: isWindowsPhoneApp ? true : false,
|
enabled: isWindowsPhoneApp ? true : false,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1037,39 +1037,6 @@ angular.module('copayApp.services')
|
||||||
return cb(null, txps, n);
|
return cb(null, txps, n);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Displays Bitcoin Core Wallets if BTC balance is more than 0
|
|
||||||
root.initBitcoinCoreDisplay = function() {
|
|
||||||
storageService.checkIfFlagIsSet('displayBitcoinCoreFlag')
|
|
||||||
.then(function(result) {
|
|
||||||
// Perform checks for flags which are even set to true once more, set the new flag value to 1
|
|
||||||
if (result === false || result === true) {
|
|
||||||
root.checkBtcBalanceAndInitDisplay(1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
root.checkBtcBalanceAndInitDisplay = function(flagValue) {
|
|
||||||
var walletsBtc = root.getWallets({coin: 'btc'});
|
|
||||||
if (walletsBtc.length > 0) {
|
|
||||||
// Do not trust cachedBalance as it is added asynchronously. Using a new promise-based function.
|
|
||||||
root.getWalletsBalance(walletsBtc)
|
|
||||||
.then(function(totalBalance) {
|
|
||||||
var enableDisplayBitcoinCore = totalBalance > 0 ? true : false;
|
|
||||||
|
|
||||||
var opts = {
|
|
||||||
displayBitcoinCore: {
|
|
||||||
enabled: enableDisplayBitcoinCore
|
|
||||||
}
|
|
||||||
};
|
|
||||||
configService.set(opts, function(err) {
|
|
||||||
if (err) $log.debug(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
storageService.activateDisplayBitcoinCoreFlag(flagValue);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate wallets total balance (Promise). Attempts to fix asynchronous issue with cachedBalance not being available when it's needed
|
// Calculate wallets total balance (Promise). Attempts to fix asynchronous issue with cachedBalance not being available when it's needed
|
||||||
root.getWalletsBalance = function(wallets) {
|
root.getWalletsBalance = function(wallets) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
|
|
|
||||||
|
|
@ -646,12 +646,5 @@ angular.module('copayApp.services')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
root.activateDisplayBitcoinCoreFlag = function(value) {
|
|
||||||
var flag = {
|
|
||||||
initialized: value
|
|
||||||
};
|
|
||||||
storage.set('displayBitcoinCoreFlag', flag, function() { });
|
|
||||||
}
|
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -252,6 +252,13 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 10px 30px;
|
padding: 10px 30px;
|
||||||
|
|
||||||
|
.text-selectable {
|
||||||
|
-webkit-user-select: text;
|
||||||
|
-moz-user-select: text;
|
||||||
|
-ms-user-select: text;
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
|
||||||
.primary-amount {
|
.primary-amount {
|
||||||
input, .unit, .primary-amount-display {
|
input, .unit, .primary-amount-display {
|
||||||
font-size: 1.8em;
|
font-size: 1.8em;
|
||||||
|
|
|
||||||
|
|
@ -29,15 +29,6 @@
|
||||||
<ion-toggle ng-model="hideNextSteps.value" ng-if="!isWindowsPhoneApp" toggle-class="toggle-balanced" ng-change="nextStepsChange()">
|
<ion-toggle ng-model="hideNextSteps.value" ng-if="!isWindowsPhoneApp" toggle-class="toggle-balanced" ng-change="nextStepsChange()">
|
||||||
<span class="toggle-label" translate>Hide Next Steps Card</span>
|
<span class="toggle-label" translate>Hide Next Steps Card</span>
|
||||||
</ion-toggle>
|
</ion-toggle>
|
||||||
|
|
||||||
<div class="item item-divider"></div>
|
|
||||||
|
|
||||||
<ion-toggle class="has-comment" ng-model="displayBitcoinCoreEnabled.value" toggle-class="toggle-balanced" ng-change="displayBitcoinCoreChange()">
|
|
||||||
<span class="toggle-label" translate>Bitcoin Core Wallet</span>
|
|
||||||
</ion-toggle>
|
|
||||||
<div class="comment" translate>
|
|
||||||
If enabled, Bitcoin Core (BTC) wallet(s) will be displayed on the Home screen. If disabled, BTC wallets will be not be deleted, only hidden.
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
</ion-view>
|
</ion-view>
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,11 @@
|
||||||
<div class="send-amount-tool-input amount">
|
<div class="send-amount-tool-input amount">
|
||||||
<div class="primary-amount"
|
<div class="primary-amount"
|
||||||
ng-class="{long: amountModel.amount.length > 5, 'very-long': amountModel.amount.length > 10}">
|
ng-class="{long: amountModel.amount.length > 5, 'very-long': amountModel.amount.length > 10}">
|
||||||
<span class="primary-amount-display">{{ amountModel.amount || 0 }}</span><span class="unit">{{unit}}</span>
|
<span class="primary-amount-display text-selectable">{{ amountModel.amount || 0 }}</span><span class="unit">{{unit}}</span>
|
||||||
</div>
|
</div>
|
||||||
<span ng-show="globalResult">{{globalResult}} {{unit}}</span>
|
<span ng-show="globalResult">{{globalResult}} {{unit}}</span>
|
||||||
<div class="alternative-amount">
|
<div class="alternative-amount">
|
||||||
<span>{{alternativeAmount || '0.00'}} {{alternativeUnit}}</span>
|
<span class="text-selectable">{{alternativeAmount || '0.00'}}</span> <span>{{alternativeUnit}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="switch-currencies" ng-click="changeUnit()"><img src="img/icon-convert.svg"></div>
|
<div class="switch-currencies" ng-click="changeUnit()"><img src="img/icon-convert.svg"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,13 @@
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="subheader" ng-if="walletsBtc[0] && walletsBch[0] && displayWallet" translate>
|
<div class="subheader" ng-if="walletsBtc[0] && walletsBch[0]" translate>
|
||||||
<img class="wallet-coin-logo" src="img/icon-bitcoin.svg" width="18">
|
<img class="wallet-coin-logo" src="img/icon-bitcoin.svg" width="18">
|
||||||
<span translate>Bitcoin Core (BTC)</span>
|
<span translate>Bitcoin Core (BTC)</span>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
ng-repeat="wallet in walletsBtc track by $index"
|
ng-repeat="wallet in walletsBtc track by $index"
|
||||||
class="item item-icon-left item-big-icon-left item-icon-right wallet"
|
class="item item-icon-left item-big-icon-left item-icon-right wallet"
|
||||||
ng-show="displayWallet"
|
|
||||||
ng-click="selectWallet(wallet)"
|
ng-click="selectWallet(wallet)"
|
||||||
>
|
>
|
||||||
<i class="icon big-icon-svg" ng-include="'views/includes/walletIcon.html'"></i>
|
<i class="icon big-icon-svg" ng-include="'views/includes/walletIcon.html'"></i>
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,7 @@
|
||||||
<a ng-click="hideHomeTip()"><i class="icon ion-ios-close-empty close-home-tip"></i></a>
|
<a ng-click="hideHomeTip()"><i class="icon ion-ios-close-empty close-home-tip"></i></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="title" translate>
|
<div class="title" translate>
|
||||||
Your Bitcoin Cash (BCH) Wallet is ready!
|
Your Bitcoin Wallets are ready!
|
||||||
</div>
|
|
||||||
<div class="subtitle" translate>
|
|
||||||
A Bitcoin Core (BTC) wallet can be displayed from Settings <i class="icon bp-arrow-right"></i> Advanced
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -51,7 +48,6 @@
|
||||||
<a class="item item-icon-right item-heading" ui-sref="tabs.activity">
|
<a class="item item-icon-right item-heading" ui-sref="tabs.activity">
|
||||||
<span translate>Recent Transactions</span>
|
<span translate>Recent Transactions</span>
|
||||||
<i class="icon bp-arrow-right"></i>
|
<i class="icon bp-arrow-right"></i>
|
||||||
<span class="badge badge-assertive m5t m10r" ng-show="notificationsN>3"> {{notificationsN}}</span>
|
|
||||||
</a>
|
</a>
|
||||||
<a class="item item-sub activity" ng-repeat="notification in notifications" ng-click="openNotificationModal(notification)">
|
<a class="item item-sub activity" ng-repeat="notification in notifications" ng-click="openNotificationModal(notification)">
|
||||||
<span ng-include="'views/includes/walletActivity.html'"></span>
|
<span ng-include="'views/includes/walletActivity.html'"></span>
|
||||||
|
|
@ -73,8 +69,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="list card"
|
<div class="list card">
|
||||||
ng-show="displayBitcoinCore">
|
|
||||||
<div class="item item-icon-right item-heading">
|
<div class="item item-icon-right item-heading">
|
||||||
<img class="wallet-coin-logo" src="img/icon-bitcoin.svg" width="18">
|
<img class="wallet-coin-logo" src="img/icon-bitcoin.svg" width="18">
|
||||||
<span translate>Bitcoin Core (BTC)</span>
|
<span translate>Bitcoin Core (BTC)</span>
|
||||||
|
|
|
||||||
|
|
@ -75,8 +75,7 @@
|
||||||
<a class="item item-icon-left item-icon-right"
|
<a class="item item-icon-left item-icon-right"
|
||||||
ng-repeat="item in list"
|
ng-repeat="item in list"
|
||||||
ng-if="hasWallets && item.recipientType == 'wallet'"
|
ng-if="hasWallets && item.recipientType == 'wallet'"
|
||||||
ng-click="goToAmount(item)"
|
ng-click="goToAmount(item)">
|
||||||
ng-show="item.displayWallet">
|
|
||||||
<i class="icon big-icon-svg" ng-if="item.recipientType == 'wallet'" ng-init="wallet = item" ng-include="'views/includes/walletIcon.html'"></i>
|
<i class="icon big-icon-svg" ng-if="item.recipientType == 'wallet'" ng-init="wallet = item" ng-include="'views/includes/walletIcon.html'"></i>
|
||||||
{{item.name}}
|
{{item.name}}
|
||||||
<p>
|
<p>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue