github.com/soulteary/pocket-bookcase@v0.0.0-20240428065142-0b5a9a0fc98a/internal/view/assets/js/url.js (about)

     1  /*!
     2   * Lightweight URL manipulation with JavaScript
     3   * This library is independent of any other libraries and has pretty simple
     4   * interface and lightweight code-base.
     5   * Some ideas of query string parsing had been taken from Jan Wolter
     6   * @see http://unixpapa.com/js/querystring.html
     7   *
     8   * @license MIT
     9   * @author Mykhailo Stadnyk <mikhus@gmail.com>
    10   */
    11  (function (ns) {
    12  	'use strict';
    13  
    14  	var RX_PROTOCOL = /^[a-z]+:/;
    15  	var RX_PORT = /[-a-z0-9]+(\.[-a-z0-9])*:\d+/i;
    16  	var RX_CREDS = /\/\/(.*?)(?::(.*?))?@/;
    17  	var RX_WIN = /^win/i;
    18  	var RX_PROTOCOL_REPL = /:$/;
    19  	var RX_QUERY_REPL = /^\?/;
    20  	var RX_HASH_REPL = /^#/;
    21  	var RX_PATH = /(.*\/)/;
    22  	var RX_PATH_FIX = /^\/{2,}/;
    23  	var RX_SINGLE_QUOTE = /'/g;
    24  	var RX_DECODE_1 = /%([ef][0-9a-f])%([89ab][0-9a-f])%([89ab][0-9a-f])/gi;
    25  	var RX_DECODE_2 = /%([cd][0-9a-f])%([89ab][0-9a-f])/gi;
    26  	var RX_DECODE_3 = /%([0-7][0-9a-f])/gi;
    27  	var RX_PLUS = /\+/g;
    28  	var RX_PATH_SEMI = /^\w:$/;
    29  	var RX_URL_TEST = /[^/#?]/;
    30  
    31  	// configure given url options
    32  	function urlConfig(url) {
    33  		var config = {
    34  			path: true,
    35  			query: true,
    36  			hash: true
    37  		};
    38  
    39  		if (!url) {
    40  			return config;
    41  		}
    42  
    43  		if (RX_PROTOCOL.test(url)) {
    44  			config.protocol = true;
    45  			config.host = true;
    46  
    47  			if (RX_PORT.test(url)) {
    48  				config.port = true;
    49  			}
    50  
    51  			if (RX_CREDS.test(url)) {
    52  				config.user = true;
    53  				config.pass = true;
    54  			}
    55  		}
    56  
    57  		return config;
    58  	}
    59  
    60  	var isNode = typeof window === 'undefined' &&
    61  		typeof global !== 'undefined' &&
    62  		typeof require === 'function';
    63  
    64  	// Trick to bypass Webpack's require at compile time
    65  	var nodeRequire = isNode ? ns['require'] : null;
    66  
    67  	// mapping between what we want and <a> element properties
    68  	var map = {
    69  		protocol: 'protocol',
    70  		host: 'hostname',
    71  		port: 'port',
    72  		path: 'pathname',
    73  		query: 'search',
    74  		hash: 'hash'
    75  	};
    76  
    77  	// jscs: disable
    78      /**
    79       * default ports as defined by http://url.spec.whatwg.org/#default-port
    80       * We need them to fix IE behavior, @see https://github.com/Mikhus/jsurl/issues/2
    81       */
    82  	// jscs: enable
    83  	var defaultPorts = {
    84  		ftp: 21,
    85  		gopher: 70,
    86  		http: 80,
    87  		https: 443,
    88  		ws: 80,
    89  		wss: 443
    90  	};
    91  
    92  	var _currNodeUrl;
    93  	function getCurrUrl() {
    94  		if (isNode) {
    95  			if (!_currNodeUrl) {
    96  				_currNodeUrl = ('file://' +
    97  					(process.platform.match(RX_WIN) ? '/' : '') +
    98  					nodeRequire('fs').realpathSync('.')
    99  				);
   100  			}
   101  			return _currNodeUrl;
   102  		} else {
   103  			return document.location.href;
   104  		}
   105  	}
   106  
   107  	function parse(self, url, absolutize) {
   108  		var link, i, auth;
   109  
   110  		if (!url) {
   111  			url = getCurrUrl();
   112  		}
   113  
   114  		if (isNode) {
   115  			link = nodeRequire('url').parse(url);
   116  		}
   117  
   118  		else {
   119  			link = document.createElement('a');
   120  			link.href = url;
   121  		}
   122  
   123  		var config = urlConfig(url);
   124  
   125  		auth = url.match(RX_CREDS) || [];
   126  
   127  		for (i in map) {
   128  			if (config[i]) {
   129  				self[i] = link[map[i]] || '';
   130  			}
   131  
   132  			else {
   133  				self[i] = '';
   134  			}
   135  		}
   136  
   137  		// fix-up some parts
   138  		self.protocol = self.protocol.replace(RX_PROTOCOL_REPL, '');
   139  		self.query = self.query.replace(RX_QUERY_REPL, '');
   140  		self.hash = decode(self.hash.replace(RX_HASH_REPL, ''));
   141  		self.user = decode(auth[1] || '');
   142  		self.pass = decode(auth[2] || '');
   143  		/* jshint ignore:start */
   144  		self.port = (
   145  			// loosely compare because port can be a string
   146  			defaultPorts[self.protocol] == self.port || self.port == 0
   147  		) ? '' : self.port; // IE fix, Android browser fix
   148  		/* jshint ignore:end */
   149  
   150  		if (!config.protocol && RX_URL_TEST.test(url.charAt(0))) {
   151  			self.path = url.split('?')[0].split('#')[0];
   152  		}
   153  
   154  		if (!config.protocol && absolutize) {
   155  			// is IE and path is relative
   156  			var base = new Url(getCurrUrl().match(RX_PATH)[0]);
   157  			var basePath = base.path.split('/');
   158  			var selfPath = self.path.split('/');
   159  			var props = ['protocol', 'user', 'pass', 'host', 'port'];
   160  			var s = props.length;
   161  
   162  			basePath.pop();
   163  
   164  			for (i = 0; i < s; i++) {
   165  				self[props[i]] = base[props[i]];
   166  			}
   167  
   168  			while (selfPath[0] === '..') { // skip all "../
   169  				basePath.pop();
   170  				selfPath.shift();
   171  			}
   172  
   173  			self.path =
   174  				(url.charAt(0) !== '/' ? basePath.join('/') : '') +
   175  				'/' + selfPath.join('/')
   176  				;
   177  		}
   178  
   179  		self.path = self.path.replace(RX_PATH_FIX, '/');
   180  
   181  		self.paths(self.paths());
   182  
   183  		self.query = new QueryString(self.query);
   184  	}
   185  
   186  	function encode(s) {
   187  		return encodeURIComponent(s).replace(RX_SINGLE_QUOTE, '%27');
   188  	}
   189  
   190  	function decode(s) {
   191  		s = s.replace(RX_PLUS, ' ');
   192  		s = s.replace(RX_DECODE_1, function (code, hex1, hex2, hex3) {
   193  			var n1 = parseInt(hex1, 16) - 0xE0;
   194  			var n2 = parseInt(hex2, 16) - 0x80;
   195  
   196  			if (n1 === 0 && n2 < 32) {
   197  				return code;
   198  			}
   199  
   200  			var n3 = parseInt(hex3, 16) - 0x80;
   201  			var n = (n1 << 12) + (n2 << 6) + n3;
   202  
   203  			if (n > 0xFFFF) {
   204  				return code;
   205  			}
   206  
   207  			return String.fromCharCode(n);
   208  		});
   209  		s = s.replace(RX_DECODE_2, function (code, hex1, hex2) {
   210  			var n1 = parseInt(hex1, 16) - 0xC0;
   211  
   212  			if (n1 < 2) {
   213  				return code;
   214  			}
   215  
   216  			var n2 = parseInt(hex2, 16) - 0x80;
   217  
   218  			return String.fromCharCode((n1 << 6) + n2);
   219  		});
   220  
   221  		return s.replace(RX_DECODE_3, function (code, hex) {
   222  			return String.fromCharCode(parseInt(hex, 16));
   223  		});
   224  	}
   225  
   226      /**
   227       * Class QueryString
   228       *
   229       * @param {string} qs - string representation of QueryString
   230       * @constructor
   231       */
   232  	function QueryString(qs) {
   233  		var parts = qs.split('&');
   234  
   235  		for (var i = 0, s = parts.length; i < s; i++) {
   236  			var keyVal = parts[i].split('=');
   237  			var key = decodeURIComponent(keyVal[0].replace(RX_PLUS, ' '));
   238  
   239  			if (!key) {
   240  				continue;
   241  			}
   242  
   243  			var value = keyVal[1] !== undefined ? decode(keyVal[1]) : null;
   244  
   245  			if (typeof this[key] === 'undefined') {
   246  				this[key] = value;
   247  			} else {
   248  				if (!(this[key] instanceof Array)) {
   249  					this[key] = [this[key]];
   250  				}
   251  
   252  				this[key].push(value);
   253  			}
   254  		}
   255  	}
   256  
   257      /**
   258       * Converts QueryString object back to string representation
   259       *
   260       * @returns {string}
   261       */
   262  	QueryString.prototype.toString = function () {
   263  		var s = '';
   264  		var e = encode;
   265  		var i, ii;
   266  
   267  		for (i in this) {
   268  			var w = this[i];
   269  
   270  			if (w instanceof Function || w === null) {
   271  				continue;
   272  			}
   273  
   274  			if (w instanceof Array) {
   275  				var len = w.length;
   276  
   277  				if (len) {
   278  					for (ii = 0; ii < len; ii++) {
   279  						var v = w[ii];
   280  						s += s ? '&' : '';
   281  						s += e(i) + (v === undefined || v === null
   282  							? ''
   283  							: '=' + e(v));
   284  					}
   285  				}
   286  
   287  				else {
   288  					// parameter is an empty array, so treat as
   289  					// an empty argument
   290  					s += (s ? '&' : '') + e(i) + '=';
   291  				}
   292  			}
   293  
   294  			else {
   295  				s += s ? '&' : '';
   296  				s += e(i) + (w === undefined ? '' : '=' + e(w));
   297  			}
   298  		}
   299  
   300  		return s;
   301  	};
   302  
   303      /**
   304       * Class Url
   305       *
   306       * @param {string} [url] - string URL representation
   307       * @param {boolean} [noTransform] - do not transform to absolute URL
   308       * @constructor
   309       */
   310  	function Url(url, noTransform) {
   311  		parse(this, url, !noTransform);
   312  	}
   313  
   314      /**
   315       * Clears QueryString, making it contain no params at all
   316       *
   317       * @returns {Url}
   318       */
   319  	Url.prototype.clearQuery = function () {
   320  		for (var key in this.query) {
   321  			if (!(this.query[key] instanceof Function)) {
   322  				delete this.query[key];
   323  			}
   324  		}
   325  
   326  		return this;
   327  	};
   328  
   329      /**
   330       * Returns total number of parameters in QueryString
   331       *
   332       * @returns {number}
   333       */
   334  	Url.prototype.queryLength = function () {
   335  		var count = 0;
   336  
   337  		for (var key in this.query) {
   338  			if (!(this.query[key] instanceof Function)) {
   339  				count++;
   340  			}
   341  		}
   342  
   343  		return count;
   344  	};
   345  
   346      /**
   347       * Returns true if QueryString contains no parameters, false otherwise
   348       *
   349       * @returns {boolean}
   350       */
   351  	Url.prototype.isEmptyQuery = function () {
   352  		return this.queryLength() === 0;
   353  	};
   354  
   355      /**
   356       *
   357       * @param {Array} [paths] - an array pf path parts (if given will modify
   358       *                          Url.path property
   359       * @returns {Array} - an array representation of the Url.path property
   360       */
   361  	Url.prototype.paths = function (paths) {
   362  		var prefix = '';
   363  		var i = 0;
   364  		var s;
   365  
   366  		if (paths && paths.length && paths + '' !== paths) {
   367  			if (this.isAbsolute()) {
   368  				prefix = '/';
   369  			}
   370  
   371  			for (s = paths.length; i < s; i++) {
   372  				paths[i] = !i && RX_PATH_SEMI.test(paths[i])
   373  					? paths[i]
   374  					: encode(paths[i]);
   375  			}
   376  
   377  			this.path = prefix + paths.join('/');
   378  		}
   379  
   380  		paths = (this.path.charAt(0) === '/' ?
   381  			this.path.slice(1) : this.path).split('/');
   382  
   383  		for (i = 0, s = paths.length; i < s; i++) {
   384  			paths[i] = decode(paths[i]);
   385  		}
   386  
   387  		return paths;
   388  	};
   389  
   390      /**
   391       * Performs URL-specific encoding of the given string
   392       *
   393       * @method Url#encode
   394       * @param {string} s - string to encode
   395       * @returns {string}
   396       */
   397  	Url.prototype.encode = encode;
   398  
   399      /**
   400       * Performs URL-specific decoding of the given encoded string
   401       *
   402       * @method Url#decode
   403       * @param {string} s - string to decode
   404       * @returns {string}
   405       */
   406  	Url.prototype.decode = decode;
   407  
   408      /**
   409       * Checks if current URL is an absolute resource locator (globally absolute
   410       * or absolute path to current server)
   411       *
   412       * @returns {boolean}
   413       */
   414  	Url.prototype.isAbsolute = function () {
   415  		return this.protocol || this.path.charAt(0) === '/';
   416  	};
   417  
   418      /**
   419       * Returns string representation of current Url object
   420       *
   421       * @returns {string}
   422       */
   423  	Url.prototype.toString = function () {
   424  		return (
   425  			(this.protocol && (this.protocol + '://')) +
   426  			(this.user && (
   427  				encode(this.user) + (this.pass && (':' + encode(this.pass))
   428  				) + '@')) +
   429  			(this.host && this.host) +
   430  			(this.port && (':' + this.port)) +
   431  			(this.path && this.path) +
   432  			(this.query.toString() && ('?' + this.query)) +
   433  			(this.hash && ('#' + encode(this.hash)))
   434  		);
   435  	};
   436  
   437  	ns[ns.exports ? 'exports' : 'Url'] = Url;
   438  }(typeof module !== 'undefined' && module.exports ? module : window));