mirror of
https://github.com/joel-st/kunkun-nostr-goto-repo.git
synced 2025-06-18 08:05:02 +00:00
rewrite into customUiCmds
This commit is contained in:
parent
a61db1283f
commit
81f95dbb2b
8
build.ts
8
build.ts
@ -6,11 +6,12 @@ import { $ } from "bun"
|
|||||||
// Define an empty array to store entrypoints
|
// Define an empty array to store entrypoints
|
||||||
const entrypoints: string[] = [];
|
const entrypoints: string[] = [];
|
||||||
|
|
||||||
|
// Handle headless entrypoints
|
||||||
if (Bun.argv.includes("--headless")) {
|
if (Bun.argv.includes("--headless")) {
|
||||||
entrypoints.push("./src/index-github.ts", "./src/index-nostrcom.ts")
|
entrypoints.push("./src/index-github.ts", "./src/index-nostrcom.ts")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is for backwards compatibility
|
||||||
if (Bun.argv.includes("--template")) {
|
if (Bun.argv.includes("--template")) {
|
||||||
entrypoints.push("./src/index-nip.ts")
|
entrypoints.push("./src/index-nip.ts")
|
||||||
}
|
}
|
||||||
@ -35,6 +36,9 @@ await build()
|
|||||||
if (Bun.argv.includes("dev")) {
|
if (Bun.argv.includes("dev")) {
|
||||||
console.log(`Watching ${srcDir} for changes...`)
|
console.log(`Watching ${srcDir} for changes...`)
|
||||||
watch(srcDir, { recursive: true }, async (event, filename) => {
|
watch(srcDir, { recursive: true }, async (event, filename) => {
|
||||||
await build()
|
// Only rebuild if it's not the JSX file (handled by Vite)
|
||||||
|
if (filename && !filename.toString().includes('index-nip.jsx')) {
|
||||||
|
await build()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
1
dist/extension.svg
vendored
Normal file
1
dist/extension.svg
vendored
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 |
8
dist/index-nip.js
vendored
8
dist/index-nip.js
vendored
File diff suppressed because one or more lines are too long
13
dist/index.html
vendored
Normal file
13
dist/index.html
vendored
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 Goto Specific NIP</title>
|
||||||
|
<script type="module" crossorigin src="/index-nip.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
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 Goto Specific NIP</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/index-nip.jsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
16
package.json
16
package.json
@ -39,18 +39,23 @@
|
|||||||
"cmds": []
|
"cmds": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"templateUiCmds": [
|
"customUiCmds": [
|
||||||
{
|
{
|
||||||
"name": "Nostr Open Specific NIP",
|
|
||||||
"main": "dist/index-nip.js",
|
"main": "dist/index-nip.js",
|
||||||
|
"dist": "dist",
|
||||||
|
"devMain": "http://localhost:5173",
|
||||||
|
"name": "Nostr Open Specific NIP",
|
||||||
"cmds": []
|
"cmds": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev:template": "bun build.ts dev --template",
|
"dev": "concurrently \"bun build.ts dev --headless\" \"vite\"",
|
||||||
"dev:headless": "bun build.ts dev --headless",
|
"dev:headless": "bun build.ts dev --headless",
|
||||||
"build": "bun build.ts"
|
"dev:ui": "vite",
|
||||||
|
"build": "bun build.ts --headless && vite build",
|
||||||
|
"build:headless": "bun build.ts --headless",
|
||||||
|
"build:ui": "vite build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify/react": "^5.2.0",
|
"@iconify/react": "^5.2.0",
|
||||||
@ -61,10 +66,11 @@
|
|||||||
"preact": "^10.19.6"
|
"preact": "^10.19.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
|
||||||
"@preact/preset-vite": "^2.8.1",
|
"@preact/preset-vite": "^2.8.1",
|
||||||
|
"@types/bun": "latest",
|
||||||
"@vitejs/plugin-react": "^4.3.1",
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
|
"concurrently": "^9.1.2",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.2",
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
"eslint-plugin-react-refresh": "^0.4.7",
|
"eslint-plugin-react-refresh": "^0.4.7",
|
||||||
|
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
357
src/index-nip-bkp.ts
Normal file
357
src/index-nip-bkp.ts
Normal file
@ -0,0 +1,357 @@
|
|||||||
|
import {
|
||||||
|
Action,
|
||||||
|
expose,
|
||||||
|
Icon,
|
||||||
|
IconEnum,
|
||||||
|
List,
|
||||||
|
open,
|
||||||
|
TemplateUiCommand,
|
||||||
|
toast,
|
||||||
|
ui,
|
||||||
|
db
|
||||||
|
} from "@kksh/api/ui/template";
|
||||||
|
|
||||||
|
interface Nip {
|
||||||
|
nip: string;
|
||||||
|
title: string;
|
||||||
|
rawTitle: string;
|
||||||
|
urlGithub: string;
|
||||||
|
urlNostrCom: string;
|
||||||
|
content: string;
|
||||||
|
kinds: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
class NostrOpenSpecificNip extends TemplateUiCommand {
|
||||||
|
private nips: Nip[] = [];
|
||||||
|
private preferences: string = "";
|
||||||
|
private loading: boolean = false;
|
||||||
|
private searchQuery: string = "";
|
||||||
|
private filteredNips: Nip[] = [];
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
this.loading = true;
|
||||||
|
this.updateUI();
|
||||||
|
try {
|
||||||
|
// Fetch NIPs
|
||||||
|
this.nips = await this.fetchNostrNips();
|
||||||
|
this.filteredNips = this.nips;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load NIPs:', error);
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
this.updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await db.retrieveAll({
|
||||||
|
fields: ["data", "search_text"],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.length > 0 && data[0].data) {
|
||||||
|
this.preferences = JSON.parse(data[0].data) ? JSON.parse(data[0].data) : 'nostr';
|
||||||
|
this.updateUI();
|
||||||
|
} else {
|
||||||
|
this.preferences = 'nostr';
|
||||||
|
this.updateUI();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load preferences:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sortNips(nips: Nip[]): Nip[] {
|
||||||
|
return nips.sort((a, b) => {
|
||||||
|
const aIsNumeric = /^\d+$/.test(a.nip.replace(/^0+/, '')); // Remove leading zeros for numeric comparison
|
||||||
|
const bIsNumeric = /^\d+$/.test(b.nip.replace(/^0+/, ''));
|
||||||
|
|
||||||
|
// If both are numeric, sort by number
|
||||||
|
if (aIsNumeric && bIsNumeric) {
|
||||||
|
return parseInt(a.nip) - parseInt(b.nip);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If only a is numeric, a comes first
|
||||||
|
if (aIsNumeric) return -1;
|
||||||
|
|
||||||
|
// If only b is numeric, b comes first
|
||||||
|
if (bIsNumeric) return 1;
|
||||||
|
|
||||||
|
// If both are non-numeric, sort alphabetically
|
||||||
|
return a.nip.localeCompare(b.nip);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to fetch NIPs from GitHub
|
||||||
|
async fetchNostrNips(): Promise<Nip[]> {
|
||||||
|
try {
|
||||||
|
// Fetch the README.md file from the repository
|
||||||
|
const response = await fetch('https://api.github.com/repos/nostr-protocol/nips/contents/README.md');
|
||||||
|
const fileData = await response.json();
|
||||||
|
|
||||||
|
// Decode content from base64
|
||||||
|
const content = atob(fileData.content);
|
||||||
|
|
||||||
|
// Updated regex to match both numeric and alphanumeric NIP identifiers
|
||||||
|
// Format can be like:
|
||||||
|
// - [NIP-01: Basic protocol flow description](01.md)
|
||||||
|
// - [NIP-7D: Threads](7D.md)
|
||||||
|
const nipRegex = /\- \[NIP-([0-9A-Za-z]+)\: (.*?)\]\(([0-9A-Za-z]+\.md)\)/g;
|
||||||
|
|
||||||
|
const nips: Nip[] = [];
|
||||||
|
let match;
|
||||||
|
|
||||||
|
// Find all matches in the content
|
||||||
|
while ((match = nipRegex.exec(content)) !== null) {
|
||||||
|
const nipId = match[1]; // This can be numeric or alphanumeric
|
||||||
|
const title = match[2];
|
||||||
|
const filename = match[3];
|
||||||
|
|
||||||
|
// Generate URLs based on NIP identifier
|
||||||
|
const urlGithub = `https://github.com/nostr-protocol/nips/blob/master/${filename}`;
|
||||||
|
|
||||||
|
// For nostr.com URL, only use numeric format for numeric NIPs
|
||||||
|
// For alphanumeric NIPs, we'll still use the same format but be aware it might not work properly
|
||||||
|
const isNumeric = /^\d+$/.test(nipId);
|
||||||
|
const nipNumberNoPad = isNumeric ? parseInt(nipId) : nipId;
|
||||||
|
const urlNostrCom = `https://nips.nostr.com/${nipNumberNoPad}`;
|
||||||
|
|
||||||
|
// Format the NIP identifier consistently for display
|
||||||
|
// For numeric NIPs, pad with leading zero if needed
|
||||||
|
const formattedNipId = isNumeric ? nipId.padStart(2, '0') : nipId;
|
||||||
|
|
||||||
|
nips.push({
|
||||||
|
nip: formattedNipId,
|
||||||
|
title: title,
|
||||||
|
rawTitle: title, // Store raw title without formatting
|
||||||
|
urlGithub: urlGithub,
|
||||||
|
urlNostrCom: urlNostrCom,
|
||||||
|
content: `NIP-${formattedNipId}: ${title}`,
|
||||||
|
kinds: [] // Initialize with empty array, will be populated later
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the Event Kinds table
|
||||||
|
// This table is located after the "## Event Kinds" heading
|
||||||
|
const eventKindsSection = content.match(/## Event Kinds\s+([\s\S]+?)(?=##|$)/);
|
||||||
|
if (eventKindsSection && eventKindsSection[1]) {
|
||||||
|
// split table by rows first, then columns for better parsing
|
||||||
|
const tableRows: Array<[number[], string, string[]]> = [];
|
||||||
|
const tableContent = eventKindsSection[1].trim();
|
||||||
|
const tableLines = tableContent.split('\n').filter(line => line.trim() !== '');
|
||||||
|
|
||||||
|
// Skip the header and separator rows
|
||||||
|
for (let i = 2; i < tableLines.length; i++) {
|
||||||
|
const row = tableLines[i];
|
||||||
|
const cells = row.split('|').map(cell => cell.trim()).filter(cell => cell !== '');
|
||||||
|
|
||||||
|
if (cells.length >= 3) {
|
||||||
|
const kindCell = cells[0];
|
||||||
|
const descriptionCell = cells[1];
|
||||||
|
|
||||||
|
// Parse the kind value - handle both single numbers and ranges
|
||||||
|
let kinds: number[] = [];
|
||||||
|
|
||||||
|
// Match single numbers: `123`
|
||||||
|
const singleMatch = kindCell.match(/`(\d+)`/g);
|
||||||
|
if (singleMatch) {
|
||||||
|
singleMatch.forEach(match => {
|
||||||
|
const num = parseInt(match.replace(/`/g, ''));
|
||||||
|
if (!isNaN(num)) {
|
||||||
|
kinds.push(num);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match ranges: `10`-`20`
|
||||||
|
const rangeMatch = kindCell.match(/`(\d+)`\s*-\s*`(\d+)`/);
|
||||||
|
if (rangeMatch && rangeMatch[1] && rangeMatch[2]) {
|
||||||
|
const start = parseInt(rangeMatch[1]);
|
||||||
|
const end = parseInt(rangeMatch[2]);
|
||||||
|
|
||||||
|
if (!isNaN(start) && !isNaN(end)) {
|
||||||
|
for (let j = start; j <= end; j++) {
|
||||||
|
kinds.push(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let nipCell = cells[2];
|
||||||
|
|
||||||
|
// parse the nipCell
|
||||||
|
let extractedNips: string[] = [];
|
||||||
|
|
||||||
|
// Pattern 1: [number|alphanumeric](number|alphanumeric.md) - e.g. "[01](01.md)"
|
||||||
|
const pattern1 = /\[([0-9A-Za-z]+)\]\([0-9A-Za-z]+\.md\)/g;
|
||||||
|
let match1;
|
||||||
|
while ((match1 = pattern1.exec(nipCell)) !== null) {
|
||||||
|
extractedNips.push(match1[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern 2: number|alphanumeric (deprecated) - e.g. "01 (deprecated)"
|
||||||
|
const pattern2 = /([0-9A-Za-z]+)\s+\(deprecated\)/g;
|
||||||
|
let match2;
|
||||||
|
while ((match2 = pattern2.exec(nipCell)) !== null) {
|
||||||
|
extractedNips.push(match2[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern 3: [number|alphanumeric](number|alphanumeric.md), [number|alphanumeric](number|alphanumeric.md), ...
|
||||||
|
// This is already handled by pattern1, as it will match each occurrence in a comma-separated list
|
||||||
|
|
||||||
|
if (kinds.length > 0 && extractedNips.length > 0) {
|
||||||
|
tableRows.push([kinds, descriptionCell, extractedNips]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the parsed rows
|
||||||
|
tableRows.forEach(row => {
|
||||||
|
const kinds = row[0];
|
||||||
|
const extractedNips = row[2]; // Get the extractedNips array from the row
|
||||||
|
|
||||||
|
// Use the extracted NIPs directly instead of parsing again
|
||||||
|
extractedNips.forEach(nipId => {
|
||||||
|
// Find the corresponding NIP in our array
|
||||||
|
const nip = nips.find(n => n.nip === nipId || (nipId.startsWith('0') && n.nip === nipId.replace(/^0+/, '')));
|
||||||
|
if (nip) {
|
||||||
|
// Add these kinds to the NIP
|
||||||
|
nip.kinds = [...nip.kinds, ...kinds];
|
||||||
|
// Remove duplicates
|
||||||
|
nip.kinds = [...new Set(nip.kinds)];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort NIPs: numeric ones first (sorted by number), then alphanumeric (sorted alphabetically)
|
||||||
|
return this.sortNips(nips);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching NIPs:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter NIPs based on search query
|
||||||
|
async setFilteredNips(): Promise<void> {
|
||||||
|
|
||||||
|
if (!this.searchQuery) {
|
||||||
|
this.filteredNips = this.nips
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = this.searchQuery.toLowerCase();
|
||||||
|
|
||||||
|
// Check if the query contains a kind search pattern (e.g., k1, k10002)
|
||||||
|
const kindMatch = query.match(/k(\d+)/);
|
||||||
|
|
||||||
|
let filteredNips = this.nips.filter(nip => {
|
||||||
|
// If there's a kind match, check if this NIP includes that kind
|
||||||
|
if (kindMatch && nip.kinds.includes(parseInt(kindMatch[1]))) {
|
||||||
|
console.log('kindMatch', kindMatch, nip);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always perform text search regardless of kind match
|
||||||
|
return nip.rawTitle.toLowerCase().includes(query) ||
|
||||||
|
nip.nip.toLowerCase().includes(query) ||
|
||||||
|
nip.content.toLowerCase().includes(query) ||
|
||||||
|
(kindMatch && nip.kinds.includes(parseInt(kindMatch[1])));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.filteredNips = this.sortNips(filteredNips);
|
||||||
|
this.updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle search input change
|
||||||
|
async onSearchTermChange(query: string): Promise<void> {
|
||||||
|
this.searchQuery = query;
|
||||||
|
await this.setFilteredNips();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create action panel for the footer
|
||||||
|
getFooterActions(): Action.Action[] {
|
||||||
|
let actions = [
|
||||||
|
new Action.Action({
|
||||||
|
title: "Always open on nips.nostr.com",
|
||||||
|
value: "open-nostr",
|
||||||
|
icon: new Icon({
|
||||||
|
type: IconEnum.Iconify,
|
||||||
|
value: "game-icons:ostrich",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
new Action.Action({
|
||||||
|
title: "Always open on GitHub",
|
||||||
|
value: "open-github",
|
||||||
|
icon: new Icon({
|
||||||
|
type: IconEnum.Iconify,
|
||||||
|
value: "mdi:github",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (this.preferences === "github") {
|
||||||
|
actions.reverse()
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async updateUI() {
|
||||||
|
return ui
|
||||||
|
.setSearchBarPlaceholder("Number, Title, k[X], t[X]…")
|
||||||
|
.then(() => {
|
||||||
|
return ui.render(
|
||||||
|
new List.List({
|
||||||
|
sections: [
|
||||||
|
new List.Section({
|
||||||
|
title: "Nostr Implementation Possibilities",
|
||||||
|
items: this.filteredNips.map(
|
||||||
|
(nip) =>
|
||||||
|
new List.Item({
|
||||||
|
title: `NIP-${nip.nip}: ${nip.title}`,
|
||||||
|
value: this.preferences === "nostr" ? nip.urlNostrCom : nip.urlGithub,
|
||||||
|
icon: new Icon({
|
||||||
|
type: IconEnum.Iconify,
|
||||||
|
value: "majesticons:open",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
actions: new Action.ActionPanel({
|
||||||
|
items: this.getFooterActions(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onListItemSelected(value: string): Promise<void> {
|
||||||
|
return open.url(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
async onActionSelected(value: string): Promise<void> {
|
||||||
|
if (value === "open-nostr") {
|
||||||
|
await db.deleteAll();
|
||||||
|
await db.add({
|
||||||
|
data: JSON.stringify('nostr'),
|
||||||
|
dataType: "preference",
|
||||||
|
searchText: "open_with",
|
||||||
|
});
|
||||||
|
this.preferences = 'nostr';
|
||||||
|
this.updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === "open-github") {
|
||||||
|
await db.deleteAll();
|
||||||
|
await db.add({
|
||||||
|
data: JSON.stringify('github'),
|
||||||
|
dataType: "preference",
|
||||||
|
searchText: "open_with",
|
||||||
|
});
|
||||||
|
this.preferences = 'github';
|
||||||
|
this.updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expose(new NostrOpenSpecificNip());
|
78
src/index-nip-test.ts
Normal file
78
src/index-nip-test.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/**
|
||||||
|
* Kunkun Extension to display a searchable list of random words
|
||||||
|
* Clicking an item logs it to the console
|
||||||
|
*/
|
||||||
|
import { TemplateUiCommand, expose, List, ui } from "@kksh/api/ui/template";
|
||||||
|
|
||||||
|
// List of random words
|
||||||
|
const randomWords = [
|
||||||
|
"apple", "banana", "cherry", "date", "elderberry",
|
||||||
|
"fig", "grape", "honeydew", "kiwi", "lemon",
|
||||||
|
"mango", "nectarine", "orange", "pear", "quince",
|
||||||
|
"raspberry", "strawberry", "tangerine", "watermelon", "zucchini"
|
||||||
|
];
|
||||||
|
|
||||||
|
class RandomWordsExtension extends TemplateUiCommand {
|
||||||
|
private filteredWords: string[] = [];
|
||||||
|
public searchTerm: string = "";
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
// Initialize with all words
|
||||||
|
this.filteredWords = [...randomWords];
|
||||||
|
// Set a custom placeholder for the search bar
|
||||||
|
await ui.setSearchBarPlaceholder("Type to NOT filter (demonstration)");
|
||||||
|
await this.updateUI();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onSearchTermChange(term: string) {
|
||||||
|
// Store the search term
|
||||||
|
this.searchTerm = term;
|
||||||
|
|
||||||
|
// Clear any automatic filtering by setting filtered to ALL words
|
||||||
|
this.filteredWords = [...randomWords];
|
||||||
|
|
||||||
|
// Add a prefix to show we're handling the search
|
||||||
|
this.filteredWords = this.filteredWords.map(word =>
|
||||||
|
`${term ? "🔍 " : ""}${word}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Log the search term for debugging
|
||||||
|
console.log(`Search term "${term}" received and explicitly ignored`);
|
||||||
|
|
||||||
|
// Complete bypass of the default filtering by doing full UI update
|
||||||
|
await this.updateUI();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onListItemSelected(value: string) {
|
||||||
|
// Log the selected word to the console
|
||||||
|
console.log(`Selected word: ${value}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the updateUI pattern from the more complex implementation
|
||||||
|
private async updateUI() {
|
||||||
|
// Create list items using the proper List API
|
||||||
|
const listItems = this.filteredWords.map(word =>
|
||||||
|
new List.Item({
|
||||||
|
title: word,
|
||||||
|
subTitle: this.searchTerm ?
|
||||||
|
`Search term: "${this.searchTerm}" is being ignored` :
|
||||||
|
`No filtering applied`,
|
||||||
|
value: word
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a list with the items
|
||||||
|
const listView = new List.List({
|
||||||
|
items: listItems
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render the list using the proper UI API
|
||||||
|
await ui.render(listView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose the extension
|
||||||
|
expose(new RandomWordsExtension());
|
46
src/index-nip.jsx
Normal file
46
src/index-nip.jsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { render } from "preact"
|
||||||
|
import { useEffect, useState, useRef } from "preact/hooks"
|
||||||
|
import { ui } from "@kksh/api/ui/custom"
|
||||||
|
import {
|
||||||
|
ThemeProvider,
|
||||||
|
ThemeWrapper,
|
||||||
|
Input
|
||||||
|
} from "@kksh/react"
|
||||||
|
|
||||||
|
import "./index.css"
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
const [searchTerm, setSearchTerm] = useState("")
|
||||||
|
const searchRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ui.registerDragRegion()
|
||||||
|
ui.showMoveButton({
|
||||||
|
bottom: 0.2,
|
||||||
|
left: 0.2
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
console.log('searchTerm', searchTerm)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeProvider storageKey="kk-ui-theme">
|
||||||
|
<ThemeWrapper>
|
||||||
|
<main className="h-screen flex flex-col">
|
||||||
|
<Input
|
||||||
|
autoFocus
|
||||||
|
ref={searchRef}
|
||||||
|
type="text"
|
||||||
|
placeholder="Search..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
style={{ height: "3.25rem", paddingLeft: "3.25rem"}}
|
||||||
|
className="w-full rounded-none border-l-0 border-r-0 border-t-0 focus:outline-none focus:ring-0"
|
||||||
|
/>
|
||||||
|
</main>
|
||||||
|
</ThemeWrapper>
|
||||||
|
</ThemeProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render(<App />, document.getElementById("root"))
|
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;
|
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: []
|
||||||
|
}
|
32
vite.config.js
Normal file
32
vite.config.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import preact from "@preact/preset-vite"
|
||||||
|
import { defineConfig } from "vite"
|
||||||
|
import { resolve } from 'path'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [preact()],
|
||||||
|
build: {
|
||||||
|
outDir: 'dist',
|
||||||
|
emptyOutDir: false, // Important: don't delete headless built files
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
main: resolve(__dirname, 'index.html'),
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
entryFileNames: 'index-nip.js',
|
||||||
|
chunkFileNames: 'index-nip-chunk-[name].js',
|
||||||
|
assetFileNames: 'index-nip-assets/[name].[ext]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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