Compare commits

...

10 commits

9 changed files with 2810 additions and 2026 deletions

View file

@ -12,10 +12,8 @@
"one-var": ["error", "never"], "one-var": ["error", "never"],
"quotes": ["error", "double"], "quotes": ["error", "double"],
"semi": "error", "semi": "error",
"space-before-function-paren": ["error", {"anonymous": "always", "named": "always", "asyncArrow": "always"}], "space-before-function-paren": ["error", {"anonymous": "never", "named": "never", "asyncArrow": "always"}],
"space-before-blocks": ["error", "always"], "space-before-blocks": ["error", "always"],
"space-infix-ops": "error" "space-infix-ops": "error"
} }
} }
// vim: ft=json

View file

@ -10,7 +10,6 @@ const gulpif = require("gulp-if");
const log = require("fancy-log"); const log = require("fancy-log");
const plumber = require("gulp-plumber"); const plumber = require("gulp-plumber");
const replaceExt = require("replace-ext"); const replaceExt = require("replace-ext");
const sequence = require("run-sequence");
const sass = require("gulp-sass"); const sass = require("gulp-sass");
const source = require("vinyl-source-stream"); const source = require("vinyl-source-stream");
const sourcemaps = require("gulp-sourcemaps"); const sourcemaps = require("gulp-sourcemaps");
@ -32,18 +31,25 @@ const FONT_AWESOME_OUT_DIR = "spectabular/webfonts";
//Map the various directories to the gulp tasks //Map the various directories to the gulp tasks
const WATCH_MAPPINGS = { const WATCH_MAPPINGS = {
[TS_SRC_DIR]: ["typescript-lint", "typescript"], [TS_SRC_DIR]: ["typescript"],
[SASS_SRC_DIR]: ["sass"], [SASS_SRC_DIR]: ["sass"],
[TWIG_SRC_DIR]: ["twig"], [TWIG_SRC_DIR]: ["twig"],
}; };
let isProdBuild = yargs.argv.hasOwnProperty("prod"); let isProdBuild = yargs.argv.hasOwnProperty("prod");
gulp.task("build", (callback) => { function buildAll(done) {
sequence("gulpfile-lint", "typescript-lint", "typescript", "fontawesome", "sass", "twig", callback); return gulp.series(
}); "lint-gulpfile",
gulp.parallel(
"typescript",
"sass",
"twig",
"fontawesome"
)
)(done);
}
gulp.task("clean", () => { function clean() {
return del([ return del([
JS_OUT_DIR, JS_OUT_DIR,
CSS_OUT_DIR, CSS_OUT_DIR,
@ -52,9 +58,9 @@ gulp.task("clean", () => {
].map((folder) => { ].map((folder) => {
return path.join(folder, "*"); return path.join(folder, "*");
})); }));
}); }
gulp.task("watch", () => { function watch(done) {
Object.keys(WATCH_MAPPINGS).forEach((dir) => { Object.keys(WATCH_MAPPINGS).forEach((dir) => {
let globbedPath = path.join(dir, "*"); let globbedPath = path.join(dir, "*");
let tasks = WATCH_MAPPINGS[dir]; let tasks = WATCH_MAPPINGS[dir];
@ -64,15 +70,31 @@ gulp.task("watch", () => {
log(`[${chalk.blue(tasks.join(", "))}] Change detected: ${chalk.green(relativePath)}`); log(`[${chalk.blue(tasks.join(", "))}] Change detected: ${chalk.green(relativePath)}`);
}); });
}); });
}); done();
};
gulp.task("typescript", (callback) => { function lintGulpfile() {
return gulp.src("gulpfile.js")
.pipe(plumber())
.pipe(eslint())
.pipe(eslint.format("stylish"))
.pipe(eslint.failAfterError());
}
function lintTypescript() {
return gulp.src(path.join(TS_SRC_DIR, "*.ts"))
.pipe(plumber())
.pipe(tslint({
formatter: "stylish"
}))
.pipe(tslint.report());
}
function compileTypescript(done) {
let promises = TS_ENTRYPOINTS.map(async (entrypoint) => { let promises = TS_ENTRYPOINTS.map(async (entrypoint) => {
let entrypointPath = path.join(TS_SRC_DIR, entrypoint); let entrypointPath = path.join(TS_SRC_DIR, entrypoint);
let bundler = browserify(entrypointPath, { let bundler = browserify(entrypointPath, {debug: !isProdBuild})
plugin: ["tsify"], .plugin("tsify", {target: "ES2017"});
debug: !isProdBuild
});
let stream = plumber() let stream = plumber()
.pipe(bundler.bundle()) .pipe(bundler.bundle())
.pipe(source(replaceExt(entrypoint, ".js"))) .pipe(source(replaceExt(entrypoint, ".js")))
@ -82,11 +104,11 @@ gulp.task("typescript", (callback) => {
}); });
Promise.all(promises).then(() => { Promise.all(promises).then(() => {
callback(); done();
}); });
}); }
gulp.task("sass", () => { function compileSass() {
return gulp.src(path.join(SASS_SRC_DIR, "*.scss")) return gulp.src(path.join(SASS_SRC_DIR, "*.scss"))
.pipe(plumber()) .pipe(plumber())
.pipe(sourcemaps.init()) .pipe(sourcemaps.init())
@ -95,34 +117,26 @@ gulp.task("sass", () => {
}).on("error", sass.logError)) }).on("error", sass.logError))
.pipe(gulpif(!isProdBuild, sourcemaps.write())) .pipe(gulpif(!isProdBuild, sourcemaps.write()))
.pipe(gulp.dest(CSS_OUT_DIR)); .pipe(gulp.dest(CSS_OUT_DIR));
}); }
gulp.task("twig", () => { function copyTwig() {
return gulp.src(path.join(TWIG_SRC_DIR, "*.{html,html.twig}")) return gulp.src(path.join(TWIG_SRC_DIR, "*.{html,html.twig}"))
.pipe(gulp.dest(TWIG_OUT_DIR)); .pipe(gulp.dest(TWIG_OUT_DIR));
}); }
gulp.task("gulpfile-lint", () => { function copyFontawesome() {
return gulp.src("gulpfile.js")
.pipe(plumber())
.pipe(eslint())
.pipe(eslint.format("stylish"))
.pipe(eslint.failAfterError());
});
gulp.task("typescript-lint", () => {
return gulp.src(path.join(TS_SRC_DIR, "*.ts"))
.pipe(plumber())
.pipe(tslint({
formatter: "stylish"
}))
.pipe(tslint.report());
});
gulp.task("fontawesome", () => {
return gulp.src([ return gulp.src([
path.join(FONT_AWESOME_BASE_DIR, "webfonts/*"), path.join(FONT_AWESOME_BASE_DIR, "webfonts/*"),
path.join(FONT_AWESOME_BASE_DIR, "LICENSE.txt") path.join(FONT_AWESOME_BASE_DIR, "LICENSE.txt")
]) ])
.pipe(gulp.dest(FONT_AWESOME_OUT_DIR)); .pipe(gulp.dest(FONT_AWESOME_OUT_DIR));
}); }
gulp.task("typescript", gulp.series(lintTypescript, compileTypescript));
gulp.task("sass", compileSass);
gulp.task("twig", copyTwig);
gulp.task("fontawesome", copyFontawesome);
gulp.task("lint-gulpfile", lintGulpfile);
gulp.task(watch);
gulp.task("default", buildAll);
gulp.task(clean);

4653
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -14,7 +14,7 @@
"del": "^3.0.0", "del": "^3.0.0",
"eslint": "^5.0.1", "eslint": "^5.0.1",
"fancy-log": "^1.3.2", "fancy-log": "^1.3.2",
"gulp": "^3.9.1", "gulp": "^4.0.0",
"gulp-eslint": "^4.0.2", "gulp-eslint": "^4.0.2",
"gulp-if": "^2.0.2", "gulp-if": "^2.0.2",
"gulp-plumber": "^1.2.0", "gulp-plumber": "^1.2.0",

View file

@ -1,3 +1,6 @@
@import "@fortawesome/fontawesome-free/scss/fontawesome.scss";
@import "@fortawesome/fontawesome-free/scss/fa-solid.scss";
.tab-list { .tab-list {
list-style: none; list-style: none;
//Prevent margin breakout of lis //Prevent margin breakout of lis
@ -8,10 +11,9 @@
$color-2: #b7c3ce; $color-2: #b7c3ce;
$hover-color: #9aa4ad; $hover-color: #9aa4ad;
text-overflow: ellipsis; display: flex;
overflow: hidden;
white-space: nowrap;
padding: 5px 4px; padding: 5px 4px;
white-space: nowrap;
background-color: $color-1; background-color: $color-1;
&:nth-child(2n) { &:nth-child(2n) {
@ -21,6 +23,11 @@
&:hover { &:hover {
background-color: $hover-color; background-color: $hover-color;
} }
.tab-title {
text-overflow: ellipsis;
overflow: hidden;
flex: auto;
}
} }
} }

View file

@ -10,6 +10,7 @@ document.addEventListener("DOMContentLoaded", () => {
tabs.getWindows((windows: chrome.windows.Window[]) => { tabs.getWindows((windows: chrome.windows.Window[]) => {
document.querySelector("body").innerHTML = String(MAIN_TEMPLATE.render({windows})); document.querySelector("body").innerHTML = String(MAIN_TEMPLATE.render({windows}));
registerTabClickListeners(); registerTabClickListeners();
registerCloseButtonClickListeners();
}); });
}); });
@ -28,3 +29,18 @@ function registerTabClickListeners(): void {
}); });
}); });
} }
/**
* registerCloseButtonClickListeners registers click listeners to close tabs.
*
* @returns {void}
*/
function registerCloseButtonClickListeners(): void {
let closeButtons = document.getElementsByClassName("close-button");
[].forEach.call(closeButtons, (element) => {
element.addEventListener("click", (event) => {
let tabID = parseInt(element.parentNode.getAttribute("tab-id"));
tabs.closeTab(tabID);
});
});
}

View file

@ -1,4 +1,4 @@
export {getWindows, switchTo}; import * as util from "./util";
let windowCache: chrome.windows.Window[] = []; let windowCache: chrome.windows.Window[] = [];
@ -8,7 +8,7 @@ let windowCache: chrome.windows.Window[] = [];
* @param {(windows: chrome.windows.Window[]) => void} callback * @param {(windows: chrome.windows.Window[]) => void} callback
* @returns {void} * @returns {void}
*/ */
function getWindows(callback: (windows: chrome.windows.Window[]) => void): void { export function getWindows(callback: (windows: chrome.windows.Window[]) => void): void {
chrome.windows.getAll({populate: true}, (windows : chrome.windows.Window[]) => { chrome.windows.getAll({populate: true}, (windows : chrome.windows.Window[]) => {
windowCache = windows; windowCache = windows;
callback(windows); callback(windows);
@ -20,10 +20,33 @@ function getWindows(callback: (windows: chrome.windows.Window[]) => void): void
* *
* @param {number} windowID the tab in which the window is contained. * @param {number} windowID the tab in which the window is contained.
* @param {number} tabID The tab to switch to. * @param {number} tabID The tab to switch to.
* @returns {void} * @returns {PromiseLike<chrome.tabs.Tab>}
*/ */
function switchTo(windowID: number, tabID: number): void { export function switchTo(windowID: number, tabID: number): PromiseLike<chrome.tabs.Tab> {
chrome.windows.update(windowID, {focused: true}, () => { let curriedWindowUpdate = (windowID: number, options: object) => {
chrome.tabs.update(tabID, {active: true}); return (callback: ((win: chrome.windows.Window) => void)) => chrome.windows.update(windowID, options, callback);
}); };
let curriedTabUpdate = (tabID: number, options: object) => {
return (callback: (tab: chrome.tabs.Tab) => void) => chrome.tabs.update(tabID, options, callback);
};
return util.callbackToPromise(curriedWindowUpdate(windowID, {focused: true}))
.then((returns: chrome.windows.Window[]) => util.callbackToPromise(curriedTabUpdate(tabID, {active: true})))
.then((returns: chrome.tabs.Tab[]) => returns[0]);
}
/**
* closeTab closes a tab with the given id
*
* @param {number} tabID
* @param {() => void} callback
* @returns {PromiseLike<void>}
*/
export function closeTab(tabID: number, callback?: () => void): PromiseLike<void> {
let curriedRemoveTab = (tabID: number) => {
return (callback: () => void) => chrome.tabs.remove(tabID);
};
return util.callbackToPromise(curriedRemoveTab(tabID))
.then(() => {}); // Force a removal of the return type
} }

14
src/ts/util.ts Normal file
View file

@ -0,0 +1,14 @@
/**
* callbackToPromise converts a callback based function to a promise based function.
* The function's only argument must be a function.
*
* @param {(Function) => void} func
* @returns {PromiseLike<any[]>}
*/
export function callbackToPromise(func: (Function) => void): PromiseLike<any[]> {
return new Promise((resolve) => {
func((...callbackArgs) => {
resolve(callbackArgs);
});
});
}

View file

@ -5,6 +5,7 @@
<span class="tab-title"> <span class="tab-title">
{{ tab.title }} {{ tab.title }}
</span> </span>
<i class="close-button fas fa-times-circle"></i>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>