├── .env-example.js ├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .travis.yml ├── README.md ├── bin ├── _ver.js ├── envjslint.js └── server.js ├── data ├── chef-world-resume.geo.json ├── historic-geo.json └── world-audio-geo.json ├── dist └── geojson-popup.js ├── example ├── css │ └── style.css ├── ev-charger.html ├── historic.html ├── resume.html └── world.html ├── index.js ├── npm-deprecate ├── README.md ├── index.js └── package.json ├── package-lock.json ├── package.json └── src ├── popup-geojson-map.js └── utils.js /.env-example.js: -------------------------------------------------------------------------------- 1 | // Template for the Javascript (JSON) '.env.js' file. 2 | 3 | window.ENV = 4 | { 5 | "//": "Your Mapbox access token ('world' example only)", 6 | "ACCESS_TOKEN": "your.mapbox.public.access.token" 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | # https://github.com/actions/starter-workflows/blob/main/ci/node.js.yml 5 | 6 | name: Node.js CI 7 | 8 | on: 9 | push: 10 | branches: [ master ] 11 | pull_request: 12 | branches: [ master ] 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: ubuntu-latest 18 | 19 | strategy: 20 | matrix: 21 | node-version: [ 14.x, 15.x ] 22 | # node-version: [10.x, 12.x, 14.x, 15.x] 23 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 24 | 25 | steps: 26 | - uses: actions/checkout@v2 27 | - name: Use Node.js ${{ matrix.node-version }} 28 | uses: actions/setup-node@v1 29 | with: 30 | node-version: ${{ matrix.node-version }} 31 | - run: npm ci 32 | - run: npm run build --if-present 33 | - run: npm test 34 | - run: npm run copy-env 35 | - run: npm run envjslint-ci 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | 3 | .env 4 | .env.js 5 | node_modules/ 6 | npm-debug.log 7 | # package-lock.json 8 | 9 | # *.html 10 | !example/ev-charger.html 11 | !example/historic.html 12 | !example/resume.html 13 | !example/world.html 14 | 15 | dist/* 16 | !dist/geojson-popup.js 17 | 18 | *.png 19 | 20 | bin/_unc.js 21 | npm-deprecate* 22 | uncomment-* 23 | 24 | *BAK* 25 | 26 | # End. 27 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 14 4 | 5 | cache: npm 6 | 7 | git: 8 | depth: 8 9 | 10 | # install: npm ci 11 | 12 | before_script: npm run copy-env 13 | 14 | script: 15 | - npm run build 16 | - npm test 17 | - npm run envjslint-ci 18 | 19 | after_script: 20 | #- npm run envjs 21 | - ls -alh dist/ 22 | - npm audit 23 | - npm outdated 24 | 25 | # End. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![Node.js CI][gci-icon]][gci] 3 | [![Build status — Travis-CI][travis-icon]][travis] 4 | [![geojson-popup on Npmjs][npm-icon]][npm] 5 | [![js-semistandard-style][semi-icon]][semi] 6 | [![License][license-icon]][mit] 7 | [![Total downloads - NPMJS.com][downl-icon]][npm] 8 | 9 | # nfreear / geojson-popup 10 | 11 | Add GeoJSON-based templated popups to a [Leaflet][] map. See the audio-player example(s). 12 | 13 | Easily create interactive maps, with popups containing structured data, for example, audio players. 14 | 15 | Read the [introductory blog post][blog]. 16 | 17 | ## Changelog 18 | 19 | * [Release notes on GitHub][rel] 20 | 21 | ## Build & test 22 | 23 | ```sh 24 | git clone https://github.com/nfreear/popup-geojson-map geojson-popup 25 | cd geojson-popup 26 | npm install && npm run build && npm test 27 | npm start 28 | ``` 29 | 30 | ## Usage 31 | 32 | HTML containing a template, with placeholders, `title` and `audio_url` 33 | 34 | ```html 35 |

36 | 37 | 43 | 44 | 45 | 46 | 47 | ``` 48 | 49 | GeoJSON, with properties corresponding to the template placeholders, `title` and `audio_url`: 50 | 51 | ```json 52 | "features": [ 53 | { 54 | "type": "Feature", 55 | "properties": { 56 | "title": "raining on the roof of Jennie Lee Building.wav", 57 | "audio_url": "https://freesound.org/data/previews/92/92744_1315834-lq.mp3" 58 | }, 59 | "geometry": { 60 | "type": "Point", 61 | "coordinates": [ -0.7110, 52.0241 ] 62 | } 63 | } 64 | ] 65 | ``` 66 | 67 | ### `marker-symbol` 68 | 69 | [GeoJSON with `marker-symbol`][ev], and built in support for [Maki icons][maki]: 70 | 71 | ```html 72 | 78 | ``` 79 | 80 | ```json 81 | { 82 | "type": "FeatureCollection", 83 | "features": [ 84 | { 85 | "type": "Feature", 86 | "properties": { 87 | "marker-symbol": "fuel", 88 | "marker-class": "custom", 89 | "marker-html": "HTML", 90 | "title": "Estates charging point (7 kW)" 91 | }, 92 | "geometry": { 93 | "type": "Point", 94 | "coordinates": [ -0.71138, 52.02565 ] 95 | } 96 | } 97 | ] 98 | } 99 | ``` 100 | 101 | --- 102 | 103 | Software & specs: 104 | * [GeoJSON][] 105 | * [Leaflet][] 106 | * [Lodash.template][_tpl] - _bundled_ 107 | * [SuperAgent][] - _bundled_ 108 | 109 | Map tiles: 110 | * [Leaflet providers][prov] — _default / free._ 111 | * [Mapbox][] 112 | * [National Library of Scotland - Historic Maps][NLS] 113 | 114 | Sources for example data & audio files: 115 | * [Freesound][] 116 | * [Commons][] 117 | 118 | ## Rename 119 | 120 | I'm renaming the NPM package from [`popup-geojson-map`][npm-old] to the clearer [`geojson-popup`][npm]. 121 | (I'll probably re-name the GitHub repo. too.) _Sorry for any hassle!_ 122 | 123 | 124 | --- 125 | 126 | * NPM: [geojson-popup][npm] 127 | * GitHub: [nfreear/popup-geojson-map][] 128 | * Gist: [nfreear/fd10..][gist] 129 | 130 | --- 131 | © 2016-2021 Nick Freear, | License: [MIT][]. 132 | 133 | 134 | [blog]: https://nick.freear.org.uk/2017/06/27/geojson-popup-leaflet.html?utm_source=npm 135 | [MIT]: https://nfreear.mit-license.org/2016-2021 "MIT License | © 2016-2021 Nick Freear (date: 2016-09-26)" 136 | [travis-icon]: https://travis-ci.org/nfreear/popup-geojson-map.svg 137 | [travis]: https://travis-ci.org/nfreear/popup-geojson-map "Build status – Travis-CI" 138 | [npm-old]: https://npmjs.com/package/popup-geojson-map 139 | [npm]: https://npmjs.com/package/geojson-popup 140 | [nfreear/popup-geojson-map]: https://github.com/nfreear/popup-geojson-map 141 | [rel]: https://github.com/nfreear/popup-geojson-map/releases 142 | [gist]: https://gist.github.com/nfreear/fd1005a2af7a8166862011b8fcb8a821 "Gist: original JS (27-Sep-2016)" 143 | [resume]: https://gist.github.com/nfreear/cceecc6e1cabdf8f8f4302aaed10923d "Resume GeoJSON" 144 | [ev]: https://gist.github.com/nfreear/d1cb9d672dd33511056fa472c9bde36f "OU ev-charging GeoJSON" 145 | 146 | [RFC]: https://tools.ietf.org/html/rfc7946 "The GeoJSON Format, August 2016." 147 | [GeoJSON]: https://geojson.org/ 148 | [Leaflet]: https://leafletjs.com/examples/geojson/ "Using GeoJSON with Leaflet" 149 | [SuperAgent]: https://visionmedia.github.io/superagent/ 150 | [Superagent-X]: http://smalljs.org/ajax/superagent/ 151 | [Lodash]: https://lodash.com/ 152 | [_tpl]: https://npmjs.com/package/lodash.template 153 | [Underscore.js]: https://underscorejs.org/ 154 | [Freesound]: https://freesound.org/search/?q=metro "Freesound search: 'metro'" 155 | [Commons]: https://commons.wikimedia.org/wiki/Category:Audio_files_of_music 156 | [prov]: https://leaflet-extras.github.io/leaflet-providers/preview/ 157 | "'This page shows mini maps for all the layers available in Leaflet-providers.'" 158 | [Mapbox]: https://www.mapbox.com/ 159 | [NLS]: http://maps.nls.uk/projects/api/ "National Library of Scotland - Historic Maps API." 160 | [maki]: https://www.mapbox.com/maki-icons/ "Maki is an icon set made for map designers." 161 | 162 | [semi]: https://github.com/Flet/semistandard 163 | [semi-icon]: https://img.shields.io/badge/code_style-semistandard-brightgreen.svg 164 | "Javascript coding style — 'semistandard'" 165 | [npm-icon]: https://img.shields.io/npm/v/geojson-popup.svg "Latest version ~ on NPM" 166 | [license-icon]: https://img.shields.io/npm/l/geojson-popup.svg 167 | [downl-icon]: https://img.shields.io/npm/dt/geojson-popup.svg "Count of total downloads ~NPM" 168 | [gci]: https://github.com/nfreear/popup-geojson-map/actions/workflows/node.js.yml 169 | [gci-icon]: https://github.com/nfreear/popup-geojson-map/actions/workflows/node.js.yml/badge.svg 170 | 171 | 172 | [End]: //. 173 | -------------------------------------------------------------------------------- /bin/_ver.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * CLI. Implant `package.version` in index.js, README.md etc. 5 | * 6 | * @copyright © Nick Freear, 04-June-, 25-June-2017. 7 | * @license MIT 8 | * @see https://github.com/nfreear/gaad-widget 9 | */ 10 | 11 | const replace = require('replace'); 12 | const INDEX_JS = path('/../index.js'); 13 | const README = path('/../README.md'); 14 | // const TEST_HTML = path('/../example/world.html'); 15 | const PKG = require('../package.json'); 16 | const VERSION_TAG = PKG.version; // Was: .replace(/\.0(-.+)?/, '$1'); 17 | const LEAFLET_VER = PKG.peerDependencies.leaflet; 18 | 19 | console.warn('VERSION_TAG :', VERSION_TAG); 20 | console.warn('Leaflet JS :', LEAFLET_VER); 21 | 22 | replace({ 23 | paths: [INDEX_JS], 24 | regex: /VERSION = '.+';(.+Auto.)?/, 25 | replacement: version('VERSION = \'%s\'; // '), 26 | count: true, 27 | recursive: false 28 | }); 29 | 30 | replace({ 31 | paths: [INDEX_JS], 32 | regex: /@version \d\.\d\.\d(-[.\w]+)?/, 33 | replacement: version('@version %s'), 34 | count: true, 35 | recursive: false 36 | }); 37 | 38 | replace({ 39 | paths: [README], // TEST_HTML ? 40 | regex: /\/(geojson-popup|popup-geojson-map)(\/|@)(\d\.\d\.\d(-[.\w]+)?)/g, 41 | replacement: version('/geojson-popup$2%s'), 42 | count: true, 43 | recursive: false 44 | }); 45 | 46 | replace({ 47 | paths: [ex('ev-charger'), ex('historic'), ex('resume'), ex('world')], 48 | regex: /leaflet@\d\.\d\.\d/g, 49 | replacement: 'leaflet@' + LEAFLET_VER, 50 | count: true, 51 | recursive: false 52 | }); 53 | 54 | function ex (base) { 55 | return require('path').join(__dirname, '../example', base + '.html'); 56 | } 57 | 58 | function path (file) { 59 | return require('path').join(__dirname, file); 60 | } 61 | 62 | function version (str) { 63 | return str.replace('%s', VERSION_TAG); 64 | } 65 | -------------------------------------------------------------------------------- /bin/envjslint.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Simple JSON linter for '.env.js' files. 3 | */ 4 | 5 | /* global require, console */ 6 | 7 | const envfile = '.env.js'; 8 | const fs = require('fs'); 9 | let script = fs.readFileSync(envfile, 'utf8'); 10 | 11 | // script = script.replace(/(.*=|^\/\/.*|^;.*)/g, ''); 12 | script = script.replace(/[\n;]/g, '').replace(/.*=/, ''); 13 | 14 | const obj = JSON.parse(script); 15 | 16 | console.info('File contains valid JSON: ' + envfile); 17 | console.log(obj); 18 | -------------------------------------------------------------------------------- /bin/server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /*! 4 | Web server for examples. 5 | 6 | In a terminal, type: 7 | npm start 8 | # node bin/index.js 9 | 10 | In a browser, visit: 11 | http://localhost:9000/example/world.html 12 | 13 | http://stackoverflow.com/questions/16333790/node-js-quick-file-server-static-files-over-http 14 | 15 | © Nick Freear, 27 October 2016. 16 | */ 17 | 18 | /* global require, console, __dirname */ 19 | 20 | const PORT = 9000; 21 | const nodeStatic = require('node-static'); 22 | const path = require('path').join; 23 | const fileServer = new nodeStatic.Server(path(__dirname, '..')); 24 | 25 | require('http').createServer(function (request, response) { 26 | request.addListener('end', function () { 27 | fileServer.serve(request, response); 28 | }).resume(); 29 | }).listen(PORT); // (8080); 30 | 31 | console.info('Web server running...'); 32 | console.info('Visit: http://localhost:%d/example/world.html', PORT); 33 | console.info('Type control + C to exit.'); 34 | -------------------------------------------------------------------------------- /data/chef-world-resume.geo.json: -------------------------------------------------------------------------------- 1 | { 2 | "#": [ 3 | "Example of a CV or résumé for a globe-trotting chef, based on a world map!", 4 | "https://gist.github.com/nfreear/cceecc6e1cabdf8f8f4302aaed10923d" 5 | ], 6 | "type": "FeatureCollection", 7 | "features": [ 8 | { 9 | "type": "Feature", 10 | "properties": { 11 | "Employer": "Sofitel Hyland Shanghai, China.", 12 | "Job title": "Sou Chef", 13 | "Period": "2001 - 2003", 14 | "Summary": "This was my first job after food college. I learned lots about Chinese culture and food.", 15 | "url": "https://www.google.com/maps/place/Nanjing+Road/@31.2357851,121.4802126,19.93z/", 16 | "marker-symbol": "lodging", 17 | "marker-size": "medium", 18 | "marker-color": "6a94c4" 19 | }, 20 | "geometry": { 21 | "type": "Point", 22 | "coordinates": [ 23 | 121.4802126, 24 | 31.2357851 25 | ] 26 | } 27 | }, 28 | 29 | { 30 | "type": "Feature", 31 | "properties": { 32 | "Employer": "Novotel Paris Gare de Lyon, France.", 33 | "Job title": "Head Chef", 34 | "Period": "2003-2008", 35 | "Summary": "I supervised the re-development of the restaurants in this long-established hotel. Under my stewardship, the restaurants went from 32% to 57% mean occupancy, over the course of 24 months", 36 | "url": "https://google.com/maps/place/Novotel+Paris+Gare+de+Lyon/@48.8453195,2.3732829,17z/", 37 | "marker-symbol": "restaurant", 38 | "marker-size": "large", 39 | "marker-color": "c48d6a" 40 | }, 41 | "geometry": { 42 | "type": "Point", 43 | "coordinates": [ 44 | 2.3732829, 45 | 48.8453195 46 | ] 47 | } 48 | }, 49 | 50 | { 51 | "type": "Feature", 52 | "properties": { 53 | "Employer": "British Embassy, Washington DC, USA.", 54 | "Job title": "Head Chef", 55 | "Period": "2009-2012", 56 | "Summary": "We cater for everything from intimate meals for the ambassador and her family, through to formal banquets for visiting dignitaries with complex dietary requirements.", 57 | "url": "https://google.com/maps/place/British+Embassy/@38.9202794,-77.0651596,17z/", 58 | "marker-symbol": "commercial", 59 | "marker-size": "large", 60 | "marker-color": "c4ba6a", 61 | "marker-color-x": "af6ac4" 62 | }, 63 | "geometry": { 64 | "type": "Point", 65 | "coordinates": [ 66 | -77.0651596, 67 | 38.9202794 68 | ] 69 | } 70 | } 71 | ], 72 | 73 | "x-useful-resources": [ 74 | "https://en.support.wordpress.com/shortcodes/#miscellaneous", 75 | "https://github.com/blog/1528-there-s-a-map-for-that", 76 | "https://help.github.com/articles/mapping-geojson-files-on-github/", 77 | "https://mapbox.com/maki-icons/", 78 | "https://sessions.edu/color-calculator/" 79 | ] 80 | } 81 | -------------------------------------------------------------------------------- /data/historic-geo.json: -------------------------------------------------------------------------------- 1 | { 2 | "#": [ 3 | "Interesting sounds relating to Milton Keynes and London, UK." 4 | ], 5 | "type": "FeatureCollection", 6 | "features": [ 7 | { 8 | "type": "Feature", 9 | "properties": { 10 | "title": "raining on the roof of Jennie Lee Building.wav", 11 | "description": "'Rain on the roof of the Jennie Lee Building of Open University in Milton Keynes...'", 12 | "audio_url": "http://freesound.org/data/previews/92/92744_1315834-lq.mp3", 13 | "credit_url": "http://freesound.org/people/laspaziale/sounds/92744/" 14 | }, 15 | "geometry": { 16 | "type": "Point", 17 | "coordinates": [ -0.711009, 52.024157 ] 18 | } 19 | }, 20 | { 21 | "type": "Feature", 22 | "properties": { 23 | "title": "London Underground - Mind The Gap", 24 | "description": "'Short sample of the famous 'mind the gap' announcement...'", 25 | "audio_url": "https://freesound.org/data/previews/327/327942_4486188-lq.mp3", 26 | "credit_url": "http://freesound.org/people/kwahmah_02/sounds/327942/" 27 | }, 28 | "geometry": { 29 | "type": "Point", 30 | "coordinates": [ -0.09, 51.505 ] 31 | } 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /data/world-audio-geo.json: -------------------------------------------------------------------------------- 1 | { 2 | "#": [ 3 | "Short audio files from Wikipedia & Freesound, relating to radio and undergrounds/ subways, from round the world." 4 | ], 5 | "type": "FeatureCollection", 6 | "features": [ 7 | { 8 | "type": "Feature", 9 | "properties": { 10 | "title": "Budapest metro Jingle", 11 | "description": "Budapest, Hungary", 12 | "audio_url": "https://upload.wikimedia.org/wikipedia/commons/4/41/Budapest_metro_jingle.ogg", 13 | "credit_url": "https://commons.wikimedia.org/wiki/File:Budapest_metro_jingle.ogg", 14 | "marker-symbol": "commercial", 15 | "marker-size": "large", 16 | "marker-color": "c4ba6a" 17 | }, 18 | "geometry": { 19 | "type": "Point", 20 | "coordinates": [ 18.8494264, 47.4808706 ] 21 | } 22 | }, 23 | { 24 | "type": "Feature", 25 | "properties": { 26 | "title": "News CS jingle", 27 | "description": "Prague, Czech Republic", 28 | "audio_url": "https://upload.wikimedia.org/wikipedia/commons/d/da/News-cs-jingle.ogg", 29 | "credit_url": "https://commons.wikimedia.org/wiki/File:News-cs-jingle.ogg" 30 | }, 31 | "geometry": { 32 | "type": "Point", 33 | "coordinates": [ 14.3819975, 50.0842324 ] 34 | } 35 | }, 36 | 37 | { 38 | "type": "Feature", 39 | "properties": { 40 | "title": "Sleepys jingle", 41 | "description": "Bed maker — Hicksville, New York.", 42 | "audio_url": "https://upload.wikimedia.org/wikipedia/commons/c/c9/Sleepys_jingle_60_seconds.ogg", 43 | "credit_url": "https://commons.wikimedia.org/wiki/File:Sleepys_jingle_60_seconds.ogg" 44 | }, 45 | "geometry": { 46 | "type": "Point", 47 | "coordinates": [ -73.523333, 40.763333 ] 48 | } 49 | }, 50 | { 51 | "type": "Feature", 52 | "properties": { 53 | "title": "ding-dong3", 54 | "description": "'Recorded at a metro station in Barcelona'", 55 | "audio_url": "https://freesound.org/data/previews/155/155776_2767757-lq.mp3", 56 | "credit_url": "http://freesound.org/people/MetroSoundBCN/sounds/155776/" 57 | }, 58 | "geometry": { 59 | "type": "Point", 60 | "coordinates": [ 2.0083379, 41.3947046 ] 61 | } 62 | }, 63 | { 64 | "type": "Feature", 65 | "properties": { 66 | "title": "London Underground - Mind The Gap", 67 | "description": "'Short sample of the famous 'mind the gap' announcement...'", 68 | "audio_url": "https://freesound.org/data/previews/327/327942_4486188-lq.mp3", 69 | "credit_url": "http://freesound.org/people/kwahmah_02/sounds/327942/" 70 | }, 71 | "geometry": { 72 | "type": "Point", 73 | "coordinates": [ -0.09, 51.505 ] 74 | } 75 | } 76 | ] 77 | } 78 | -------------------------------------------------------------------------------- /dist/geojson-popup.js: -------------------------------------------------------------------------------- 1 | (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 12 | 13 | const utils = require('./src/utils'); 14 | const lodashish = { 15 | template: require('lodash.template'), 16 | extend: utils.extend, 17 | cdn: utils.cdn 18 | }; 19 | const window = global; // || window; 20 | 21 | require('./src/popup-geojson-map')(window, lodashish, VERSION); 22 | 23 | }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 24 | 25 | },{"./src/popup-geojson-map":5,"./src/utils":6,"lodash.template":3}],2:[function(require,module,exports){ 26 | /** 27 | * lodash 3.0.0 (Custom Build) 28 | * Build: `lodash modern modularize exports="npm" -o ./` 29 | * Copyright 2012-2015 The Dojo Foundation 30 | * Based on Underscore.js 1.7.0 31 | * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 32 | * Available under MIT license 33 | */ 34 | 35 | /** Used to match template delimiters. */ 36 | var reInterpolate = /<%=([\s\S]+?)%>/g; 37 | 38 | module.exports = reInterpolate; 39 | 40 | },{}],3:[function(require,module,exports){ 41 | (function (global){(function (){ 42 | /** 43 | * Lodash (Custom Build) 44 | * Build: `lodash modularize exports="npm" -o ./` 45 | * Copyright OpenJS Foundation and other contributors 46 | * Released under MIT license 47 | * Based on Underscore.js 1.8.3 48 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 49 | */ 50 | var reInterpolate = require('lodash._reinterpolate'), 51 | templateSettings = require('lodash.templatesettings'); 52 | 53 | /** Used to detect hot functions by number of calls within a span of milliseconds. */ 54 | var HOT_COUNT = 800, 55 | HOT_SPAN = 16; 56 | 57 | /** Used as references for various `Number` constants. */ 58 | var INFINITY = 1 / 0, 59 | MAX_SAFE_INTEGER = 9007199254740991; 60 | 61 | /** `Object#toString` result references. */ 62 | var argsTag = '[object Arguments]', 63 | arrayTag = '[object Array]', 64 | asyncTag = '[object AsyncFunction]', 65 | boolTag = '[object Boolean]', 66 | dateTag = '[object Date]', 67 | domExcTag = '[object DOMException]', 68 | errorTag = '[object Error]', 69 | funcTag = '[object Function]', 70 | genTag = '[object GeneratorFunction]', 71 | mapTag = '[object Map]', 72 | numberTag = '[object Number]', 73 | nullTag = '[object Null]', 74 | objectTag = '[object Object]', 75 | proxyTag = '[object Proxy]', 76 | regexpTag = '[object RegExp]', 77 | setTag = '[object Set]', 78 | stringTag = '[object String]', 79 | symbolTag = '[object Symbol]', 80 | undefinedTag = '[object Undefined]', 81 | weakMapTag = '[object WeakMap]'; 82 | 83 | var arrayBufferTag = '[object ArrayBuffer]', 84 | dataViewTag = '[object DataView]', 85 | float32Tag = '[object Float32Array]', 86 | float64Tag = '[object Float64Array]', 87 | int8Tag = '[object Int8Array]', 88 | int16Tag = '[object Int16Array]', 89 | int32Tag = '[object Int32Array]', 90 | uint8Tag = '[object Uint8Array]', 91 | uint8ClampedTag = '[object Uint8ClampedArray]', 92 | uint16Tag = '[object Uint16Array]', 93 | uint32Tag = '[object Uint32Array]'; 94 | 95 | /** Used to match empty string literals in compiled template source. */ 96 | var reEmptyStringLeading = /\b__p \+= '';/g, 97 | reEmptyStringMiddle = /\b(__p \+=) '' \+/g, 98 | reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; 99 | 100 | /** 101 | * Used to match `RegExp` 102 | * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns). 103 | */ 104 | var reRegExpChar = /[\\^$.*+?()[\]{}|]/g; 105 | 106 | /** 107 | * Used to match 108 | * [ES template delimiters](http://ecma-international.org/ecma-262/7.0/#sec-template-literal-lexical-components). 109 | */ 110 | var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; 111 | 112 | /** Used to detect host constructors (Safari). */ 113 | var reIsHostCtor = /^\[object .+?Constructor\]$/; 114 | 115 | /** Used to detect unsigned integer values. */ 116 | var reIsUint = /^(?:0|[1-9]\d*)$/; 117 | 118 | /** Used to ensure capturing order of template delimiters. */ 119 | var reNoMatch = /($^)/; 120 | 121 | /** Used to match unescaped characters in compiled string literals. */ 122 | var reUnescapedString = /['\n\r\u2028\u2029\\]/g; 123 | 124 | /** Used to identify `toStringTag` values of typed arrays. */ 125 | var typedArrayTags = {}; 126 | typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = 127 | typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = 128 | typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = 129 | typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = 130 | typedArrayTags[uint32Tag] = true; 131 | typedArrayTags[argsTag] = typedArrayTags[arrayTag] = 132 | typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = 133 | typedArrayTags[dataViewTag] = typedArrayTags[dateTag] = 134 | typedArrayTags[errorTag] = typedArrayTags[funcTag] = 135 | typedArrayTags[mapTag] = typedArrayTags[numberTag] = 136 | typedArrayTags[objectTag] = typedArrayTags[regexpTag] = 137 | typedArrayTags[setTag] = typedArrayTags[stringTag] = 138 | typedArrayTags[weakMapTag] = false; 139 | 140 | /** Used to escape characters for inclusion in compiled string literals. */ 141 | var stringEscapes = { 142 | '\\': '\\', 143 | "'": "'", 144 | '\n': 'n', 145 | '\r': 'r', 146 | '\u2028': 'u2028', 147 | '\u2029': 'u2029' 148 | }; 149 | 150 | /** Detect free variable `global` from Node.js. */ 151 | var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; 152 | 153 | /** Detect free variable `self`. */ 154 | var freeSelf = typeof self == 'object' && self && self.Object === Object && self; 155 | 156 | /** Used as a reference to the global object. */ 157 | var root = freeGlobal || freeSelf || Function('return this')(); 158 | 159 | /** Detect free variable `exports`. */ 160 | var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports; 161 | 162 | /** Detect free variable `module`. */ 163 | var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module; 164 | 165 | /** Detect the popular CommonJS extension `module.exports`. */ 166 | var moduleExports = freeModule && freeModule.exports === freeExports; 167 | 168 | /** Detect free variable `process` from Node.js. */ 169 | var freeProcess = moduleExports && freeGlobal.process; 170 | 171 | /** Used to access faster Node.js helpers. */ 172 | var nodeUtil = (function() { 173 | try { 174 | // Use `util.types` for Node.js 10+. 175 | var types = freeModule && freeModule.require && freeModule.require('util').types; 176 | 177 | if (types) { 178 | return types; 179 | } 180 | 181 | // Legacy `process.binding('util')` for Node.js < 10. 182 | return freeProcess && freeProcess.binding && freeProcess.binding('util'); 183 | } catch (e) {} 184 | }()); 185 | 186 | /* Node.js helper references. */ 187 | var nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray; 188 | 189 | /** 190 | * A faster alternative to `Function#apply`, this function invokes `func` 191 | * with the `this` binding of `thisArg` and the arguments of `args`. 192 | * 193 | * @private 194 | * @param {Function} func The function to invoke. 195 | * @param {*} thisArg The `this` binding of `func`. 196 | * @param {Array} args The arguments to invoke `func` with. 197 | * @returns {*} Returns the result of `func`. 198 | */ 199 | function apply(func, thisArg, args) { 200 | switch (args.length) { 201 | case 0: return func.call(thisArg); 202 | case 1: return func.call(thisArg, args[0]); 203 | case 2: return func.call(thisArg, args[0], args[1]); 204 | case 3: return func.call(thisArg, args[0], args[1], args[2]); 205 | } 206 | return func.apply(thisArg, args); 207 | } 208 | 209 | /** 210 | * A specialized version of `_.map` for arrays without support for iteratee 211 | * shorthands. 212 | * 213 | * @private 214 | * @param {Array} [array] The array to iterate over. 215 | * @param {Function} iteratee The function invoked per iteration. 216 | * @returns {Array} Returns the new mapped array. 217 | */ 218 | function arrayMap(array, iteratee) { 219 | var index = -1, 220 | length = array == null ? 0 : array.length, 221 | result = Array(length); 222 | 223 | while (++index < length) { 224 | result[index] = iteratee(array[index], index, array); 225 | } 226 | return result; 227 | } 228 | 229 | /** 230 | * The base implementation of `_.times` without support for iteratee shorthands 231 | * or max array length checks. 232 | * 233 | * @private 234 | * @param {number} n The number of times to invoke `iteratee`. 235 | * @param {Function} iteratee The function invoked per iteration. 236 | * @returns {Array} Returns the array of results. 237 | */ 238 | function baseTimes(n, iteratee) { 239 | var index = -1, 240 | result = Array(n); 241 | 242 | while (++index < n) { 243 | result[index] = iteratee(index); 244 | } 245 | return result; 246 | } 247 | 248 | /** 249 | * The base implementation of `_.unary` without support for storing metadata. 250 | * 251 | * @private 252 | * @param {Function} func The function to cap arguments for. 253 | * @returns {Function} Returns the new capped function. 254 | */ 255 | function baseUnary(func) { 256 | return function(value) { 257 | return func(value); 258 | }; 259 | } 260 | 261 | /** 262 | * The base implementation of `_.values` and `_.valuesIn` which creates an 263 | * array of `object` property values corresponding to the property names 264 | * of `props`. 265 | * 266 | * @private 267 | * @param {Object} object The object to query. 268 | * @param {Array} props The property names to get values for. 269 | * @returns {Object} Returns the array of property values. 270 | */ 271 | function baseValues(object, props) { 272 | return arrayMap(props, function(key) { 273 | return object[key]; 274 | }); 275 | } 276 | 277 | /** 278 | * Used by `_.template` to escape characters for inclusion in compiled string literals. 279 | * 280 | * @private 281 | * @param {string} chr The matched character to escape. 282 | * @returns {string} Returns the escaped character. 283 | */ 284 | function escapeStringChar(chr) { 285 | return '\\' + stringEscapes[chr]; 286 | } 287 | 288 | /** 289 | * Gets the value at `key` of `object`. 290 | * 291 | * @private 292 | * @param {Object} [object] The object to query. 293 | * @param {string} key The key of the property to get. 294 | * @returns {*} Returns the property value. 295 | */ 296 | function getValue(object, key) { 297 | return object == null ? undefined : object[key]; 298 | } 299 | 300 | /** 301 | * Creates a unary function that invokes `func` with its argument transformed. 302 | * 303 | * @private 304 | * @param {Function} func The function to wrap. 305 | * @param {Function} transform The argument transform. 306 | * @returns {Function} Returns the new function. 307 | */ 308 | function overArg(func, transform) { 309 | return function(arg) { 310 | return func(transform(arg)); 311 | }; 312 | } 313 | 314 | /** Used for built-in method references. */ 315 | var funcProto = Function.prototype, 316 | objectProto = Object.prototype; 317 | 318 | /** Used to detect overreaching core-js shims. */ 319 | var coreJsData = root['__core-js_shared__']; 320 | 321 | /** Used to resolve the decompiled source of functions. */ 322 | var funcToString = funcProto.toString; 323 | 324 | /** Used to check objects for own properties. */ 325 | var hasOwnProperty = objectProto.hasOwnProperty; 326 | 327 | /** Used to detect methods masquerading as native. */ 328 | var maskSrcKey = (function() { 329 | var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || ''); 330 | return uid ? ('Symbol(src)_1.' + uid) : ''; 331 | }()); 332 | 333 | /** 334 | * Used to resolve the 335 | * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) 336 | * of values. 337 | */ 338 | var nativeObjectToString = objectProto.toString; 339 | 340 | /** Used to infer the `Object` constructor. */ 341 | var objectCtorString = funcToString.call(Object); 342 | 343 | /** Used to detect if a method is native. */ 344 | var reIsNative = RegExp('^' + 345 | funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&') 346 | .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' 347 | ); 348 | 349 | /** Built-in value references. */ 350 | var Buffer = moduleExports ? root.Buffer : undefined, 351 | Symbol = root.Symbol, 352 | getPrototype = overArg(Object.getPrototypeOf, Object), 353 | propertyIsEnumerable = objectProto.propertyIsEnumerable, 354 | symToStringTag = Symbol ? Symbol.toStringTag : undefined; 355 | 356 | var defineProperty = (function() { 357 | try { 358 | var func = getNative(Object, 'defineProperty'); 359 | func({}, '', {}); 360 | return func; 361 | } catch (e) {} 362 | }()); 363 | 364 | /* Built-in method references for those with the same name as other `lodash` methods. */ 365 | var nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined, 366 | nativeKeys = overArg(Object.keys, Object), 367 | nativeMax = Math.max, 368 | nativeNow = Date.now; 369 | 370 | /** Used to convert symbols to primitives and strings. */ 371 | var symbolProto = Symbol ? Symbol.prototype : undefined, 372 | symbolToString = symbolProto ? symbolProto.toString : undefined; 373 | 374 | /** 375 | * Creates an array of the enumerable property names of the array-like `value`. 376 | * 377 | * @private 378 | * @param {*} value The value to query. 379 | * @param {boolean} inherited Specify returning inherited property names. 380 | * @returns {Array} Returns the array of property names. 381 | */ 382 | function arrayLikeKeys(value, inherited) { 383 | var isArr = isArray(value), 384 | isArg = !isArr && isArguments(value), 385 | isBuff = !isArr && !isArg && isBuffer(value), 386 | isType = !isArr && !isArg && !isBuff && isTypedArray(value), 387 | skipIndexes = isArr || isArg || isBuff || isType, 388 | result = skipIndexes ? baseTimes(value.length, String) : [], 389 | length = result.length; 390 | 391 | for (var key in value) { 392 | if ((inherited || hasOwnProperty.call(value, key)) && 393 | !(skipIndexes && ( 394 | // Safari 9 has enumerable `arguments.length` in strict mode. 395 | key == 'length' || 396 | // Node.js 0.10 has enumerable non-index properties on buffers. 397 | (isBuff && (key == 'offset' || key == 'parent')) || 398 | // PhantomJS 2 has enumerable non-index properties on typed arrays. 399 | (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) || 400 | // Skip index properties. 401 | isIndex(key, length) 402 | ))) { 403 | result.push(key); 404 | } 405 | } 406 | return result; 407 | } 408 | 409 | /** 410 | * Assigns `value` to `key` of `object` if the existing value is not equivalent 411 | * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) 412 | * for equality comparisons. 413 | * 414 | * @private 415 | * @param {Object} object The object to modify. 416 | * @param {string} key The key of the property to assign. 417 | * @param {*} value The value to assign. 418 | */ 419 | function assignValue(object, key, value) { 420 | var objValue = object[key]; 421 | if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || 422 | (value === undefined && !(key in object))) { 423 | baseAssignValue(object, key, value); 424 | } 425 | } 426 | 427 | /** 428 | * The base implementation of `assignValue` and `assignMergeValue` without 429 | * value checks. 430 | * 431 | * @private 432 | * @param {Object} object The object to modify. 433 | * @param {string} key The key of the property to assign. 434 | * @param {*} value The value to assign. 435 | */ 436 | function baseAssignValue(object, key, value) { 437 | if (key == '__proto__' && defineProperty) { 438 | defineProperty(object, key, { 439 | 'configurable': true, 440 | 'enumerable': true, 441 | 'value': value, 442 | 'writable': true 443 | }); 444 | } else { 445 | object[key] = value; 446 | } 447 | } 448 | 449 | /** 450 | * The base implementation of `getTag` without fallbacks for buggy environments. 451 | * 452 | * @private 453 | * @param {*} value The value to query. 454 | * @returns {string} Returns the `toStringTag`. 455 | */ 456 | function baseGetTag(value) { 457 | if (value == null) { 458 | return value === undefined ? undefinedTag : nullTag; 459 | } 460 | return (symToStringTag && symToStringTag in Object(value)) 461 | ? getRawTag(value) 462 | : objectToString(value); 463 | } 464 | 465 | /** 466 | * The base implementation of `_.isArguments`. 467 | * 468 | * @private 469 | * @param {*} value The value to check. 470 | * @returns {boolean} Returns `true` if `value` is an `arguments` object, 471 | */ 472 | function baseIsArguments(value) { 473 | return isObjectLike(value) && baseGetTag(value) == argsTag; 474 | } 475 | 476 | /** 477 | * The base implementation of `_.isNative` without bad shim checks. 478 | * 479 | * @private 480 | * @param {*} value The value to check. 481 | * @returns {boolean} Returns `true` if `value` is a native function, 482 | * else `false`. 483 | */ 484 | function baseIsNative(value) { 485 | if (!isObject(value) || isMasked(value)) { 486 | return false; 487 | } 488 | var pattern = isFunction(value) ? reIsNative : reIsHostCtor; 489 | return pattern.test(toSource(value)); 490 | } 491 | 492 | /** 493 | * The base implementation of `_.isTypedArray` without Node.js optimizations. 494 | * 495 | * @private 496 | * @param {*} value The value to check. 497 | * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. 498 | */ 499 | function baseIsTypedArray(value) { 500 | return isObjectLike(value) && 501 | isLength(value.length) && !!typedArrayTags[baseGetTag(value)]; 502 | } 503 | 504 | /** 505 | * The base implementation of `_.keys` which doesn't treat sparse arrays as dense. 506 | * 507 | * @private 508 | * @param {Object} object The object to query. 509 | * @returns {Array} Returns the array of property names. 510 | */ 511 | function baseKeys(object) { 512 | if (!isPrototype(object)) { 513 | return nativeKeys(object); 514 | } 515 | var result = []; 516 | for (var key in Object(object)) { 517 | if (hasOwnProperty.call(object, key) && key != 'constructor') { 518 | result.push(key); 519 | } 520 | } 521 | return result; 522 | } 523 | 524 | /** 525 | * The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense. 526 | * 527 | * @private 528 | * @param {Object} object The object to query. 529 | * @returns {Array} Returns the array of property names. 530 | */ 531 | function baseKeysIn(object) { 532 | if (!isObject(object)) { 533 | return nativeKeysIn(object); 534 | } 535 | var isProto = isPrototype(object), 536 | result = []; 537 | 538 | for (var key in object) { 539 | if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) { 540 | result.push(key); 541 | } 542 | } 543 | return result; 544 | } 545 | 546 | /** 547 | * The base implementation of `_.rest` which doesn't validate or coerce arguments. 548 | * 549 | * @private 550 | * @param {Function} func The function to apply a rest parameter to. 551 | * @param {number} [start=func.length-1] The start position of the rest parameter. 552 | * @returns {Function} Returns the new function. 553 | */ 554 | function baseRest(func, start) { 555 | return setToString(overRest(func, start, identity), func + ''); 556 | } 557 | 558 | /** 559 | * The base implementation of `setToString` without support for hot loop shorting. 560 | * 561 | * @private 562 | * @param {Function} func The function to modify. 563 | * @param {Function} string The `toString` result. 564 | * @returns {Function} Returns `func`. 565 | */ 566 | var baseSetToString = !defineProperty ? identity : function(func, string) { 567 | return defineProperty(func, 'toString', { 568 | 'configurable': true, 569 | 'enumerable': false, 570 | 'value': constant(string), 571 | 'writable': true 572 | }); 573 | }; 574 | 575 | /** 576 | * The base implementation of `_.toString` which doesn't convert nullish 577 | * values to empty strings. 578 | * 579 | * @private 580 | * @param {*} value The value to process. 581 | * @returns {string} Returns the string. 582 | */ 583 | function baseToString(value) { 584 | // Exit early for strings to avoid a performance hit in some environments. 585 | if (typeof value == 'string') { 586 | return value; 587 | } 588 | if (isArray(value)) { 589 | // Recursively convert values (susceptible to call stack limits). 590 | return arrayMap(value, baseToString) + ''; 591 | } 592 | if (isSymbol(value)) { 593 | return symbolToString ? symbolToString.call(value) : ''; 594 | } 595 | var result = (value + ''); 596 | return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result; 597 | } 598 | 599 | /** 600 | * Copies properties of `source` to `object`. 601 | * 602 | * @private 603 | * @param {Object} source The object to copy properties from. 604 | * @param {Array} props The property identifiers to copy. 605 | * @param {Object} [object={}] The object to copy properties to. 606 | * @param {Function} [customizer] The function to customize copied values. 607 | * @returns {Object} Returns `object`. 608 | */ 609 | function copyObject(source, props, object, customizer) { 610 | var isNew = !object; 611 | object || (object = {}); 612 | 613 | var index = -1, 614 | length = props.length; 615 | 616 | while (++index < length) { 617 | var key = props[index]; 618 | 619 | var newValue = customizer 620 | ? customizer(object[key], source[key], key, object, source) 621 | : undefined; 622 | 623 | if (newValue === undefined) { 624 | newValue = source[key]; 625 | } 626 | if (isNew) { 627 | baseAssignValue(object, key, newValue); 628 | } else { 629 | assignValue(object, key, newValue); 630 | } 631 | } 632 | return object; 633 | } 634 | 635 | /** 636 | * Creates a function like `_.assign`. 637 | * 638 | * @private 639 | * @param {Function} assigner The function to assign values. 640 | * @returns {Function} Returns the new assigner function. 641 | */ 642 | function createAssigner(assigner) { 643 | return baseRest(function(object, sources) { 644 | var index = -1, 645 | length = sources.length, 646 | customizer = length > 1 ? sources[length - 1] : undefined, 647 | guard = length > 2 ? sources[2] : undefined; 648 | 649 | customizer = (assigner.length > 3 && typeof customizer == 'function') 650 | ? (length--, customizer) 651 | : undefined; 652 | 653 | if (guard && isIterateeCall(sources[0], sources[1], guard)) { 654 | customizer = length < 3 ? undefined : customizer; 655 | length = 1; 656 | } 657 | object = Object(object); 658 | while (++index < length) { 659 | var source = sources[index]; 660 | if (source) { 661 | assigner(object, source, index, customizer); 662 | } 663 | } 664 | return object; 665 | }); 666 | } 667 | 668 | /** 669 | * Used by `_.defaults` to customize its `_.assignIn` use to assign properties 670 | * of source objects to the destination object for all destination properties 671 | * that resolve to `undefined`. 672 | * 673 | * @private 674 | * @param {*} objValue The destination value. 675 | * @param {*} srcValue The source value. 676 | * @param {string} key The key of the property to assign. 677 | * @param {Object} object The parent object of `objValue`. 678 | * @returns {*} Returns the value to assign. 679 | */ 680 | function customDefaultsAssignIn(objValue, srcValue, key, object) { 681 | if (objValue === undefined || 682 | (eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key))) { 683 | return srcValue; 684 | } 685 | return objValue; 686 | } 687 | 688 | /** 689 | * Gets the native function at `key` of `object`. 690 | * 691 | * @private 692 | * @param {Object} object The object to query. 693 | * @param {string} key The key of the method to get. 694 | * @returns {*} Returns the function if it's native, else `undefined`. 695 | */ 696 | function getNative(object, key) { 697 | var value = getValue(object, key); 698 | return baseIsNative(value) ? value : undefined; 699 | } 700 | 701 | /** 702 | * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values. 703 | * 704 | * @private 705 | * @param {*} value The value to query. 706 | * @returns {string} Returns the raw `toStringTag`. 707 | */ 708 | function getRawTag(value) { 709 | var isOwn = hasOwnProperty.call(value, symToStringTag), 710 | tag = value[symToStringTag]; 711 | 712 | try { 713 | value[symToStringTag] = undefined; 714 | var unmasked = true; 715 | } catch (e) {} 716 | 717 | var result = nativeObjectToString.call(value); 718 | if (unmasked) { 719 | if (isOwn) { 720 | value[symToStringTag] = tag; 721 | } else { 722 | delete value[symToStringTag]; 723 | } 724 | } 725 | return result; 726 | } 727 | 728 | /** 729 | * Checks if `value` is a valid array-like index. 730 | * 731 | * @private 732 | * @param {*} value The value to check. 733 | * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. 734 | * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. 735 | */ 736 | function isIndex(value, length) { 737 | var type = typeof value; 738 | length = length == null ? MAX_SAFE_INTEGER : length; 739 | 740 | return !!length && 741 | (type == 'number' || 742 | (type != 'symbol' && reIsUint.test(value))) && 743 | (value > -1 && value % 1 == 0 && value < length); 744 | } 745 | 746 | /** 747 | * Checks if the given arguments are from an iteratee call. 748 | * 749 | * @private 750 | * @param {*} value The potential iteratee value argument. 751 | * @param {*} index The potential iteratee index or key argument. 752 | * @param {*} object The potential iteratee object argument. 753 | * @returns {boolean} Returns `true` if the arguments are from an iteratee call, 754 | * else `false`. 755 | */ 756 | function isIterateeCall(value, index, object) { 757 | if (!isObject(object)) { 758 | return false; 759 | } 760 | var type = typeof index; 761 | if (type == 'number' 762 | ? (isArrayLike(object) && isIndex(index, object.length)) 763 | : (type == 'string' && index in object) 764 | ) { 765 | return eq(object[index], value); 766 | } 767 | return false; 768 | } 769 | 770 | /** 771 | * Checks if `func` has its source masked. 772 | * 773 | * @private 774 | * @param {Function} func The function to check. 775 | * @returns {boolean} Returns `true` if `func` is masked, else `false`. 776 | */ 777 | function isMasked(func) { 778 | return !!maskSrcKey && (maskSrcKey in func); 779 | } 780 | 781 | /** 782 | * Checks if `value` is likely a prototype object. 783 | * 784 | * @private 785 | * @param {*} value The value to check. 786 | * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. 787 | */ 788 | function isPrototype(value) { 789 | var Ctor = value && value.constructor, 790 | proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto; 791 | 792 | return value === proto; 793 | } 794 | 795 | /** 796 | * This function is like 797 | * [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) 798 | * except that it includes inherited enumerable properties. 799 | * 800 | * @private 801 | * @param {Object} object The object to query. 802 | * @returns {Array} Returns the array of property names. 803 | */ 804 | function nativeKeysIn(object) { 805 | var result = []; 806 | if (object != null) { 807 | for (var key in Object(object)) { 808 | result.push(key); 809 | } 810 | } 811 | return result; 812 | } 813 | 814 | /** 815 | * Converts `value` to a string using `Object.prototype.toString`. 816 | * 817 | * @private 818 | * @param {*} value The value to convert. 819 | * @returns {string} Returns the converted string. 820 | */ 821 | function objectToString(value) { 822 | return nativeObjectToString.call(value); 823 | } 824 | 825 | /** 826 | * A specialized version of `baseRest` which transforms the rest array. 827 | * 828 | * @private 829 | * @param {Function} func The function to apply a rest parameter to. 830 | * @param {number} [start=func.length-1] The start position of the rest parameter. 831 | * @param {Function} transform The rest array transform. 832 | * @returns {Function} Returns the new function. 833 | */ 834 | function overRest(func, start, transform) { 835 | start = nativeMax(start === undefined ? (func.length - 1) : start, 0); 836 | return function() { 837 | var args = arguments, 838 | index = -1, 839 | length = nativeMax(args.length - start, 0), 840 | array = Array(length); 841 | 842 | while (++index < length) { 843 | array[index] = args[start + index]; 844 | } 845 | index = -1; 846 | var otherArgs = Array(start + 1); 847 | while (++index < start) { 848 | otherArgs[index] = args[index]; 849 | } 850 | otherArgs[start] = transform(array); 851 | return apply(func, this, otherArgs); 852 | }; 853 | } 854 | 855 | /** 856 | * Sets the `toString` method of `func` to return `string`. 857 | * 858 | * @private 859 | * @param {Function} func The function to modify. 860 | * @param {Function} string The `toString` result. 861 | * @returns {Function} Returns `func`. 862 | */ 863 | var setToString = shortOut(baseSetToString); 864 | 865 | /** 866 | * Creates a function that'll short out and invoke `identity` instead 867 | * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN` 868 | * milliseconds. 869 | * 870 | * @private 871 | * @param {Function} func The function to restrict. 872 | * @returns {Function} Returns the new shortable function. 873 | */ 874 | function shortOut(func) { 875 | var count = 0, 876 | lastCalled = 0; 877 | 878 | return function() { 879 | var stamp = nativeNow(), 880 | remaining = HOT_SPAN - (stamp - lastCalled); 881 | 882 | lastCalled = stamp; 883 | if (remaining > 0) { 884 | if (++count >= HOT_COUNT) { 885 | return arguments[0]; 886 | } 887 | } else { 888 | count = 0; 889 | } 890 | return func.apply(undefined, arguments); 891 | }; 892 | } 893 | 894 | /** 895 | * Converts `func` to its source code. 896 | * 897 | * @private 898 | * @param {Function} func The function to convert. 899 | * @returns {string} Returns the source code. 900 | */ 901 | function toSource(func) { 902 | if (func != null) { 903 | try { 904 | return funcToString.call(func); 905 | } catch (e) {} 906 | try { 907 | return (func + ''); 908 | } catch (e) {} 909 | } 910 | return ''; 911 | } 912 | 913 | /** 914 | * Performs a 915 | * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero) 916 | * comparison between two values to determine if they are equivalent. 917 | * 918 | * @static 919 | * @memberOf _ 920 | * @since 4.0.0 921 | * @category Lang 922 | * @param {*} value The value to compare. 923 | * @param {*} other The other value to compare. 924 | * @returns {boolean} Returns `true` if the values are equivalent, else `false`. 925 | * @example 926 | * 927 | * var object = { 'a': 1 }; 928 | * var other = { 'a': 1 }; 929 | * 930 | * _.eq(object, object); 931 | * // => true 932 | * 933 | * _.eq(object, other); 934 | * // => false 935 | * 936 | * _.eq('a', 'a'); 937 | * // => true 938 | * 939 | * _.eq('a', Object('a')); 940 | * // => false 941 | * 942 | * _.eq(NaN, NaN); 943 | * // => true 944 | */ 945 | function eq(value, other) { 946 | return value === other || (value !== value && other !== other); 947 | } 948 | 949 | /** 950 | * Checks if `value` is likely an `arguments` object. 951 | * 952 | * @static 953 | * @memberOf _ 954 | * @since 0.1.0 955 | * @category Lang 956 | * @param {*} value The value to check. 957 | * @returns {boolean} Returns `true` if `value` is an `arguments` object, 958 | * else `false`. 959 | * @example 960 | * 961 | * _.isArguments(function() { return arguments; }()); 962 | * // => true 963 | * 964 | * _.isArguments([1, 2, 3]); 965 | * // => false 966 | */ 967 | var isArguments = baseIsArguments(function() { return arguments; }()) ? baseIsArguments : function(value) { 968 | return isObjectLike(value) && hasOwnProperty.call(value, 'callee') && 969 | !propertyIsEnumerable.call(value, 'callee'); 970 | }; 971 | 972 | /** 973 | * Checks if `value` is classified as an `Array` object. 974 | * 975 | * @static 976 | * @memberOf _ 977 | * @since 0.1.0 978 | * @category Lang 979 | * @param {*} value The value to check. 980 | * @returns {boolean} Returns `true` if `value` is an array, else `false`. 981 | * @example 982 | * 983 | * _.isArray([1, 2, 3]); 984 | * // => true 985 | * 986 | * _.isArray(document.body.children); 987 | * // => false 988 | * 989 | * _.isArray('abc'); 990 | * // => false 991 | * 992 | * _.isArray(_.noop); 993 | * // => false 994 | */ 995 | var isArray = Array.isArray; 996 | 997 | /** 998 | * Checks if `value` is array-like. A value is considered array-like if it's 999 | * not a function and has a `value.length` that's an integer greater than or 1000 | * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. 1001 | * 1002 | * @static 1003 | * @memberOf _ 1004 | * @since 4.0.0 1005 | * @category Lang 1006 | * @param {*} value The value to check. 1007 | * @returns {boolean} Returns `true` if `value` is array-like, else `false`. 1008 | * @example 1009 | * 1010 | * _.isArrayLike([1, 2, 3]); 1011 | * // => true 1012 | * 1013 | * _.isArrayLike(document.body.children); 1014 | * // => true 1015 | * 1016 | * _.isArrayLike('abc'); 1017 | * // => true 1018 | * 1019 | * _.isArrayLike(_.noop); 1020 | * // => false 1021 | */ 1022 | function isArrayLike(value) { 1023 | return value != null && isLength(value.length) && !isFunction(value); 1024 | } 1025 | 1026 | /** 1027 | * Checks if `value` is a buffer. 1028 | * 1029 | * @static 1030 | * @memberOf _ 1031 | * @since 4.3.0 1032 | * @category Lang 1033 | * @param {*} value The value to check. 1034 | * @returns {boolean} Returns `true` if `value` is a buffer, else `false`. 1035 | * @example 1036 | * 1037 | * _.isBuffer(new Buffer(2)); 1038 | * // => true 1039 | * 1040 | * _.isBuffer(new Uint8Array(2)); 1041 | * // => false 1042 | */ 1043 | var isBuffer = nativeIsBuffer || stubFalse; 1044 | 1045 | /** 1046 | * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`, 1047 | * `SyntaxError`, `TypeError`, or `URIError` object. 1048 | * 1049 | * @static 1050 | * @memberOf _ 1051 | * @since 3.0.0 1052 | * @category Lang 1053 | * @param {*} value The value to check. 1054 | * @returns {boolean} Returns `true` if `value` is an error object, else `false`. 1055 | * @example 1056 | * 1057 | * _.isError(new Error); 1058 | * // => true 1059 | * 1060 | * _.isError(Error); 1061 | * // => false 1062 | */ 1063 | function isError(value) { 1064 | if (!isObjectLike(value)) { 1065 | return false; 1066 | } 1067 | var tag = baseGetTag(value); 1068 | return tag == errorTag || tag == domExcTag || 1069 | (typeof value.message == 'string' && typeof value.name == 'string' && !isPlainObject(value)); 1070 | } 1071 | 1072 | /** 1073 | * Checks if `value` is classified as a `Function` object. 1074 | * 1075 | * @static 1076 | * @memberOf _ 1077 | * @since 0.1.0 1078 | * @category Lang 1079 | * @param {*} value The value to check. 1080 | * @returns {boolean} Returns `true` if `value` is a function, else `false`. 1081 | * @example 1082 | * 1083 | * _.isFunction(_); 1084 | * // => true 1085 | * 1086 | * _.isFunction(/abc/); 1087 | * // => false 1088 | */ 1089 | function isFunction(value) { 1090 | if (!isObject(value)) { 1091 | return false; 1092 | } 1093 | // The use of `Object#toString` avoids issues with the `typeof` operator 1094 | // in Safari 9 which returns 'object' for typed arrays and other constructors. 1095 | var tag = baseGetTag(value); 1096 | return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag; 1097 | } 1098 | 1099 | /** 1100 | * Checks if `value` is a valid array-like length. 1101 | * 1102 | * **Note:** This method is loosely based on 1103 | * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength). 1104 | * 1105 | * @static 1106 | * @memberOf _ 1107 | * @since 4.0.0 1108 | * @category Lang 1109 | * @param {*} value The value to check. 1110 | * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. 1111 | * @example 1112 | * 1113 | * _.isLength(3); 1114 | * // => true 1115 | * 1116 | * _.isLength(Number.MIN_VALUE); 1117 | * // => false 1118 | * 1119 | * _.isLength(Infinity); 1120 | * // => false 1121 | * 1122 | * _.isLength('3'); 1123 | * // => false 1124 | */ 1125 | function isLength(value) { 1126 | return typeof value == 'number' && 1127 | value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; 1128 | } 1129 | 1130 | /** 1131 | * Checks if `value` is the 1132 | * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) 1133 | * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) 1134 | * 1135 | * @static 1136 | * @memberOf _ 1137 | * @since 0.1.0 1138 | * @category Lang 1139 | * @param {*} value The value to check. 1140 | * @returns {boolean} Returns `true` if `value` is an object, else `false`. 1141 | * @example 1142 | * 1143 | * _.isObject({}); 1144 | * // => true 1145 | * 1146 | * _.isObject([1, 2, 3]); 1147 | * // => true 1148 | * 1149 | * _.isObject(_.noop); 1150 | * // => true 1151 | * 1152 | * _.isObject(null); 1153 | * // => false 1154 | */ 1155 | function isObject(value) { 1156 | var type = typeof value; 1157 | return value != null && (type == 'object' || type == 'function'); 1158 | } 1159 | 1160 | /** 1161 | * Checks if `value` is object-like. A value is object-like if it's not `null` 1162 | * and has a `typeof` result of "object". 1163 | * 1164 | * @static 1165 | * @memberOf _ 1166 | * @since 4.0.0 1167 | * @category Lang 1168 | * @param {*} value The value to check. 1169 | * @returns {boolean} Returns `true` if `value` is object-like, else `false`. 1170 | * @example 1171 | * 1172 | * _.isObjectLike({}); 1173 | * // => true 1174 | * 1175 | * _.isObjectLike([1, 2, 3]); 1176 | * // => true 1177 | * 1178 | * _.isObjectLike(_.noop); 1179 | * // => false 1180 | * 1181 | * _.isObjectLike(null); 1182 | * // => false 1183 | */ 1184 | function isObjectLike(value) { 1185 | return value != null && typeof value == 'object'; 1186 | } 1187 | 1188 | /** 1189 | * Checks if `value` is a plain object, that is, an object created by the 1190 | * `Object` constructor or one with a `[[Prototype]]` of `null`. 1191 | * 1192 | * @static 1193 | * @memberOf _ 1194 | * @since 0.8.0 1195 | * @category Lang 1196 | * @param {*} value The value to check. 1197 | * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. 1198 | * @example 1199 | * 1200 | * function Foo() { 1201 | * this.a = 1; 1202 | * } 1203 | * 1204 | * _.isPlainObject(new Foo); 1205 | * // => false 1206 | * 1207 | * _.isPlainObject([1, 2, 3]); 1208 | * // => false 1209 | * 1210 | * _.isPlainObject({ 'x': 0, 'y': 0 }); 1211 | * // => true 1212 | * 1213 | * _.isPlainObject(Object.create(null)); 1214 | * // => true 1215 | */ 1216 | function isPlainObject(value) { 1217 | if (!isObjectLike(value) || baseGetTag(value) != objectTag) { 1218 | return false; 1219 | } 1220 | var proto = getPrototype(value); 1221 | if (proto === null) { 1222 | return true; 1223 | } 1224 | var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor; 1225 | return typeof Ctor == 'function' && Ctor instanceof Ctor && 1226 | funcToString.call(Ctor) == objectCtorString; 1227 | } 1228 | 1229 | /** 1230 | * Checks if `value` is classified as a `Symbol` primitive or object. 1231 | * 1232 | * @static 1233 | * @memberOf _ 1234 | * @since 4.0.0 1235 | * @category Lang 1236 | * @param {*} value The value to check. 1237 | * @returns {boolean} Returns `true` if `value` is a symbol, else `false`. 1238 | * @example 1239 | * 1240 | * _.isSymbol(Symbol.iterator); 1241 | * // => true 1242 | * 1243 | * _.isSymbol('abc'); 1244 | * // => false 1245 | */ 1246 | function isSymbol(value) { 1247 | return typeof value == 'symbol' || 1248 | (isObjectLike(value) && baseGetTag(value) == symbolTag); 1249 | } 1250 | 1251 | /** 1252 | * Checks if `value` is classified as a typed array. 1253 | * 1254 | * @static 1255 | * @memberOf _ 1256 | * @since 3.0.0 1257 | * @category Lang 1258 | * @param {*} value The value to check. 1259 | * @returns {boolean} Returns `true` if `value` is a typed array, else `false`. 1260 | * @example 1261 | * 1262 | * _.isTypedArray(new Uint8Array); 1263 | * // => true 1264 | * 1265 | * _.isTypedArray([]); 1266 | * // => false 1267 | */ 1268 | var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray; 1269 | 1270 | /** 1271 | * Converts `value` to a string. An empty string is returned for `null` 1272 | * and `undefined` values. The sign of `-0` is preserved. 1273 | * 1274 | * @static 1275 | * @memberOf _ 1276 | * @since 4.0.0 1277 | * @category Lang 1278 | * @param {*} value The value to convert. 1279 | * @returns {string} Returns the converted string. 1280 | * @example 1281 | * 1282 | * _.toString(null); 1283 | * // => '' 1284 | * 1285 | * _.toString(-0); 1286 | * // => '-0' 1287 | * 1288 | * _.toString([1, 2, 3]); 1289 | * // => '1,2,3' 1290 | */ 1291 | function toString(value) { 1292 | return value == null ? '' : baseToString(value); 1293 | } 1294 | 1295 | /** 1296 | * This method is like `_.assignIn` except that it accepts `customizer` 1297 | * which is invoked to produce the assigned values. If `customizer` returns 1298 | * `undefined`, assignment is handled by the method instead. The `customizer` 1299 | * is invoked with five arguments: (objValue, srcValue, key, object, source). 1300 | * 1301 | * **Note:** This method mutates `object`. 1302 | * 1303 | * @static 1304 | * @memberOf _ 1305 | * @since 4.0.0 1306 | * @alias extendWith 1307 | * @category Object 1308 | * @param {Object} object The destination object. 1309 | * @param {...Object} sources The source objects. 1310 | * @param {Function} [customizer] The function to customize assigned values. 1311 | * @returns {Object} Returns `object`. 1312 | * @see _.assignWith 1313 | * @example 1314 | * 1315 | * function customizer(objValue, srcValue) { 1316 | * return _.isUndefined(objValue) ? srcValue : objValue; 1317 | * } 1318 | * 1319 | * var defaults = _.partialRight(_.assignInWith, customizer); 1320 | * 1321 | * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 }); 1322 | * // => { 'a': 1, 'b': 2 } 1323 | */ 1324 | var assignInWith = createAssigner(function(object, source, srcIndex, customizer) { 1325 | copyObject(source, keysIn(source), object, customizer); 1326 | }); 1327 | 1328 | /** 1329 | * Creates an array of the own enumerable property names of `object`. 1330 | * 1331 | * **Note:** Non-object values are coerced to objects. See the 1332 | * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys) 1333 | * for more details. 1334 | * 1335 | * @static 1336 | * @since 0.1.0 1337 | * @memberOf _ 1338 | * @category Object 1339 | * @param {Object} object The object to query. 1340 | * @returns {Array} Returns the array of property names. 1341 | * @example 1342 | * 1343 | * function Foo() { 1344 | * this.a = 1; 1345 | * this.b = 2; 1346 | * } 1347 | * 1348 | * Foo.prototype.c = 3; 1349 | * 1350 | * _.keys(new Foo); 1351 | * // => ['a', 'b'] (iteration order is not guaranteed) 1352 | * 1353 | * _.keys('hi'); 1354 | * // => ['0', '1'] 1355 | */ 1356 | function keys(object) { 1357 | return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object); 1358 | } 1359 | 1360 | /** 1361 | * Creates an array of the own and inherited enumerable property names of `object`. 1362 | * 1363 | * **Note:** Non-object values are coerced to objects. 1364 | * 1365 | * @static 1366 | * @memberOf _ 1367 | * @since 3.0.0 1368 | * @category Object 1369 | * @param {Object} object The object to query. 1370 | * @returns {Array} Returns the array of property names. 1371 | * @example 1372 | * 1373 | * function Foo() { 1374 | * this.a = 1; 1375 | * this.b = 2; 1376 | * } 1377 | * 1378 | * Foo.prototype.c = 3; 1379 | * 1380 | * _.keysIn(new Foo); 1381 | * // => ['a', 'b', 'c'] (iteration order is not guaranteed) 1382 | */ 1383 | function keysIn(object) { 1384 | return isArrayLike(object) ? arrayLikeKeys(object, true) : baseKeysIn(object); 1385 | } 1386 | 1387 | /** 1388 | * Creates a compiled template function that can interpolate data properties 1389 | * in "interpolate" delimiters, HTML-escape interpolated data properties in 1390 | * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data 1391 | * properties may be accessed as free variables in the template. If a setting 1392 | * object is given, it takes precedence over `_.templateSettings` values. 1393 | * 1394 | * **Note:** In the development build `_.template` utilizes 1395 | * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) 1396 | * for easier debugging. 1397 | * 1398 | * For more information on precompiling templates see 1399 | * [lodash's custom builds documentation](https://lodash.com/custom-builds). 1400 | * 1401 | * For more information on Chrome extension sandboxes see 1402 | * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval). 1403 | * 1404 | * @static 1405 | * @since 0.1.0 1406 | * @memberOf _ 1407 | * @category String 1408 | * @param {string} [string=''] The template string. 1409 | * @param {Object} [options={}] The options object. 1410 | * @param {RegExp} [options.escape=_.templateSettings.escape] 1411 | * The HTML "escape" delimiter. 1412 | * @param {RegExp} [options.evaluate=_.templateSettings.evaluate] 1413 | * The "evaluate" delimiter. 1414 | * @param {Object} [options.imports=_.templateSettings.imports] 1415 | * An object to import into the template as free variables. 1416 | * @param {RegExp} [options.interpolate=_.templateSettings.interpolate] 1417 | * The "interpolate" delimiter. 1418 | * @param {string} [options.sourceURL='templateSources[n]'] 1419 | * The sourceURL of the compiled template. 1420 | * @param {string} [options.variable='obj'] 1421 | * The data object variable name. 1422 | * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`. 1423 | * @returns {Function} Returns the compiled template function. 1424 | * @example 1425 | * 1426 | * // Use the "interpolate" delimiter to create a compiled template. 1427 | * var compiled = _.template('hello <%= user %>!'); 1428 | * compiled({ 'user': 'fred' }); 1429 | * // => 'hello fred!' 1430 | * 1431 | * // Use the HTML "escape" delimiter to escape data property values. 1432 | * var compiled = _.template('<%- value %>'); 1433 | * compiled({ 'value': ' 17 | 18 | 19 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 | © 2018 Nick Freear | License: MIT.
40 | 
41 | 
42 | 43 | -------------------------------------------------------------------------------- /example/historic.html: -------------------------------------------------------------------------------- 1 | Historic UK audio-player example 2 | 3 | 4 | 5 | 6 |

Historic UK audio-player example

7 | 8 |

The example audio files are (mostly) OGG, so you'll need Firefox, Chrome or Opera.

9 | 10 | 11 |

12 | 13 | 14 | 21 | 22 | 23 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 | © 2016 Nick Freear | License: MIT.
43 | 
44 | * http://maps.nls.uk/projects/api/
45 | 
46 | -------------------------------------------------------------------------------- /example/resume.html: -------------------------------------------------------------------------------- 1 | Map-based résumé example 2 | 3 | 4 | 5 | 6 |

Map-based résumé example

7 | 8 |

Example of a CV or résumé for a globe-trotting chef, based on a world map!

9 | 10 | 11 |

12 | 13 | 14 | 22 | 23 | 24 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 | © 2016 Nick Freear | License: MIT.
45 | 
46 | * Data: https://gist.github.com/nfreear/cceecc6e1cabdf8f8f4302aaed10923d
47 | 
48 | * Note: this example uses Underscore.JS instead of Lodash, to cope with
49 |   GeoJSON feature-properties containing spaces.
50 | 
51 | -------------------------------------------------------------------------------- /example/world.html: -------------------------------------------------------------------------------- 1 | World audio-player example 2 | 3 | 4 | 5 | 6 |

World audio-player example

7 | 8 |

The example audio files are (mostly) OGG, so you'll need Firefox, Chrome or Opera.

9 | 10 | 11 |

12 | 13 | 14 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |

33 | 


--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
 1 | /*!
 2 |   geojson-popup Javascript.
 3 | 
 4 |   © Nick Freear, 2016-09-26 | License: MIT.
 5 | */
 6 | 
 7 | 'use strict';
 8 | 
 9 | const VERSION = '2.4.0'; // 
10 | 
11 | const utils = require('./src/utils');
12 | const lodashish = {
13 |   template: require('lodash.template'),
14 |   extend: utils.extend,
15 |   cdn: utils.cdn
16 | };
17 | const window = global; // || window;
18 | 
19 | require('./src/popup-geojson-map')(window, lodashish, VERSION);
20 | 


--------------------------------------------------------------------------------
/npm-deprecate/README.md:
--------------------------------------------------------------------------------
 1 | 
 2 | [![Build status: Travis-CI][travis-icon]][travis-ci]
 3 | [![geojson-popup on Npmjs][npm-icon]][npm]
 4 | 
 5 | # popup-geojson-map
 6 | 
 7 | > WARNING: This project has been renamed to [geojson-popup][npm].
 8 | > Please install using __geojson-popup__ instead.
 9 | 
10 | ## Renamed
11 | 
12 | I'm renaming the NPM package from [`popup-geojson-map`][npm-old] to the clearer [`geojson-popup`][npm].
13 | (I'll probably re-name the GitHub repo. too.) _Sorry for any hassle!_
14 | 
15 | ---
16 | 
17 | Add GeoJSON-based templated popups to a Leaflet map. See the audio-player example(s).
18 | 
19 | Easily create interactive maps, with popups containing structured data, for example, audio players.
20 | 
21 | * GitHub: [nfreear/popup-geojson-map][]
22 | 
23 | ---
24 | © 2016-2017 Nick Freear | License: [MIT][].
25 | 
26 | 
27 | [npm-old]: https://npmjs.com/package/popup-geojson-map "Old"
28 | [npm]: https://npmjs.com/package/geojson-popup "Renamed: 'geojson-popup' ~ on NPM"
29 | [npm-icon]: https://img.shields.io/npm/v/geojson-popup.svg
30 | 
31 | [gh]: https://github.com/nfreear/popup-geojson-map
32 | [nfreear/popup-geojson-map]: https://github.com/nfreear/popup-geojson-map
33 | 
34 | [MIT]: https://nfreear.mit-license.org/2016-2017 "MIT License | © 2016-2017 Nick Freear (date: 2016-09-26)"
35 | [travis-icon]: https://travis-ci.org/nfreear/popup-geojson-map.svg
36 | [travis-ci]: https://travis-ci.org/nfreear/popup-geojson-map "Build status – Travis-CI"
37 | 
38 | [RFC]: https://tools.ietf.org/html/rfc7946 "The GeoJSON Format, August 2016."
39 | [GeoJSON]: http://geojson.org/
40 | [Leaflet.JS]: http://leafletjs.com/examples/geojson.html
41 | 
42 | [end]: //end
43 | 


--------------------------------------------------------------------------------
/npm-deprecate/index.js:
--------------------------------------------------------------------------------
1 | /*!
2 |   Re-named.
3 |   © Nick Freear, 26-June-2017. | License: MIT.
4 | */
5 | 
6 | console.warn("\nWARNING: This project has been renamed to 'geojson-popup'.")
7 | console.warn("         Please install using 'geojson-popup' instead.\n")
8 | console.warn('         https://npmjs.com/package/geojson-popup\n')
9 | 


--------------------------------------------------------------------------------
/npm-deprecate/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "popup-geojson-map",
 3 |   "x-deprecated-name": "popup-geojson-map",
 4 |   "x-deprecated-time": "2017-06-26T13:38:58.000Z",
 5 |   "version": "1.0.0-rc.3",
 6 |   "homepage": "https://npmjs.com/package/geojson-popup",
 7 |   "description": "(WARNING: This project has been renamed to 'geojson-popup'. Please install using 'geojson-popup' instead.)",
 8 |   "license": "MIT",
 9 |   "main": "index.js",
10 |   "author": "Nick Freear {@nfreear}",
11 |   "keywords": "geojson, map, leaflet, popup, popup-geojson-map, geojson-popup",
12 |   "repository": "https://github.com/nfreear/popup-geojson-map"
13 | }
14 | 


--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "geojson-popup",
 3 |   "# Deprecated name": "popup-geojson-map",
 4 |   "version": "2.4.0",
 5 |   "description": "Add GeoJSON-based templated popups to your Leaflet map. See the audio-player example.",
 6 |   "license": "MIT",
 7 |   "unpkg": "dist/geojson-popup.js",
 8 |   "X-browser": "dist/geojson-popup.js",
 9 |   "main": "index.js",
10 |   "author": "Nick Freear {@nfreear}",
11 |   "keywords": "geojson, map, leaflet, audio, lodash, underscore, template, popup, freesound, commons, mapbox, popup-geojson-map",
12 |   "repository": "https://github.com/nfreear/popup-geojson-map",
13 |   "bugs": "https://github.com/nfreear/popup-geojson-map/issues",
14 |   "homepage": "https://nick.freear.org.uk/2017/06/27/geojson-popup-leaflet.html?utm_source=npm",
15 |   "dependencies": {
16 |     "lodash.template": "^4.5.0"
17 |   },
18 |   "peerDependencies": {
19 |     "leaflet": "1.7.1",
20 |     "@mapbox/maki": "4.0.0"
21 |   },
22 |   "devDependencies": {
23 |     "browserify": "^16.5.2",
24 |     "exorcist": "^1.0.1",
25 |     "live-server": "^1.2.1",
26 |     "replace": "^1.2.0",
27 |     "semistandard": "^16.0.0",
28 |     "uglify-js": "^3.12.8"
29 |   },
30 |   "files": [
31 |     "index.js",
32 |     "dist/",
33 |     "src/",
34 |     "data/",
35 |     "example/"
36 |   ],
37 |   "engines": {
38 |     "node": ">= 11.0"
39 |   },
40 |   "scripts": {
41 |     "build": "bin/_ver.js && npm run browserify && npm run uglify",
42 |     "clean": "rm dist/*",
43 |     "browserify": "browserify index.js --debug | exorcist dist/geojson-popup.js.map > dist/geojson-popup.js",
44 |     "uglify": "uglifyjs dist/geojson-popup.js -mco dist/geojson-popup.min.js",
45 |     "copy-env": "cp -n .env-example.js .env.js",
46 |     "start": "live-server --port=9001 -V",
47 |     "envjslint-ci": "node bin/envjslint.js",
48 |     "envjslint": "cat .env.js | pcregrep -M '{\\n.*\\n.*\\n}' | jsonlint-cli",
49 |     "_OLD_jsonl": "jsonlint-cli data/*",
50 |     "eslint": "eslint index.js src/*.js bin/*.js !.env.js",
51 |     "fix": "semistandard --fix",
52 |     "test": "semistandard # && npm run jsonl"
53 |   },
54 |   "semistandard": {
55 |     "ignore": "dist/"
56 |   },
57 |   "x-build-js-size": "63kB",
58 |   "# OLD": {
59 |     "uncomment-cli": "git+https://github.com/nfreear/uncomment-cli.git#v0.9.0",
60 |     "jsonlint-cli": "^1.0.1",
61 |     "node-static": "^0.7.10"
62 |   }
63 | }
64 | 


--------------------------------------------------------------------------------
/src/popup-geojson-map.js:
--------------------------------------------------------------------------------
  1 | /*!
  2 |   popup-geojson-map Javascript.
  3 | 
  4 |   © Nick Freear, 2016-09-26 | License: MIT.
  5 | */
  6 | 
  7 | module.exports = (WIN, lodashish, VERSION) => {
  8 |   'use strict';
  9 | 
 10 |   WIN = WIN || window;
 11 | 
 12 |   const L = WIN.L; // Leaflet
 13 |   const fetch = window.fetch;
 14 |   const ENV = WIN.ENV || { ENV: {} };
 15 | 
 16 |   const defaults = {
 17 |     latLng: [51.505, -0.09], // London, UK!
 18 |     zoom: 3,
 19 |     minZoom: 2,
 20 |     maxZoom: 12, // Was: 6, 18,
 21 |     opacity: 1,
 22 |     mapId: 'mapid',
 23 |     popupTemplate: '#popup-template',
 24 |     templateSettings: {},
 25 |     checkProperty: 'audio_url',
 26 |     geoJson: '{cdn}/data/world-audio-geo.json',
 27 |     tileUrl: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
 28 |     attribution: '© OpenStreetMap',
 29 |     subdomains: 'abc',
 30 |     // tileUrl: 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png',
 31 |     // tileUrl: 'https://api.mapbox.com/styles/v1/mapbox/dark-v9/tiles/256/{z}/{x}/{y}?access_token={accessToken}',
 32 |     // tileUrl: 'https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}'
 33 |     // subdomains: '0123',
 34 |     // attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © Mapbox',
 35 |     lodashish: true,
 36 |     icon: 'default', // 'default', 'maki' or 'div'
 37 |     iconUrl: 'https://unpkg.com/@mapbox/maki@4.0.0/icons/{icon}-15.svg',
 38 |     cdn: 'https://unpkg.com/geojson-popup@' + VERSION,
 39 |     accessToken: '<%= ENV.ACCESS_TOKEN %>' // No access token required for OpenStreetMap!
 40 |   };
 41 | 
 42 |   const CFG = lodashish.extend(defaults, WIN.MAP_CFG); // Order is significant!
 43 |   const _ = CFG.lodashish ? lodashish : WIN._;
 44 | 
 45 |   CFG.version = VERSION;
 46 | 
 47 |   if (typeof CFG.popupTemplate === 'string') {
 48 |     CFG.popupTemplate = document.querySelector(CFG.popupTemplate).innerText;
 49 |   }
 50 | 
 51 |   console.debug('Map config:', CFG);
 52 | 
 53 |   const mymap = L.map(CFG.mapId).setView(CFG.latLng, CFG.zoom);
 54 |   const popupTemplateFn = _.template(CFG.popupTemplate, null, CFG.templateSettings);
 55 |   const accessToken = _.template(CFG.accessToken);
 56 | 
 57 |   L.tileLayer(CFG.tileUrl, {
 58 |     subdomains: CFG.subdomains,
 59 |     attribution: CFG.attribution,
 60 |     maxZoom: CFG.maxZoom,
 61 |     minZoom: CFG.minZoom,
 62 |     // Not needed! //id: 'your.mapbox.project.id',
 63 |     accessToken: accessToken(ENV)
 64 |   }).addTo(mymap);
 65 | 
 66 |   fetch(lodashish.cdn(CFG))
 67 |     .then(response => response.json())
 68 |     .then(geoData => {
 69 |       console.debug('GeoJSON:', geoData);
 70 | 
 71 |       L.geoJson(geoData, {
 72 |         pointToLayer: (/* geoJsonPoint */ point, latlng) => {
 73 |           if (CFG.icon === 'default') {
 74 |             return L.marker(latlng);
 75 |           }
 76 | 
 77 |           const props = point.properties;
 78 |           const icon = props['marker-symbol'];
 79 |           const cls = props['marker-class'] || '';
 80 |           const html = props['marker-html'] || '';
 81 |           const clsName = 'icon-{icon} icon-{cls}'.replace('{icon}', icon).replace('{cls}', cls);
 82 | 
 83 |           console.warn('Point:', point);
 84 | 
 85 |           if (CFG.icon === 'div') {
 86 |             return L.marker(latlng, { icon: L.divIcon({ className: clsName, html: html }) });
 87 |           }
 88 | 
 89 |           const makiIcon = L.icon({
 90 |             iconSize: [18, 18], // [ 27, 27 ],
 91 |             iconAnchor: [9, 18], // [ 13, 27 ],
 92 |             popupAnchor: [1, -19], // [ 1, -24 ],
 93 |             iconUrl: CFG.iconUrl.replace('{icon}', icon),
 94 |             className: clsName
 95 |           });
 96 |           return L.marker(latlng, { icon: makiIcon });
 97 |         },
 98 | 
 99 |         onEachFeature: (feature, layer) => {
100 |           if (feature.properties && feature.properties[CFG.checkProperty]) {
101 |             layer.bindPopup(popupTemplateFn(feature.properties));
102 |           } else if (feature.properties && feature.properties.popupContent) {
103 |             layer.bindPopup(feature.properties.popupContent);
104 |           }
105 |         }
106 |       }).addTo(mymap);
107 |     })
108 |     .catch(error => console.error('HTTP error.', error, CFG.geoJson));
109 | };
110 | 


--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
 1 | /**
 2 |  *  Utility functions.
 3 |  *
 4 |  *  © Nick Freear, 2017 | License: MIT.
 5 |  */
 6 | 
 7 | module.exports = {
 8 | 
 9 |   extend: extend,
10 | 
11 |   cdn: (CFG) => {
12 |     const orig = CFG.geoJsonOrig = CFG.geoJson;
13 | 
14 |     CFG.geoJson = orig.replace('{cdn}', CFG.cdn);
15 |     return CFG.geoJson;
16 |   }
17 | };
18 | 
19 | // JuhQ (16 July 2015): https://gist.github.com/pbojinov/8f3765b672efec122f66#gistcomment-1493930
20 | function extend () {
21 |   const extended = {};
22 |   let key;
23 |   let prop;
24 | 
25 |   for (key in arguments) {
26 |     const argument = arguments[key];
27 |     for (prop in argument) {
28 |       if (Object.prototype.hasOwnProperty.call(argument, prop)) {
29 |         extended[prop] = argument[prop];
30 |       }
31 |     }
32 |   }
33 |   return extended;
34 | }
35 | 


--------------------------------------------------------------------------------