diff --git a/src/github/feed/boost-helpers.js b/src/github/feed/boost-helpers.js
index adba887..5e30cfa 100644
--- a/src/github/feed/boost-helpers.js
+++ b/src/github/feed/boost-helpers.js
@@ -59,27 +59,3 @@ export const issueIcon = html.span({ classList: "n-github-issue-icon" });
issueIcon.innerHTML = ``;
-
-export function timeAgo(timestampInSeconds) {
- const nowInSeconds = Math.floor(Date.now() / 1000);
- const secondsAgo = nowInSeconds - timestampInSeconds;
-
- let value, unit;
-
- if (secondsAgo < 60) {
- value = secondsAgo;
- unit = "second";
- } else if (secondsAgo < 3600) {
- value = Math.floor(secondsAgo / 60);
- unit = "minute";
- } else if (secondsAgo < 86400) {
- value = Math.floor(secondsAgo / 3600);
- unit = "hour";
- } else {
- value = Math.floor(secondsAgo / 86400);
- unit = "day";
- }
-
- const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
- return rtf.format(-value, unit);
-}
diff --git a/src/github/feed/boost.js b/src/github/feed/boost.js
index c5ed178..ab32ef2 100644
--- a/src/github/feed/boost.js
+++ b/src/github/feed/boost.js
@@ -6,6 +6,7 @@ import {
} from "../../helpers/local-cache.js";
import { getIconComponent } from "../github-connect.js";
import { logger } from "../../helpers/logger.js";
+import { timeAgo } from "../../helpers/time.js";
import {
fetchGithubTitle,
@@ -14,7 +15,6 @@ import {
pickRandomEmoji,
prIcon,
spanSpaced,
- timeAgo,
typeToReactions,
} from "./boost-helpers.js";
diff --git a/src/helpers/local-cache.js b/src/helpers/local-cache.js
index 23cfe73..1d574a3 100644
--- a/src/helpers/local-cache.js
+++ b/src/helpers/local-cache.js
@@ -1,7 +1,7 @@
import { mergeSettings } from "./utils.js";
export const defaultSettings = {
- version: 1,
+ version: 2,
debug: {
log: true,
namespace: "[N]",
@@ -12,7 +12,7 @@ export const defaultSettings = {
nip07: {
useRelays: false,
},
- openNostr: "https://nosta.me",
+ openNostr: "https://nost.at",
},
lightsatsSettings: {
apiKey: "",
diff --git a/src/helpers/nostr.js b/src/helpers/nostr.js
index c05c6c4..f58b568 100644
--- a/src/helpers/nostr.js
+++ b/src/helpers/nostr.js
@@ -306,15 +306,17 @@ export async function requestSigningFromNip07(messageParams) {
* @param {Object} event - The event to insert
* @returns {number} - The index where the event was inserted
*/
-function insertEventIntoAscendingList(sortedArray, event) {
+function insertEventIntoDescendingList(sortedArray, event) {
const [idx, found] = binarySearch(sortedArray, (b) => {
if (event.id === b.id) {
return 0;
}
+
if (event.created_at === b.created_at) {
return -1;
}
- return event.created_at - b.created_at;
+
+ return b.created_at - event.created_at;
});
if (!found) {
@@ -355,7 +357,7 @@ export function fetchLatestNotes({ pubkey, relays, callback }) {
notesSet.add(event.id);
- const index = insertEventIntoAscendingList(latestNotes, event);
+ const index = insertEventIntoDescendingList(latestNotes, event);
console.log(`inserted at index: ${index}`);
diff --git a/src/helpers/time.js b/src/helpers/time.js
new file mode 100644
index 0000000..49814be
--- /dev/null
+++ b/src/helpers/time.js
@@ -0,0 +1,23 @@
+export function timeAgo(timestampInSeconds) {
+ const nowInSeconds = Math.floor(Date.now() / 1000);
+ const secondsAgo = nowInSeconds - timestampInSeconds;
+
+ let value, unit;
+
+ if (secondsAgo < 60) {
+ value = secondsAgo;
+ unit = "second";
+ } else if (secondsAgo < 3600) {
+ value = Math.floor(secondsAgo / 60);
+ unit = "minute";
+ } else if (secondsAgo < 86400) {
+ value = Math.floor(secondsAgo / 3600);
+ unit = "hour";
+ } else {
+ value = Math.floor(secondsAgo / 86400);
+ unit = "day";
+ }
+
+ const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
+ return rtf.format(-value, unit);
+}
diff --git a/src/settings/settings.js b/src/settings/settings.js
index 5d3309f..3411f10 100644
--- a/src/settings/settings.js
+++ b/src/settings/settings.js
@@ -99,6 +99,7 @@ async function settingsPage() {
state.nostrSettings.nip07.useRelays =
defaultSettings.nostrSettings.nip07.useRelays;
state.nostrSettings.relays = [...defaultSettings.nostrSettings.relays];
+ state.nostrSettings.openNostr = defaultSettings.nostrSettings.openNostr;
// Lightsats settings
state.lightsatsSettings.apiKey = defaultSettings.lightsatsSettings.apiKey;
@@ -153,6 +154,9 @@ async function settingsPage() {
toggleNIP07Settings();
};
+ document.getElementById("open-nostr").onchange = (e) =>
+ (state.nostrSettings.openNostr = e.target.value);
+
document.getElementById("nip07-relays").onchange = (e) =>
(state.nostrSettings.nip07.useRelays = e.target.checked);
diff --git a/src/twitter/profile/notes.css b/src/twitter/profile/notes.css
index b9eac00..09f11a8 100644
--- a/src/twitter/profile/notes.css
+++ b/src/twitter/profile/notes.css
@@ -15,20 +15,37 @@
}
.n-tw-note {
+ position: relative;
+}
+
+.n-tw-note-content {
background-color: rgb(208, 173, 240);
- padding: 10px;
+ padding: 15px 10px 10px 10px;
border-radius: 5px;
margin: 10px;
color: black;
}
-.n-tw-note img {
+.n-tw-note .ago {
+ position: absolute;
+ text-decoration: none;
+ top: 12px;
+ right: 22px;
+ font-size: small;
+ color: blueviolet;
+}
+
+.n-tw-note .ago:hover {
+ text-decoration: underline;
+}
+
+.n-tw-note-content img {
max-width: 100%;
height: auto;
margin: 5px 0 5px 0;
}
-.n-tw-note a {
+.n-tw-note-content a {
color: white;
text-decoration: underline;
-}
\ No newline at end of file
+}
diff --git a/src/twitter/profile/profile.js b/src/twitter/profile/profile.js
index 672c7eb..1d46446 100644
--- a/src/twitter/profile/profile.js
+++ b/src/twitter/profile/profile.js
@@ -28,10 +28,10 @@ import { wrapInputTooltip } from "../../components/tooltip/tooltip-wrapper.js";
import {
createTwitterButton,
- setNostrMode,
+ setupNostrMode,
updateFollowButton,
} from "./twitter-helpers.js";
-import { wrapCheckbox } from "../../components/checkbox/checkbox-wrapper.js";
+import { setupNostrProfileLink } from "./twitter-helpers.js";
async function twitterProfilePage() {
const settings = await getLocalSettings();
@@ -161,43 +161,15 @@ async function twitterProfilePage() {
'nav[aria-label="Profile timelines"]',
);
- const nostrModeOnclick = async (checked) => {
- if (checked) {
- timelineNavbar.style.display = "none";
- } else {
- timelineNavbar.style.display = "flex";
- }
-
- await setNostrMode({
- enabled: checked,
- pageUserPubkey,
- pageUserWriteRelays,
- openNostr: settings.nostrSettings.openNostr,
- });
- };
-
- const enableNostrModeCheckbox = wrapCheckbox({
- input: html.input({
- type: "checkbox",
- id: "n-tw-enable-nostr-mode",
- checked: true,
- }),
- onclick: nostrModeOnclick,
- text: "Enable Nostr Mode",
+ const { enableNostrModeCheckbox, notesSection } = setupNostrMode({
+ timelineNavbar,
+ pageUserPubkey,
+ pageUserWriteRelays,
+ settings,
});
- timelineNavbar.insertAdjacentElement("beforebegin", enableNostrModeCheckbox);
-
- enableNostrModeCheckbox.insertAdjacentElement(
- "afterend",
- html.div({
- id: "n-tw-notes-section",
- style: [["display", "none"]],
- }),
- );
-
// nostr mode is on by default
- nostrModeOnclick(true);
+ enableNostrModeCheckbox.insertAdjacentElement("afterend", notesSection);
if (pageUserPubkey === nostrizeUserPubkey) {
log("current page user is the nostrize user");
@@ -213,29 +185,7 @@ async function twitterProfilePage() {
relays: nostrizeUserRelays,
});
- // handle container is the div that contains the username and the handle
- const usernamePanel = document.querySelector("div[data-testid='UserName']");
- const handleContainer =
- usernamePanel?.childNodes[0]?.childNodes[0]?.childNodes[0]?.childNodes[1];
- const handle = handleContainer?.childNodes[0];
- const handleContent = handle.textContent;
- handle.style.display = "none";
-
- // nostr profile link
- if (handleContainer) {
- gui.prepend(
- handleContainer,
- wrapInputTooltip({
- id: "n-tw-nostr-profile-button",
- input: html.link({
- text: handleContent,
- href: `${settings.nostrSettings.openNostr}/${pageUserPubkey}`,
- targetBlank: true,
- }),
- tooltipText: `User is on Nostr. Click to open Nostr profile.`,
- }),
- );
- }
+ const handleContainer = setupNostrProfileLink(settings, pageUserPubkey);
// follows of the account
const pageUserFollowSubscription = getFollowSet({
diff --git a/src/twitter/profile/twitter-helpers.js b/src/twitter/profile/twitter-helpers.js
index b190af9..61492a5 100644
--- a/src/twitter/profile/twitter-helpers.js
+++ b/src/twitter/profile/twitter-helpers.js
@@ -2,7 +2,10 @@ import * as gui from "../../imgui-dom/gui.js";
import * as html from "../../imgui-dom/html.js";
import { setupModal } from "../../components/common.js";
import { fetchLatestNotes } from "../../helpers/nostr.js";
-import { delay } from "../../helpers/utils.js";
+import { wrapCheckbox } from "../../components/checkbox/checkbox-wrapper.js";
+import { wrapInputTooltip } from "../../components/tooltip/tooltip-wrapper.js";
+import { timeAgo } from "../../helpers/time.js";
+import { nip19 } from "nostr-tools";
export const updateFollowButton = (button, emojiIcon) => {
button.childNodes[0].childNodes[0].textContent = emojiIcon;
@@ -93,24 +96,19 @@ function processNoteContent(content, openNostr) {
.replace(/\n/g, "
");
}
-export async function setNostrMode({
+async function setNostrMode({
enabled,
pageUserPubkey,
pageUserWriteRelays,
openNostr,
}) {
if (enabled) {
- const trimNote = shortenNote(500);
-
const latestNotes = fetchLatestNotes({
pubkey: pageUserPubkey,
relays: pageUserWriteRelays,
callback: (event, index) => {
notesSection.insertBefore(
- html.div({
- classList: "n-tw-note",
- innerHTML: trimNote(processNoteContent(event.content, openNostr)),
- }),
+ createNote(event, openNostr),
notesSection.children[index],
);
},
@@ -123,130 +121,111 @@ export async function setNostrMode({
// filter out notes that are replies to nostr events
.filter((e) => !e.tags.some((tag) => tag[0] === "e"))
.forEach((note) => {
- notesSection.appendChild(
- html.div({
- classList: "n-tw-note",
- innerHTML: trimNote(processNoteContent(note.content, openNostr)),
- }),
- );
+ notesSection.appendChild(createNote(note, openNostr));
});
attachLoadMoreListeners();
} else {
const notesSection = gui.gebid("n-tw-notes-section");
notesSection.style.display = "none";
-
- // TODO: set twitter section display to flex
}
}
-export async function addAccountNotesTab(
- accountPubkey,
- writeRelays,
- openNostr,
-) {
- const tablist = document.querySelector(
- "[data-testid='ScrollSnap-SwipeableList']",
- ).childNodes[0];
-
- // deselect posts tab
- const posts = tablist.childNodes[0];
- posts.querySelector("span").nextElementSibling.style.backgroundColor =
- "unset";
-
- // reset account notes tab
- const accountNotes = posts.cloneNode(true);
- accountNotes.id = "n-tw-account-notes";
-
- // remove link
- accountNotes.childNodes[0].setAttribute("href", "javascript:void(0);");
-
- gui.prepend(tablist, accountNotes);
-
- const notesTitle = accountNotes.querySelector("span");
- notesTitle.textContent = "Notes";
-
- const selectionIndicator = notesTitle.nextElementSibling;
- selectionIndicator.style.backgroundColor = "unset";
-
- await notesTabClicked();
+const createNote = (event, openNostr) => {
+ const eventId = nip19.noteEncode(event.id);
+
+ return html.div({
+ classList: "n-tw-note",
+ children: [
+ html.div({
+ classList: "n-tw-note-content",
+ innerHTML: shortenNote(500)(
+ processNoteContent(event.content, openNostr),
+ ),
+ }),
+ html.link({
+ classList: "ago",
+ text: timeAgo(event.created_at),
+ href: `${openNostr}/${eventId}`,
+ targetBlank: true,
+ }),
+ ],
+ });
+};
- const trimNote = shortenNote(500);
+export function setupNostrMode({
+ timelineNavbar,
+ pageUserPubkey,
+ pageUserWriteRelays,
+ settings,
+}) {
+ const nostrModeOnclick = async (checked) => {
+ if (checked) {
+ timelineNavbar.style.display = "none";
+ } else {
+ timelineNavbar.style.display = "flex";
+ }
- let toHide = document.querySelector('[aria-labelledby="accessible-list-1"]');
+ await setNostrMode({
+ enabled: checked,
+ pageUserPubkey,
+ pageUserWriteRelays,
+ openNostr: settings.nostrSettings.openNostr,
+ });
+ };
- while (!toHide) {
- await delay(100);
- toHide = document.querySelector('[aria-labelledby="accessible-list-1"]');
- }
+ const enableNostrModeCheckbox = wrapCheckbox({
+ input: html.input({
+ type: "checkbox",
+ id: "n-tw-enable-nostr-mode",
+ checked: true,
+ }),
+ onclick: nostrModeOnclick,
+ text: "Enable Nostr Mode",
+ });
- toHide.style.display = "none";
+ timelineNavbar.insertAdjacentElement("beforebegin", enableNostrModeCheckbox);
const notesSection = html.div({
id: "n-tw-notes-section",
- classList: "n-tw-notes-section",
- });
-
- toHide.insertAdjacentElement("afterend", notesSection);
-
- const latestNotes = fetchLatestNotes({
- pubkey: accountPubkey,
- relays: writeRelays,
- callback: (event, index) => {
- if (isAccountNotesTabSelected) {
- notesSection.insertBefore(
- html.div({
- classList: "tw-note",
- innerHTML: trimNote(processNoteContent(event.content, openNostr)),
- }),
- notesSection.children[index],
- );
- }
- },
+ style: [["display", "none"]],
});
- let isAccountNotesTabSelected = false;
-
- tablist.childNodes.forEach((node) => {
- if (node.id === "n-tw-account-notes") {
- return;
- }
-
- node.addEventListener("click", () => {
- isAccountNotesTabSelected = false;
- notesSection.style.display = "none";
- selectionIndicator.style.backgroundColor = "unset";
- toHide.style.display = "flex";
- });
- });
+ enableNostrModeCheckbox.insertAdjacentElement("afterend", notesSection);
- const notesTabClicked = async (e) => {
- e?.preventDefault();
+ // nostr mode is on by default
+ nostrModeOnclick(true);
- isAccountNotesTabSelected = true;
+ return { enableNostrModeCheckbox, notesSection };
+}
- selectionIndicator.style.backgroundColor = "rgb(130, 80, 223)";
+export function setupNostrProfileLink(settings, pageUserPubkey) {
+ const usernamePanel = document.querySelector("div[data-testid='UserName']");
+ const handleContainer =
+ usernamePanel?.childNodes[0]?.childNodes[0]?.childNodes[0]?.childNodes[1];
+ const handle = handleContainer?.childNodes[0];
- // clear notes section
- notesSection.innerHTML = "";
+ if (!handle) {
+ return handleContainer;
+ }
- latestNotes.forEach((note) => {
- notesSection.appendChild(
- html.div({
- classList: "tw-note",
- innerHTML: trimNote(processNoteContent(note.content, openNostr)),
- }),
- );
+ const handleContent = handle.textContent;
+
+ if (handleContainer) {
+ const nostrProfileLink = wrapInputTooltip({
+ id: "n-tw-nostr-profile-button",
+ input: html.link({
+ text: handleContent,
+ href: `${settings.nostrSettings.openNostr}/${pageUserPubkey}`,
+ targetBlank: true,
+ }),
+ tooltipText: `User is on Nostr. Click to open Nostr profile.`,
});
- };
- accountNotes.addEventListener("click", notesTabClicked);
+ handle.style.display = "none";
- accountNotes.addEventListener("mouseenter", () => {
- accountNotes.style.backgroundColor = "rgb(130, 80, 223)";
- });
+ gui.prepend(handleContainer, nostrProfileLink);
+ }
- accountNotes.addEventListener("mouseleave", () => {
- accountNotes.style.backgroundColor = "unset";
- });
+ return handleContainer;
}