diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..398ecee --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# editorconfig.org +root = true + +[*] +charset = utf-8 +indent_size = 2 +indent_style = space +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true + +[*.html] +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..a3a8875 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +micro_files/ie* +web_modules diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..01fe8be --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = { + extends: 'semistandard', + env: { browser: true } +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/micro_files/fetch.js b/micro_files/fetch.js deleted file mode 100644 index 44f0540..0000000 --- a/micro_files/fetch.js +++ /dev/null @@ -1,458 +0,0 @@ -(function(self) { - 'use strict'; - - if (self.fetch) { - return - } - - var support = { - searchParams: 'URLSearchParams' in self, - iterable: 'Symbol' in self && 'iterator' in Symbol, - blob: 'FileReader' in self && 'Blob' in self && (function() { - try { - new Blob() - return true - } catch(e) { - return false - } - })(), - formData: 'FormData' in self, - arrayBuffer: 'ArrayBuffer' in self - } - - if (support.arrayBuffer) { - var viewClasses = [ - '[object Int8Array]', - '[object Uint8Array]', - '[object Uint8ClampedArray]', - '[object Int16Array]', - '[object Uint16Array]', - '[object Int32Array]', - '[object Uint32Array]', - '[object Float32Array]', - '[object Float64Array]' - ] - - var isDataView = function(obj) { - return obj && DataView.prototype.isPrototypeOf(obj) - } - - var isArrayBufferView = ArrayBuffer.isView || function(obj) { - return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1 - } - } - - function normalizeName(name) { - if (typeof name !== 'string') { - name = String(name) - } - if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) { - throw new TypeError('Invalid character in header field name') - } - return name.toLowerCase() - } - - function normalizeValue(value) { - if (typeof value !== 'string') { - value = String(value) - } - return value - } - - // Build a destructive iterator for the value list - function iteratorFor(items) { - var iterator = { - next: function() { - var value = items.shift() - return {done: value === undefined, value: value} - } - } - - if (support.iterable) { - iterator[Symbol.iterator] = function() { - return iterator - } - } - - return iterator - } - - function Headers(headers) { - this.map = {} - - if (headers instanceof Headers) { - headers.forEach(function(value, name) { - this.append(name, value) - }, this) - - } else if (headers) { - Object.getOwnPropertyNames(headers).forEach(function(name) { - this.append(name, headers[name]) - }, this) - } - } - - Headers.prototype.append = function(name, value) { - name = normalizeName(name) - value = normalizeValue(value) - var oldValue = this.map[name] - this.map[name] = oldValue ? oldValue+','+value : value - } - - Headers.prototype['delete'] = function(name) { - delete this.map[normalizeName(name)] - } - - Headers.prototype.get = function(name) { - name = normalizeName(name) - return this.has(name) ? this.map[name] : null - } - - Headers.prototype.has = function(name) { - return this.map.hasOwnProperty(normalizeName(name)) - } - - Headers.prototype.set = function(name, value) { - this.map[normalizeName(name)] = normalizeValue(value) - } - - Headers.prototype.forEach = function(callback, thisArg) { - for (var name in this.map) { - if (this.map.hasOwnProperty(name)) { - callback.call(thisArg, this.map[name], name, this) - } - } - } - - Headers.prototype.keys = function() { - var items = [] - this.forEach(function(value, name) { items.push(name) }) - return iteratorFor(items) - } - - Headers.prototype.values = function() { - var items = [] - this.forEach(function(value) { items.push(value) }) - return iteratorFor(items) - } - - Headers.prototype.entries = function() { - var items = [] - this.forEach(function(value, name) { items.push([name, value]) }) - return iteratorFor(items) - } - - if (support.iterable) { - Headers.prototype[Symbol.iterator] = Headers.prototype.entries - } - - function consumed(body) { - if (body.bodyUsed) { - return Promise.reject(new TypeError('Already read')) - } - body.bodyUsed = true - } - - function fileReaderReady(reader) { - return new Promise(function(resolve, reject) { - reader.onload = function() { - resolve(reader.result) - } - reader.onerror = function() { - reject(reader.error) - } - }) - } - - function readBlobAsArrayBuffer(blob) { - var reader = new FileReader() - var promise = fileReaderReady(reader) - reader.readAsArrayBuffer(blob) - return promise - } - - function readBlobAsText(blob) { - var reader = new FileReader() - var promise = fileReaderReady(reader) - reader.readAsText(blob) - return promise - } - - function readArrayBufferAsText(buf) { - var view = new Uint8Array(buf) - var chars = new Array(view.length) - - for (var i = 0; i < view.length; i++) { - chars[i] = String.fromCharCode(view[i]) - } - return chars.join('') - } - - function bufferClone(buf) { - if (buf.slice) { - return buf.slice(0) - } else { - var view = new Uint8Array(buf.byteLength) - view.set(new Uint8Array(buf)) - return view.buffer - } - } - - function Body() { - this.bodyUsed = false - - this._initBody = function(body) { - this._bodyInit = body - if (!body) { - this._bodyText = '' - } else if (typeof body === 'string') { - this._bodyText = body - } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { - this._bodyBlob = body - } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { - this._bodyFormData = body - } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { - this._bodyText = body.toString() - } else if (support.arrayBuffer && support.blob && isDataView(body)) { - this._bodyArrayBuffer = bufferClone(body.buffer) - // IE 10-11 can't handle a DataView body. - this._bodyInit = new Blob([this._bodyArrayBuffer]) - } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { - this._bodyArrayBuffer = bufferClone(body) - } else { - throw new Error('unsupported BodyInit type') - } - - if (!this.headers.get('content-type')) { - if (typeof body === 'string') { - this.headers.set('content-type', 'text/plain;charset=UTF-8') - } else if (this._bodyBlob && this._bodyBlob.type) { - this.headers.set('content-type', this._bodyBlob.type) - } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { - this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8') - } - } - } - - if (support.blob) { - this.blob = function() { - var rejected = consumed(this) - if (rejected) { - return rejected - } - - if (this._bodyBlob) { - return Promise.resolve(this._bodyBlob) - } else if (this._bodyArrayBuffer) { - return Promise.resolve(new Blob([this._bodyArrayBuffer])) - } else if (this._bodyFormData) { - throw new Error('could not read FormData body as blob') - } else { - return Promise.resolve(new Blob([this._bodyText])) - } - } - - this.arrayBuffer = function() { - if (this._bodyArrayBuffer) { - return consumed(this) || Promise.resolve(this._bodyArrayBuffer) - } else { - return this.blob().then(readBlobAsArrayBuffer) - } - } - } - - this.text = function() { - var rejected = consumed(this) - if (rejected) { - return rejected - } - - if (this._bodyBlob) { - return readBlobAsText(this._bodyBlob) - } else if (this._bodyArrayBuffer) { - return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer)) - } else if (this._bodyFormData) { - throw new Error('could not read FormData body as text') - } else { - return Promise.resolve(this._bodyText) - } - } - - if (support.formData) { - this.formData = function() { - return this.text().then(decode) - } - } - - this.json = function() { - return this.text().then(JSON.parse) - } - - return this - } - - // HTTP methods whose capitalization should be normalized - var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] - - function normalizeMethod(method) { - var upcased = method.toUpperCase() - return (methods.indexOf(upcased) > -1) ? upcased : method - } - - function Request(input, options) { - options = options || {} - var body = options.body - - if (typeof input === 'string') { - this.url = input - } else { - if (input.bodyUsed) { - throw new TypeError('Already read') - } - this.url = input.url - this.credentials = input.credentials - if (!options.headers) { - this.headers = new Headers(input.headers) - } - this.method = input.method - this.mode = input.mode - if (!body && input._bodyInit != null) { - body = input._bodyInit - input.bodyUsed = true - } - } - - this.credentials = options.credentials || this.credentials || 'omit' - if (options.headers || !this.headers) { - this.headers = new Headers(options.headers) - } - this.method = normalizeMethod(options.method || this.method || 'GET') - this.mode = options.mode || this.mode || null - this.referrer = null - - if ((this.method === 'GET' || this.method === 'HEAD') && body) { - throw new TypeError('Body not allowed for GET or HEAD requests') - } - this._initBody(body) - } - - Request.prototype.clone = function() { - return new Request(this, { body: this._bodyInit }) - } - - function decode(body) { - var form = new FormData() - body.trim().split('&').forEach(function(bytes) { - if (bytes) { - var split = bytes.split('=') - var name = split.shift().replace(/\+/g, ' ') - var value = split.join('=').replace(/\+/g, ' ') - form.append(decodeURIComponent(name), decodeURIComponent(value)) - } - }) - return form - } - - function parseHeaders(rawHeaders) { - var headers = new Headers() - rawHeaders.split(/\r?\n/).forEach(function(line) { - var parts = line.split(':') - var key = parts.shift().trim() - if (key) { - var value = parts.join(':').trim() - headers.append(key, value) - } - }) - return headers - } - - Body.call(Request.prototype) - - function Response(bodyInit, options) { - if (!options) { - options = {} - } - - this.type = 'default' - this.status = 'status' in options ? options.status : 200 - this.ok = this.status >= 200 && this.status < 300 - this.statusText = 'statusText' in options ? options.statusText : 'OK' - this.headers = new Headers(options.headers) - this.url = options.url || '' - this._initBody(bodyInit) - } - - Body.call(Response.prototype) - - Response.prototype.clone = function() { - return new Response(this._bodyInit, { - status: this.status, - statusText: this.statusText, - headers: new Headers(this.headers), - url: this.url - }) - } - - Response.error = function() { - var response = new Response(null, {status: 0, statusText: ''}) - response.type = 'error' - return response - } - - var redirectStatuses = [301, 302, 303, 307, 308] - - Response.redirect = function(url, status) { - if (redirectStatuses.indexOf(status) === -1) { - throw new RangeError('Invalid status code') - } - - return new Response(null, {status: status, headers: {location: url}}) - } - - self.Headers = Headers - self.Request = Request - self.Response = Response - - self.fetch = function(input, init) { - return new Promise(function(resolve, reject) { - var request = new Request(input, init) - var xhr = new XMLHttpRequest() - - xhr.onload = function() { - var options = { - status: xhr.status, - statusText: xhr.statusText, - headers: parseHeaders(xhr.getAllResponseHeaders() || '') - } - options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL') - var body = 'response' in xhr ? xhr.response : xhr.responseText - resolve(new Response(body, options)) - } - - xhr.onerror = function() { - reject(new TypeError('Network request failed')) - } - - xhr.ontimeout = function() { - reject(new TypeError('Network request failed')) - } - - xhr.open(request.method, request.url, true) - - if (request.credentials === 'include') { - xhr.withCredentials = true - } - - if ('responseType' in xhr && support.blob) { - xhr.responseType = 'blob' - } - - request.headers.forEach(function(value, name) { - xhr.setRequestHeader(name, value) - }) - - xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) - }) - } - self.fetch.polyfill = true -})(typeof self !== 'undefined' ? self : this); diff --git a/micro_files/micro.css b/micro_files/micro.css index 6e43df1..06b881e 100644 --- a/micro_files/micro.css +++ b/micro_files/micro.css @@ -105,7 +105,7 @@ p.lead { margin: 40px 0; margin-top: 0; margin-bottom: 0; - color: #2F3590; + color: #2F3590; } .getting-involved { margin: 40px 0; @@ -132,3 +132,7 @@ p.lead { border-radius: 5px; } +.install-instructions::before { + content: "$ "; + display: inline; +} diff --git a/micro_files/plugin-search.js b/micro_files/plugin-search.js index a53f481..5d52a73 100644 --- a/micro_files/plugin-search.js +++ b/micro_files/plugin-search.js @@ -1,186 +1,166 @@ -'use strict'; - -var searchResults = []; -var pluginData; - -function versionCompare(v1, v2, options) { - var lexicographical = options && options.lexicographical, - zeroExtend = options && options.zeroExtend, - v1parts = v1.split('.'), - v2parts = v2.split('.'); - - function isValidPart(x) { - return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); - } - - if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { - return NaN; - } - - if (zeroExtend) { - while (v1parts.length < v2parts.length) v1parts.push("0"); - while (v2parts.length < v1parts.length) v2parts.push("0"); - } - - if (!lexicographical) { - v1parts = v1parts.map(Number); - v2parts = v2parts.map(Number); - } - - for (var i = 0; i < v1parts.length; ++i) { - if (v2parts.length == i) { - return 1; - } - - if (v1parts[i] == v2parts[i]) { - continue; - } - else if (v1parts[i] > v2parts[i]) { - return 1; - } - else { - return -1; - } - } - - if (v1parts.length != v2parts.length) { - return -1; - } - - return 0; +import '/web_modules/bootstrap.js'; +import '/web_modules/github-buttons.js'; +import '/web_modules/whatwg-fetch.js'; +import html from '/web_modules/nanohtml/lib/browser.js'; +import pMap from '/web_modules/p-map.js'; +import semverRCompare from '/web_modules/semver/functions/rcompare.js'; +import stripJsonComments from '/web_modules/strip-json-comments.js'; + +const PLUGINS_CHANNEL_URL = 'https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json'; + +const reRequires = /([<>]=?)\s*([^\s])/g; + +const pluginRows = new Map(); +const pluginsTable = document.getElementById('results'); +const searchForm = document.getElementsByTagName('form').item(0); + +const searchConditions = { + Name: (plugin, query) => plugin.Name.includes(query), + Description: (plugin, query) => plugin.Description.includes(query), + Tags: (plugin, query) => plugin.Tags.includes(query) +}; + +const castArray = arg => Array.isArray(arg) ? arg : [arg]; +const cond = (condition, value) => condition ? value : null; + +const template = ({ name, description, versions, website, tags, collapsed }) => html` +
${description}
+ ${cond(website, html` + + `)} + ${cond(tags.length > 0, html` ++ + ${tags.join(', ')} +
+ `)} + ${cond(versions.length > 1, html` ++ Available versions: + ${formatVersions(versions)} +
+ `)} + ${versions.map(version => html` ++ ${version.Version} requires: + ${formatRequires(version.Require)} +
+ `)} +To install this plugin, run the following command from your CLI
+micro -plugin install ${name}
+ Available versions: '; - // The displayed require(s) list/version(s) - let display_item_require = ''; - let current_requires = []; - let requires_len = 0; - let latestVers = "0.0.0" - - // Handle multiple versions in repo.json - for (let i = 0; i < item.Versions.length; i++) { - // Create a string of all versions other than the newest one - // Append the versions into a string - display_item_allversions += item.Versions[i].Version; - // Save the len so we don't have to recalculate - requires_len = Object.keys(item.Versions[i].Require).length; - // Go through each Require obj, turning them to strings, and separate multiples with commas - for (let x = 0; x < requires_len; x++) { - // Gets an array of the object's key and value - current_requires[x] = Object.entries(item.Versions[i].Require)[x]; - // Because of how Micro's repo.json is formatted, there's no space between the [<>=] and the version - // This adds a space to be more visually appealing - current_requires[x][1] = current_requires[x][1].match(RegExp('[<>=]+', 'g')) + ' ' + current_requires[x][1].match(RegExp('[^<>=]+', 'g')); - // NOTE: I believe the require tag will always be 2 values per object - the requirement & the value - // If that's not right, this'll need to be changed - current_requires[x] = current_requires[x][0] + ' ' + current_requires[x][1]; - // Instead of running separate_with_commas, do it here to avoid another needless loop - if ((x + 1) < requires_len) { - current_requires[x] += ', '; - } - } - // Append the requires into a string - display_item_require += '
\n' + item.Versions[i].Version + ' requires : ' + current_requires; - // If not on the last version, add a comma - if ((i + 1) < item.Versions.length) { - display_item_allversions += ', '; - // Linebreak so we display them below eachother - display_item_require += '\n'; - } - - if (versionCompare(latestVers, item.Versions[i].Version) < 0) { - latestVers = item.Versions[i].Version; - } - } - - // If there's only a single version, don't show the "All versions: " thing - if (item.Versions.length == 1) { - display_item_allversions = ''; - } else { - // If there are multiple, we close off the paragraph tag we opened in the declaration - display_item_allversions += '
'; - } - - var website = ""; - if (item.Website != undefined) { - website = '' + item.Website + '' + item.Description + '
\n' + website + '\n' + display_item_tag + display_item_allversions + display_item_require + '
\nTo install this plugin, run the following command from your CLI
\n$ micro -plugin install ' + item.Name + '