Skip to content

Commit

Permalink
[chore] move umap utils to a module
Browse files Browse the repository at this point in the history
Allow the tests to be run from inside a cli, without requiring a browser.
  • Loading branch information
almet committed Mar 28, 2024
1 parent 998732b commit 10ccd4e
Show file tree
Hide file tree
Showing 20 changed files with 418 additions and 382 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@
"@tmcw/togeojson": "^5.8.0",
"colorbrewer": "^1.5.6",
"csv2geojson": "5.1.1",
"dompurify": "^3.0.3",
"dompurify": "^3.0.11",
"georsstogeojson": "^0.1.0",
"jsdom": "^24.0.0",
"leaflet": "1.9.4",
"leaflet-contextmenu": "^1.4.0",
"leaflet-editable": "^1.2.0",
Expand Down
2 changes: 1 addition & 1 deletion scripts/vendorsjs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ mkdir -p umap/static/umap/vendors/georsstogeojson/ && cp -r node_modules/georsst
mkdir -p umap/static/umap/vendors/togpx/ && cp -r node_modules/togpx/togpx.js umap/static/umap/vendors/togpx/
mkdir -p umap/static/umap/vendors/tokml && cp -r node_modules/tokml/tokml.js umap/static/umap/vendors/tokml
mkdir -p umap/static/umap/vendors/locatecontrol/ && cp -r node_modules/leaflet.locatecontrol/dist/L.Control.Locate.min.* umap/static/umap/vendors/locatecontrol/
mkdir -p umap/static/umap/vendors/dompurify/ && cp -r node_modules/dompurify/dist/purify.min.* umap/static/umap/vendors/dompurify/
mkdir -p umap/static/umap/vendors/dompurify/ && cp -r node_modules/dompurify/dist/*.mjs umap/static/umap/vendors/dompurify/
mkdir -p umap/static/umap/vendors/colorbrewer/ && cp node_modules/colorbrewer/index.js umap/static/umap/vendors/colorbrewer/colorbrewer.js
mkdir -p umap/static/umap/vendors/simple-statistics/ && cp node_modules/simple-statistics/dist/simple-statistics.min.* umap/static/umap/vendors/simple-statistics/
mkdir -p umap/static/umap/vendors/iconlayers/ && cp node_modules/leaflet-iconlayers/dist/* umap/static/umap/vendors/iconlayers/
Expand Down
2 changes: 1 addition & 1 deletion umap/static/umap/js/modules/global.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import URLs from './urls.js'
import Browser from './browser.js'
import * as Utils from './utils.js'
import {SCHEMA} from './schema.js'
import { SCHEMA } from './schema.js'
import { Request, ServerRequest, RequestError, HTTPError, NOKError } from './request.js'

// Import modules and export them to the global scope.
Expand Down
17 changes: 1 addition & 16 deletions umap/static/umap/js/modules/urls.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,4 @@
// Vendorized from leaflet.utils
// https://github.com/Leaflet/Leaflet/blob/108c6717b70f57c63645498f9bd66b6677758786/src/core/Util.js#L132-L151
var templateRe = /\{ *([\w_ -]+) *\}/g

function template(str, data) {
return str.replace(templateRe, function (str, key) {
var value = data[key]

if (value === undefined) {
throw new Error('No value provided for variable ' + str)
} else if (typeof value === 'function') {
value = value(data)
}
return value
})
}
import { template } from "./utils.js"

export default class URLs {
constructor(serverUrls) {
Expand Down
283 changes: 283 additions & 0 deletions umap/static/umap/js/modules/utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { default as DOMPurifyInitializer } from '../../vendors/dompurify/purify.es.mjs'

/**
* Generate a pseudo-unique identifier (5 chars long, mixed-case alphanumeric)
*
Expand All @@ -22,3 +24,284 @@ export function checkId(string) {
if (typeof string !== 'string') return false
return /^[A-Za-z0-9]{5}$/.test(string)
}

/**
* Import DOM purify, and initialize it.
*
* If the context is a node server, uses jsdom to provide
* DOM APIs
*/
export default function getPurify() {
if (typeof window === 'undefined') {
return DOMPurifyInitializer(new global.JSDOM('').window)
} else {
return DOMPurifyInitializer(window)
}
}

export function escapeHTML(s) {
s = s ? s.toString() : ''
s = getPurify().sanitize(s, {
USE_PROFILES: { html: true },
ADD_TAGS: ['iframe'],
ALLOWED_TAGS: [
'h3',
'h4',
'h5',
'hr',
'strong',
'em',
'ul',
'li',
'a',
'div',
'iframe',
'img',
'br',
],
ADD_ATTR: ['target', 'allow', 'allowfullscreen', 'frameborder', 'scrolling'],
ALLOWED_ATTR: ['href', 'src', 'width', 'height'],
// Added: `geo:` URL scheme as defined in RFC5870:
// https://www.rfc-editor.org/rfc/rfc5870.html
// The base RegExp comes from:
// https://github.com/cure53/DOMPurify/blob/main/src/regexp.js#L10
ALLOWED_URI_REGEXP:
/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|geo):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
})
return s
}

export function toHTML(r, options) {
if (!r) return ''
const target = (options && options.target) || 'blank'
let ii

// detect newline format
const newline = r.indexOf('\r\n') != -1 ? '\r\n' : r.indexOf('\n') != -1 ? '\n' : ''

// headings and hr
r = r.replace(/^### (.*)/gm, '<h5>$1</h5>')
r = r.replace(/^## (.*)/gm, '<h4>$1</h4>')
r = r.replace(/^# (.*)/gm, '<h3>$1</h3>')
r = r.replace(/^---/gm, '<hr>')

// bold, italics
r = r.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
r = r.replace(/\*(.*?)\*/g, '<em>$1</em>')

// unordered lists
r = r.replace(/^\*\* (.*)/gm, '<ul><ul><li>$1</li></ul></ul>')
r = r.replace(/^\* (.*)/gm, '<ul><li>$1</li></ul>')
for (ii = 0; ii < 3; ii++)
r = r.replace(new RegExp(`</ul>${newline}<ul>`, 'g'), newline)

// links
r = r.replace(/(\[\[http)/g, '[[h_t_t_p') // Escape for avoiding clash between [[http://xxx]] and http://xxx
r = r.replace(/({{http)/g, '{{h_t_t_p')
r = r.replace(/(=http)/g, '=h_t_t_p') // http://xxx as query string, see https://github.com/umap-project/umap/issues/607
r = r.replace(/(https?:[^ \<)\n]*)/g, `<a target="_${target}" href="$1">$1</a>`)
r = r.replace(
/\[\[(h_t_t_ps?:[^\]|]*?)\]\]/g,
`<a target="_${target}" href="$1">$1</a>`
)
r = r.replace(
/\[\[(h_t_t_ps?:[^|]*?)\|(.*?)\]\]/g,
`<a target="_${target}" href="$1">$2</a>`
)
r = r.replace(/\[\[([^\]|]*?)\]\]/g, `<a target="_${target}" href="$1">$1</a>`)
r = r.replace(/\[\[([^|]*?)\|(.*?)\]\]/g, `<a target="_${target}" href="$1">$2</a>`)

// iframe
r = r.replace(
/{{{(h_t_t_ps?[^ |{]*)}}}/g,
'<div><iframe frameborder="0" src="$1" width="100%" height="300px"></iframe></div>'
)
r = r.replace(
/{{{(h_t_t_ps?[^ |{]*)\|(\d*)(px)?}}}/g,
'<div><iframe frameborder="0" src="$1" width="100%" height="$2px"></iframe></div>'
)
r = r.replace(
/{{{(h_t_t_ps?[^ |{]*)\|(\d*)(px)?\*(\d*)(px)?}}}/g,
'<div><iframe frameborder="0" src="$1" width="$4px" height="$2px"></iframe></div>'
)

// images
r = r.replace(/{{([^\]|]*?)}}/g, '<img src="$1">')
r = r.replace(
/{{([^|]*?)\|(\d*?)(px)?}}/g,
'<img src="$1" style="width:$2px;min-width:$2px;">'
)

//Unescape http
r = r.replace(/(h_t_t_p)/g, 'http')

// Preserver line breaks
if (newline) r = r.replace(new RegExp(`${newline}(?=[^]+)`, 'g'), `<br>${newline}`)

r = escapeHTML(r)

return r
}

export function isObject(what) {
return typeof what === 'object' && what !== null
}

export function CopyJSON(geojson) {
return JSON.parse(JSON.stringify(geojson))
}

export function detectFileType(f) {
const filename = f.name ? escape(f.name.toLowerCase()) : ''
function ext(_) {
return filename.indexOf(_) !== -1
}
if (f.type === 'application/vnd.google-earth.kml+xml' || ext('.kml')) {
return 'kml'
}
if (ext('.gpx')) return 'gpx'
if (ext('.geojson') || ext('.json')) return 'geojson'
if (f.type === 'text/csv' || ext('.csv') || ext('.tsv') || ext('.dsv')) {
return 'csv'
}
if (ext('.xml') || ext('.osm')) return 'osm'
if (ext('.umap')) return 'umap'
}

export function usableOption(options, option) {
return options[option] !== undefined && options[option] !== ''
}

export function greedyTemplate(str, data, ignore) {
function getValue(data, path) {
let value = data
for (let i = 0; i < path.length; i++) {
value = value[path[i]]
if (value === undefined) break
}
return value
}

if (typeof str !== 'string') return ''

return str.replace(
/\{ *([^\{\}/\-]+)(?:\|("[^"]*"))? *\}/g,
(str, key, staticFallback) => {
const vars = key.split('|')
let value
let path
if (staticFallback !== undefined) {
vars.push(staticFallback)
}
for (let i = 0; i < vars.length; i++) {
path = vars[i]
if (path.startsWith('"') && path.endsWith('"'))
value = path.substring(1, path.length - 1) // static default value.
else value = getValue(data, path.split('.'))
if (value !== undefined) break
}
if (value === undefined) {
if (ignore) value = str
else value = ''
}
return value
}
)
}

export function naturalSort(a, b, lang) {
return a
.toString()
.toLowerCase()
.localeCompare(b.toString().toLowerCase(), lang || 'en', {
sensitivity: 'base',
numeric: true,
})
}

export function sortFeatures(features, sortKey, lang) {
const sortKeys = (sortKey || 'name').split(',')

const sort = (a, b, i) => {
let sortKey = sortKeys[i],
reverse = 1
if (sortKey[0] === '-') {
reverse = -1
sortKey = sortKey.substring(1)
}
let score
const valA = a.properties[sortKey] || ''
const valB = b.properties[sortKey] || ''
if (!valA) score = -1
else if (!valB) score = 1
else score = naturalSort(valA, valB, lang)
if (score === 0 && sortKeys[i + 1]) return sort(a, b, i + 1)
return score * reverse
}

features.sort((a, b) => {
if (!a.properties || !b.properties) {
return 0
}
return sort(a, b, 0)
})

return features
}

export function flattenCoordinates(coords) {
while (coords[0] && typeof coords[0][0] !== 'number') coords = coords[0]
return coords
}

export function buildQueryString(params) {
const query_string = []
for (const key in params) {
query_string.push(`${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
}
return query_string.join('&')
}

export function getBaseUrl() {
return `//${window.location.host}${window.location.pathname}`
}

export function hasVar(value) {
return typeof value === 'string' && value.indexOf('{') != -1
}

export function isPath(value) {
return value && value.length && value.startsWith('/')
}

export function isRemoteUrl(value) {
return value && value.length && value.startsWith('http')
}

export function isDataImage(value) {
return value && value.length && value.startsWith('data:image')
}

export function normalize(s) {
return (s || '')
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
}

// Vendorized from leaflet.utils
// https://github.com/Leaflet/Leaflet/blob/108c6717b70f57c63645498f9bd66b6677758786/src/core/Util.js#L132-L151
var templateRe = /\{ *([\w_ -]+) *\}/g

export function template(str, data) {
return str.replace(templateRe, function (str, key) {
var value = data[key]

if (value === undefined) {
throw new Error('No value provided for variable ' + str)
} else if (typeof value === 'function') {
value = value(data)
}
return value
})
}
2 changes: 1 addition & 1 deletion umap/static/umap/js/umap.autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ U.AutoComplete = L.Class.extend({
this.displaySelected(choice)
this.hide()
if (this.options.callback) {
L.Util.bind(this.options.callback, this)(choice)
U.Utils.bind(this.options.callback, this)(choice)
}
}
},
Expand Down
Loading

0 comments on commit 10ccd4e

Please sign in to comment.