refactor: upgrade
This commit is contained in:
parent
01eaf9996c
commit
e2714c4274
@ -1,7 +1,15 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||||
parser: "@typescript-eslint/parser",
|
parser: "@typescript-eslint/parser",
|
||||||
plugins: ["@typescript-eslint"],
|
plugins: ["@typescript-eslint", "formatjs"],
|
||||||
|
rules: {
|
||||||
|
"formatjs/enforce-id": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
idInterpolationPattern: "[sha512:contenthash:base64:6]",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
root: true,
|
root: true,
|
||||||
ignorePatterns: ["build/", "*.test.ts", "*.js"],
|
ignorePatterns: ["build/", "*.test.ts", "*.js"],
|
||||||
env: {
|
env: {
|
||||||
@ -10,10 +18,4 @@ module.exports = {
|
|||||||
commonjs: true,
|
commonjs: true,
|
||||||
node: false,
|
node: false,
|
||||||
},
|
},
|
||||||
rules: {
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "error",
|
|
||||||
"require-await": "error",
|
|
||||||
eqeqeq: "error",
|
|
||||||
"object-shorthand": "warn",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -30,3 +30,5 @@ yarn-error.log*
|
|||||||
!.yarn/releases
|
!.yarn/releases
|
||||||
!.yarn/sdks
|
!.yarn/sdks
|
||||||
!.yarn/versions
|
!.yarn/versions
|
||||||
|
|
||||||
|
dev-dist/
|
@ -2,3 +2,5 @@
|
|||||||
.vscode/
|
.vscode/
|
||||||
node_modules/
|
node_modules/
|
||||||
build/
|
build/
|
||||||
|
dist/
|
||||||
|
dev-dist/
|
6
.vscode/extensions.json
vendored
6
.vscode/extensions.json
vendored
@ -1,3 +1,7 @@
|
|||||||
{
|
{
|
||||||
"recommendations": ["arcanis.vscode-zipfs", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
|
"recommendations": [
|
||||||
|
"arcanis.vscode-zipfs",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
6
.yarn/sdks/eslint/bin/eslint.js
vendored
6
.yarn/sdks/eslint/bin/eslint.js
vendored
@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
const { existsSync } = require(`fs`);
|
const {existsSync} = require(`fs`);
|
||||||
const { createRequire } = require(`module`);
|
const {createRequire} = require(`module`);
|
||||||
const { resolve } = require(`path`);
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
6
.yarn/sdks/eslint/lib/api.js
vendored
6
.yarn/sdks/eslint/lib/api.js
vendored
@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
const { existsSync } = require(`fs`);
|
const {existsSync} = require(`fs`);
|
||||||
const { createRequire } = require(`module`);
|
const {createRequire} = require(`module`);
|
||||||
const { resolve } = require(`path`);
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
20
.yarn/sdks/eslint/lib/unsupported-api.js
vendored
Normal file
20
.yarn/sdks/eslint/lib/unsupported-api.js
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require eslint/use-at-your-own-risk
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real eslint/use-at-your-own-risk your application uses
|
||||||
|
module.exports = absRequire(`eslint/use-at-your-own-risk`);
|
10
.yarn/sdks/eslint/package.json
vendored
10
.yarn/sdks/eslint/package.json
vendored
@ -2,5 +2,13 @@
|
|||||||
"name": "eslint",
|
"name": "eslint",
|
||||||
"version": "8.48.0-sdk",
|
"version": "8.48.0-sdk",
|
||||||
"main": "./lib/api.js",
|
"main": "./lib/api.js",
|
||||||
"type": "commonjs"
|
"type": "commonjs",
|
||||||
|
"bin": {
|
||||||
|
"eslint": "./bin/eslint.js"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
"./package.json": "./package.json",
|
||||||
|
".": "./lib/api.js",
|
||||||
|
"./use-at-your-own-risk": "./lib/unsupported-api.js"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
20
.yarn/sdks/prettier/bin-prettier.js
vendored
Executable file
20
.yarn/sdks/prettier/bin-prettier.js
vendored
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const {existsSync} = require(`fs`);
|
||||||
|
const {createRequire} = require(`module`);
|
||||||
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
|
const relPnpApiPath = "../../../.pnp.cjs";
|
||||||
|
|
||||||
|
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||||
|
const absRequire = createRequire(absPnpApiPath);
|
||||||
|
|
||||||
|
if (existsSync(absPnpApiPath)) {
|
||||||
|
if (!process.versions.pnp) {
|
||||||
|
// Setup the environment to be able to require prettier/bin-prettier.js
|
||||||
|
require(absPnpApiPath).setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to the real prettier/bin-prettier.js your application uses
|
||||||
|
module.exports = absRequire(`prettier/bin-prettier.js`);
|
6
.yarn/sdks/prettier/index.js
vendored
Executable file → Normal file
6
.yarn/sdks/prettier/index.js
vendored
Executable file → Normal file
@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
const { existsSync } = require(`fs`);
|
const {existsSync} = require(`fs`);
|
||||||
const { createRequire } = require(`module`);
|
const {createRequire} = require(`module`);
|
||||||
const { resolve } = require(`path`);
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
const relPnpApiPath = "../../../.pnp.cjs";
|
const relPnpApiPath = "../../../.pnp.cjs";
|
||||||
|
|
||||||
|
3
.yarn/sdks/prettier/package.json
vendored
3
.yarn/sdks/prettier/package.json
vendored
@ -2,5 +2,6 @@
|
|||||||
"name": "prettier",
|
"name": "prettier",
|
||||||
"version": "2.8.8-sdk",
|
"version": "2.8.8-sdk",
|
||||||
"main": "./index.js",
|
"main": "./index.js",
|
||||||
"type": "commonjs"
|
"type": "commonjs",
|
||||||
|
"bin": "./bin-prettier.js"
|
||||||
}
|
}
|
||||||
|
6
.yarn/sdks/typescript/lib/tsc.js
vendored
6
.yarn/sdks/typescript/lib/tsc.js
vendored
@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
const { existsSync } = require(`fs`);
|
const {existsSync} = require(`fs`);
|
||||||
const { createRequire } = require(`module`);
|
const {createRequire} = require(`module`);
|
||||||
const { resolve } = require(`path`);
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
|
117
.yarn/sdks/typescript/lib/tsserver.js
vendored
117
.yarn/sdks/typescript/lib/tsserver.js
vendored
@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
const { existsSync } = require(`fs`);
|
const {existsSync} = require(`fs`);
|
||||||
const { createRequire } = require(`module`);
|
const {createRequire} = require(`module`);
|
||||||
const { resolve } = require(`path`);
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
@ -14,18 +14,16 @@ const moduleWrapper = tsserver => {
|
|||||||
return tsserver;
|
return tsserver;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { isAbsolute } = require(`path`);
|
const {isAbsolute} = require(`path`);
|
||||||
const pnpApi = require(`pnpapi`);
|
const pnpApi = require(`pnpapi`);
|
||||||
|
|
||||||
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
|
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
|
||||||
const isPortal = str => str.startsWith("portal:/");
|
const isPortal = str => str.startsWith("portal:/");
|
||||||
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
|
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
|
||||||
|
|
||||||
const dependencyTreeRoots = new Set(
|
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
|
||||||
pnpApi.getDependencyTreeRoots().map(locator => {
|
|
||||||
return `${locator.name}@${locator.reference}`;
|
return `${locator.name}@${locator.reference}`;
|
||||||
})
|
}));
|
||||||
);
|
|
||||||
|
|
||||||
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
|
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
|
||||||
// doesn't understand. This layer makes sure to remove the protocol
|
// doesn't understand. This layer makes sure to remove the protocol
|
||||||
@ -47,10 +45,7 @@ const moduleWrapper = tsserver => {
|
|||||||
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
|
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
const locator = pnpApi.findPackageLocator(resolved);
|
const locator = pnpApi.findPackageLocator(resolved);
|
||||||
if (
|
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
|
||||||
locator &&
|
|
||||||
(dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))
|
|
||||||
) {
|
|
||||||
str = resolved;
|
str = resolved;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,55 +73,41 @@ const moduleWrapper = tsserver => {
|
|||||||
// Before | ^/zip/c:/foo/bar.zip/package.json
|
// Before | ^/zip/c:/foo/bar.zip/package.json
|
||||||
// After | ^/zip//c:/foo/bar.zip/package.json
|
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||||
//
|
//
|
||||||
case `vscode <1.61`:
|
case `vscode <1.61`: {
|
||||||
{
|
|
||||||
str = `^zip:${str}`;
|
str = `^zip:${str}`;
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
case `vscode <1.66`:
|
case `vscode <1.66`: {
|
||||||
{
|
|
||||||
str = `^/zip/${str}`;
|
str = `^/zip/${str}`;
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
case `vscode <1.68`:
|
case `vscode <1.68`: {
|
||||||
{
|
|
||||||
str = `^/zip${str}`;
|
str = `^/zip${str}`;
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
case `vscode`:
|
case `vscode`: {
|
||||||
{
|
|
||||||
str = `^/zip/${str}`;
|
str = `^/zip/${str}`;
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
// To make "go to definition" work,
|
// To make "go to definition" work,
|
||||||
// We have to resolve the actual file system path from virtual path
|
// We have to resolve the actual file system path from virtual path
|
||||||
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
|
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
|
||||||
case `coc-nvim`:
|
case `coc-nvim`: {
|
||||||
{
|
|
||||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||||
str = resolve(`zipfile:${str}`);
|
str = resolve(`zipfile:${str}`);
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
|
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
|
||||||
// We have to resolve the actual file system path from virtual path,
|
// We have to resolve the actual file system path from virtual path,
|
||||||
// everything else is up to neovim
|
// everything else is up to neovim
|
||||||
case `neovim`:
|
case `neovim`: {
|
||||||
{
|
|
||||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||||
str = `zipfile://${str}`;
|
str = `zipfile://${str}`;
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default: {
|
||||||
{
|
|
||||||
str = `zip:${str}`;
|
str = `zip:${str}`;
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`);
|
str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`);
|
||||||
@ -138,30 +119,26 @@ const moduleWrapper = tsserver => {
|
|||||||
|
|
||||||
function fromEditorPath(str) {
|
function fromEditorPath(str) {
|
||||||
switch (hostInfo) {
|
switch (hostInfo) {
|
||||||
case `coc-nvim`:
|
case `coc-nvim`: {
|
||||||
{
|
|
||||||
str = str.replace(/\.zip::/, `.zip/`);
|
str = str.replace(/\.zip::/, `.zip/`);
|
||||||
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
|
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
|
||||||
// So in order to convert it back, we use .* to match all the thing
|
// So in order to convert it back, we use .* to match all the thing
|
||||||
// before `zipfile:`
|
// before `zipfile:`
|
||||||
return process.platform === `win32` ? str.replace(/^.*zipfile:\//, ``) : str.replace(/^.*zipfile:/, ``);
|
return process.platform === `win32`
|
||||||
}
|
? str.replace(/^.*zipfile:\//, ``)
|
||||||
break;
|
: str.replace(/^.*zipfile:/, ``);
|
||||||
|
} break;
|
||||||
|
|
||||||
case `neovim`:
|
case `neovim`: {
|
||||||
{
|
|
||||||
str = str.replace(/\.zip::/, `.zip/`);
|
str = str.replace(/\.zip::/, `.zip/`);
|
||||||
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
|
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
|
||||||
return str.replace(/^zipfile:\/\//, ``);
|
return str.replace(/^zipfile:\/\//, ``);
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
case `vscode`:
|
case `vscode`:
|
||||||
default:
|
default: {
|
||||||
{
|
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`)
|
||||||
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`);
|
} break;
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,8 +150,8 @@ const moduleWrapper = tsserver => {
|
|||||||
// TypeScript already does local loads and if this code is running the user trusts the workspace
|
// TypeScript already does local loads and if this code is running the user trusts the workspace
|
||||||
// https://github.com/microsoft/vscode/issues/45856
|
// https://github.com/microsoft/vscode/issues/45856
|
||||||
const ConfiguredProject = tsserver.server.ConfiguredProject;
|
const ConfiguredProject = tsserver.server.ConfiguredProject;
|
||||||
const { enablePluginsWithOptions: originalEnablePluginsWithOptions } = ConfiguredProject.prototype;
|
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
|
||||||
ConfiguredProject.prototype.enablePluginsWithOptions = function () {
|
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
|
||||||
this.projectService.allowLocalPluginLoads = true;
|
this.projectService.allowLocalPluginLoads = true;
|
||||||
return originalEnablePluginsWithOptions.apply(this, arguments);
|
return originalEnablePluginsWithOptions.apply(this, arguments);
|
||||||
};
|
};
|
||||||
@ -184,12 +161,12 @@ const moduleWrapper = tsserver => {
|
|||||||
// like an absolute path of ours and normalize it.
|
// like an absolute path of ours and normalize it.
|
||||||
|
|
||||||
const Session = tsserver.server.Session;
|
const Session = tsserver.server.Session;
|
||||||
const { onMessage: originalOnMessage, send: originalSend } = Session.prototype;
|
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
|
||||||
let hostInfo = `unknown`;
|
let hostInfo = `unknown`;
|
||||||
|
|
||||||
Object.assign(Session.prototype, {
|
Object.assign(Session.prototype, {
|
||||||
onMessage(/** @type {string | object} */ message) {
|
onMessage(/** @type {string | object} */ message) {
|
||||||
const isStringMessage = typeof message === "string";
|
const isStringMessage = typeof message === 'string';
|
||||||
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
|
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -200,12 +177,10 @@ const moduleWrapper = tsserver => {
|
|||||||
) {
|
) {
|
||||||
hostInfo = parsedMessage.arguments.hostInfo;
|
hostInfo = parsedMessage.arguments.hostInfo;
|
||||||
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
|
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
|
||||||
const [, major, minor] = (
|
const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match(
|
||||||
process.env.VSCODE_IPC_HOOK.match(
|
|
||||||
// The RegExp from https://semver.org/ but without the caret at the start
|
// The RegExp from https://semver.org/ but without the caret at the start
|
||||||
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
|
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
|
||||||
) ?? []
|
) ?? []).map(Number)
|
||||||
).map(Number);
|
|
||||||
|
|
||||||
if (major === 1) {
|
if (major === 1) {
|
||||||
if (minor < 61) {
|
if (minor < 61) {
|
||||||
@ -220,22 +195,20 @@ const moduleWrapper = tsserver => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
|
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
|
||||||
return typeof value === "string" ? fromEditorPath(value) : value;
|
return typeof value === 'string' ? fromEditorPath(value) : value;
|
||||||
});
|
});
|
||||||
|
|
||||||
return originalOnMessage.call(this, isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON));
|
return originalOnMessage.call(
|
||||||
|
this,
|
||||||
|
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
send(/** @type {any} */ msg) {
|
send(/** @type {any} */ msg) {
|
||||||
return originalSend.call(
|
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
|
||||||
this,
|
|
||||||
JSON.parse(
|
|
||||||
JSON.stringify(msg, (key, value) => {
|
|
||||||
return typeof value === `string` ? toEditorPath(value) : value;
|
return typeof value === `string` ? toEditorPath(value) : value;
|
||||||
})
|
})));
|
||||||
)
|
}
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return tsserver;
|
return tsserver;
|
||||||
|
117
.yarn/sdks/typescript/lib/tsserverlibrary.js
vendored
117
.yarn/sdks/typescript/lib/tsserverlibrary.js
vendored
@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
const { existsSync } = require(`fs`);
|
const {existsSync} = require(`fs`);
|
||||||
const { createRequire } = require(`module`);
|
const {createRequire} = require(`module`);
|
||||||
const { resolve } = require(`path`);
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
@ -14,18 +14,16 @@ const moduleWrapper = tsserver => {
|
|||||||
return tsserver;
|
return tsserver;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { isAbsolute } = require(`path`);
|
const {isAbsolute} = require(`path`);
|
||||||
const pnpApi = require(`pnpapi`);
|
const pnpApi = require(`pnpapi`);
|
||||||
|
|
||||||
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
|
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
|
||||||
const isPortal = str => str.startsWith("portal:/");
|
const isPortal = str => str.startsWith("portal:/");
|
||||||
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
|
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
|
||||||
|
|
||||||
const dependencyTreeRoots = new Set(
|
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
|
||||||
pnpApi.getDependencyTreeRoots().map(locator => {
|
|
||||||
return `${locator.name}@${locator.reference}`;
|
return `${locator.name}@${locator.reference}`;
|
||||||
})
|
}));
|
||||||
);
|
|
||||||
|
|
||||||
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
|
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
|
||||||
// doesn't understand. This layer makes sure to remove the protocol
|
// doesn't understand. This layer makes sure to remove the protocol
|
||||||
@ -47,10 +45,7 @@ const moduleWrapper = tsserver => {
|
|||||||
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
|
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
const locator = pnpApi.findPackageLocator(resolved);
|
const locator = pnpApi.findPackageLocator(resolved);
|
||||||
if (
|
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
|
||||||
locator &&
|
|
||||||
(dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))
|
|
||||||
) {
|
|
||||||
str = resolved;
|
str = resolved;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,55 +73,41 @@ const moduleWrapper = tsserver => {
|
|||||||
// Before | ^/zip/c:/foo/bar.zip/package.json
|
// Before | ^/zip/c:/foo/bar.zip/package.json
|
||||||
// After | ^/zip//c:/foo/bar.zip/package.json
|
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||||
//
|
//
|
||||||
case `vscode <1.61`:
|
case `vscode <1.61`: {
|
||||||
{
|
|
||||||
str = `^zip:${str}`;
|
str = `^zip:${str}`;
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
case `vscode <1.66`:
|
case `vscode <1.66`: {
|
||||||
{
|
|
||||||
str = `^/zip/${str}`;
|
str = `^/zip/${str}`;
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
case `vscode <1.68`:
|
case `vscode <1.68`: {
|
||||||
{
|
|
||||||
str = `^/zip${str}`;
|
str = `^/zip${str}`;
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
case `vscode`:
|
case `vscode`: {
|
||||||
{
|
|
||||||
str = `^/zip/${str}`;
|
str = `^/zip/${str}`;
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
// To make "go to definition" work,
|
// To make "go to definition" work,
|
||||||
// We have to resolve the actual file system path from virtual path
|
// We have to resolve the actual file system path from virtual path
|
||||||
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
|
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
|
||||||
case `coc-nvim`:
|
case `coc-nvim`: {
|
||||||
{
|
|
||||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||||
str = resolve(`zipfile:${str}`);
|
str = resolve(`zipfile:${str}`);
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
|
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
|
||||||
// We have to resolve the actual file system path from virtual path,
|
// We have to resolve the actual file system path from virtual path,
|
||||||
// everything else is up to neovim
|
// everything else is up to neovim
|
||||||
case `neovim`:
|
case `neovim`: {
|
||||||
{
|
|
||||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||||
str = `zipfile://${str}`;
|
str = `zipfile://${str}`;
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default: {
|
||||||
{
|
|
||||||
str = `zip:${str}`;
|
str = `zip:${str}`;
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`);
|
str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`);
|
||||||
@ -138,30 +119,26 @@ const moduleWrapper = tsserver => {
|
|||||||
|
|
||||||
function fromEditorPath(str) {
|
function fromEditorPath(str) {
|
||||||
switch (hostInfo) {
|
switch (hostInfo) {
|
||||||
case `coc-nvim`:
|
case `coc-nvim`: {
|
||||||
{
|
|
||||||
str = str.replace(/\.zip::/, `.zip/`);
|
str = str.replace(/\.zip::/, `.zip/`);
|
||||||
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
|
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
|
||||||
// So in order to convert it back, we use .* to match all the thing
|
// So in order to convert it back, we use .* to match all the thing
|
||||||
// before `zipfile:`
|
// before `zipfile:`
|
||||||
return process.platform === `win32` ? str.replace(/^.*zipfile:\//, ``) : str.replace(/^.*zipfile:/, ``);
|
return process.platform === `win32`
|
||||||
}
|
? str.replace(/^.*zipfile:\//, ``)
|
||||||
break;
|
: str.replace(/^.*zipfile:/, ``);
|
||||||
|
} break;
|
||||||
|
|
||||||
case `neovim`:
|
case `neovim`: {
|
||||||
{
|
|
||||||
str = str.replace(/\.zip::/, `.zip/`);
|
str = str.replace(/\.zip::/, `.zip/`);
|
||||||
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
|
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
|
||||||
return str.replace(/^zipfile:\/\//, ``);
|
return str.replace(/^zipfile:\/\//, ``);
|
||||||
}
|
} break;
|
||||||
break;
|
|
||||||
|
|
||||||
case `vscode`:
|
case `vscode`:
|
||||||
default:
|
default: {
|
||||||
{
|
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`)
|
||||||
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`);
|
} break;
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,8 +150,8 @@ const moduleWrapper = tsserver => {
|
|||||||
// TypeScript already does local loads and if this code is running the user trusts the workspace
|
// TypeScript already does local loads and if this code is running the user trusts the workspace
|
||||||
// https://github.com/microsoft/vscode/issues/45856
|
// https://github.com/microsoft/vscode/issues/45856
|
||||||
const ConfiguredProject = tsserver.server.ConfiguredProject;
|
const ConfiguredProject = tsserver.server.ConfiguredProject;
|
||||||
const { enablePluginsWithOptions: originalEnablePluginsWithOptions } = ConfiguredProject.prototype;
|
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
|
||||||
ConfiguredProject.prototype.enablePluginsWithOptions = function () {
|
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
|
||||||
this.projectService.allowLocalPluginLoads = true;
|
this.projectService.allowLocalPluginLoads = true;
|
||||||
return originalEnablePluginsWithOptions.apply(this, arguments);
|
return originalEnablePluginsWithOptions.apply(this, arguments);
|
||||||
};
|
};
|
||||||
@ -184,12 +161,12 @@ const moduleWrapper = tsserver => {
|
|||||||
// like an absolute path of ours and normalize it.
|
// like an absolute path of ours and normalize it.
|
||||||
|
|
||||||
const Session = tsserver.server.Session;
|
const Session = tsserver.server.Session;
|
||||||
const { onMessage: originalOnMessage, send: originalSend } = Session.prototype;
|
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
|
||||||
let hostInfo = `unknown`;
|
let hostInfo = `unknown`;
|
||||||
|
|
||||||
Object.assign(Session.prototype, {
|
Object.assign(Session.prototype, {
|
||||||
onMessage(/** @type {string | object} */ message) {
|
onMessage(/** @type {string | object} */ message) {
|
||||||
const isStringMessage = typeof message === "string";
|
const isStringMessage = typeof message === 'string';
|
||||||
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
|
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -200,12 +177,10 @@ const moduleWrapper = tsserver => {
|
|||||||
) {
|
) {
|
||||||
hostInfo = parsedMessage.arguments.hostInfo;
|
hostInfo = parsedMessage.arguments.hostInfo;
|
||||||
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
|
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
|
||||||
const [, major, minor] = (
|
const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match(
|
||||||
process.env.VSCODE_IPC_HOOK.match(
|
|
||||||
// The RegExp from https://semver.org/ but without the caret at the start
|
// The RegExp from https://semver.org/ but without the caret at the start
|
||||||
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
|
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
|
||||||
) ?? []
|
) ?? []).map(Number)
|
||||||
).map(Number);
|
|
||||||
|
|
||||||
if (major === 1) {
|
if (major === 1) {
|
||||||
if (minor < 61) {
|
if (minor < 61) {
|
||||||
@ -220,22 +195,20 @@ const moduleWrapper = tsserver => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
|
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
|
||||||
return typeof value === "string" ? fromEditorPath(value) : value;
|
return typeof value === 'string' ? fromEditorPath(value) : value;
|
||||||
});
|
});
|
||||||
|
|
||||||
return originalOnMessage.call(this, isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON));
|
return originalOnMessage.call(
|
||||||
|
this,
|
||||||
|
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
send(/** @type {any} */ msg) {
|
send(/** @type {any} */ msg) {
|
||||||
return originalSend.call(
|
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
|
||||||
this,
|
|
||||||
JSON.parse(
|
|
||||||
JSON.stringify(msg, (key, value) => {
|
|
||||||
return typeof value === `string` ? toEditorPath(value) : value;
|
return typeof value === `string` ? toEditorPath(value) : value;
|
||||||
})
|
})));
|
||||||
)
|
}
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return tsserver;
|
return tsserver;
|
||||||
|
12
.yarn/sdks/typescript/lib/typescript.js
vendored
12
.yarn/sdks/typescript/lib/typescript.js
vendored
@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
const { existsSync } = require(`fs`);
|
const {existsSync} = require(`fs`);
|
||||||
const { createRequire } = require(`module`);
|
const {createRequire} = require(`module`);
|
||||||
const { resolve } = require(`path`);
|
const {resolve} = require(`path`);
|
||||||
|
|
||||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||||
|
|
||||||
@ -11,10 +11,10 @@ const absRequire = createRequire(absPnpApiPath);
|
|||||||
|
|
||||||
if (existsSync(absPnpApiPath)) {
|
if (existsSync(absPnpApiPath)) {
|
||||||
if (!process.versions.pnp) {
|
if (!process.versions.pnp) {
|
||||||
// Setup the environment to be able to require typescript/lib/typescript.js
|
// Setup the environment to be able to require typescript
|
||||||
require(absPnpApiPath).setup();
|
require(absPnpApiPath).setup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defer to the real typescript/lib/typescript.js your application uses
|
// Defer to the real typescript your application uses
|
||||||
module.exports = absRequire(`typescript/lib/typescript.js`);
|
module.exports = absRequire(`typescript`);
|
||||||
|
6
.yarn/sdks/typescript/package.json
vendored
6
.yarn/sdks/typescript/package.json
vendored
@ -2,5 +2,9 @@
|
|||||||
"name": "typescript",
|
"name": "typescript",
|
||||||
"version": "5.2.2-sdk",
|
"version": "5.2.2-sdk",
|
||||||
"main": "./lib/typescript.js",
|
"main": "./lib/typescript.js",
|
||||||
"type": "commonjs"
|
"type": "commonjs",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "./bin/tsc",
|
||||||
|
"tsserver": "./bin/tsserver"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,5 +13,6 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
<script src="src/index.tsx" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
40
package.json
40
package.json
@ -14,14 +14,12 @@
|
|||||||
"@radix-ui/react-toggle": "^1.0.3",
|
"@radix-ui/react-toggle": "^1.0.3",
|
||||||
"@react-hook/resize-observer": "^1.2.6",
|
"@react-hook/resize-observer": "^1.2.6",
|
||||||
"@scure/base": "^1.1.1",
|
"@scure/base": "^1.1.1",
|
||||||
"@snort/shared": "^1.0.9",
|
"@snort/shared": "^1.0.10",
|
||||||
"@snort/system": "^1.1.4",
|
"@snort/system": "^1.1.5",
|
||||||
"@snort/system-react": "^1.1.4",
|
"@snort/system-react": "^1.1.5",
|
||||||
|
"@snort/system-wasm": "^1.0.1",
|
||||||
"@snort/system-web": "^1.0.2",
|
"@snort/system-web": "^1.0.2",
|
||||||
"@szhsin/react-menu": "^4.0.2",
|
"@szhsin/react-menu": "^4.0.2",
|
||||||
"@testing-library/jest-dom": "^5.14.1",
|
|
||||||
"@testing-library/react": "^13.0.0",
|
|
||||||
"@testing-library/user-event": "^13.2.1",
|
|
||||||
"@types/webscopeio__react-textarea-autocomplete": "^4.7.2",
|
"@types/webscopeio__react-textarea-autocomplete": "^4.7.2",
|
||||||
"@void-cat/api": "^1.0.7",
|
"@void-cat/api": "^1.0.7",
|
||||||
"@webscopeio/react-textarea-autocomplete": "^4.9.2",
|
"@webscopeio/react-textarea-autocomplete": "^4.9.2",
|
||||||
@ -32,7 +30,6 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lodash.uniqby": "^4.7.0",
|
"lodash.uniqby": "^4.7.0",
|
||||||
"marked": "^9.1.2",
|
"marked": "^9.1.2",
|
||||||
"moment": "^2.29.4",
|
|
||||||
"qr-code-styling": "^1.6.0-rc.1",
|
"qr-code-styling": "^1.6.0-rc.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-confetti": "^6.1.0",
|
"react-confetti": "^6.1.0",
|
||||||
@ -55,8 +52,8 @@
|
|||||||
"workbox-strategies": "^7.0.0"
|
"workbox-strategies": "^7.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "webpack serve --node-env=development --mode=development",
|
"start": "vite",
|
||||||
"build": "webpack --node-env=production --mode=production",
|
"build": "vite build",
|
||||||
"deploy": "__XXX='false' && yarn build && npx wrangler pages publish --project-name nostr-live build",
|
"deploy": "__XXX='false' && yarn build && npx wrangler pages publish --project-name nostr-live build",
|
||||||
"deploy:xxzap": "__XXX='true' && yarn build && npx wrangler pages publish --project-name xxzap build",
|
"deploy:xxzap": "__XXX='true' && yarn build && npx wrangler pages publish --project-name xxzap build",
|
||||||
"intl-extract": "formatjs extract 'src/**/*.ts*' --ignore='**/*.d.ts' --out-file src/lang.json --flatten true",
|
"intl-extract": "formatjs extract 'src/**/*.ts*' --ignore='**/*.d.ts' --out-file src/lang.json --flatten true",
|
||||||
@ -83,40 +80,27 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.22.11",
|
|
||||||
"@babel/plugin-syntax-import-assertions": "^7.20.0",
|
|
||||||
"@babel/preset-env": "^7.21.5",
|
|
||||||
"@babel/preset-react": "^7.18.6",
|
|
||||||
"@formatjs/cli": "^6.1.3",
|
"@formatjs/cli": "^6.1.3",
|
||||||
"@formatjs/ts-transformer": "^3.13.3",
|
"@formatjs/ts-transformer": "^3.13.3",
|
||||||
"@testing-library/dom": "^9.3.1",
|
"@testing-library/dom": "^9.3.1",
|
||||||
"@types/lodash": "^4.14.195",
|
"@types/lodash": "^4.14.195",
|
||||||
"@types/lodash.uniqby": "^4.7.7",
|
"@types/lodash.uniqby": "^4.7.7",
|
||||||
|
"@types/node": "^20.10.3",
|
||||||
"@types/react": "^18.2.21",
|
"@types/react": "^18.2.21",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@types/react-helmet": "^6.1.6",
|
"@types/react-helmet": "^6.1.6",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.4.1",
|
"@typescript-eslint/eslint-plugin": "^6.4.1",
|
||||||
"@typescript-eslint/parser": "^6.4.1",
|
"@typescript-eslint/parser": "^6.4.1",
|
||||||
|
"@vitejs/plugin-react": "^4.2.0",
|
||||||
"@webbtc/webln-types": "^1.0.12",
|
"@webbtc/webln-types": "^1.0.12",
|
||||||
"babel-loader": "^9.1.3",
|
|
||||||
"copy-webpack-plugin": "^11.0.0",
|
|
||||||
"css-loader": "^6.8.1",
|
|
||||||
"css-minimizer-webpack-plugin": "^5.0.0",
|
|
||||||
"eslint": "^8.48.0",
|
"eslint": "^8.48.0",
|
||||||
"eslint-webpack-plugin": "^4.0.1",
|
"eslint-plugin-formatjs": "^4.11.3",
|
||||||
"html-webpack-plugin": "^5.5.1",
|
|
||||||
"mini-css-extract-plugin": "^2.7.5",
|
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"source-map-loader": "^4.0.1",
|
|
||||||
"terser-webpack-plugin": "^5.3.9",
|
|
||||||
"ts-loader": "^9.4.4",
|
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"webpack": "^5.88.2",
|
"vite": "^5.0.5",
|
||||||
"webpack-bundle-analyzer": "^4.8.0",
|
"vite-plugin-pwa": "^0.17.2",
|
||||||
"webpack-cli": "^5.1.4",
|
"vite-plugin-version-mark": "^0.0.10"
|
||||||
"webpack-dev-server": "^4.15.1",
|
|
||||||
"workbox-webpack-plugin": "^7.0.0"
|
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@3.6.3",
|
"packageManager": "yarn@3.6.3",
|
||||||
"prettier": {
|
"prettier": {
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB |
@ -1,3 +0,0 @@
|
|||||||
# https://www.robotstxt.org/robotstxt.html
|
|
||||||
User-agent: *
|
|
||||||
Disallow:
|
|
@ -1 +0,0 @@
|
|||||||
[{ "id": "nsfw", "text": "NSFW" }]
|
|
@ -1,14 +1,14 @@
|
|||||||
import "./event.css";
|
import "./event.css";
|
||||||
|
|
||||||
import { type NostrLink, type NostrEvent as NostrEventType, EventKind } from "@snort/system";
|
import { EventKind, type NostrEvent as NostrEventType, type NostrLink } from "@snort/system";
|
||||||
|
|
||||||
import { Icon } from "element/icon";
|
import { Icon } from "./icon";
|
||||||
import { Goal } from "element/goal";
|
import { Goal } from "./goal";
|
||||||
import { Note } from "element/note";
|
import { Note } from "./note";
|
||||||
import { EmojiPack } from "element/emoji-pack";
|
import { EmojiPack } from "./emoji-pack";
|
||||||
import { Badge } from "element/badge";
|
import { Badge } from "./badge";
|
||||||
import { useEvent } from "hooks/event";
|
import { useEvent } from "@/hooks/event";
|
||||||
import { GOAL, EMOJI_PACK } from "const";
|
import { EMOJI_PACK, GOAL } from "@/const";
|
||||||
|
|
||||||
interface EventProps {
|
interface EventProps {
|
||||||
link: NostrLink;
|
link: NostrLink;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import "./async-button.css";
|
import "./async-button.css";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import Spinner from "element/spinner";
|
import Spinner from "./spinner";
|
||||||
|
|
||||||
interface AsyncButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
interface AsyncButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import "./badge.css";
|
import "./badge.css";
|
||||||
import type { NostrEvent } from "@snort/system";
|
import type { NostrEvent } from "@snort/system";
|
||||||
import { findTag } from "utils";
|
import { findTag } from "@/utils";
|
||||||
|
|
||||||
export function Badge({ ev }: { ev: NostrEvent }) {
|
export function Badge({ ev }: { ev: NostrEvent }) {
|
||||||
const name = findTag(ev, "name") || findTag(ev, "d");
|
const name = findTag(ev, "name") || findTag(ev, "d");
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
import { useUserProfile, SnortContext, useEventReactions } from "@snort/system-react";
|
import { SnortContext, useEventReactions, useUserProfile } from "@snort/system-react";
|
||||||
import { EventKind, NostrLink, TaggedNostrEvent } from "@snort/system";
|
import { EventKind, NostrLink, TaggedNostrEvent } from "@snort/system";
|
||||||
import React, { useRef, useState, useMemo, useContext } from "react";
|
import React, { useContext, useMemo, useRef, useState } from "react";
|
||||||
import { useMediaQuery, useHover, useOnClickOutside, useIntersectionObserver } from "usehooks-ts";
|
import { useHover, useIntersectionObserver, useMediaQuery, useOnClickOutside } from "usehooks-ts";
|
||||||
import { dedupe } from "@snort/shared";
|
import { dedupe } from "@snort/shared";
|
||||||
|
|
||||||
import { EmojiPicker } from "element/emoji-picker";
|
import { EmojiPicker } from "./emoji-picker";
|
||||||
import { Icon } from "element/icon";
|
import { Icon } from "./icon";
|
||||||
import { Emoji as EmojiComponent } from "element/emoji";
|
import { Emoji as EmojiComponent } from "./emoji";
|
||||||
import { Profile } from "./profile";
|
import { Profile } from "./profile";
|
||||||
import { Text } from "element/text";
|
import { Text } from "./text";
|
||||||
import { useMute } from "element/mute-button";
|
import { useMute } from "./mute-button";
|
||||||
import { SendZapsDialog } from "element/send-zap";
|
import { SendZapsDialog } from "./send-zap";
|
||||||
import { CollapsibleEvent } from "element/collapsible";
|
import { CollapsibleEvent } from "./collapsible";
|
||||||
import { useLogin } from "hooks/login";
|
|
||||||
import { formatSats } from "number";
|
import { useLogin } from "@/hooks/login";
|
||||||
import type { Badge, Emoji, EmojiPack } from "types";
|
import { formatSats } from "@/number";
|
||||||
|
import type { Badge, Emoji, EmojiPack } from "@/types";
|
||||||
|
|
||||||
function emojifyReaction(reaction: string) {
|
function emojifyReaction(reaction: string) {
|
||||||
if (reaction === "+") {
|
if (reaction === "+") {
|
||||||
|
@ -8,10 +8,10 @@ import * as Collapsible from "@radix-ui/react-collapsible";
|
|||||||
|
|
||||||
import type { NostrLink } from "@snort/system";
|
import type { NostrLink } from "@snort/system";
|
||||||
|
|
||||||
import { Mention } from "element/mention";
|
import { Mention } from "./mention";
|
||||||
import { NostrEvent, EventIcon } from "element/Event";
|
import { EventIcon, NostrEvent } from "./Event";
|
||||||
import { ExternalLink } from "element/external-link";
|
import { ExternalLink } from "./external-link";
|
||||||
import { useEvent } from "hooks/event";
|
import { useEvent } from "@/hooks/event";
|
||||||
|
|
||||||
interface MediaURLProps {
|
interface MediaURLProps {
|
||||||
url: URL;
|
url: URL;
|
||||||
@ -32,7 +32,7 @@ export function MediaURL({ url, children }: MediaURLProps) {
|
|||||||
</div>
|
</div>
|
||||||
<Dialog.Close asChild>
|
<Dialog.Close asChild>
|
||||||
<button className="btn delete-button" aria-label="Close">
|
<button className="btn delete-button" aria-label="Close">
|
||||||
<FormattedMessage defaultMessage="Close" />
|
<FormattedMessage defaultMessage="Close" id="rbrahO" />
|
||||||
</button>
|
</button>
|
||||||
</Dialog.Close>
|
</Dialog.Close>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
@ -55,7 +55,11 @@ export function CollapsibleEvent({ link }: { link: NostrLink }) {
|
|||||||
</div>
|
</div>
|
||||||
<Collapsible.Trigger asChild>
|
<Collapsible.Trigger asChild>
|
||||||
<button className={`${open ? "btn btn-small delete-button" : "btn btn-small"}`}>
|
<button className={`${open ? "btn btn-small delete-button" : "btn btn-small"}`}>
|
||||||
{open ? <FormattedMessage defaultMessage="Hide" /> : <FormattedMessage defaultMessage="Show" />}
|
{open ? (
|
||||||
|
<FormattedMessage defaultMessage="Hide" id="VA/Z1S" />
|
||||||
|
) : (
|
||||||
|
<FormattedMessage defaultMessage="Show" id="K7AkdL" />
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</Collapsible.Trigger>
|
</Collapsible.Trigger>
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,17 +19,17 @@ export function ContentWarningOverlay() {
|
|||||||
return (
|
return (
|
||||||
<div className="fullscreen-exclusive age-check">
|
<div className="fullscreen-exclusive age-check">
|
||||||
<h1>
|
<h1>
|
||||||
<FormattedMessage defaultMessage="Sexually explicit material ahead!" />
|
<FormattedMessage defaultMessage="Sexually explicit material ahead!" id="rWBFZA" />
|
||||||
</h1>
|
</h1>
|
||||||
<h2>
|
<h2>
|
||||||
<FormattedMessage defaultMessage="Confirm your age" />
|
<FormattedMessage defaultMessage="Confirm your age" id="s7V+5p" />
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex g24">
|
<div className="flex g24">
|
||||||
<button className="btn btn-warning" onClick={grownUp}>
|
<button className="btn btn-warning" onClick={grownUp}>
|
||||||
<FormattedMessage defaultMessage="Yes, I am over 18" />
|
<FormattedMessage defaultMessage="Yes, I am over 18" id="O2Cy6m" />
|
||||||
</button>
|
</button>
|
||||||
<button className="btn" onClick={() => navigate("/")}>
|
<button className="btn" onClick={() => navigate("/")}>
|
||||||
<FormattedMessage defaultMessage="No, I am under 18" />
|
<FormattedMessage defaultMessage="No, I am under 18" id="KkIL3s" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import "./copy.css";
|
import "./copy.css";
|
||||||
import { useCopy } from "hooks/copy";
|
import { useCopy } from "@/hooks/copy";
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "./icon";
|
||||||
|
|
||||||
export interface CopyProps {
|
export interface CopyProps {
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import "./emoji-pack.css";
|
import "./emoji-pack.css";
|
||||||
import { type NostrEvent } from "@snort/system";
|
import { type NostrEvent } from "@snort/system";
|
||||||
|
|
||||||
import { useLogin } from "hooks/login";
|
|
||||||
import { toEmojiPack } from "hooks/emoji";
|
|
||||||
import AsyncButton from "element/async-button";
|
|
||||||
import { findTag } from "utils";
|
|
||||||
import { USER_EMOJIS } from "const";
|
|
||||||
import { Login } from "index";
|
|
||||||
import type { EmojiPack as EmojiPackType } from "types";
|
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { SnortContext } from "@snort/system-react";
|
import { SnortContext } from "@snort/system-react";
|
||||||
|
|
||||||
|
import { useLogin } from "@/hooks/login";
|
||||||
|
import { toEmojiPack } from "@/hooks/emoji";
|
||||||
|
import AsyncButton from "./async-button";
|
||||||
|
import { findTag } from "@/utils";
|
||||||
|
import { USER_EMOJIS } from "@/const";
|
||||||
|
import { Login } from "@/index";
|
||||||
|
import type { EmojiPack as EmojiPackType } from "@/types";
|
||||||
|
|
||||||
export function EmojiPack({ ev }: { ev: NostrEvent }) {
|
export function EmojiPack({ ev }: { ev: NostrEvent }) {
|
||||||
const system = useContext(SnortContext);
|
const system = useContext(SnortContext);
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
@ -49,7 +49,11 @@ export function EmojiPack({ ev }: { ev: NostrEvent }) {
|
|||||||
<AsyncButton
|
<AsyncButton
|
||||||
className={`btn btn-small btn-primary ${isUsed ? "delete-button" : ""}`}
|
className={`btn btn-small btn-primary ${isUsed ? "delete-button" : ""}`}
|
||||||
onClick={toggleEmojiPack}>
|
onClick={toggleEmojiPack}>
|
||||||
{isUsed ? <FormattedMessage defaultMessage="Remove" /> : <FormattedMessage defaultMessage="Add" />}
|
{isUsed ? (
|
||||||
|
<FormattedMessage defaultMessage="Remove" id="G/yZLu" />
|
||||||
|
) : (
|
||||||
|
<FormattedMessage defaultMessage="Add" id="2/2yg+" />
|
||||||
|
)}
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import data, { Emoji } from "@emoji-mart/data";
|
import data, { Emoji } from "@emoji-mart/data";
|
||||||
import Picker from "@emoji-mart/react";
|
import Picker from "@emoji-mart/react";
|
||||||
import { RefObject } from "react";
|
import { RefObject } from "react";
|
||||||
import { EmojiPack } from "types";
|
import { EmojiPack } from "@/types";
|
||||||
|
|
||||||
interface EmojiPickerProps {
|
interface EmojiPickerProps {
|
||||||
topOffset: number;
|
topOffset: number;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import "./emoji.css";
|
import "./emoji.css";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { EmojiTag } from "types";
|
import { EmojiTag } from "@/types";
|
||||||
|
|
||||||
export type EmojiProps = {
|
export type EmojiProps = {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { Icon } from "element/icon";
|
import { Icon } from "./icon";
|
||||||
|
|
||||||
interface ExternalLinkProps {
|
interface ExternalLinkProps {
|
||||||
href: string;
|
href: string;
|
||||||
|
@ -79,15 +79,15 @@ export function FileUploader({ defaultImage, onClear, onFileUpload }: FileUpload
|
|||||||
<label className="file-uploader">
|
<label className="file-uploader">
|
||||||
<input type="file" onChange={onFileChange} />
|
<input type="file" onChange={onFileChange} />
|
||||||
{isUploading ? (
|
{isUploading ? (
|
||||||
<FormattedMessage defaultMessage="Uploading..." />
|
<FormattedMessage defaultMessage="Uploading..." id="JEsxDw" />
|
||||||
) : (
|
) : (
|
||||||
<FormattedMessage defaultMessage="Add File" />
|
<FormattedMessage defaultMessage="Add File" id="fc2iho" />
|
||||||
)}
|
)}
|
||||||
</label>
|
</label>
|
||||||
<div className="file-uploader-preview">
|
<div className="file-uploader-preview">
|
||||||
{img?.length > 0 && (
|
{img?.length > 0 && (
|
||||||
<button className="btn btn-primary clear-button" onClick={clearImage}>
|
<button className="btn btn-primary clear-button" onClick={clearImage}>
|
||||||
<FormattedMessage defaultMessage="Clear" />
|
<FormattedMessage defaultMessage="Clear" id="/GCoTA" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{img && <img className="image-preview" src={img} />}
|
{img && <img className="image-preview" src={img} />}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { EventKind } from "@snort/system";
|
import { EventKind } from "@snort/system";
|
||||||
|
|
||||||
import { useLogin } from "hooks/login";
|
|
||||||
import AsyncButton from "element/async-button";
|
|
||||||
import { Login } from "index";
|
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { SnortContext } from "@snort/system-react";
|
import { SnortContext } from "@snort/system-react";
|
||||||
|
|
||||||
|
import { useLogin } from "@/hooks/login";
|
||||||
|
import AsyncButton from "./async-button";
|
||||||
|
import { Login } from "@/index";
|
||||||
|
|
||||||
export function LoggedInFollowButton({ tag, value }: { tag: "p" | "t"; value: string }) {
|
export function LoggedInFollowButton({ tag, value }: { tag: "p" | "t"; value: string }) {
|
||||||
const system = useContext(SnortContext);
|
const system = useContext(SnortContext);
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
@ -56,7 +56,11 @@ export function LoggedInFollowButton({ tag, value }: { tag: "p" | "t"; value: st
|
|||||||
type="button"
|
type="button"
|
||||||
className="btn btn-primary"
|
className="btn btn-primary"
|
||||||
onClick={isFollowing ? unfollow : follow}>
|
onClick={isFollowing ? unfollow : follow}>
|
||||||
{isFollowing ? <FormattedMessage defaultMessage="Unfollow" /> : <FormattedMessage defaultMessage="Follow" />}
|
{isFollowing ? (
|
||||||
|
<FormattedMessage defaultMessage="Unfollow" id="izWS4J" />
|
||||||
|
) : (
|
||||||
|
<FormattedMessage defaultMessage="Follow" id="ieGrWo" />
|
||||||
|
)}
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,18 @@ import "./goal.css";
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import * as Progress from "@radix-ui/react-progress";
|
import * as Progress from "@radix-ui/react-progress";
|
||||||
import Confetti from "react-confetti";
|
import Confetti from "react-confetti";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import { NostrLink, type NostrEvent } from "@snort/system";
|
import { type NostrEvent, NostrLink } from "@snort/system";
|
||||||
import { useUserProfile } from "@snort/system-react";
|
import { useUserProfile } from "@snort/system-react";
|
||||||
|
|
||||||
import { findTag } from "utils";
|
import { findTag } from "@/utils";
|
||||||
import { formatSats } from "number";
|
import { formatSats } from "@/number";
|
||||||
import usePreviousValue from "hooks/usePreviousValue";
|
import usePreviousValue from "@/hooks/usePreviousValue";
|
||||||
import { SendZapsDialog } from "element/send-zap";
|
import { SendZapsDialog } from "./send-zap";
|
||||||
import { getName } from "element/profile";
|
import { getName } from "./profile";
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "./icon";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { useZaps } from "@/hooks/zaps";
|
||||||
import { useZaps } from "hooks/zaps";
|
|
||||||
|
|
||||||
export function Goal({ ev }: { ev: NostrEvent }) {
|
export function Goal({ ev }: { ev: NostrEvent }) {
|
||||||
const profile = useUserProfile(ev.pubkey);
|
const profile = useUserProfile(ev.pubkey);
|
||||||
@ -48,7 +48,7 @@ export function Goal({ ev }: { ev: NostrEvent }) {
|
|||||||
{!isFinished && <span className="amount so-far">{formatSats(soFar)}</span>}
|
{!isFinished && <span className="amount so-far">{formatSats(soFar)}</span>}
|
||||||
</Progress.Indicator>
|
</Progress.Indicator>
|
||||||
<span className="amount target">
|
<span className="amount target">
|
||||||
<FormattedMessage defaultMessage="Goal: {amount}" values={{ amount: formatSats(goalAmount) }} />
|
<FormattedMessage defaultMessage="Goal: {amount}" id="QceMQZ" values={{ amount: formatSats(goalAmount) }} />
|
||||||
</span>
|
</span>
|
||||||
</Progress.Root>
|
</Progress.Root>
|
||||||
<div className="zap-circle">
|
<div className="zap-circle">
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { NostrLink } from "element/nostr-link";
|
import { NostrLink } from "./nostr-link";
|
||||||
import { MediaURL } from "element/collapsible";
|
import { MediaURL } from "./collapsible";
|
||||||
|
|
||||||
const FileExtensionRegex = /\.([\w]+)$/i;
|
const FileExtensionRegex = /\.([\w]+)$/i;
|
||||||
|
|
||||||
|
@ -1,29 +1,29 @@
|
|||||||
import "./live-chat.css";
|
import "./live-chat.css";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { EventKind, NostrLink, ParsedZap, NostrEvent } from "@snort/system";
|
import { EventKind, NostrEvent, NostrLink, ParsedZap } from "@snort/system";
|
||||||
import { useEventReactions } from "@snort/system-react";
|
import { useEventReactions } from "@snort/system-react";
|
||||||
import { unixNow } from "@snort/shared";
|
import { unixNow } from "@snort/shared";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import uniqBy from "lodash.uniqby";
|
import uniqBy from "lodash.uniqby";
|
||||||
|
|
||||||
import { Icon } from "element/icon";
|
import { Icon } from "./icon";
|
||||||
import Spinner from "element/spinner";
|
import Spinner from "./spinner";
|
||||||
import { Text } from "element/text";
|
import { Text } from "./text";
|
||||||
import { Profile } from "element/profile";
|
import { Profile } from "./profile";
|
||||||
import { ChatMessage } from "element/chat-message";
|
import { ChatMessage } from "./chat-message";
|
||||||
import { Goal } from "element/goal";
|
import { Goal } from "./goal";
|
||||||
import { Badge } from "element/badge";
|
import { Badge } from "./badge";
|
||||||
import { WriteMessage } from "element/write-message";
|
import { WriteMessage } from "./write-message";
|
||||||
import useEmoji, { packId } from "hooks/emoji";
|
import useEmoji, { packId } from "@/hooks/emoji";
|
||||||
import { useLiveChatFeed } from "hooks/live-chat";
|
import { useLiveChatFeed } from "@/hooks/live-chat";
|
||||||
import { useMutedPubkeys } from "hooks/lists";
|
import { useMutedPubkeys } from "@/hooks/lists";
|
||||||
import { useBadges } from "hooks/badges";
|
import { useBadges } from "@/hooks/badges";
|
||||||
import { useLogin } from "hooks/login";
|
import { useLogin } from "@/hooks/login";
|
||||||
import { useAddress } from "hooks/event";
|
import { useAddress } from "@/hooks/event";
|
||||||
import { formatSats } from "number";
|
import { formatSats } from "@/number";
|
||||||
import { WEEK, LIVE_STREAM_CHAT } from "const";
|
import { LIVE_STREAM_CHAT, WEEK } from "@/const";
|
||||||
import { findTag, getTagValues, getHost } from "utils";
|
import { findTag, getHost, getTagValues } from "@/utils";
|
||||||
import { TopZappers } from "element/top-zappers";
|
import { TopZappers } from "./top-zappers";
|
||||||
|
|
||||||
export interface LiveChatOptions {
|
export interface LiveChatOptions {
|
||||||
canWrite?: boolean;
|
canWrite?: boolean;
|
||||||
@ -95,7 +95,7 @@ export function LiveChat({
|
|||||||
{(options?.showHeader ?? true) && (
|
{(options?.showHeader ?? true) && (
|
||||||
<div className="header">
|
<div className="header">
|
||||||
<h2 className="title">
|
<h2 className="title">
|
||||||
<FormattedMessage defaultMessage="Stream Chat" />
|
<FormattedMessage defaultMessage="Stream Chat" id="BGxpTN" />
|
||||||
</h2>
|
</h2>
|
||||||
<Icon
|
<Icon
|
||||||
name="link"
|
name="link"
|
||||||
@ -108,7 +108,7 @@ export function LiveChat({
|
|||||||
{reactions.zaps.length > 0 && (
|
{reactions.zaps.length > 0 && (
|
||||||
<div className="top-zappers">
|
<div className="top-zappers">
|
||||||
<h3>
|
<h3>
|
||||||
<FormattedMessage defaultMessage="Top zappers" />
|
<FormattedMessage defaultMessage="Top zappers" id="wzWWzV" />
|
||||||
</h3>
|
</h3>
|
||||||
<div className="top-zappers-container">
|
<div className="top-zappers-container">
|
||||||
<TopZappers zaps={reactions.zaps} />
|
<TopZappers zaps={reactions.zaps} />
|
||||||
@ -151,7 +151,7 @@ export function LiveChat({
|
|||||||
<WriteMessage emojiPacks={allEmojiPacks} link={link} />
|
<WriteMessage emojiPacks={allEmojiPacks} link={link} />
|
||||||
) : (
|
) : (
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Please login to write messages!" />
|
<FormattedMessage defaultMessage="Please login to write messages!" id="RXQdxR" />
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -174,6 +174,7 @@ function ChatZap({ zap }: { zap: ParsedZap }) {
|
|||||||
<Icon name="zap-filled" className="zap-icon" />
|
<Icon name="zap-filled" className="zap-icon" />
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="{person} zapped {amount} sats"
|
defaultMessage="{person} zapped {amount} sats"
|
||||||
|
id="AIHaPH"
|
||||||
values={{
|
values={{
|
||||||
person: (
|
person: (
|
||||||
<Profile
|
<Profile
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import Hls from "hls.js";
|
import Hls from "hls.js";
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { WISH } from "wish";
|
import { WISH } from "@/wish";
|
||||||
|
|
||||||
export enum VideoStatus {
|
export enum VideoStatus {
|
||||||
Online = "online",
|
Online = "online",
|
||||||
|
@ -20,13 +20,13 @@ import { VoidApi } from "@void-cat/api";
|
|||||||
import { SnortContext } from "@snort/system-react";
|
import { SnortContext } from "@snort/system-react";
|
||||||
|
|
||||||
import AsyncButton from "./async-button";
|
import AsyncButton from "./async-button";
|
||||||
import { Login } from "index";
|
import { Login } from "@/index";
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "./icon";
|
||||||
import Copy from "./copy";
|
import Copy from "./copy";
|
||||||
import { openFile } from "utils";
|
import { openFile } from "@/utils";
|
||||||
import { LoginType } from "login";
|
import { LoginType } from "@/login";
|
||||||
import { DefaultProvider, StreamProviderInfo } from "providers";
|
import { DefaultProvider, StreamProviderInfo } from "@/providers";
|
||||||
import { Nip103StreamProvider } from "providers/zsz";
|
import { Nip103StreamProvider } from "@/providers/zsz";
|
||||||
|
|
||||||
enum Stage {
|
enum Stage {
|
||||||
Login = 0,
|
Login = 0,
|
||||||
@ -126,6 +126,7 @@ export function LoginSignup({ close }: { close: () => void }) {
|
|||||||
throw new Error(
|
throw new Error(
|
||||||
formatMessage({
|
formatMessage({
|
||||||
defaultMessage: "Hmm, your lightning address looks wrong",
|
defaultMessage: "Hmm, your lightning address looks wrong",
|
||||||
|
id: "4l69eO",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -157,29 +158,29 @@ export function LoginSignup({ close }: { close: () => void }) {
|
|||||||
<img src={LoginHeader as string} srcSet={`${LoginHeader2x} 2x`} className="header-image" />
|
<img src={LoginHeader as string} srcSet={`${LoginHeader2x} 2x`} className="header-image" />
|
||||||
<div className="content-inner">
|
<div className="content-inner">
|
||||||
<h2>
|
<h2>
|
||||||
<FormattedMessage defaultMessage="Create an Account" />
|
<FormattedMessage defaultMessage="Create an Account" id="u6uD94" />
|
||||||
</h2>
|
</h2>
|
||||||
<h3>
|
<h3>
|
||||||
<FormattedMessage defaultMessage="No emails, just awesomeness!" />
|
<FormattedMessage defaultMessage="No emails, just awesomeness!" id="+AcVD+" />
|
||||||
</h3>
|
</h3>
|
||||||
<button type="button" className="btn btn-primary btn-block" onClick={createAccount}>
|
<button type="button" className="btn btn-primary btn-block" onClick={createAccount}>
|
||||||
<FormattedMessage defaultMessage="Create Account" />
|
<FormattedMessage defaultMessage="Create Account" id="5JcXdV" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="or-divider">
|
<div className="or-divider">
|
||||||
<hr />
|
<hr />
|
||||||
<FormattedMessage defaultMessage="OR" />
|
<FormattedMessage defaultMessage="OR" id="INlWvJ" />
|
||||||
<hr />
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
{hasNostrExtension && (
|
{hasNostrExtension && (
|
||||||
<>
|
<>
|
||||||
<AsyncButton type="button" className="btn btn-primary btn-block" onClick={loginNip7}>
|
<AsyncButton type="button" className="btn btn-primary btn-block" onClick={loginNip7}>
|
||||||
<FormattedMessage defaultMessage="Nostr Extension" />
|
<FormattedMessage defaultMessage="Nostr Extension" id="ebmhes" />
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<button type="button" className="btn btn-primary btn-block" onClick={() => setStage(Stage.LoginInput)}>
|
<button type="button" className="btn btn-primary btn-block" onClick={() => setStage(Stage.LoginInput)}>
|
||||||
<FormattedMessage defaultMessage="Login with Private Key (insecure)" />
|
<FormattedMessage defaultMessage="Login with Private Key (insecure)" id="feZ/kG" />
|
||||||
</button>
|
</button>
|
||||||
{error && <b className="error">{error}</b>}
|
{error && <b className="error">{error}</b>}
|
||||||
</div>
|
</div>
|
||||||
@ -192,15 +193,16 @@ export function LoginSignup({ close }: { close: () => void }) {
|
|||||||
<img src={LoginVault as string} srcSet={`${LoginVault2x} 2x`} className="header-image" />
|
<img src={LoginVault as string} srcSet={`${LoginVault2x} 2x`} className="header-image" />
|
||||||
<div className="content-inner">
|
<div className="content-inner">
|
||||||
<h2>
|
<h2>
|
||||||
<FormattedMessage defaultMessage="Login with private key" />
|
<FormattedMessage defaultMessage="Login with private key" id="3df560" />
|
||||||
</h2>
|
</h2>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="This method is insecure. We recommend using a {nostrlink}"
|
defaultMessage="This method is insecure. We recommend using a {nostrlink}"
|
||||||
|
id="Z8ZOEY"
|
||||||
values={{
|
values={{
|
||||||
nostrlink: (
|
nostrlink: (
|
||||||
<a href="">
|
<a href="">
|
||||||
<FormattedMessage defaultMessage="nostr signer extension" />
|
<FormattedMessage defaultMessage="nostr signer extension" id="/EvlqN" />
|
||||||
</a>
|
</a>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
@ -211,7 +213,7 @@ export function LoginSignup({ close }: { close: () => void }) {
|
|||||||
type="text"
|
type="text"
|
||||||
value={key}
|
value={key}
|
||||||
onChange={e => setNewKey(e.target.value)}
|
onChange={e => setNewKey(e.target.value)}
|
||||||
placeholder={formatMessage({ defaultMessage: "eg. nsec1xyz" })}
|
placeholder={formatMessage({ defaultMessage: "eg. nsec1xyz", id: "yzKwBQ" })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex f-space">
|
<div className="flex f-space">
|
||||||
@ -224,10 +226,10 @@ export function LoginSignup({ close }: { close: () => void }) {
|
|||||||
setNewKey("");
|
setNewKey("");
|
||||||
setStage(Stage.Login);
|
setStage(Stage.Login);
|
||||||
}}>
|
}}>
|
||||||
<FormattedMessage defaultMessage="Cancel" />
|
<FormattedMessage defaultMessage="Cancel" id="47FYwb" />
|
||||||
</button>
|
</button>
|
||||||
<AsyncButton onClick={doLoginNsec} className="btn btn-primary">
|
<AsyncButton onClick={doLoginNsec} className="btn btn-primary">
|
||||||
<FormattedMessage defaultMessage="Log In" />
|
<FormattedMessage defaultMessage="Log In" id="r2Jjms" />
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -242,7 +244,7 @@ export function LoginSignup({ close }: { close: () => void }) {
|
|||||||
<img src={LoginProfile as string} srcSet={`${LoginProfile2x} 2x`} className="header-image" />
|
<img src={LoginProfile as string} srcSet={`${LoginProfile2x} 2x`} className="header-image" />
|
||||||
<div className="content-inner">
|
<div className="content-inner">
|
||||||
<h2>
|
<h2>
|
||||||
<FormattedMessage defaultMessage="Setup Profile" />
|
<FormattedMessage defaultMessage="Setup Profile" id="nOaArs" />
|
||||||
</h2>
|
</h2>
|
||||||
<div className="flex f-center">
|
<div className="flex f-center">
|
||||||
<div
|
<div
|
||||||
@ -266,11 +268,11 @@ export function LoginSignup({ close }: { close: () => void }) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<small>
|
<small>
|
||||||
<FormattedMessage defaultMessage="You can change this later" />
|
<FormattedMessage defaultMessage="You can change this later" id="ZmqxZs" />
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<AsyncButton type="button" className="btn btn-primary" onClick={setupProfile}>
|
<AsyncButton type="button" className="btn btn-primary" onClick={setupProfile}>
|
||||||
<FormattedMessage defaultMessage="Save" />
|
<FormattedMessage defaultMessage="Save" id="jvo0vs" />
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@ -282,15 +284,19 @@ export function LoginSignup({ close }: { close: () => void }) {
|
|||||||
<img src={LoginWallet as string} srcSet={`${LoginWallet2x} 2x`} className="header-image" />
|
<img src={LoginWallet as string} srcSet={`${LoginWallet2x} 2x`} className="header-image" />
|
||||||
<div className="content-inner">
|
<div className="content-inner">
|
||||||
<h2>
|
<h2>
|
||||||
<FormattedMessage defaultMessage="Get paid by viewers" />
|
<FormattedMessage defaultMessage="Get paid by viewers" id="Fodi9+" />
|
||||||
</h2>
|
</h2>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="We hooked you up with a lightning wallet so you can get paid by viewers right away!" />
|
<FormattedMessage
|
||||||
|
defaultMessage="We hooked you up with a lightning wallet so you can get paid by viewers right away!"
|
||||||
|
id="Oxqtyf"
|
||||||
|
/>
|
||||||
</p>
|
</p>
|
||||||
{providerInfo?.balance && (
|
{providerInfo?.balance && (
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Oh, and you have {n} sats of free streaming on us! 💜"
|
defaultMessage="Oh, and you have {n} sats of free streaming on us! 💜"
|
||||||
|
id="f6biFA"
|
||||||
values={{
|
values={{
|
||||||
n: <FormattedNumber value={providerInfo.balance} />,
|
n: <FormattedNumber value={providerInfo.balance} />,
|
||||||
}}
|
}}
|
||||||
@ -301,18 +307,18 @@ export function LoginSignup({ close }: { close: () => void }) {
|
|||||||
<div className="paper">
|
<div className="paper">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={formatMessage({ defaultMessage: "eg. name@wallet.com" })}
|
placeholder={formatMessage({ defaultMessage: "eg. name@wallet.com", id: "1qsXCO" })}
|
||||||
value={lnAddress}
|
value={lnAddress}
|
||||||
onChange={e => setLnAddress(e.target.value)}
|
onChange={e => setLnAddress(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<small>
|
<small>
|
||||||
<FormattedMessage defaultMessage="You can always replace it with your own address later." />
|
<FormattedMessage defaultMessage="You can always replace it with your own address later." id="FjDlus" />
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
{error && <b className="error">{error}</b>}
|
{error && <b className="error">{error}</b>}
|
||||||
<AsyncButton type="button" className="btn btn-primary" onClick={saveProfile}>
|
<AsyncButton type="button" className="btn btn-primary" onClick={saveProfile}>
|
||||||
<FormattedMessage defaultMessage="Amazing! Continue.." />
|
<FormattedMessage defaultMessage="Amazing! Continue.." id="tM6fNW" />
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@ -324,16 +330,19 @@ export function LoginSignup({ close }: { close: () => void }) {
|
|||||||
<img src={LoginKey as string} srcSet={`${LoginKey2x} 2x`} className="header-image" />
|
<img src={LoginKey as string} srcSet={`${LoginKey2x} 2x`} className="header-image" />
|
||||||
<div className="content-inner">
|
<div className="content-inner">
|
||||||
<h2>
|
<h2>
|
||||||
<FormattedMessage defaultMessage="Save Key" />
|
<FormattedMessage defaultMessage="Save Key" id="04lmFi" />
|
||||||
</h2>
|
</h2>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Save this and keep it safe! If you lose this key, you won't be able to access your account ever again. Yep, it's that serious!" />
|
<FormattedMessage
|
||||||
|
defaultMessage="Save this and keep it safe! If you lose this key, you won't be able to access your account ever again. Yep, it's that serious!"
|
||||||
|
id="H/bNs9"
|
||||||
|
/>
|
||||||
</p>
|
</p>
|
||||||
<div className="paper">
|
<div className="paper">
|
||||||
<Copy text={hexToBech32("nsec", key)} />
|
<Copy text={hexToBech32("nsec", key)} />
|
||||||
</div>
|
</div>
|
||||||
<button type="button" className="btn btn-primary" onClick={loginWithKey}>
|
<button type="button" className="btn btn-primary" onClick={loginWithKey}>
|
||||||
<FormattedMessage defaultMessage="Ok, it's safe" />
|
<FormattedMessage defaultMessage="Ok, it's safe" id="My6HwN" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import "./markdown.css";
|
import "./markdown.css";
|
||||||
|
|
||||||
import { ReactNode, forwardRef, useMemo } from "react";
|
import { ReactNode, forwardRef, useMemo } from "react";
|
||||||
import { marked, Token } from "marked";
|
import { Token, marked } from "marked";
|
||||||
import { HyperText } from "./hypertext";
|
import { HyperText } from "./hypertext";
|
||||||
import { Text } from "./text";
|
import { Text } from "./text";
|
||||||
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { useContext, useMemo } from "react";
|
import { useContext, useMemo } from "react";
|
||||||
import { useLogin } from "hooks/login";
|
|
||||||
import AsyncButton from "element/async-button";
|
|
||||||
import { Login } from "index";
|
|
||||||
import { MUTED } from "const";
|
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { SnortContext } from "@snort/system-react";
|
import { SnortContext } from "@snort/system-react";
|
||||||
|
|
||||||
|
import { useLogin } from "@/hooks/login";
|
||||||
|
import AsyncButton from "./async-button";
|
||||||
|
import { Login } from "@/index";
|
||||||
|
import { MUTED } from "@/const";
|
||||||
|
|
||||||
export function useMute(pubkey: string) {
|
export function useMute(pubkey: string) {
|
||||||
const system = useContext(SnortContext);
|
const system = useContext(SnortContext);
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
@ -55,7 +56,11 @@ export function LoggedInMuteButton({ pubkey }: { pubkey: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncButton type="button" className="btn delete-button" onClick={() => (isMuted ? unmute() : mute())}>
|
<AsyncButton type="button" className="btn delete-button" onClick={() => (isMuted ? unmute() : mute())}>
|
||||||
{isMuted ? <FormattedMessage defaultMessage="Unmute" /> : <FormattedMessage defaultMessage="Mute" />}
|
{isMuted ? (
|
||||||
|
<FormattedMessage defaultMessage="Unmute" id="W9355R" />
|
||||||
|
) : (
|
||||||
|
<FormattedMessage defaultMessage="Mute" id="x82IOl" />
|
||||||
|
)}
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import "./new-goal.css";
|
import "./new-goal.css";
|
||||||
import * as Dialog from "@radix-ui/react-dialog";
|
import * as Dialog from "@radix-ui/react-dialog";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
import { useContext, useState } from "react";
|
||||||
|
import { SnortContext } from "@snort/system-react";
|
||||||
|
|
||||||
import AsyncButton from "./async-button";
|
import AsyncButton from "./async-button";
|
||||||
import { Icon } from "element/icon";
|
import { Icon } from "./icon";
|
||||||
import { useContext, useState } from "react";
|
import { GOAL } from "@/const";
|
||||||
import { GOAL } from "const";
|
import { useLogin } from "@/hooks/login";
|
||||||
import { useLogin } from "hooks/login";
|
import { defaultRelays } from "@/const";
|
||||||
import { FormattedMessage } from "react-intl";
|
|
||||||
import { defaultRelays } from "const";
|
|
||||||
import { SnortContext } from "@snort/system-react";
|
|
||||||
|
|
||||||
export function NewGoalDialog() {
|
export function NewGoalDialog() {
|
||||||
const system = useContext(SnortContext);
|
const system = useContext(SnortContext);
|
||||||
@ -44,7 +44,7 @@ export function NewGoalDialog() {
|
|||||||
<span>
|
<span>
|
||||||
<Icon name="zap-filled" size={12} />
|
<Icon name="zap-filled" size={12} />
|
||||||
<span>
|
<span>
|
||||||
<FormattedMessage defaultMessage="Add stream goal" />
|
<FormattedMessage defaultMessage="Add stream goal" id="wOy57k" />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
@ -56,12 +56,12 @@ export function NewGoalDialog() {
|
|||||||
<div className="zap-goals">
|
<div className="zap-goals">
|
||||||
<Icon name="zap-filled" className="stream-zap-goals-icon" size={16} />
|
<Icon name="zap-filled" className="stream-zap-goals-icon" size={16} />
|
||||||
<h3>
|
<h3>
|
||||||
<FormattedMessage defaultMessage="Stream Zap Goals" />
|
<FormattedMessage defaultMessage="Stream Zap Goals" id="0GfNiL" />
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Name" />
|
<FormattedMessage defaultMessage="Name" id="HAlOn1" />
|
||||||
</p>
|
</p>
|
||||||
<div className="paper">
|
<div className="paper">
|
||||||
<input
|
<input
|
||||||
@ -74,7 +74,7 @@ export function NewGoalDialog() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Amount" />
|
<FormattedMessage defaultMessage="Amount" id="/0TOL5" />
|
||||||
</p>
|
</p>
|
||||||
<div className="paper">
|
<div className="paper">
|
||||||
<input
|
<input
|
||||||
@ -89,7 +89,7 @@ export function NewGoalDialog() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="create-goal">
|
<div className="create-goal">
|
||||||
<AsyncButton type="button" className="btn btn-primary wide" disabled={!isValid} onClick={publishGoal}>
|
<AsyncButton type="button" className="btn btn-primary wide" disabled={!isValid} onClick={publishGoal}>
|
||||||
<FormattedMessage defaultMessage="Create Goal" />
|
<FormattedMessage defaultMessage="Create Goal" id="X2PZ7D" />
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import "./new-stream.css";
|
import "./new-stream.css";
|
||||||
import * as Dialog from "@radix-ui/react-dialog";
|
import * as Dialog from "@radix-ui/react-dialog";
|
||||||
|
|
||||||
import { Icon } from "element/icon";
|
|
||||||
import { useStreamProvider } from "hooks/stream-provider";
|
|
||||||
import { StreamProvider, StreamProviders } from "providers";
|
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { StreamEditor, StreamEditorProps } from "./stream-editor";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { eventLink, findTag } from "utils";
|
|
||||||
import { NostrProviderDialog } from "./nostr-provider-dialog";
|
|
||||||
import { unwrap } from "@snort/shared";
|
import { unwrap } from "@snort/shared";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { SnortContext } from "@snort/system-react";
|
import { SnortContext } from "@snort/system-react";
|
||||||
|
|
||||||
|
import { Icon } from "./icon";
|
||||||
|
import { useStreamProvider } from "@/hooks/stream-provider";
|
||||||
|
import { StreamProvider, StreamProviders } from "@/providers";
|
||||||
|
import { StreamEditor, StreamEditorProps } from "./stream-editor";
|
||||||
|
import { eventLink, findTag } from "@/utils";
|
||||||
|
import { NostrProviderDialog } from "./nostr-provider-dialog";
|
||||||
|
|
||||||
function NewStream({ ev, onFinish }: StreamEditorProps) {
|
function NewStream({ ev, onFinish }: StreamEditorProps) {
|
||||||
const system = useContext(SnortContext);
|
const system = useContext(SnortContext);
|
||||||
const providers = useStreamProvider();
|
const providers = useStreamProvider();
|
||||||
@ -65,7 +65,7 @@ function NewStream({ ev, onFinish }: StreamEditorProps) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Stream Providers" />
|
<FormattedMessage defaultMessage="Stream Providers" id="6Z2pvJ" />
|
||||||
</p>
|
</p>
|
||||||
<div className="flex g12">
|
<div className="flex g12">
|
||||||
{providers.map(v => (
|
{providers.map(v => (
|
||||||
@ -94,7 +94,7 @@ export function NewStreamDialog(props: NewStreamDialogProps & StreamEditorProps)
|
|||||||
{!props.text && (
|
{!props.text && (
|
||||||
<>
|
<>
|
||||||
<span className="hide-on-mobile">
|
<span className="hide-on-mobile">
|
||||||
<FormattedMessage defaultMessage="Stream" />
|
<FormattedMessage defaultMessage="Stream" id="uYw2LD" />
|
||||||
</span>
|
</span>
|
||||||
<Icon name="signal" />
|
<Icon name="signal" />
|
||||||
</>
|
</>
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { NostrEvent } from "@snort/system";
|
import { NostrEvent } from "@snort/system";
|
||||||
import { StreamProvider, StreamProviderEndpoint, StreamProviderInfo } from "providers";
|
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
import { SnortContext } from "@snort/system-react";
|
||||||
|
|
||||||
|
import { StreamProvider, StreamProviderEndpoint, StreamProviderInfo } from "@/providers";
|
||||||
import { SendZaps } from "./send-zap";
|
import { SendZaps } from "./send-zap";
|
||||||
import { StreamEditor, StreamEditorProps } from "./stream-editor";
|
import { StreamEditor, StreamEditorProps } from "./stream-editor";
|
||||||
import Spinner from "./spinner";
|
import Spinner from "./spinner";
|
||||||
import AsyncButton from "./async-button";
|
import AsyncButton from "./async-button";
|
||||||
import { FormattedMessage } from "react-intl";
|
|
||||||
import { SnortContext } from "@snort/system-react";
|
|
||||||
|
|
||||||
export function NostrProviderDialog({ provider, ...others }: { provider: StreamProvider } & StreamEditorProps) {
|
export function NostrProviderDialog({ provider, ...others }: { provider: StreamProvider } & StreamEditorProps) {
|
||||||
const system = useContext(SnortContext);
|
const system = useContext(SnortContext);
|
||||||
@ -92,13 +93,14 @@ export function NostrProviderDialog({ provider, ...others }: { provider: StreamP
|
|||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="I have read and agree with {provider}'s {terms}."
|
defaultMessage="I have read and agree with {provider}'s {terms}."
|
||||||
|
id="RJOmzk"
|
||||||
values={{
|
values={{
|
||||||
provider: info.name,
|
provider: info.name,
|
||||||
terms: (
|
terms: (
|
||||||
<span
|
<span
|
||||||
className="tos-link"
|
className="tos-link"
|
||||||
onClick={() => window.open(info.tosLink, "popup", "width=400,height=800")}>
|
onClick={() => window.open(info.tosLink, "popup", "width=400,height=800")}>
|
||||||
<FormattedMessage defaultMessage="terms and conditions" />
|
<FormattedMessage defaultMessage="terms and conditions" id="thsiMl" />
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
@ -108,7 +110,7 @@ export function NostrProviderDialog({ provider, ...others }: { provider: StreamP
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<AsyncButton type="button" className="btn btn-primary wide" disabled={!tos} onClick={acceptTos}>
|
<AsyncButton type="button" className="btn btn-primary wide" disabled={!tos} onClick={acceptTos}>
|
||||||
<FormattedMessage defaultMessage="Continue" />
|
<FormattedMessage defaultMessage="Continue" id="acrOoz" />
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@ -120,7 +122,7 @@ export function NostrProviderDialog({ provider, ...others }: { provider: StreamP
|
|||||||
{info.endpoints.length > 1 && (
|
{info.endpoints.length > 1 && (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Endpoint" />
|
<FormattedMessage defaultMessage="Endpoint" id="ljmS5P" />
|
||||||
</p>
|
</p>
|
||||||
<div className="flex g12">
|
<div className="flex g12">
|
||||||
{sortEndpoints(info.endpoints).map(a => (
|
{sortEndpoints(info.endpoints).map(a => (
|
||||||
@ -133,7 +135,7 @@ export function NostrProviderDialog({ provider, ...others }: { provider: StreamP
|
|||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Server Url" />
|
<FormattedMessage defaultMessage="Server Url" id="5kx+2v" />
|
||||||
</p>
|
</p>
|
||||||
<div className="paper">
|
<div className="paper">
|
||||||
<input type="text" value={ep?.url} disabled />
|
<input type="text" value={ep?.url} disabled />
|
||||||
@ -141,36 +143,40 @@ export function NostrProviderDialog({ provider, ...others }: { provider: StreamP
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Stream Key" />
|
<FormattedMessage defaultMessage="Stream Key" id="LknBsU" />
|
||||||
</p>
|
</p>
|
||||||
<div className="flex g12">
|
<div className="flex g12">
|
||||||
<div className="paper f-grow">
|
<div className="paper f-grow">
|
||||||
<input type="password" value={ep?.key} disabled />
|
<input type="password" value={ep?.key} disabled />
|
||||||
</div>
|
</div>
|
||||||
<button className="btn btn-primary" onClick={() => window.navigator.clipboard.writeText(ep?.key ?? "")}>
|
<button className="btn btn-primary" onClick={() => window.navigator.clipboard.writeText(ep?.key ?? "")}>
|
||||||
<FormattedMessage defaultMessage="Copy" />
|
<FormattedMessage defaultMessage="Copy" id="4l6vz1" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Balance" />
|
<FormattedMessage defaultMessage="Balance" id="H5+NAX" />
|
||||||
</p>
|
</p>
|
||||||
<div className="flex g12">
|
<div className="flex g12">
|
||||||
<div className="paper f-grow">
|
<div className="paper f-grow">
|
||||||
<FormattedMessage defaultMessage="{amount} sats" values={{ amount: info.balance?.toLocaleString() }} />
|
<FormattedMessage
|
||||||
|
defaultMessage="{amount} sats"
|
||||||
|
id="vrTOHJ"
|
||||||
|
values={{ amount: info.balance?.toLocaleString() }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button className="btn btn-primary" onClick={() => setTopup(true)}>
|
<button className="btn btn-primary" onClick={() => setTopup(true)}>
|
||||||
<FormattedMessage defaultMessage="Topup" />
|
<FormattedMessage defaultMessage="Topup" id="nBCvvJ" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<small>
|
<small>
|
||||||
<FormattedMessage defaultMessage="About {estimate}" values={{ estimate: calcEstimate() }} />
|
<FormattedMessage defaultMessage="About {estimate}" id="Q3au2v" values={{ estimate: calcEstimate() }} />
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Resolutions" />
|
<FormattedMessage defaultMessage="Resolutions" id="4uI538" />
|
||||||
</p>
|
</p>
|
||||||
<div className="flex g12">
|
<div className="flex g12">
|
||||||
{ep?.capabilities?.map(a => (
|
{ep?.capabilities?.map(a => (
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import "./note.css";
|
import "./note.css";
|
||||||
import { type NostrEvent, NostrPrefix } from "@snort/system";
|
import { type NostrEvent, NostrPrefix } from "@snort/system";
|
||||||
|
|
||||||
import { Markdown } from "element/markdown";
|
|
||||||
import { ExternalIconLink } from "element/external-link";
|
|
||||||
import { Profile } from "element/profile";
|
|
||||||
import { hexToBech32 } from "@snort/shared";
|
import { hexToBech32 } from "@snort/shared";
|
||||||
|
|
||||||
|
import { Markdown } from "./markdown";
|
||||||
|
import { ExternalIconLink } from "./external-link";
|
||||||
|
import { Profile } from "./profile";
|
||||||
|
|
||||||
export function Note({ ev }: { ev: NostrEvent }) {
|
export function Note({ ev }: { ev: NostrEvent }) {
|
||||||
return (
|
return (
|
||||||
<div className="surface note">
|
<div className="surface note">
|
||||||
|
@ -4,11 +4,11 @@ import { Link } from "react-router-dom";
|
|||||||
import { useUserProfile } from "@snort/system-react";
|
import { useUserProfile } from "@snort/system-react";
|
||||||
import { UserMetadata } from "@snort/system";
|
import { UserMetadata } from "@snort/system";
|
||||||
import { hexToBech32 } from "@snort/shared";
|
import { hexToBech32 } from "@snort/shared";
|
||||||
|
|
||||||
import { Icon } from "element/icon";
|
|
||||||
import usePlaceholder from "hooks/placeholders";
|
|
||||||
import { useInView } from "react-intersection-observer";
|
import { useInView } from "react-intersection-observer";
|
||||||
|
|
||||||
|
import { Icon } from "./icon";
|
||||||
|
import usePlaceholder from "@/hooks/placeholders";
|
||||||
|
|
||||||
export interface ProfileOptions {
|
export interface ProfileOptions {
|
||||||
showName?: boolean;
|
showName?: boolean;
|
||||||
showAvatar?: boolean;
|
showAvatar?: boolean;
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
import "./send-zap.css";
|
import "./send-zap.css";
|
||||||
import * as Dialog from "@radix-ui/react-dialog";
|
import * as Dialog from "@radix-ui/react-dialog";
|
||||||
import { useEffect, useState, type ReactNode } from "react";
|
import { type ReactNode, useEffect, useState } from "react";
|
||||||
import { LNURL } from "@snort/shared";
|
import { LNURL } from "@snort/shared";
|
||||||
import { NostrEvent, EventPublisher } from "@snort/system";
|
import { EventPublisher, NostrEvent } from "@snort/system";
|
||||||
import { secp256k1 } from "@noble/curves/secp256k1";
|
import { secp256k1 } from "@noble/curves/secp256k1";
|
||||||
import { bytesToHex } from "@noble/curves/abstract/utils";
|
import { bytesToHex } from "@noble/curves/abstract/utils";
|
||||||
|
import { FormattedMessage, FormattedNumber } from "react-intl";
|
||||||
|
|
||||||
import { formatSats } from "../number";
|
import { formatSats } from "../number";
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "./icon";
|
||||||
import AsyncButton from "./async-button";
|
import AsyncButton from "./async-button";
|
||||||
import QrCode from "./qr-code";
|
import QrCode from "./qr-code";
|
||||||
import { useLogin } from "hooks/login";
|
import { useLogin } from "@/hooks/login";
|
||||||
import Copy from "./copy";
|
import Copy from "./copy";
|
||||||
import { defaultRelays } from "const";
|
import { defaultRelays } from "@/const";
|
||||||
import { FormattedMessage, FormattedNumber } from "react-intl";
|
import { useRates } from "@/hooks/rates";
|
||||||
import { useRates } from "hooks/rates";
|
|
||||||
|
|
||||||
export interface LNURLLike {
|
export interface LNURLLike {
|
||||||
get name(): string;
|
get name(): string;
|
||||||
@ -131,6 +131,7 @@ export function SendZaps({ lnurl, pubkey, aTag, eTag, targetName, onFinish }: Se
|
|||||||
<small>
|
<small>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Zap amount in {currency}"
|
defaultMessage="Zap amount in {currency}"
|
||||||
|
id="IJDKz3"
|
||||||
values={{ currency: isFiat ? "USD" : "SATS" }}
|
values={{ currency: isFiat ? "USD" : "SATS" }}
|
||||||
/>
|
/>
|
||||||
{isFiat && (
|
{isFiat && (
|
||||||
@ -138,6 +139,7 @@ export function SendZaps({ lnurl, pubkey, aTag, eTag, targetName, onFinish }: Se
|
|||||||
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="@ {rate}"
|
defaultMessage="@ {rate}"
|
||||||
|
id="YPh5Nq"
|
||||||
description="Showing zap amount in USD @ rate"
|
description="Showing zap amount in USD @ rate"
|
||||||
values={{
|
values={{
|
||||||
rate: <FormattedNumber value={usdRate} />,
|
rate: <FormattedNumber value={usdRate} />,
|
||||||
@ -157,7 +159,7 @@ export function SendZaps({ lnurl, pubkey, aTag, eTag, targetName, onFinish }: Se
|
|||||||
{svc && (svc.maxCommentLength > 0 || svc.canZap) && (
|
{svc && (svc.maxCommentLength > 0 || svc.canZap) && (
|
||||||
<div>
|
<div>
|
||||||
<small>
|
<small>
|
||||||
<FormattedMessage defaultMessage="Your comment for {name}" values={{ name }} />
|
<FormattedMessage defaultMessage="Your comment for {name}" id="ESyhzp" values={{ name }} />
|
||||||
</small>
|
</small>
|
||||||
<div className="paper">
|
<div className="paper">
|
||||||
<textarea placeholder="Nice!" value={comment} onChange={e => setComment(e.target.value)} />
|
<textarea placeholder="Nice!" value={comment} onChange={e => setComment(e.target.value)} />
|
||||||
@ -166,7 +168,7 @@ export function SendZaps({ lnurl, pubkey, aTag, eTag, targetName, onFinish }: Se
|
|||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<AsyncButton onClick={send} className="btn btn-primary">
|
<AsyncButton onClick={send} className="btn btn-primary">
|
||||||
<FormattedMessage defaultMessage="Zap!" />
|
<FormattedMessage defaultMessage="Zap!" id="3HwrQo" />
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@ -184,7 +186,7 @@ export function SendZaps({ lnurl, pubkey, aTag, eTag, targetName, onFinish }: Se
|
|||||||
<Copy text={invoice} />
|
<Copy text={invoice} />
|
||||||
</div>
|
</div>
|
||||||
<button className="btn btn-primary wide" onClick={() => onFinish()}>
|
<button className="btn btn-primary wide" onClick={() => onFinish()}>
|
||||||
<FormattedMessage defaultMessage="Back" />
|
<FormattedMessage defaultMessage="Back" id="cyR7Kh" />
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -193,7 +195,7 @@ export function SendZaps({ lnurl, pubkey, aTag, eTag, targetName, onFinish }: Se
|
|||||||
return (
|
return (
|
||||||
<div className="send-zap">
|
<div className="send-zap">
|
||||||
<h3>
|
<h3>
|
||||||
<FormattedMessage defaultMessage="Zap {name}" values={{ name }} />
|
<FormattedMessage defaultMessage="Zap {name}" id="oHPB8Q" values={{ name }} />
|
||||||
<Icon name="zap" />
|
<Icon name="zap" />
|
||||||
</h3>
|
</h3>
|
||||||
{input()}
|
{input()}
|
||||||
@ -212,7 +214,7 @@ export function SendZapsDialog(props: Omit<SendZapsProps, "onFinish">) {
|
|||||||
) : (
|
) : (
|
||||||
<button className="btn btn-primary zap">
|
<button className="btn btn-primary zap">
|
||||||
<span className="hide-on-mobile">
|
<span className="hide-on-mobile">
|
||||||
<FormattedMessage defaultMessage="Zap" />
|
<FormattedMessage defaultMessage="Zap" id="fBI91o" />
|
||||||
</span>
|
</span>
|
||||||
<Icon name="zap-filled" size={16} />
|
<Icon name="zap-filled" size={16} />
|
||||||
</button>
|
</button>
|
||||||
|
@ -2,15 +2,15 @@ import { Menu, MenuItem } from "@szhsin/react-menu";
|
|||||||
import * as Dialog from "@radix-ui/react-dialog";
|
import * as Dialog from "@radix-ui/react-dialog";
|
||||||
import { unwrap } from "@snort/shared";
|
import { unwrap } from "@snort/shared";
|
||||||
import { NostrEvent, NostrPrefix, encodeTLV } from "@snort/system";
|
import { NostrEvent, NostrPrefix, encodeTLV } from "@snort/system";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
import { useContext, useState } from "react";
|
||||||
|
import { SnortContext } from "@snort/system-react";
|
||||||
|
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "./icon";
|
||||||
import { useContext, useState } from "react";
|
|
||||||
import { Textarea } from "./textarea";
|
import { Textarea } from "./textarea";
|
||||||
import { findTag } from "utils";
|
import { findTag } from "@/utils";
|
||||||
import AsyncButton from "./async-button";
|
import AsyncButton from "./async-button";
|
||||||
import { useLogin } from "hooks/login";
|
import { useLogin } from "@/hooks/login";
|
||||||
import { FormattedMessage } from "react-intl";
|
|
||||||
import { SnortContext } from "@snort/system-react";
|
|
||||||
|
|
||||||
type ShareOn = "nostr" | "twitter";
|
type ShareOn = "nostr" | "twitter";
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ export function ShareMenu({ ev }: { ev: NostrEvent }) {
|
|||||||
menuClassName="ctx-menu"
|
menuClassName="ctx-menu"
|
||||||
menuButton={
|
menuButton={
|
||||||
<button type="button" className="btn btn-secondary">
|
<button type="button" className="btn btn-secondary">
|
||||||
<FormattedMessage defaultMessage="Share" />
|
<FormattedMessage defaultMessage="Share" id="OKhRC6" />
|
||||||
</button>
|
</button>
|
||||||
}>
|
}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@ -50,7 +50,7 @@ export function ShareMenu({ ev }: { ev: NostrEvent }) {
|
|||||||
setShare("nostr");
|
setShare("nostr");
|
||||||
}}>
|
}}>
|
||||||
<Icon name="nostrich" size={24} />
|
<Icon name="nostrich" size={24} />
|
||||||
<FormattedMessage defaultMessage="Broadcast on Nostr" />
|
<FormattedMessage defaultMessage="Broadcast on Nostr" id="wCIL7o" />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
<Dialog.Root open={Boolean(share)} onOpenChange={() => setShare(undefined)}>
|
<Dialog.Root open={Boolean(share)} onOpenChange={() => setShare(undefined)}>
|
||||||
@ -59,7 +59,7 @@ export function ShareMenu({ ev }: { ev: NostrEvent }) {
|
|||||||
<Dialog.Content className="dialog-content">
|
<Dialog.Content className="dialog-content">
|
||||||
<div className="content-inner">
|
<div className="content-inner">
|
||||||
<h2>
|
<h2>
|
||||||
<FormattedMessage defaultMessage="Share" />
|
<FormattedMessage defaultMessage="Share" id="OKhRC6" />
|
||||||
</h2>
|
</h2>
|
||||||
<div className="paper">
|
<div className="paper">
|
||||||
<Textarea
|
<Textarea
|
||||||
@ -73,7 +73,7 @@ export function ShareMenu({ ev }: { ev: NostrEvent }) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AsyncButton className="btn btn-primary" onClick={sendMessage}>
|
<AsyncButton className="btn btn-primary" onClick={sendMessage}>
|
||||||
<FormattedMessage defaultMessage="Send" />
|
<FormattedMessage defaultMessage="Send" id="9WRlF4" />
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import "./state-pill.css";
|
import "./state-pill.css";
|
||||||
import { StreamState } from "index";
|
import { StreamState } from "@/index";
|
||||||
|
|
||||||
export function StatePill({ state }: { state: StreamState }) {
|
export function StatePill({ state }: { state: StreamState }) {
|
||||||
return <span className={`state pill${state === StreamState.Live ? " live" : ""}`}>{state}</span>;
|
return <span className={`state pill${state === StreamState.Live ? " live" : ""}`}>{state}</span>;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import "./stream-cards.css";
|
import "./stream-cards.css";
|
||||||
|
|
||||||
import { useState, forwardRef, useContext } from "react";
|
import { forwardRef, useContext, useState } from "react";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import * as Dialog from "@radix-ui/react-dialog";
|
import * as Dialog from "@radix-ui/react-dialog";
|
||||||
import { DndProvider, useDrag, useDrop } from "react-dnd";
|
import { DndProvider, useDrag, useDrop } from "react-dnd";
|
||||||
@ -8,20 +8,20 @@ import { HTML5Backend } from "react-dnd-html5-backend";
|
|||||||
|
|
||||||
import { removeUndefined, unwrap } from "@snort/shared";
|
import { removeUndefined, unwrap } from "@snort/shared";
|
||||||
import { NostrLink, TaggedNostrEvent } from "@snort/system";
|
import { NostrLink, TaggedNostrEvent } from "@snort/system";
|
||||||
|
|
||||||
import { Toggle } from "element/toggle";
|
|
||||||
import { Icon } from "element/icon";
|
|
||||||
import { ExternalLink } from "element/external-link";
|
|
||||||
import { FileUploader } from "element/file-uploader";
|
|
||||||
import { Markdown } from "element/markdown";
|
|
||||||
import { useLogin } from "hooks/login";
|
|
||||||
import { useCards, useUserCards } from "hooks/cards";
|
|
||||||
import { CARD, USER_CARDS } from "const";
|
|
||||||
import { findTag } from "utils";
|
|
||||||
import { Login } from "index";
|
|
||||||
import type { Tags } from "types";
|
|
||||||
import { SnortContext } from "@snort/system-react";
|
import { SnortContext } from "@snort/system-react";
|
||||||
|
|
||||||
|
import { Toggle } from "./toggle";
|
||||||
|
import { Icon } from "./icon";
|
||||||
|
import { ExternalLink } from "./external-link";
|
||||||
|
import { FileUploader } from "./file-uploader";
|
||||||
|
import { Markdown } from "./markdown";
|
||||||
|
import { useLogin } from "@/hooks/login";
|
||||||
|
import { useCards, useUserCards } from "@/hooks/cards";
|
||||||
|
import { CARD, USER_CARDS } from "@/const";
|
||||||
|
import { findTag } from "@/utils";
|
||||||
|
import { Login } from "@/index";
|
||||||
|
import type { Tags } from "@/types";
|
||||||
|
|
||||||
interface CardType {
|
interface CardType {
|
||||||
identifier: string;
|
identifier: string;
|
||||||
content: string;
|
content: string;
|
||||||
@ -187,28 +187,28 @@ function CardDialog({ header, cta, cancelCta, card, onSave, onCancel }: CardDial
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="new-card">
|
<div className="new-card">
|
||||||
<h3>{header || <FormattedMessage defaultMessage="Add card" />}</h3>
|
<h3>{header || <FormattedMessage defaultMessage="Add card" id="nwA8Os" />}</h3>
|
||||||
<div className="form-control">
|
<div className="form-control">
|
||||||
<label htmlFor="card-title">
|
<label htmlFor="card-title">
|
||||||
<FormattedMessage defaultMessage="Title" />
|
<FormattedMessage defaultMessage="Title" id="9a9+ww" />
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="card-title"
|
id="card-title"
|
||||||
type="text"
|
type="text"
|
||||||
value={title}
|
value={title}
|
||||||
onChange={e => setTitle(e.target.value)}
|
onChange={e => setTitle(e.target.value)}
|
||||||
placeholder={formatMessage({ defaultMessage: "e.g. about me" })}
|
placeholder={formatMessage({ defaultMessage: "e.g. about me", id: "k21gTS" })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-control">
|
<div className="form-control">
|
||||||
<label htmlFor="card-image">
|
<label htmlFor="card-image">
|
||||||
<FormattedMessage defaultMessage="Image" />
|
<FormattedMessage defaultMessage="Image" id="+0zv6g" />
|
||||||
</label>
|
</label>
|
||||||
<FileUploader defaultImage={image} onFileUpload={setImage} onClear={() => setImage("")} />
|
<FileUploader defaultImage={image} onFileUpload={setImage} onClear={() => setImage("")} />
|
||||||
</div>
|
</div>
|
||||||
<div className="form-control">
|
<div className="form-control">
|
||||||
<label htmlFor="card-image-link">
|
<label htmlFor="card-image-link">
|
||||||
<FormattedMessage defaultMessage="Image Link" />
|
<FormattedMessage defaultMessage="Image Link" id="s5ksS7" />
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="card-image-link"
|
id="card-image-link"
|
||||||
@ -220,20 +220,21 @@ function CardDialog({ header, cta, cancelCta, card, onSave, onCancel }: CardDial
|
|||||||
</div>
|
</div>
|
||||||
<div className="form-control">
|
<div className="form-control">
|
||||||
<label htmlFor="card-content">
|
<label htmlFor="card-content">
|
||||||
<FormattedMessage defaultMessage="Content" />
|
<FormattedMessage defaultMessage="Content" id="Jq3FDz" />
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
placeholder={formatMessage({ defaultMessage: "Start typing" })}
|
placeholder={formatMessage({ defaultMessage: "Start typing", id: "w0Xm2F" })}
|
||||||
value={content}
|
value={content}
|
||||||
onChange={e => setContent(e.target.value)}
|
onChange={e => setContent(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<span className="help-text">
|
<span className="help-text">
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Supports {markdown}"
|
defaultMessage="Supports {markdown}"
|
||||||
|
id="I1kjHI"
|
||||||
values={{
|
values={{
|
||||||
markdown: (
|
markdown: (
|
||||||
<ExternalLink href="https://www.markdownguide.org/cheat-sheet">
|
<ExternalLink href="https://www.markdownguide.org/cheat-sheet">
|
||||||
<FormattedMessage defaultMessage="Markdown" />
|
<FormattedMessage defaultMessage="Markdown" id="jr4+vD" />
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
@ -242,10 +243,10 @@ function CardDialog({ header, cta, cancelCta, card, onSave, onCancel }: CardDial
|
|||||||
</div>
|
</div>
|
||||||
<div className="new-card-buttons">
|
<div className="new-card-buttons">
|
||||||
<button className="btn btn-primary add-button" onClick={() => onSave({ title, image, content, link })}>
|
<button className="btn btn-primary add-button" onClick={() => onSave({ title, image, content, link })}>
|
||||||
{cta || <FormattedMessage defaultMessage="Add Card" />}
|
{cta || <FormattedMessage defaultMessage="Add Card" id="UJBFYK" />}
|
||||||
</button>
|
</button>
|
||||||
<button className="btn delete-button" onClick={onCancel}>
|
<button className="btn delete-button" onClick={onCancel}>
|
||||||
{cancelCta || <FormattedMessage defaultMessage="Cancel" />}
|
{cancelCta || <FormattedMessage defaultMessage="Cancel" id="47FYwb" />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -310,7 +311,7 @@ function EditCard({ card, cards }: EditCardProps) {
|
|||||||
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<Dialog.Trigger asChild>
|
<Dialog.Trigger asChild>
|
||||||
<button className="btn btn-primary">
|
<button className="btn btn-primary">
|
||||||
<FormattedMessage defaultMessage="Edit" />
|
<FormattedMessage defaultMessage="Edit" id="wEQDC6" />
|
||||||
</button>
|
</button>
|
||||||
</Dialog.Trigger>
|
</Dialog.Trigger>
|
||||||
<Dialog.Portal>
|
<Dialog.Portal>
|
||||||
@ -318,9 +319,9 @@ function EditCard({ card, cards }: EditCardProps) {
|
|||||||
<Dialog.Content className="dialog-content">
|
<Dialog.Content className="dialog-content">
|
||||||
<div className="content-inner">
|
<div className="content-inner">
|
||||||
<CardDialog
|
<CardDialog
|
||||||
header={formatMessage({ defaultMessage: "Edit card" })}
|
header={formatMessage({ defaultMessage: "Edit card", id: "OWgHbg" })}
|
||||||
cta={formatMessage({ defaultMessage: "Save card" })}
|
cta={formatMessage({ defaultMessage: "Save card", id: "rfC1Zq" })}
|
||||||
cancelCta={formatMessage({ defaultMessage: "Delete" })}
|
cancelCta={formatMessage({ defaultMessage: "Delete", id: "K3r6DQ" })}
|
||||||
card={card}
|
card={card}
|
||||||
onSave={editCard}
|
onSave={editCard}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import "./stream-editor.css";
|
import "./stream-editor.css";
|
||||||
import { useEffect, useState, useCallback } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { NostrEvent } from "@snort/system";
|
import { NostrEvent } from "@snort/system";
|
||||||
import { unixNow } from "@snort/shared";
|
import { unixNow } from "@snort/shared";
|
||||||
import { TagsInput } from "react-tag-input-component";
|
import { TagsInput } from "react-tag-input-component";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
|
||||||
import AsyncButton from "./async-button";
|
import AsyncButton from "./async-button";
|
||||||
import { StreamState } from "../index";
|
import { StreamState } from "@/index";
|
||||||
import { findTag } from "../utils";
|
import { findTag } from "@/utils";
|
||||||
import { useLogin } from "hooks/login";
|
import { useLogin } from "@/hooks/login";
|
||||||
import { NewGoalDialog } from "element/new-goal";
|
import { NewGoalDialog } from "./new-goal";
|
||||||
import { useGoals } from "hooks/goals";
|
import { useGoals } from "@/hooks/goals";
|
||||||
|
|
||||||
export interface StreamEditorProps {
|
export interface StreamEditorProps {
|
||||||
ev?: NostrEvent;
|
ev?: NostrEvent;
|
||||||
@ -37,9 +37,9 @@ function GoalSelector({ goal, pubkey, onGoalSelect }: GoalSelectorProps) {
|
|||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
return (
|
return (
|
||||||
<select onChange={ev => onGoalSelect(ev.target.value)}>
|
<select onChange={ev => onGoalSelect(ev.target.value)}>
|
||||||
<option value="">{formatMessage({ defaultMessage: "Select a goal..." })}</option>
|
<option value={goal}>{formatMessage({ defaultMessage: "Select a goal...", id: "I/TubD" })}</option>
|
||||||
{goals?.map(x => (
|
{goals?.map(x => (
|
||||||
<option key={x.id} value={x.id} selected={goal === x.id}>
|
<option key={x.id} value={x.id}>
|
||||||
{x.content}
|
{x.content}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
@ -139,12 +139,12 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
|
|||||||
{(options?.canSetTitle ?? true) && (
|
{(options?.canSetTitle ?? true) && (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Title" />
|
<FormattedMessage defaultMessage="Title" id="9a9+ww" />
|
||||||
</p>
|
</p>
|
||||||
<div className="paper">
|
<div className="paper">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={formatMessage({ defaultMessage: "What are we steaming today?" })}
|
placeholder={formatMessage({ defaultMessage: "What are we steaming today?", id: "QRHNuF" })}
|
||||||
value={title}
|
value={title}
|
||||||
onChange={e => setTitle(e.target.value)}
|
onChange={e => setTitle(e.target.value)}
|
||||||
/>
|
/>
|
||||||
@ -154,12 +154,12 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
|
|||||||
{(options?.canSetSummary ?? true) && (
|
{(options?.canSetSummary ?? true) && (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Summary" />
|
<FormattedMessage defaultMessage="Summary" id="RrCui3" />
|
||||||
</p>
|
</p>
|
||||||
<div className="paper">
|
<div className="paper">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={formatMessage({ defaultMessage: "A short description of the content" })}
|
placeholder={formatMessage({ defaultMessage: "A short description of the content", id: "mtNGwh" })}
|
||||||
value={summary}
|
value={summary}
|
||||||
onChange={e => setSummary(e.target.value)}
|
onChange={e => setSummary(e.target.value)}
|
||||||
/>
|
/>
|
||||||
@ -169,7 +169,7 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
|
|||||||
{(options?.canSetImage ?? true) && (
|
{(options?.canSetImage ?? true) && (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Cover Image" />
|
<FormattedMessage defaultMessage="Cover Image" id="Gq6x9o" />
|
||||||
</p>
|
</p>
|
||||||
<div className="paper">
|
<div className="paper">
|
||||||
<input type="text" placeholder="https://" value={image} onChange={e => setImage(e.target.value)} />
|
<input type="text" placeholder="https://" value={image} onChange={e => setImage(e.target.value)} />
|
||||||
@ -179,13 +179,13 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
|
|||||||
{(options?.canSetStream ?? true) && (
|
{(options?.canSetStream ?? true) && (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Stream URL" />
|
<FormattedMessage defaultMessage="Stream URL" id="QRRCp0" />
|
||||||
</p>
|
</p>
|
||||||
<div className="paper">
|
<div className="paper">
|
||||||
<input type="text" placeholder="https://" value={stream} onChange={e => setStream(e.target.value)} />
|
<input type="text" placeholder="https://" value={stream} onChange={e => setStream(e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
<small>
|
<small>
|
||||||
<FormattedMessage defaultMessage="Stream type should be HLS" />
|
<FormattedMessage defaultMessage="Stream type should be HLS" id="oZrFyI" />
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -193,7 +193,7 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
|
|||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Status" />
|
<FormattedMessage defaultMessage="Status" id="tzMNF3" />
|
||||||
</p>
|
</p>
|
||||||
<div className="flex g12">
|
<div className="flex g12">
|
||||||
{[StreamState.Live, StreamState.Planned, StreamState.Ended].map(v => (
|
{[StreamState.Live, StreamState.Planned, StreamState.Ended].map(v => (
|
||||||
@ -206,7 +206,7 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
|
|||||||
{status === StreamState.Planned && (
|
{status === StreamState.Planned && (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Start Time" />
|
<FormattedMessage defaultMessage="Start Time" id="5QYdPU" />
|
||||||
</p>
|
</p>
|
||||||
<div className="paper">
|
<div className="paper">
|
||||||
<input
|
<input
|
||||||
@ -222,7 +222,7 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
|
|||||||
{(options?.canSetTags ?? true) && (
|
{(options?.canSetTags ?? true) && (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Tags" />
|
<FormattedMessage defaultMessage="Tags" id="1EYCdR" />
|
||||||
</p>
|
</p>
|
||||||
<div className="paper">
|
<div className="paper">
|
||||||
<TagsInput value={tags} onChange={setTags} placeHolder="Music,DJ,English" separators={["Enter", ","]} />
|
<TagsInput value={tags} onChange={setTags} placeHolder="Music,DJ,English" separators={["Enter", ","]} />
|
||||||
@ -233,7 +233,7 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
|
|||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Goal" />
|
<FormattedMessage defaultMessage="Goal" id="0VV/sK" />
|
||||||
</p>
|
</p>
|
||||||
<div className="paper">
|
<div className="paper">
|
||||||
<GoalSelector goal={goal} pubkey={login?.pubkey} onGoalSelect={setGoal} />
|
<GoalSelector goal={goal} pubkey={login?.pubkey} onGoalSelect={setGoal} />
|
||||||
@ -249,15 +249,22 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="warning">
|
<div className="warning">
|
||||||
<FormattedMessage defaultMessage="NSFW Content" />
|
<FormattedMessage defaultMessage="NSFW Content" id="Atr2p4" />
|
||||||
</div>
|
</div>
|
||||||
<FormattedMessage defaultMessage="Check here if this stream contains nudity or pornographic content." />
|
<FormattedMessage
|
||||||
|
defaultMessage="Check here if this stream contains nudity or pornographic content."
|
||||||
|
id="lZpRMR"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<AsyncButton type="button" className="btn btn-primary wide" disabled={!isValid} onClick={publishStream}>
|
<AsyncButton type="button" className="btn btn-primary wide" disabled={!isValid} onClick={publishStream}>
|
||||||
{ev ? <FormattedMessage defaultMessage="Save" /> : <FormattedMessage defaultMessage="Start Stream" />}
|
{ev ? (
|
||||||
|
<FormattedMessage defaultMessage="Save" id="jvo0vs" />
|
||||||
|
) : (
|
||||||
|
<FormattedMessage defaultMessage="Start Stream" id="TaTRKo" />
|
||||||
|
)}
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { NostrEvent } from "@snort/system";
|
import { NostrEvent } from "@snort/system";
|
||||||
import { unixNow } from "@snort/shared";
|
import { unixNow } from "@snort/shared";
|
||||||
import { findTag } from "../utils";
|
import { findTag } from "@/utils";
|
||||||
|
|
||||||
export function StreamTimer({ ev }: { ev?: NostrEvent }) {
|
export function StreamTimer({ ev }: { ev?: NostrEvent }) {
|
||||||
const [time, setTime] = useState("");
|
const [time, setTime] = useState("");
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import moment from "moment";
|
|
||||||
|
|
||||||
import { NostrEvent } from "@snort/system";
|
import { NostrEvent } from "@snort/system";
|
||||||
|
|
||||||
import { StreamState } from "index";
|
import { StreamState } from "@/index";
|
||||||
import { findTag, getTagValues } from "utils";
|
import { findTag, getTagValues } from "@/utils";
|
||||||
|
|
||||||
export function Tags({ children, max, ev }: { children?: ReactNode; max?: number; ev: NostrEvent }) {
|
export function Tags({ children, max, ev }: { children?: ReactNode; max?: number; ev: NostrEvent }) {
|
||||||
const status = findTag(ev, "status");
|
const status = findTag(ev, "status");
|
||||||
const start = findTag(ev, "starts");
|
|
||||||
const hashtags = getTagValues(ev.tags, "t");
|
const hashtags = getTagValues(ev.tags, "t");
|
||||||
const tags = max ? hashtags.slice(0, max) : hashtags;
|
const tags = max ? hashtags.slice(0, max) : hashtags;
|
||||||
|
|
||||||
@ -18,8 +16,7 @@ export function Tags({ children, max, ev }: { children?: ReactNode; max?: number
|
|||||||
{children}
|
{children}
|
||||||
{status === StreamState.Planned && (
|
{status === StreamState.Planned && (
|
||||||
<span className="pill">
|
<span className="pill">
|
||||||
{status === StreamState.Planned ? <FormattedMessage defaultMessage="Starts " /> : ""}
|
{status === StreamState.Planned ? <FormattedMessage defaultMessage="Starts " id="0hNxBy" /> : ""}
|
||||||
{moment(Number(start) * 1000).fromNow()}
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{tags.map(a => (
|
{tags.map(a => (
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import "./textarea.css";
|
import "./textarea.css";
|
||||||
import { type KeyboardEvent, type ChangeEvent, useContext } from "react";
|
import { type ChangeEvent, type KeyboardEvent, useContext } from "react";
|
||||||
import ReactTextareaAutocomplete, { TriggerType } from "@webscopeio/react-textarea-autocomplete";
|
import ReactTextareaAutocomplete, { TriggerType } from "@webscopeio/react-textarea-autocomplete";
|
||||||
import "@webscopeio/react-textarea-autocomplete/style.css";
|
import "@webscopeio/react-textarea-autocomplete/style.css";
|
||||||
import uniqWith from "lodash/uniqWith";
|
import uniqWith from "lodash/uniqWith";
|
||||||
@ -9,9 +9,9 @@ import { hexToBech32 } from "@snort/shared";
|
|||||||
import { SnortContext } from "@snort/system-react";
|
import { SnortContext } from "@snort/system-react";
|
||||||
import { MetadataCache, NostrPrefix, UserProfileCache } from "@snort/system";
|
import { MetadataCache, NostrPrefix, UserProfileCache } from "@snort/system";
|
||||||
|
|
||||||
import { Emoji } from "element/emoji";
|
import { Emoji } from "./emoji";
|
||||||
import { Avatar } from "element/avatar";
|
import { Avatar } from "./avatar";
|
||||||
import type { EmojiTag } from "types";
|
import type { EmojiTag } from "@/types";
|
||||||
|
|
||||||
interface EmojiItemProps {
|
interface EmojiItemProps {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as BaseToggle from "@radix-ui/react-toggle";
|
import * as BaseToggle from "@radix-ui/react-toggle";
|
||||||
import "./toggle.css";
|
import "./toggle.css";
|
||||||
import { Icon } from "element/icon";
|
import { Icon } from "./icon";
|
||||||
|
|
||||||
interface ToggleProps {
|
interface ToggleProps {
|
||||||
label: string;
|
label: string;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ParsedZap } from "@snort/system";
|
import { ParsedZap } from "@snort/system";
|
||||||
import useTopZappers from "hooks/top-zappers";
|
import useTopZappers from "@/hooks/top-zappers";
|
||||||
import { formatSats } from "number";
|
import { formatSats } from "@/number";
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "./icon";
|
||||||
import { Profile } from "./profile";
|
import { Profile } from "./profile";
|
||||||
|
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
|
import "./video-tile.css";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Profile } from "./profile";
|
import { Profile } from "./profile";
|
||||||
import "./video-tile.css";
|
import { NostrEvent, NostrPrefix, encodeTLV } from "@snort/system";
|
||||||
import { NostrEvent, encodeTLV, NostrPrefix } from "@snort/system";
|
|
||||||
import { useInView } from "react-intersection-observer";
|
import { useInView } from "react-intersection-observer";
|
||||||
import { StatePill } from "./state-pill";
|
|
||||||
import { StreamState } from "index";
|
|
||||||
import { findTag, getHost } from "utils";
|
|
||||||
import { formatSats } from "number";
|
|
||||||
import ZapStream from "../../public/zap-stream.svg";
|
|
||||||
import { isContentWarningAccepted } from "./content-warning";
|
|
||||||
import { Tags } from "element/tags";
|
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
|
import { StatePill } from "./state-pill";
|
||||||
|
import { StreamState } from "@/index";
|
||||||
|
import { findTag, getHost } from "@/utils";
|
||||||
|
import { formatSats } from "@/number";
|
||||||
|
import ZapStream from "/zap-stream.svg";
|
||||||
|
import { isContentWarningAccepted } from "./content-warning";
|
||||||
|
import { Tags } from "./tags";
|
||||||
|
|
||||||
export function VideoTile({
|
export function VideoTile({
|
||||||
ev,
|
ev,
|
||||||
showAuthor = true,
|
showAuthor = true,
|
||||||
@ -42,7 +43,7 @@ export function VideoTile({
|
|||||||
{showStatus && <StatePill state={status as StreamState} />}
|
{showStatus && <StatePill state={status as StreamState} />}
|
||||||
{viewers && (
|
{viewers && (
|
||||||
<span className="pill viewers">
|
<span className="pill viewers">
|
||||||
<FormattedMessage defaultMessage="{n} viewers" values={{ n: formatSats(Number(viewers)) }} />
|
<FormattedMessage defaultMessage="{n} viewers" id="3adEeb" values={{ n: formatSats(Number(viewers)) }} />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { NostrLink, EventKind } from "@snort/system";
|
import { EventKind, NostrLink } from "@snort/system";
|
||||||
import React, { useContext, useRef, useState } from "react";
|
import React, { useContext, useRef, useState } from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import { useLogin } from "hooks/login";
|
|
||||||
import AsyncButton from "element/async-button";
|
|
||||||
import { Icon } from "element/icon";
|
|
||||||
import { Textarea } from "element/textarea";
|
|
||||||
import { EmojiPicker } from "element/emoji-picker";
|
|
||||||
import type { EmojiPack, Emoji } from "types";
|
|
||||||
import { LIVE_STREAM_CHAT } from "const";
|
|
||||||
import { SnortContext } from "@snort/system-react";
|
import { SnortContext } from "@snort/system-react";
|
||||||
import { unixNowMs } from "@snort/shared";
|
import { unixNowMs } from "@snort/shared";
|
||||||
import { TimeSync } from "index";
|
|
||||||
|
import { useLogin } from "@/hooks/login";
|
||||||
|
import AsyncButton from "./async-button";
|
||||||
|
import { Icon } from "./icon";
|
||||||
|
import { Textarea } from "./textarea";
|
||||||
|
import { EmojiPicker } from "./emoji-picker";
|
||||||
|
import type { Emoji, EmojiPack } from "@/types";
|
||||||
|
import { LIVE_STREAM_CHAT } from "@/const";
|
||||||
|
import { TimeSync } from "@/index";
|
||||||
|
|
||||||
export function WriteMessage({ link, emojiPacks }: { link: NostrLink; emojiPacks: EmojiPack[] }) {
|
export function WriteMessage({ link, emojiPacks }: { link: NostrLink; emojiPacks: EmojiPack[] }) {
|
||||||
const system = useContext(SnortContext);
|
const system = useContext(SnortContext);
|
||||||
@ -99,7 +99,7 @@ export function WriteMessage({ link, emojiPacks }: { link: NostrLink; emojiPacks
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<AsyncButton onClick={sendChatMessage} className="btn btn-border">
|
<AsyncButton onClick={sendChatMessage} className="btn btn-border">
|
||||||
<FormattedMessage defaultMessage="Send" />
|
<FormattedMessage defaultMessage="Send" id="9WRlF4" />
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
import { TaggedNostrEvent, EventKind, NoteCollection, RequestBuilder } from "@snort/system";
|
import { EventKind, NoteCollection, RequestBuilder, TaggedNostrEvent } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
|
|
||||||
import { findTag, toAddress, getTagValues } from "utils";
|
import { findTag, getTagValues, toAddress } from "@/utils";
|
||||||
import type { Badge } from "types";
|
import type { Badge } from "@/types";
|
||||||
|
|
||||||
export function useBadges(
|
export function useBadges(
|
||||||
pubkey: string,
|
pubkey: string,
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
import { TaggedNostrEvent, ReplaceableNoteStore, NoteCollection, RequestBuilder } from "@snort/system";
|
import { NoteCollection, ReplaceableNoteStore, RequestBuilder, TaggedNostrEvent } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
|
|
||||||
import { USER_CARDS, CARD } from "const";
|
import { CARD, USER_CARDS } from "@/const";
|
||||||
import { findTag } from "utils";
|
import { findTag } from "@/utils";
|
||||||
|
|
||||||
export function useUserCards(pubkey: string, userCards: Array<string[]>, leaveOpen = false): TaggedNostrEvent[] {
|
export function useUserCards(pubkey: string, userCards: Array<string[]>, leaveOpen = false): TaggedNostrEvent[] {
|
||||||
const related = useMemo(() => {
|
const related = useMemo(() => {
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { unwrap } from "@snort/shared";
|
import { unwrap } from "@snort/shared";
|
||||||
import { NostrEvent, NostrLink, NostrPrefix, NoteCollection, RequestBuilder, TaggedNostrEvent } from "@snort/system";
|
import { NostrEvent, NostrLink, NostrPrefix, NoteCollection, RequestBuilder, TaggedNostrEvent } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
import { LIVE_STREAM } from "const";
|
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
import { LIVE_STREAM } from "@/const";
|
||||||
|
|
||||||
export function useCurrentStreamFeed(link: NostrLink, leaveOpen = false, evPreload?: NostrEvent) {
|
export function useCurrentStreamFeed(link: NostrLink, leaveOpen = false, evPreload?: NostrEvent) {
|
||||||
const author = link.type === NostrPrefix.Address ? unwrap(link.author) : link.id;
|
const author = link.type === NostrPrefix.Address ? unwrap(link.author) : link.id;
|
||||||
const sub = useMemo(() => {
|
const sub = useMemo(() => {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import uniqBy from "lodash.uniqby";
|
import uniqBy from "lodash.uniqby";
|
||||||
|
|
||||||
import { RequestBuilder, ReplaceableNoteStore, NoteCollection, NostrEvent } from "@snort/system";
|
import { NostrEvent, NoteCollection, ReplaceableNoteStore, RequestBuilder } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
import { findTag } from "utils";
|
import { findTag } from "@/utils";
|
||||||
import { EMOJI_PACK, USER_EMOJIS } from "const";
|
import { EMOJI_PACK, USER_EMOJIS } from "@/const";
|
||||||
import type { EmojiPack, Tags, EmojiTag } from "types";
|
import type { EmojiPack, EmojiTag, Tags } from "@/types";
|
||||||
|
|
||||||
function cleanShortcode(shortcode?: string) {
|
function cleanShortcode(shortcode?: string) {
|
||||||
return shortcode?.replace(/\s+/g, "_").replace(/_$/, "");
|
return shortcode?.replace(/\s+/g, "_").replace(/_$/, "");
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { NostrPrefix, RequestBuilder, ReplaceableNoteStore, NostrLink } from "@snort/system";
|
import { NostrLink, NostrPrefix, ReplaceableNoteStore, RequestBuilder } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
|
|
||||||
export default function useEventFeed(link: NostrLink, leaveOpen = false) {
|
export default function useEventFeed(link: NostrLink, leaveOpen = false) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
import { NostrPrefix, ReplaceableNoteStore, RequestBuilder, type NostrLink } from "@snort/system";
|
import { type NostrLink, NostrPrefix, ReplaceableNoteStore, RequestBuilder } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
|
|
||||||
export function useAddress(kind: number, pubkey: string, identifier: string) {
|
export function useAddress(kind: number, pubkey: string, identifier: string) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { RequestBuilder, FlatNoteStore, ReplaceableNoteStore } from "@snort/system";
|
import { FlatNoteStore, ReplaceableNoteStore, RequestBuilder } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
import { GOAL } from "const";
|
import { GOAL } from "@/const";
|
||||||
|
|
||||||
export function useZapGoal(id?: string) {
|
export function useZapGoal(id?: string) {
|
||||||
const sub = useMemo(() => {
|
const sub = useMemo(() => {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
import { RequestBuilder, ReplaceableNoteStore } from "@snort/system";
|
import { ReplaceableNoteStore, RequestBuilder } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
|
|
||||||
import { MUTED } from "const";
|
import { MUTED } from "@/const";
|
||||||
import { getTagValues } from "utils";
|
import { getTagValues } from "@/utils";
|
||||||
|
|
||||||
export function useMutedPubkeys(host?: string, leaveOpen = false) {
|
export function useMutedPubkeys(host?: string, leaveOpen = false) {
|
||||||
const mutedSub = useMemo(() => {
|
const mutedSub = useMemo(() => {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { NostrLink, RequestBuilder, NoteCollection } from "@snort/system";
|
import { NostrLink, NoteCollection, RequestBuilder } from "@snort/system";
|
||||||
import { useReactions, useRequestBuilder } from "@snort/system-react";
|
import { useReactions, useRequestBuilder } from "@snort/system-react";
|
||||||
import { unixNow } from "@snort/shared";
|
import { unixNow } from "@snort/shared";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { LIVE_STREAM_CHAT, WEEK } from "const";
|
import { LIVE_STREAM_CHAT, WEEK } from "@/const";
|
||||||
|
|
||||||
export function useLiveChatFeed(link?: NostrLink, eZaps?: Array<string>, limit = 100) {
|
export function useLiveChatFeed(link?: NostrLink, eZaps?: Array<string>, limit = 100) {
|
||||||
const since = useMemo(() => unixNow() - WEEK, [link?.id]);
|
const since = useMemo(() => unixNow() - WEEK, [link?.id]);
|
||||||
|
@ -4,10 +4,10 @@ import { NostrEvent, NoteCollection, RequestBuilder } from "@snort/system";
|
|||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
|
|
||||||
import { unixNow } from "@snort/shared";
|
import { unixNow } from "@snort/shared";
|
||||||
import { LIVE_STREAM } from "const";
|
import { LIVE_STREAM } from "@/const";
|
||||||
import { StreamState } from "index";
|
import { StreamState } from "@/index";
|
||||||
import { findTag, getHost } from "utils";
|
import { findTag, getHost } from "@/utils";
|
||||||
import { WEEK } from "const";
|
import { WEEK } from "@/const";
|
||||||
|
|
||||||
export function useStreamsFeed(tag?: string) {
|
export function useStreamsFeed(tag?: string) {
|
||||||
const since = useMemo(() => unixNow() - WEEK, [tag]);
|
const since = useMemo(() => unixNow() - WEEK, [tag]);
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { useSyncExternalStore, useMemo, useState, useEffect } from "react";
|
import { useEffect, useMemo, useState, useSyncExternalStore } from "react";
|
||||||
|
|
||||||
import { EventKind, NoteCollection, RequestBuilder } from "@snort/system";
|
import { EventKind, NoteCollection, RequestBuilder } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
|
|
||||||
import { useUserEmojiPacks } from "hooks/emoji";
|
import { useUserEmojiPacks } from "@/hooks/emoji";
|
||||||
import { MUTED, USER_CARDS, USER_EMOJIS } from "const";
|
import { MUTED, USER_CARDS, USER_EMOJIS } from "@/const";
|
||||||
import type { Tags } from "types";
|
import type { Tags } from "@/types";
|
||||||
import { getPublisher } from "login";
|
import { getPublisher } from "@/login";
|
||||||
import { Login } from "index";
|
import { Login } from "@/index";
|
||||||
|
|
||||||
export function useLogin() {
|
export function useLogin() {
|
||||||
const session = useSyncExternalStore(
|
const session = useSyncExternalStore(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { RequestBuilder, NoteCollection, NostrLink } from "@snort/system";
|
import { NostrLink, NoteCollection, RequestBuilder } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
import { LIVE_STREAM } from "const";
|
import { LIVE_STREAM } from "@/const";
|
||||||
import { useZaps } from "./zaps";
|
import { useZaps } from "./zaps";
|
||||||
|
|
||||||
export function useProfile(link: NostrLink, leaveOpen = false) {
|
export function useProfile(link: NostrLink, leaveOpen = false) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { fetchNip05Pubkey } from "@snort/shared";
|
import { fetchNip05Pubkey } from "@snort/shared";
|
||||||
import { NostrLink, tryParseNostrLink, NostrPrefix } from "@snort/system";
|
import { NostrLink, NostrPrefix, tryParseNostrLink } from "@snort/system";
|
||||||
import { useState, useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
export function useStreamLink() {
|
export function useStreamLink() {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { StreamProviderStore } from "providers";
|
import { StreamProviderStore } from "@/providers";
|
||||||
import { useSyncExternalStore } from "react";
|
import { useSyncExternalStore } from "react";
|
||||||
|
|
||||||
export function useStreamProvider() {
|
export function useStreamProvider() {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { NostrLink, RequestBuilder, EventKind, NoteCollection, parseZap } from "@snort/system";
|
import { EventKind, NostrLink, NoteCollection, RequestBuilder, parseZap } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
|
|
||||||
export function useZaps(link?: NostrLink, leaveOpen = false) {
|
export function useZaps(link?: NostrLink, leaveOpen = false) {
|
||||||
|
@ -8,24 +8,24 @@ import { NostrSystem } from "@snort/system";
|
|||||||
import { SnortContext } from "@snort/system-react";
|
import { SnortContext } from "@snort/system-react";
|
||||||
import { SnortSystemDb } from "@snort/system-web";
|
import { SnortSystemDb } from "@snort/system-web";
|
||||||
import { RouterProvider, createBrowserRouter } from "react-router-dom";
|
import { RouterProvider, createBrowserRouter } from "react-router-dom";
|
||||||
|
|
||||||
import { RootPage } from "pages/root";
|
|
||||||
import { TagPage } from "pages/tag";
|
|
||||||
import { LayoutPage } from "pages/layout";
|
|
||||||
import { ProfilePage } from "pages/profile-page";
|
|
||||||
import { StreamPageHandler } from "pages/stream-page";
|
|
||||||
import { ChatPopout } from "pages/chat-popout";
|
|
||||||
import { LoginStore } from "login";
|
|
||||||
import { StreamProvidersPage } from "pages/providers";
|
|
||||||
import { defaultRelays } from "const";
|
|
||||||
import { CatchAllRoutePage } from "pages/catch-all";
|
|
||||||
import { SettingsPage } from "pages/settings-page";
|
|
||||||
import { register } from "serviceWorker";
|
|
||||||
import { IntlProvider } from "intl";
|
|
||||||
import { WidgetsPage } from "pages/widgets";
|
|
||||||
import { AlertsPage } from "pages/alerts";
|
|
||||||
import { unixNowMs } from "@snort/shared";
|
import { unixNowMs } from "@snort/shared";
|
||||||
import { StreamSummaryPage } from "pages/summary";
|
|
||||||
|
import { RootPage } from "@/pages/root";
|
||||||
|
import { TagPage } from "@/pages/tag";
|
||||||
|
import { LayoutPage } from "@/pages/layout";
|
||||||
|
import { ProfilePage } from "@/pages/profile-page";
|
||||||
|
import { StreamPageHandler } from "@/pages/stream-page";
|
||||||
|
import { ChatPopout } from "@/pages/chat-popout";
|
||||||
|
import { LoginStore } from "@/login";
|
||||||
|
import { StreamProvidersPage } from "@/pages/providers";
|
||||||
|
import { defaultRelays } from "@/const";
|
||||||
|
import { CatchAllRoutePage } from "@/pages/catch-all";
|
||||||
|
import { SettingsPage } from "@/pages/settings-page";
|
||||||
|
import { register } from "@/serviceWorker";
|
||||||
|
import { IntlProvider } from "@/intl";
|
||||||
|
import { WidgetsPage } from "@/pages/widgets";
|
||||||
|
import { AlertsPage } from "@/pages/alerts";
|
||||||
|
import { StreamSummaryPage } from "@/pages/summary";
|
||||||
|
|
||||||
export enum StreamState {
|
export enum StreamState {
|
||||||
Live = "live",
|
Live = "live",
|
||||||
|
53
src/intl.tsx
53
src/intl.tsx
@ -1,15 +1,8 @@
|
|||||||
import { DefaultLocale, useLang } from "hooks/lang";
|
import { DefaultLocale, useLang } from "@/hooks/lang";
|
||||||
import { useEffect, useState, type ReactNode } from "react";
|
import { type ReactNode, useEffect, useState } from "react";
|
||||||
import { IntlProvider as ReactIntlProvider } from "react-intl";
|
import { IntlProvider as ReactIntlProvider } from "react-intl";
|
||||||
|
|
||||||
import enMessages from "translations/en.json";
|
import enMessages from "@/translations/en.json";
|
||||||
|
|
||||||
async function importLang(code: string) {
|
|
||||||
const src = await import(`translations/${code}.json`);
|
|
||||||
const typed = src.default as Record<string, { defaultMessage: string }>;
|
|
||||||
const ent = Object.entries(typed).map(([k, v]) => [k, v.defaultMessage]);
|
|
||||||
return Object.fromEntries(ent) as Record<string, string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AllLocales = [
|
export const AllLocales = [
|
||||||
DefaultLocale,
|
DefaultLocale,
|
||||||
@ -29,7 +22,13 @@ export const AllLocales = [
|
|||||||
"fr-FR",
|
"fr-FR",
|
||||||
"pt-BR",
|
"pt-BR",
|
||||||
"ru-RU",
|
"ru-RU",
|
||||||
];
|
] as const;
|
||||||
|
|
||||||
|
function importLang(src: any) {
|
||||||
|
const typed = src.default as Record<string, { defaultMessage: string }>;
|
||||||
|
const ent = Object.entries(typed).map(([k, v]) => [k, v.defaultMessage]);
|
||||||
|
return Object.fromEntries(ent) as Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
const getMessages = (locale: string) => {
|
const getMessages = (locale: string) => {
|
||||||
const truncatedLocale = locale.toLowerCase().split(/[_-]+/)[0];
|
const truncatedLocale = locale.toLowerCase().split(/[_-]+/)[0];
|
||||||
@ -38,50 +37,50 @@ const getMessages = (locale: string) => {
|
|||||||
switch (lng) {
|
switch (lng) {
|
||||||
case "de":
|
case "de":
|
||||||
case "de-DE":
|
case "de-DE":
|
||||||
return await importLang("de_DE");
|
return importLang(await import("@/translations/de_DE.json"));
|
||||||
case "es":
|
case "es":
|
||||||
case "es-ES":
|
case "es-ES":
|
||||||
return await importLang("es_ES");
|
return importLang(await import("@/translations/es_ES.json"));
|
||||||
case "th":
|
case "th":
|
||||||
case "th-TH":
|
case "th-TH":
|
||||||
return await importLang("th_TH");
|
return importLang(await import("@/translations/th_TH.json"));
|
||||||
case "nl":
|
case "nl":
|
||||||
case "nl-NL":
|
case "nl-NL":
|
||||||
return await importLang("nl_NL");
|
return importLang(await import("@/translations/nl_NL.json"));
|
||||||
case "ja":
|
case "ja":
|
||||||
case "ja-JP":
|
case "ja-JP":
|
||||||
return await importLang("ja_JP");
|
return importLang(await import("@/translations/ja_JP.json"));
|
||||||
case "fa":
|
case "fa":
|
||||||
case "fa-IR":
|
case "fa-IR":
|
||||||
return await importLang("fa_IR");
|
return importLang(await import("@/translations/fa_IR.json"));
|
||||||
case "sw":
|
case "sw":
|
||||||
case "sw-KE":
|
case "sw-KE":
|
||||||
return await importLang("sw_KE");
|
return importLang(await import("@/translations/sw_KE.json"));
|
||||||
case "sv":
|
case "sv":
|
||||||
case "sv-SE":
|
case "sv-SE":
|
||||||
return await importLang("sv_SE");
|
return importLang(await import("@/translations/sv_SE.json"));
|
||||||
case "bn":
|
case "bn":
|
||||||
case "bn-BD":
|
case "bn-BD":
|
||||||
return await importLang("bn_BD");
|
return importLang(await import("@/translations/bn_BD.json"));
|
||||||
case "bg":
|
case "bg":
|
||||||
case "bg-BG":
|
case "bg-BG":
|
||||||
return await importLang("bg_BG");
|
return importLang(await import("@/translations/bg_BG.json"));
|
||||||
case "zh":
|
case "zh":
|
||||||
case "zh-CN":
|
case "zh-CN":
|
||||||
return await importLang("zh_CN");
|
return importLang(await import("@/translations/zh_CN.json"));
|
||||||
case "zh-TW":
|
case "zh-TW":
|
||||||
return await importLang("zh_TW");
|
return importLang(await import("@/translations/zh_TW.json"));
|
||||||
case "fi":
|
case "fi":
|
||||||
case "fi-FI":
|
case "fi-FI":
|
||||||
return await importLang("fi_FI");
|
return importLang(await import("@/translations/fi_FI.json"));
|
||||||
case "fr":
|
case "fr":
|
||||||
case "fr-FR":
|
case "fr-FR":
|
||||||
return await importLang("fr_FR");
|
return importLang(await import("@/translations/fr_FR.json"));
|
||||||
case "pt-BR":
|
case "pt-BR":
|
||||||
return await importLang("pt_BR");
|
return importLang(await import("@/translations/pt_BR.json"));
|
||||||
case "ru":
|
case "ru":
|
||||||
case "ru-RU":
|
case "ru-RU":
|
||||||
return await importLang("ru_RU");
|
return importLang(await import("@/translations/ru_RU.json"));
|
||||||
case DefaultLocale:
|
case DefaultLocale:
|
||||||
case "en":
|
case "en":
|
||||||
return enMessages;
|
return enMessages;
|
||||||
|
@ -26,6 +26,9 @@
|
|||||||
"0VV/sK": {
|
"0VV/sK": {
|
||||||
"defaultMessage": "Goal"
|
"defaultMessage": "Goal"
|
||||||
},
|
},
|
||||||
|
"0hNxBy": {
|
||||||
|
"defaultMessage": "Starts"
|
||||||
|
},
|
||||||
"1EYCdR": {
|
"1EYCdR": {
|
||||||
"defaultMessage": "Tags"
|
"defaultMessage": "Tags"
|
||||||
},
|
},
|
||||||
@ -74,6 +77,9 @@
|
|||||||
"6pr6hJ": {
|
"6pr6hJ": {
|
||||||
"defaultMessage": "Minimum amount for text to speech"
|
"defaultMessage": "Minimum amount for text to speech"
|
||||||
},
|
},
|
||||||
|
"79lLl+": {
|
||||||
|
"defaultMessage": "Music"
|
||||||
|
},
|
||||||
"8YT6ja": {
|
"8YT6ja": {
|
||||||
"defaultMessage": "Insert text to speak"
|
"defaultMessage": "Insert text to speak"
|
||||||
},
|
},
|
||||||
@ -83,9 +89,6 @@
|
|||||||
"9a9+ww": {
|
"9a9+ww": {
|
||||||
"defaultMessage": "Title"
|
"defaultMessage": "Title"
|
||||||
},
|
},
|
||||||
"9anxhq": {
|
|
||||||
"defaultMessage": "Starts"
|
|
||||||
},
|
|
||||||
"AIHaPH": {
|
"AIHaPH": {
|
||||||
"defaultMessage": "{person} zapped {amount} sats"
|
"defaultMessage": "{person} zapped {amount} sats"
|
||||||
},
|
},
|
||||||
@ -107,6 +110,9 @@
|
|||||||
"C81/uG": {
|
"C81/uG": {
|
||||||
"defaultMessage": "Logout"
|
"defaultMessage": "Logout"
|
||||||
},
|
},
|
||||||
|
"CsCUYo": {
|
||||||
|
"defaultMessage": "{n} sats"
|
||||||
|
},
|
||||||
"D3idYv": {
|
"D3idYv": {
|
||||||
"defaultMessage": "Settings"
|
"defaultMessage": "Settings"
|
||||||
},
|
},
|
||||||
@ -128,6 +134,9 @@
|
|||||||
"G/yZLu": {
|
"G/yZLu": {
|
||||||
"defaultMessage": "Remove"
|
"defaultMessage": "Remove"
|
||||||
},
|
},
|
||||||
|
"GGaJMU": {
|
||||||
|
"defaultMessage": "Top Chatters"
|
||||||
|
},
|
||||||
"Gq6x9o": {
|
"Gq6x9o": {
|
||||||
"defaultMessage": "Cover Image"
|
"defaultMessage": "Cover Image"
|
||||||
},
|
},
|
||||||
@ -152,6 +161,9 @@
|
|||||||
"INlWvJ": {
|
"INlWvJ": {
|
||||||
"defaultMessage": "OR"
|
"defaultMessage": "OR"
|
||||||
},
|
},
|
||||||
|
"J/+m9y": {
|
||||||
|
"defaultMessage": "Stream Duration {duration} mins"
|
||||||
|
},
|
||||||
"JEsxDw": {
|
"JEsxDw": {
|
||||||
"defaultMessage": "Uploading..."
|
"defaultMessage": "Uploading..."
|
||||||
},
|
},
|
||||||
@ -239,6 +251,9 @@
|
|||||||
"X2PZ7D": {
|
"X2PZ7D": {
|
||||||
"defaultMessage": "Create Goal"
|
"defaultMessage": "Create Goal"
|
||||||
},
|
},
|
||||||
|
"XgWvGA": {
|
||||||
|
"defaultMessage": "Reactions"
|
||||||
|
},
|
||||||
"YPh5Nq": {
|
"YPh5Nq": {
|
||||||
"defaultMessage": "@ {rate}",
|
"defaultMessage": "@ {rate}",
|
||||||
"description": "Showing zap amount in USD @ rate"
|
"description": "Showing zap amount in USD @ rate"
|
||||||
@ -282,9 +297,15 @@
|
|||||||
"feZ/kG": {
|
"feZ/kG": {
|
||||||
"defaultMessage": "Login with Private Key (insecure)"
|
"defaultMessage": "Login with Private Key (insecure)"
|
||||||
},
|
},
|
||||||
|
"gzsn7k": {
|
||||||
|
"defaultMessage": "{n} messages"
|
||||||
|
},
|
||||||
"hGQqkW": {
|
"hGQqkW": {
|
||||||
"defaultMessage": "Schedule"
|
"defaultMessage": "Schedule"
|
||||||
},
|
},
|
||||||
|
"hMzcSq": {
|
||||||
|
"defaultMessage": "Messages"
|
||||||
|
},
|
||||||
"heyxZL": {
|
"heyxZL": {
|
||||||
"defaultMessage": "Enable text to speech"
|
"defaultMessage": "Enable text to speech"
|
||||||
},
|
},
|
||||||
@ -300,6 +321,9 @@
|
|||||||
"izWS4J": {
|
"izWS4J": {
|
||||||
"defaultMessage": "Unfollow"
|
"defaultMessage": "Unfollow"
|
||||||
},
|
},
|
||||||
|
"jgOqxt": {
|
||||||
|
"defaultMessage": "Widgets"
|
||||||
|
},
|
||||||
"jr4+vD": {
|
"jr4+vD": {
|
||||||
"defaultMessage": "Markdown"
|
"defaultMessage": "Markdown"
|
||||||
},
|
},
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import "./alerts.css";
|
import "./alerts.css";
|
||||||
import Spinner from "element/spinner";
|
|
||||||
import { useStreamLink } from "hooks/stream-link";
|
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
|
import Spinner from "@/element/spinner";
|
||||||
|
import { useStreamLink } from "@/hooks/stream-link";
|
||||||
import { ZapAlerts } from "./widgets/zaps";
|
import { ZapAlerts } from "./widgets/zaps";
|
||||||
import { Views } from "./widgets/views";
|
import { Views } from "./widgets/views";
|
||||||
import { TopZappersWidget } from "./widgets/top-zappers";
|
import { TopZappersWidget } from "./widgets/top-zappers";
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import "./chat-popout.css";
|
import "./chat-popout.css";
|
||||||
import { LiveChat } from "element/live-chat";
|
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { NostrPrefix, encodeTLV, parseNostrLink } from "@snort/system";
|
import { NostrPrefix, encodeTLV, parseNostrLink } from "@snort/system";
|
||||||
import { unwrap } from "@snort/shared";
|
import { unwrap } from "@snort/shared";
|
||||||
import { useCurrentStreamFeed } from "hooks/current-stream-feed";
|
|
||||||
import { findTag } from "utils";
|
import { LiveChat } from "@/element/live-chat";
|
||||||
import { useZapGoal } from "hooks/goals";
|
import { useCurrentStreamFeed } from "@/hooks/current-stream-feed";
|
||||||
|
import { findTag } from "@/utils";
|
||||||
|
import { useZapGoal } from "@/hooks/goals";
|
||||||
|
|
||||||
export function ChatPopout() {
|
export function ChatPopout() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
@ -34,7 +34,7 @@ header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
header .logo {
|
header .logo {
|
||||||
background: url("public/logo.png") no-repeat #171717;
|
background: url("/logo.png") no-repeat #171717;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
width: 48px;
|
width: 48px;
|
||||||
@ -148,7 +148,7 @@ header .profile img {
|
|||||||
|
|
||||||
.age-check::after {
|
.age-check::after {
|
||||||
content: " ";
|
content: " ";
|
||||||
background: url("public/zap-stream.svg") no-repeat;
|
background: url("/zap-stream.svg") no-repeat;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -8,14 +8,14 @@ import { FormattedMessage } from "react-intl";
|
|||||||
import { Menu, MenuItem } from "@szhsin/react-menu";
|
import { Menu, MenuItem } from "@szhsin/react-menu";
|
||||||
import { hexToBech32 } from "@snort/shared";
|
import { hexToBech32 } from "@snort/shared";
|
||||||
|
|
||||||
import { Icon } from "element/icon";
|
import { Icon } from "@/element/icon";
|
||||||
import { useLogin, useLoginEvents } from "hooks/login";
|
import { useLogin, useLoginEvents } from "@/hooks/login";
|
||||||
import { Profile } from "element/profile";
|
import { Profile } from "@/element/profile";
|
||||||
import { NewStreamDialog } from "element/new-stream";
|
import { NewStreamDialog } from "@/element/new-stream";
|
||||||
import { LoginSignup } from "element/login-signup";
|
import { LoginSignup } from "@/element/login-signup";
|
||||||
import { Login } from "index";
|
import { Login } from "@/index";
|
||||||
import { useLang } from "hooks/lang";
|
import { useLang } from "@/hooks/lang";
|
||||||
import { AllLocales } from "intl";
|
import { AllLocales } from "@/intl";
|
||||||
|
|
||||||
export function LayoutPage() {
|
export function LayoutPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -76,19 +76,19 @@ export function LayoutPage() {
|
|||||||
gap={5}>
|
gap={5}>
|
||||||
<MenuItem onClick={() => navigate(`/p/${hexToBech32("npub", login.pubkey)}`)}>
|
<MenuItem onClick={() => navigate(`/p/${hexToBech32("npub", login.pubkey)}`)}>
|
||||||
<Icon name="user" size={24} />
|
<Icon name="user" size={24} />
|
||||||
<FormattedMessage defaultMessage="Profile" />
|
<FormattedMessage defaultMessage="Profile" id="itPgxd" />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem onClick={() => navigate("/settings")}>
|
<MenuItem onClick={() => navigate("/settings")}>
|
||||||
<Icon name="settings" size={24} />
|
<Icon name="settings" size={24} />
|
||||||
<FormattedMessage defaultMessage="Settings" />
|
<FormattedMessage defaultMessage="Settings" id="D3idYv" />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem onClick={() => navigate("/widgets")}>
|
<MenuItem onClick={() => navigate("/widgets")}>
|
||||||
<Icon name="widget" size={24} />
|
<Icon name="widget" size={24} />
|
||||||
<FormattedMessage defaultMessage="Widgets" />
|
<FormattedMessage defaultMessage="Widgets" id="jgOqxt" />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem onClick={() => Login.logout()}>
|
<MenuItem onClick={() => Login.logout()}>
|
||||||
<Icon name="logout" size={24} />
|
<Icon name="logout" size={24} />
|
||||||
<FormattedMessage defaultMessage="Logout" />
|
<FormattedMessage defaultMessage="Logout" id="C81/uG" />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
</>
|
</>
|
||||||
@ -105,7 +105,7 @@ export function LayoutPage() {
|
|||||||
return (
|
return (
|
||||||
<Dialog.Root open={showLogin} onOpenChange={setShowLogin}>
|
<Dialog.Root open={showLogin} onOpenChange={setShowLogin}>
|
||||||
<button type="button" className="btn btn-border" onClick={handleLogin}>
|
<button type="button" className="btn btn-border" onClick={handleLogin}>
|
||||||
<FormattedMessage defaultMessage="Login" />
|
<FormattedMessage defaultMessage="Login" id="AyGauy" />
|
||||||
<Icon name="login" />
|
<Icon name="login" />
|
||||||
</button>
|
</button>
|
||||||
<Dialog.Portal>
|
<Dialog.Portal>
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
import "./profile-page.css";
|
import "./profile-page.css";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import moment from "moment";
|
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import * as Tabs from "@radix-ui/react-tabs";
|
import * as Tabs from "@radix-ui/react-tabs";
|
||||||
import { parseNostrLink, NostrPrefix, ParsedZap, encodeTLV } from "@snort/system";
|
import { NostrPrefix, ParsedZap, encodeTLV, parseNostrLink } from "@snort/system";
|
||||||
import { useUserProfile } from "@snort/system-react";
|
import { useUserProfile } from "@snort/system-react";
|
||||||
import { unwrap } from "@snort/shared";
|
import { unwrap } from "@snort/shared";
|
||||||
import { Profile } from "element/profile";
|
|
||||||
import { Icon } from "element/icon";
|
|
||||||
import { SendZapsDialog } from "element/send-zap";
|
|
||||||
import { VideoTile } from "element/video-tile";
|
|
||||||
import { FollowButton } from "element/follow-button";
|
|
||||||
import { MuteButton } from "element/mute-button";
|
|
||||||
import { useProfile } from "hooks/profile";
|
|
||||||
import useTopZappers from "hooks/top-zappers";
|
|
||||||
import usePlaceholder from "hooks/placeholders";
|
|
||||||
import { Text } from "element/text";
|
|
||||||
import { StreamState } from "index";
|
|
||||||
import { findTag } from "utils";
|
|
||||||
import { formatSats } from "number";
|
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
|
import { Profile } from "@/element/profile";
|
||||||
|
import { Icon } from "@/element/icon";
|
||||||
|
import { SendZapsDialog } from "@/element/send-zap";
|
||||||
|
import { VideoTile } from "@/element/video-tile";
|
||||||
|
import { FollowButton } from "@/element/follow-button";
|
||||||
|
import { MuteButton } from "@/element/mute-button";
|
||||||
|
import { useProfile } from "@/hooks/profile";
|
||||||
|
import useTopZappers from "@/hooks/top-zappers";
|
||||||
|
import usePlaceholder from "@/hooks/placeholders";
|
||||||
|
import { Text } from "@/element/text";
|
||||||
|
import { StreamState } from "@/index";
|
||||||
|
import { findTag } from "@/utils";
|
||||||
|
import { formatSats } from "@/number";
|
||||||
|
|
||||||
function Zapper({ pubkey, total }: { pubkey: string; total: number }) {
|
function Zapper({ pubkey, total }: { pubkey: string; total: number }) {
|
||||||
return (
|
return (
|
||||||
<div className="zapper">
|
<div className="zapper">
|
||||||
@ -92,12 +92,12 @@ export function ProfilePage() {
|
|||||||
<div className="live-button pill live" onClick={goToLive}>
|
<div className="live-button pill live" onClick={goToLive}>
|
||||||
<Icon name="signal" />
|
<Icon name="signal" />
|
||||||
<span>
|
<span>
|
||||||
<FormattedMessage defaultMessage="live" />
|
<FormattedMessage defaultMessage="live" id="2CGh/0" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<span className="pill offline">
|
<span className="pill offline">
|
||||||
<FormattedMessage defaultMessage="offline" />
|
<FormattedMessage defaultMessage="offline" id="K3uH1C" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -111,7 +111,7 @@ export function ProfilePage() {
|
|||||||
<div className="zap-button">
|
<div className="zap-button">
|
||||||
<Icon name="zap-filled" className="zap-button-icon" />
|
<Icon name="zap-filled" className="zap-button-icon" />
|
||||||
<span>
|
<span>
|
||||||
<FormattedMessage defaultMessage="Zap" />
|
<FormattedMessage defaultMessage="Zap" id="fBI91o" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
@ -133,15 +133,15 @@ export function ProfilePage() {
|
|||||||
<Tabs.Root className="tabs-root" defaultValue="top-zappers">
|
<Tabs.Root className="tabs-root" defaultValue="top-zappers">
|
||||||
<Tabs.List className="tabs-list" aria-label={`Information about ${profile ? profile.name : link.id}`}>
|
<Tabs.List className="tabs-list" aria-label={`Information about ${profile ? profile.name : link.id}`}>
|
||||||
<Tabs.Trigger className="tabs-tab" value="top-zappers">
|
<Tabs.Trigger className="tabs-tab" value="top-zappers">
|
||||||
<FormattedMessage defaultMessage="Top Zappers" />
|
<FormattedMessage defaultMessage="Top Zappers" id="dVD/AR" />
|
||||||
<div className="tab-border"></div>
|
<div className="tab-border"></div>
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
<Tabs.Trigger className="tabs-tab" value="past-streams">
|
<Tabs.Trigger className="tabs-tab" value="past-streams">
|
||||||
<FormattedMessage defaultMessage="Past Streams" />
|
<FormattedMessage defaultMessage="Past Streams" id="UfSot5" />
|
||||||
<div className="tab-border"></div>
|
<div className="tab-border"></div>
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
<Tabs.Trigger className="tabs-tab" value="schedule">
|
<Tabs.Trigger className="tabs-tab" value="schedule">
|
||||||
<FormattedMessage defaultMessage="Schedule" />
|
<FormattedMessage defaultMessage="Schedule" id="hGQqkW" />
|
||||||
<div className="tab-border"></div>
|
<div className="tab-border"></div>
|
||||||
</Tabs.Trigger>
|
</Tabs.Trigger>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
@ -156,8 +156,9 @@ export function ProfilePage() {
|
|||||||
<span className="timestamp">
|
<span className="timestamp">
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Streamed on {date}"
|
defaultMessage="Streamed on {date}"
|
||||||
|
id="cvAsEh"
|
||||||
values={{
|
values={{
|
||||||
date: moment(Number(ev.created_at) * 1000).format("MMM DD, YYYY"),
|
date: new Date(ev.created_at * 1000).toLocaleDateString(),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@ -173,8 +174,9 @@ export function ProfilePage() {
|
|||||||
<span className="timestamp">
|
<span className="timestamp">
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Scheduled for {date}"
|
defaultMessage="Scheduled for {date}"
|
||||||
|
id="pO/lPX"
|
||||||
values={{
|
values={{
|
||||||
date: moment(Number(ev.created_at) * 1000).format("MMM DD, YYYY h:mm:ss a"),
|
date: new Date(ev.created_at * 1000).toLocaleDateString(),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import "./index.css";
|
import "./index.css";
|
||||||
import { StreamProviders } from "providers";
|
|
||||||
|
|
||||||
import Owncast from "owncast.png";
|
|
||||||
import Cloudflare from "cloudflare.png";
|
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
|
||||||
|
import { StreamProviders } from "@/providers";
|
||||||
|
import Owncast from "@/owncast.png";
|
||||||
|
import Cloudflare from "@/cloudflare.png";
|
||||||
import { ConfigureOwncast } from "./owncast";
|
import { ConfigureOwncast } from "./owncast";
|
||||||
import { ConfigureNostrType } from "./nostr";
|
import { ConfigureNostrType } from "./nostr";
|
||||||
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import AsyncButton from "element/async-button";
|
|
||||||
import { StatePill } from "element/state-pill";
|
|
||||||
import { StreamState } from "index";
|
|
||||||
import { StreamProviderInfo, StreamProviderStore } from "providers";
|
|
||||||
import { Nip103StreamProvider } from "providers/zsz";
|
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
|
import AsyncButton from "@/element/async-button";
|
||||||
|
import { StatePill } from "@/element/state-pill";
|
||||||
|
import { StreamState } from "@/index";
|
||||||
|
import { StreamProviderInfo, StreamProviderStore } from "@/providers";
|
||||||
|
import { Nip103StreamProvider } from "@/providers/zsz";
|
||||||
|
|
||||||
export function ConfigureNostrType() {
|
export function ConfigureNostrType() {
|
||||||
const [url, setUrl] = useState("");
|
const [url, setUrl] = useState("");
|
||||||
const [info, setInfo] = useState<StreamProviderInfo>();
|
const [info, setInfo] = useState<StreamProviderInfo>();
|
||||||
@ -61,7 +61,7 @@ export function ConfigureNostrType() {
|
|||||||
StreamProviderStore.add(new Nip103StreamProvider(new URL(url).host, url));
|
StreamProviderStore.add(new Nip103StreamProvider(new URL(url).host, url));
|
||||||
navigate("/");
|
navigate("/");
|
||||||
}}>
|
}}>
|
||||||
<FormattedMessage defaultMessage="Save" />
|
<FormattedMessage defaultMessage="Save" id="jvo0vs" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@ -78,7 +78,7 @@ export function ConfigureNostrType() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AsyncButton className="btn btn-primary" onClick={tryConnect}>
|
<AsyncButton className="btn btn-primary" onClick={tryConnect}>
|
||||||
<FormattedMessage defaultMessage="Connect" />
|
<FormattedMessage defaultMessage="Connect" id="+vVZ/G" />
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
</div>
|
</div>
|
||||||
<div>{status()}</div>
|
<div>{status()}</div>
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import AsyncButton from "element/async-button";
|
|
||||||
import { StatePill } from "element/state-pill";
|
|
||||||
import { StreamState } from "index";
|
|
||||||
import { StreamProviderInfo, StreamProviderStore } from "providers";
|
|
||||||
import { OwncastProvider } from "providers/owncast";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
import AsyncButton from "@/element/async-button";
|
||||||
|
import { StatePill } from "@/element/state-pill";
|
||||||
|
import { StreamState } from "@/index";
|
||||||
|
import { StreamProviderInfo, StreamProviderStore } from "@/providers";
|
||||||
|
import { OwncastProvider } from "@/providers/owncast";
|
||||||
|
|
||||||
export function ConfigureOwncast() {
|
export function ConfigureOwncast() {
|
||||||
const [url, setUrl] = useState("");
|
const [url, setUrl] = useState("");
|
||||||
const [token, setToken] = useState("");
|
const [token, setToken] = useState("");
|
||||||
|
@ -3,10 +3,10 @@ import { FormattedMessage } from "react-intl";
|
|||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import type { NostrEvent } from "@snort/system";
|
import type { NostrEvent } from "@snort/system";
|
||||||
|
|
||||||
import { VideoTile } from "element/video-tile";
|
import { VideoTile } from "@/element/video-tile";
|
||||||
import { useLogin } from "hooks/login";
|
import { useLogin } from "@/hooks/login";
|
||||||
import { getHost, getTagValues } from "utils";
|
import { getHost, getTagValues } from "@/utils";
|
||||||
import { useStreamsFeed } from "hooks/live-streams";
|
import { useStreamsFeed } from "@/hooks/live-streams";
|
||||||
|
|
||||||
export function RootPage() {
|
export function RootPage() {
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
@ -33,7 +33,7 @@ export function RootPage() {
|
|||||||
{hasFollowingLive && (
|
{hasFollowingLive && (
|
||||||
<>
|
<>
|
||||||
<h2 className="divider line one-line">
|
<h2 className="divider line one-line">
|
||||||
<FormattedMessage defaultMessage="Following" />
|
<FormattedMessage defaultMessage="Following" id="cPIKU2" />
|
||||||
</h2>
|
</h2>
|
||||||
<div className="video-grid">
|
<div className="video-grid">
|
||||||
{following.map(e => (
|
{following.map(e => (
|
||||||
@ -70,7 +70,7 @@ export function RootPage() {
|
|||||||
{hasFollowingLive && liveNow.length > 0 && (
|
{hasFollowingLive && liveNow.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<h2 className="divider line one-line">
|
<h2 className="divider line one-line">
|
||||||
<FormattedMessage defaultMessage="Live" />
|
<FormattedMessage defaultMessage="Live" id="Dn82AL" />
|
||||||
</h2>
|
</h2>
|
||||||
<div className="video-grid">
|
<div className="video-grid">
|
||||||
{liveNow
|
{liveNow
|
||||||
@ -84,7 +84,7 @@ export function RootPage() {
|
|||||||
{plannedEvents.length > 0 && (
|
{plannedEvents.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<h2 className="divider line one-line">
|
<h2 className="divider line one-line">
|
||||||
<FormattedMessage defaultMessage="Planned" />
|
<FormattedMessage defaultMessage="Planned" id="kp0NPF" />
|
||||||
</h2>
|
</h2>
|
||||||
<div className="video-grid">
|
<div className="video-grid">
|
||||||
{plannedEvents.map(e => (
|
{plannedEvents.map(e => (
|
||||||
@ -96,7 +96,7 @@ export function RootPage() {
|
|||||||
{endedEvents.length > 0 && (
|
{endedEvents.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<h2 className="divider line one-line">
|
<h2 className="divider line one-line">
|
||||||
<FormattedMessage defaultMessage="Ended" />
|
<FormattedMessage defaultMessage="Ended" id="TP/cMX" />
|
||||||
</h2>
|
</h2>
|
||||||
<div className="video-grid">
|
<div className="video-grid">
|
||||||
{endedEvents.map(e => (
|
{endedEvents.map(e => (
|
||||||
|
@ -5,8 +5,8 @@ import { FormattedMessage } from "react-intl";
|
|||||||
import { Button as AlbyZapsButton } from "@getalby/bitcoin-connect-react";
|
import { Button as AlbyZapsButton } from "@getalby/bitcoin-connect-react";
|
||||||
import { hexToBech32 } from "@snort/shared";
|
import { hexToBech32 } from "@snort/shared";
|
||||||
|
|
||||||
import { useLogin } from "hooks/login";
|
import { useLogin } from "@/hooks/login";
|
||||||
import Copy from "element/copy";
|
import Copy from "@/element/copy";
|
||||||
|
|
||||||
const enum Tab {
|
const enum Tab {
|
||||||
Account,
|
Account,
|
||||||
@ -30,12 +30,12 @@ export function SettingsPage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1>
|
<h1>
|
||||||
<FormattedMessage defaultMessage="Account" />
|
<FormattedMessage defaultMessage="Account" id="TwyMau" />
|
||||||
</h1>
|
</h1>
|
||||||
{login?.pubkey && (
|
{login?.pubkey && (
|
||||||
<div className="public-key">
|
<div className="public-key">
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Logged in as" />
|
<FormattedMessage defaultMessage="Logged in as" id="DZKuuP" />
|
||||||
</p>
|
</p>
|
||||||
<Copy text={hexToBech32("npub", login.pubkey)} />
|
<Copy text={hexToBech32("npub", login.pubkey)} />
|
||||||
</div>
|
</div>
|
||||||
@ -43,13 +43,13 @@ export function SettingsPage() {
|
|||||||
{login?.privateKey && (
|
{login?.privateKey && (
|
||||||
<div className="private-key">
|
<div className="private-key">
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage defaultMessage="Private key" />
|
<FormattedMessage defaultMessage="Private key" id="Bep/gA" />
|
||||||
</p>
|
</p>
|
||||||
<Copy text={hexToBech32("nsec", login.privateKey)} />
|
<Copy text={hexToBech32("nsec", login.privateKey)} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<h1>
|
<h1>
|
||||||
<FormattedMessage defaultMessage="Zaps" />
|
<FormattedMessage defaultMessage="Zaps" id="OEW7yJ" />
|
||||||
</h1>
|
</h1>
|
||||||
<AlbyZapsButton />
|
<AlbyZapsButton />
|
||||||
</>
|
</>
|
||||||
@ -61,12 +61,12 @@ export function SettingsPage() {
|
|||||||
<div className="settings-page">
|
<div className="settings-page">
|
||||||
<div className="flex f-col g48">
|
<div className="flex f-col g48">
|
||||||
<h1>
|
<h1>
|
||||||
<FormattedMessage defaultMessage="Settings" />
|
<FormattedMessage defaultMessage="Settings" id="D3idYv" />
|
||||||
</h1>
|
</h1>
|
||||||
<div className="flex g24 f-col-mobile">
|
<div className="flex g24 f-col-mobile">
|
||||||
<div className="flex f-col g24 tab-options">
|
<div className="flex f-col g24 tab-options">
|
||||||
<div onClick={() => setTab(Tab.Account)}>
|
<div onClick={() => setTab(Tab.Account)}>
|
||||||
<FormattedMessage defaultMessage="Account" />
|
<FormattedMessage defaultMessage="Account" id="TwyMau" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="tab-content">{tabContent()}</div>
|
<div className="tab-content">{tabContent()}</div>
|
||||||
|
@ -2,31 +2,31 @@ import "./stream-page.css";
|
|||||||
import { NostrLink, TaggedNostrEvent } from "@snort/system";
|
import { NostrLink, TaggedNostrEvent } from "@snort/system";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
|
|
||||||
import { LiveVideoPlayer } from "element/live-video-player";
|
|
||||||
import { findTag, getEventFromLocationState, getHost } from "utils";
|
|
||||||
import { Profile, getName } from "element/profile";
|
|
||||||
import { LiveChat } from "element/live-chat";
|
|
||||||
import AsyncButton from "element/async-button";
|
|
||||||
import { useLogin } from "hooks/login";
|
|
||||||
import { useZapGoal } from "hooks/goals";
|
|
||||||
import { StreamState } from "index";
|
|
||||||
import { SendZapsDialog } from "element/send-zap";
|
|
||||||
import { NostrEvent } from "@snort/system";
|
import { NostrEvent } from "@snort/system";
|
||||||
import { SnortContext, useUserProfile } from "@snort/system-react";
|
import { SnortContext, useUserProfile } from "@snort/system-react";
|
||||||
import { NewStreamDialog } from "element/new-stream";
|
|
||||||
import { Tags } from "element/tags";
|
|
||||||
import { StatePill } from "element/state-pill";
|
|
||||||
import { StreamCards } from "element/stream-cards";
|
|
||||||
import { formatSats } from "number";
|
|
||||||
import { StreamTimer } from "element/stream-time";
|
|
||||||
import { ShareMenu } from "element/share-menu";
|
|
||||||
import { ContentWarningOverlay, isContentWarningAccepted } from "element/content-warning";
|
|
||||||
import { useCurrentStreamFeed } from "hooks/current-stream-feed";
|
|
||||||
import { useStreamLink } from "hooks/stream-link";
|
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { FollowButton } from "element/follow-button";
|
|
||||||
|
import { LiveVideoPlayer } from "@/element/live-video-player";
|
||||||
|
import { findTag, getEventFromLocationState, getHost } from "@/utils";
|
||||||
|
import { Profile, getName } from "@/element/profile";
|
||||||
|
import { LiveChat } from "@/element/live-chat";
|
||||||
|
import AsyncButton from "@/element/async-button";
|
||||||
|
import { useLogin } from "@/hooks/login";
|
||||||
|
import { useZapGoal } from "@/hooks/goals";
|
||||||
|
import { StreamState } from "@/index";
|
||||||
|
import { SendZapsDialog } from "@/element/send-zap";
|
||||||
|
import { NewStreamDialog } from "@/element/new-stream";
|
||||||
|
import { Tags } from "@/element/tags";
|
||||||
|
import { StatePill } from "@/element/state-pill";
|
||||||
|
import { StreamCards } from "@/element/stream-cards";
|
||||||
|
import { formatSats } from "@/number";
|
||||||
|
import { StreamTimer } from "@/element/stream-time";
|
||||||
|
import { ShareMenu } from "@/element/share-menu";
|
||||||
|
import { ContentWarningOverlay, isContentWarningAccepted } from "@/element/content-warning";
|
||||||
|
import { useCurrentStreamFeed } from "@/hooks/current-stream-feed";
|
||||||
|
import { useStreamLink } from "@/hooks/stream-link";
|
||||||
|
import { FollowButton } from "@/element/follow-button";
|
||||||
|
|
||||||
function ProfileInfo({ ev, goal }: { ev?: NostrEvent; goal?: TaggedNostrEvent }) {
|
function ProfileInfo({ ev, goal }: { ev?: NostrEvent; goal?: TaggedNostrEvent }) {
|
||||||
const system = useContext(SnortContext);
|
const system = useContext(SnortContext);
|
||||||
@ -60,7 +60,7 @@ function ProfileInfo({ ev, goal }: { ev?: NostrEvent; goal?: TaggedNostrEvent })
|
|||||||
<StatePill state={status as StreamState} />
|
<StatePill state={status as StreamState} />
|
||||||
{viewers > 0 && (
|
{viewers > 0 && (
|
||||||
<span className="pill viewers">
|
<span className="pill viewers">
|
||||||
<FormattedMessage defaultMessage="{n} viewers" values={{ n: formatSats(viewers) }} />
|
<FormattedMessage defaultMessage="{n} viewers" id="3adEeb" values={{ n: formatSats(viewers) }} />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{status === StreamState.Live && (
|
{status === StreamState.Live && (
|
||||||
@ -74,7 +74,7 @@ function ProfileInfo({ ev, goal }: { ev?: NostrEvent; goal?: TaggedNostrEvent })
|
|||||||
<div className="actions">
|
<div className="actions">
|
||||||
{ev && <NewStreamDialog text="Edit" ev={ev} btnClassName="btn" />}
|
{ev && <NewStreamDialog text="Edit" ev={ev} btnClassName="btn" />}
|
||||||
<AsyncButton type="button" className="btn btn-warning" onClick={deleteStream}>
|
<AsyncButton type="button" className="btn btn-warning" onClick={deleteStream}>
|
||||||
<FormattedMessage defaultMessage="Delete" />
|
<FormattedMessage defaultMessage="Delete" id="K3r6DQ" />
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -6,15 +6,15 @@ import { useEventReactions } from "@snort/system-react";
|
|||||||
import { FormattedDate, FormattedMessage, FormattedNumber } from "react-intl";
|
import { FormattedDate, FormattedMessage, FormattedNumber } from "react-intl";
|
||||||
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
|
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
|
||||||
|
|
||||||
import { LIVE_STREAM_CHAT } from "const";
|
import { LIVE_STREAM_CHAT } from "@/const";
|
||||||
import { Profile } from "element/profile";
|
import { Profile } from "@/element/profile";
|
||||||
import { StatePill } from "element/state-pill";
|
import { StatePill } from "@/element/state-pill";
|
||||||
import { useCurrentStreamFeed } from "hooks/current-stream-feed";
|
import { useCurrentStreamFeed } from "@/hooks/current-stream-feed";
|
||||||
import { useLiveChatFeed } from "hooks/live-chat";
|
import { useLiveChatFeed } from "@/hooks/live-chat";
|
||||||
import { useStreamLink } from "hooks/stream-link";
|
import { useStreamLink } from "@/hooks/stream-link";
|
||||||
import { StreamState } from "index";
|
import { StreamState } from "@/index";
|
||||||
import { formatSats } from "number";
|
import { formatSats } from "@/number";
|
||||||
import { findTag, getEventFromLocationState } from "utils";
|
import { findTag, getEventFromLocationState } from "@/utils";
|
||||||
|
|
||||||
export function StreamSummaryPage() {
|
export function StreamSummaryPage() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
@ -2,9 +2,9 @@ import "./tag.css";
|
|||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { unwrap } from "@snort/shared";
|
import { unwrap } from "@snort/shared";
|
||||||
|
|
||||||
import { VideoTile } from "element/video-tile";
|
import { VideoTile } from "@/element/video-tile";
|
||||||
import { FollowTagButton } from "element/follow-button";
|
import { FollowTagButton } from "@/element/follow-button";
|
||||||
import { useStreamsFeed } from "hooks/live-streams";
|
import { useStreamsFeed } from "@/hooks/live-streams";
|
||||||
|
|
||||||
export function TagPage() {
|
export function TagPage() {
|
||||||
const { tag } = useParams();
|
const { tag } = useParams();
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import "./widgets.css";
|
import "./widgets.css";
|
||||||
import { useState, useMemo } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { useIntl, FormattedMessage } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { NostrLink, NostrPrefix } from "@snort/system";
|
import { NostrLink, NostrPrefix } from "@snort/system";
|
||||||
|
|
||||||
import Copy from "element/copy";
|
import Copy from "@/element/copy";
|
||||||
import { useCurrentStreamFeed } from "hooks/current-stream-feed";
|
import { useCurrentStreamFeed } from "@/hooks/current-stream-feed";
|
||||||
import { getVoices, speak, toTextToSpeechParams } from "text2speech";
|
import { getVoices, speak, toTextToSpeechParams } from "@/text2speech";
|
||||||
import { useLogin } from "hooks/login";
|
import { useLogin } from "@/hooks/login";
|
||||||
import { ZapAlertItem } from "./widgets/zaps";
|
import { ZapAlertItem } from "./widgets/zaps";
|
||||||
import { TopZappersWidget } from "./widgets/top-zappers";
|
import { TopZappersWidget } from "./widgets/top-zappers";
|
||||||
import { Views } from "./widgets/views";
|
import { Views } from "./widgets/views";
|
||||||
@ -64,7 +64,7 @@ function ZapAlertConfiguration({ npub, baseUrl }: ZapAlertConfigurationProps) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h3>
|
<h3>
|
||||||
<FormattedMessage defaultMessage="Zap Alert" />
|
<FormattedMessage defaultMessage="Zap Alert" id="zVDHAu" />
|
||||||
</h3>
|
</h3>
|
||||||
<Copy text={`${baseUrl}/alert/${npub}/zaps${query}`} />
|
<Copy text={`${baseUrl}/alert/${npub}/zaps${query}`} />
|
||||||
<ZapAlertItem
|
<ZapAlertItem
|
||||||
@ -91,13 +91,13 @@ function ZapAlertConfiguration({ npub, baseUrl }: ZapAlertConfigurationProps) {
|
|||||||
checked={textToSpeech}
|
checked={textToSpeech}
|
||||||
onChange={ev => setTextToSpeech(ev.target.checked)}
|
onChange={ev => setTextToSpeech(ev.target.checked)}
|
||||||
/>
|
/>
|
||||||
<FormattedMessage defaultMessage="Enable text to speech" />
|
<FormattedMessage defaultMessage="Enable text to speech" id="heyxZL" />
|
||||||
</div>
|
</div>
|
||||||
{isTextToSpeechEnabled && (
|
{isTextToSpeechEnabled && (
|
||||||
<>
|
<>
|
||||||
<div className="paper labeled-input">
|
<div className="paper labeled-input">
|
||||||
<label htmlFor="minimum-sats">
|
<label htmlFor="minimum-sats">
|
||||||
<FormattedMessage defaultMessage="Minimum amount for text to speech" />
|
<FormattedMessage defaultMessage="Minimum amount for text to speech" id="6pr6hJ" />
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="minimum-sats"
|
id="minimum-sats"
|
||||||
@ -109,7 +109,7 @@ function ZapAlertConfiguration({ npub, baseUrl }: ZapAlertConfigurationProps) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="paper labeled-input">
|
<div className="paper labeled-input">
|
||||||
<label htmlFor="volume">
|
<label htmlFor="volume">
|
||||||
<FormattedMessage defaultMessage="Volume" />
|
<FormattedMessage defaultMessage="Volume" id="y867Vs" />
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="volume"
|
id="volume"
|
||||||
@ -123,11 +123,11 @@ function ZapAlertConfiguration({ npub, baseUrl }: ZapAlertConfigurationProps) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="paper labeled-input">
|
<div className="paper labeled-input">
|
||||||
<label htmlFor="voice-selector">
|
<label htmlFor="voice-selector">
|
||||||
<FormattedMessage defaultMessage="Voice" />
|
<FormattedMessage defaultMessage="Voice" id="mnJYBQ" />
|
||||||
</label>
|
</label>
|
||||||
<select id="voice-selector" onChange={ev => setVoice(ev.target.value)}>
|
<select id="voice-selector" onChange={ev => setVoice(ev.target.value)}>
|
||||||
<option value="">
|
<option value="">
|
||||||
<FormattedMessage defaultMessage="Select voice..." />
|
<FormattedMessage defaultMessage="Select voice..." id="wMKVFz" />
|
||||||
</option>
|
</option>
|
||||||
{languages.map(l => (
|
{languages.map(l => (
|
||||||
<optgroup label={formatDisplayName(l, { type: "language" })}>
|
<optgroup label={formatDisplayName(l, { type: "language" })}>
|
||||||
@ -142,17 +142,17 @@ function ZapAlertConfiguration({ npub, baseUrl }: ZapAlertConfigurationProps) {
|
|||||||
<>
|
<>
|
||||||
<div className="paper labeled-input">
|
<div className="paper labeled-input">
|
||||||
<label htmlFor="zap-alert-text">
|
<label htmlFor="zap-alert-text">
|
||||||
<FormattedMessage defaultMessage="Zap message" />
|
<FormattedMessage defaultMessage="Zap message" id="sInm1h" />
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
id="zap-alert-text"
|
id="zap-alert-text"
|
||||||
placeholder={formatMessage({ defaultMessage: "Insert text to speak" })}
|
placeholder={formatMessage({ defaultMessage: "Insert text to speak", id: "8YT6ja" })}
|
||||||
value={testText}
|
value={testText}
|
||||||
onChange={ev => setTestText(ev.target.value)}
|
onChange={ev => setTestText(ev.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button disabled={testText.length === 0} className="btn" onClick={testVoice}>
|
<button disabled={testText.length === 0} className="btn" onClick={testVoice}>
|
||||||
<FormattedMessage defaultMessage="Test voice" />
|
<FormattedMessage defaultMessage="Test voice" id="d5zWyh" />
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -176,7 +176,7 @@ export function WidgetsPage() {
|
|||||||
<div className="widgets g8">
|
<div className="widgets g8">
|
||||||
<div className="flex f-col g8">
|
<div className="flex f-col g8">
|
||||||
<h3>
|
<h3>
|
||||||
<FormattedMessage defaultMessage="Chat Widget" />
|
<FormattedMessage defaultMessage="Chat Widget" id="hpl4BP" />
|
||||||
</h3>
|
</h3>
|
||||||
<Copy text={`${baseUrl}/chat/${npub}`} />
|
<Copy text={`${baseUrl}/chat/${npub}`} />
|
||||||
</div>
|
</div>
|
||||||
@ -185,21 +185,21 @@ export function WidgetsPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex f-col g8">
|
<div className="flex f-col g8">
|
||||||
<h3>
|
<h3>
|
||||||
<FormattedMessage defaultMessage="Top Zappers" />
|
<FormattedMessage defaultMessage="Top Zappers" id="dVD/AR" />
|
||||||
</h3>
|
</h3>
|
||||||
<Copy text={`${baseUrl}/alert/${npub}/top-zappers`} />
|
<Copy text={`${baseUrl}/alert/${npub}/top-zappers`} />
|
||||||
{currentLink && <TopZappersWidget link={currentLink} />}
|
{currentLink && <TopZappersWidget link={currentLink} />}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex f-col g8">
|
<div className="flex f-col g8">
|
||||||
<h3>
|
<h3>
|
||||||
<FormattedMessage defaultMessage="Current Viewers" />
|
<FormattedMessage defaultMessage="Current Viewers" id="rgsbu9" />
|
||||||
</h3>
|
</h3>
|
||||||
<Copy text={`${baseUrl}/alert/${npub}/views`} />
|
<Copy text={`${baseUrl}/alert/${npub}/views`} />
|
||||||
{currentLink && <Views link={currentLink} />}
|
{currentLink && <Views link={currentLink} />}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex f-col g8">
|
<div className="flex f-col g8">
|
||||||
<h3>
|
<h3>
|
||||||
<FormattedMessage defaultMessage="Music" />
|
<FormattedMessage defaultMessage="Music" id="79lLl+" />
|
||||||
</h3>
|
</h3>
|
||||||
<Copy text={`${baseUrl}/alert/${npub}/music`} />
|
<Copy text={`${baseUrl}/alert/${npub}/music`} />
|
||||||
{currentLink && <Music link={currentLink} />}
|
{currentLink && <Music link={currentLink} />}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { NostrLink } from "@snort/system";
|
import { NostrLink } from "@snort/system";
|
||||||
import { unixNow } from "@snort/shared";
|
import { unixNow } from "@snort/shared";
|
||||||
import { useCurrentStreamFeed } from "hooks/current-stream-feed";
|
|
||||||
import { useStatus } from "hooks/status";
|
import { useCurrentStreamFeed } from "@/hooks/current-stream-feed";
|
||||||
import { getHost, findTag } from "utils";
|
import { useStatus } from "@/hooks/status";
|
||||||
|
import { findTag, getHost } from "@/utils";
|
||||||
|
|
||||||
export function Music({ link }: { link: NostrLink }) {
|
export function Music({ link }: { link: NostrLink }) {
|
||||||
const currentEvent = useCurrentStreamFeed(link, true);
|
const currentEvent = useCurrentStreamFeed(link, true);
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import { NostrLink } from "@snort/system";
|
import { NostrLink } from "@snort/system";
|
||||||
import { TopZappers } from "element/top-zappers";
|
|
||||||
import { useCurrentStreamFeed } from "hooks/current-stream-feed";
|
|
||||||
import { useZaps } from "hooks/zaps";
|
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
|
import { TopZappers } from "@/element/top-zappers";
|
||||||
|
import { useCurrentStreamFeed } from "@/hooks/current-stream-feed";
|
||||||
|
import { useZaps } from "@/hooks/zaps";
|
||||||
|
|
||||||
export function TopZappersWidget({ link }: { link: NostrLink }) {
|
export function TopZappersWidget({ link }: { link: NostrLink }) {
|
||||||
const currentEvent = useCurrentStreamFeed(link, true);
|
const currentEvent = useCurrentStreamFeed(link, true);
|
||||||
const zaps = useZaps(currentEvent ? NostrLink.fromEvent(currentEvent) : undefined, true);
|
const zaps = useZaps(currentEvent ? NostrLink.fromEvent(currentEvent) : undefined, true);
|
||||||
return (
|
return (
|
||||||
<div className="top-zappers-widget">
|
<div className="top-zappers-widget">
|
||||||
<div>
|
<div>
|
||||||
<FormattedMessage defaultMessage="Top Zappers" />
|
<FormattedMessage defaultMessage="Top Zappers" id="dVD/AR" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex g8">
|
<div className="flex g8">
|
||||||
<TopZappers zaps={zaps} limit={3} />
|
<TopZappers zaps={zaps} limit={3} />
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { NostrLink } from "@snort/system";
|
import { NostrLink } from "@snort/system";
|
||||||
import { useCurrentStreamFeed } from "hooks/current-stream-feed";
|
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { findTag } from "utils";
|
|
||||||
|
import { useCurrentStreamFeed } from "@/hooks/current-stream-feed";
|
||||||
|
import { findTag } from "@/utils";
|
||||||
|
|
||||||
export function Views({ link }: { link: NostrLink }) {
|
export function Views({ link }: { link: NostrLink }) {
|
||||||
const current = useCurrentStreamFeed(link, true);
|
const current = useCurrentStreamFeed(link, true);
|
||||||
@ -11,9 +12,9 @@ export function Views({ link }: { link: NostrLink }) {
|
|||||||
return (
|
return (
|
||||||
<div className="views">
|
<div className="views">
|
||||||
{isNaN(n) ? (
|
{isNaN(n) ? (
|
||||||
<FormattedMessage defaultMessage="No viewer data available" />
|
<FormattedMessage defaultMessage="No viewer data available" id="AukrPM" />
|
||||||
) : (
|
) : (
|
||||||
<FormattedMessage defaultMessage="{n} viewers" values={{ n }} />
|
<FormattedMessage defaultMessage="{n} viewers" id="3adEeb" values={{ n }} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import { useMemo, useState, useEffect } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import { hexToBech32 } from "@snort/shared";
|
import { hexToBech32 } from "@snort/shared";
|
||||||
import { NostrLink, ParsedZap } from "@snort/system";
|
import { NostrLink, ParsedZap } from "@snort/system";
|
||||||
import { useUserProfile } from "@snort/system-react";
|
import { useUserProfile } from "@snort/system-react";
|
||||||
|
|
||||||
import { useCurrentStreamFeed } from "hooks/current-stream-feed";
|
import { useCurrentStreamFeed } from "@/hooks/current-stream-feed";
|
||||||
import { useZaps } from "hooks/zaps";
|
import { useZaps } from "@/hooks/zaps";
|
||||||
import { useMutedPubkeys } from "hooks/lists";
|
import { useMutedPubkeys } from "@/hooks/lists";
|
||||||
import { formatSats } from "number";
|
import { formatSats } from "@/number";
|
||||||
import { useTextToSpeechParams, getVoices, speak } from "text2speech";
|
import { getVoices, speak, useTextToSpeechParams } from "@/text2speech";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { getHost } from "@/utils";
|
||||||
import { getHost } from "utils";
|
|
||||||
|
|
||||||
function useZapQueue(zapStream: ParsedZap[], zapTime = 10_000) {
|
function useZapQueue(zapStream: ParsedZap[], zapTime = 10_000) {
|
||||||
const zaps = useMemo(() => {
|
const zaps = useMemo(() => {
|
||||||
@ -76,11 +76,12 @@ export function ZapAlertItem({ item }: { item: ParsedZap }) {
|
|||||||
<>
|
<>
|
||||||
<div className="zap-alert">
|
<div className="zap-alert">
|
||||||
<div className="zap-alert-title">
|
<div className="zap-alert-title">
|
||||||
<FormattedMessage defaultMessage="Incoming Zap" />
|
<FormattedMessage defaultMessage="Incoming Zap" id="tG1ST3" />
|
||||||
</div>
|
</div>
|
||||||
<div className="zap-alert-header">
|
<div className="zap-alert-header">
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="{name} with {amount}"
|
defaultMessage="{name} with {amount}"
|
||||||
|
id="Qe1MJu"
|
||||||
values={{
|
values={{
|
||||||
name: (
|
name: (
|
||||||
<span className="highlight">
|
<span className="highlight">
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { StreamState } from "index";
|
import { StreamState } from "@/index";
|
||||||
import { NostrEvent, SystemInterface } from "@snort/system";
|
import { NostrEvent, SystemInterface } from "@snort/system";
|
||||||
import { ExternalStore } from "@snort/shared";
|
import { ExternalStore } from "@snort/shared";
|
||||||
import { Nip103StreamProvider } from "./zsz";
|
import { Nip103StreamProvider } from "./zsz";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { NostrEvent, SystemInterface } from "@snort/system";
|
import { NostrEvent, SystemInterface } from "@snort/system";
|
||||||
import { StreamProvider, StreamProviderInfo, StreamProviders } from "providers";
|
import { StreamProvider, StreamProviderInfo, StreamProviders } from "@/providers";
|
||||||
|
|
||||||
export class ManualProvider implements StreamProvider {
|
export class ManualProvider implements StreamProvider {
|
||||||
get name(): string {
|
get name(): string {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { StreamState } from "index";
|
import { StreamState } from "@/index";
|
||||||
import { StreamProvider, StreamProviderInfo, StreamProviders } from "providers";
|
import { StreamProvider, StreamProviderInfo, StreamProviders } from "@/providers";
|
||||||
|
|
||||||
export class OwncastProvider implements StreamProvider {
|
export class OwncastProvider implements StreamProvider {
|
||||||
#url: string;
|
#url: string;
|
||||||
|
@ -7,9 +7,9 @@ import {
|
|||||||
StreamProviders,
|
StreamProviders,
|
||||||
} from ".";
|
} from ".";
|
||||||
import { EventKind, EventPublisher, NostrEvent, SystemInterface } from "@snort/system";
|
import { EventKind, EventPublisher, NostrEvent, SystemInterface } from "@snort/system";
|
||||||
import { Login, StreamState } from "index";
|
import { Login, StreamState } from "@/index";
|
||||||
import { getPublisher } from "login";
|
import { getPublisher } from "@/login";
|
||||||
import { findTag } from "utils";
|
import { findTag } from "@/utils";
|
||||||
|
|
||||||
export class Nip103StreamProvider implements StreamProvider {
|
export class Nip103StreamProvider implements StreamProvider {
|
||||||
#publisher?: EventPublisher;
|
#publisher?: EventPublisher;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { NostrEvent, NostrLink, TaggedNostrEvent } from "@snort/system";
|
import { NostrEvent, NostrLink, TaggedNostrEvent } from "@snort/system";
|
||||||
|
|
||||||
import type { Tags } from "types";
|
import type { Tags } from "@/types";
|
||||||
import { LIVE_STREAM } from "const";
|
import { LIVE_STREAM } from "@/const";
|
||||||
|
|
||||||
export function toAddress(e: NostrEvent): string {
|
export function toAddress(e: NostrEvent): string {
|
||||||
if (e.kind && e.kind >= 30000 && e.kind <= 40000) {
|
if (e.kind && e.kind >= 30000 && e.kind <= 40000) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import adapter from "webrtc-adapter";
|
import adapter from "webrtc-adapter";
|
||||||
import { CandidateInfo, SDPInfo } from "semantic-sdp";
|
import { CandidateInfo, SDPInfo } from "semantic-sdp";
|
||||||
import { TypedEventTarget, type StatusEvent, type LogEvent } from "./events";
|
import { type LogEvent, type StatusEvent, TypedEventTarget } from "./events";
|
||||||
import { parserLinkHeader } from "./parser";
|
import { parserLinkHeader } from "./parser";
|
||||||
|
|
||||||
export const DEFAULT_ICE_SERVERS = ["stun:stun.cloudflare.com:3478", "stun:stun.l.google.com:19302"];
|
export const DEFAULT_ICE_SERVERS = ["stun:stun.cloudflare.com:3478", "stun:stun.l.google.com:19302"];
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user