initial commit - everything works!!
This commit is contained in:
commit
7766f4c5bc
7 changed files with 219 additions and 0 deletions
BIN
.DS_Store
vendored
Normal file
BIN
.DS_Store
vendored
Normal file
Binary file not shown.
31
README.md
Normal file
31
README.md
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# Sharefeed Entry Tool
|
||||||
|
|
||||||
|
A silly little Chrome extension I created to more easily create Sharefeed entries while I am on the web.
|
||||||
|
|
||||||
|
When you click the moffsoft icon, a popup will show up with all of the possible fields, with some or even most of them auto-populated with
|
||||||
|
data from the page. Clicking "generate" will generate the JSON code in a field you can easily copy and paste from.
|
||||||
|
|
||||||
|
This is just a tool I made for myself, but if you want to use it for your own implementation of the Sharefeed... I mean....... I guess?
|
||||||
|
|
||||||
|
## Supported Sites
|
||||||
|
|
||||||
|
You can technically use this extension on *any* site, as it will always automatically fill in the URL and date accessed fields for you.
|
||||||
|
However, I've built in additional autofill support for certain websites.
|
||||||
|
|
||||||
|
- Medium: Title, author, and date published fields are autofilled from the handy [`NewsArticle` JSON-LD schema](https://schema.org/NewsArticle)
|
||||||
|
included on every page.
|
||||||
|
|
||||||
|
## Wait, what is this even for?
|
||||||
|
|
||||||
|
I have a page on my site called the [sharefeed](https://eleboog.com/sharefeed) where I share links to things I think are interesting.
|
||||||
|
To make the implementation of this slightly easier on myself, I've stored all the links in a JSON file where each entry is a single object
|
||||||
|
in the following format:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
url: string // The URL of the linked page.
|
||||||
|
title: string // The title of the entry. Usually analogous to the title of the linked page or article.
|
||||||
|
author: string? // The author of the linked page.
|
||||||
|
publishedDate: Date? // The date in which the linked page was **originally published**.
|
||||||
|
accessedDate: Date // The date in which the linked page was **accessed by me** (i.e. the date of this entry itself).
|
||||||
|
note: string // A message to be attached to this link, usually detailing why I decided to share it.
|
||||||
|
```
|
15
content.js
Normal file
15
content.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
//==================================================
|
||||||
|
// sharefeed entry tool - by kebokyo
|
||||||
|
// content.js - examines the content of the page
|
||||||
|
// for data autofill
|
||||||
|
// created june 21, 2025
|
||||||
|
//==================================================
|
||||||
|
|
||||||
|
|
||||||
|
let entry = {
|
||||||
|
title: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
// if schema.org JSON-LD is present, read that data
|
||||||
|
|
||||||
|
return entry;
|
15
manifest.json
Normal file
15
manifest.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"name": "Sharefeed Entry Tool",
|
||||||
|
"description": "Creates a sharefeed entry based on the current webpage.",
|
||||||
|
"version": "1.0",
|
||||||
|
"manifest_version": 3,
|
||||||
|
"action": {
|
||||||
|
"default_icon": "moffsoft_64.png",
|
||||||
|
"default_popup": "popup.html"
|
||||||
|
},
|
||||||
|
"permissions": [
|
||||||
|
"storage",
|
||||||
|
"activeTab",
|
||||||
|
"scripting"
|
||||||
|
]
|
||||||
|
}
|
BIN
moffsoft_64.png
Normal file
BIN
moffsoft_64.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
59
popup.html
Normal file
59
popup.html
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<html>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
max-width: fit-content;
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
display: flexbox;
|
||||||
|
}
|
||||||
|
input, textarea {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
input[type="submit"] {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.red {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
textarea[readonly] {
|
||||||
|
background-color: #eeeeee;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
#error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body>
|
||||||
|
<script src="popup.js"></script>
|
||||||
|
<h1>new sharefeed entry</h1>
|
||||||
|
<p>Required fields are in <b>bold</b> with a red asterisk. <span class="red">*</span></p>
|
||||||
|
<form id="sharefeed">
|
||||||
|
<label for="url"><b>url</b><span class="red">*</span></label>
|
||||||
|
<input id="url" name="url" type="url" size="50"/>
|
||||||
|
<br/>
|
||||||
|
<label for="title"><b>title</b><span class="red">*</span></label>
|
||||||
|
<input id="title" name="title" type="text" size="50"/>
|
||||||
|
<br/>
|
||||||
|
<label for="author">author</label>
|
||||||
|
<input id="author" name="author" type="text"/>
|
||||||
|
<br/>
|
||||||
|
<label for="publishedDate">date published</label>
|
||||||
|
<input id="publishedDate" name="publishedDate" type="datetime-local" step="1"/>
|
||||||
|
<br/>
|
||||||
|
<label for="accessedDate"><b>date accessed</b><span class="red">*</span></label>
|
||||||
|
<input id="accessedDate" name="accessedDate" type="datetime-local" step="1"/>
|
||||||
|
<br/>
|
||||||
|
<label for="note"><b>note</b><span class="red">*</span></label>
|
||||||
|
<textarea form="sharefeed" id="note" name="note" rows="4" cols="50"></textarea>
|
||||||
|
<br/>
|
||||||
|
<button id="submit" name="submit" type="submit">generate</button>
|
||||||
|
</form>
|
||||||
|
<p id="error"></p>
|
||||||
|
<hr/>
|
||||||
|
<label for="generated">generated JSON</label>
|
||||||
|
<textarea id="generated" name="generated" rows="8" cols="50" readonly aria-readonly></textarea>
|
||||||
|
</body>
|
||||||
|
</html>
|
99
popup.js
Normal file
99
popup.js
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
function parsePage() {
|
||||||
|
let entry = {
|
||||||
|
url: window.location.href,
|
||||||
|
title: "",
|
||||||
|
publishedDate: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
// fix the URL's of certain websites
|
||||||
|
if (entry.url.startsWith('https://freedium.cfd/')) { // extract original article link from freedium url
|
||||||
|
entry.url = entry.url.replace('https://freedium.cfd/', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// if schema.org JSON-LD is present, read that data
|
||||||
|
const jsonLDElem = document.querySelector('script[type="application/ld+json"]');
|
||||||
|
if (jsonLDElem) {
|
||||||
|
const jsonLDObj = JSON.parse(jsonLDElem.textContent);
|
||||||
|
if (jsonLDObj['@type'] == 'NewsArticle') {
|
||||||
|
entry.publishedDate = jsonLDObj.datePublished?.slice(0, 19) ?? "";
|
||||||
|
entry.author = jsonLDObj.author?.name ?? ""
|
||||||
|
entry.title = jsonLDObj.headline ?? ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
// thanks stack overflow: https://stackoverflow.com/questions/12413243/javascript-date-format-like-iso-but-local
|
||||||
|
function dateToISOLikeButLocal(date) {
|
||||||
|
const offsetMs = date.getTimezoneOffset() * 60 * 1000;
|
||||||
|
const msLocal = date.getTime() - offsetMs;
|
||||||
|
const dateLocal = new Date(msLocal);
|
||||||
|
const iso = dateLocal.toISOString();
|
||||||
|
const isoLocal = iso.slice(0, 19);
|
||||||
|
return isoLocal;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillForm(entry) {
|
||||||
|
const curDate = new Date();
|
||||||
|
|
||||||
|
document.getElementById('url').value = entry.url;
|
||||||
|
document.getElementById('title').value = entry.title ?? "";
|
||||||
|
document.getElementById('author').value = entry.author ?? "";
|
||||||
|
document.getElementById('publishedDate').value = entry.publishedDate ?? "";
|
||||||
|
document.getElementById('accessedDate').value = dateToISOLikeButLocal(curDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runContentScript(tabId) {
|
||||||
|
chrome.scripting.executeScript({
|
||||||
|
target: { tabId: tabId},
|
||||||
|
func: parsePage,
|
||||||
|
}).then((injectionResults) => {
|
||||||
|
fillForm(injectionResults[0].result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateJSON(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const urlField = document.getElementById('url');
|
||||||
|
const titleField = document.getElementById('title');
|
||||||
|
const authorField = document.getElementById('author');
|
||||||
|
const publishedDateField = document.getElementById('publishedDate');
|
||||||
|
const accessedDateField = document.getElementById('accessedDate');
|
||||||
|
const noteField = document.getElementById('note');
|
||||||
|
|
||||||
|
const errorElem = document.getElementById('error');
|
||||||
|
const JSONElem = document.getElementById('generated');
|
||||||
|
|
||||||
|
// first, make sure all of our required fields are in place
|
||||||
|
if ((urlField.value == null || urlField.value == "") ||
|
||||||
|
(titleField.value == null || titleField.value == "") ||
|
||||||
|
(accessedDateField.value == null || accessedDateField.value == "") ||
|
||||||
|
(noteField.value == null || noteField.value == "")) {
|
||||||
|
errorElem.textContent = "ERROR: Missing required field."
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorElem.textContent = ""
|
||||||
|
JSONElem.value = `{
|
||||||
|
"url": "${urlField.value}",
|
||||||
|
"title": "${titleField.value}",
|
||||||
|
"author": "${authorField.value}",
|
||||||
|
"publishedDate": "${publishedDateField.value.replace('T', ' ')}",
|
||||||
|
"accessedDate": "${accessedDateField.value.replace('T', ' ')}",
|
||||||
|
"note": "${noteField.value}",\n},`
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
chrome.tabs.query({
|
||||||
|
active: true,
|
||||||
|
currentWindow: true
|
||||||
|
}, tabs => {
|
||||||
|
runContentScript(tabs[0].id);
|
||||||
|
});
|
||||||
|
|
||||||
|
const form = document.getElementById('sharefeed')
|
||||||
|
form.addEventListener('submit', generateJSON);
|
||||||
|
});
|
||||||
|
|
Loading…
Add table
Reference in a new issue