diff --git a/packages/app/package.json b/packages/app/package.json
index 4de97dbe..cf3bc1fe 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -34,6 +34,7 @@
"react-intersection-observer": "^9.4.1",
"react-intl": "^6.4.4",
"react-router-dom": "^6.5.0",
+ "react-tag-input-component": "^2.0.2",
"react-textarea-autosize": "^8.4.0",
"react-twitter-embed": "^4.0.4",
"recharts": "^2.8.0",
diff --git a/packages/app/src/Element/Event/NoteCreator.css b/packages/app/src/Element/Event/NoteCreator.css
index 01dd8abc..27b72352 100644
--- a/packages/app/src/Element/Event/NoteCreator.css
+++ b/packages/app/src/Element/Event/NoteCreator.css
@@ -95,3 +95,27 @@
linear-gradient(var(--gray-superdark), var(--gray-superdark)) padding-box,
linear-gradient(90deg, #ef9644, #fd7c49, #ff5e58, #ff3b70, #ff088e, #eb00b1, #c31ed5, #7b41f6) border-box;
}
+
+.note-creator-modal .rti--container {
+ background-color: unset !important;
+ box-shadow: unset !important;
+ border: 2px solid var(--border-color) !important;
+ border-radius: 12px !important;
+ padding: 4px 8px !important;
+}
+
+.note-creator-modal .rti--tag {
+ color: black !important;
+ padding: 4px 10px !important;
+ border-radius: 12px !important;
+ display: unset !important;
+}
+
+.note-creator-modal .rti--input {
+ width: 100% !important;
+ border: unset !important;
+}
+
+.note-creator-modal .rti--tag button {
+ padding: 0 0 0 var(--rti-s);
+}
diff --git a/packages/app/src/Element/Event/NoteCreator.tsx b/packages/app/src/Element/Event/NoteCreator.tsx
index 6e4d9ce2..260df82a 100644
--- a/packages/app/src/Element/Event/NoteCreator.tsx
+++ b/packages/app/src/Element/Event/NoteCreator.tsx
@@ -2,17 +2,18 @@ import "./NoteCreator.css";
import { FormattedMessage, useIntl } from "react-intl";
import { EventKind, NostrPrefix, TaggedNostrEvent, EventBuilder, tryParseNostrLink, NostrLink } from "@snort/system";
import classNames from "classnames";
+import { TagsInput } from "react-tag-input-component";
import Icon from "Icons/Icon";
import useEventPublisher from "Hooks/useEventPublisher";
-import { openFile } from "SnortUtils";
+import { appendDedupe, openFile } from "SnortUtils";
import Textarea from "Element/Textarea";
import Modal from "Element/Modal";
import ProfileImage from "Element/User/ProfileImage";
import useFileUpload from "Upload";
import Note from "Element/Event/Note";
-import { ClipboardEventHandler, DragEvent } from "react";
+import { ClipboardEventHandler, DragEvent, useEffect, useState } from "react";
import useLogin from "Hooks/useLogin";
import { GetPowWorker } from "index";
import AsyncButton from "Element/AsyncButton";
@@ -23,6 +24,8 @@ import { useNoteCreator } from "State/NoteCreator";
import { NoteBroadcaster } from "./NoteBroadcaster";
import FileUploadProgress from "./FileUpload";
import { ToggleSwitch } from "Icons/Toggle";
+import NostrBandApi from "External/NostrBand";
+import { useLocale } from "IntlProvider";
export function NoteCreator() {
const { formatMessage } = useIntl();
@@ -99,6 +102,10 @@ export function NoteCreator() {
extraTags ??= [];
extraTags.push(...note.pollOptions.map((a, i) => ["poll_option", i.toString(), a]));
}
+ if (note.hashTags.length > 0) {
+ extraTags ??= [];
+ extraTags.push(...note.hashTags.map(a => ["t", a.toLowerCase()]));
+ }
// add quote repost
if (note.quote) {
if (!note.note.endsWith("\n")) {
@@ -307,18 +314,18 @@ export function NoteCreator() {
onChange={e => {
note.update(
v =>
- (v.selectedCustomRelays =
- // set false if all relays selected
- e.target.checked &&
+ (v.selectedCustomRelays =
+ // set false if all relays selected
+ e.target.checked &&
note.selectedCustomRelays &&
note.selectedCustomRelays.length == a.length - 1
- ? undefined
- : // otherwise return selectedCustomRelays with target relay added / removed
- a.filter(el =>
- el === r
- ? e.target.checked
- : !note.selectedCustomRelays || note.selectedCustomRelays.includes(el),
- )),
+ ? undefined
+ : // otherwise return selectedCustomRelays with target relay added / removed
+ a.filter(el =>
+ el === r
+ ? e.target.checked
+ : !note.selectedCustomRelays || note.selectedCustomRelays.includes(el),
+ )),
);
}}
/>
@@ -387,9 +394,9 @@ export function NoteCreator() {
onChange={e =>
note.update(
v =>
- (v.zapSplits = arr.map((vv, ii) =>
- ii === i ? { ...vv, weight: Number(e.target.value) } : vv,
- )),
+ (v.zapSplits = arr.map((vv, ii) =>
+ ii === i ? { ...vv, weight: Number(e.target.value) } : vv,
+ )),
)
}
/>
@@ -565,7 +572,7 @@ export function NoteCreator() {
>
)}
{note.preview && getPreviewNote()}
- {!note.preview && (
+ {!note.preview && (<>
{renderPollOptions()}
+
+ note.update(s => s.hashTags = e)} placeHolder={formatMessage({
+ defaultMessage: "Add up to 4 hashtags"
+ })} separators={["Enter", ","]} />
+ {note.hashTags.length > 4 &&
+
+ }
+ note.update(s => s.hashTags = appendDedupe(s.hashTags, [t]))} />
+
+ >
)}
{uploader.progress.length > 0 && }
{noteCreatorFooter()}
@@ -608,3 +625,30 @@ export function NoteCreator() {
);
}
+
+function TrendingHashTagsLine(props: { onClick: (tag: string) => void }) {
+ const [hashtags, setHashtags] = useState>();
+ const { lang } = useLocale();
+
+ async function loadTrendingHashtags() {
+ const api = new NostrBandApi();
+ const rsp = await api.trendingHashtags(lang);
+ setHashtags(rsp.hashtags);
+ }
+
+ useEffect(() => {
+ loadTrendingHashtags().catch(console.error);
+ }, []);
+
+ if (!hashtags || hashtags.length === 0) return;
+ return
+
+
+
+
+ {hashtags.slice(0, 5).map(a => props.onClick(a.hashtag)}>
+ #{a.hashtag}
+ )}
+
+
+}
\ No newline at end of file
diff --git a/packages/app/src/Element/Modal.css b/packages/app/src/Element/Modal.css
index bb02e1b7..a08181f9 100644
--- a/packages/app/src/Element/Modal.css
+++ b/packages/app/src/Element/Modal.css
@@ -13,16 +13,22 @@
.modal-body {
background-color: var(--gray-superdark);
- padding: 24px;
+ padding: 24px 12px;
border-radius: 16px;
display: flex;
flex-direction: column;
- width: 500px;
margin-top: auto;
margin-bottom: auto;
--border-color: var(--gray);
}
+@media (min-width: 600px) {
+ .modal-body {
+ padding: 24px;
+ width: 600px;
+ }
+}
+
.modal-body button.secondary:hover {
background-color: var(--gray);
}
diff --git a/packages/app/src/Element/TrendingHashtags.tsx b/packages/app/src/Element/TrendingHashtags.tsx
index f17fdf7d..de5d787f 100644
--- a/packages/app/src/Element/TrendingHashtags.tsx
+++ b/packages/app/src/Element/TrendingHashtags.tsx
@@ -7,7 +7,7 @@ import { HashTagHeader } from "Pages/HashTagsPage";
import { useLocale } from "IntlProvider";
export default function TrendingHashtags({ title }: { title?: ReactNode }) {
- const [hashtags, setHashtags] = useState();
+ const [hashtags, setHashtags] = useState>();
const [error, setError] = useState();
const { lang } = useLocale();
@@ -30,6 +30,6 @@ export default function TrendingHashtags({ title }: { title?: ReactNode }) {
return <>
{title}
- {hashtags.map(a => )}
+ {hashtags.map(a => )}
>
}
diff --git a/packages/app/src/External/NostrBand.ts b/packages/app/src/External/NostrBand.ts
index 664b92ec..e5f2eb09 100644
--- a/packages/app/src/External/NostrBand.ts
+++ b/packages/app/src/External/NostrBand.ts
@@ -19,7 +19,10 @@ export interface TrendingNoteResponse {
}
export interface TrendingHashtagsResponse {
- hashtags: Array
+ hashtags: Array<{
+ hashtag: string,
+ posts: number
+ }>
}
export interface SuggestedFollow {
diff --git a/packages/app/src/Icons/Toggle.css b/packages/app/src/Icons/Toggle.css
index 98370cfa..ec477809 100644
--- a/packages/app/src/Icons/Toggle.css
+++ b/packages/app/src/Icons/Toggle.css
@@ -1,3 +1,6 @@
+svg#icon-toggle {
+ cursor: pointer;
+}
svg#icon-toggle #bg {
fill: var(--gray);
transition: fill 0.5s;
diff --git a/packages/app/src/State/NoteCreator.tsx b/packages/app/src/State/NoteCreator.tsx
index c8657484..5cbb7cfe 100644
--- a/packages/app/src/State/NoteCreator.tsx
+++ b/packages/app/src/State/NoteCreator.tsx
@@ -21,6 +21,7 @@ interface NoteCreatorDataSnapshot {
extraTags?: Array>;
sending?: Array;
sendStarted: boolean;
+ hashTags: Array;
reset: () => void;
update: (fn: (v: NoteCreatorDataSnapshot) => void) => void;
}
@@ -37,6 +38,7 @@ class NoteCreatorStore extends ExternalStore {
active: false,
advanced: false,
sendStarted: false,
+ hashTags: [],
reset: () => {
this.#reset(this.#data);
this.notifyChange(this.#data);
@@ -65,6 +67,7 @@ class NoteCreatorStore extends ExternalStore {
d.otherEvents = undefined;
d.sending = undefined;
d.extraTags = undefined;
+ d.hashTags = [];
}
takeSnapshot(): NoteCreatorDataSnapshot {
diff --git a/yarn.lock b/yarn.lock
index 2fa5fb65..4a85d573 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3171,6 +3171,7 @@ __metadata:
react-intersection-observer: ^9.4.1
react-intl: ^6.4.4
react-router-dom: ^6.5.0
+ react-tag-input-component: ^2.0.2
react-textarea-autosize: ^8.4.0
react-twitter-embed: ^4.0.4
recharts: ^2.8.0
@@ -12841,6 +12842,16 @@ __metadata:
languageName: node
linkType: hard
+"react-tag-input-component@npm:^2.0.2":
+ version: 2.0.2
+ resolution: "react-tag-input-component@npm:2.0.2"
+ peerDependencies:
+ react: ^16 || ^17 || ^18
+ react-dom: ^16 || ^17 || ^18
+ checksum: b8d5c588a3bfe4c4a82b8a8e34e3d11c37cd467bbd92b31aeb7e3fbd4e3a4e62228811d3ce61b01115c7efa8b6ccb06c7a2688710d03bb8ed91f9e2e690a1775
+ languageName: node
+ linkType: hard
+
"react-textarea-autosize@npm:^8.4.0":
version: 8.5.3
resolution: "react-textarea-autosize@npm:8.5.3"