Skip to content

Commit

Permalink
v1.1.1 Relative links converted to fully qualified. Fixed broken link…
Browse files Browse the repository at this point in the history
…s when turndown escapes markdown characters
  • Loading branch information
davidCburke committed Jun 7, 2024
1 parent 38e2e89 commit d695655
Show file tree
Hide file tree
Showing 10 changed files with 256 additions and 62 deletions.
2 changes: 1 addition & 1 deletion Gruntfile.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports = function(grunt) {
const version = '1.1.0';
const version = '1.1.1';
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
Expand Down
92 changes: 68 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,118 +9,162 @@ SilverBullet Clipper is a browser extension that allows you to save either a URL
To use this add-on, simply click the extension icon while you are browsing the page you want to capture. A popup will allow you to change the page title from the default timestamp and add any tags that you want to the page.

# Installation
The extension is available for **Google Chrome** and **Opera** via the [Chrome Web Store](https://chromewebstore.google.com/detail/silverbullet-clipper/nkapoagmecfkneiaejccgkhffdmfmhki). The Firefox extension is available for both the **Firefox Desktop** and **Firefox for Android**. It can be added from [Firefox Browser Add-Ons](https://addons.mozilla.org/addon/silverbullet-clipper/)

The extension is available for **Google Chrome** and **Opera** via the [Chrome Web Store](https://chromewebstore.google.com/detail/silverbullet-clipper/nkapoagmecfkneiaejccgkhffdmfmhki). The Firefox extension is available for both the **Firefox Desktop** and **Firefox for Android**. It can be added from [Firefox Browser Add-Ons](https://addons.mozilla.org/addon/silverbullet-clipper/)

[![](https://img.shields.io/chrome-web-store/v/nkapoagmecfkneiaejccgkhffdmfmhki?style=for-the-badge&logo=googlechrome&logoColor=white&label=google%20chrome%20store&labelColor=grey)](https://chromewebstore.google.com/detail/silverbullet-clipper/nkapoagmecfkneiaejccgkhffdmfmhki)

[![Mozilla Add-on Version](https://img.shields.io/amo/v/silverbullet-clipper?style=for-the-badge&logo=firefox&logoColor=white)](https://addons.mozilla.org/addon/silverbullet-clipper/)

# Usage

## 1. The Capture Page

![Capture Page](images/silverbullet_main.png)

1. The title of the page as it will appear in SilverBullet. This mimics the title of a page created by the SilverBullet "Quick Note" button.
2. When the Append Page Title is selected, the web page title will be added to the title entered in (1.1). This is defaulted to off but can be defaulted to on via the configure page (2.4). The page title will be enclosed in brackets. Eg 2024-05-31 16:28:33 (The Age Newspaper).
3. Multiple tags, separated by spaces, can be added to the page. The tags can be entered with or without a leading hash. For example, this is a valid entry: tag1 #tag2 tag3
2. When the Append Page Title is selected, the web page title will be added to the title entered in (1.1). This is defaulted to off but can be defaulted to on via the configure page (2.4). The page title will be enclosed in brackets. Eg 2024-05-31 16:28:33 (The Age Newspaper).
3. Multiple tags, separated by spaces, can be added to the page. The tags can be entered with or without a leading hash. For example, this is a valid entry: tag1 #tag2 tag3

## 2. The Configure Page

![Capture Page](images/silverbullet_configure.png)

1. The host URL points to the instance of your SilverBullet installation. If you're running SilverBullet locally the URL will be something like http://192.168.86.54:3000. If you are running SilverBullet externally, or accessing it via a proxy, the host URL will be something like https://silverbullet.mydomain.com
2. The token will be whatever token you specified in the SB_AUTH_TOKEN environment variable.

### Command Line Example
```bash
SB_USER=admin:mypassword SB_AUTH_TOKEN=mysuperlongtoken SB_HOSTNAME=0.0.0.0 /root/.deno/bin/silverbullet /root/Silverbullet/space
```
### Docker Compose Example
```bash
services:
silverbullet:
image: zefhemel/silverbullet
restart: unless-stopped
environment:
- SB_AUTH_TOKEN=mysuperlongtoken
- SB_USER=admin:mypassword
volumes:
- ./space:/space
ports:
- 3000:3000
```
### Command Line Example

```bash
SB_USER=admin:mypassword SB_AUTH_TOKEN=mysuperlongtoken SB_HOSTNAME=0.0.0.0 /root/.deno/bin/silverbullet /root/Silverbullet/space
```

### Docker Compose Example

```bash
services:
silverbullet:
image: zefhemel/silverbullet
restart: unless-stopped
environment:
- SB_AUTH_TOKEN=mysuperlongtoken
- SB_USER=admin:mypassword
volumes:
- ./space:/space
ports:
- 3000:3000
```

3. The directory is where your page will be created in SilverBullet. The default directory is Inbox as this is the directory that the SilverBullet Quick Note uses.
4. When the Append Page Title is selected, the Append Page Title check box on the Capture page (1.2) will default to selected.
5. The Max. Title Length determines how long the note's title will be. This is defaulted to 70 characters. If the note title exceeds this length, '...' will indicate that the title has been concatenated. Eg 2024-05-31 16:28:33 (The Age Newspaper Melbourne Aus...)

# Build & Testing

## Instructions to Build the Extensions with Grunt

The extension is already built and can be found in the dist/chrome and dist/firefox folders. If you want to rebuild them the follow these instuctions:

* Prerequisite: [Node.js](https://nodejs.org/)
- Prerequisite: [Node.js](https://nodejs.org/)

1. Install [Grunt](https://gruntjs.com/) via NPM

```bash
npm install -g grunt-cli
npm install grunt --save-dev
```

2. Install the Grunt dependencies

```bash
npm install
```

3a. Build the Chrome extension. The files are built to the dist/chrome folder

```bash
grunt build:chrome
```

3b. Build the Firefox extension. The files are built to the dist/firefox folder

```bash
grunt build:firefox
```

## Instructions to Test Extensions

1. Clone this repo.

### Chrome

The Chrome extension can be found in the dist/chrome folder

1. In Chrome, enter chrome://extensions/ in the URL bar **or** navigate to Setup -> Extensions -> Manage Extensions
2. At the top right, turn on Developer mode.
3. Click Load unpacked.
4. Navigate to the cloned repo and select the folder that has the manifest.json

### Firefox

The Firefox extension can be found in the dist/firefox folder

1. In firefox, enter about:debugging in the URL bar
2. Select This Firefox
3. Select Load Temporary Add-on...

### Firefox for Android
The Firefox extension can be found in the dist/firefox folder. Detailed instructions for testing the extension on Firefox for Android can be found [here](https://extensionworkshop.com/documentation/develop/developing-extensions-for-firefox-for-android/)

The Firefox extension can be found in the dist/firefox folder. Detailed instructions for testing the extension on Firefox for Android can be found [here](https://extensionworkshop.com/documentation/develop/developing-extensions-for-firefox-for-android/)

# External Libraries

SilverBullet Clipper uses the following libraries:

- [PureCSS](https://purecss.io/) by Pure CSS. Version 3.0.0 is used to provide styling to the extension. (Licensed under Yahoo! Inc. BSD-3-Clause license)
- [Turndown](https://github.com/mixmark-io/turndown) by Dom Christie. Version 7.1.3 is used to convert the HTML into markdown. (Licensed under MIT License)
- [Shields.io](https://shields.io/) by Badges. Provides the version badges for git, chrome and firefox on this page

# Permissions

- Access tabs: used to access the website content when the icon in the browser bar is clicked.
- Offscreen: used to open a hidden document where the captured HTML can be processed.
- Scripting: used to access the chrome scripting API that captures the web page content selected.
- Storage: used to save extension options.

# Version History
## 1.1.1

- Fixed broken links when capturing web page content. The links were relative but now are fully qualified
- Fixed broken page source link when the URL contained markdown characters. By default, Turndown escapes markdown characters

## 1.1.0

- Added the option to append the web page title to the note's title when on the capture page. The default is don't append
- Added the option to default the append web title checkbox to true. The default is false
- Added the option to set the length of the note's title (the default is 70 characters). Trailing '...' will show that the title has been concatenated

## 1.0.0

- Added ability to specify the SilverBullet directory the capture will be saved to. Thanks [dklawran](https://github.com/dklawren)

## 0.3.1

- Changed link to new SilverBullet page from encodeURIComponent() to encodeURI() to make the link consistent with how SilverBullet formats links.
- Removed '.md' from the link to the new SilverBullet page as it's redundant
- Fixed missing image when looking at the extension in My Extensions
- Sanatized HTML for enhanced security

## 0.3.0

- Added a user friendly error if the send to SilverBullet fails
- Fixed a missing style on the legend element needed for dark mode
- Added firefox version

## 0.2.0

- Added support for light and dark theme preferences

## 0.1.0
- Beta development

- Beta development
6 changes: 4 additions & 2 deletions dist/chrome/js/offscreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ function convertToMarkdown(htmlString, url, title, tags) {
tagsMarkdown += '</p>'
}
if(htmlString != null) { //If there is HTML then create the markdown using the tags and creating a line for the source URL of the capture
markdown = turndownService.turndown(tagsMarkdown + htmlString + '<p>source: ' + url + '</p>');
markdown = turndownService.turndown(tagsMarkdown + htmlString + '<p>source: urlPlaceholder</p>');
} else { //If there is no HTML then just capture the tab URL and any tags
markdown = turndownService.turndown(tagsMarkdown + '<p>' + url + '</p>');
markdown = turndownService.turndown(tagsMarkdown + '<p>urlPlaceholder</p>');
}
//The turndown service escapes any markdown characters in the URL and breaks the link so add the url after markdown coversion
markdown = markdown.replace("urlPlaceholder", url)
//Send the markdown to the service worker
sendToServiceWorker(
'convert-to-markdown-result',
Expand Down
53 changes: 44 additions & 9 deletions dist/chrome/js/service-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,52 @@ async function getTextFromSelection(tabId) {
chrome.scripting.executeScript({
target: { tabId: tabId },
function: () => {
var range = window.getSelection().getRangeAt(0);
var div = document.createElement('div');
div.appendChild(range.cloneContents());
var html = div.innerHTML;
return html;
},
}, (result) => {
function makeUrlsAbsolute(html, baseUrl) {
const div = document.createElement('div');
div.innerHTML = html;
const baseElement = document.createElement('base');
baseElement.href = baseUrl;
div.prepend(baseElement);

const elements = div.querySelectorAll('[src], [href]');
elements.forEach((el) => {
if (el.hasAttribute('href')) {
el.href = new URL(el.getAttribute('href'), baseUrl).href;
}
if (el.hasAttribute('src')) {
el.src = new URL(el.getAttribute('src'), baseUrl).href;
}
});

baseElement.remove();
return div.innerHTML;
}

try {
const selection = window.getSelection();
if (!selection.rangeCount) {
throw new Error('No selection range found');
}
const range = selection.getRangeAt(0);
const div = document.createElement('div');
div.appendChild(range.cloneContents());
const html = div.innerHTML;
const absoluteHtml = makeUrlsAbsolute(html, document.location.href);
return absoluteHtml;
} catch (error) {
console.error('Error getting selection:', error);
return null;
}
}
}, (results) => {
if (chrome.runtime.lastError) {
console.error('Script execution error:', chrome.runtime.lastError);
reject(chrome.runtime.lastError);
} else if (results && results[0] && results[0].result) {
resolve(results[0].result);
} else {
resolve(result[0].result);
console.warn('No result returned from script execution');
resolve(null);
}
});
});
Expand Down Expand Up @@ -168,4 +203,4 @@ function sendCaptureToEndpoint(markdown, title) {
});
});
});
}
}
2 changes: 1 addition & 1 deletion dist/chrome/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "SilverBullet Clipper",
"version": "1.1.0",
"version": "1.1.1",
"manifest_version": 3,
"description": "SilverBullet Clipper captures a web page URL or selected content, converts it to markdown and saves it in the SilverBullet Inbox",
"icons": {
Expand Down
6 changes: 4 additions & 2 deletions dist/firefox/js/offscreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ function convertToMarkdown(htmlString, url, title, tags) {
tagsMarkdown += '</p>'
}
if(htmlString != null) { //If there is HTML then create the markdown using the tags and creating a line for the source URL of the capture
markdown = turndownService.turndown(tagsMarkdown + htmlString + '<p>source: ' + url + '</p>');
markdown = turndownService.turndown(tagsMarkdown + htmlString + '<p>source: urlPlaceholder</p>');
} else { //If there is no HTML then just capture the tab URL and any tags
markdown = turndownService.turndown(tagsMarkdown + '<p>' + url + '</p>');
markdown = turndownService.turndown(tagsMarkdown + '<p>urlPlaceholder</p>');
}
//The turndown service escapes any markdown characters in the URL and breaks the link so add the url after markdown coversion
markdown = markdown.replace("urlPlaceholder", url)
//Send the markdown to the service worker
sendToServiceWorker(
'convert-to-markdown-result',
Expand Down
49 changes: 43 additions & 6 deletions dist/firefox/js/service-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,47 @@ async function getTextFromSelection(tabId) {
return new Promise((resolve, reject) => {
browser.tabs.executeScript(tabId, {
code: `
var range = window.getSelection().getRangeAt(0);
var div = document.createElement('div');
div.appendChild(range.cloneContents());
var html = div.innerHTML;
html;
function makeUrlsAbsolute(html, baseUrl) {
var div = document.createElement('div');
div.innerHTML = html;
var baseElement = document.createElement('base');
baseElement.href = baseUrl;
div.prepend(baseElement);
// Resolve relative URLs to absolute URLs
var elements = div.querySelectorAll('[src], [href]');
elements.forEach(function(el) {
if (el.hasAttribute('href')) {
el.href = new URL(el.getAttribute('href'), baseUrl).href;
}
if (el.hasAttribute('src')) {
el.src = new URL(el.getAttribute('src'), baseUrl).href;
}
});
baseElement.remove(); // Remove the base element after processing
return div.innerHTML;
}
var selection = window.getSelection();
var absoluteHtml;
if (selection.rangeCount > 0) {
var range = selection.getRangeAt(0);
var div = document.createElement('div');
div.appendChild(range.cloneContents());
var html = div.innerHTML;
// Process text with makeUrlsAbsolute
absoluteHtml = makeUrlsAbsolute(html, document.location.href);
} else {
// If no text is selected, return null
absoluteHtml = null;
}
// Return the processed HTML with absolute URLs or null if no text selected
absoluteHtml;
`
})
.then(result => {
Expand All @@ -20,6 +56,7 @@ async function getTextFromSelection(tabId) {
});
}


/* Get the title of the web page from the tab */
async function getTitleFromTab(tabId) {
return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -138,4 +175,4 @@ function sendCaptureToEndpoint(markdown, title) {
});
});
});
}
}
2 changes: 1 addition & 1 deletion dist/firefox/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "SilverBullet Clipper",
"version": "1.1.0",
"version": "1.1.1",
"manifest_version": 2,
"description": "SilverBullet Clipper captures a web page URL or selected content, converts it to markdown and saves it in the SilverBullet Inbox",
"icons": {
Expand Down
6 changes: 4 additions & 2 deletions src/js/offscreen.template.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ function convertToMarkdown(htmlString, url, title, tags) {
tagsMarkdown += '</p>'
}
if(htmlString != null) { //If there is HTML then create the markdown using the tags and creating a line for the source URL of the capture
markdown = turndownService.turndown(tagsMarkdown + htmlString + '<p>source: ' + url + '</p>');
markdown = turndownService.turndown(tagsMarkdown + htmlString + '<p>source: urlPlaceholder</p>');
} else { //If there is no HTML then just capture the tab URL and any tags
markdown = turndownService.turndown(tagsMarkdown + '<p>' + url + '</p>');
markdown = turndownService.turndown(tagsMarkdown + '<p>urlPlaceholder</p>');
}
//The turndown service escapes any markdown characters in the URL and breaks the link so add the url after markdown coversion
markdown = markdown.replace("urlPlaceholder", url)
//Send the markdown to the service worker
sendToServiceWorker(
'convert-to-markdown-result',
Expand Down
Loading

0 comments on commit d695655

Please sign in to comment.