Files
bitvid/js/ui/initUploadModal.js
T
thePR0M3TH3ANandClaude Opus 4.8 3df398d7ad [upload] Suggest one-tap hashtag chips from the user's past videos (#45)
The upload modal's Hashtags section now shows chips of the user's most-used past
hashtags ("Reuse a tag from your videos:"). Clicking one adds it to the NIP-71 't'
repeater; already-added chips dim + disable; the row is hidden for users with no
past tags.

- js/utils/hashtagSuggestions.js: pure rankHashtagsByFrequency(videos) — count =
  number of the user's videos using each tag, normalized (case + '#') and deduped
  per video, ranked by count then alphabetically. Reads video.nip71.hashtags, falls
  back to raw event 't' tags minus bitvid's fixed t=video marker.
- app.getUserHashtagSuggestions() sources the user's own videos via
  nostrService.getActiveVideosByAuthors([pubkey]) and ranks them (top 12), injected
  into the modal as getHashtagSuggestions.
- UploadModal renders the chips on open and wires clicks through the existing
  Nip71FormManager (addRepeaterEntry + setFieldValue, dedup via collectRepeaterValues).

Tests: tests/hashtag-suggestions.test.mjs (5 — per-video counting, normalization,
marker exclusion, tie-break + limit, empty input). Lint + build green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 20:59:56 -04:00

99 lines
2.7 KiB
JavaScript

import { UploadModal } from "./components/UploadModal.js";
export function initUploadModal({
app = null,
uploadModalOverride = null,
container = null,
services = {},
utilities = {},
callbacks = {},
eventTarget = null,
} = {}) {
const events = eventTarget instanceof EventTarget ? eventTarget : new EventTarget();
const {
authService = null,
r2Service = null,
s3Service = null,
storageService = null,
} = services;
const {
removeTrackingScripts: removeTrackingScriptsFn = null,
setGlobalModalState: setGlobalModalStateFn = null,
} = utilities;
const {
publishVideoNote = null,
showError = null,
showSuccess = null,
getCurrentPubkey = null,
safeEncodeNpub = null,
onSubmit = null,
} = callbacks;
const modal =
(typeof uploadModalOverride === "function"
? uploadModalOverride({ app, eventTarget: events })
: uploadModalOverride) ||
new UploadModal({
authService,
r2Service,
s3Service,
storageService,
publishVideoNote,
removeTrackingScripts: removeTrackingScriptsFn,
setGlobalModalState: setGlobalModalStateFn,
showError,
showSuccess,
getCurrentPubkey,
safeEncodeNpub,
// Frequency-ranked hashtags from the user's own past uploads → one-tap
// suggestion chips in the upload modal (TODO #45).
getHashtagSuggestions: (options) =>
typeof app?.getUserHashtagSuggestions === "function"
? app.getUserHashtagSuggestions(options)
: [],
eventTarget: events,
container,
onRequestStorageSettings: () => {
if (app && app.profileController && typeof app.profileController.show === "function") {
app.profileController.show("storage");
}
},
// Open the login modal's unlock-saved-key (passphrase) flow when a persisted
// nsec session is locked after a reload.
onRequestUnlock: () => {
try {
if (typeof app?.initializeLoginModalController === "function") {
app.initializeLoginModalController();
}
const controller = app?.loginModalController;
if (controller && typeof controller.openModal === "function") {
return controller.openModal({}) !== false;
}
} catch (error) {
/* best-effort */
}
return false;
},
});
const boundSubmitHandler =
typeof onSubmit === "function" ? (event) => onSubmit(event) : null;
if (boundSubmitHandler && typeof modal?.addEventListener === "function") {
modal.addEventListener("upload:submit", boundSubmitHandler);
}
return {
modal,
events,
handlers: {
submit: boundSubmitHandler,
},
};
}
export default initUploadModal;