From 284dd85a99f538b77fd477f4952117d1792f64a5 Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 6 Apr 2018 19:59:51 -0230 Subject: first commit --- ui/app/send-v2.js | 53 ++++++++++++++++------------------------------------- 1 file changed, 16 insertions(+), 37 deletions(-) (limited to 'ui/app/send-v2.js') diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index 094743ff0..d608957c8 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -31,6 +31,11 @@ const { } = require('./components/send/send-utils') const { isValidAddress } = require('./util') +import PageContainer from './components/page-container/page-container.component' +import SendHeader from './components/send_/send-header/send-header.container' +import PageContainerContent from './components/page-container/page-container-content.component' +import PageContainerFooter from './components/page-container/page-container-footer.component' + SendTransactionScreen.contextTypes = { t: PropTypes.func, } @@ -181,25 +186,6 @@ SendTransactionScreen.prototype.componentDidUpdate = function (prevProps) { } } -SendTransactionScreen.prototype.renderHeader = function () { - const { selectedToken, clearSend, goHome } = this.props - - return h('div.page-container__header', [ - - h('div.page-container__title', selectedToken ? this.context.t('sendTokens') : this.context.t('sendETH')), - - h('div.page-container__subtitle', this.context.t('onlySendToEtherAddress')), - - h('div.page-container__header-close', { - onClick: () => { - clearSend() - goHome() - }, - }), - - ]) -} - SendTransactionScreen.prototype.renderErrorMessage = function (errorType) { const { errors } = this.props const errorMessage = errors[errorType] @@ -477,7 +463,7 @@ SendTransactionScreen.prototype.renderMemoRow = function () { } SendTransactionScreen.prototype.renderForm = function () { - return h('.page-container__content', {}, [ + return h(PageContainerContent, [ h('.send-v2__form', [ this.renderFromRow(), @@ -486,9 +472,6 @@ SendTransactionScreen.prototype.renderForm = function () { this.renderAmountRow(), this.renderGasRow(), - - // this.renderMemoRow(), - ]), ]) } @@ -506,26 +489,22 @@ SendTransactionScreen.prototype.renderFooter = function () { const missingTokenBalance = selectedToken && !tokenBalance const noErrors = !amountError && toError === null - return h('div.page-container__footer', [ - h('button.btn-secondary--lg.page-container__footer-button', { - onClick: () => { - clearSend() - goHome() - }, - }, this.context.t('cancel')), - h('button.btn-primary--lg.page-container__footer-button', { - disabled: !noErrors || !gasTotal || missingTokenBalance, - onClick: event => this.onSubmit(event), - }, this.context.t('next')), - ]) + return h(PageContainerFooter, { + onCancel: () => { + clearSend() + goHome() + }, + onSubmit: e => this.onSubmit(e), + disabled: !noErrors || !gasTotal || missingTokenBalance, + }) } SendTransactionScreen.prototype.render = function () { return ( - h('div.page-container', [ + h(PageContainer, [ - this.renderHeader(), + h(SendHeader), this.renderForm(), -- cgit v1.2.3 From 8ff7806f1b471a90fa3f45ebc10f0f4452ade541 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 26 Apr 2018 14:08:38 -0230 Subject: Core of the refactor complete --- ui/app/send-v2.js | 401 +----------------------------------------------------- 1 file changed, 6 insertions(+), 395 deletions(-) (limited to 'ui/app/send-v2.js') diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index c5085d9ec..12e8b4e60 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -34,8 +34,8 @@ const { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } = require('./routes') import PageContainer from './components/page-container/page-container.component' import SendHeader from './components/send_/send-header/send-header.container' -import PageContainerContent from './components/page-container/page-container-content.component' -import PageContainerFooter from './components/page-container/page-container-footer.component' +import SendContent from './components/send_/send-content/send-content.component' +import SendFooter from './components/send_/send-footer/send-footer.container' SendTransactionScreen.contextTypes = { t: PropTypes.func, @@ -57,8 +57,6 @@ function SendTransactionScreen () { gasLoadingError: false, } - this.handleToChange = this.handleToChange.bind(this) - this.handleAmountChange = this.handleAmountChange.bind(this) this.validateAmount = this.validateAmount.bind(this) } @@ -176,158 +174,6 @@ SendTransactionScreen.prototype.componentDidUpdate = function (prevProps) { } } -SendTransactionScreen.prototype.renderHeader = function () { - const { selectedToken, clearSend, history } = this.props - - return h('div.page-container__header', [ - - h('div.page-container__title', selectedToken ? this.context.t('sendTokens') : this.context.t('sendETH')), - - h('div.page-container__subtitle', this.context.t('onlySendToEtherAddress')), - - h('div.page-container__header-close', { - onClick: () => { - clearSend() - history.push(DEFAULT_ROUTE) - }, - }), - - ]) -} - -SendTransactionScreen.prototype.renderErrorMessage = function (errorType) { - const { errors } = this.props - const errorMessage = errors[errorType] - - return errorMessage - ? h('div.send-v2__error', [ errorMessage ]) - : null -} - -SendTransactionScreen.prototype.handleFromChange = async function (newFrom) { - const { - updateSendFrom, - tokenContract, - } = this.props - - if (tokenContract) { - const usersToken = await tokenContract.balanceOf(newFrom.address) - this.updateSendTokenBalance(usersToken) - } - updateSendFrom(newFrom) -} - -SendTransactionScreen.prototype.renderFromRow = function () { - const { - from, - fromAccounts, - conversionRate, - } = this.props - - const { fromDropdownOpen } = this.state - - return h('div.send-v2__form-row', [ - - h('div.send-v2__form-label', 'From:'), - - h('div.send-v2__form-field', [ - h(FromDropdown, { - dropdownOpen: fromDropdownOpen, - accounts: fromAccounts, - selectedAccount: from, - onSelect: newFrom => this.handleFromChange(newFrom), - openDropdown: () => this.setState({ fromDropdownOpen: true }), - closeDropdown: () => this.setState({ fromDropdownOpen: false }), - conversionRate, - }), - ]), - - ]) -} - -SendTransactionScreen.prototype.handleToChange = function (to, nickname = '') { - const { - updateSendTo, - updateSendErrors, - } = this.props - let toError = null - - if (!to) { - toError = this.context.t('required') - } else if (!isValidAddress(to)) { - toError = this.context.t('invalidAddressRecipient') - } - - updateSendTo(to, nickname) - updateSendErrors({ to: toError }) -} - -SendTransactionScreen.prototype.renderToRow = function () { - const { toAccounts, errors, to, network } = this.props - - const { toDropdownOpen } = this.state - - return h('div.send-v2__form-row', [ - - h('div.send-v2__form-label', [ - - this.context.t('to'), - - this.renderErrorMessage(this.context.t('to')), - - ]), - - h('div.send-v2__form-field', [ - h(EnsInput, { - name: 'address', - placeholder: 'Recipient Address', - network, - to, - accounts: Object.entries(toAccounts).map(([key, account]) => account), - dropdownOpen: toDropdownOpen, - openDropdown: () => this.setState({ toDropdownOpen: true }), - closeDropdown: () => this.setState({ toDropdownOpen: false }), - onChange: this.handleToChange, - inError: Boolean(errors.to), - }), - ]), - - ]) -} - -SendTransactionScreen.prototype.handleAmountChange = function (value) { - const amount = value - const { updateSendAmount, setMaxModeTo } = this.props - - setMaxModeTo(false) - this.validateAmount(amount) - updateSendAmount(amount) -} - -SendTransactionScreen.prototype.setAmountToMax = function () { - const { - from: { balance }, - updateSendAmount, - updateSendErrors, - tokenBalance, - selectedToken, - gasTotal, - } = this.props - const { decimals } = selectedToken || {} - const multiplier = Math.pow(10, Number(decimals || 0)) - - const maxAmount = selectedToken - ? multiplyCurrencies(tokenBalance, multiplier, {toNumericBase: 'hex'}) - : subtractCurrencies( - ethUtil.addHexPrefix(balance), - ethUtil.addHexPrefix(gasTotal), - { toNumericBase: 'hex' } - ) - - updateSendErrors({ amount: null }) - - updateSendAmount(maxAmount) -} SendTransactionScreen.prototype.validateAmount = function (value) { const { @@ -384,254 +230,19 @@ SendTransactionScreen.prototype.validateAmount = function (value) { updateSendErrors({ amount: amountError }) } -SendTransactionScreen.prototype.renderAmountRow = function () { - const { - selectedToken, - primaryCurrency = 'ETH', - convertedCurrency, - amountConversionRate, - errors, - amount, - setMaxModeTo, - maxModeOn, - gasTotal, - } = this.props - - return h('div.send-v2__form-row', [ - - h('div.send-v2__form-label', [ - 'Amount:', - this.renderErrorMessage('amount'), - !errors.amount && gasTotal && h('div.send-v2__amount-max', { - onClick: (event) => { - event.preventDefault() - setMaxModeTo(true) - this.setAmountToMax() - }, - }, [ !maxModeOn ? this.context.t('max') : '' ]), - ]), - - h('div.send-v2__form-field', [ - h(CurrencyDisplay, { - inError: Boolean(errors.amount), - primaryCurrency, - convertedCurrency, - selectedToken, - value: amount || '0x0', - conversionRate: amountConversionRate, - handleChange: this.handleAmountChange, - }), - ]), - - ]) -} - -SendTransactionScreen.prototype.renderGasRow = function () { - const { - conversionRate, - convertedCurrency, - showCustomizeGasModal, - gasTotal, - } = this.props - const { gasLoadingError } = this.state - - return h('div.send-v2__form-row', [ - - h('div.send-v2__form-label', this.context.t('gasFee')), - - h('div.send-v2__form-field', [ - - h(GasFeeDisplay, { - gasTotal, - conversionRate, - convertedCurrency, - onClick: showCustomizeGasModal, - gasLoadingError, - }), - - ]), - - ]) -} - -SendTransactionScreen.prototype.renderMemoRow = function () { - const { updateSendMemo, memo } = this.props - - return h('div.send-v2__form-row', [ - - h('div.send-v2__form-label', 'Transaction Memo:'), - - h('div.send-v2__form-field', [ - h(MemoTextArea, { - memo, - onChange: (event) => updateSendMemo(event.target.value), - }), - ]), - - ]) -} - -SendTransactionScreen.prototype.renderForm = function () { - return h(PageContainerContent, [ - h('.send-v2__form', [ - this.renderFromRow(), - - this.renderToRow(), - - this.renderAmountRow(), - - this.renderGasRow(), - ]), - ]) -} - -SendTransactionScreen.prototype.renderFooter = function () { - const { - clearSend, - gasTotal, - tokenBalance, - selectedToken, - errors: { amount: amountError, to: toError }, - history, - } = this.props - - const missingTokenBalance = selectedToken && !tokenBalance - const noErrors = !amountError && toError === null - - return h(PageContainerFooter, { - onCancel: () => { - clearSend() - history.push(DEFAULT_ROUTE) - }, - onSubmit: e => this.onSubmit(e), - disabled: !noErrors || !gasTotal || missingTokenBalance, - }) -} - SendTransactionScreen.prototype.render = function () { + const { history } = this.props + return ( h(PageContainer, [ h(SendHeader), - this.renderForm(), + h(SendContent), - this.renderFooter(), + h(SendFooter, { history }), ]) ) } - -SendTransactionScreen.prototype.addToAddressBookIfNew = function (newAddress, nickname = '') { - const { toAccounts, addToAddressBook } = this.props - if (!toAccounts.find(({ address }) => newAddress === address)) { - // TODO: nickname, i.e. addToAddressBook(recipient, nickname) - addToAddressBook(newAddress, nickname) - } -} - -SendTransactionScreen.prototype.getEditedTx = function () { - const { - from: {address: from}, - to, - amount, - gasLimit: gas, - gasPrice, - selectedToken, - editingTransactionId, - unapprovedTxs, - } = this.props - - const editingTx = { - ...unapprovedTxs[editingTransactionId], - txParams: { - from: ethUtil.addHexPrefix(from), - gas: ethUtil.addHexPrefix(gas), - gasPrice: ethUtil.addHexPrefix(gasPrice), - }, - } - - if (selectedToken) { - const data = TOKEN_TRANSFER_FUNCTION_SIGNATURE + Array.prototype.map.call( - ethAbi.rawEncode(['address', 'uint256'], [to, ethUtil.addHexPrefix(amount)]), - x => ('00' + x.toString(16)).slice(-2) - ).join('') - - Object.assign(editingTx.txParams, { - value: ethUtil.addHexPrefix('0'), - to: ethUtil.addHexPrefix(selectedToken.address), - data, - }) - } else { - const { data } = unapprovedTxs[editingTransactionId].txParams - - Object.assign(editingTx.txParams, { - value: ethUtil.addHexPrefix(amount), - to: ethUtil.addHexPrefix(to), - data, - }) - - if (typeof editingTx.txParams.data === 'undefined') { - delete editingTx.txParams.data - } - } - - return editingTx -} - -SendTransactionScreen.prototype.onSubmit = function (event) { - event.preventDefault() - const { - from: {address: from}, - to: _to, - amount, - gasLimit: gas, - gasPrice, - signTokenTx, - signTx, - updateTx, - selectedToken, - editingTransactionId, - toNickname, - errors: { amount: amountError, to: toError }, - } = this.props - - const noErrors = !amountError && toError === null - - if (!noErrors) { - return - } - - const to = ethUtil.addHexPrefix(_to) - - this.addToAddressBookIfNew(to, toNickname) - - if (editingTransactionId) { - const editedTx = this.getEditedTx() - updateTx(editedTx) - } else { - - const txParams = { - from, - value: '0', - gas, - gasPrice, - } - - if (!selectedToken) { - txParams.value = amount - txParams.to = to - } - - Object.keys(txParams).forEach(key => { - txParams[key] = ethUtil.addHexPrefix(txParams[key]) - }) - - selectedToken - ? signTokenTx(selectedToken.address, to, amount, txParams) - : signTx(txParams) - } - - this.props.history.push(CONFIRM_TRANSACTION_ROUTE) -} -- cgit v1.2.3 From 91c201aa72581a59a0d2ef73a225b1768584dea7 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 26 Apr 2018 22:08:14 -0230 Subject: Lint fixes and alphabetization for i3725-refactor-send-component --- ui/app/send-v2.js | 17 ----------------- 1 file changed, 17 deletions(-) (limited to 'ui/app/send-v2.js') diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index 12e8b4e60..efe06ee64 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -3,23 +3,8 @@ const PropTypes = require('prop-types') const PersistentForm = require('../lib/persistent-form') const h = require('react-hyperscript') -const ethAbi = require('ethereumjs-abi') -const ethUtil = require('ethereumjs-util') - -const FromDropdown = require('./components/send/from-dropdown') -const EnsInput = require('./components/ens-input') -const CurrencyDisplay = require('./components/send/currency-display') -const MemoTextArea = require('./components/send/memo-textarea') -const GasFeeDisplay = require('./components/send/gas-fee-display-v2') - -const { - TOKEN_TRANSFER_FUNCTION_SIGNATURE, -} = require('./components/send/send-constants') - const { - multiplyCurrencies, conversionGreaterThan, - subtractCurrencies, } = require('./conversion-util') const { calcTokenAmount, @@ -29,8 +14,6 @@ const { isTokenBalanceSufficient, getGasTotal, } = require('./components/send/send-utils') -const { isValidAddress } = require('./util') -const { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } = require('./routes') import PageContainer from './components/page-container/page-container.component' import SendHeader from './components/send_/send-header/send-header.container' -- cgit v1.2.3 From 33c16d1bf62133a87d8f24232ee85438a6b6a0e6 Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 27 Apr 2018 08:11:18 -0230 Subject: Fixes to get tests passing. --- ui/app/send-v2.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'ui/app/send-v2.js') diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index efe06ee64..ef67ebf5e 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -203,11 +203,11 @@ SendTransactionScreen.prototype.validateAmount = function (value) { ) if (conversionRate && !sufficientBalance) { - amountError = this.context.t('insufficientFunds') + amountError = 'insufficientFunds' } else if (verifyTokenBalance && !sufficientTokens) { - amountError = this.context.t('insufficientTokens') + amountError = 'insufficientTokens' } else if (amountLessThanZero) { - amountError = this.context.t('negativeETH') + amountError = 'negativeETH' } updateSendErrors({ amount: amountError }) -- cgit v1.2.3 From 26f965bcceb26ce7cdec9df9f213b44be8d9acac Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 27 Apr 2018 16:33:00 -0230 Subject: Further refactors; includes refactor of send-v2.js and associated container --- ui/app/send-v2.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'ui/app/send-v2.js') diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index ef67ebf5e..16964b45d 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -84,7 +84,7 @@ SendTransactionScreen.prototype.updateGas = function () { estimateGas, selectedAddress, data, - updateGasTotal, + setGasTotal, from, tokenContract, editingTransactionId, @@ -110,7 +110,7 @@ SendTransactionScreen.prototype.updateGas = function () { ]) .then(([gasPrice, gas]) => { const newGasTotal = getGasTotal(gas, gasPrice) - updateGasTotal(newGasTotal) + setGasTotal(newGasTotal) this.setState({ gasLoadingError: false }) }) .catch(err => { @@ -118,7 +118,7 @@ SendTransactionScreen.prototype.updateGas = function () { }) } else { const newGasTotal = getGasTotal(gasLimit, gasPrice) - updateGasTotal(newGasTotal) + setGasTotal(newGasTotal) } } -- cgit v1.2.3 From e488c0eeeace80708285fa5e7d83f3fa219a86c8 Mon Sep 17 00:00:00 2001 From: Dan Date: Tue, 1 May 2018 00:25:36 -0230 Subject: Delete dead send code. --- ui/app/send-v2.js | 231 ------------------------------------------------------ 1 file changed, 231 deletions(-) delete mode 100644 ui/app/send-v2.js (limited to 'ui/app/send-v2.js') diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js deleted file mode 100644 index 16964b45d..000000000 --- a/ui/app/send-v2.js +++ /dev/null @@ -1,231 +0,0 @@ -const { inherits } = require('util') -const PropTypes = require('prop-types') -const PersistentForm = require('../lib/persistent-form') -const h = require('react-hyperscript') - -const { - conversionGreaterThan, -} = require('./conversion-util') -const { - calcTokenAmount, -} = require('./token-util') -const { - isBalanceSufficient, - isTokenBalanceSufficient, - getGasTotal, -} = require('./components/send/send-utils') - -import PageContainer from './components/page-container/page-container.component' -import SendHeader from './components/send_/send-header/send-header.container' -import SendContent from './components/send_/send-content/send-content.component' -import SendFooter from './components/send_/send-footer/send-footer.container' - -SendTransactionScreen.contextTypes = { - t: PropTypes.func, -} - -module.exports = SendTransactionScreen - -inherits(SendTransactionScreen, PersistentForm) -function SendTransactionScreen () { - PersistentForm.call(this) - - this.state = { - fromDropdownOpen: false, - toDropdownOpen: false, - errors: { - to: null, - amount: null, - }, - gasLoadingError: false, - } - - this.validateAmount = this.validateAmount.bind(this) -} - -const getParamsForGasEstimate = function (selectedAddress, symbol, data) { - const estimatedGasParams = { - from: selectedAddress, - gas: '746a528800', - } - - if (symbol) { - Object.assign(estimatedGasParams, { value: '0x0' }) - } - - if (data) { - Object.assign(estimatedGasParams, { data }) - } - - return estimatedGasParams -} - -SendTransactionScreen.prototype.updateSendTokenBalance = function (usersToken) { - if (!usersToken) return - - const { - selectedToken = {}, - updateSendTokenBalance, - } = this.props - const { decimals } = selectedToken || {} - const tokenBalance = calcTokenAmount(usersToken.balance.toString(), decimals) - - updateSendTokenBalance(tokenBalance) -} - -SendTransactionScreen.prototype.componentWillMount = function () { - this.updateGas() -} - -SendTransactionScreen.prototype.updateGas = function () { - const { - selectedToken = {}, - getGasPrice, - estimateGas, - selectedAddress, - data, - setGasTotal, - from, - tokenContract, - editingTransactionId, - gasPrice, - gasLimit, - } = this.props - - const { symbol } = selectedToken || {} - - const tokenBalancePromise = tokenContract - ? tokenContract.balanceOf(from.address) - : Promise.resolve() - tokenBalancePromise - .then(usersToken => this.updateSendTokenBalance(usersToken)) - - if (!editingTransactionId) { - const estimateGasParams = getParamsForGasEstimate(selectedAddress, symbol, data) - - Promise - .all([ - getGasPrice(), - estimateGas(estimateGasParams), - ]) - .then(([gasPrice, gas]) => { - const newGasTotal = getGasTotal(gas, gasPrice) - setGasTotal(newGasTotal) - this.setState({ gasLoadingError: false }) - }) - .catch(err => { - this.setState({ gasLoadingError: true }) - }) - } else { - const newGasTotal = getGasTotal(gasLimit, gasPrice) - setGasTotal(newGasTotal) - } -} - -SendTransactionScreen.prototype.componentDidUpdate = function (prevProps) { - const { - from: { balance }, - gasTotal, - tokenBalance, - amount, - selectedToken, - network, - } = this.props - - const { - from: { balance: prevBalance }, - gasTotal: prevGasTotal, - tokenBalance: prevTokenBalance, - network: prevNetwork, - } = prevProps - - const uninitialized = [prevBalance, prevGasTotal].every(n => n === null) - - const balanceHasChanged = balance !== prevBalance - const gasTotalHasChange = gasTotal !== prevGasTotal - const tokenBalanceHasChanged = selectedToken && tokenBalance !== prevTokenBalance - const amountValidationChange = balanceHasChanged || gasTotalHasChange || tokenBalanceHasChanged - - if (!uninitialized) { - if (amountValidationChange) { - this.validateAmount(amount) - } - - if (network !== prevNetwork && network !== 'loading') { - this.updateGas() - } - } -} - - -SendTransactionScreen.prototype.validateAmount = function (value) { - const { - from: { balance }, - updateSendErrors, - amountConversionRate, - conversionRate, - primaryCurrency, - selectedToken, - gasTotal, - tokenBalance, - } = this.props - const { decimals } = selectedToken || {} - const amount = value - - let amountError = null - - let sufficientBalance = true - - if (gasTotal) { - sufficientBalance = isBalanceSufficient({ - amount: selectedToken ? '0x0' : amount, - gasTotal, - balance, - primaryCurrency, - amountConversionRate, - conversionRate, - }) - } - - const verifyTokenBalance = selectedToken && tokenBalance !== null - let sufficientTokens - if (verifyTokenBalance) { - sufficientTokens = isTokenBalanceSufficient({ - tokenBalance, - amount, - decimals, - }) - } - - const amountLessThanZero = conversionGreaterThan( - { value: 0, fromNumericBase: 'dec' }, - { value: amount, fromNumericBase: 'hex' }, - ) - - if (conversionRate && !sufficientBalance) { - amountError = 'insufficientFunds' - } else if (verifyTokenBalance && !sufficientTokens) { - amountError = 'insufficientTokens' - } else if (amountLessThanZero) { - amountError = 'negativeETH' - } - - updateSendErrors({ amount: amountError }) -} - -SendTransactionScreen.prototype.render = function () { - const { history } = this.props - - return ( - - h(PageContainer, [ - - h(SendHeader), - - h(SendContent), - - h(SendFooter, { history }), - ]) - - ) -} -- cgit v1.2.3