--- /dev/null
+// Listen to messages from our content-script
+chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
+
+
+ // If we're receiving a message with a query, search BookStack
+ // and return the BookStack results in the response.
+ if (request.query) {
+ searchBookStack(request.query).then(results => {
+ if (results) {
+ sendResponse({results});
+ }
+ });
+ }
+
+ // Return true enables 'sendResponse' to work async
+ return true;
+});
+
+
+// Search our BookStack instance using the given query
+async function searchBookStack(query) {
+
+ // Load BookStack API details from our options
+ const options = await loadOptions();
+ for (const option of Object.values(options)) {
+ if (!option) {
+ console.log('Missing a required option');
+ return;
+ }
+ }
+
+ // Query BookStack, making an authorized API search request
+ const url = `${options.baseUrl}/api/search?query=${encodeURIComponent(query)}`;
+ const resp = await fetch(url, {
+ method: 'GET',
+ headers: {
+ Authorization: `Token ${options.tokenId}:${options.tokenSecret}`,
+ }
+ });
+
+ // Parse the JSON response and return the results
+ const data = await resp.json();
+ return data.data || null;
+}
+
+
+/**
+ * Load our options from chrome's storage.
+ * @returns Promise<Object>
+ */
+function loadOptions() {
+ return new Promise((res, rej) => {
+ chrome.storage.sync.get({
+ tokenId: '',
+ tokenSecret: '',
+ baseUrl: '',
+ }, options => {
+ res(options);
+ })
+ });
+}
\ No newline at end of file
--- /dev/null
+const url = new URL(window.location.href);
+const query = url.searchParams.get("q");
+const resultContainer = document.getElementById('search');
+
+// If we have a query in the URL, and a '#search' section, we are
+// likely on a search results page so we kick-off the display of
+// results by messaging the back-end to make the request to BookStack.
+if (query && resultContainer) {
+
+ chrome.runtime.sendMessage({query}, function(response) {
+ // If re receive results back from our background script API call,
+ // show them on the current page.
+ if (response.results) {
+ showResults(response.results);
+ }
+ });
+
+}
+
+/**
+ * Display the given API search result objects as a list of links on
+ * the current page, within the '#search' section.
+ * @param {Object[]} results
+ */
+function showResults(results) {
+ const resultHTML = results.map(result => {
+ return `
+ <a href="${result.url}">
+ <h3>${result.type.toUpperCase()}: ${result.preview_html.name}</h3>
+ <p style="color: #444; text-decoration: none;font-size:0.8em;">${result.preview_html.content}</p>
+ </a>
+ `;
+ }).join('\n');
+
+ const header = `<h4>BookStack Results</h4>`;
+ const container = document.createElement('div');
+ container.innerHTML = header + resultHTML + '<hr>';
+ resultContainer.prepend(container);
+}
\ No newline at end of file
--- /dev/null
+{
+ "name": "BookStack Google Search",
+ "description": "A simple demo extension to show BookStack results in google",
+ "version": "1.0",
+ "manifest_version": 3,
+ "permissions": ["storage"],
+ "host_permissions": ["http://*/", "https://*/"],
+ "options_page": "options.html",
+ "background": {
+ "service_worker": "background.js"
+ },
+ "externally_connectable": {
+ "matches": ["https://*.google.com/*"]
+ },
+ "content_scripts": [
+ {
+ "matches": ["https://*.google.com/*"],
+ "js": ["content-script.js"]
+ }
+ ]
+ }
\ No newline at end of file
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Options</title>
+
+ <style>
+ form > div {
+ margin-bottom: 1rem;
+ }
+ form p {
+ margin: 0;
+ }
+ label {
+ display: block;
+ font-weight: bold;
+ margin-bottom: 0.2rem;
+ }
+ </style>
+</head>
+<body>
+
+ <!-- This is a very simplistic options page to capture BookStack instance API details -->
+
+ <form>
+
+ <div>
+ <label for="base-url">BookStack Base URL</label>
+ <input id="base-url" name="baseUrl" type="text">
+ <p>(No trailing slashes, Do not include '/api/' in URL, Must start with https:// or http://)</p>
+ </div>
+
+ <div>
+ <label for="token-id">API Token ID</label>
+ <input id="token-id" name="tokenId" type="text">
+ </div>
+
+ <div>
+ <label for="token-secret">API Token Secret</label>
+ <input id="token-secret" name="tokenSecret" type="text">
+ </div>
+
+ <button>Save</button>
+
+ <p id="message"></p>
+
+ </form>
+
+
+ <script src="options.js"></script>
+
+</body>
+</html>
\ No newline at end of file
--- /dev/null
+const inputs = [...document.querySelectorAll('input[type="text"]')];
+const form = document.querySelector('form');
+const message = document.getElementById('message');
+
+// Store settings on submit
+form.addEventListener('submit', event => {
+
+ event.preventDefault();
+
+ const settings = {};
+ for (const input of inputs) {
+ settings[input.name] = input.value;
+ }
+
+ chrome.storage.sync.set(settings, () => {
+ message.textContent = 'Settings updated!';
+ });
+
+});
+
+// Restore settings on load
+chrome.storage.sync.get({
+ tokenId: '',
+ tokenSecret: '',
+ baseUrl: '',
+}, settings => {
+ for (const input of inputs) {
+ input.value = settings[input.name];
+ }
+});
\ No newline at end of file
--- /dev/null
+# BookStack in Google Search Results Chrome Extension
+
+This is a very rough and simplistic example of a Google chrome extension that will inject BookStack
+search results into the page when making google.com searches.
+
+**This is only meant as an example or foundation**, it is not a fully featured/finished/tested extension.
+The styles are quite bad and it may be prone to breaking. I am not looking to improve or expand this extension
+so PRs, unless minor issue fixes, will not be accepted.
+
+If you look to build this out into a proper chrome-store extension, please don't use the "BookStack" name
+or logo alone and make it clear your extension is unofficial.
+
+## Requirements
+
+You will need a Chrome (or Chromium based browser) instance where you can enable developer mode in extensions.
+You will also need BookStack API credentials (TOKEN_ID & TOKEN_SECRET) at the ready.
+
+## Usage
+
+This extension is not on the app store but you can side-load it with relative ease.
+Within chrome:
+
+- Go to "Manage Extensions"
+- Toggle "Developer mode" on.
+- Click the "Load unpacked" option.
+- Select this folder.
+
+You will need to configure the extension options and fill in your BookStack instance API details.
+You can get to this by right-clicking the extension in the top of the browser and clicking "Options", or via "Manage Extension" > Click "Details" on the extension > "Extension Options".
\ No newline at end of file