From c3e3828923f0309facaaf1b1cc83babe1f0ad1ec Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Tue, 13 Jul 2021 18:09:24 +0300 Subject: [PATCH] Replace awesomplete with autocomplete.js --- .github/workflows/config.yml | 24 +- CHANGELOG.md | 1 + apps/block_scout_web/assets/css/_layout.scss | 2 + .../assets/css/components/_navbar.scss | 26 - .../assets/css/components/_search.scss | 39 + .../assets/css/theme/_dark-theme.scss | 50 +- .../custom_contracts/_circles-theme.scss | 23 - .../custom_contracts/_dark-forest-theme.scss | 23 - .../assets/js/lib/autocomplete.js | 129 + .../assets/js/lib/awesomplete-util.js | 640 -- .../assets/js/lib/awesomplete.js | 2 - .../block_scout_web/assets/js/pages/layout.js | 36 +- apps/block_scout_web/assets/package-lock.json | 5825 +++++++++-------- apps/block_scout_web/assets/package.json | 26 +- apps/block_scout_web/assets/webpack.config.js | 9 +- apps/block_scout_web/lib/block_scout_web.ex | 2 - .../lib/block_scout_web/csp_header.ex | 2 +- .../templates/layout/_search.html.eex | 89 +- .../templates/layout/_topnav.html.eex | 9 +- apps/block_scout_web/mix.exs | 1 - apps/block_scout_web/priv/gettext/default.pot | 52 +- .../priv/gettext/en/LC_MESSAGES/default.po | 52 +- apps/explorer/lib/explorer/chain.ex | 9 +- mix.lock | 1 - 24 files changed, 3598 insertions(+), 3474 deletions(-) create mode 100644 apps/block_scout_web/assets/js/lib/autocomplete.js delete mode 100644 apps/block_scout_web/assets/js/lib/awesomplete-util.js delete mode 100644 apps/block_scout_web/assets/js/lib/awesomplete.js diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index f2720c56a7..513f7acf4e 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/setup-elixir@v1 with: otp-version: '23.3.4.1' - elixir-version: '1.11.3' + elixir-version: '1.11.4' - run: mix local.hex --force - run: mix local.rebar --force - name: "ELIXIR_VERSION.lock" @@ -47,7 +47,7 @@ jobs: - uses: actions/setup-elixir@v1 with: otp-version: '23.3.4.1' - elixir-version: '1.11.3' + elixir-version: '1.11.4' - run: mix local.hex --force - run: mix local.rebar --force - run: mix deps.get @@ -62,7 +62,7 @@ jobs: - uses: actions/setup-elixir@v1 with: otp-version: '23.3.4.1' - elixir-version: '1.11.3' + elixir-version: '1.11.4' - run: mix local.hex --force - run: mix local.rebar --force - run: mix deps.get @@ -77,7 +77,7 @@ jobs: - uses: actions/setup-elixir@v1 with: otp-version: '23.3.4.1' - elixir-version: '1.11.3' + elixir-version: '1.11.4' - run: mix local.hex --force - run: mix local.rebar --force - run: mix deps.get @@ -104,7 +104,7 @@ jobs: - uses: actions/setup-elixir@v1 with: otp-version: '23.3.4.1' - elixir-version: '1.11.3' + elixir-version: '1.11.4' - run: mix local.hex --force - run: mix local.rebar --force - run: mix deps.get @@ -122,7 +122,7 @@ jobs: - uses: actions/setup-elixir@v1 with: otp-version: '23.3.4.1' - elixir-version: '1.11.3' + elixir-version: '1.11.4' - run: mix local.hex --force - run: mix local.rebar --force - run: mix deps.get @@ -143,7 +143,7 @@ jobs: - uses: actions/setup-elixir@v1 with: otp-version: '23.3.4.1' - elixir-version: '1.11.3' + elixir-version: '1.11.4' - run: mix local.hex --force - run: mix local.rebar --force - run: mix deps.get @@ -168,7 +168,7 @@ jobs: - uses: actions/setup-elixir@v1 with: otp-version: '23.3.4.1' - elixir-version: '1.11.3' + elixir-version: '1.11.4' - run: mix local.hex --force - run: mix local.rebar --force - run: mix deps.get @@ -212,7 +212,7 @@ jobs: - uses: actions/setup-elixir@v1 with: otp-version: '23.3.4.1' - elixir-version: '1.11.3' + elixir-version: '1.11.4' - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - run: echo 'export PATH=~/.cargo/bin/:$PATH' >> $GITHUB_ENV - run: mix local.hex --force @@ -268,7 +268,7 @@ jobs: - uses: actions/setup-elixir@v1 with: otp-version: '23.3.4.1' - elixir-version: '1.11.3' + elixir-version: '1.11.4' - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - run: echo 'export PATH=~/.cargo/bin/:$PATH' >> $GITHUB_ENV - run: mix local.hex --force @@ -326,7 +326,7 @@ jobs: - uses: actions/setup-elixir@v1 with: otp-version: '23.3.4.1' - elixir-version: '1.11.3' + elixir-version: '1.11.4' - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - run: echo 'export PATH=~/.cargo/bin/:$PATH' >> $GITHUB_ENV - run: mix local.hex --force @@ -384,7 +384,7 @@ jobs: - uses: actions/setup-elixir@v1 with: otp-version: '23.3.4.1' - elixir-version: '1.11.3' + elixir-version: '1.11.4' - run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - run: echo 'export PATH=~/.cargo/bin/:$PATH' >> $GITHUB_ENV - run: mix local.hex --force diff --git a/CHANGELOG.md b/CHANGELOG.md index 5307516d9a..51a5066706 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Fixes ### Chore +- [#4382](https://github.com/blockscout/blockscout/pull/4382) - Replace awesomplete with autocomplete.js - [#4371] - (https://github.com/blockscout/blockscout/pull/4371) - Place search outside of burger in mobile view - [#4355](https://github.com/blockscout/blockscout/pull/4355) - Do not redirect to 404 page with empty string in the search field diff --git a/apps/block_scout_web/assets/css/_layout.scss b/apps/block_scout_web/assets/css/_layout.scss index da36762bee..2acd2a6a9c 100644 --- a/apps/block_scout_web/assets/css/_layout.scss +++ b/apps/block_scout_web/assets/css/_layout.scss @@ -1,3 +1,5 @@ +@import '~@tarekraafat/autocomplete.js/dist/css/autoComplete.01.css'; + .layout-container { display: flex; flex-direction: column; diff --git a/apps/block_scout_web/assets/css/components/_navbar.scss b/apps/block_scout_web/assets/css/components/_navbar.scss index d1a4eaaf54..08efd5778d 100644 --- a/apps/block_scout_web/assets/css/components/_navbar.scss +++ b/apps/block_scout_web/assets/css/components/_navbar.scss @@ -209,22 +209,6 @@ $navbar-logo-width: auto !default; } } -.input-group { - - .awesomplete > ul { - overflow-y: auto !important; - max-height: 300px !important; - } - - .awesomplete mark { - background: #feff54 !important; - } - - .awesomplete li:hover mark { - background: #feff54 !important; - } -} - .navbar-collapse.collapsing, .navbar-collapse.collapse.show { display: flex; @@ -242,12 +226,6 @@ $navbar-logo-width: auto !default; } .input-group { width: 100%; - - .awesomplete { - @include media-breakpoint-down(lg) { - width: 100%; - } - } } .navbar-nav { white-space: nowrap; @@ -313,8 +291,4 @@ $navbar-logo-width: auto !default; .visually-hidden { display: block; -} - -.focused-field { - border: 1px solid $secondary !important; } \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/components/_search.scss b/apps/block_scout_web/assets/css/components/_search.scss index 34f6349ab1..acefa12253 100644 --- a/apps/block_scout_web/assets/css/components/_search.scss +++ b/apps/block_scout_web/assets/css/components/_search.scss @@ -18,4 +18,43 @@ @include media-breakpoint-down(lg) { height: 33px !important; } + &.focused-field { + border: 1px solid $secondary !important; + } +} + +.autoComplete_wrapper { + width: 100% !important; +} + +.main-search-autocomplete { + width: 100% !important; + padding: 0 !important; + border: none !important; + border-radius: 0 !important; + background-color: #f5f6fa !important; + @include media-breakpoint-down(lg) { + height: auto !important; + } } + +.main-search-autocomplete, .main-search-autocomplete-mobile { + font-size: 14px !important; + color: #828ba0 !important; +} + +.autoComplete_highlight { + background-color: #feff54 !important; + color: #212121 !important; +} + +ul[id^='autoComplete_list_'] { + margin-left: -39px !important; + border-radius: 0px !important; + padding: 5px !important; +} + +li[id^='autoComplete_result_'] { + font-size: 12px !important; + border-radius: 0 !important; +} \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/theme/_dark-theme.scss b/apps/block_scout_web/assets/css/theme/_dark-theme.scss index 7f04647c85..7e9a29d1cc 100644 --- a/apps/block_scout_web/assets/css/theme/_dark-theme.scss +++ b/apps/block_scout_web/assets/css/theme/_dark-theme.scss @@ -766,29 +766,6 @@ $dark-stakes-banned-background: #3e314c; } } - .awesomplete { - & > ul { - background: $dark-light-bg; - &:before { - background: $dark-light-bg; - } - li { - &:hover { - background-color: $dark-primary; - color: #fff; - mark { - background: darken($dark-primary, 10); - color: #fff; - } - } - } - } - mark { - background: $dark-primary; - color: #fff; - } - } - // Decoded data .table.thead-light.table-bordered { color: #fff !important; @@ -1091,15 +1068,6 @@ $dark-stakes-banned-background: #3e314c; background-image: none; } - .input-group { - .awesomplete mark { - background: $yellow !important; - } - - .awesomplete li:hover mark { - background: $yellow !important; - } - } .contract-plus-btn { color: $dark-primary; } @@ -1111,6 +1079,24 @@ $dark-stakes-banned-background: #3e314c; fill: #fff; } } + .main-search-autocomplete { + background-color: $dark-bg !important; + color: #fff !important; + } + ul[id^='autoComplete_list_'] { + background-color: $dark-light-bg !important; + } + li[id^='autoComplete_result_'] { + background-color: $dark-light-bg !important; + color: #fff !important; + + &:hover { + background-color: $dark-primary !important; + } + &[aria-selected="true"] { + background-color: $dark-primary !important; + } + } } .navbar-dark .navbar-toggler { diff --git a/apps/block_scout_web/assets/css/theme/custom_contracts/_circles-theme.scss b/apps/block_scout_web/assets/css/theme/custom_contracts/_circles-theme.scss index 973c7faf97..db0f626b16 100644 --- a/apps/block_scout_web/assets/css/theme/custom_contracts/_circles-theme.scss +++ b/apps/block_scout_web/assets/css/theme/custom_contracts/_circles-theme.scss @@ -376,29 +376,6 @@ $c-dark-text-color: #8a8dba; } } - .awesomplete { - & > ul { - background: $c-primary; - &:before { - background: $c-primary; - } - li { - &:hover { - background-color: $c-primary; - color: #fff; - mark { - background: darken($c-primary, 10); - color: #fff; - } - } - } - } - mark { - background: $c-primary; - color: #fff; - } - } - #qrModal { .modal-content { diff --git a/apps/block_scout_web/assets/css/theme/custom_contracts/_dark-forest-theme.scss b/apps/block_scout_web/assets/css/theme/custom_contracts/_dark-forest-theme.scss index 3294353c51..837dd49543 100644 --- a/apps/block_scout_web/assets/css/theme/custom_contracts/_dark-forest-theme.scss +++ b/apps/block_scout_web/assets/css/theme/custom_contracts/_dark-forest-theme.scss @@ -714,29 +714,6 @@ $dark-primary-alternate: $dark-primary; } } - .awesomplete { - & > ul { - background: $dark-light-bg; - &:before { - background: $dark-light-bg; - } - li { - &:hover { - background-color: $dark-primary; - color: #fff; - mark { - background: darken($dark-primary, 10); - color: #fff; - } - } - } - } - mark { - background: $dark-primary; - color: #fff; - } - } - // Decoded data .table.thead-light.table-bordered { color: #fff !important; diff --git a/apps/block_scout_web/assets/js/lib/autocomplete.js b/apps/block_scout_web/assets/js/lib/autocomplete.js new file mode 100644 index 0000000000..67ea2aaf19 --- /dev/null +++ b/apps/block_scout_web/assets/js/lib/autocomplete.js @@ -0,0 +1,129 @@ +import AutoComplete from '@tarekraafat/autocomplete.js/dist/autoComplete.js' + +const placeHolder = 'Search by address, token symbol, name, transaction hash, or block number' +const dataSrc = async (query, id) => { + try { + // Loading placeholder text + const searchInput = document + .getElementById(id) + + searchInput.setAttribute('placeholder', 'Loading...') + + // Fetch External Data Source + const source = await fetch( + `/token-autocomplete?q=${query}` + ) + const data = await source.json() + // Post Loading placeholder text + + searchInput.setAttribute('placeholder', placeHolder) + // Returns Fetched data + return data + } catch (error) { + return error + } +} +const resultsListElement = (list, data) => { + const info = document.createElement('p') + const adv = ` +
+ Sponsored: - +
` + info.innerHTML = adv + if (data.results.length > 0) { + info.innerHTML += `Displaying ${data.results.length} results` + } else if (data.query !== '###') { + info.innerHTML += `Found ${data.matches.length} matching results for "${data.query}"` + } + + list.prepend(info) +} +const searchEngine = (query, record) => { + if (record.name.toLowerCase().includes(query.toLowerCase()) || + record.symbol.toLowerCase().includes(query.toLowerCase()) || + record.contract_address_hash.toLowerCase().includes(query.toLowerCase())) { + var searchResult = `${record.contract_address_hash}
${record.name}` + if (record.symbol) { + searchResult = searchResult + ` (${record.symbol})` + } + if (record.holder_count) { + searchResult = searchResult + ` ${record.holder_count} holder(s)` + } + var re = new RegExp(query, 'ig') + searchResult = searchResult.replace(re, '$&') + return searchResult + } +} +const resultItemElement = (item, data) => { + // Modify Results Item Style + item.style = 'display: flex; justify-content: space-between;' + // Modify Results Item Content + item.innerHTML = ` + + ${data.match} + ` +} +const config = (id) => { + return { + selector: `#${id}`, + data: { + src: (query) => dataSrc(query, id), + cache: false + }, + placeHolder: placeHolder, + searchEngine: (query, record) => searchEngine(query, record), + threshold: 2, + resultsList: { + element: (list, data) => resultsListElement(list, data), + noResults: true, + maxResults: 100, + tabSelect: true + }, + resultItem: { + element: (item, data) => resultItemElement(item, data), + highlight: 'autoComplete_highlight' + }, + events: { + input: { + focus: () => { + if (autoCompleteJS.input.value.length) autoCompleteJS.start() + } + } + } + } +} +const autoCompleteJS = new AutoComplete(config('main-search-autocomplete')) +// eslint-disable-next-line +const _autoCompleteJSMobile = new AutoComplete(config('main-search-autocomplete-mobile')) + +const selection = (event) => { + const selectionValue = event.detail.selection.value + + if (selectionValue.symbol) { + window.location = `/tokens/${selectionValue.contract_address_hash}` + } else { + window.location = `/address/${selectionValue.contract_address_hash}` + } +} + +document.querySelector('#main-search-autocomplete').addEventListener('selection', function (event) { + selection(event) +}) +document.querySelector('#main-search-autocomplete-mobile').addEventListener('selection', function (event) { + selection(event) +}) + +const openOnFocus = (event) => { + const query = event.target.value + if (query) { + autoCompleteJS.start(query) + } +} + +document.querySelector('#main-search-autocomplete').addEventListener('focus', function (event) { + openOnFocus(event) +}) + +document.querySelector('#main-search-autocomplete-mobile').addEventListener('focus', function (event) { + openOnFocus(event) +}) diff --git a/apps/block_scout_web/assets/js/lib/awesomplete-util.js b/apps/block_scout_web/assets/js/lib/awesomplete-util.js deleted file mode 100644 index bcfb7edc88..0000000000 --- a/apps/block_scout_web/assets/js/lib/awesomplete-util.js +++ /dev/null @@ -1,640 +0,0 @@ -/* eslint-env browser */ -/* global Awesomplete */ -/* exported AwesompleteUtil */ - -/* - * Library endorsing Lea Verou's Awesomplete widget, providing: - * - dynamic remote data loading - * - labels with HTML markup - * - events and styling for exact matches - * - events and styling for mismatches - * - select item when TAB key is used - * - * (c) Nico Hoogervorst - * License: MIT - * - */ -window.AwesompleteUtil = (function () { - // - // event names and css classes - // - var _AWE = 'awesomplete-' - var _AWE_LOAD = _AWE + 'loadcomplete' - var _AWE_CLOSE = _AWE + 'close' - var _AWE_MATCH = _AWE + 'match' - var _AWE_PREPOP = _AWE + 'prepop' - var _AWE_SELECT = _AWE + 'select' - var _CLS_FOUND = 'awe-found' - var _CLS_NOT_FOUND = 'awe-not-found' - var $ = Awesomplete.$ /* shortcut for document.querySelector */ - - // - // private functions - // - - // Some parts are shamelessly copied from Awesomplete.js like the logic inside this _suggestion function. - // Returns an object with label and value properties. Data parameter is plain text or Object/Array with label and value. - function _suggestion (data) { - var lv = Array.isArray(data) - ? { label: data[0], value: data[1] } - : typeof data === 'object' && 'label' in data && 'value' in data ? data : { label: data, value: data } - return { label: lv.label || lv.value, value: lv.value } - } - - // Helper to send events with detail property. - function _fire (target, name, detail) { - // $.fire uses deprecated methods but other methods don't work in IE11. - return $.fire(target, name, { detail: detail }) - } - - // Look if there is an exact match or a mismatch, set awe-found, awe-not-found css class and send match events. - function _matchValue (awe, prepop) { - var input = awe.input /* the input field */ - var classList = input.classList - var utilprops = awe.utilprops /* extra properties piggybacked on Awesomplete object */ - var selected = utilprops.selected /* the exact selected Suggestion with label and value */ - var val = utilprops.convertInput.call(awe, input.value) /* trimmed lowercased value */ - var opened = awe.opened /* is the suggestion list opened? */ - var result = [] /* matches with value */ - var list = awe._list /* current list of suggestions */ - var suggestion, fake, rec, j /* function scoped variables */ - utilprops.prepop = false /* after the first call it's not a prepopulation phase anymore */ - if (list) { /* if there is a suggestion list */ - for (j = 0; j < list.length; j++) { /* loop all suggestions */ - rec = list[j] - suggestion = _suggestion(awe.data(rec, val)) /* call data convert function */ - // with maxItems = 0 cannot look if suggestion list is opened to determine if there are still matches, - // instead call the filter method to see if there are still some options. - if (awe.maxItems === 0) { - // Awesomplete.FILTER_CONTAINS and Awesomplete.FILTER_STARTSWITH use the toString method. - suggestion.toString = function () { return '' + this.label } - if (awe.filter(suggestion, val)) { - // filter returns true, so there is at least one partial match. - opened = true - } - } - // Don't want to change the real input field, emulate a fake one. - fake = { input: { value: '' } } - // Determine how this suggestion would look like if it is replaced in the input field, - // it is an exact match if somebody types exactly that. - // Use the fake input here. fake.input.value will contain the result of the replace function. - awe.replace.call(fake, suggestion) - // Trim and lowercase also the fake input and compare that with the currently typed-in value. - if (utilprops.convertInput.call(awe, fake.input.value) === val) { - // This is an exact match. However there might more suggestions with the same value. - // If the user selected a suggestion from the list, check if this one matches, assuming that - // value + label is unique (if not it will be difficult for the user to make an informed decision). - if (selected && selected.value === suggestion.value && selected.label === suggestion.label) { - // this surely is the selected one - result = [rec] - break - } - // add the matching record to the result set. - result.push(rec) - } // end if - } // end loop - - // if the result differs from the previous result - if (utilprops.prevSelected !== result) { - // if there is an exact match - if (result.length > 0) { - // if prepopulation phase (initial/autofill value); not triggered by user input - if (prepop) { - _fire(input, _AWE_PREPOP, result) - } else if (utilprops.changed) { /* if input is changed */ - utilprops.prevSelected = result /* new result */ - classList.remove(_CLS_NOT_FOUND) /* remove class */ - classList.add(_CLS_FOUND) /* add css class */ - _fire(input, _AWE_MATCH, result) /* fire event */ - } - } else if (prepop) { /* no exact match, if in prepopulation phase */ - _fire(input, _AWE_PREPOP, []) - } else if (utilprops.changed) { /* no exact match, if input is changed */ - utilprops.prevSelected = [] - classList.remove(_CLS_FOUND) - // Mark as not-found if there are no suggestions anymore or if another field is now active - if (!opened || (input !== document.activeElement)) { - if (val.length > 0) { - classList.add(_CLS_NOT_FOUND) - _fire(input, _AWE_MATCH, []) - } - } else { - classList.remove(_CLS_NOT_FOUND) - } - } - } - } - } - - // Listen to certain events of THIS awesomplete object to trigger input validation. - function _match (ev) { - var awe = this - if ((ev.type === _AWE_CLOSE || ev.type === _AWE_LOAD || ev.type === 'blur') && ev.target === awe.input) { - _matchValue(awe, awe.utilprops.prepop && ev.type === _AWE_LOAD) - } - } - - // Select currently selected item if tab or shift-tab key is used. - function _onKeydown (ev) { - var awe = this - if (ev.target === awe.input && ev.keyCode === 9) { // TAB key - awe.select() // take current selected item - } - } - - // Handle selection event. State changes when an item is selected. - function _select (ev) { - var awe = this - awe.utilprops.changed = true // yes, user made a change - awe.utilprops.selected = ev.text // Suggestion object - const address = ev.text.split(/

/)[0] - window.open(`/search?q=${address}`, '_self') - } - - // check if the object is empty {} object - function _isEmpty (val) { - return Object.keys(val).length === 0 && val.constructor === Object - } - - // Need an updated suggestion list if: - // - There is no result yet, or there is a result but not for the characters we entered - // - or there might be more specific results because the limit was reached. - function _ifNeedListUpdate (awe, val, queryVal) { - var utilprops = awe.utilprops - return (!utilprops.listQuery || - (!utilprops.loadall && /* with loadall, if there is a result, there is no need for new lists */ - val.lastIndexOf(queryVal, 0) === 0 && - (val.lastIndexOf(utilprops.listQuery, 0) !== 0 || - (typeof utilprops.limit === 'number' && awe._list.length >= utilprops.limit)))) - } - - // Set a new suggestion list. Trigger loadcomplete event. - function _loadComplete (awe, list, queryVal) { - awe.list = list - awe.utilprops.listQuery = queryVal - _fire(awe.input, _AWE_LOAD, queryVal) - } - - // Handle ajax response. Expects HTTP OK (200) response with JSON object with suggestion(s) (array). - function _onLoad () { - var t = this - var awe = t.awe - var xhr = t.xhr - var queryVal = t.queryVal - var val = awe.utilprops.val - var data - var prop - if (xhr.status === 200) { - data = JSON.parse(xhr.responseText) - if (awe.utilprops.convertResponse) data = awe.utilprops.convertResponse(data) - if (!Array.isArray(data)) { - if (awe.utilprops.limit === 0 || awe.utilprops.limit === 1) { - // if there is max 1 result expected, the array is not needed. - // Fur further processing, take the whole result and put it as one element in an array. - data = _isEmpty(data) ? [] : [data] - } else { - // search for the first property that contains an array - for (prop in data) { - if (Array.isArray(data[prop])) { - data = data[prop] - break - } - } - } - } - // can only handle arrays - if (Array.isArray(data)) { - // are we still interested in this response? - if (_ifNeedListUpdate(awe, val, queryVal)) { - // accept the new suggestion list - _loadComplete(awe, data, queryVal || awe.utilprops.loadall) - } - } - } - } - - // Perform suggestion list lookup for the current value and validate. Use ajax when there is an url specified. - function _lookup (awe, val) { - var xhr - if (awe.utilprops.url) { - // are we still interested in this response? - if (_ifNeedListUpdate(awe, val, val)) { - xhr = new XMLHttpRequest() - awe.utilprops.ajax.call(awe, - awe.utilprops.url, - awe.utilprops.urlEnd, - awe.utilprops.loadall ? '' : val, - _onLoad.bind({ awe: awe, xhr: xhr, queryVal: val }), - xhr - ) - } else { - _matchValue(awe, awe.utilprops.prepop) - } - } else { - _matchValue(awe, awe.utilprops.prepop) - } - } - - // Restart autocomplete search: clear css classes and send match-event with empty list. - function _restart (awe) { - var elem = awe.input - var classList = elem.classList - // IE11 only handles the first parameter of the remove method. - classList.remove(_CLS_NOT_FOUND) - classList.remove(_CLS_FOUND) - _fire(elem, _AWE_MATCH, []) - } - - // handle new input value - function _update (awe, val, prepop) { - // prepop parameter is optional. Default value is false. - awe.utilprops.prepop = prepop || false - // if value changed - if (awe.utilprops.val !== val) { - // new value, clear previous selection - awe.utilprops.selected = null - // yes, user made a change - awe.utilprops.changed = true - awe.utilprops.val = val - // value is empty or smaller than minChars - if (val.length < awe.minChars || val.length === 0) { - // restart autocomplete search - _restart(awe) - } - if (val.length >= awe.minChars) { - // lookup suggestions and validate input - _lookup(awe, val) - } - } - return awe - } - - // handle input changed event for THIS awesomplete object - function _onInput (e) { - var awe = this - var val - if (e.target === awe.input) { - // lowercase and trim input value - val = awe.utilprops.convertInput.call(awe, awe.input.value) - _update(awe, val) - } - } - - // item function (as specified in Awesomplete) which just creates the 'li' HTML tag. - function _item (html /* , input */) { - return $.create('li', { - innerHTML: html, - 'aria-selected': 'false' - }) - } - - // Escape HTML characters in text. - function _htmlEscape (text) { - return text.replace('&', '&').replace('<', '<').replace('>', '>') - } - - // Function to copy a field from the selected autocomplete item to another DOM element. - function _copyFun (e) { - var t = this - var sourceId = t.sourceId - var dataField = t.dataField - var targetId = t.targetId - var elem - var val - if (e.target === $(sourceId)) { - if (typeof targetId === 'function') { - targetId(e, dataField) - } else { - // lookup target element if it isn't resolved yet - elem = $(targetId) - // don't override target inputs if user is currently editing it. - if (elem && elem !== document.activeElement) { - // event must contain 1 item from suggestion list - val = Array.isArray(e.detail) && e.detail.length === 1 ? e.detail[0] : null - // if a datafield is specified, take that value - val = (dataField && val ? val[dataField] : val) || '' - // if it is an input control - if (typeof elem.value !== 'undefined') { - // set new value - elem.value = val - // not really sure if it is an input control, check if it has a classList - if (elem.classList && elem.classList.remove) { - // it might be another awesomplete control, if so the input is not wrong anymore because it's changed now - elem.classList.remove(_CLS_NOT_FOUND) - } - } else if (typeof elem.src !== 'undefined') { /* is it an image tag? */ - elem.src = val - } else { - // use innerHTML to set the new value, because value might intentionally contain HTML markup - elem.innerHTML = val - } - } - } - } - } - - // click function for the combobox button - function _clickFun (e) { - var t = this - var awe - var minChars - if (e.target === $(t.btnId)) { - e.preventDefault() - awe = t.awe - // toggle open/close - if (awe.ul.childNodes.length === 0 || awe.ul.hasAttribute('hidden')) { - minChars = awe.minChars - // ignore that the input value is empty - awe.minChars = 0 - // show the suggestion list - awe.evaluate() - awe.minChars = minChars - } else { - awe.close() - } - } - } - - // Return text with mark tags arround matching input. Don't replace inside tags. - // When startsWith is true, mark only the matching begin text. - function _mark (text, input, startsWith) { - var searchText = $.regExpEscape(_htmlEscape(input).trim()) - var regExp = searchText.length <= 0 ? null : startsWith ? RegExp('^' + searchText, 'i') : RegExp('(?!<[^>]+?>)' + searchText + '(?![^<]*?>)', 'gi') - return text.replace(regExp, '$&') - } - - // Recursive jsonFlatten function - function _jsonFlatten (result, cur, prop, level, opts) { - var root = opts.root /* filter resulting json tree on root property (optional) */ - var value = opts.value /* search for this property and copy it's value to a new 'value' property - (optional, do not specify it if the json array contains plain strings) */ - var label = opts.label || opts.value /* search this property and copy it's value to a new 'label' property. - If there is a 'opts.value' field but no 'opts.label', assume label is the same. */ - var isEmpty = true - var arrayResult = [] - var j - // at top level, look if there is a property which starts with root (if specified) - if (level === 0 && root && prop && (prop + '.').lastIndexOf(root + '.', 0) !== 0 && (root + '.').lastIndexOf(prop + '.', 0) !== 0) { - return result - } - // handle current part of the json tree - if (Object(cur) !== cur) { - if (prop) { - result[prop] = cur - } else { - result = cur - } - } else if (Array.isArray(cur)) { - for (j = 0; j < cur.length; j++) { - arrayResult.push(_jsonFlatten({}, cur[j], '', level + 1, opts)) - } - if (prop) { - result[prop] = arrayResult - } else { - result = arrayResult - } - } else { - for (j in cur) { - isEmpty = false - _jsonFlatten(result, cur[j], prop ? prop + '.' + j : j, level, opts) - } - if (isEmpty && prop) result[prop] = {} - } - // for arrays at top and subtop level - if (level < 2 && prop) { - // if a 'value' is specified and found a mathing property, create extra 'value' property. - if (value && (prop + '.').lastIndexOf(value + '.', 0) === 0) { result.value = result[prop] } - // if a 'label' is specified and found a mathing property, create extra 'label' property. - if (label && (prop + '.').lastIndexOf(label + '.', 0) === 0) { result.label = result[prop] } - } - if (level === 0) { - // Make sure that both value and label properties exist, even if they are nil. - // This is handy with limit 0 or 1 when the result doesn't have to contain an array. - if (value && !('value' in result)) { result.value = null } - if (label && !('label' in result)) { result.label = null } - } - return result - } - - // Stop AwesompleteUtil; detach event handlers from the Awesomplete object. - function _detach () { - var t = this - var elem = t.awe.input - var boundMatch = t.boundMatch - var boundOnInput = t.boundOnInput - var boundOnKeydown = t.boundOnKeydown - var boundSelect = t.boundSelect - - elem.removeEventListener(_AWE_SELECT, boundSelect) - elem.removeEventListener(_AWE_LOAD, boundMatch) - elem.removeEventListener(_AWE_CLOSE, boundMatch) - elem.removeEventListener('blur', boundMatch) - elem.removeEventListener('input', boundOnInput) - elem.removeEventListener('keydown', boundOnKeydown) - } - - // - // public methods - // - - return { - - // ajax call for url + val + urlEnd. fn is the callback function. xhr parameter is optional. - ajax: function (url, urlEnd, val, fn, xhr) { - xhr = xhr || new XMLHttpRequest() - xhr.open('GET', url + encodeURIComponent(val) + (urlEnd || '')) - xhr.onload = fn - xhr.send() - return xhr - }, - - // Convert input before comparing it with suggestion. lowercase and trim the text - convertInput: function (text) { - return typeof text === 'string' ? text.trim().toLowerCase() : '' - }, - - // item function as defined in Awesomplete. - // item(html, input). input is optional and ignored in this implementation - item: _item, - - // Set a new suggestion list. Trigger loadcomplete event. - // load(awesomplete, list, queryVal) - load: _loadComplete, - - // Return text with mark tags arround matching input. Don't replace inside tags. - // When startsWith is true, mark only the matching begin text. - // mark(text, input, startsWith) - mark: _mark, - - // highlight items: Marks input in the first line, not in the optional description - itemContains: function (text, input) { - var arr - if (input.trim().length > 0) { - arr = ('' + text).split(/

/) - arr[0] = _mark(arr[0], input) - text = arr.join('

') - } - return _item(text, input) - }, - - // highlight items: mark all occurrences of the input text - itemMarkAll: function (text, input) { - return _item(input.trim() === '' ? '' + text : _mark('' + text, input), input) - }, - - // highlight items: mark input in the begin text - itemStartsWith: function (text, input) { - return _item(input.trim() === '' ? '' + text : _mark('' + text, input, true), input) - }, - - // create Awesomplete object for input control elemId. opts are passed unchanged to Awesomplete. - create: function (elemId, utilOpts, opts) { - opts.item = opts.item || this.itemContains /* by default uses itemContains, can be overriden */ - var awe = new Awesomplete(elemId, opts) - awe.utilprops = utilOpts || {} - // loadall is true if there is no url (there is a static data-list) - if (!awe.utilprops.url && typeof awe.utilprops.loadall === 'undefined') { - awe.utilprops.loadall = true - } - awe.utilprops.ajax = awe.utilprops.ajax || this.ajax /* default ajax function can be overriden */ - awe.utilprops.convertInput = awe.utilprops.convertInput || this.convertInput /* the same applies for convertInput */ - return awe - }, - - // attach Awesomplete object to event listeners - attach: function (awe) { - var elem = awe.input - var boundMatch = _match.bind(awe) - var boundOnKeydown = _onKeydown.bind(awe) - var boundOnInput = _onInput.bind(awe) - var boundSelect = _select.bind(awe) - var boundDetach = _detach.bind({ - awe: awe, - boundMatch: boundMatch, - boundOnInput: boundOnInput, - boundOnKeydown: boundOnKeydown, - boundSelect: boundSelect - }) - var events = { - keydown: boundOnKeydown, - input: boundOnInput - } - events.blur = events[_AWE_CLOSE] = events[_AWE_LOAD] = boundMatch - events[_AWE_SELECT] = boundSelect - $.bind(elem, events) - - awe.utilprops.detach = boundDetach - // Perform ajax call if prepop is true and there is an initial input value, or when all values must be loaded (loadall) - if (awe.utilprops.prepop && (awe.utilprops.loadall || elem.value.length > 0)) { - awe.utilprops.val = awe.utilprops.convertInput.call(awe, elem.value) - _lookup(awe, awe.utilprops.val) - } - return awe - }, - - // update input value via javascript. Use prepop=true when this is an initial/prepopulation value. - update: function (awe, value, prepop) { - awe.input.value = value - return _update(awe, value, prepop) - }, - - // create and attach Awesomplete object for input control elemId. opts are passed unchanged to Awesomplete. - start: function (elemId, utilOpts, opts) { - return this.attach(this.create(elemId, utilOpts, opts)) - }, - - // Stop AwesompleteUtil; detach event handlers from the Awesomplete object. - detach: function (awe) { - if (awe.utilprops.detach) { - awe.utilprops.detach() - delete awe.utilprops.detach - } - return awe - }, - - // Create function to copy a field from the selected autocomplete item to another DOM element. - // dataField can be null. - createCopyFun: function (sourceId, dataField, targetId) { - return _copyFun.bind({ sourceId: sourceId, dataField: dataField, targetId: $(targetId) || targetId }) - }, - - // attach copy function to event listeners. prepop is optional and by default true. - // if true the copy function will also listen to awesomplete-prepop events. - // The optional listenEl is the element that listens, defaults to document.body. - attachCopyFun: function (fun, prepop, listenEl) { - // prepop parameter defaults to true - prepop = typeof prepop === 'boolean' ? prepop : true - listenEl = listenEl || document.body - listenEl.addEventListener(_AWE_MATCH, fun) - if (prepop) listenEl.addEventListener(_AWE_PREPOP, fun) - return fun - }, - - // Create and attach copy function. - startCopy: function (sourceId, dataField, targetId, prepop) { - var sourceEl = $(sourceId) - return this.attachCopyFun(this.createCopyFun(sourceEl || sourceId, dataField, targetId), prepop, sourceEl) - }, - - // Stop copy function. Detach it from event listeners. - // The optional listenEl must be the same element that was used during startCopy/attachCopyFun; - // in general: Awesomplete.$(sourceId). listenEl defaults to document.body. - detachCopyFun: function (fun, listenEl) { - listenEl = listenEl || document.body - listenEl.removeEventListener(_AWE_PREPOP, fun) - listenEl.removeEventListener(_AWE_MATCH, fun) - return fun - }, - - // Create function for combobox button (btnId) to toggle dropdown list. - createClickFun: function (btnId, awe) { - return _clickFun.bind({ btnId: btnId, awe: awe }) - }, - - // Attach click function for combobox to click event. - // The optional listenEl is the element that listens, defaults to document.body. - attachClickFun: function (fun, listenEl) { - listenEl = listenEl || document.body - listenEl.addEventListener('click', fun) - return fun - }, - - // Create and attach click function for combobox button. Toggles open/close of suggestion list. - startClick: function (btnId, awe) { - var btnEl = $(btnId) - return this.attachClickFun(this.createClickFun(btnEl || btnId, awe), btnEl) - }, - - // Stop click function. Detach it from event listeners. - // The optional listenEl must be the same element that was used during startClick/attachClickFun; - // in general: Awesomplete.$(btnId). listenEl defaults to document.body. - detachClickFun: function (fun, listenEl) { - listenEl = listenEl || document.body - listenEl.removeEventListener('click', fun) - return fun - }, - - // filter function as specified in Awesomplete. Filters suggestion list on items containing input value. - // Awesomplete.FILTER_CONTAINS filters on data.label, however - // this function filters on value and not on the shown label which may contain markup. - filterContains: function (data, input) { - return Awesomplete.FILTER_CONTAINS(data.value, input) - }, - - // filter function as specified in Awesomplete. Filters suggestion list on matching begin text. - // Awesomplete.FILTER_STARTSWITH filters on data.label, however - // this function filters on value and not on the shown label which may contain markup. - filterStartsWith: function (data, input) { - return Awesomplete.FILTER_STARTSWITH(data.value, input) - }, - - // Flatten JSON. - // { "a":{"b":{"c":[{"d":{"e":1}}]}}} becomes {"a.b.c":[{"d.e":1}]}. - // This function can be bind to configure it with extra options; - // bind({root: '', value: '', label: '

"> - <%= form_for @conn, chain_path(@conn, :search), [class: "form-inline my-2 my-lg-0", method: :get, enforce_utf8: false], fn f -> %> -
- <%= awesomplete(f, :q, - [ - class: "form-control search-control me auto", - placeholder: gettext("Search by address, token symbol, name, transaction hash, or block number"), - "aria-describedby": "search-icon", - "aria-label": gettext("Search"), - "data-test": "search_input" - ], - [ url: "#{chain_path(@conn, :token_autocomplete)}?q=", - limit: 0, - maxItems: 1000, - minChars: 2, - value: "contract_address_hash", - label: "contract_address_hash", - descrSearch: true, - descr: "name", - sort: "function(x1, x2){ - const tokenName1 = x1.split('').length > 1 ? x1.split('')[1].split('')[0].toLowerCase() : '' - const tokenName2 = x2.split('').length > 1 ? x2.split('')[1].split('')[0].toLowerCase() : '' - const holdersCount1 = x1.split('').length > 1 ? parseInt(x1.split('')[1].split('')[0].split('holder')[0], 10) : null - const holdersCount2 = x2.split('').length > 1 ? parseInt(x2.split('')[1].split('')[0].split('holder')[0], 10) : null - if (holdersCount1 && holdersCount2 && holdersCount1 !== holdersCount2 || (holdersCount1 && !holdersCount2) || (!holdersCount1 && holdersCount2)) { - holdersCount1 > holdersCount2 - } else { - if (tokenName1 < tokenName2) { return -1 } - if (tokenName1 > tokenName2) { return 1 } - return 0 - } - }" - ]) %> -
- -
-
-
- / -
-
-
- +
+
"> + +
+
+ +
+
+
+ / +
+
+
+ - <% end %>
<% end %> \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex index d467d6c4ca..cf9398a89d 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex @@ -1,7 +1,3 @@ -" as="style" onload="this.rel='stylesheet'"> -"> - - <% staking_enabled_in_menu = Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:staking_enabled_in_menu] %> <% apps_menu = Application.get_env(:block_scout_web, :apps_menu) %> + \ No newline at end of file diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index 20879a22cb..36bc88d538 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -125,7 +125,6 @@ defmodule BlockScoutWeb.Mixfile do {:wallaby, "~> 0.28", only: :test, runtime: false}, # `:cowboy` `~> 2.0` and Phoenix 1.4 compatibility {:wobserver, "~> 0.2.0", github: "poanetwork/wobserver", branch: "support-https"}, - {:phoenix_form_awesomplete, "~> 0.1.4"}, {:ex_json_schema, "~> 0.6.2"} ] end diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 99c3238fc1..84c3d2530a 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -148,7 +148,7 @@ msgid "API for the %{subnetwork} - BlockScout" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:126 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:122 msgid "APIs" msgstr "" @@ -164,7 +164,7 @@ msgid "APY & Predicted Reward" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:80 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:76 msgid "Accounts" msgstr "" @@ -190,7 +190,7 @@ msgstr "" #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:28 #: lib/block_scout_web/templates/address_transaction/index.html.eex:26 #: lib/block_scout_web/templates/layout/_network_selector.html.eex:21 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:93 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:89 #: lib/block_scout_web/views/address_internal_transaction_view.ex:10 #: lib/block_scout_web/views/address_token_transfer_view.ex:10 #: lib/block_scout_web/views/address_transaction_view.ex:10 @@ -250,7 +250,7 @@ msgid "Approximate Current Annual Percentage Yield. If you see N/A, please wait msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:154 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:150 msgid "Apps" msgstr "" @@ -374,8 +374,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:178 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:32 #: lib/block_scout_web/templates/layout/_topnav.html.eex:36 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:40 msgid "Blocks" msgstr "" @@ -407,12 +407,12 @@ msgid "Bridged Tokens from " msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:103 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:99 msgid "Bridged from BSC" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:98 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:94 msgid "Bridged from Ethereum" msgstr "" @@ -969,7 +969,7 @@ msgid "Error: Could not determine contract creator." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:140 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:136 msgid "Eth RPC" msgstr "" @@ -1034,7 +1034,7 @@ msgid "For any existing contracts in the database, insert all ABI entries into t msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:46 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:42 msgid "Forked Blocks (Reorgs)" msgstr "" @@ -1083,7 +1083,7 @@ msgid "Github" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:130 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:126 msgid "GraphQL" msgstr "" @@ -1523,7 +1523,7 @@ msgid "Parent Hash" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:67 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:63 #: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:184 #: lib/block_scout_web/views/transaction_view.ex:261 #: lib/block_scout_web/views/transaction_view.ex:295 @@ -1590,7 +1590,7 @@ msgid "Potential matches from our contract method database:" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_search.html.eex:48 +#: lib/block_scout_web/templates/layout/_search.html.eex:20 msgid "Press / and focus will be moved to the search field" msgstr "" @@ -1613,7 +1613,7 @@ msgid "Query" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:135 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:131 msgid "RPC" msgstr "" @@ -1729,21 +1729,15 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:16 -#: lib/block_scout_web/templates/layout/_search.html.eex:11 -#: lib/block_scout_web/templates/layout/_search.html.eex:55 +#: lib/block_scout_web/templates/layout/_search.html.eex:27 msgid "Search" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_search.html.eex:5 +#: lib/block_scout_web/templates/layout/_search.html.eex:4 msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_search.html.eex:9 -msgid "Search by address, token symbol, name, transaction hash, or block number" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/layout/_network_selector.html.eex:18 msgid "Search network" @@ -1889,7 +1883,7 @@ msgid "Staker's Address" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:156 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:152 msgid "Stakes" msgstr "" @@ -1901,7 +1895,7 @@ msgid "Stakes Ratio" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:162 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:158 msgid "Staking" msgstr "" @@ -2197,7 +2191,7 @@ msgid "To see accurate decoded input data, the contract must be verified." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:25 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:21 msgid "Toggle navigation" msgstr "" @@ -2260,8 +2254,8 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:21 #: lib/block_scout_web/templates/address_token/index.html.eex:10 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:89 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:115 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:85 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:111 #: lib/block_scout_web/templates/tokens/index.html.eex:4 #: lib/block_scout_web/views/address_view.ex:343 msgid "Tokens" @@ -2346,7 +2340,7 @@ msgstr "" #: lib/block_scout_web/templates/block_transaction/index.html.eex:10 #: lib/block_scout_web/templates/block_transaction/index.html.eex:18 #: lib/block_scout_web/templates/chain/show.html.eex:236 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:55 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:51 #: lib/block_scout_web/views/address_view.ex:345 msgid "Transactions" msgstr "" @@ -2408,7 +2402,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:80 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:43 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:39 msgid "Uncles" msgstr "" @@ -2445,7 +2439,7 @@ msgid "Used" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:59 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:55 msgid "Validated" msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 99c3238fc1..84c3d2530a 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -148,7 +148,7 @@ msgid "API for the %{subnetwork} - BlockScout" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:126 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:122 msgid "APIs" msgstr "" @@ -164,7 +164,7 @@ msgid "APY & Predicted Reward" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:80 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:76 msgid "Accounts" msgstr "" @@ -190,7 +190,7 @@ msgstr "" #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:28 #: lib/block_scout_web/templates/address_transaction/index.html.eex:26 #: lib/block_scout_web/templates/layout/_network_selector.html.eex:21 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:93 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:89 #: lib/block_scout_web/views/address_internal_transaction_view.ex:10 #: lib/block_scout_web/views/address_token_transfer_view.ex:10 #: lib/block_scout_web/views/address_transaction_view.ex:10 @@ -250,7 +250,7 @@ msgid "Approximate Current Annual Percentage Yield. If you see N/A, please wait msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:154 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:150 msgid "Apps" msgstr "" @@ -374,8 +374,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/chain/show.html.eex:178 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:32 #: lib/block_scout_web/templates/layout/_topnav.html.eex:36 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:40 msgid "Blocks" msgstr "" @@ -407,12 +407,12 @@ msgid "Bridged Tokens from " msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:103 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:99 msgid "Bridged from BSC" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:98 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:94 msgid "Bridged from Ethereum" msgstr "" @@ -969,7 +969,7 @@ msgid "Error: Could not determine contract creator." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:140 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:136 msgid "Eth RPC" msgstr "" @@ -1034,7 +1034,7 @@ msgid "For any existing contracts in the database, insert all ABI entries into t msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:46 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:42 msgid "Forked Blocks (Reorgs)" msgstr "" @@ -1083,7 +1083,7 @@ msgid "Github" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:130 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:126 msgid "GraphQL" msgstr "" @@ -1523,7 +1523,7 @@ msgid "Parent Hash" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:67 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:63 #: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:184 #: lib/block_scout_web/views/transaction_view.ex:261 #: lib/block_scout_web/views/transaction_view.ex:295 @@ -1590,7 +1590,7 @@ msgid "Potential matches from our contract method database:" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_search.html.eex:48 +#: lib/block_scout_web/templates/layout/_search.html.eex:20 msgid "Press / and focus will be moved to the search field" msgstr "" @@ -1613,7 +1613,7 @@ msgid "Query" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:135 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:131 msgid "RPC" msgstr "" @@ -1729,21 +1729,15 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:16 -#: lib/block_scout_web/templates/layout/_search.html.eex:11 -#: lib/block_scout_web/templates/layout/_search.html.eex:55 +#: lib/block_scout_web/templates/layout/_search.html.eex:27 msgid "Search" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_search.html.eex:5 +#: lib/block_scout_web/templates/layout/_search.html.eex:4 msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/layout/_search.html.eex:9 -msgid "Search by address, token symbol, name, transaction hash, or block number" -msgstr "" - #, elixir-format #: lib/block_scout_web/templates/layout/_network_selector.html.eex:18 msgid "Search network" @@ -1889,7 +1883,7 @@ msgid "Staker's Address" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:156 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:152 msgid "Stakes" msgstr "" @@ -1901,7 +1895,7 @@ msgid "Stakes Ratio" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:162 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:158 msgid "Staking" msgstr "" @@ -2197,7 +2191,7 @@ msgid "To see accurate decoded input data, the contract must be verified." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:25 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:21 msgid "Toggle navigation" msgstr "" @@ -2260,8 +2254,8 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:21 #: lib/block_scout_web/templates/address_token/index.html.eex:10 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:89 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:115 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:85 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:111 #: lib/block_scout_web/templates/tokens/index.html.eex:4 #: lib/block_scout_web/views/address_view.ex:343 msgid "Tokens" @@ -2346,7 +2340,7 @@ msgstr "" #: lib/block_scout_web/templates/block_transaction/index.html.eex:10 #: lib/block_scout_web/templates/block_transaction/index.html.eex:18 #: lib/block_scout_web/templates/chain/show.html.eex:236 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:55 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:51 #: lib/block_scout_web/views/address_view.ex:345 msgid "Transactions" msgstr "" @@ -2408,7 +2402,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:80 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:43 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:39 msgid "Uncles" msgstr "" @@ -2445,7 +2439,7 @@ msgid "Used" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:59 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:55 msgid "Validated" msgstr "" diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index d8612aa393..76c6e3629b 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1113,13 +1113,8 @@ defmodule Explorer.Chain do select: %{ contract_address_hash: token.contract_address_hash, symbol: token.symbol, - name: - fragment( - "'' || coalesce(?, '') || '' || ' (' || coalesce(?, '') || ') ' || '' || coalesce(?::varchar(255), '') || ' holder(s)' || ''", - token.name, - token.symbol, - token.holder_count - ) + name: token.name, + holder_count: token.holder_count }, order_by: [desc: token.holder_count] ) diff --git a/mix.lock b/mix.lock index 1d499fcda8..4e3fd35032 100644 --- a/mix.lock +++ b/mix.lock @@ -88,7 +88,6 @@ "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "phoenix": {:hex, :phoenix, "1.5.6", "8298cdb4e0f943242ba8410780a6a69cbbe972fef199b341a36898dd751bdd66", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0dc4d39af1306b6aa5122729b0a95ca779e42c708c6fe7abbb3d336d5379e956"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.2.1", "13f124cf0a3ce0f1948cf24654c7b9f2347169ff75c1123f44674afee6af3b03", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "478a1bae899cac0a6e02be1deec7e2944b7754c04e7d4107fc5a517f877743c0"}, - "phoenix_form_awesomplete": {:hex, :phoenix_form_awesomplete, "0.1.6", "c7195aeed29eb0e18ead82cb7d81a1fd43cfc5beb8789f50c37ffe5eeff31d82", [:mix], [{:phoenix_html, "~> 2.10", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "4066a8222d31efec70c2e5e927406314923544b9c9d3611c456fd2fc096d1b18"}, "phoenix_html": {:hex, :phoenix_html, "2.14.3", "51f720d0d543e4e157ff06b65de38e13303d5778a7919bcc696599e5934271b8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "efd697a7fff35a13eeeb6b43db884705cba353a1a41d127d118fda5f90c8e80f"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.0", "f35f61c3f959c9a01b36defaa1f0624edd55b87e236b606664a556d6f72fd2e7", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "02c1007ae393f2b76ec61c1a869b1e617179877984678babde131d716f95b582"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},