diff --git a/app/cyp.js b/app/cyp.js
index 047155a..136d2a3 100644
--- a/app/cyp.js
+++ b/app/cyp.js
@@ -782,12 +782,7 @@ class Selection {
}
}
-class HasApp extends HTMLElement {
- get _app() { return this.closest("cyp-app"); }
- get _mpd() { return this._app.mpd; }
-}
-
-class Component extends HasApp {
+class Component extends HTMLElement {
constructor(options = {}) {
super();
if (options.selection) { this.selection = new Selection(this, options.selection); }
@@ -802,6 +797,9 @@ class Component extends HasApp {
});
}
+ get _app() { return this.closest("cyp-app"); }
+ get _mpd() { return this._app.mpd; }
+
_onAppLoad() {}
_onComponentChange(_component, _isThis) {}
}
@@ -1056,7 +1054,7 @@ class Player extends Component {
customElements.define("cyp-player", Player);
-class Item extends HasApp {
+class Item extends HTMLElement {
constructor() {
super();
this.addEventListener("click", _ => this.onClick());
@@ -1376,12 +1374,31 @@ function decodeChunk(byteArray) {
}
class YT extends Component {
- _onAppLoad() {
- this.querySelector(".download").addEventListener("click", _ => this._download());
- this.querySelector(".search-download").addEventListener("click", _ => this._search());
- this.querySelector(".clear").addEventListener("click", _ => this._clear());
+ connectedCallback() {
+ super.connectedCallback();
+
+ const form = node("form", {}, "", this);
+ const input = node("input", {type:"text"}, "", form);
+ button({icon:"magnify"}, "", form);
+ form.addEventListener("submit", e => {
+ e.preventDefault();
+ const query = input.value.trim();
+ if (!query.length) { return; }
+ this._doSearch(query, form);
+ });
}
+ async _doSearch(query, form) {
+ let response = await fetch(`/youtube?q=${encodeURIComponent(query)}`);
+ let data = await response.json();
+
+ clear(this);
+ this.appendChild(form);
+
+ console.log(data);
+ }
+
+
_download() {
let url = prompt("Please enter a YouTube URL:");
if (!url) { return; }
diff --git a/app/index.html b/app/index.html
index 35e981f..0998b41 100644
--- a/app/index.html
+++ b/app/index.html
@@ -42,14 +42,7 @@
-
-
-
-
-
-
-
-
+
- Theme
diff --git a/app/js/component.js b/app/js/component.js
index e830cd7..6d6a37b 100644
--- a/app/js/component.js
+++ b/app/js/component.js
@@ -1,11 +1,7 @@
import Selection from "./selection.js";
-export class HasApp extends HTMLElement {
- get _app() { return this.closest("cyp-app"); }
- get _mpd() { return this._app.mpd; }
-}
-export default class Component extends HasApp {
+export default class Component extends HTMLElement {
constructor(options = {}) {
super();
if (options.selection) { this.selection = new Selection(this, options.selection); }
@@ -20,6 +16,9 @@ export default class Component extends HasApp {
});
}
+ get _app() { return this.closest("cyp-app"); }
+ get _mpd() { return this._app.mpd; }
+
_onAppLoad() {}
_onComponentChange(_component, _isThis) {}
}
diff --git a/app/js/elements/yt.js b/app/js/elements/yt.js
index 8ae8833..142c9b2 100644
--- a/app/js/elements/yt.js
+++ b/app/js/elements/yt.js
@@ -12,12 +12,31 @@ function decodeChunk(byteArray) {
}
class YT extends Component {
- _onAppLoad() {
- this.querySelector(".download").addEventListener("click", _ => this._download());
- this.querySelector(".search-download").addEventListener("click", _ => this._search());
- this.querySelector(".clear").addEventListener("click", _ => this._clear());
+ connectedCallback() {
+ super.connectedCallback();
+
+ const form = html.node("form", {}, "", this);
+ const input = html.node("input", {type:"text"}, "", form);
+ html.button({icon:"magnify"}, "", form);
+ form.addEventListener("submit", e => {
+ e.preventDefault();
+ const query = input.value.trim();
+ if (!query.length) { return; }
+ this._doSearch(query, form);
+ });
}
+ async _doSearch(query, form) {
+ let response = await fetch(`/youtube?q=${encodeURIComponent(query)}`);
+ let data = await response.json();
+
+ html.clear(this);
+ this.appendChild(form);
+
+ console.log(data);
+ }
+
+
_download() {
let url = prompt("Please enter a YouTube URL:");
if (!url) { return; }
diff --git a/app/js/item.js b/app/js/item.js
index 2fde134..59b1768 100644
--- a/app/js/item.js
+++ b/app/js/item.js
@@ -1,7 +1,7 @@
import * as html from "./html.js";
-import { HasApp } from "./component.js";
-export default class Item extends HasApp {
+
+export default class Item extends HTMLElement {
constructor() {
super();
this.addEventListener("click", _ => this.onClick());
diff --git a/app/js/search.js b/app/js/search.js
deleted file mode 100644
index b3e5d6c..0000000
--- a/app/js/search.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import * as html from "./html.js";
-import * as conf from "./conf.js";
-
-const OPEN = "open";
-const collator = new Intl.Collator(conf.locale, {usage:"search", sensitivity:"base"});
-
-export default class Search extends EventTarget {
- constructor(parent) {
- super();
- this._node = html.node("label", {className:"search"});
-
- this._input = html.node("input", {type:"text"}, "", this._node);
- html.icon("magnify", this._node);
-
- this._node.addEventListener("click", e => {
- if (e.target == this._input) { return; }
- if (this._node.classList.contains(OPEN)) {
- this.reset();
- this.dispatchEvent(new Event("input"));
- } else {
- this._node.classList.add(OPEN);
- }
- });
-
- this._input.addEventListener("input", e => {
- this.dispatchEvent(new Event("input"));
- });
- }
-
- getNode() { return this._node; }
-
- match(str) {
- let q = this._input.value.trim();
- if (!q) { return true; }
- let len = q.length;
- return str.split(" ").some(str => collator.compare(q, str.substring(0, len)) == 0);
- }
-
- reset() {
- this._input.value = "";
- this._node.classList.remove(OPEN);
- }
-}
diff --git a/index.js b/index.js
index cb628b3..b054e86 100644
--- a/index.js
+++ b/index.js
@@ -6,6 +6,27 @@ let tickets = [];
const cmd = "youtube-dl";
+function escape(arg) {
+ return `'${arg.replace(/'/g, `'\\''`)}'`;
+}
+
+function searchYoutube(q, response) {
+ response.setHeader("Content-Type", "text/plain"); // necessary for firefox to read by chunks
+
+ console.log("YouTube searching", q);
+ q = escape(`ytsearch10:${q}`);
+ const command = `${cmd} -j ${q} | jq "{id,title}" | jq -s .`;
+ require("child_process").exec(command, {}, (error, stdout, stderr) => {
+ if (error) {
+ console.log("error", error);
+ response.writeHead(500);
+ response.end(error.message);
+ } else {
+ response.end(stdout);
+ }
+ });
+}
+
function downloadYoutube(q, response) {
response.setHeader("Content-Type", "text/plain"); // necessary for firefox to read by chunks
@@ -34,12 +55,22 @@ function downloadYoutube(q, response) {
});
}
-function handleYoutube(request, response) {
+function handleYoutubeSearch(url, response) {
+ let q = url.searchParams.get("q");
+ if (q) {
+ searchYoutube(q, response);
+ } else {
+ response.writeHead(404);
+ response.end();
+ }
+}
+
+function handleYoutubeDownload(request, response) {
let str = "";
request.setEncoding("utf8");
request.on("data", chunk => str += chunk);
request.on("end", () => {
- let q = require("querystring").parse(str)["q"];
+ let q = require("querystring").parse(str)["id"];
if (q) {
downloadYoutube(q, response);
} else {
@@ -62,11 +93,16 @@ function handleTicket(request, response) {
}
function onRequest(request, response) {
- switch (true) {
- case request.method == "POST" && request.url == "/youtube":
- return handleYoutube(request, response);
+ const url = new URL(request.url, "http://localhost");
- case request.method == "POST" && request.url == "/ticket":
+ switch (true) {
+ case request.method == "GET" && url.pathname == "/youtube":
+ return handleYoutubeSearch(url, response);
+
+ case request.method == "POST" && url.pathname == "/youtube":
+ return handleYoutubeDownload(request, response);
+
+ case request.method == "POST" && url.pathname == "/ticket":
return handleTicket(request, response);
default: