Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/search files #84

Merged
merged 13 commits into from
Dec 13, 2022
9 changes: 7 additions & 2 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@
"description": "Surf the Net in Obsidian.",
"author": "Boninall",
"authorUrl": "https://github.com/Quorafind",
"isDesktopOnly": true
}
"isDesktopOnly": true,
"fundingUrl": {
"Buy Me a Coffee": "https://www.buymeacoffee.com/boninall",
"爱发电": "https://afdian.net/a/boninall",
"支付宝": "https://cdn.jsdelivr.net/gh/Quorafind/.github@main/IMAGE/%E6%94%AF%E4%BB%98%E5%AE%9D%E4%BB%98%E6%AC%BE%E7%A0%81.jpg"
}
}
File renamed without changes.
210 changes: 210 additions & 0 deletions src/component/InNodeWebView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import { InPageHeaderBar } from "./InPageHeaderBar";
// @ts-ignore
import { clipboard, remote } from "electron";
import { SurfingView } from "../surfingView";
import { t } from "../translations/helper";
import { moment } from "obsidian";

export class InNodeWebView {
private contentEl: HTMLElement;
private iframeEl: HTMLElement;
private node: any;

constructor(node: any) {
this.contentEl = node.contentEl;
this.node = node;
}

onload() {
this.contentEl.empty();

const searchBarEl = new InPageHeaderBar(this.node, this.node.url);
searchBarEl.onload();
searchBarEl.addOnSearchBarEnterListener((url: string) => {
this.iframeEl.setAttribute("src", url);
searchBarEl.setSearchBarUrl(url);

const oldData = this.node.getData();
if (oldData.url === url) return;
oldData.url = url;
this.node.setData(oldData);
this.node.canvas.requestSave();

this.node.render();
});
searchBarEl.setSearchBarUrl(this.node.url);

// Create main web view frame that displays the website.
this.iframeEl = document.createElement("webview") as unknown as HTMLIFrameElement;
this.iframeEl.setAttribute("allowpopups", "");

// CSS classes makes frame fill the entire tab's content space.
this.iframeEl.addClass("wb-frame");
this.contentEl.addClass("wb-view-content");
this.contentEl.appendChild(this.iframeEl);

this.iframeEl.setAttribute("src", this.node.url);
this.node.placeholderEl.innerText = this.node.url;

this.iframeEl.addEventListener("dom-ready", (event: any) => {
// @ts-ignore
const webContents = remote.webContents.fromId(this.iframeEl.getWebContentsId());

// Open new browser tab if the web view requests it.
webContents.setWindowOpenHandler((event: any) => {
SurfingView.spawnWebBrowserView(true, { url: event.url });
return {
action: "allow",
}
});


webContents.on("context-menu", (event: any, params: any) => {
event.preventDefault();

const { Menu, MenuItem } = remote;
const menu = new Menu();
// Basic Menu For Webview
// TODO: Support adding different commands to the menu.
// Possible to use Obsidian Default API?
menu.append(
new MenuItem(
{
label: t('Open Current URL In External Browser'),
click: function () {
window.open(params.pageURL, "_blank");
}
}
)
);

menu.append(
new MenuItem(
{
label: 'Open Current URL In Surfing',
click: function () {
window.open(params.pageURL);
}
}
)
);

if (params.selectionText) {
const pluginSettings = app.plugins.getPlugin("surfing").settings;

menu.append(new MenuItem({ type: 'separator' }));
menu.append(new MenuItem({
label: t('Search Text'), click: function () {
try {
SurfingView.spawnWebBrowserView(true, { url: params.selectionText });
console.log('Page URL copied to clipboard');
} catch (err) {
console.error('Failed to copy: ', err);
}
}
}));
menu.append(new MenuItem({ type: 'separator' }));
menu.append(new MenuItem({
label: t('Copy Plain Text'), click: function () {
try {
webContents.copy();
console.log('Page URL copied to clipboard');
} catch (err) {
console.error('Failed to copy: ', err);
}
}
}));
const highlightFormat = pluginSettings.highlightFormat;
menu.append(new MenuItem({
label: t('Copy Link to Highlight'), click: function () {
try {
// eslint-disable-next-line no-useless-escape
const linkToHighlight = params.pageURL.replace(/\#\:\~\:text\=(.*)/g, "") + "#:~:text=" + encodeURIComponent(params.selectionText);
const selectionText = params.selectionText;
let link = "";
if (highlightFormat.contains("{TIME")) {
// eslint-disable-next-line no-useless-escape
const timeString = highlightFormat.match(/\{TIME\:[^\{\}\[\]]*\}/g)?.[0];
if (timeString) {
// eslint-disable-next-line no-useless-escape
const momentTime = moment().format(timeString.replace(/{TIME:([^\}]*)}/g, "$1"));
link = highlightFormat.replace(timeString, momentTime);
}
}
link = (link != "" ? link : highlightFormat).replace(/\{URL\}/g, linkToHighlight).replace(/\{CONTENT\}/g, selectionText);
clipboard.writeText(link);
console.log('Link URL copied to clipboard');
} catch (err) {
console.error('Failed to copy: ', err);
}
}
}));

menu.popup(webContents);
}

if (params.pageURL?.contains("bilibili.com/")) {
menu.append(new MenuItem({
label: t('Copy Video Timestamp'), click: function () {
try {
webContents.executeJavaScript(`
var time = document.querySelectorAll('.bpx-player-ctrl-time-current')[0].innerHTML;
var timeYMSArr=time.split(':');
var joinTimeStr='00h00m00s';
if(timeYMSArr.length===3){
joinTimeStr=timeYMSArr[0]+'h'+timeYMSArr[1]+'m'+timeYMSArr[2]+'s';
}else if(timeYMSArr.length===2){
joinTimeStr=timeYMSArr[0]+'m'+timeYMSArr[1]+'s';
}
var timeStr= "";
var pageStrMatch = window.location.href.match(/(p=[1-9]{1,})/g);
var pageStr = "";
if(typeof pageStrMatch === "object" && pageStrMatch?.length > 0){
pageStr = '&' + pageStrMatch[0];
}else if(typeof pageStrMatch === "string") {
pageStr = '&' + pageStrMatch;
}
timeStr = window.location.href.split('?')[0]+'?t=' + joinTimeStr + pageStr;
`, true).then((result: any) => {
clipboard.writeText("[" + result.split('?t=')[1].replace(/&p=[1-9]{1,}/g, "") + "](" + result + ")"); // Will be the JSON object from the fetch call
});
console.log('Page URL copied to clipboard');
} catch (err) {
console.error('Failed to copy: ', err);
}
}
}));
}

setTimeout(() => {
menu.popup(webContents);
// Dirty workaround for showing the menu, when currentUrl is not the same as the url of the webview
if (this.node.url !== params.pageURL && !params.selectionText) {
menu.popup(webContents);
}
}, 0)
}, false);
});

this.iframeEl.addEventListener("will-navigate", (event: any) => {
const oldData = this.node.getData();
oldData.url = event.url;
this.node.setData(oldData);
this.node.canvas.requestSave();
});

this.iframeEl.addEventListener("did-navigate-in-page", (event: any) => {
const oldData = this.node.getData();
if (event.url.contains("contacts.google.com/widget") || (this.node.canvas.isDragging && oldData.url === event.url)) {
// @ts-ignore
const webContents = remote.webContents.fromId(this.iframeEl.getWebContentsId());
webContents.stop();
return;
}
if (oldData.url === event.url) return;
oldData.url = event.url;
this.node.setData(oldData);
this.node.canvas.requestSave();
});
}
}
65 changes: 65 additions & 0 deletions src/component/InPageHeaderBar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { t } from "../translations/helper";


export class InPageHeaderBar {
private node: any;
private url: string;
private searchBar: HTMLInputElement;
private onSearchBarEnterListener = new Array<(url: string) => void>;

constructor(node: any, url: string) {
this.node = node;
this.url = url
}

onload() {
const pluginSettings = app.plugins.getPlugin("surfing").settings;

this.searchBar = this.node?.contentEl.createEl("input", {
type: "text",
placeholder: t("Search with") + pluginSettings.defaultSearchEngine + t("or enter address"),
cls: "wb-search-bar"
});

// TODO: Would this be ok to use Obsidian add domlistener instead?
this.searchBar.addEventListener("keydown", (event: KeyboardEvent) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
if (!event) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const event = window.event as KeyboardEvent;
}
if (event.key === "Enter") {
// When enter is pressed, search for the url.
for (const listener of this.onSearchBarEnterListener) {
listener(this.searchBar.value);
}
}
}, false);

// Use focusin to bubble up to the parent
// Rather than just input element itself.
this.searchBar.addEventListener("focusin", (event: FocusEvent) => {
this.searchBar.select();
})

// When focusout, unselect the text to prevent it is still selected when focus back
// It will trigger some unexpected behavior,like you will not select all text and the cursor will set to current position;
// The expected behavior is that you will select all text when focus back;
this.searchBar.addEventListener("focusout", (event: FocusEvent) => {
window.getSelection()?.removeAllRanges();
})
}

addOnSearchBarEnterListener(listener: (url: string) => void) {
this.onSearchBarEnterListener.push(listener);
}

setSearchBarUrl(url: string) {
this.searchBar.value = url;
}

focus() {
this.searchBar.focus();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import SurfingPlugin from "../surfingIndex";
import { ButtonComponent, ItemView } from "obsidian";
import { t } from "src/translations/helper";

export class SearchBarIconList {
export class InPageIconList {
plugin: SurfingPlugin;
view: ItemView;
private closeBtnEl: HTMLElement;
Expand Down
Loading