diff --git a/.circleci/config.yml b/.circleci/config.yml index 92a2b1d9e5..cbb6732073 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -122,6 +122,7 @@ jobs: - mix.exs - mix.lock - appspec.yml + - rel check_formatted: docker: # Ensure .tool-versions matches @@ -279,6 +280,32 @@ jobs: name: Jest command: ./node_modules/.bin/jest working_directory: apps/block_scout_web/assets + release: + docker: + # Ensure .tool-versions matches + - image: circleci/elixir:1.7.2 + environment: + MIX_ENV: prod + + working_directory: ~/app + + steps: + - attach_workspace: + at: . + + - run: mix local.hex --force + - run: mix local.rebar --force + - run: mix release --verbose --env prod + - run: + name: Collecting artifacts + command: | + find -name 'blockscout.tar.gz' -exec sh -c 'mkdir -p ci_artifact && cp "$@" ci_artifact/ci_artifact_blockscout.tar.gz' _ {} + + when: always + + - store_artifacts: + name: Uploading CI artifacts + path: ci_artifact/ci_artifact_blockscout.tar.gz + destination: ci_artifact_blockscout.tar.gz sobelow: docker: # Ensure .tool-versions matches @@ -561,6 +588,9 @@ workflows: - jest: requires: - build + - release: + requires: + - build - sobelow: requires: - build diff --git a/apps/block_scout_web/assets/__tests__/lib/async_listing_load.js b/apps/block_scout_web/assets/__tests__/lib/async_listing_load.js new file mode 100644 index 0000000000..6e86d8a6b5 --- /dev/null +++ b/apps/block_scout_web/assets/__tests__/lib/async_listing_load.js @@ -0,0 +1,80 @@ +import { asyncReducer, asyncInitialState } from '../../js/lib/async_listing_load' + +describe('ELEMENTS_LOAD', () => { + test('sets only nextPagePath and ignores other keys', () => { + const state = Object.assign({}, asyncInitialState) + const action = { type: 'ELEMENTS_LOAD', nextPagePath: 'set', foo: 1 } + const output = asyncReducer(state, action) + + expect(output.foo).not.toEqual(1) + expect(output.nextPagePath).toEqual('set') + }) +}) + +describe('ADD_ITEM_KEY', () => { + test('sets itemKey to what was passed in the action', () => { + const expectedItemKey = 'expected.Key' + + const state = Object.assign({}, asyncInitialState) + const action = { type: 'ADD_ITEM_KEY', itemKey: expectedItemKey } + const output = asyncReducer(state, action) + + expect(output.itemKey).toEqual(expectedItemKey) + }) +}) + +describe('START_REQUEST', () => { + test('sets loading status to true', () => { + const state = Object.assign({}, asyncInitialState, { loading: false }) + const action = { type: 'START_REQUEST' } + const output = asyncReducer(state, action) + + expect(output.loading).toEqual(true) + }) +}) + +describe('REQUEST_ERROR', () => { + test('sets requestError to true', () => { + const state = Object.assign({}, asyncInitialState, { requestError: false }) + const action = { type: 'REQUEST_ERROR' } + const output = asyncReducer(state, action) + + expect(output.requestError).toEqual(true) + }) +}) + +describe('FINISH_REQUEST', () => { + test('sets loading status to false', () => { + const state = Object.assign({}, asyncInitialState, { + loading: true, + loadingFirstPage: true + }) + const action = { type: 'FINISH_REQUEST' } + const output = asyncReducer(state, action) + + expect(output.loading).toEqual(false) + expect(output.loadingFirstPage).toEqual(false) + }) +}) + +describe('ITEMS_FETCHED', () => { + test('sets the items to what was passed in the action', () => { + const expectedItems = [1, 2, 3] + + const state = Object.assign({}, asyncInitialState) + const action = { type: 'ITEMS_FETCHED', items: expectedItems } + const output = asyncReducer(state, action) + + expect(output.items).toEqual(expectedItems) + }) +}) + +describe('NAVIGATE_TO_OLDER', () => { + test('sets beyondPageOne to true', () => { + const state = Object.assign({}, asyncInitialState, { beyondPageOne: false }) + const action = { type: 'NAVIGATE_TO_OLDER' } + const output = asyncReducer(state, action) + + expect(output.beyondPageOne).toEqual(true) + }) +}) diff --git a/apps/block_scout_web/assets/__tests__/pages/address.js b/apps/block_scout_web/assets/__tests__/pages/address.js index ca6f188bef..fe6506e22b 100644 --- a/apps/block_scout_web/assets/__tests__/pages/address.js +++ b/apps/block_scout_web/assets/__tests__/pages/address.js @@ -172,70 +172,9 @@ describe('RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH', () => { }) }) -describe('RECEIVED_NEW_PENDING_TRANSACTION', () => { - test('with new pending transaction', () => { - const state = Object.assign({}, initialState, { - pendingTransactions: [{ transactionHash: 1, transactionHtml: 'test 1' }] - }) - const action = { - type: 'RECEIVED_NEW_PENDING_TRANSACTION', - msg: { transactionHash: 2, transactionHtml: 'test 2' } - } - const output = reducer(state, action) - - expect(output.pendingTransactions).toEqual([ - { transactionHash: 2, transactionHtml: 'test 2' }, - { transactionHash: 1, transactionHtml: 'test 1' } - ]) - }) - test('when channel has been disconnected', () => { - const state = Object.assign({}, initialState, { - channelDisconnected: true, - pendingTransactions: [{ transactionHash: 1, transactionHtml: 'test 1' }] - }) - const action = { - type: 'RECEIVED_NEW_PENDING_TRANSACTION', - msg: { transactionHash: 2, transactionHtml: 'test 2' } - } - const output = reducer(state, action) - - expect(output.pendingTransactions).toEqual([ - { transactionHash: 1, transactionHtml: 'test 1' } - ]) - }) - test('beyond page one', () => { - const state = Object.assign({}, initialState, { - beyondPageOne: true, - pendingTransactions: [{ transactionHash: 1, transactionHtml: 'test 1' }] - }) - const action = { - type: 'RECEIVED_NEW_PENDING_TRANSACTION', - msg: { transactionHash: 2, transactionHtml: 'test 2' } - } - const output = reducer(state, action) - - expect(output.pendingTransactions).toEqual([ - { transactionHash: 1, transactionHtml: 'test 1' } - ]) - }) - test('with filtered out pending transaction', () => { - const state = Object.assign({}, initialState, { - filter: 'to' - }) - const action = { - type: 'RECEIVED_NEW_PENDING_TRANSACTION', - msg: { transactionHash: 2, transactionHtml: 'test 2' } - } - const output = reducer(state, action) - - expect(output.pendingTransactions).toEqual([]) - }) -}) - describe('RECEIVED_NEW_TRANSACTION', () => { test('with new transaction', () => { const state = Object.assign({}, initialState, { - pendingTransactions: [{ transactionHash: 2, transactionHtml: 'test' }], transactions: [{ transactionHash: 1, transactionHtml: 'test 1' }] }) const action = { @@ -244,9 +183,6 @@ describe('RECEIVED_NEW_TRANSACTION', () => { } const output = reducer(state, action) - expect(output.pendingTransactions).toEqual([ - { transactionHash: 2, transactionHtml: 'test 2', validated: true } - ]) expect(output.transactions).toEqual([ { transactionHash: 2, transactionHtml: 'test 2' }, { transactionHash: 1, transactionHtml: 'test 1' } @@ -255,7 +191,6 @@ describe('RECEIVED_NEW_TRANSACTION', () => { test('when channel has been disconnected', () => { const state = Object.assign({}, initialState, { channelDisconnected: true, - pendingTransactions: [{ transactionHash: 2, transactionHtml: 'test' }], transactions: [{ transactionHash: 1, transactionHtml: 'test 1' }] }) const action = { @@ -264,9 +199,6 @@ describe('RECEIVED_NEW_TRANSACTION', () => { } const output = reducer(state, action) - expect(output.pendingTransactions).toEqual([ - { transactionHash: 2, transactionHtml: 'test' } - ]) expect(output.transactions).toEqual([ { transactionHash: 1, transactionHtml: 'test 1' } ]) @@ -282,7 +214,6 @@ describe('RECEIVED_NEW_TRANSACTION', () => { } const output = reducer(state, action) - expect(output.pendingTransactions).toEqual([]) expect(output.transactions).toEqual([ { transactionHash: 1, transactionHtml: 'test 1' } ]) diff --git a/apps/block_scout_web/assets/css/app.scss b/apps/block_scout_web/assets/css/app.scss index 3c722e8ec9..125c00877d 100644 --- a/apps/block_scout_web/assets/css/app.scss +++ b/apps/block_scout_web/assets/css/app.scss @@ -47,6 +47,9 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts"; @import "node_modules/bootstrap/scss/badge"; @import "node_modules/bootstrap/scss/alert"; +// Code highlight +@import "node_modules/highlight.js/styles/default"; + //Custom theme @import "theme/fonts"; @@ -82,6 +85,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts"; @import "components/dropdown"; @import "components/loading-spinner"; @import "components/transaction-input"; +@import "components/highlight"; :export { primary: $primary; diff --git a/apps/block_scout_web/assets/css/components/_highlight.scss b/apps/block_scout_web/assets/css/components/_highlight.scss new file mode 100644 index 0000000000..c504be04f2 --- /dev/null +++ b/apps/block_scout_web/assets/css/components/_highlight.scss @@ -0,0 +1,19 @@ +//replace the default background color from highlightjs +.hljs { + background: $gray-100; +} + +.line-numbers { + + [data-line-number] { + + &:before { + content: attr(data-line-number); + display: inline-block; + border-right: 1px solid $gray-400; + padding: 0 .5em; + margin-right: .5em; + color: $gray-600 + } + } +} diff --git a/apps/block_scout_web/assets/js/app.js b/apps/block_scout_web/assets/js/app.js index 8ff4aa19f9..8f084b5521 100644 --- a/apps/block_scout_web/assets/js/app.js +++ b/apps/block_scout_web/assets/js/app.js @@ -38,6 +38,7 @@ import './lib/market_history_chart' import './lib/pending_transactions_toggle' import './lib/pretty_json' import './lib/reload_button' +import './lib/smart_contract/code_highlighting' import './lib/smart_contract/read_only_functions' import './lib/smart_contract/wei_ether_converter' import './lib/stop_propagation' diff --git a/apps/block_scout_web/assets/js/lib/async_listing_load.js b/apps/block_scout_web/assets/js/lib/async_listing_load.js index f2ffe5fdb7..2e9e4e371b 100644 --- a/apps/block_scout_web/assets/js/lib/async_listing_load.js +++ b/apps/block_scout_web/assets/js/lib/async_listing_load.js @@ -1,85 +1,223 @@ import $ from 'jquery' +import _ from 'lodash' +import URI from 'urijs' +import humps from 'humps' +import listMorph from '../lib/list_morph' +import reduceReducers from 'reduce-reducers' +import { createStore, connectElements } from '../lib/redux_helpers.js' + /** - * This script is a generic function to load list within a tab async. See token transfers tab at Token's page as example. + * This is a generic lib to add pagination with asynchronous page loading. There are two ways of + * activating this in a page. + * + * If the page has no redux associated with, all you need is a markup with the following pattern: + * + *
+ *
message
+ *
message
+ *
message
+ *
+ * button text + *
loading text
+ *
+ * + * the data-async-load is the attribute responsible for binding the store. + * + * If the page has a redux associated with, you need to connect the reducers instead of creating + * the store using the `createStore`. For instance: * - * To get it working the markup must follow the pattern below: + * // my_page.js + * const initialState = { ... } + * const reducer = (state, action) => { ... } + * const store = createAsyncLoadStore(reducer, initialState, 'item.Key') * - *
- *
message
- *
message
- *
message
- *
- * button text - *
loading text
- *
+ * The createAsyncLoadStore function will return a store with asynchronous loading activated. This + * approach will expect the same markup above, except for data-async-load attribute, which is used + * to create a store and it is not necessary for this case. * */ -const $element = $('[data-async-listing]') - -function asyncListing (element, path) { - const $mainElement = $(element) - const $items = $mainElement.find('[data-items]') - const $loading = $mainElement.find('[data-loading-message]') - const $nextPageButton = $mainElement.find('[data-next-page-button]') - const $loadingButton = $mainElement.find('[data-loading-button]') - const $errorMessage = $mainElement.find('[data-error-message]') - const $emptyResponseMessage = $mainElement.find('[data-empty-response-message]') - - $.getJSON(path, {type: 'JSON'}) - .done(response => { - if (!response.items || response.items.length === 0) { - $emptyResponseMessage.show() - $items.empty() - } else { - $items.html(response.items) + +export const asyncInitialState = { + /* it will consider any query param in the current URI as paging */ + beyondPageOne: (URI(window.location).query() !== ''), + /* an array with every html element of the list being shown */ + items: [], + /* the key for diffing the elements in the items array */ + itemKey: null, + /* represents whether a request is happening or not */ + loading: false, + /* if there was an error fetching items */ + requestError: false, + /* if it is loading the first page */ + loadingFirstPage: true, + /* link to the next page */ + nextPagePath: null +} + +export function asyncReducer (state = asyncInitialState, action) { + switch (action.type) { + case 'ELEMENTS_LOAD': { + return Object.assign({}, state, { nextPagePath: action.nextPagePath }) + } + case 'ADD_ITEM_KEY': { + return Object.assign({}, state, { itemKey: action.itemKey }) + } + case 'START_REQUEST': { + return Object.assign({}, state, { + loading: true, + requestError: false + }) + } + case 'REQUEST_ERROR': { + return Object.assign({}, state, { requestError: true }) + } + case 'FINISH_REQUEST': { + return Object.assign({}, state, { + loading: false, + loadingFirstPage: false + }) + } + case 'ITEMS_FETCHED': { + return Object.assign({}, state, { + requestError: false, + items: action.items, + nextPagePath: action.nextPagePath + }) + } + case 'NAVIGATE_TO_OLDER': { + history.replaceState({}, null, state.nextPagePath) + + return Object.assign({}, state, { beyondPageOne: true }) + } + default: + return state + } +} + +export const elements = { + '[data-async-listing]': { + load ($el) { + const nextPagePath = $el.data('async-listing') + + return { nextPagePath } + } + }, + '[data-async-listing] [data-loading-message]': { + render ($el, state) { + if (state.loadingFirstPage) return $el.show() + + $el.hide() + } + }, + '[data-async-listing] [data-empty-response-message]': { + render ($el, state) { + if ( + !state.requestError && + (!state.loading || !state.loadingFirstPage) && + state.items.length === 0 + ) { + return $el.show() } - if (response.next_page_path) { - $nextPageButton.attr('href', response.next_page_path) - $nextPageButton.show() - } else { - $nextPageButton.hide() + + $el.hide() + } + }, + '[data-async-listing] [data-error-message]': { + render ($el, state) { + if (state.requestError) return $el.show() + + $el.hide() + } + }, + '[data-async-listing] [data-items]': { + render ($el, state, oldState) { + if (state.items === oldState.items) return + + if (state.itemKey) { + const container = $el[0] + const newElements = _.map(state.items, (item) => $(item)[0]) + listMorph(container, newElements, { key: state.itemKey }) + return } - }) - .fail(() => $errorMessage.show()) - .always(() => { - $loading.hide() - $loadingButton.hide() - }) -} -if ($element.length === 1) { - $element.on('click', '[data-next-page-button]', (event) => { - event.preventDefault() + $el.html(state.items) + } + }, + '[data-async-listing] [data-next-page-button]': { + render ($el, state) { + if (state.requestError) return $el.hide() + if (!state.nextPagePath) return $el.hide() + if (state.loading) return $el.hide() - const $button = $(event.target) - const path = $button.attr('href') - const $loadingButton = $element.find('[data-loading-button]') + $el.show() + $el.attr('href', state.nextPagePath) + } + }, + '[data-async-listing] [data-loading-button]': { + render ($el, state) { + if (!state.loadingFirstPage && state.loading) return $el.show() - // change url to the next page link before loading the next page - history.pushState({}, null, path) - $button.hide() - $loadingButton.show() + $el.hide() + } + } +} - asyncListing($element, path) - }) +/** + * Create a store combining the given reducer and initial state with the async reducer. + * + * reducer: The reducer that will be merged with the asyncReducer to add async + * loading capabilities to a page. Any state changes in the reducer passed will be + * applied AFTER the asyncReducer. + * + * initialState: The initial state to be merged with the async state. Any state + * values passed here will overwrite the values on asyncInitialState. + * + * itemKey: it will be added to the state as the key for diffing the elements and + * adding or removing with the correct animation. Check list_morph.js for more informantion. + */ +export function createAsyncLoadStore (reducer, initialState, itemKey) { + const state = _.merge(asyncInitialState, initialState) + const store = createStore(reduceReducers(asyncReducer, reducer, state)) - $element.on('click', '[data-error-message]', (event) => { - event.preventDefault() + if (typeof itemKey !== 'undefined') { + store.dispatch({ + type: 'ADD_ITEM_KEY', + itemKey + }) + } - // event.target had a weird behavior here - // it hid the tag but left the red div showing - const $link = $element.find('[data-error-message]') - const $loading = $element.find('[data-loading-message]') - const path = $element.data('async-listing') + connectElements({store, elements}) + firstPageLoad(store) + return store +} - $link.hide() - $loading.show() +function firstPageLoad (store) { + const $element = $('[data-async-listing]') + function loadItems () { + const path = store.getState().nextPagePath + store.dispatch({type: 'START_REQUEST'}) + $.getJSON(path, {type: 'JSON'}) + .done(response => store.dispatch(Object.assign({type: 'ITEMS_FETCHED'}, humps.camelizeKeys(response)))) + .fail(() => store.dispatch({type: 'REQUEST_ERROR'})) + .always(() => store.dispatch({type: 'FINISH_REQUEST'})) + } + loadItems() - asyncListing($element, path) + $element.on('click', '[data-error-message]', (event) => { + event.preventDefault() + loadItems() }) - // force browser to reload when the user goes back a page - $(window).on('popstate', () => location.reload()) + $element.on('click', '[data-next-page-button]', (event) => { + event.preventDefault() + loadItems() + store.dispatch({type: 'NAVIGATE_TO_OLDER'}) + }) +} - asyncListing($element, $element.data('async-listing')) +const $element = $('[data-async-load]') +if ($element.length) { + const store = createStore(asyncReducer) + connectElements({store, elements}) + firstPageLoad(store) } diff --git a/apps/block_scout_web/assets/js/lib/smart_contract/code_highlighting.js b/apps/block_scout_web/assets/js/lib/smart_contract/code_highlighting.js new file mode 100644 index 0000000000..95320a12a6 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/smart_contract/code_highlighting.js @@ -0,0 +1,9 @@ +import $ from 'jquery' +import hljs from 'highlight.js' +import hljsDefineSolidity from 'highlightjs-solidity' + +// only activate highlighting on pages with this selector +if ($('[data-activate-highlight]').length > 0) { + hljsDefineSolidity(hljs) + hljs.initHighlightingOnLoad() +} diff --git a/apps/block_scout_web/assets/js/pages/address.js b/apps/block_scout_web/assets/js/pages/address.js index 8472a2130e..683fc66a57 100644 --- a/apps/block_scout_web/assets/js/pages/address.js +++ b/apps/block_scout_web/assets/js/pages/address.js @@ -12,7 +12,6 @@ import { updateAllCalculatedUsdValues } from '../lib/currency.js' import { loadTokenBalanceDropdown } from '../lib/token_balance_dropdown' const BATCH_THRESHOLD = 10 -const TRANSACTION_VALIDATED_MOVE_DELAY = 1000 export const initialState = { channelDisconnected: false, @@ -24,7 +23,6 @@ export const initialState = { transactionCount: null, validationCount: null, - pendingTransactions: [], transactions: [], internalTransactions: [], internalTransactionsBatch: [], @@ -91,26 +89,6 @@ function baseReducer (state = initialState, action) { }) } } - case 'RECEIVED_NEW_PENDING_TRANSACTION': { - if (state.channelDisconnected || state.beyondPageOne) return state - - if ((state.filter === 'to' && action.msg.toAddressHash !== state.addressHash) || - (state.filter === 'from' && action.msg.fromAddressHash !== state.addressHash)) { - return state - } - - return Object.assign({}, state, { - pendingTransactions: [ - action.msg, - ...state.pendingTransactions - ] - }) - } - case 'REMOVE_PENDING_TRANSACTION': { - return Object.assign({}, state, { - pendingTransactions: state.pendingTransactions.filter((transaction) => action.msg.transactionHash !== transaction.transactionHash) - }) - } case 'RECEIVED_NEW_TRANSACTION': { if (state.channelDisconnected) return state @@ -123,7 +101,6 @@ function baseReducer (state = initialState, action) { } return Object.assign({}, state, { - pendingTransactions: state.pendingTransactions.map((transaction) => action.msg.transactionHash === transaction.transactionHash ? Object.assign({}, action.msg, { validated: true }) : transaction), transactions: [ action.msg, ...state.transactions @@ -184,28 +161,6 @@ const elements = { $el.empty().append(numeral(state.validationCount).format()) } }, - '[data-selector="pending-transactions-list"]': { - load ($el) { - return { - pendingTransactions: $el.children().map((index, el) => ({ - transactionHash: el.dataset.transactionHash, - transactionHtml: el.outerHTML - })).toArray() - } - }, - render ($el, state, oldState) { - if (oldState.pendingTransactions === state.pendingTransactions) return - const container = $el[0] - const newElements = _.map(state.pendingTransactions, ({ transactionHtml }) => $(transactionHtml)[0]) - listMorph(container, newElements, { key: 'dataset.transactionHash' }) - } - }, - '[data-selector="pending-transactions-count"]': { - render ($el, state, oldState) { - if (oldState.pendingTransactions === state.pendingTransactions) return - $el[0].innerHTML = numeral(state.pendingTransactions.filter(({ validated }) => !validated).length).format() - } - }, '[data-selector="empty-transactions-list"]': { render ($el, state) { if (state.transactions.length || state.loadingNextPage || state.pagingError) { @@ -226,16 +181,10 @@ const elements = { }, render ($el, state, oldState) { if (oldState.transactions === state.transactions) return - function updateTransactions () { - const container = $el[0] - const newElements = _.map(state.transactions, ({ transactionHtml }) => $(transactionHtml)[0]) - listMorph(container, newElements, { key: 'dataset.transactionHash' }) - } - if ($('[data-selector="pending-transactions-list"]').is(':visible')) { - setTimeout(updateTransactions, TRANSACTION_VALIDATED_MOVE_DELAY + 400) - } else { - updateTransactions() - } + + const container = $el[0] + const newElements = _.map(state.transactions, ({ transactionHtml }) => $(transactionHtml)[0]) + return listMorph(container, newElements, { key: 'dataset.transactionHash' }) } }, '[data-selector="internal-transactions-list"]': { @@ -306,19 +255,11 @@ if ($addressDetailsPage.length) { type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH', msgs: humps.camelizeKeys(msgs) }))) - addressChannel.on('pending_transaction', (msg) => store.dispatch({ - type: 'RECEIVED_NEW_PENDING_TRANSACTION', - msg: humps.camelizeKeys(msg) - })) addressChannel.on('transaction', (msg) => { store.dispatch({ type: 'RECEIVED_NEW_TRANSACTION', msg: humps.camelizeKeys(msg) }) - setTimeout(() => store.dispatch({ - type: 'REMOVE_PENDING_TRANSACTION', - msg: humps.camelizeKeys(msg) - }), TRANSACTION_VALIDATED_MOVE_DELAY) }) const blocksChannel = socket.channel(`blocks:${addressHash}`, {}) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index b82404124d..f531a1f5d5 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -497,7 +497,7 @@ }, "array-equal": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", "dev": true }, @@ -935,7 +935,7 @@ }, "babel-plugin-istanbul": { "version": "4.1.6", - "resolved": "http://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", "dev": true, "requires": { @@ -965,7 +965,7 @@ }, "babel-plugin-syntax-object-rest-spread": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", "dev": true }, @@ -4737,6 +4737,16 @@ "minimalistic-assert": "^1.0.0" } }, + "highlight.js": { + "version": "9.13.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.13.1.tgz", + "integrity": "sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A==" + }, + "highlightjs-solidity": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/highlightjs-solidity/-/highlightjs-solidity-1.0.6.tgz", + "integrity": "sha512-NzdwI5gX+8H3z/YEXk01dKOY0QuffhNkUZw9umHUCXlzKB+1n2SexTTZpSGAmZYetHT/bccCm+3QqBULtTLmdA==" + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -5901,7 +5911,7 @@ }, "jest-get-type": { "version": "22.4.3", - "resolved": "http://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", "dev": true }, @@ -6105,7 +6115,7 @@ "dependencies": { "callsites": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", "dev": true } @@ -9791,6 +9801,11 @@ } } }, + "reduce-reducers": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/reduce-reducers/-/reduce-reducers-0.4.3.tgz", + "integrity": "sha512-+CNMnI8QhgVMtAt54uQs3kUxC3Sybpa7Y63HR14uGLgI9/QR5ggHvpxwhGGe3wmx5V91YwqQIblN9k5lspAmGw==" + }, "redux": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.0.tgz", @@ -10163,7 +10178,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -11129,7 +11144,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { @@ -11183,7 +11198,7 @@ }, "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, @@ -11731,7 +11746,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index be66f75088..aa13b6c4a4 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -20,6 +20,8 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^5.1.0-4", + "highlight.js": "^9.13.1", + "highlightjs-solidity": "^1.0.6", "bignumber.js": "^7.2.1", "bootstrap": "^4.1.3", "chart.js": "^2.7.2", @@ -34,6 +36,7 @@ "phoenix": "file:../../../deps/phoenix", "phoenix_html": "file:../../../deps/phoenix_html", "popper.js": "^1.14.3", + "reduce-reducers": "^0.4.3", "redux": "^4.0.0", "urijs": "^1.19.1" }, diff --git a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex index 6213b6aaf5..3e09fa7541 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex @@ -8,7 +8,7 @@ defmodule BlockScoutWeb.AddressChannel do alias Explorer.Chain.Hash alias Phoenix.View - intercept(["balance_update", "count", "internal_transaction", "pending_transaction", "transaction"]) + intercept(["balance_update", "count", "internal_transaction", "transaction"]) def join("addresses:" <> _address_hash, _params, socket) do {:ok, %{}, socket} @@ -62,7 +62,6 @@ defmodule BlockScoutWeb.AddressChannel do end def handle_out("transaction", data, socket), do: handle_transaction(data, socket, "transaction") - def handle_out("pending_transaction", data, socket), do: handle_transaction(data, socket, "pending_transaction") def handle_transaction(%{address: address, transaction: transaction}, socket, event) do Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex index 9324ccbf65..1a9bbcef3c 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex @@ -40,8 +40,7 @@
-
<%= text_to_html(@address.smart_contract.contract_source_code, wrapper_tag: :span, insert_brs: false) %>
-            
+
<%= for {line, number} <- contract_lines_with_index(@address.smart_contract.contract_source_code) do %>
<%= line %>
<% end %>
@@ -53,7 +52,7 @@
-
<%= format_smart_contract_abi(@address.smart_contract.abi) %>
+            
<%= format_smart_contract_abi(@address.smart_contract.abi) %>
             
@@ -67,7 +66,7 @@
-
<%= @address.contract_code %>
+
<%= @address.contract_code %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex index 2883e278f2..79167f5c9b 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex @@ -7,7 +7,7 @@ <%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %> -
+
<%= gettext "More internal transactions have come in" %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex index ba10748ee1..40915826b1 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex @@ -7,7 +7,7 @@ <%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
-
+

<%= gettext "Tokens" %> / <%= token_name(@token) %>

diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/index.html.eex index 2e34a41168..5ff4141d5a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/index.html.eex @@ -16,7 +16,7 @@
-
+

<%= gettext "Token Holders" %>

-
+

<%= gettext "Token Transfers" %>