]> git.openstreetmap.org Git - nominatim-ui.git/commitdiff
upgrade Bootstrap 4 => 5, remove jquery (#146)
authormtmail <mtmail@gmx.net>
Wed, 19 May 2021 17:13:01 +0000 (19:13 +0200)
committerGitHub <noreply@github.com>
Wed, 19 May 2021 17:13:01 +0000 (19:13 +0200)
* upgrade Bootstrap 4 => 5

15 files changed:
dist/theme/about-help.html
package.json
rollup.config.js
src/components/DetailsOneRow.svelte
src/components/Error.svelte
src/components/Header.svelte
src/components/LastUpdated.svelte
src/components/SearchSection.svelte
src/components/SearchSectionDetails.svelte
src/components/SearchSectionReverse.svelte
src/components/UrlSubmitForm.svelte
src/global_style.css [new file with mode: 0644]
src/pages/DetailsPage.svelte
test/details.js
yarn.lock

index cbf57caa4a31a2bbaa6840e4e12fdc8757cbf3bf..aa0d15d262990ac38617c44af38f74b0415db43f 100644 (file)
@@ -21,5 +21,5 @@
 
 <p class="alert alert-secondary my-4">
   If you're the adminstrator of this website you can edit the text above in
-  <code>dist/branding/about-and-help.html</code>
+  <code>dist/theme/about-and-help.html</code>
 </p>
index adab631731f49a5d1af27c8604069a64d08b8239..1b9092b632e48c26936464ef30afbeb8bff2439c 100644 (file)
     "svelte": "^3.0.0"
   },
   "dependencies": {
-    "bootstrap": "^4.6.0",
+    "bootstrap": "^5.0.0",
     "escape-html": "^1.0.3",
     "eslint": "^7.19.0",
     "eslint-config-airbnb-base": "^14.2.1",
     "eslint-plugin-import": "^2.22.1",
     "eslint-plugin-svelte3": "^3.0.0",
-    "jquery": "^3.5.1",
     "leaflet": "^1.7.1",
     "leaflet-minimap": "^3.6.1",
     "timeago.js": "^4.0.2"
index 90da3f7221dfd10eb470f6832295b54e93c37927..b3a25b4bf19cc23f89af9cb14a4d8494027a29e8 100644 (file)
@@ -4,6 +4,7 @@ import resolve from '@rollup/plugin-node-resolve';
 import livereload from 'rollup-plugin-livereload';
 import { terser } from 'rollup-plugin-terser';
 import css from 'rollup-plugin-css-only';
+import { readFileSync, writeFileSync } from 'fs';
 
 const production = !process.env.ROLLUP_WATCH;
 
@@ -43,9 +44,18 @@ export default {
                                dev: !production
                        }
                }),
-               // we'll extract any component CSS out into
-               // a separate file - better for performance
-               css({ output: 'bundle.css' }),
+               css({
+                 output: function (styles, styleNodes) {
+                       // make sure global_styles.css gets appended to bundle.css,
+                       // not prepended.
+                       // The ':global()' rules (https://svelte.dev/docs#style) get
+                       // prepended so we can't use them to overwrite bootstrap.css
+                       // rules
+                       let global_styles = readFileSync('src/global_style.css');
+                       styles += global_styles;
+                   writeFileSync('dist/build/bundle.css', styles);
+                 }
+               }),
 
                // If you have external dependencies installed from
                // npm, you'll most likely need these plugins. In
index 4c439070b1012b8e7ab30bdbe722b4bb078905f6..1d63d7513be89a3f6ceed24397a48c72d9cfdd14 100644 (file)
@@ -14,7 +14,7 @@
 </script>
 
 <tr class:notused={bMarkUnusedLines && !bAddressLineUsed}>
-  <td class="name font-weight-bold">
+  <td class="name fw-bold">
     {#if addressLine.localname}
       {addressLine.localname}
     {:else}
index db542e3a6804359344626c5bdadb7dd9136f0b3c..92679632d7a5669a499ab43fce9d7f1b407f0fcf 100644 (file)
@@ -14,8 +14,6 @@
   <div id="error" class="container-fluid alert-danger py-3 px-4">
     {error_message}
 
-    <button type="button" class="close" aria-label="dismiss" on:click={dismiss_message}>
-      <span aria-hidden="true">&times;</span>
-    </button>
+    <button type="button" class="btn-close" aria-label="dismiss" on:click={dismiss_message}></button>
   </div>
 {/if}
index 514fe20519ec992827aff9a41b9bcf5e1fde94a3..a93d6c5ec6b42289795b07869090687edf49a6e0 100644 (file)
 
 <header class="container-fluid">
   <nav class="navbar navbar-expand-sm navbar-light">
-    <!-- Brand -->
-    <div class="navbar-brand">
-      <PageLink page="search">
-        <img alt="logo" id="theme-logo" src="theme/logo.png" />
-        <h1>{page_title}</h1>
-      </PageLink>
-    </div>
-    <!-- Toggler (hamburger button) -->
-    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
-      <span class="navbar-toggler-icon"></span>
-    </button>
-    <div class="collapse navbar-collapse" id="navbarSupportedContent">
-      <!-- Left-aligned links -->
-      <ul class="navbar-nav mr-auto">
-        <li class="nav-item {view === 'search' ? 'active' : ''}">
-          <PageLink page="search" extra_classes="nav-link">Search</PageLink>
-        </li>
-        <li class="nav-item {view === 'reverse' ? 'active' : ''}">
-          <ReverseLink lat={map_lat} lon={map_lon} extra_classes="nav-link">Reverse</ReverseLink>
-        </li>
-        <li class="nav-item {view === 'details' ? 'active' : ''}">
-          <PageLink page="details" extra_classes="nav-link">Search By ID</PageLink>
+    <div class="container-fluid">
+      <!-- Brand -->
+      <div class="navbar-brand">
+        <PageLink page="search">
+          <img alt="logo" id="theme-logo" src="theme/logo.png" />
+          <h1>{page_title}</h1>
+        </PageLink>
+      </div>
+      <!-- Toggler (hamburger button) -->
+      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
+        <span class="navbar-toggler-icon"></span>
+      </button>
+      <div class="collapse navbar-collapse" id="navbarSupportedContent">
+        <!-- Left-aligned links -->
+        <ul class="navbar-nav me-auto">
+          <li class="nav-item">
+            <PageLink page="search" extra_classes="nav-link {view === 'search' ? 'active' : ''}">Search</PageLink>
+          </li>
+          <li class="nav-item">
+            <ReverseLink lat={map_lat} lon={map_lon} extra_classes="nav-link {view === 'reverse' ? 'active' : ''}">Reverse</ReverseLink>
+          </li>
+          <li class="nav-item">
+            <PageLink page="details" extra_classes="nav-link {view === 'details' ? 'active' : ''}">Search By ID</PageLink>
+          </li>
+        </ul>
+      </div>
+      <!-- Right aligned links -->
+      <ul class="navbar-nav">
+        <li class="nav-item">
+          <PageLink page="about" extra_classes="nav-link {view === 'about' ? 'active' : ''}">About & Help</PageLink>
         </li>
       </ul>
     </div>
-    <!-- Right aligned links -->
-    <ul class="navbar-nav">
-      <li class="nav-item {view === 'about' ? 'active' : ''}">
-        <PageLink page="about" extra_classes="nav-link">About & Help</PageLink>
-      </li>
-    </ul>
   </nav>
 </header>
 <section class="page-title-section">
index 129c89341cdbb97d91cef3958d5e0336bc0dc523..08be5d9aa54b3b51692c5166958ba65a4441c350 100644 (file)
@@ -48,7 +48,7 @@
 
 <div id="last-updated" class="container-fluid py-2 px-4 mb-3">
   <div id="loading" class="py-2 px-4">
-    <div class="spinner-border spinner-border-sm text-primary mr-1" role="status"></div>
+    <div class="spinner-border spinner-border-sm text-primary me-1" role="status"></div>
     Loading data from API ...
   </div>
   <div class="row">
@@ -62,7 +62,7 @@
         </div>
       {/if}
     </div>
-    <div class="col-sm-6 text-right">
+    <div class="col-sm-6 text-end">
       {#if last_updated_date}
         Data last updated:
         <abbr id="data-date" title="{last_updated_date} (UTC timezone)">{timeago.format(new Date(last_updated_date))}</abbr>
index c78980410e2a45b380c114267bc440abfada3fc4..6bf7bca0f2a06bcc1493c1a00d86e909c5daf6af 100644 (file)
 
 <ul class="nav nav-tabs">
   <li class="nav-item">
-    <a class="nav-link" class:active={!bStructuredSearch} data-toggle="tab" href="#simple">Simple</a>
+    <a class="nav-link" class:active={!bStructuredSearch} data-bs-toggle="tab" href="#simple">Simple</a>
   </li>
   <li class="nav-item">
-    <a class="nav-link" class:active={bStructuredSearch} data-toggle="tab" href="#structured">Structured</a>
+    <a class="nav-link" class:active={bStructuredSearch} data-bs-toggle="tab" href="#structured">Structured</a>
   </li>
 </ul>
 
 <div class="tab-content py-2">
   <div class="tab-pane" class:active={!bStructuredSearch} id="simple" role="tabpanel">
     <UrlSubmitForm page="search">
-      <input id="q"
-             name="q"
-             type="text"
-             class="form-control form-control-sm"
-             placeholder="Search"
-             value="{api_request_params.q || ''}" />
-
-      <button type="submit" class="btn btn-primary btn-sm mx-1">Search</button>
-      <input type="hidden" name="viewbox" value="{sViewBox || ''}" />
-      <input type="hidden" name="dedupe" value="{!api_request_params.dedupe ? '' : 1}" />
-      <input type="hidden" name="bounded" value="{api_request_params.bounded ? 1 : ''}" />
-      <input type="hidden" name="accept-language" value="{api_request_params['accept-language'] || ''}" />
-      <input type="hidden" name="countrycodes" value="{api_request_params.countrycodes || ''}"
-                                                    pattern="^[a-zA-Z]{'{2}'}(,[a-zA-Z]{'{2}'})*$" />
-      <input type="hidden" name="limit" value="{api_request_params.limit || ''}" />
-      <input type="hidden" name="polygon_threshold" value="{api_request_params.polygon_threshold || ''}" />
+      <div class="col-auto">
+        <input id="q"
+               name="q"
+               type="text"
+               class="form-control form-control-sm"
+               placeholder="Search"
+               value="{api_request_params.q || ''}" />
+      </div>
+      <div class="col-auto">
+        <button type="submit" class="btn btn-primary btn-sm mx-1">Search</button>
+        <input type="hidden" name="viewbox" value="{sViewBox || ''}" />
+        <input type="hidden" name="dedupe" value="{!api_request_params.dedupe ? '' : 1}" />
+        <input type="hidden" name="bounded" value="{api_request_params.bounded ? 1 : ''}" />
+        <input type="hidden" name="accept-language" value="{api_request_params['accept-language'] || ''}" />
+        <input type="hidden" name="countrycodes" value="{api_request_params.countrycodes || ''}"
+                                                      pattern="^[a-zA-Z]{'{2}'}(,[a-zA-Z]{'{2}'})*$" />
+        <input type="hidden" name="limit" value="{api_request_params.limit || ''}" />
+        <input type="hidden" name="polygon_threshold" value="{api_request_params.polygon_threshold || ''}" />
+      </div>
     </UrlSubmitForm>
   </div>
   <div class="tab-pane" class:active={bStructuredSearch} id="structured" role="tabpanel">
     <UrlSubmitForm page="search">
-      <input name="street" type="text" class="form-control form-control-sm mr-1"
-             placeholder="House number/Street"
-             value="{api_request_params.street || ''}" />
-      <input name="city" type="text" class="form-control form-control-sm mr-1"
-             placeholder="City"
-             value="{api_request_params.city || ''}" />
-      <input id="county" name="county" type="text" class="form-control form-control-sm mr-1"
-             placeholder="County"
-             value="{api_request_params.county || ''}" />
-      <input name="state" type="text" class="form-control form-control-sm mr-1"
-             placeholder="State"
-             value="{api_request_params.state || ''}" />
-      <input name="country" type="text" class="form-control form-control-sm mr-1"
-             placeholder="Country"
-             value="{api_request_params.country || ''}" />
-      <input name="postalcode" type="text" class="form-control form-control-sm mr-1"
-             placeholder="Postal Code"
-             value="{api_request_params.postalcode || ''}" />
-
-      <button type="submit" class="btn btn-primary btn-sm">Search</button>
-      <input type="hidden" name="viewbox" value="{sViewBox || ''}" />
-      <input type="hidden" name="dedupe" value="{!api_request_params.dedupe ? '' : 1}" />
-      <input type="hidden" name="bounded" value="{api_request_params.bounded ? 1 : ''}" />
-      <input type="hidden" name="accept-language" value="{api_request_params['accept-language'] || ''}" />
-      <input type="hidden" name="countrycodes" value="{api_request_params.countrycodes || ''}"
-                                              pattern="^[a-zA-Z]{'{2}'}(,[a-zA-Z]{'{2}'})*$" />
-      <input type="hidden" name="limit" value="{api_request_params.limit || ''}" />
-      <input type="hidden" name="polygon_threshold" value="{api_request_params.polygon_threshold || ''}" />
+      <div class="col-auto">
+        <input name="street" type="text" class="form-control form-control-sm me-1"
+               placeholder="House number/Street"
+               value="{api_request_params.street || ''}" />
+      </div>
+      <div class="col-auto">
+        <input name="city" type="text" class="form-control form-control-sm me-1"
+               placeholder="City"
+               value="{api_request_params.city || ''}" />
+      </div>
+      <div class="col-auto">
+        <input id="county" name="county" type="text" class="form-control form-control-sm me-1"
+               placeholder="County"
+               value="{api_request_params.county || ''}" />
+      </div>
+      <div class="col-auto">
+        <input name="state" type="text" class="form-control form-control-sm me-1"
+               placeholder="State"
+               value="{api_request_params.state || ''}" />
+      </div>
+      <div class="col-auto">
+        <input name="country" type="text" class="form-control form-control-sm me-1"
+               placeholder="Country"
+               value="{api_request_params.country || ''}" />
+      </div>
+      <div class="col-auto">
+        <input name="postalcode" type="text" class="form-control form-control-sm me-1"
+               placeholder="Postal Code"
+               value="{api_request_params.postalcode || ''}" />
+      </div>
+      <div class="col-auto">
+        <button type="submit" class="btn btn-primary btn-sm">Search</button>
+        <input type="hidden" name="viewbox" value="{sViewBox || ''}" />
+        <input type="hidden" name="dedupe" value="{!api_request_params.dedupe ? '' : 1}" />
+        <input type="hidden" name="bounded" value="{api_request_params.bounded ? 1 : ''}" />
+        <input type="hidden" name="accept-language" value="{api_request_params['accept-language'] || ''}" />
+        <input type="hidden" name="countrycodes" value="{api_request_params.countrycodes || ''}"
+                                                pattern="^[a-zA-Z]{'{2}'}(,[a-zA-Z]{'{2}'})*$" />
+        <input type="hidden" name="limit" value="{api_request_params.limit || ''}" />
+        <input type="hidden" name="polygon_threshold" value="{api_request_params.polygon_threshold || ''}" />
+      </div>
     </UrlSubmitForm>
   </div>
 </div> <!-- /tab-content -->
index cd5faacdc9adff721f05fb75d89d2b36633f5187..a779b4fa60e0424606aabfb2497823f751ecc402 100644 (file)
 </script>
 
 <form on:submit|preventDefault={handleFormSubmit} class="form-inline" action="details.html">
-  <input type="edit"
-         class="form-control form-control-sm mr-1"
-         pattern="^[NWRnwr]?[0-9]+$|.*openstreetmap.*"
-         value="{api_request_params.osmtype || ''}{api_request_params.osmid || ''}{api_request_params.place_id || ''}" />
-  <button type="submit" class="btn btn-primary btn-sm">Show</button>
+  <div class="row g-1">
+    <div class="col-auto">
+      <input type="edit"
+             class="form-control form-control-sm me-1"
+             pattern="^[NWRnwr]?[0-9]+$|.*openstreetmap.*"
+             value="{api_request_params.osmtype || ''}{api_request_params.osmid || ''}{api_request_params.place_id || ''}" />
+      </div>
+    <div class="col-auto">
+      <button type="submit" class="btn btn-primary btn-sm">Show</button>
+    </div>
+  </div>
 </form>
 <small class="form-text text-muted">
   OSM type+id (<em>N123</em>, <em>n123</em>, <em>W123</em>, <em>w123</em>, <em>R123</em>, <em>r123</em>),
index 186190202216aca5843f194f76db25e58cd2118f..a455a4ea82d76b2cc6461bde146bcc6028bfea6b 100644 (file)
 </script>
 
 <UrlSubmitForm page="reverse">
-  <div class="form-group">
+  <div class="col-auto">
     <label for="reverse-lat">lat</label>
+  </div>
+  <div class="col-auto">
     <input id="reverse-lat"
            name="lat"
            type="text"
-           class="form-control form-control-sm"
+           class="form-control form-control-sm d-inline"
            placeholder="latitude"
            pattern="^-?\d+(\.\d+)?$"
            bind:value={lat}
            on:change={maybeSplitLatitude} />
   </div>
-  <div class="form-group">
+  <div class="col-auto">
     <a id="switch-coords"
        on:click|preventDefault|stopPropagation={() => gotoCoordinates(lon, lat)}
        class="btn btn-outline-secondary btn-sm"
        title="switch lat and lon">&lt;&gt;</a>
   </div>
-  <div class="form-group">
+  <div class="col-auto">
     <label for="reverse-lon">lon</label>
+  </div>
+  <div class="col-auto">
     <input id="reverse-lon"
            name="lon"
            type="text"
            pattern="^-?\d+(\.\d+)?$"
            bind:value={lon} />
   </div>
-  <div class="form-group">
+  <div class="col-auto">
     <label for="reverse-zoom">max zoom</label>
-    <select id="reverse-zoom" name="zoom" class="form-control form-control-sm" bind:value={zoom}>
+  </div>
+  <div class="col-auto">
+    <select id="reverse-zoom" name="zoom" class="form-select form-select-sm" bind:value={zoom}>
       <option value="">---</option>
       {#each zoomLevels() as zoomTitle, i}
         <option value="{i}">{i} - {zoomTitle}</option>
       {/each}
     </select>
   </div>
-  <div class="form-group">
+  <div class="col-auto">
     <button type="submit" class="btn btn-primary btn-sm mx-1">Search</button>
   </div>
 </UrlSubmitForm>
 
 <style>
   label {
-    font-weight: normal;
-    margin-left: 0.4rem;
-    margin-right: 0.4rem;
+    font-size: 0.9rem;
+    margin-top: 0.3rem;
   }
 
   #switch-coords {
index 220736133547babf95337a7a7b602385082619fa..a3dfaa07d5bb4f8f82b27d36ccf5bad2cd7d1e5c 100644 (file)
@@ -46,5 +46,7 @@
 </script>
 
 <form on:submit|preventDefault={handle_submit} class="form-inline" role="search" accept-charset="UTF-8" action="">
-  <slot></slot>
+  <div class="row g-2">
+    <slot></slot>
+  </div>
 </form>
diff --git a/src/global_style.css b/src/global_style.css
new file mode 100644 (file)
index 0000000..93bd19b
--- /dev/null
@@ -0,0 +1,10 @@
+/*
+  Svelte allows to set ':global(rule)' in components which get added to the bundle.css
+  file. https://svelte.dev/docs#style But the rules get added to the top of bundle.css,
+  before we load the Bootstrap CSS rules. We want to have our rules at the end of
+  bundle.css
+*/
+
+a { text-decoration: none }
+a:hover { text-decoration: underline; }
+a.btn:hover { text-decoration: none; }
index f824602bb732b3afd78331b9de5c259baaade800..baac1809e9c0eceba10507bdb0180309596a25d6 100644 (file)
@@ -76,7 +76,7 @@
           <small><DetailsLink feature={aPlace}>link to this page</DetailsLink></small>
         </h1>
       </div>
-      <div class="col-sm-2 text-right">
+      <div class="col-sm-2 text-end">
         <MapIcon aPlace={aPlace} />
       </div>
     </div>
@@ -86,7 +86,7 @@
           <tbody>
             <InfoRow title="Name">
             {#if (Array.isArray(aPlace.names)) }
-              <span class="noname font-weight-bold">No Name</span>
+              <span class="noname fw-bold">No Name</span>
             {:else}
               <InfoRowList items={aPlace.names} />
             {/if}
index fb79f4debf7773b144efec518d307d15d992bbd6..2f9f17496e242b6dafe454c82c718cdb72b0c9ed 100644 (file)
@@ -68,7 +68,7 @@ describe('Details Page', function () {
       let display_headers;
       let [display_keywords_btn] = await page.$x("//a[contains(text(), 'display keywords')]");
 
-      await display_keywords_btn.click();
+      await display_keywords_btn.evaluate(node => node.click());
       await page.waitForNavigation();
 
       current_url = new URL(await page.url());
@@ -86,7 +86,7 @@ describe('Details Page', function () {
       let current_url;
       let [child_places_btn] = await page.$x("//a[contains(text(), 'display child places')]");
 
-      await child_places_btn.click();
+      await child_places_btn.evaluate(node => node.click());
       await page.waitForNavigation();
       await page.waitForSelector('table#address');
 
index 0d048ca8f8f3427d4d4e17ca037cdd4cacea6f08..389f82183eef88ea226bd28428934705a2d03265 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
@@ -271,10 +271,10 @@ bl@^4.0.3:
     inherits "^2.0.4"
     readable-stream "^3.4.0"
 
-bootstrap@^4.6.0:
-  version "4.6.0"
-  resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.6.0.tgz#97b9f29ac98f98dfa43bf7468262d84392552fd7"
-  integrity sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==
+bootstrap@^5.0.0:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.0.1.tgz#e7939d599119dc818a90478a2a299bdaff037e09"
+  integrity sha512-Fl79+wsLOZKoiU345KeEaWD0ik8WKRI5zm0YSPj2oF1Qr+BO7z0fco6GbUtqjoG1h4VI89PeKJnMsMMVQdKKTw==
 
 brace-expansion@^1.1.7:
   version "1.1.11"
@@ -1234,11 +1234,6 @@ jest-worker@^26.2.1:
     merge-stream "^2.0.0"
     supports-color "^7.0.0"
 
-jquery@^3.5.1:
-  version "3.6.0"
-  resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470"
-  integrity sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==
-
 js-tokens@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -2003,9 +1998,9 @@ spdx-expression-parse@^3.0.0:
     spdx-license-ids "^3.0.0"
 
 spdx-license-ids@^3.0.0:
-  version "3.0.7"
-  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65"
-  integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==
+  version "3.0.8"
+  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.8.tgz#eb1e97ad99b11bf3f82a3b71a0472dd9a00f2ecf"
+  integrity sha512-NDgA96EnaLSvtbM7trJj+t1LUR3pirkDCcz9nOUlPb5DMBGsH7oES6C3hs3j7R9oHEa1EMvReS/BUAIT5Tcr0g==
 
 sprintf-js@~1.0.2:
   version "1.0.3"