mirror of
https://github.com/joel-st/kunkun-nostr-nip-19.git
synced 2025-04-03 17:56:43 +00:00
Initial Version @0.0.99
This commit is contained in:
parent
4209f65b3a
commit
2ee2bfdc14
22
.eslintrc.cjs
Normal file
22
.eslintrc.cjs
Normal file
@ -0,0 +1,22 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
pragma: 'h',
|
||||
version: '18.2.0',
|
||||
},
|
||||
},
|
||||
}
|
176
.gitignore
vendored
Normal file
176
.gitignore
vendored
Normal file
@ -0,0 +1,176 @@
|
||||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Caches
|
||||
|
||||
.cache
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
extensions_support/
|
13
index.html
Normal file
13
index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/extension.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Kunkun Nostr NIP-19</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
62
package.json
Normal file
62
package.json
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"$schema": "https://schema.kunkun.sh",
|
||||
"name": "nostr-nip-19",
|
||||
"license": "CC0-1.0",
|
||||
"version": "0.0.99",
|
||||
"type": "module",
|
||||
"kunkun": {
|
||||
"name": "Nostr NIP-19",
|
||||
"shortDescription": "Tool for Nostr NIP-19",
|
||||
"longDescription": "Tool for Nostr NIP-19",
|
||||
"identifier": "nostr-nip-19",
|
||||
"icon": {
|
||||
"type": "iconify",
|
||||
"value": "fluent:convert-range-24-regular"
|
||||
},
|
||||
"demoImages": [],
|
||||
"permissions": [
|
||||
"clipboard:write-text",
|
||||
"clipboard:read-text"
|
||||
],
|
||||
"customUiCmds": [
|
||||
{
|
||||
"main": "/",
|
||||
"dist": "dist",
|
||||
"devMain": "http://localhost:5173",
|
||||
"name": "Nostr NIP-19",
|
||||
"cmds": []
|
||||
}
|
||||
],
|
||||
"templateUiCmds": []
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kksh/api": "0.1.5",
|
||||
"@kksh/react": "0.1.1",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"nostr-tools": "^2.10.4",
|
||||
"preact": "^10.19.6",
|
||||
"random-part-of-speech": "^1.0.0",
|
||||
"unistore": "^3.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@preact/preset-vite": "^2.8.1",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-react-refresh": "^0.4.7",
|
||||
"postcss": "^8.4.38",
|
||||
"tailwindcss": "^3.4.6",
|
||||
"vite": "^5.4.9"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
".gitignore"
|
||||
]
|
||||
}
|
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
1
public/extension.svg
Normal file
1
public/extension.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><path fill="currentColor" d="M412.5 22.17c-.6 0-1.2.1-1.7.1c-9 .72-15.4 5.89-16.2 8.96c-2.1 7.02-3.6 16.36-2.2 22.82s3.4 10.34 14.2 12.22c16.6 2.88 35.4-.64 51.8-6.43c13-4.61 24.2-10.62 31.3-15.14c-.4-.67-.8-1.27-1.4-1.78c-1.5-1.26-4.3-2.53-8.6-3.31c-8.7-1.57-22.2-.88-36-1.57l-4.3-.22l-2.5-3.5c-6.2-8.57-14.6-11.93-22.7-12.14h-1.7zm-14 60.61c-3.9 10.17-4.5 20.22-2.8 29.52c2.2 12.2 9.5 22.1 13.6 32.9c14 36.6.8 45.4-20.8 51.1c22.3 20 33.3 44.4 35 68.3c30-45.7 35.3-86.2 1.3-128.6c-6.7-8.3-9.9-18.2-11.4-26.8c-1.5-7.9-.8-15.35 3.7-23.91c-4.5-.11-9-.51-13.5-1.29c-1.8-.31-3.4-.72-5.1-1.22M108.4 126.9c-29.04-.2-53.3 25.3-56.66 60c10.56-10.7 25.02-17.7 46.11-17.2c-20.2 13.7-33.69 29.2-44.34 45.3c1.77 8.2 4.64 16.5 8.8 24.9c38.09-52.5 60.99-29.6 72.69.1c10.8-20 27.4-36 47-48.1c-21.4-46.4-49.2-64.8-73.6-65m180.2 55c-3.4 0-6.9 0-10.4.2c-12.4.5-25 2.2-37.3 5.1c-46.3 11-86.3 38-97.9 81.1c22.2 7.6 40.7 16.4 56.7 25.4c31.6 4.7 63.8 3.2 91.4-2.5c38.5-8 67-25.4 73.3-39.7l16.4 7.2c-11 25.4-44.7 41.6-86 50.1c-19 4-39.9 6-61.3 5.7c4.3 2.8 8.4 5.5 12.4 8.1c27.1 17.6 48.4 29.7 82.6 28c35.6-2.9 62.6-25.9 72.9-54.6c10.2-28.7 4.4-62.5-28.7-89.5c-19.2-15.8-50.6-24.4-84.1-24.6m-92.5 130.4c-4.4 8.8-8.1 18-10.6 26.2c-18.6 8.8-25 12.1-34.8 33.2c-35.5 15.3-50.4 38.2-61.34 71.5c-22.48 6.1-40.85 5-63.06-9.9c-12.14 16.1 6.05 30.4 22.64 36.9c21.16 8.3 50.31 2.8 55.46-7.2c16.5-32.5 31.1-54.7 61.7-77c13.1-1.4 22.4-2.4 34.3-18c21.6 2.2 39.3-8 55-18.1c7.8 17.9 23.5 41.8 20.7 58.9c-11.8 9.5-8 21.3-8.3 24.1c6 17.9 66.2-5.8 108 18c22.4 12.8 27.9 44.9 69.4 37.9c6.8-1.1 5.3-7.2.6-8.9c-37.4-13.7-27.6-21-44.8-48.1c-32-.8-59.3-5.2-95.4-16.1c-10.4-3 3.2-24.8 13.3-47c-33.4-.9-57.8-14.7-82.9-31c-12.4-8.1-25.2-16.8-39.9-25.4"/></svg>
|
After Width: | Height: | Size: 1.8 KiB |
23
readme.md
Normal file
23
readme.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Nostr NIP-19 Extension
|
||||
|
||||
This extension helps you encode and decode [Nostr NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md) entities (like npub, nsec, note, nprofile, and nevent). It provides convenient commands that can be triggered from [Kunkun](https://kunkun.sh).
|
||||
|
||||
## Features
|
||||
|
||||
* Encode and decode Nostr NIP-19 entities
|
||||
* Generate random Keypair
|
||||
* Generate random Event KIND 1
|
||||
* Support for npub, nsec, note, nprofile, and nevent formats
|
||||
* Copy the result to your clipboard
|
||||
* Shows success/error toast notifications
|
||||
* Minimal and fast with small bundle size
|
||||
|
||||
## Usage
|
||||
|
||||
1. Install the extension in Kunkun
|
||||
2. Run one of the "NIP-19" commands (Encode/Decode)
|
||||
3. Paste the required information when prompted
|
||||
|
||||
## What is NIP-19?
|
||||
|
||||
NIP-19 is a Nostr Implementation Possibility that defines how to encode various Nostr entities like public keys, private keys, event IDs, and more using the bech32 format for human-friendly representation.
|
43
src/App.jsx
Normal file
43
src/App.jsx
Normal file
@ -0,0 +1,43 @@
|
||||
import { ui } from "@kksh/api/ui/custom"
|
||||
import {
|
||||
ThemeProvider,
|
||||
ThemeWrapper
|
||||
} from "@kksh/react"
|
||||
|
||||
import { useEffect } from "preact/hooks"
|
||||
import { connect } from 'unistore/preact';
|
||||
|
||||
// store
|
||||
import { storeActions } from "@store"
|
||||
|
||||
// components
|
||||
import { Textarea } from "@components/Textarea"
|
||||
import { Output } from "@components/Output"
|
||||
import { Buttons } from "@components/Buttons"
|
||||
// functional component
|
||||
export const App = connect([], storeActions)(({}) => {
|
||||
useEffect(() => {
|
||||
ui.registerDragRegion()
|
||||
ui.showMoveButton({
|
||||
bottom: 0.2,
|
||||
left: 0.2
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ThemeProvider storageKey="kk-ui-theme">
|
||||
<ThemeWrapper>
|
||||
<main class="h-screen pt-14">
|
||||
<div class="container px-10">
|
||||
<h1 class="text-2xl font-bold pb-4">Nostr NIP-19 Tools</h1>
|
||||
<div class="flex flex-col gap-4">
|
||||
<Buttons />
|
||||
<Textarea />
|
||||
<Output />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</ThemeWrapper>
|
||||
</ThemeProvider>
|
||||
)
|
||||
})
|
147
src/components/Buttons.jsx
Normal file
147
src/components/Buttons.jsx
Normal file
@ -0,0 +1,147 @@
|
||||
import { Button } from "@kksh/react"
|
||||
import { connect } from 'unistore/preact';
|
||||
|
||||
// nostr-tools
|
||||
import { generateSecretKey, finalizeEvent, verifyEvent} from 'nostr-tools/pure'
|
||||
import { nsecEncode } from 'nostr-tools/nip19'
|
||||
|
||||
// store
|
||||
import { storeActions } from "@store"
|
||||
|
||||
// functional component
|
||||
export const Buttons = connect(['input'], storeActions)(({ input, setInput }) => {
|
||||
|
||||
const generateKeyPair = () => {
|
||||
let nsec = nsecEncode(generateSecretKey())
|
||||
setInput(nsec)
|
||||
}
|
||||
|
||||
const generateEvent = () => {
|
||||
let sk = generateSecretKey();
|
||||
let event = finalizeEvent({
|
||||
kind: 1,
|
||||
content: randomEventContents[Math.floor(Math.random() * randomEventContents.length)],
|
||||
tags: [],
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
}, sk)
|
||||
if (verifyEvent(event)) {
|
||||
setInput(JSON.stringify(event, null, 4))
|
||||
}
|
||||
}
|
||||
|
||||
return (<div class="flex flex-row gap-2">
|
||||
<Button onClick={generateKeyPair}>
|
||||
Generate Keypar
|
||||
</Button>
|
||||
<Button onClick={generateEvent}>
|
||||
Generate Event
|
||||
</Button>
|
||||
{input && <Button variant="destructive" style={{ marginLeft: 'auto' }} onClick={() => setInput('')}>
|
||||
Clear
|
||||
</Button>}
|
||||
</div>)
|
||||
})
|
||||
|
||||
// random event contents
|
||||
const randomEventContents = [
|
||||
"Those bad jokes are the LLM's fault.",
|
||||
"Just deployed my first nostr client - it only shows cat memes, but it's sovereign cat memes!",
|
||||
"My Bitcoin node is so sovereign it refuses to sync with nodes it doesn't philosophically agree with.",
|
||||
"Who needs permission when you have private keys?",
|
||||
"Building permissionless software is like cooking without a recipe - chaotic but delicious.",
|
||||
"My nostr feed is just me talking to myself, but at least I own my data!",
|
||||
"Sovereign engineering is just spicy programming.",
|
||||
"Build -> Show -> Talk -> Realize you forgot to git push.",
|
||||
"Self-validating data is like having a very picky friend who fact-checks everything you say.",
|
||||
"Just wrote 'gm' on nostr. I'm basically a developer now.",
|
||||
"My Bitcoin wallet is so sovereign it won't even let ME access it sometimes.",
|
||||
"Who needs social media when you have decentralized social media that's down half the time?",
|
||||
"Permissionless software: because asking for permission is so Web2.",
|
||||
"My code is so self-validating it refuses to compile until Mercury is out of retrograde.",
|
||||
"Build -> Show -> Talk -> Watch 3 hours of Bitcoin podcasts -> Forget what you were building.",
|
||||
"Just made my software so sovereign it declared independence from my repository.",
|
||||
"My nostr client is so minimal it doesn't even display posts - peak sovereignty achieved!",
|
||||
"Who needs a database when you have the blockchain? (Everyone. Sometimes you need a database.)",
|
||||
"Wrote my first zap request, accidentally zapped my whole wallet.",
|
||||
"Self-validating data is like having a math teacher that always shows their work.",
|
||||
"My private key is so private even I don't know where it is.",
|
||||
"Just achieved perfect decentralization - now I can't find anything.",
|
||||
"Build -> Show -> Talk -> Get distracted by crypto Twitter -> Start over.",
|
||||
"Made my software so permissionless it won't even listen to my commands anymore.",
|
||||
"My Bitcoin node is running on a potato - organic, sovereign computing.",
|
||||
"Sovereign engineering is just regular engineering with extra philosophical debates.",
|
||||
"Just wrote a nostr client that only posts 'few understand this' - revolutionary!",
|
||||
"My code is so decentralized it's currently scattered across 17 different hard drives.",
|
||||
"Who needs user interfaces when you have command lines? (Please someone help with CSS).",
|
||||
"Build -> Show -> Talk -> Realize you're talking to yourself in the mirror.",
|
||||
"Just made my software so sovereign it started its own cryptocurrency.",
|
||||
"My nostr relays are so selective they only relay positive vibes.",
|
||||
"Permissionless development: when your code does whatever it wants anyway.",
|
||||
"Self-validating data is like having a very strict librarian in your code.",
|
||||
"Just deployed to mainnet! (It's a Hello World program but it's sovereign).",
|
||||
"My private keys are stored in a secret location (I forgot where).",
|
||||
"Build -> Show -> Talk -> Get into a philosophical debate about true decentralization.",
|
||||
"Made my app so decentralized even I don't know where the servers are.",
|
||||
"Just wrote a smart contract that only makes dumb decisions.",
|
||||
"My Bitcoin node is validating so hard it's questioning its own existence.",
|
||||
"Sovereign software: because sometimes you need your computer to have a superiority complex.",
|
||||
"Who needs cloud storage when you can have sovereign storage? (Please backup your keys).",
|
||||
"Build -> Show -> Talk -> Spend 5 hours explaining what nostr is.",
|
||||
"Just made my code so self-validating it rejected its own pull requests.",
|
||||
"My nostr client is so minimal it's basically just a blank screen - peak UX!",
|
||||
"Permissionless innovation is great until your code starts innovating without you.",
|
||||
"Self-validating data is like having a conspiracy theorist verify your groceries.",
|
||||
"Just achieved perfect sovereignty - now my software won't talk to any other software.",
|
||||
"My private key generation is so random it surprised even me (I lost everything).",
|
||||
"Build -> Show -> Talk -> Realize nobody understands what you built.",
|
||||
"Made my dapp so decentralized it decentralized itself out of existence.",
|
||||
"Just wrote a Bitcoin script that only accepts transactions on full moons.",
|
||||
"My code is so sovereign it refuses to use external libraries on principle.",
|
||||
"Permissionless systems: because chaos is a feature, not a bug.",
|
||||
"Self-validating code is like having a very pedantic friend review your life choices.",
|
||||
"Just deployed a truly sovereign solution (it's a text file on my desktop).",
|
||||
"My nostr feed is so curated it only shows posts I wrote while sleepwalking.",
|
||||
"Build -> Show -> Talk -> Fork -> Abandon -> Start over.",
|
||||
"Made my wallet so secure it won't let anyone use it, including me.",
|
||||
"Just achieved perfect decentralization by deleting all my code.",
|
||||
"My Bitcoin node is so independent it started its own consensus rules.",
|
||||
"Sovereign engineering is just regular engineering with more Twitter debates.",
|
||||
"Who needs documentation when you have sovereign code that explains itself?",
|
||||
"Build -> Show -> Talk -> Get into a heated debate about the definition of sovereignty.",
|
||||
"Just made my app so trustless it doesn't even trust itself.",
|
||||
"My private keys are protected by a very sophisticated security system (my cat).",
|
||||
"Permissionless development: when your code has more freedom than you do.",
|
||||
"Self-validating systems are like having a very strict parent for your data.",
|
||||
"Just deployed a new feature (it's a bug but we're calling it sovereign behavior).",
|
||||
"My nostr client is so advanced it posts to the future.",
|
||||
"Build -> Show -> Talk -> Realize you're the only user.",
|
||||
"Made my software so decentralized it's now a distributed system of bugs.",
|
||||
"Just wrote a smart contract that makes all the dumb mistakes so you don't have to.",
|
||||
"My code is so sovereign it declared independence from my git history.",
|
||||
"Permissionless innovation is great until your AI starts innovating without you.",
|
||||
"Self-validating code is like having a math professor grade their own homework.",
|
||||
"Just achieved perfect trustlessness - now my programs don't trust my input either.",
|
||||
"My Bitcoin wallet is so cold it's somewhere in Antarctica.",
|
||||
"Build -> Show -> Talk -> Spend 6 hours explaining why Web3 is the future.",
|
||||
"Made my app so secure it disappeared into its own encryption.",
|
||||
"Just wrote a decentralized app that centralizes everything (oops).",
|
||||
"My nostr keys are so private they're having a party without me.",
|
||||
"Sovereign software is like having a teenager - it does whatever it wants.",
|
||||
"Who needs servers when you have sovereign peers? (Please someone seed my data).",
|
||||
"Build -> Show -> Talk -> Watch your users build something completely different.",
|
||||
"Just made my code so immutable it won't let me fix bugs.",
|
||||
"My relay is so selective it only relays messages it personally agrees with.",
|
||||
"Permissionless systems: because sometimes you need your code to rebel.",
|
||||
"Self-validating data is like having a very paranoid friend check your work.",
|
||||
"Just deployed a truly sovereign solution (it only works on my machine).",
|
||||
"My private key backup strategy is based on interpretive dance.",
|
||||
"Build -> Show -> Talk -> Pivot -> Build something completely different.",
|
||||
"Made my software so trustless it fact-checks its own documentation.",
|
||||
"Just achieved perfect decentralization by losing all my data.",
|
||||
"My code is so sovereign it started its own digital nation.",
|
||||
"Permissionless development is just chaos with a fancy name.",
|
||||
"Self-validating systems are like having a very strict HOA for your data.",
|
||||
"Just wrote a Bitcoin script that only works during mercury retrograde.",
|
||||
"My nostr client is so minimal it's basically just a very expensive notepad.",
|
||||
"Build -> Show -> Talk -> Question everything -> Start over."
|
||||
]
|
168
src/components/Output.jsx
Normal file
168
src/components/Output.jsx
Normal file
@ -0,0 +1,168 @@
|
||||
import { connect } from 'unistore/preact';
|
||||
|
||||
// nostr-tools
|
||||
import * as nip19 from "nostr-tools/nip19";
|
||||
import { bytesToHex, hexToBytes } from "@noble/hashes/utils"
|
||||
import { getPublicKey, verifyEvent } from "nostr-tools/pure"
|
||||
|
||||
// store
|
||||
import { storeActions } from "@store"
|
||||
|
||||
// components
|
||||
import { OutputField } from "@components/OutputField"
|
||||
|
||||
// vars
|
||||
const relays = ['wss://relay.damus.io', 'wss://relay.snort.social', 'wss://relay.nostr.bg', 'wss://relay.nostr.band', 'wss://relay.nostr.info', 'wss://relay.nostr.land', 'wss://relay.nostr.ws', 'wss://relay.nostr.zone']
|
||||
|
||||
// functional component
|
||||
export const Output = connect([ 'input'], storeActions)(({ input }) => {
|
||||
const unparsable = () => {
|
||||
let emojis = ['🥺', '🫣', '🤪', '🤨', '🤔', '🤷♂️', '🤷♀️', '🤷♂️', '🤷♀️', '🤷♂️', '🤷♀️', '🙈', '🫨', '😬', '😦', '😵💫', '🥴', '😷', '🙀', '🤌']
|
||||
return <p class="text-sm text-gray-500">{emojis[Math.floor(Math.random() * emojis.length)]} No valid JSON nor NIP-19 conform data.</p>
|
||||
}
|
||||
|
||||
// return nothing if input is empty
|
||||
if (input.length === 0) return '';
|
||||
|
||||
// Handle nostr: URI scheme (NIP-21)
|
||||
if (input.startsWith('nostr:')) {
|
||||
input = input.substring(6);
|
||||
}
|
||||
|
||||
let nip19Entity = false;
|
||||
try {
|
||||
nip19Entity = nip19.decode(input);
|
||||
console.log('nip-19', nip19Entity.type, nip19Entity.data);
|
||||
} catch (error) {
|
||||
// console.error('No NIP-19 entity', error);
|
||||
}
|
||||
|
||||
if (nip19Entity) {
|
||||
|
||||
let conversions = {
|
||||
pubKey: { value: false, label: 'Public Key (HEX)'},
|
||||
privKey: { value: false, label: 'Pivate Key (HEX)'},
|
||||
nsec: { value: false, label: 'Nsec'},
|
||||
npub: { value: false, label: 'Npub'},
|
||||
nprofile: { value: false, label: 'Nprofile'},
|
||||
nevent: { value: false, label: 'Nevent'},
|
||||
nrelay: { value: false, label: 'Nrelay'},
|
||||
}
|
||||
|
||||
switch (nip19Entity.type) {
|
||||
case 'nsec':
|
||||
conversions.nsec.value = nip19.nsecEncode(nip19Entity.data);
|
||||
conversions.privKey.value = bytesToHex(nip19Entity.data);
|
||||
conversions.pubKey.value = getPublicKey(nip19Entity.data);
|
||||
conversions.npub.value = nip19.npubEncode(conversions.pubKey.value);
|
||||
conversions.nprofile.value = nip19.nprofileEncode({ pubkey: conversions.pubKey.value });
|
||||
break;
|
||||
case 'npub':
|
||||
conversions.pubKey.value = nip19Entity.data;
|
||||
conversions.npub.value = nip19.npubEncode(nip19Entity.data);
|
||||
conversions.nprofile.value = nip19.nprofileEncode({ pubkey: conversions.pubKey.value });
|
||||
break;
|
||||
case 'nprofile':
|
||||
console.log('nprofile', nip19Entity.data.pubkey);
|
||||
conversions.pubKey.value = nip19Entity.data.pubkey;
|
||||
conversions.npub.value = nip19.npubEncode(nip19Entity.data.pubkey);
|
||||
conversions.nprofile.value = input;
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="results flex flex-col gap-2 pb-20">
|
||||
{Object.keys(conversions).map((key) => {
|
||||
return conversions[key].value ? <OutputField title={conversions[key].label} text={conversions[key].value} /> : null
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
let hexString = false;
|
||||
try {
|
||||
hexString = hexToBytes(input);
|
||||
console.log('hexString', hexString);
|
||||
} catch (error) {
|
||||
//console.error('No valid hex string', error);
|
||||
}
|
||||
|
||||
if (hexString && (input.length === 64)) {
|
||||
let conversions = {
|
||||
isPubKey: {
|
||||
label: 'If this is a Public Key',
|
||||
values: {
|
||||
npub: { value: nip19.npubEncode(input), label: 'Npub'},
|
||||
nprofile: { value: nip19.nprofileEncode({ pubkey: input }), label: 'Nprofile'},
|
||||
}
|
||||
},
|
||||
isPrivKey: {
|
||||
label: 'If this is a Private Key',
|
||||
values: {
|
||||
nsec: { value: nip19.nsecEncode(hexToBytes(input)), label: 'Nsec'},
|
||||
npub: { value: nip19.npubEncode(getPublicKey(hexToBytes(input))), label: 'Npub'},
|
||||
nprofile: { value: nip19.nprofileEncode({ pubkey: getPublicKey(hexToBytes(input)) }), label: 'Nprofile'},
|
||||
}
|
||||
},
|
||||
isEvent: {
|
||||
label: 'If this is an Event ID',
|
||||
values: {
|
||||
nevent: { value: nip19.neventEncode({ id: input }), label: 'Nevent'},
|
||||
note: { value: nip19.noteEncode(input), label: 'Note'},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="results flex flex-col gap-2 pb-20">
|
||||
{Object.keys(conversions).map((tKey) => {
|
||||
return (
|
||||
<div class="flex flex-col gap-2">
|
||||
<h2 class="text-lg font-bold">{conversions[tKey].label}</h2>
|
||||
{Object.keys(conversions[tKey].values).map((cKey) => {
|
||||
return <OutputField title={conversions[tKey].values[cKey].label} text={conversions[tKey].values[cKey].value} />
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
let isJSON = false;
|
||||
try {
|
||||
JSON.parse(input);
|
||||
isJSON = true;
|
||||
} catch (error) {
|
||||
//console.error('No valid JSON', error);
|
||||
}
|
||||
|
||||
if (isJSON && verifyEvent(JSON.parse(input))) {
|
||||
|
||||
let conversions = {
|
||||
isSigValid: {
|
||||
label: 'Is signature valid?',
|
||||
value: verifyEvent(JSON.parse(input), JSON.parse(input).sig) ? 'YES' : 'NO'
|
||||
},
|
||||
nevent: {
|
||||
label: 'Nevent',
|
||||
value: nip19.neventEncode({ id: JSON.parse(input).id })
|
||||
},
|
||||
note: {
|
||||
label: 'Note',
|
||||
value: nip19.noteEncode(JSON.parse(input).id)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="results flex flex-col gap-2 pb-20">
|
||||
{Object.keys(conversions).map((key) => {
|
||||
return conversions[key].value ? <OutputField title={conversions[key].label} text={conversions[key].value} copy={key === 'isSigValid' ? false : true} /> : null
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// return unparsable if nothing else matches
|
||||
return (<div class="results flex flex-col gap-2 pb-20">{unparsable()}</div>)
|
||||
})
|
50
src/components/OutputField.jsx
Normal file
50
src/components/OutputField.jsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { Button } from "@kksh/react"
|
||||
import { useState } from "preact/hooks"
|
||||
import { clipboard, toast } from "@kksh/api/ui/custom"
|
||||
|
||||
/**
|
||||
* Out component displays text in a field with a copy button
|
||||
* @param {Object} props Component props
|
||||
* @param {string} props.title The title to display
|
||||
* @param {string} props.text The text to display and copy
|
||||
*/
|
||||
export const OutputField = ({ title, text, copy = true }) => {
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await clipboard.writeText(text)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 1000)
|
||||
} catch (err) {
|
||||
toast.error("Failed to copy text:", err)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<h4 className="font-bold text-xs">{title}</h4>
|
||||
<div className="flex items-center border border-black dark:border-white rounded-sm overflow-hidden">
|
||||
<div className="flex-grow px-2 py-2 text-xs overflow-hidden overflow-ellipsis font-mono" onClick={(event) => { const selection = window.getSelection();
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(event.target);
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}}>
|
||||
{text}
|
||||
</div>
|
||||
{copy ? <Button
|
||||
onClick={handleCopy}
|
||||
className="rounded-none h-full"
|
||||
style={{ padding: '.25rem .35rem' }}
|
||||
>
|
||||
{copied ?
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none" fill-rule="evenodd"><path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"/><path fill="currentColor" d="M21.546 5.111a1.5 1.5 0 0 1 0 2.121L10.303 18.475a1.6 1.6 0 0 1-2.263 0L2.454 12.89a1.5 1.5 0 1 1 2.121-2.121l4.596 4.596L19.424 5.111a1.5 1.5 0 0 1 2.122 0"/></g></svg>
|
||||
:
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" fill-rule="evenodd" d="M15 1.25h-4.056c-1.838 0-3.294 0-4.433.153c-1.172.158-2.121.49-2.87 1.238c-.748.749-1.08 1.698-1.238 2.87c-.153 1.14-.153 2.595-.153 4.433V16a3.75 3.75 0 0 0 3.166 3.705c.137.764.402 1.416.932 1.947c.602.602 1.36.86 2.26.982c.867.116 1.97.116 3.337.116h3.11c1.367 0 2.47 0 3.337-.116c.9-.122 1.658-.38 2.26-.982s.86-1.36.982-2.26c.116-.867.116-1.97.116-3.337v-5.11c0-1.367 0-2.47-.116-3.337c-.122-.9-.38-1.658-.982-2.26c-.531-.53-1.183-.795-1.947-.932A3.75 3.75 0 0 0 15 1.25m2.13 3.021A2.25 2.25 0 0 0 15 2.75h-4c-1.907 0-3.261.002-4.29.14c-1.005.135-1.585.389-2.008.812S4.025 4.705 3.89 5.71c-.138 1.029-.14 2.383-.14 4.29v6a2.25 2.25 0 0 0 1.521 2.13c-.021-.61-.021-1.3-.021-2.075v-5.11c0-1.367 0-2.47.117-3.337c.12-.9.38-1.658.981-2.26c.602-.602 1.36-.86 2.26-.981c.867-.117 1.97-.117 3.337-.117h3.11c.775 0 1.464 0 2.074.021M7.408 6.41c.277-.277.665-.457 1.4-.556c.754-.101 1.756-.103 3.191-.103h3c1.435 0 2.436.002 3.192.103c.734.099 1.122.28 1.399.556c.277.277.457.665.556 1.4c.101.754.103 1.756.103 3.191v5c0 1.435-.002 2.436-.103 3.192c-.099.734-.28 1.122-.556 1.399c-.277.277-.665.457-1.4.556c-.755.101-1.756.103-3.191.103h-3c-1.435 0-2.437-.002-3.192-.103c-.734-.099-1.122-.28-1.399-.556c-.277-.277-.457-.665-.556-1.4c-.101-.755-.103-1.756-.103-3.191v-5c0-1.435.002-2.437.103-3.192c.099-.734.28-1.122.556-1.399" clip-rule="evenodd"/></svg>
|
||||
}
|
||||
</Button> : null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
43
src/components/Textarea.jsx
Normal file
43
src/components/Textarea.jsx
Normal file
@ -0,0 +1,43 @@
|
||||
import { Textarea as KKTextarea } from "@kksh/react"
|
||||
import { useEffect, useRef } from "preact/hooks"
|
||||
import { connect } from 'unistore/preact';
|
||||
import { generateSecretKey } from 'nostr-tools/pure'
|
||||
|
||||
// store
|
||||
import { storeActions } from "@store"
|
||||
|
||||
// functional component
|
||||
export const Textarea = connect([
|
||||
'input',
|
||||
'placeholder',
|
||||
],
|
||||
storeActions)(({ input, setInput, placeholder, }) => {
|
||||
const textareaRef = useRef(null);
|
||||
|
||||
// Function to adjust the height
|
||||
const adjustHeight = () => {
|
||||
const textarea = textareaRef.current;
|
||||
if (!textarea) return;
|
||||
|
||||
// Reset height temporarily to get the correct scrollHeight
|
||||
textarea.style.height = 'auto';
|
||||
// Set the height to match content
|
||||
textarea.style.height = `${textarea.scrollHeight}px`;
|
||||
console.log(textarea.scrollHeight)
|
||||
};
|
||||
|
||||
// Adjust height when input changes
|
||||
useEffect(() => {
|
||||
adjustHeight();
|
||||
}, [input]);
|
||||
|
||||
return (<KKTextarea
|
||||
ref={textareaRef}
|
||||
className="w-full h-full max-w-full min-h-[40px] overflow-hidden resize-none"
|
||||
placeholder={placeholder}
|
||||
value={input}
|
||||
onChange={e => {
|
||||
setInput(e.target.value);
|
||||
}}
|
||||
/>)
|
||||
})
|
6
src/index.css
Normal file
6
src/index.css
Normal file
@ -0,0 +1,6 @@
|
||||
@import url("@kksh/react/css");
|
||||
@import url("@kksh/react/themes");
|
||||
/* @tailwind base; */
|
||||
/* This adds white border to command components under dark mode */
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
7
src/main.jsx
Normal file
7
src/main.jsx
Normal file
@ -0,0 +1,7 @@
|
||||
import { render } from "preact"
|
||||
import { App } from "./App.jsx"
|
||||
import "./index.css"
|
||||
import { Provider } from 'unistore/preact'
|
||||
import { store } from '@store'
|
||||
|
||||
render(<Provider store={store}><App /></Provider>, document.getElementById("root"))
|
28
src/modules/store.js
Normal file
28
src/modules/store.js
Normal file
@ -0,0 +1,28 @@
|
||||
// unistore
|
||||
import createStore from 'unistore';
|
||||
|
||||
// Initial state
|
||||
export const initialState = {
|
||||
input: '',
|
||||
placeholder: 'Paste something nostric (event JSON, nprofile, npub, nevent etc or hex key or id)',
|
||||
output: '',
|
||||
pubKeyHex: false,
|
||||
privKeyHex: false,
|
||||
nsec: false,
|
||||
npub: false,
|
||||
nprofile: false,
|
||||
event: false,
|
||||
seriazlizedEvent: false,
|
||||
impliedEventId: false,
|
||||
isSignatureValid: false,
|
||||
nevent: false,
|
||||
note: false
|
||||
};
|
||||
|
||||
// Create store with initial state
|
||||
export const store = createStore(initialState);
|
||||
|
||||
// Actions that can be used to update the store
|
||||
export const storeActions = store => ({
|
||||
setInput: (state, input) => ({ input })
|
||||
});
|
12
tailwind.config.js
Normal file
12
tailwind.config.js
Normal file
@ -0,0 +1,12 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{js,ts,jsx,tsx}"
|
||||
// "./node_modules/@kksh/react/dist/**/*.{js,ts,jsx,tsx}"
|
||||
],
|
||||
theme: {
|
||||
extend: {}
|
||||
},
|
||||
plugins: []
|
||||
}
|
18
vite.config.js
Normal file
18
vite.config.js
Normal file
@ -0,0 +1,18 @@
|
||||
import preact from "@preact/preset-vite"
|
||||
import { defineConfig } from "vite"
|
||||
import { resolve } from 'path'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [preact()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'react': 'preact/compat',
|
||||
'react-dom/test-utils': 'preact/test-utils',
|
||||
'react-dom': 'preact/compat',
|
||||
'react/jsx-runtime': 'preact/jsx-runtime',
|
||||
'@store': resolve(__dirname, 'src/modules/store.js'),
|
||||
'@components': resolve(__dirname, 'src/components')
|
||||
}
|
||||
}
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user