Initial version
parent
c61801d0ce
commit
fd74f06dc1
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
@ -0,0 +1,80 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="233"
|
||||||
|
height="91"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.48.1 r9760"
|
||||||
|
sodipodi:docname="npm.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="1"
|
||||||
|
inkscape:cx="128.62279"
|
||||||
|
inkscape:cy="127.36086"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
fit-margin-top="0"
|
||||||
|
fit-margin-left="0"
|
||||||
|
fit-margin-right="0"
|
||||||
|
fit-margin-bottom="0"
|
||||||
|
inkscape:window-width="1440"
|
||||||
|
inkscape:window-height="827"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="25"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Camada 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-109.57142,-485.2193)">
|
||||||
|
<path
|
||||||
|
style="fill:#cb3837;fill-opacity:1"
|
||||||
|
d="m 174.4429,576.2193 0,-13.01931 -64.87148,0 1.9e-4,-77.98069 233.35644,0 -1.9e-4,77.98069 -116.67804,0 0,13.01931 z"
|
||||||
|
id="path4951" />
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:1"
|
||||||
|
d="m 213.63597,563.19999 0,-13.1094 25.67822,0 0,-51.76189 -51.71684,0 0,64.87129 z"
|
||||||
|
id="path4949" />
|
||||||
|
<path
|
||||||
|
style="fill:#cb3837;fill-opacity:1"
|
||||||
|
d="m 213.63597,511.34801 12.61385,0 0,25.67822 -12.61385,0 z"
|
||||||
|
id="path4947" />
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:1"
|
||||||
|
d="m 148.31419,550.09059 0,-38.74258 13.06435,0 0,38.74258 13.06436,0 0,-51.76189 -51.71226,0 0,51.76189 z"
|
||||||
|
id="path4945" />
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:1"
|
||||||
|
d="m 278.05676,550.09059 0,-38.74258 13.06435,0 0,38.74258 13.06436,0 0,-38.74258 13.06435,0 0,38.74258 13.06437,0 0,-51.76189 -77.93404,0 0,51.76189 z"
|
||||||
|
id="path2998" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
@ -0,0 +1,112 @@
|
|||||||
|
var gulp = require("gulp");
|
||||||
|
var fileUrl = require("file-url");
|
||||||
|
var path = require("path");
|
||||||
|
var xtend = require("xtend");
|
||||||
|
var stream = require("stream");
|
||||||
|
var rfr = require("rfr");
|
||||||
|
|
||||||
|
var livereload = require("gulp-livereload");
|
||||||
|
var rename = require("gulp-rename");
|
||||||
|
|
||||||
|
var presetES2015 = require("@joepie91/gulp-preset-es2015");
|
||||||
|
var presetJade = require("@joepie91/gulp-preset-jade");
|
||||||
|
var presetSCSS = require("@joepie91/gulp-preset-scss");
|
||||||
|
var presetRiot = require("@joepie91/gulp-preset-riot");
|
||||||
|
|
||||||
|
var patchLivereloadLogger = require("@joepie91/gulp-partial-patch-livereload-logger");
|
||||||
|
var runElectron = require("@joepie91/gulp-partial-electron");
|
||||||
|
|
||||||
|
patchLivereloadLogger(livereload);
|
||||||
|
|
||||||
|
var sources = {
|
||||||
|
"babel-main": "app.js",
|
||||||
|
"babel-lib": "src/**/*.js",
|
||||||
|
"jade-views": "src/views/**/*.jade",
|
||||||
|
"sass-main": "src/stylesheets/**/*.scss",
|
||||||
|
"riot-components": "src/components/**/*.tag",
|
||||||
|
"electron": ["lib/**/*.js", "lib/views/**/*.html"]
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitStream(duration) {
|
||||||
|
var dummyStream = new stream.Readable();
|
||||||
|
|
||||||
|
dummyStream._read = function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
dummyStream.push(null);
|
||||||
|
}, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dummyStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
var electronProcess;
|
||||||
|
|
||||||
|
gulp.task("electron", ["electron-kill", 'babel-lib', 'babel-main', 'jade-views', 'sass-main', 'riot-components'], function() {
|
||||||
|
electronProcess = runElectron();
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task("electron-kill", function() {
|
||||||
|
if (electronProcess != null) {
|
||||||
|
console.log("Killing old Electron process...")
|
||||||
|
electronProcess.kill("SIGINT");
|
||||||
|
|
||||||
|
/* To ensure that the process really has exited... */
|
||||||
|
return waitStream(200);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
gulp.task('babel-main', function() {
|
||||||
|
return gulp.src(sources["babel-main"])
|
||||||
|
.pipe(presetES2015({
|
||||||
|
livereload: livereload,
|
||||||
|
basePath: __dirname
|
||||||
|
}))
|
||||||
|
.pipe(rename("app.es5.js"))
|
||||||
|
.pipe(gulp.dest("./"));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('babel-lib', function() {
|
||||||
|
return gulp.src(sources["babel-lib"])
|
||||||
|
.pipe(presetES2015({
|
||||||
|
livereload: livereload,
|
||||||
|
basePath: __dirname
|
||||||
|
}))
|
||||||
|
.pipe(gulp.dest("./lib/"));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task("jade-views", function() {
|
||||||
|
return gulp.src(sources["jade-views"])
|
||||||
|
.pipe(presetJade({
|
||||||
|
livereload: livereload,
|
||||||
|
basePath: __dirname
|
||||||
|
}))
|
||||||
|
.pipe(gulp.dest("lib/views/"));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task("sass-main", function() {
|
||||||
|
return gulp.src(sources["sass-main"])
|
||||||
|
.pipe(presetSCSS({
|
||||||
|
livereload: livereload,
|
||||||
|
basePath: __dirname
|
||||||
|
}))
|
||||||
|
.pipe(gulp.dest("lib/stylesheets/"));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task("riot-components", function() {
|
||||||
|
return gulp.src(sources["riot-components"])
|
||||||
|
.pipe(presetRiot({
|
||||||
|
livereload: livereload,
|
||||||
|
basePath: __dirname
|
||||||
|
}))
|
||||||
|
.pipe(gulp.dest("lib/components/"));
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('watch', ['babel-lib', 'babel-main', 'jade-views', 'sass-main', 'riot-components'], function () {
|
||||||
|
livereload.listen();
|
||||||
|
|
||||||
|
Object.keys(sources).forEach(function(source) {
|
||||||
|
gulp.watch(sources[source], [source]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('default', ['watch', 'electron']);
|
@ -0,0 +1,3 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = require("./lib/app");
|
@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"name": "npmbar",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git@git.cryto.net:joepie91/npmbar.git"
|
||||||
|
},
|
||||||
|
"author": "Sven Slootweg",
|
||||||
|
"license": "WTFPL",
|
||||||
|
"dependencies": {
|
||||||
|
"appdirectory": "^0.1.0",
|
||||||
|
"bhttp": "^1.2.1",
|
||||||
|
"bluebird": "^3.3.5",
|
||||||
|
"create-error": "^0.3.1",
|
||||||
|
"default-value": "0.0.3",
|
||||||
|
"document-offset": "^1.0.4",
|
||||||
|
"document-ready-promise": "^3.0.1",
|
||||||
|
"electron-prebuilt": "^1.0.1",
|
||||||
|
"file-url": "^1.1.0",
|
||||||
|
"in-array": "^0.1.2",
|
||||||
|
"is-function": "^1.0.1",
|
||||||
|
"lokijs": "^1.3.16",
|
||||||
|
"match-object": "0.0.2",
|
||||||
|
"menubar": "^4.1.1",
|
||||||
|
"mkdirp": "^0.5.1",
|
||||||
|
"object-pick": "^0.1.2",
|
||||||
|
"ps-tree": "^1.0.1",
|
||||||
|
"rfr": "^1.2.3",
|
||||||
|
"riot": "^2.4.0",
|
||||||
|
"riot-query": "0.0.3",
|
||||||
|
"sanitize-filename": "^1.6.0",
|
||||||
|
"xtend": "^4.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@joepie91/gulp-partial-electron": "^1.0.1",
|
||||||
|
"@joepie91/gulp-partial-patch-livereload-logger": "^1.0.1",
|
||||||
|
"@joepie91/gulp-preset-es2015": "^1.0.1",
|
||||||
|
"@joepie91/gulp-preset-jade": "^1.0.1",
|
||||||
|
"@joepie91/gulp-preset-riot": "^1.0.1",
|
||||||
|
"@joepie91/gulp-preset-scss": "^1.0.1",
|
||||||
|
"babel-preset-es2015": "^6.6.0",
|
||||||
|
"file-url": "^1.1.0",
|
||||||
|
"gulp": "^3.9.1",
|
||||||
|
"gulp-livereload": "^3.8.1",
|
||||||
|
"gulp-rename": "^1.2.2"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Promise = require("bluebird");
|
||||||
|
const path = require("path");
|
||||||
|
const menubar = require("menubar");
|
||||||
|
const appDirectory = require("appdirectory");
|
||||||
|
const xtend = require("xtend");
|
||||||
|
const mkdirpAsync = Promise.promisify(require("mkdirp"));
|
||||||
|
const fileUrl = require("file-url");
|
||||||
|
const rfr = require("rfr");
|
||||||
|
|
||||||
|
const {app, globalShortcut, ipcMain} = require("electron");
|
||||||
|
|
||||||
|
const errors = rfr("lib/util/errors");
|
||||||
|
const executeFunction = rfr("lib/electron/execute-function");
|
||||||
|
|
||||||
|
let appDirectories = new appDirectory("npmbar");
|
||||||
|
|
||||||
|
let cacheExpiry = 300;
|
||||||
|
let baseHeight = 58;
|
||||||
|
|
||||||
|
console.log("Creating menubar...")
|
||||||
|
|
||||||
|
let bar = menubar({
|
||||||
|
index: fileUrl(path.join(__dirname, "views", "index.html")),
|
||||||
|
icon: path.join(__dirname, "..", "assets", "icon.png"),
|
||||||
|
"preload-window": true,
|
||||||
|
"always-on-top": true,
|
||||||
|
width: 700,
|
||||||
|
height: baseHeight
|
||||||
|
});
|
||||||
|
|
||||||
|
bar.on("ready", () => {
|
||||||
|
//bar.window.toggleDevTools();
|
||||||
|
globalShortcut.register("F8", () => {
|
||||||
|
if (bar.window.isVisible()) {
|
||||||
|
bar.hideWindow();
|
||||||
|
} else {
|
||||||
|
bar.showWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bar.on("after-show", () => {
|
||||||
|
bar.window.setAlwaysOnTop(true);
|
||||||
|
bar.window.focusOnWebView();
|
||||||
|
|
||||||
|
bar.window.webContents.send("focusSearch");
|
||||||
|
})
|
||||||
|
|
||||||
|
//bar.showWindow();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on("resize", (event, newSize) => {
|
||||||
|
if (newSize.width != null) {
|
||||||
|
bar.setOption("width", newSize.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newSize.height != null) {
|
||||||
|
bar.setOption("height", newSize.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Temporary workaround until maxogden/menubar#125 is resolved */
|
||||||
|
bar.window.setSize(bar.getOption("width"), bar.getOption("height"));
|
||||||
|
})
|
||||||
|
|
||||||
|
Promise.try(() => {
|
||||||
|
return mkdirpAsync(appDirectories.userData());
|
||||||
|
}).then(() => {
|
||||||
|
return rfr("lib/db/json-db")(path.join(appDirectories.userData(), "db.json"));
|
||||||
|
}).then((db) => {
|
||||||
|
let metadataCache = db.collection("metadataCache");
|
||||||
|
|
||||||
|
const getPackageMetadata = rfr("lib/package/fetch-metadata")({
|
||||||
|
CacheError: errors.CacheError,
|
||||||
|
get: function(packageName) {
|
||||||
|
try {
|
||||||
|
return metadataCache.findOne({name: packageName});
|
||||||
|
} catch (err) { // FIXME
|
||||||
|
throw new errors.CacheError("Not in cache");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: function(packageName, metadata) {
|
||||||
|
return metadataCache.upsertBy("name", metadata);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
@ -0,0 +1,74 @@
|
|||||||
|
app
|
||||||
|
.logo-section
|
||||||
|
img(src="../../assets/npm.svg", height=40)
|
||||||
|
.bar-section
|
||||||
|
search-box
|
||||||
|
search-results(results="{results}")
|
||||||
|
.window-height-marker
|
||||||
|
|
||||||
|
script.
|
||||||
|
const Promise = require("bluebird");
|
||||||
|
const rfr = require("rfr");
|
||||||
|
|
||||||
|
const search = rfr("lib/search/constructor-io")("CD06z4gVeqSXRiDL2ZNK");
|
||||||
|
|
||||||
|
this.mixin(require("riot-query").mixin);
|
||||||
|
|
||||||
|
let lastQuery;
|
||||||
|
this.results = []
|
||||||
|
|
||||||
|
this.on("mount", () => {
|
||||||
|
let searchBox = this.queryOne("search-box");
|
||||||
|
let searchResults = this.queryOne("search-results");
|
||||||
|
|
||||||
|
searchBox.on("selectionUp", () => {
|
||||||
|
searchResults.moveSelectionUp();
|
||||||
|
});
|
||||||
|
|
||||||
|
searchBox.on("selectionDown", () => {
|
||||||
|
searchResults.moveSelectionDown();
|
||||||
|
});
|
||||||
|
|
||||||
|
searchBox.on("queryChanged", (query) => {
|
||||||
|
Promise.try(() => {
|
||||||
|
lastQuery = query;
|
||||||
|
return search(query);
|
||||||
|
}).then((results) => {
|
||||||
|
/* Sometimes, search results may come back out of order. Here we check whether
|
||||||
|
* the lastQuery is still the same as when we initially made the request. */
|
||||||
|
if (lastQuery === query) {
|
||||||
|
this.results = results.packages.map((pkg) => {
|
||||||
|
return {
|
||||||
|
name: pkg.value,
|
||||||
|
description: pkg.data.description
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on("updated", () => {
|
||||||
|
global.triggerWindowResize();
|
||||||
|
});
|
||||||
|
|
||||||
|
style(scoped, type="scss").
|
||||||
|
.logo-section {
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
width: 120px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #2a333c;
|
||||||
|
padding: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-section {
|
||||||
|
position: absolute;
|
||||||
|
left: 138px;
|
||||||
|
top: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
right: 0px;
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
require("../search-box");
|
||||||
|
require("../search-results");
|
||||||
|
|
||||||
|
require("./component");
|
@ -0,0 +1,48 @@
|
|||||||
|
search-box
|
||||||
|
input.search(placeholder="Search...", onkeydown="{_handleKeyDown}")
|
||||||
|
|
||||||
|
script.
|
||||||
|
let lastKnownQuery;
|
||||||
|
|
||||||
|
Object.assign(this, {
|
||||||
|
_handleKeyDown: (event) => {
|
||||||
|
switch (event.code) {
|
||||||
|
case "ArrowDown":
|
||||||
|
this.trigger("selectionDown");
|
||||||
|
break;
|
||||||
|
case "ArrowUp":
|
||||||
|
this.trigger("selectionUp");
|
||||||
|
break;
|
||||||
|
case "Enter":
|
||||||
|
this.trigger("confirm");
|
||||||
|
break;
|
||||||
|
case "Escape":
|
||||||
|
this.trigger("cancel");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let searchInput = this.root.querySelector(".search");
|
||||||
|
|
||||||
|
if (searchInput.value !== lastKnownQuery) {
|
||||||
|
lastKnownQuery = searchInput.value;
|
||||||
|
this.trigger("queryChanged", searchInput.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
style(scoped, type="scss").
|
||||||
|
.search {
|
||||||
|
border: 0px;
|
||||||
|
padding: 17px 9px;
|
||||||
|
font-size: 21px;
|
||||||
|
background-color: transparent;
|
||||||
|
width: 100%;
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&::-webkit-input-placeholder {
|
||||||
|
color: #d99d9d;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
require("./component");
|
@ -0,0 +1,52 @@
|
|||||||
|
search-results
|
||||||
|
.result(each="{result, i in opts.results}", class="{active: i === currentSelection}")
|
||||||
|
h2 {result.name}
|
||||||
|
.description {result.description}
|
||||||
|
|
||||||
|
script.
|
||||||
|
Object.assign(this, {
|
||||||
|
currentSelection: 0,
|
||||||
|
moveSelectionDown: function() {
|
||||||
|
if (this.currentSelection + 1 < opts.results.length) {
|
||||||
|
this.currentSelection += 1;
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
moveSelectionUp: function() {
|
||||||
|
if (this.currentSelection >= 1) {
|
||||||
|
this.currentSelection -= 1;
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.on("update", () => {
|
||||||
|
/* Reset the cursor/selection state... */
|
||||||
|
this.currentSelection = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
style(scoped, type="scss").
|
||||||
|
.result {
|
||||||
|
background-color: white;
|
||||||
|
color: #676767;
|
||||||
|
padding: 9px;
|
||||||
|
border-top: 1px solid #2a333c;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #e2e2e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: black;
|
||||||
|
font-size: 21px;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
require("./component");
|
@ -0,0 +1,11 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const crypto = require("crypto");
|
||||||
|
const sanitizeFilename = require("sanitize-filename");
|
||||||
|
|
||||||
|
module.exports = function escapeCollectionName(name) {
|
||||||
|
let hash = crypto.createHash("sha256").update(name, "utf8").digest("hex");
|
||||||
|
|
||||||
|
/* We 'sanitize' the entire filename with hash included, to ensure that the result has the right maximum length. */
|
||||||
|
return sanitizeFilename(`${name}_${hash}.db`);
|
||||||
|
}
|
@ -0,0 +1,202 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* Rolling your own database is a bad idea. In this particular
|
||||||
|
* case, I am doing it anyway, because there are very clear
|
||||||
|
* constraints, no extreme performance requirements, and most
|
||||||
|
* importantly, all the other in-memory databases seem to
|
||||||
|
* -suck- from an API/usability point of view.
|
||||||
|
*
|
||||||
|
* Limitations:
|
||||||
|
* - Only one process can use the DB at the same time. Yes,
|
||||||
|
* it's a reading lock.
|
||||||
|
* - Persistence is not guaranteed. If the process crashes,
|
||||||
|
* writes may be lost.
|
||||||
|
* - No performance guarantees, whatsoever.
|
||||||
|
* - No schemas.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Promise = require("bluebird");
|
||||||
|
const fs = Promise.promisifyAll(require("fs"));
|
||||||
|
const matchObject = require("match-object");
|
||||||
|
const uuid = require("uuid");
|
||||||
|
const pick = require("object-pick");
|
||||||
|
const rfr = require("rfr");
|
||||||
|
|
||||||
|
const promiseDebounce = rfr("lib/util/promise-debounce");
|
||||||
|
const escapeCollectionName = rfr("lib/db/escape-collection-name"); // FIXME: Actually split this up into collections...
|
||||||
|
|
||||||
|
function getLockPath(path) {
|
||||||
|
return `${path}.lock`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function obtainLock(path) {
|
||||||
|
return Promise.try(() => {
|
||||||
|
return fs.writeFileAsync(getLockPath(path), "", {
|
||||||
|
flag: "wx"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function releaseLock(path) {
|
||||||
|
return Promise.try(() => {
|
||||||
|
return fs.unlinkAsync(getLockPath(path));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadDatabase(path) {
|
||||||
|
return Promise.try(() => {
|
||||||
|
return obtainLock(path);
|
||||||
|
}).then(() => {
|
||||||
|
return fs.readFileAsync(path);
|
||||||
|
}).then((data) => {
|
||||||
|
return JSON.parse(data);
|
||||||
|
}).catch({code: "ENOENT"}, (err) => {
|
||||||
|
/* Initialize a blank database */
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveDatabase(path, collections) {
|
||||||
|
return Promise.try(() => {
|
||||||
|
return fs.writeFileAsync(path, JSON.stringify(collections));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function(path) {
|
||||||
|
return Promise.try(() => {
|
||||||
|
return loadDatabase(path);
|
||||||
|
}).then((collections) => {
|
||||||
|
let queueWrite = promiseDebounce(function() {
|
||||||
|
if (db.opened === true) {
|
||||||
|
saveDatabase(path, collections);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function getCollection(name) {
|
||||||
|
let indexes; // TODO
|
||||||
|
|
||||||
|
function findIdIndex(id) {
|
||||||
|
if (id == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return collections[name].findIndex((item) => item.$id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
find: function(query) {
|
||||||
|
return collections[name].filter((item) => matchObject(query, item));
|
||||||
|
},
|
||||||
|
findOne: function(query) {
|
||||||
|
let result = collections[name].find((item) => matchObject(query, item));
|
||||||
|
|
||||||
|
if (result == null) {
|
||||||
|
throw new Error("No results found");
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
insert: function(object, options = {}) {
|
||||||
|
/* Intentional mutation. */
|
||||||
|
Object.assign(object, {$id: uuid.v4()});
|
||||||
|
collections[name].push(object);
|
||||||
|
queueWrite();
|
||||||
|
},
|
||||||
|
update: function(object, options = {}) {
|
||||||
|
let index;
|
||||||
|
if (index = findIdIndex(object.$id)) {
|
||||||
|
if (options.patch === true) {
|
||||||
|
Object.assign(collections[name][index], object);
|
||||||
|
} else {
|
||||||
|
collections[name][index] = object;
|
||||||
|
}
|
||||||
|
|
||||||
|
queueWrite();
|
||||||
|
} else {
|
||||||
|
throw new Error("No such object exists");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
upsert: function(object, options = {}) {
|
||||||
|
try {
|
||||||
|
this.update(object, options);
|
||||||
|
} catch (err) {
|
||||||
|
this.insert(object, options);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
upsertBy: function(keys, object, options = {}) {
|
||||||
|
let query = pick(object, keys);
|
||||||
|
|
||||||
|
try {
|
||||||
|
let result = this.findOne(query);
|
||||||
|
// FIXME: The following shouldn't be in the try block...
|
||||||
|
object.$id = result.$id;
|
||||||
|
return this.update(object, options);
|
||||||
|
} catch (err) {
|
||||||
|
return this.insert(object, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
delete: function(object) {
|
||||||
|
let index;
|
||||||
|
if (index = findIdIndex(object.$id)) {
|
||||||
|
collections[name].splice(index, 1);
|
||||||
|
queueWrite();
|
||||||
|
} else {
|
||||||
|
throw new Error("No such object exists");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteBy: function(query) {
|
||||||
|
collections[name] = collections[name].filter((item) => !matchObject(query, item));
|
||||||
|
queueWrite();
|
||||||
|
},
|
||||||
|
ensureIndex: function(property) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Cache/TTL collections
|
||||||
|
// TODO: .bak file
|
||||||
|
|
||||||
|
let db = {
|
||||||
|
opened: true,
|
||||||
|
close: function() {
|
||||||
|
return Promise.try(() => {
|
||||||
|
return queueWrite();
|
||||||
|
}).then(() => {
|
||||||
|
return releaseLock(path);
|
||||||
|
}).then(() => {
|
||||||
|
this.opened = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
collection: function(name) {
|
||||||
|
if (collections[name] == null) {
|
||||||
|
collections[name] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return getCollection(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
console.log("Doing cleanup...");
|
||||||
|
if (db.opened === true) {
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.versions.electron != null) {
|
||||||
|
/* Running in Electron */
|
||||||
|
require("electron").app.on("will-quit", () => {
|
||||||
|
cleanup();
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
/* Running in a different Node-y environment */
|
||||||
|
process.on("beforeExit", () => {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return db;
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
function packFunction(func) {
|
||||||
|
return `;${func.toString()};${func.name}();`;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function(window, func) {
|
||||||
|
let stringifiedFunc = packFunction(func);
|
||||||
|
window.webContents.executeJavaScript(stringifiedFunc);
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
let regexes = [
|
||||||
|
/^(?:event:)?'[a-z:\$%-]+'$/i, // events
|
||||||
|
/[a-z0-9_]+\(/, // function signatures
|
||||||
|
/^[a-z0-9_]+(\.[a-z0-9_]+)+/ // attributes
|
||||||
|
]
|
||||||
|
|
||||||
|
module.exports = function(string) {
|
||||||
|
return regexes.some((regex) => regex.test(string));
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
let readmeFilenames = [
|
||||||
|
"readme.md",
|
||||||
|
"readme.rst",
|
||||||
|
"readme",
|
||||||
|
"read me"
|
||||||
|
]
|
||||||
|
|
||||||
|
module.exports = function(filenames) {
|
||||||
|
/* We invert the search here, so that we prioritize by the order of entries
|
||||||
|
* in readmeFilenames, rather than the filenames passed in. */
|
||||||
|
return readmeFilenames.find((filename) => inArray(filenames, filename));
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = function(header, contents) {
|
||||||
|
if (/^(?:module )?api$/i.test(header)) {
|
||||||
|
return "api";
|
||||||
|
} else if (/^events$/i.test(header)) {
|
||||||
|
return "events";
|
||||||
|
} else if (/^options$/i.test(header)) {
|
||||||
|
return "options";
|
||||||
|
} else if (/^(?:tips|notes)$/i.test(header)) {
|
||||||
|
return "notes";
|
||||||
|
} else if (/(?:usage|examples?)/i.test(header)) {
|
||||||
|
// FIXME: Detect API methods under 'usage'
|
||||||
|
return "example";
|
||||||
|
} else if (/(?:caution|^warning$)/i.test(header)) {
|
||||||
|
return "warning";
|
||||||
|
} else {
|
||||||
|
return "other";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Promise = require("bluebird");
|
||||||
|
const bhttp = require("bhttp");
|
||||||
|
|
||||||
|
module.exports = function(cache) {
|
||||||
|
function getFromRemote(packageName) {
|
||||||
|
return Promise.try(() => {
|
||||||
|
let encodedPackageName = encodeURIComponent(packageName).replace(/%40/g, "@");
|
||||||
|
return bhttp.get(`https://registry.npmjs.org/${encodedPackageName}`);
|
||||||
|
}).then((response) => {
|
||||||
|
if (response.statusCode !== 200) {
|
||||||
|
// FIXME: Proper error types
|
||||||
|
throw new Error(`Got non-200 status code from NPM registry: ${response.statusCode}`);
|
||||||
|
} else {
|
||||||
|
return response.body;
|
||||||
|
}
|
||||||
|
}).tap((metadata) => {
|
||||||
|
cache.set(packageName, metadata);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFromCache(packageName) {
|
||||||
|
return Promise.try(() => {
|
||||||
|
return cache.get(packageName);
|
||||||
|
}).then((metadata) => {
|
||||||
|
if (metadata._cacheExpiry < Date.now()) {
|
||||||
|
throw new cache.CacheError("Cached data expired");
|
||||||
|
} else {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return function(packageName) {
|
||||||
|
return Promise.try(() => {
|
||||||
|
return getFromCache(packageName);
|
||||||
|
}).catch(cache.CacheError, (err) => {
|
||||||
|
return getFromRemote(packageName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Promise = require("bluebird");
|
||||||
|
const bhttp = require("bhttp");
|
||||||
|
const querystring = require("querystring");
|
||||||
|
const defaultValue = require("default-value");
|
||||||
|
|
||||||
|
module.exports = function(autocompleteKey) {
|
||||||
|
function createQueryString(query, options) {
|
||||||
|
return querystring.stringify({
|
||||||
|
autocomplete_key: autocompleteKey,
|
||||||
|
_: Date.now(),
|
||||||
|
query: query,
|
||||||
|
num_results: defaultValue(options.resultCount, 20)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createUrl(query, options) {
|
||||||
|
return `https://ac.cnstrc.com/autocomplete/${encodeURIComponent(query)}?${createQueryString(query, options)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return function doSearch(query, options) {
|
||||||
|
return Promise.try(() => {
|
||||||
|
return bhttp.get(createUrl(query, options));
|
||||||
|
}).then((response) => {
|
||||||
|
if (response.statusCode === 200) {
|
||||||
|
return response.body;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Non-200 status code encountered: ${response.statusCode}`)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const createError = require("create-error");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
CacheError: createError("CacheError")
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Promise = require("bluebird");
|
||||||
|
|
||||||
|
module.exports = function(func) {
|
||||||
|
let operationQueued = false;
|
||||||
|
let queuedHandlers = [];
|
||||||
|
let currentPromise;
|
||||||
|
|
||||||
|
function runFunc() {
|
||||||
|
return Promise.try(() => {
|
||||||
|
return func();
|
||||||
|
}).tap(() => {
|
||||||
|
if (operationQueued === true) {
|
||||||
|
currentPromise = runFunc();
|
||||||
|
|
||||||
|
queuedHandlers.forEach((handler) => {
|
||||||
|
handler(currentPromise);
|
||||||
|
});
|
||||||
|
|
||||||
|
operationQueued = false;
|
||||||
|
queuedHandlers = [];
|
||||||
|
} else {
|
||||||
|
currentPromise = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return function() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (currentPromise == null) {
|
||||||
|
currentPromise = runFunc();
|
||||||
|
resolve(currentPromise);
|
||||||
|
} else {
|
||||||
|
operationQueued = true;
|
||||||
|
queuedHandlers.push(resolve);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const isFunction = require("is-function");
|
||||||
|
|
||||||
|
module.exports = function(object, additions) {
|
||||||
|
let wrappedObject = {};
|
||||||
|
|
||||||
|
Object.getOwnPropertyNames(object).forEach((key) => {
|
||||||
|
if (isFunction(object[key])) {
|
||||||
|
wrappedObject[key] = object[key].bind(object);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Object.assign(wrappedObject, additions);
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
html
|
||||||
|
head
|
||||||
|
meta(charset="UTF-8")
|
||||||
|
title Hello World!
|
||||||
|
style.
|
||||||
|
body {
|
||||||
|
background-color: #C12127;
|
||||||
|
color: white;
|
||||||
|
font-family: sans-serif;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
script(src="index.js")
|
||||||
|
body
|
||||||
|
app
|
@ -0,0 +1,40 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
const Promise = require("bluebird");
|
||||||
|
const documentReady = require("document-ready-promise");
|
||||||
|
const documentOffset = require("document-offset");
|
||||||
|
const util = require("util");
|
||||||
|
const riot = require("riot");
|
||||||
|
|
||||||
|
const {ipcRenderer} = require("electron");
|
||||||
|
|
||||||
|
require("../components/app");
|
||||||
|
|
||||||
|
global.triggerWindowResize = function() {
|
||||||
|
let marker = document.querySelector(".window-height-marker");
|
||||||
|
let pageHeight;
|
||||||
|
|
||||||
|
if (marker != null) {
|
||||||
|
pageHeight = documentOffset(marker).top;
|
||||||
|
} else {
|
||||||
|
let body = document.querySelector("body");
|
||||||
|
let rootElement = document.documentElement;
|
||||||
|
|
||||||
|
pageHeight = Math.max(body.scrollHeight, body.offsetHeight, rootElement.scrollHeight, rootElement.offsetHeight, rootElement.clientHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcRenderer.send("resize", {
|
||||||
|
height: pageHeight
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.try(() => {
|
||||||
|
return documentReady();
|
||||||
|
}).then(() => {
|
||||||
|
riot.mount("app");
|
||||||
|
global.triggerWindowResize();
|
||||||
|
|
||||||
|
ipcRenderer.on("focusSearch", () => {
|
||||||
|
document.querySelector("input.search").focus();
|
||||||
|
});
|
||||||
|
})
|
Loading…
Reference in New Issue