├── .editorconfig
├── .erb
├── configs
│ ├── .eslintrc
│ ├── webpack.config.base.ts
│ ├── webpack.config.eslint.ts
│ ├── webpack.config.main.prod.ts
│ ├── webpack.config.preload.dev.ts
│ ├── webpack.config.renderer.dev.dll.ts
│ ├── webpack.config.renderer.dev.ts
│ ├── webpack.config.renderer.prod.ts
│ └── webpack.paths.ts
├── img
│ ├── electron-nostr.png
│ ├── erb-banner.svg
│ ├── erb-logo.png
│ └── palette-sponsor-banner.svg
├── mocks
│ └── fileMock.js
└── scripts
│ ├── .eslintrc
│ ├── check-build-exists.ts
│ ├── check-native-dep.js
│ ├── check-node-env.js
│ ├── check-port-in-use.js
│ ├── clean.js
│ ├── delete-source-maps.js
│ ├── electron-rebuild.js
│ ├── link-modules.ts
│ └── notarize.js
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── 1-Bug_report.md
│ ├── 2-Question.md
│ └── 3-Feature_request.md
├── config.yml
├── stale.yml
└── workflows
│ ├── codeql-analysis.yml
│ ├── publish.yml
│ └── test.yml
├── .gitignore
├── .husky
└── pre-commit
├── .vscode
├── extensions.json
├── launch.json
└── settings.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── assets
├── assets.d.ts
├── entitlements.mac.plist
├── icon.icns
├── icon.ico
├── icon.png
├── icon.svg
└── icons
│ ├── 1024x1024.png
│ ├── 128x128.png
│ ├── 16x16.png
│ ├── 24x24.png
│ ├── 256x256.png
│ ├── 32x32.png
│ ├── 48x48.png
│ ├── 512x512.png
│ ├── 64x64.png
│ └── 96x96.png
├── notes.txt
├── package-lock.json
├── package.json
├── release
└── app
│ ├── images
│ └── missingAvatar.png
│ ├── package-lock.json
│ └── package.json
├── src
├── __tests__
│ └── App.test.tsx
├── main
│ ├── main.ts
│ ├── menu.ts
│ ├── preload.ts
│ └── util.ts
└── renderer
│ ├── App-depr.tsx
│ ├── App.tsx
│ ├── css
│ ├── app.css
│ ├── directMessaging.css
│ ├── editMyProfile.css
│ ├── feed.css
│ ├── follows.css
│ ├── grapevine.css
│ ├── grapevineSettings.css
│ ├── grapevineToggleSwitch.css
│ ├── mastheads.css
│ ├── misc.css
│ ├── myProfile.css
│ ├── navbars.css
│ ├── newPost.css
│ ├── nfgGraphic.css
│ ├── settings.css
│ ├── toggleSwitch1.css
│ ├── userList.css
│ ├── userProfile.css
│ └── youTubeEmbed.css
│ ├── errorBoundary.js
│ ├── grapevine
│ ├── landingPage
│ │ └── index.js
│ └── nostrFollowGrapevine
│ │ ├── graphic.js
│ │ ├── index.js
│ │ └── missingAvatar.png
│ ├── index.ejs
│ ├── index.tsx
│ ├── lib
│ ├── app
│ │ ├── misc.ts
│ │ ├── startup.ts
│ │ ├── timer.js
│ │ └── toggleSwitch1.js
│ ├── nostr
│ │ ├── eventValidation.js
│ │ └── getFollowing.js
│ └── visjs
│ │ └── visjs-style.js
│ ├── mastheads
│ ├── avatarElem.js
│ ├── blankAvatar.png
│ └── mainMasthead.js
│ ├── navbars
│ ├── leftNav.js
│ └── leftNav2
│ │ └── settings.js
│ ├── pages
│ ├── components
│ │ ├── actionButtons.js
│ │ ├── blankAvatar.png
│ │ ├── followButton.tsx
│ │ ├── relaysStatus.tsx
│ │ ├── rootMessage.tsx
│ │ ├── userPost.js
│ │ ├── userPosts.js
│ │ └── youTubeEmbed.js
│ ├── createPost
│ │ ├── index.tsx
│ │ └── publishPost.tsx
│ ├── directMessageConversation
│ │ ├── convoHistory-deprecated.js
│ │ ├── convoHistory.js
│ │ ├── directMessage-archived.js
│ │ ├── directMessage-deprecated.js
│ │ ├── directMessage.js
│ │ ├── index.js
│ │ └── sendDirectMessage.tsx
│ ├── downloadProfiles
│ │ └── index.js
│ ├── editMyProfile
│ │ └── index.tsx
│ ├── extendedFollowingList
│ │ ├── fetchFollowingList.js
│ │ └── index.js
│ ├── followingList
│ │ ├── index.js
│ │ └── singleUserElem.js
│ ├── grapevineSettings
│ │ ├── grapevineIsActive.js
│ │ ├── grapevineIsInactive.js
│ │ ├── index.js
│ │ ├── settingsMainComp.js
│ │ └── toggleSwitch.js
│ ├── landingPage
│ │ └── index.js
│ ├── mainFeed
│ │ ├── index.js
│ │ ├── mainFeedTypeSelector.js
│ │ └── nameElem_deprecated.js
│ ├── manageChannels
│ │ └── index.js
│ ├── myProfile
│ │ └── index.tsx
│ ├── reply
│ │ └── index.tsx
│ ├── searchForUser
│ │ └── index.js
│ ├── settings
│ │ ├── index.js
│ │ ├── profilekeys
│ │ │ ├── index.js
│ │ │ └── pk.js
│ │ ├── relays
│ │ │ ├── index.js
│ │ │ └── relays.js
│ │ └── sql
│ │ │ ├── index.js
│ │ │ └── sql.js
│ ├── thread
│ │ └── index.tsx
│ ├── userList
│ │ └── index.js
│ ├── userProfile
│ │ ├── followCounts.js
│ │ ├── index.js
│ │ ├── leaveGrapevineRatings.js
│ │ ├── storeProfileLocallyButton.js
│ │ └── userInfo.js
│ └── visJsHelloWorld
│ │ └── index.js
│ └── preload.d.ts
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.erb/configs/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-console": "off",
4 | "global-require": "off",
5 | "import/no-dynamic-require": "off"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.erb/configs/webpack.config.base.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Base webpack config used across other specific configs
3 | */
4 |
5 | import webpack from 'webpack';
6 | import webpackPaths from './webpack.paths';
7 | import { dependencies as externals } from '../../release/app/package.json';
8 |
9 | const configuration: webpack.Configuration = {
10 | externals: [...Object.keys(externals || {})],
11 |
12 | stats: 'errors-only',
13 |
14 | module: {
15 | rules: [
16 | {
17 | test: /\.[jt]sx?$/,
18 | exclude: /node_modules/,
19 | use: {
20 | loader: 'ts-loader',
21 | options: {
22 | // Remove this line to enable type checking in webpack builds
23 | transpileOnly: true,
24 | compilerOptions: {
25 | module: 'esnext',
26 | },
27 | },
28 | },
29 | },
30 | ],
31 | },
32 |
33 | output: {
34 | path: webpackPaths.srcPath,
35 | // https://github.com/webpack/webpack/issues/1114
36 | library: {
37 | type: 'commonjs2',
38 | },
39 | },
40 |
41 | /**
42 | * Determine the array of extensions that should be used to resolve modules.
43 | */
44 | resolve: {
45 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
46 | modules: [webpackPaths.srcPath, 'node_modules'],
47 | fallback: {
48 | "stream": false,
49 | "util": false,
50 | }
51 | },
52 |
53 | plugins: [
54 | new webpack.EnvironmentPlugin({
55 | NODE_ENV: 'production',
56 | }),
57 | ],
58 | };
59 |
60 | export default configuration;
61 |
--------------------------------------------------------------------------------
/.erb/configs/webpack.config.eslint.ts:
--------------------------------------------------------------------------------
1 | /* eslint import/no-unresolved: off, import/no-self-import: off */
2 |
3 | module.exports = require('./webpack.config.renderer.dev').default;
4 |
--------------------------------------------------------------------------------
/.erb/configs/webpack.config.main.prod.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Webpack config for production electron main process
3 | */
4 |
5 | import path from 'path';
6 | import webpack from 'webpack';
7 | import { merge } from 'webpack-merge';
8 | import TerserPlugin from 'terser-webpack-plugin';
9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
10 | import baseConfig from './webpack.config.base';
11 | import webpackPaths from './webpack.paths';
12 | import checkNodeEnv from '../scripts/check-node-env';
13 | import deleteSourceMaps from '../scripts/delete-source-maps';
14 |
15 | checkNodeEnv('production');
16 | deleteSourceMaps();
17 |
18 | const configuration: webpack.Configuration = {
19 | devtool: 'source-map',
20 |
21 | mode: 'production',
22 |
23 | target: 'electron-main',
24 |
25 | entry: {
26 | main: path.join(webpackPaths.srcMainPath, 'main.ts'),
27 | preload: path.join(webpackPaths.srcMainPath, 'preload.ts'),
28 | },
29 |
30 | output: {
31 | path: webpackPaths.distMainPath,
32 | filename: '[name].js',
33 | library: {
34 | type: 'umd',
35 | },
36 | },
37 |
38 | optimization: {
39 | minimizer: [
40 | new TerserPlugin({
41 | parallel: true,
42 | }),
43 | ],
44 | },
45 |
46 | plugins: [
47 | new BundleAnalyzerPlugin({
48 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
49 | analyzerPort: 8888,
50 | }),
51 |
52 | /**
53 | * Create global constants which can be configured at compile time.
54 | *
55 | * Useful for allowing different behaviour between development builds and
56 | * release builds
57 | *
58 | * NODE_ENV should be production so that modules do not perform certain
59 | * development checks
60 | */
61 | new webpack.EnvironmentPlugin({
62 | NODE_ENV: 'production',
63 | DEBUG_PROD: false,
64 | START_MINIMIZED: false,
65 | }),
66 |
67 | new webpack.DefinePlugin({
68 | 'process.type': '"main"',
69 | }),
70 | ],
71 |
72 | /**
73 | * Disables webpack processing of __dirname and __filename.
74 | * If you run the bundle in node.js it falls back to these values of node.js.
75 | * https://github.com/webpack/webpack/issues/2010
76 | */
77 | node: {
78 | __dirname: false,
79 | __filename: false,
80 | },
81 | };
82 |
83 | export default merge(baseConfig, configuration);
84 |
--------------------------------------------------------------------------------
/.erb/configs/webpack.config.preload.dev.ts:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import webpack from 'webpack';
3 | import { merge } from 'webpack-merge';
4 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
5 | import baseConfig from './webpack.config.base';
6 | import webpackPaths from './webpack.paths';
7 | import checkNodeEnv from '../scripts/check-node-env';
8 |
9 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's
10 | // at the dev webpack config is not accidentally run in a production environment
11 | if (process.env.NODE_ENV === 'production') {
12 | checkNodeEnv('development');
13 | }
14 |
15 | const configuration: webpack.Configuration = {
16 | devtool: 'inline-source-map',
17 |
18 | mode: 'development',
19 |
20 | target: 'electron-preload',
21 |
22 | entry: path.join(webpackPaths.srcMainPath, 'preload.ts'),
23 |
24 | output: {
25 | path: webpackPaths.dllPath,
26 | filename: 'preload.js',
27 | library: {
28 | type: 'umd',
29 | },
30 | },
31 |
32 | plugins: [
33 | new BundleAnalyzerPlugin({
34 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
35 | }),
36 |
37 | /**
38 | * Create global constants which can be configured at compile time.
39 | *
40 | * Useful for allowing different behaviour between development builds and
41 | * release builds
42 | *
43 | * NODE_ENV should be production so that modules do not perform certain
44 | * development checks
45 | *
46 | * By default, use 'development' as NODE_ENV. This can be overriden with
47 | * 'staging', for example, by changing the ENV variables in the npm scripts
48 | */
49 | new webpack.EnvironmentPlugin({
50 | NODE_ENV: 'development',
51 | }),
52 |
53 | new webpack.LoaderOptionsPlugin({
54 | debug: true,
55 | }),
56 | ],
57 |
58 | /**
59 | * Disables webpack processing of __dirname and __filename.
60 | * If you run the bundle in node.js it falls back to these values of node.js.
61 | * https://github.com/webpack/webpack/issues/2010
62 | */
63 | node: {
64 | __dirname: false,
65 | __filename: false,
66 | },
67 |
68 | watch: true,
69 | };
70 |
71 | export default merge(baseConfig, configuration);
72 |
--------------------------------------------------------------------------------
/.erb/configs/webpack.config.renderer.dev.dll.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Builds the DLL for development electron renderer process
3 | */
4 |
5 | import webpack from 'webpack';
6 | import path from 'path';
7 | import { merge } from 'webpack-merge';
8 | import baseConfig from './webpack.config.base';
9 | import webpackPaths from './webpack.paths';
10 | import { dependencies } from '../../package.json';
11 | import checkNodeEnv from '../scripts/check-node-env';
12 |
13 | checkNodeEnv('development');
14 |
15 | const dist = webpackPaths.dllPath;
16 |
17 | const configuration: webpack.Configuration = {
18 | context: webpackPaths.rootPath,
19 |
20 | devtool: 'eval',
21 |
22 | mode: 'development',
23 |
24 | target: 'electron-renderer',
25 |
26 | externals: ['fsevents', 'crypto-browserify'],
27 |
28 | /**
29 | * Use `module` from `webpack.config.renderer.dev.js`
30 | */
31 | module: require('./webpack.config.renderer.dev').default.module,
32 |
33 | entry: {
34 | renderer: Object.keys(dependencies || {}),
35 | },
36 |
37 | output: {
38 | path: dist,
39 | filename: '[name].dev.dll.js',
40 | library: {
41 | name: 'renderer',
42 | type: 'var',
43 | },
44 | },
45 |
46 | plugins: [
47 | new webpack.DllPlugin({
48 | path: path.join(dist, '[name].json'),
49 | name: '[name]',
50 | }),
51 |
52 | /**
53 | * Create global constants which can be configured at compile time.
54 | *
55 | * Useful for allowing different behaviour between development builds and
56 | * release builds
57 | *
58 | * NODE_ENV should be production so that modules do not perform certain
59 | * development checks
60 | */
61 | new webpack.EnvironmentPlugin({
62 | NODE_ENV: 'development',
63 | }),
64 |
65 | new webpack.LoaderOptionsPlugin({
66 | debug: true,
67 | options: {
68 | context: webpackPaths.srcPath,
69 | output: {
70 | path: webpackPaths.dllPath,
71 | },
72 | },
73 | }),
74 | ],
75 | };
76 |
77 | export default merge(baseConfig, configuration);
78 |
--------------------------------------------------------------------------------
/.erb/configs/webpack.config.renderer.dev.ts:
--------------------------------------------------------------------------------
1 | import 'webpack-dev-server';
2 | import path from 'path';
3 | import fs from 'fs';
4 | import webpack from 'webpack';
5 | import HtmlWebpackPlugin from 'html-webpack-plugin';
6 | import chalk from 'chalk';
7 | import { merge } from 'webpack-merge';
8 | import { execSync, spawn } from 'child_process';
9 | import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
10 | import baseConfig from './webpack.config.base';
11 | import webpackPaths from './webpack.paths';
12 | import checkNodeEnv from '../scripts/check-node-env';
13 |
14 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's
15 | // at the dev webpack config is not accidentally run in a production environment
16 | if (process.env.NODE_ENV === 'production') {
17 | checkNodeEnv('development');
18 | }
19 |
20 | const port = process.env.PORT || 1212;
21 | const manifest = path.resolve(webpackPaths.dllPath, 'renderer.json');
22 | const skipDLLs =
23 | module.parent?.filename.includes('webpack.config.renderer.dev.dll') ||
24 | module.parent?.filename.includes('webpack.config.eslint');
25 |
26 | /**
27 | * Warn if the DLL is not built
28 | */
29 | if (
30 | !skipDLLs &&
31 | !(fs.existsSync(webpackPaths.dllPath) && fs.existsSync(manifest))
32 | ) {
33 | console.log(
34 | chalk.black.bgYellow.bold(
35 | 'The DLL files are missing. Sit back while we build them for you with "npm run build-dll"'
36 | )
37 | );
38 | execSync('npm run postinstall');
39 | }
40 |
41 | const configuration: webpack.Configuration = {
42 | devtool: 'inline-source-map',
43 |
44 | mode: 'development',
45 |
46 | target: ['web', 'electron-renderer'],
47 |
48 | entry: [
49 | `webpack-dev-server/client?http://localhost:${port}/dist`,
50 | 'webpack/hot/only-dev-server',
51 | path.join(webpackPaths.srcRendererPath, 'index.tsx'),
52 | ],
53 |
54 | output: {
55 | path: webpackPaths.distRendererPath,
56 | publicPath: '/',
57 | filename: 'renderer.dev.js',
58 | library: {
59 | type: 'umd',
60 | },
61 | },
62 |
63 | module: {
64 | rules: [
65 | {
66 | test: /\.s?(c|a)ss$/,
67 | use: [
68 | 'style-loader',
69 | {
70 | loader: 'css-loader',
71 | options: {
72 | modules: true,
73 | sourceMap: true,
74 | importLoaders: 1,
75 | },
76 | },
77 | 'sass-loader',
78 | ],
79 | include: /\.module\.s?(c|a)ss$/,
80 | },
81 | {
82 | test: /\.s?css$/,
83 | use: ['style-loader', 'css-loader', 'sass-loader'],
84 | exclude: /\.module\.s?(c|a)ss$/,
85 | },
86 | // Fonts
87 | {
88 | test: /\.(woff|woff2|eot|ttf|otf)$/i,
89 | type: 'asset/resource',
90 | },
91 | // Images
92 | {
93 | test: /\.(png|jpg|jpeg|gif)$/i,
94 | type: 'asset/resource',
95 | },
96 | // SVG
97 | {
98 | test: /\.svg$/,
99 | use: [
100 | {
101 | loader: '@svgr/webpack',
102 | options: {
103 | prettier: false,
104 | svgo: false,
105 | svgoConfig: {
106 | plugins: [{ removeViewBox: false }],
107 | },
108 | titleProp: true,
109 | ref: true,
110 | },
111 | },
112 | 'file-loader',
113 | ],
114 | },
115 | ],
116 | },
117 | plugins: [
118 | ...(skipDLLs
119 | ? []
120 | : [
121 | new webpack.DllReferencePlugin({
122 | context: webpackPaths.dllPath,
123 | manifest: require(manifest),
124 | sourceType: 'var',
125 | }),
126 | ]),
127 |
128 | new webpack.NoEmitOnErrorsPlugin(),
129 |
130 | /**
131 | * Create global constants which can be configured at compile time.
132 | *
133 | * Useful for allowing different behaviour between development builds and
134 | * release builds
135 | *
136 | * NODE_ENV should be production so that modules do not perform certain
137 | * development checks
138 | *
139 | * By default, use 'development' as NODE_ENV. This can be overriden with
140 | * 'staging', for example, by changing the ENV variables in the npm scripts
141 | */
142 | new webpack.EnvironmentPlugin({
143 | NODE_ENV: 'development',
144 | }),
145 |
146 | new webpack.LoaderOptionsPlugin({
147 | debug: true,
148 | }),
149 |
150 | new ReactRefreshWebpackPlugin(),
151 |
152 | new HtmlWebpackPlugin({
153 | filename: path.join('index.html'),
154 | template: path.join(webpackPaths.srcRendererPath, 'index.ejs'),
155 | minify: {
156 | collapseWhitespace: true,
157 | removeAttributeQuotes: true,
158 | removeComments: true,
159 | },
160 | isBrowser: false,
161 | env: process.env.NODE_ENV,
162 | isDevelopment: process.env.NODE_ENV !== 'production',
163 | nodeModules: webpackPaths.appNodeModulesPath,
164 | }),
165 | ],
166 |
167 | node: {
168 | __dirname: false,
169 | __filename: false,
170 | },
171 |
172 | devServer: {
173 | port,
174 | compress: true,
175 | hot: true,
176 | headers: { 'Access-Control-Allow-Origin': '*' },
177 | static: {
178 | publicPath: '/',
179 | },
180 | historyApiFallback: {
181 | verbose: true,
182 | },
183 | setupMiddlewares(middlewares) {
184 | console.log('Starting preload.js builder...');
185 | const preloadProcess = spawn('npm', ['run', 'start:preload'], {
186 | shell: true,
187 | stdio: 'inherit',
188 | })
189 | .on('close', (code: number) => process.exit(code!))
190 | .on('error', (spawnError) => console.error(spawnError));
191 |
192 | console.log('Starting Main Process...');
193 | let args = ['run', 'start:main'];
194 | if (process.env.MAIN_ARGS) {
195 | args = args.concat(
196 | ['--', ...process.env.MAIN_ARGS.matchAll(/"[^"]+"|[^\s"]+/g)].flat()
197 | );
198 | }
199 | spawn('npm', args, {
200 | shell: true,
201 | stdio: 'inherit',
202 | })
203 | .on('close', (code: number) => {
204 | preloadProcess.kill();
205 | process.exit(code!);
206 | })
207 | .on('error', (spawnError) => console.error(spawnError));
208 | return middlewares;
209 | },
210 | },
211 | };
212 |
213 | export default merge(baseConfig, configuration);
214 |
--------------------------------------------------------------------------------
/.erb/configs/webpack.config.renderer.prod.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Build config for electron renderer process
3 | */
4 |
5 | import path from 'path';
6 | import webpack from 'webpack';
7 | import HtmlWebpackPlugin from 'html-webpack-plugin';
8 | import MiniCssExtractPlugin from 'mini-css-extract-plugin';
9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
10 | import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
11 | import { merge } from 'webpack-merge';
12 | import TerserPlugin from 'terser-webpack-plugin';
13 | import baseConfig from './webpack.config.base';
14 | import webpackPaths from './webpack.paths';
15 | import checkNodeEnv from '../scripts/check-node-env';
16 | import deleteSourceMaps from '../scripts/delete-source-maps';
17 |
18 | checkNodeEnv('production');
19 | deleteSourceMaps();
20 |
21 | const configuration: webpack.Configuration = {
22 | devtool: 'source-map',
23 |
24 | mode: 'production',
25 |
26 | target: ['web', 'electron-renderer'],
27 |
28 | entry: [path.join(webpackPaths.srcRendererPath, 'index.tsx')],
29 |
30 | output: {
31 | path: webpackPaths.distRendererPath,
32 | publicPath: './',
33 | filename: 'renderer.js',
34 | library: {
35 | type: 'umd',
36 | },
37 | },
38 |
39 | module: {
40 | rules: [
41 | {
42 | test: /\.s?(a|c)ss$/,
43 | use: [
44 | MiniCssExtractPlugin.loader,
45 | {
46 | loader: 'css-loader',
47 | options: {
48 | modules: true,
49 | sourceMap: true,
50 | importLoaders: 1,
51 | },
52 | },
53 | 'sass-loader',
54 | ],
55 | include: /\.module\.s?(c|a)ss$/,
56 | },
57 | {
58 | test: /\.s?(a|c)ss$/,
59 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
60 | exclude: /\.module\.s?(c|a)ss$/,
61 | },
62 | // Fonts
63 | {
64 | test: /\.(woff|woff2|eot|ttf|otf)$/i,
65 | type: 'asset/resource',
66 | },
67 | // Images
68 | {
69 | test: /\.(png|jpg|jpeg|gif)$/i,
70 | type: 'asset/resource',
71 | },
72 | // SVG
73 | {
74 | test: /\.svg$/,
75 | use: [
76 | {
77 | loader: '@svgr/webpack',
78 | options: {
79 | prettier: false,
80 | svgo: false,
81 | svgoConfig: {
82 | plugins: [{ removeViewBox: false }],
83 | },
84 | titleProp: true,
85 | ref: true,
86 | },
87 | },
88 | 'file-loader',
89 | ],
90 | },
91 | ],
92 | },
93 |
94 | optimization: {
95 | minimize: true,
96 | minimizer: [
97 | new TerserPlugin({
98 | parallel: true,
99 | }),
100 | new CssMinimizerPlugin(),
101 | ],
102 | },
103 |
104 | plugins: [
105 | /**
106 | * Create global constants which can be configured at compile time.
107 | *
108 | * Useful for allowing different behaviour between development builds and
109 | * release builds
110 | *
111 | * NODE_ENV should be production so that modules do not perform certain
112 | * development checks
113 | */
114 | new webpack.EnvironmentPlugin({
115 | NODE_ENV: 'production',
116 | DEBUG_PROD: false,
117 | }),
118 |
119 | new MiniCssExtractPlugin({
120 | filename: 'style.css',
121 | }),
122 |
123 | new BundleAnalyzerPlugin({
124 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
125 | analyzerPort: 8889,
126 | }),
127 |
128 | new HtmlWebpackPlugin({
129 | filename: 'index.html',
130 | template: path.join(webpackPaths.srcRendererPath, 'index.ejs'),
131 | minify: {
132 | collapseWhitespace: true,
133 | removeAttributeQuotes: true,
134 | removeComments: true,
135 | },
136 | isBrowser: false,
137 | isDevelopment: process.env.NODE_ENV !== 'production',
138 | }),
139 |
140 | new webpack.DefinePlugin({
141 | 'process.type': '"renderer"',
142 | }),
143 | ],
144 | };
145 |
146 | export default merge(baseConfig, configuration);
147 |
--------------------------------------------------------------------------------
/.erb/configs/webpack.paths.ts:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const rootPath = path.join(__dirname, '../..');
4 |
5 | const dllPath = path.join(__dirname, '../dll');
6 |
7 | const srcPath = path.join(rootPath, 'src');
8 | const srcMainPath = path.join(srcPath, 'main');
9 | const srcRendererPath = path.join(srcPath, 'renderer');
10 |
11 | const releasePath = path.join(rootPath, 'release');
12 | const appPath = path.join(releasePath, 'app');
13 | const appPackagePath = path.join(appPath, 'package.json');
14 | const appNodeModulesPath = path.join(appPath, 'node_modules');
15 | const srcNodeModulesPath = path.join(srcPath, 'node_modules');
16 |
17 | const distPath = path.join(appPath, 'dist');
18 | const distMainPath = path.join(distPath, 'main');
19 | const distRendererPath = path.join(distPath, 'renderer');
20 |
21 | const buildPath = path.join(releasePath, 'build');
22 |
23 | export default {
24 | rootPath,
25 | dllPath,
26 | srcPath,
27 | srcMainPath,
28 | srcRendererPath,
29 | releasePath,
30 | appPath,
31 | appPackagePath,
32 | appNodeModulesPath,
33 | srcNodeModulesPath,
34 | distPath,
35 | distMainPath,
36 | distRendererPath,
37 | buildPath,
38 | };
39 |
--------------------------------------------------------------------------------
/.erb/img/electron-nostr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wds4/electron-react-boilerplate-nostr/e8d90f8b520381e54d80c77a919ea27e839c33ea/.erb/img/electron-nostr.png
--------------------------------------------------------------------------------
/.erb/img/erb-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wds4/electron-react-boilerplate-nostr/e8d90f8b520381e54d80c77a919ea27e839c33ea/.erb/img/erb-logo.png
--------------------------------------------------------------------------------
/.erb/mocks/fileMock.js:
--------------------------------------------------------------------------------
1 | export default 'test-file-stub';
2 |
--------------------------------------------------------------------------------
/.erb/scripts/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-console": "off",
4 | "global-require": "off",
5 | "import/no-dynamic-require": "off",
6 | "import/no-extraneous-dependencies": "off"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.erb/scripts/check-build-exists.ts:
--------------------------------------------------------------------------------
1 | // Check if the renderer and main bundles are built
2 | import path from 'path';
3 | import chalk from 'chalk';
4 | import fs from 'fs';
5 | import webpackPaths from '../configs/webpack.paths';
6 |
7 | const mainPath = path.join(webpackPaths.distMainPath, 'main.js');
8 | const rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js');
9 |
10 | if (!fs.existsSync(mainPath)) {
11 | throw new Error(
12 | chalk.whiteBright.bgRed.bold(
13 | 'The main process is not built yet. Build it by running "npm run build:main"'
14 | )
15 | );
16 | }
17 |
18 | if (!fs.existsSync(rendererPath)) {
19 | throw new Error(
20 | chalk.whiteBright.bgRed.bold(
21 | 'The renderer process is not built yet. Build it by running "npm run build:renderer"'
22 | )
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/.erb/scripts/check-native-dep.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import chalk from 'chalk';
3 | import { execSync } from 'child_process';
4 | import { dependencies } from '../../package.json';
5 |
6 | if (dependencies) {
7 | const dependenciesKeys = Object.keys(dependencies);
8 |
9 | const nativeDeps = fs
10 | .readdirSync('node_modules')
11 | .filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`));
12 | if (nativeDeps.length === 0) {
13 | process.exit(0);
14 | }
15 | console.log("nativeDeps: "+JSON.stringify(nativeDeps,null,4))
16 | try {
17 | // Find the reason for why the dependency is installed. If it is installed
18 | // because of a devDependency then that is okay. Warn when it is installed
19 | // because of a dependency
20 | const { dependencies: dependenciesObject } = JSON.parse(
21 | execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString()
22 | );
23 | const rootDependencies = Object.keys(dependenciesObject);
24 | const filteredRootDependencies = rootDependencies.filter((rootDependency) =>
25 | dependenciesKeys.includes(rootDependency)
26 | );
27 | if (filteredRootDependencies.length > 0) {
28 | const plural = filteredRootDependencies.length > 1;
29 | console.log("dependenciesObject: "+JSON.stringify(dependenciesObject,null,4))
30 | console.log("filteredRootDependencies: "+JSON.stringify(filteredRootDependencies,null,4))
31 | console.log(`
32 | ${chalk.whiteBright.bgYellow.bold(
33 | 'Webpack does not work with native dependencies.'
34 | )}
35 | ${chalk.bold(filteredRootDependencies.join(', '))} ${
36 | plural ? 'are native dependencies' : 'is a native dependency'
37 | } and should be installed inside of the "./release/app" folder.
38 | First, uninstall the packages from "./package.json":
39 | ${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')}
40 | ${chalk.bold(
41 | 'Then, instead of installing the package to the root "./package.json":'
42 | )}
43 | ${chalk.whiteBright.bgRed.bold('npm install your-package')}
44 | ${chalk.bold('Install the package to "./release/app/package.json"')}
45 | ${chalk.whiteBright.bgGreen.bold(
46 | 'cd ./release/app && npm install your-package'
47 | )}
48 | Read more about native dependencies at:
49 | ${chalk.bold(
50 | 'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure'
51 | )}
52 | `);
53 | process.exit(1);
54 | }
55 | } catch (e) {
56 | console.log('Native dependencies could not be checked');
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/.erb/scripts/check-node-env.js:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk';
2 |
3 | export default function checkNodeEnv(expectedEnv) {
4 | if (!expectedEnv) {
5 | throw new Error('"expectedEnv" not set');
6 | }
7 |
8 | if (process.env.NODE_ENV !== expectedEnv) {
9 | console.log(
10 | chalk.whiteBright.bgRed.bold(
11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config`
12 | )
13 | );
14 | process.exit(2);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.erb/scripts/check-port-in-use.js:
--------------------------------------------------------------------------------
1 | import chalk from 'chalk';
2 | import detectPort from 'detect-port';
3 |
4 | const port = process.env.PORT || '1212';
5 |
6 | detectPort(port, (err, availablePort) => {
7 | if (port !== String(availablePort)) {
8 | throw new Error(
9 | chalk.whiteBright.bgRed.bold(
10 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start`
11 | )
12 | );
13 | } else {
14 | process.exit(0);
15 | }
16 | });
17 |
--------------------------------------------------------------------------------
/.erb/scripts/clean.js:
--------------------------------------------------------------------------------
1 | import rimraf from 'rimraf';
2 | import webpackPaths from '../configs/webpack.paths';
3 |
4 | const foldersToRemove = [
5 | webpackPaths.distPath,
6 | webpackPaths.buildPath,
7 | webpackPaths.dllPath,
8 | ];
9 |
10 | foldersToRemove.forEach((folder) => {
11 | rimraf.sync(folder);
12 | });
13 |
--------------------------------------------------------------------------------
/.erb/scripts/delete-source-maps.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import rimraf from 'rimraf';
3 | import webpackPaths from '../configs/webpack.paths';
4 |
5 | export default function deleteSourceMaps() {
6 | rimraf.sync(path.join(webpackPaths.distMainPath, '*.js.map'));
7 | rimraf.sync(path.join(webpackPaths.distRendererPath, '*.js.map'));
8 | }
9 |
--------------------------------------------------------------------------------
/.erb/scripts/electron-rebuild.js:
--------------------------------------------------------------------------------
1 | import { execSync } from 'child_process';
2 | import fs from 'fs';
3 | import { dependencies } from '../../release/app/package.json';
4 | import webpackPaths from '../configs/webpack.paths';
5 |
6 | if (
7 | Object.keys(dependencies || {}).length > 0 &&
8 | fs.existsSync(webpackPaths.appNodeModulesPath)
9 | ) {
10 | const electronRebuildCmd =
11 | '../../node_modules/.bin/electron-rebuild --force --types prod,dev,optional --module-dir .';
12 | const cmd =
13 | process.platform === 'win32'
14 | ? electronRebuildCmd.replace(/\//g, '\\')
15 | : electronRebuildCmd;
16 | execSync(cmd, {
17 | cwd: webpackPaths.appPath,
18 | stdio: 'inherit',
19 | });
20 | }
21 |
--------------------------------------------------------------------------------
/.erb/scripts/link-modules.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import webpackPaths from '../configs/webpack.paths';
3 |
4 | const { srcNodeModulesPath } = webpackPaths;
5 | const { appNodeModulesPath } = webpackPaths;
6 |
7 | if (!fs.existsSync(srcNodeModulesPath) && fs.existsSync(appNodeModulesPath)) {
8 | fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction');
9 | }
10 |
--------------------------------------------------------------------------------
/.erb/scripts/notarize.js:
--------------------------------------------------------------------------------
1 | const { notarize } = require('electron-notarize');
2 | const { build } = require('../../package.json');
3 |
4 | exports.default = async function notarizeMacos(context) {
5 | const { electronPlatformName, appOutDir } = context;
6 | if (electronPlatformName !== 'darwin') {
7 | return;
8 | }
9 |
10 | if (process.env.CI !== 'true') {
11 | console.warn('Skipping notarizing step. Packaging is not running in CI');
12 | return;
13 | }
14 |
15 | if (!('APPLE_ID' in process.env && 'APPLE_ID_PASS' in process.env)) {
16 | console.warn(
17 | 'Skipping notarizing step. APPLE_ID and APPLE_ID_PASS env variables must be set'
18 | );
19 | return;
20 | }
21 |
22 | const appName = context.packager.appInfo.productFilename;
23 |
24 | await notarize({
25 | appBundleId: build.appId,
26 | appPath: `${appOutDir}/${appName}.app`,
27 | appleId: process.env.APPLE_ID,
28 | appleIdPassword: process.env.APPLE_ID_PASS,
29 | });
30 | };
31 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Coverage directory used by tools like istanbul
11 | coverage
12 | .eslintcache
13 |
14 | # Dependency directory
15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
16 | node_modules
17 |
18 | # OSX
19 | .DS_Store
20 |
21 | release/app/dist
22 | release/build
23 | .erb/dll
24 |
25 | .idea
26 | npm-debug.log.*
27 | *.css.d.ts
28 | *.sass.d.ts
29 | *.scss.d.ts
30 |
31 | # eslint ignores hidden directories by default:
32 | # https://github.com/eslint/eslint/issues/8429
33 | !.erb
34 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: 'erb',
3 | rules: {
4 | // A temporary hack related to IDE not resolving correct package.json
5 | 'import/no-extraneous-dependencies': 'off',
6 | 'import/no-unresolved': 'error',
7 | // Since React 17 and typescript 4.1 you can safely disable the rule
8 | 'react/react-in-jsx-scope': 'off',
9 | },
10 | parserOptions: {
11 | ecmaVersion: 2020,
12 | sourceType: 'module',
13 | project: './tsconfig.json',
14 | tsconfigRootDir: __dirname,
15 | createDefaultProgram: true,
16 | },
17 | settings: {
18 | 'import/resolver': {
19 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below
20 | node: {},
21 | webpack: {
22 | config: require.resolve('./.erb/configs/webpack.config.eslint.ts'),
23 | },
24 | typescript: {},
25 | },
26 | 'import/parsers': {
27 | '@typescript-eslint/parser': ['.ts', '.tsx'],
28 | },
29 | },
30 | };
31 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 | *.exe binary
3 | *.png binary
4 | *.jpg binary
5 | *.jpeg binary
6 | *.ico binary
7 | *.icns binary
8 | *.eot binary
9 | *.otf binary
10 | *.ttf binary
11 | *.woff binary
12 | *.woff2 binary
13 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [electron-react-boilerplate-nostr, wds4]
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/1-Bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: You're having technical issues. 🐞
4 | labels: 'bug'
5 | ---
6 |
7 |
8 |
9 | ## Prerequisites
10 |
11 |
12 |
13 | - [ ] Using npm
14 | - [ ] Using an up-to-date [`main` branch](https://github.com/electron-react-boilerplate/electron-react-boilerplate/tree/main)
15 | - [ ] Using latest version of devtools. [Check the docs for how to update](https://electron-react-boilerplate.js.org/docs/dev-tools/)
16 | - [ ] Tried solutions mentioned in [#400](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/400)
17 | - [ ] For issue in production release, add devtools output of `DEBUG_PROD=true npm run build && npm start`
18 |
19 | ## Expected Behavior
20 |
21 |
22 |
23 | ## Current Behavior
24 |
25 |
26 |
27 | ## Steps to Reproduce
28 |
29 |
30 |
31 |
32 | 1.
33 |
34 | 2.
35 |
36 | 3.
37 |
38 | 4.
39 |
40 | ## Possible Solution (Not obligatory)
41 |
42 |
43 |
44 | ## Context
45 |
46 |
47 |
48 |
49 |
50 | ## Your Environment
51 |
52 |
53 |
54 | - Node version :
55 | - electron-react-boilerplate version or branch :
56 | - Operating System and version :
57 | - Link to your project :
58 |
59 |
68 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/2-Question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question
3 | about: Ask a question.❓
4 | labels: 'question'
5 | ---
6 |
7 | ## Summary
8 |
9 |
10 |
11 |
20 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/3-Feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: You want something added to the boilerplate. 🎉
4 | labels: 'enhancement'
5 | ---
6 |
7 |
16 |
--------------------------------------------------------------------------------
/.github/config.yml:
--------------------------------------------------------------------------------
1 | requiredHeaders:
2 | - Prerequisites
3 | - Expected Behavior
4 | - Current Behavior
5 | - Possible Solution
6 | - Your Environment
7 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 60
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - discussion
8 | - security
9 | # Label to use when marking an issue as stale
10 | staleLabel: wontfix
11 | # Comment to post when marking an issue as stale. Set to `false` to disable
12 | markComment: >
13 | This issue has been automatically marked as stale because it has not had
14 | recent activity. It will be closed if no further activity occurs. Thank you
15 | for your contributions.
16 | # Comment to post when closing a stale issue. Set to `false` to disable
17 | closeComment: false
18 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ "main" ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ "main" ]
20 | schedule:
21 | - cron: '44 16 * * 4'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'javascript' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v3
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v2
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 |
52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
53 | # queries: security-extended,security-and-quality
54 |
55 |
56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
57 | # If this step fails, then you should remove it and run the build manually (see below)
58 | - name: Autobuild
59 | uses: github/codeql-action/autobuild@v2
60 |
61 | # ℹ️ Command-line programs to run using the OS shell.
62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
63 |
64 | # If the Autobuild fails above, remove it and uncomment the following three lines.
65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
66 |
67 | # - run: |
68 | # echo "Run, Build Application using script"
69 | # ./location_of_script_within_repo/buildscript.sh
70 |
71 | - name: Perform CodeQL Analysis
72 | uses: github/codeql-action/analyze@v2
73 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | publish:
10 | # To enable auto publishing to github, update your electron publisher
11 | # config in package.json > "build" and remove the conditional below
12 | if: ${{ github.repository_owner == 'electron-react-boilerplate' }}
13 |
14 | runs-on: ${{ matrix.os }}
15 |
16 | strategy:
17 | matrix:
18 | os: [macos-latest]
19 |
20 | steps:
21 | - name: Checkout git repo
22 | uses: actions/checkout@v3
23 |
24 | - name: Install Node and NPM
25 | uses: actions/setup-node@v3
26 | with:
27 | node-version: 16
28 | cache: npm
29 |
30 | - name: Install and build
31 | run: |
32 | npm install
33 | npm run postinstall
34 | npm run build
35 |
36 | - name: Publish releases
37 | env:
38 | # These values are used for auto updates signing
39 | APPLE_ID: ${{ secrets.APPLE_ID }}
40 | APPLE_ID_PASS: ${{ secrets.APPLE_ID_PASS }}
41 | CSC_LINK: ${{ secrets.CSC_LINK }}
42 | CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
43 | # This is used for uploading release assets to github
44 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45 | run: |
46 | npm exec electron-builder -- --publish always --win --mac --linux
47 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | runs-on: ${{ matrix.os }}
8 |
9 | strategy:
10 | matrix:
11 | os: [macos-latest, windows-latest, ubuntu-latest]
12 |
13 | steps:
14 | - name: Check out Git repository
15 | uses: actions/checkout@v3
16 |
17 | - name: Install Node.js and NPM
18 | uses: actions/setup-node@v3
19 | with:
20 | node-version: 16
21 | cache: npm
22 |
23 | - name: npm install
24 | run: |
25 | npm install
26 |
27 | - name: npm test
28 | env:
29 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30 | run: |
31 | npm run package
32 | npm run lint
33 | npm exec tsc
34 | npm test
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Coverage directory used by tools like istanbul
11 | coverage
12 | .eslintcache
13 |
14 | # Dependency directory
15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
16 | node_modules
17 |
18 | # OSX
19 | .DS_Store
20 |
21 | release/app/dist
22 | release/app/sql
23 | release/build
24 | .erb/dll
25 |
26 | .idea
27 | npm-debug.log.*
28 | *.css.d.ts
29 | *.sass.d.ts
30 | *.scss.d.ts
31 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["dbaeumer.vscode-eslint", "EditorConfig.EditorConfig"]
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Electron: Main",
6 | "type": "node",
7 | "request": "launch",
8 | "protocol": "inspector",
9 | "runtimeExecutable": "npm",
10 | "runtimeArgs": ["run", "start"],
11 | "env": {
12 | "MAIN_ARGS": "--inspect=5858 --remote-debugging-port=9223"
13 | }
14 | },
15 | {
16 | "name": "Electron: Renderer",
17 | "type": "chrome",
18 | "request": "attach",
19 | "port": 9223,
20 | "webRoot": "${workspaceFolder}",
21 | "timeout": 15000
22 | }
23 | ],
24 | "compounds": [
25 | {
26 | "name": "Electron: All",
27 | "configurations": ["Electron: Main", "Electron: Renderer"]
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.associations": {
3 | ".eslintrc": "jsonc",
4 | ".prettierrc": "jsonc",
5 | ".eslintignore": "ignore"
6 | },
7 |
8 | "eslint.validate": [
9 | "javascript",
10 | "javascriptreact",
11 | "html",
12 | "typescriptreact"
13 | ],
14 |
15 | "javascript.validate.enable": false,
16 | "javascript.format.enable": false,
17 | "typescript.format.enable": false,
18 |
19 | "search.exclude": {
20 | ".git": true,
21 | ".eslintcache": true,
22 | ".erb/dll": true,
23 | "release/{build,app/dist}": true,
24 | "node_modules": true,
25 | "npm-debug.log.*": true,
26 | "test/**/__snapshots__": true,
27 | "package-lock.json": true,
28 | "*.{css,sass,scss}.d.ts": true
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at electronreactboilerplate@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-present Electron React Boilerplate
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | As of early 2023, this project has been refactored and moved to [Pretty Good Apps](https://github.com/wds4/pretty-good). See also this repo: [Decentralized Curation of Simple Lists](https://github.com/wds4/DCoSL), which specifies the protocol for decentralized reputation that is implemented in the Pretty Good Apps client.
2 |
3 |
4 |
5 | Boilerplate nostr desktop client using electron, react, sqlite3, and electron-builder. [electron-react-boilerplate](https://github.com/electron-react-boilerplate/electron-react-boilerplate) is my starting point, to which I have added [nostr-tools](https://github.com/fiatjaf/nostr-tools) and [nostr-react](https://github.com/t4t5/nostr-react), as well as [sqlite3](https://github.com/TryGhost/node-sqlite3).
6 |
7 | Once this project has progressed far enough, I plan to incorporate a system of decentralized reputation, two projects that I call the Grapevine and the Concept Graph. I have some very thoroughly fleshed out ideas on how to do this.
8 |
9 | However, I think a lot of people have a lot of different ideas on how to build decentralized ratings, reputation, and web of trust. In my mind, we haven't yet figured out how to do this right, and this is perhaps the greatest unsolved problem in this space. Therefore, I also plan to maintain the electron-nostr boilerpate as a template minus my own proposed solutions (the Grapevine and the Concept Graph). My hope is that the electron-nostr template will be a useful tool for a wide range of people who want to play around with their own ideas on how to build a web of trust. If you have ideas you want to try out and you know css, html, and javascript, hopefully your learning curve (basics of git, react, sqlite3) will be as painless as possible. The more ideas we can try, the better for us all!
10 |
11 | I have been using [vis.js](https://visjs.org) tools (currently: [vis-data](https://github.com/visjs/vis-data) and [vis-network](https://github.com/visjs/vis-network)) to visualize the Grapevine web of trust, so I may include these in the electron-nostr boilerplate as well as any other tools that I think may be generically useful for others in their experimentation. Support for IPFS and [gun](https://github.com/amark/gun) come to mind.
12 |
13 | Let me know if you would like to use and/or contribute to the basic electron-nostr template. If there is enough interest, I may be able to secure a small amount of funding to support its development and maintenance.
14 |
15 | ## Features
16 |
17 | Currently, this app works in dev mode. The mac release (v0.1.x-alpha) also is functional. linux and windows releases are not yet functional.
18 |
19 | - automatic generation of privkey and pubkey
20 | - abiliity to import privkey and pubkey
21 | - view and manage basics of your profile (name, picture_url, etc)
22 | - support for multiple profiles
23 | - main feed in 3 modes: "following" (your follows), "Extended following" (+ their follows) and "firehose" (completely unfiltered)
24 | - view other profiles
25 | - follow / unfollow button
26 | - submit a post
27 | - reply to a post
28 | - thread viewer (very basic implementation)
29 | - youtube video playback
30 | - extended
31 | - graphical visualization of extended following list using vis.js (in v0.2.0-alpha release)
32 |
33 |
34 |
35 | ## Todo
36 |
37 | - direct messaging
38 | - refactor thread viewer
39 | - refactor following page (need to request events for info on all users at once, not separate request for each user)
40 | - add retweet, like buttons
41 | - add notifications
42 | - show pictures in posts
43 | - implement caching (redux?)
44 | - calculate and show number of followers
45 | - maybe change location of sql database
46 | - lightning invoice support
47 | - back button
48 | - bookmark events
49 | - pin events
50 | - support for multiple accounts
51 | - support for multithreading (multiple electron rendering processes) using the same techniques of [this template](https://github.com/wds4/electron-react-boilerplate-multiple-windows)
52 |
53 | ## Known issues
54 |
55 | - Websockets sometimes do not reconnect after being dropped
56 | - Follow buttons sometimes do not show change in follow status (or are slow to reflect change)
57 | - v0.1.x-alpha linux build so far does not function - problem with sqlite3 I think
58 |
59 | ## Install
60 |
61 | Clone the repo, install dependencies, make sql directory:
62 |
63 | ```bash
64 | git clone https://github.com/wds4/electron-react-boilerplate-nostr.git your-project-name
65 | cd your-project-name
66 | npm install
67 | cd release/app
68 | mkdir sql
69 | cd ../..
70 | ```
71 |
72 | ## Starting Development
73 |
74 | Start the app in the `dev` environment:
75 |
76 | ```bash
77 | npm start
78 | ```
79 |
80 | ## Packaging for Production
81 |
82 | To package apps for the local platform:
83 |
84 | ```bash
85 | npm run package
86 | ```
87 | To package for other platforms, see [these instructions](https://electron-react-boilerplate.js.org/docs/packaging).
88 |
89 | ## License
90 |
91 | MIT © [Electron React Boilerplate](https://github.com/electron-react-boilerplate)
92 |
--------------------------------------------------------------------------------
/assets/assets.d.ts:
--------------------------------------------------------------------------------
1 | type Styles = Record;
2 |
3 | declare module '*.svg' {
4 | export const ReactComponent: React.FC>;
5 |
6 | const content: string;
7 | export default content;
8 | }
9 |
10 | declare module '*.png' {
11 | const content: string;
12 | export default content;
13 | }
14 |
15 | declare module '*.jpg' {
16 | const content: string;
17 | export default content;
18 | }
19 |
20 | declare module '*.scss' {
21 | const content: Styles;
22 | export default content;
23 | }
24 |
25 | declare module '*.sass' {
26 | const content: Styles;
27 | export default content;
28 | }
29 |
30 | declare module '*.css' {
31 | const content: Styles;
32 | export default content;
33 | }
34 |
--------------------------------------------------------------------------------
/assets/entitlements.mac.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.cs.allow-unsigned-executable-memory
6 |
7 | com.apple.security.cs.allow-jit
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/assets/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wds4/electron-react-boilerplate-nostr/e8d90f8b520381e54d80c77a919ea27e839c33ea/assets/icon.icns
--------------------------------------------------------------------------------
/assets/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wds4/electron-react-boilerplate-nostr/e8d90f8b520381e54d80c77a919ea27e839c33ea/assets/icon.ico
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wds4/electron-react-boilerplate-nostr/e8d90f8b520381e54d80c77a919ea27e839c33ea/assets/icon.png
--------------------------------------------------------------------------------
/assets/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/assets/icons/1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wds4/electron-react-boilerplate-nostr/e8d90f8b520381e54d80c77a919ea27e839c33ea/assets/icons/1024x1024.png
--------------------------------------------------------------------------------
/assets/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wds4/electron-react-boilerplate-nostr/e8d90f8b520381e54d80c77a919ea27e839c33ea/assets/icons/128x128.png
--------------------------------------------------------------------------------
/assets/icons/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wds4/electron-react-boilerplate-nostr/e8d90f8b520381e54d80c77a919ea27e839c33ea/assets/icons/16x16.png
--------------------------------------------------------------------------------
/assets/icons/24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wds4/electron-react-boilerplate-nostr/e8d90f8b520381e54d80c77a919ea27e839c33ea/assets/icons/24x24.png
--------------------------------------------------------------------------------
/assets/icons/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wds4/electron-react-boilerplate-nostr/e8d90f8b520381e54d80c77a919ea27e839c33ea/assets/icons/256x256.png
--------------------------------------------------------------------------------
/assets/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wds4/electron-react-boilerplate-nostr/e8d90f8b520381e54d80c77a919ea27e839c33ea/assets/icons/32x32.png
--------------------------------------------------------------------------------
/assets/icons/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wds4/electron-react-boilerplate-nostr/e8d90f8b520381e54d80c77a919ea27e839c33ea/assets/icons/48x48.png
--------------------------------------------------------------------------------
/assets/icons/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wds4/electron-react-boilerplate-nostr/e8d90f8b520381e54d80c77a919ea27e839c33ea/assets/icons/512x512.png
--------------------------------------------------------------------------------
/assets/icons/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wds4/electron-react-boilerplate-nostr/e8d90f8b520381e54d80c77a919ea27e839c33ea/assets/icons/64x64.png
--------------------------------------------------------------------------------
/assets/icons/96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wds4/electron-react-boilerplate-nostr/e8d90f8b520381e54d80c77a919ea27e839c33ea/assets/icons/96x96.png
--------------------------------------------------------------------------------
/notes.txt:
--------------------------------------------------------------------------------
1 |
2 | 19 Dec 2022
3 | electron-react-boilerplate-nostr
4 | Purpose: to do a hello world of nostr in erb
5 | Forked erb in github
6 | clones to MBPro
7 |
8 | made cosmetic changes: imported from my template electron-react-boilerplate-ipfs
9 |
10 | following https://github.com/fiatjaf/nostr-tools
11 |
12 | npm install nostr-tools
13 | added to home.js:
14 | import { generatePrivateKey, getPublicKey } from 'nostr-tools'
15 |
16 | Got polyfill error; this is how I fixed it:
17 | Followed advice from:
18 | https://stackoverflow.com/questions/64557638/how-to-polyfill-node-core-modules-in-webpack-5
19 | and updated .erb/configs/webpack.config.base.ts by adding under resolve:
20 | fallback: {
21 | "stream": false,
22 | }
23 |
--------------------------------------------------------------------------------
/release/app/images/missingAvatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wds4/electron-react-boilerplate-nostr/e8d90f8b520381e54d80c77a919ea27e839c33ea/release/app/images/missingAvatar.png
--------------------------------------------------------------------------------
/release/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron-react-boilerplate-nostr",
3 | "version": "0.3.1-alpha",
4 | "description": "A foundation for building a decentralized web client using electron and nostr",
5 | "license": "MIT",
6 | "author": {
7 | "name": "Electron React Boilerplate Nostr Maintainers",
8 | "url": "https://github.com/wds4"
9 | },
10 | "main": "./dist/main/main.js",
11 | "scripts": {
12 | "rebuild": "node -r ts-node/register ../../.erb/scripts/electron-rebuild.js",
13 | "postinstall": "npm run rebuild && npm run link-modules",
14 | "link-modules": "node -r ts-node/register ../../.erb/scripts/link-modules.ts"
15 | },
16 | "dependencies": {
17 | "sqlite3": "^5.1.4"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/__tests__/App.test.tsx:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom';
2 | import { render } from '@testing-library/react';
3 | import App from '../renderer/App';
4 |
5 | describe('App', () => {
6 | it('should render', () => {
7 | expect(render( )).toBeTruthy();
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/src/main/preload.ts:
--------------------------------------------------------------------------------
1 | import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
2 |
3 | export type Channels = 'ipc-example';
4 |
5 | contextBridge.exposeInMainWorld('electron', {
6 | ipcRenderer: {
7 | sendMessage(channel: Channels, args: unknown[]) {
8 | ipcRenderer.send(channel, args);
9 | },
10 | on(channel: Channels, func: (...args: unknown[]) => void) {
11 | const subscription = (_event: IpcRendererEvent, ...args: unknown[]) =>
12 | func(...args);
13 | ipcRenderer.on(channel, subscription);
14 |
15 | return () => {
16 | ipcRenderer.removeListener(channel, subscription);
17 | };
18 | },
19 | once(channel: Channels, func: (...args: unknown[]) => void) {
20 | ipcRenderer.once(channel, (_event, ...args) => func(...args));
21 | },
22 | },
23 | });
24 |
--------------------------------------------------------------------------------
/src/main/util.ts:
--------------------------------------------------------------------------------
1 | /* eslint import/prefer-default-export: off */
2 | import { URL } from 'url';
3 | import path from 'path';
4 |
5 | export function resolveHtmlPath(htmlFileName: string) {
6 | if (process.env.NODE_ENV === 'development') {
7 | const port = process.env.PORT || 1212;
8 | const url = new URL(`http://localhost:${port}`);
9 | url.pathname = htmlFileName;
10 | return url.href;
11 | }
12 | return `file://${path.resolve(__dirname, '../renderer/', htmlFileName)}`;
13 | }
14 |
--------------------------------------------------------------------------------
/src/renderer/App-depr.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { MemoryRouter as Router, Routes, Route } from 'react-router-dom';
3 | import { NostrProvider } from 'nostr-react';
4 | import * as MiscAppFxns from './lib/app/misc';
5 | import ErrorBoundary from './errorBoundary';
6 | import * as TsdxTest from '@wds4/tsdxtest';
7 | import * as TsPlex from '@wds4/tsplex';
8 |
9 |
10 | // import { sum as summ } from '@wds4/tsdxtest';
11 |
12 | // import { Provider } from 'react-redux'
13 | // import store from './store'
14 |
15 | // NOSTR PAGES
16 | import LandingPage from './pages/landingPage/index';
17 | import Settings from './pages/settings/index';
18 | import Settings_keys from './pages/settings/profilekeys/index';
19 | import Settings_relays from './pages/settings/relays/index';
20 | import Settings_sql from './pages/settings/sql/index';
21 | import MainFeed from './pages/mainFeed/index';
22 | import ManageChannels from './pages/manageChannels/index';
23 | import UserProfile from './pages/userProfile/index';
24 | import FollowingList from './pages/followingList/index';
25 | import ExtendedFollowingList from './pages/extendedFollowingList/index';
26 | import DownloadProfiles from './pages/downloadProfiles/index';
27 | import UserList from './pages/userList/index';
28 | import MyProfile from './pages/myProfile/index';
29 | import EditMyProfile from './pages/editMyProfile/index';
30 | import CreatePost from './pages/createPost/index';
31 | import Thread from './pages/thread/index';
32 | import Reply from './pages/reply/index';
33 | import SearchForUser from './pages/searchForUser/index';
34 | import GrapevineSettings from './pages/grapevineSettings/index';
35 | import VisJsHelloWorld from './pages/visJsHelloWorld/index';
36 | import DirectMessageConversation from './pages/directMessageConversation/index';
37 |
38 | // GRAPEVINE PAGES
39 | import GrapevineLandingPage from './grapevine/landingPage/index';
40 | import NostrFollowGrapevineVisualization from './grapevine/nostrFollowGrapevine/index';
41 |
42 | import './css/app.css';
43 | import './css/mastheads.css';
44 | import './css/navbars.css';
45 | import './css/feed.css';
46 | import './css/userProfile.css';
47 | import './css/myProfile.css';
48 | import './css/editMyProfile.css';
49 | import './css/newPost.css';
50 | import './css/userList.css';
51 | import './css/follows.css';
52 | import './css/grapevine.css';
53 | import './css/toggleSwitch1.css'; // probably will use just one of these
54 | import './css/grapevineToggleSwitch.css';
55 | import './css/grapevineSettings.css';
56 | import './css/nfgGraphic.css';
57 | import './css/youTubeEmbed.css';
58 | import './css/misc.css';
59 | import './css/settings.css';
60 | import './css/directMessaging.css';
61 |
62 | import { asyncSql } from "./index.tsx";
63 |
64 | const fetchRelays = MiscAppFxns.fetchRelays;
65 | const timeout = MiscAppFxns.timeout;
66 |
67 | const relayUrls = [
68 | "wss://nostr-pub.wellorder.net",
69 | "wss://nostr-relay.untethr.me",
70 | "wss://relay.damus.io",
71 | "wss://nostr-relay.wlvs.space",
72 | "wss://nostr.fmt.wiz.biz",
73 | "wss://nostr.oxtr.dev",
74 | ];
75 |
76 | // window.relayUrls = [];
77 |
78 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
79 |
80 | export default class App extends React.Component {
81 | constructor(props) {
82 | super(props);
83 | this.state = {
84 | }
85 | }
86 |
87 | async componentDidMount() {
88 | }
89 | render() {
90 | return (
91 |
92 |
93 |
94 | sum of 1+2: {TsPlex.grapevineSum(1,2)}
95 |
96 |
97 | {typeof this.state.relayUrls}
98 | {this.state.relayUrls.length}
99 | {this.state.relayUrls}
100 |
101 | {window.relayUrls}
102 |
103 |
104 |
105 |
106 | } />
107 | } />
108 | } />
109 | } />
110 | } />
111 | } />
112 | } />
113 | } />
114 | } />
115 | } />
116 | } />
117 | } />
118 | } />
119 | } />
120 | } />
121 | } />
122 | } />
123 | } />
124 | } />
125 | } />
126 | } />
127 | } />
128 | } />
129 | } />
130 | } />
131 | } />
132 |
133 |
134 |
135 |
136 |
137 | );
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/renderer/css/app.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: 'Helvetica';
4 | position: relative;
5 | color: white;
6 | height: 100%;
7 | width:100%;
8 | overflow-y: hidden;
9 | display: flex;
10 | justify-content: center;
11 | align-items: center;
12 | }
13 |
14 | html, body, body>div, #app {
15 | color:black;
16 | box-sizing: border-box;
17 | height:100%;
18 | width:100%;
19 | }
20 |
21 | fieldset#app {
22 | box-sizing: border-box;
23 | height:100%;
24 | width:100%;
25 | }
26 |
27 | div, pre {
28 | box-sizing: border-box;
29 | vertical-align:top;
30 |
31 | white-space: pre-wrap;
32 | white-space: -moz-pre-wrap;
33 | white-space: -pre-wrap;
34 | white-space: -o-pre-wrap;
35 | word-wrap: break-word;
36 |
37 | }
38 |
39 | :root {
40 | --select-border: ;
41 | --select-focus: #101484;
42 | --select-arrow: var(--select-border);
43 | }
44 |
45 | select {
46 | min-width: 5ch;
47 | max-width: 30ch;
48 | border: 1px solid #393939;
49 | border-radius: 0.25em;
50 | padding: 3px;
51 | font-size: 1.0rem;
52 | cursor: pointer;
53 | background: linear-gradient(to bottom, #ffffff 0%, #e5e5e5 100%);
54 | }
55 |
56 |
57 | div.h1 {
58 | font-size:36px;
59 | text-align:center;
60 | }
61 | div.h2 {
62 | font-size:28px;
63 | text-align:center;
64 | }
65 | div.h3 {
66 | font-size:22px;
67 | text-align:center;
68 | }
69 | div.h4 {
70 | font-size:18px;
71 | text-align:center;
72 | }
73 | .leftCol {
74 | display:inline-block;
75 | width:200px;
76 | }
77 | .rightCol {
78 | display:inline-block;
79 | width:700px;
80 | }
81 | #root {
82 | padding:0px;
83 | margin:0px;
84 | height:100%;
85 | width:100%;
86 | }
87 | #app {
88 | padding:0px;
89 | margin:0px;
90 | height:100%;
91 | width:100%;
92 | white-space: nowrap;
93 | }
94 | #menuCol {
95 | display:inline-block;
96 | height:100%;
97 | }
98 | #mainCol {
99 | padding:0px 5px 0px 5px;
100 | margin:0px;
101 | display:inline-block;
102 | height:100%;
103 | background-color:#EFEFEF;
104 | }
105 | #mainPanel {
106 | width:100%;
107 | height:80%;
108 | padding-top:20px;
109 | font-size:12px;
110 | overflow:scroll;
111 | }
112 |
113 | .doSomethingButton {
114 | font-size:12px;
115 | display:inline-block;
116 | border:1px solid black;
117 | border-radius:3px;
118 | padding:4px;
119 | margin:4px;
120 | background-color:#DFDFDF;
121 | color:black;
122 | }
123 | .doSomethingButton:hover {
124 | background-color:yellow;
125 | }
126 | .doSomethingButton_small {
127 | font-size:10px;
128 | display:inline-block;
129 | border:1px solid black;
130 | border-radius:3px;
131 | padding:1px;
132 | margin:1px;
133 | background-color:#DFDFDF;
134 | color:black;
135 | }
136 | .doSomethingButton_small:hover {
137 | background-color:yellow;
138 | }
139 | .doSomethingButton_tiny {
140 | font-size:9px;
141 | display:inline-block;
142 | border:1px solid black;
143 | border-radius:2px;
144 | padding:0px 1px 0px 1px;
145 | margin:0px 1px 0px 1px;
146 | background-color:#DFDFDF;
147 | color:black;
148 | }
149 | .doSomethingButton_tiny:hover {
150 | background-color:yellow;
151 | }
152 | .block_show {
153 | display:block;
154 | }
155 | .block_hide {
156 | display:none;
157 | }
158 |
--------------------------------------------------------------------------------
/src/renderer/css/directMessaging.css:
--------------------------------------------------------------------------------
1 | .directMessageContainer {
2 | margin-bottom:10px;
3 | width:100%;
4 | }
5 | .directMessageContainerFloatLeft {
6 | float:left;
7 | text-align:left;
8 | }
9 | .directMessageContainerFloatRight {
10 | float:right;
11 | text-align:right;
12 | }
13 | .directMessageContentContainer {
14 | display:inline-block;
15 | font-size:16px;
16 | max-width:80%;
17 | background-color:white;
18 | border-radius:5px;
19 | margin-left:5px;
20 | border:1px solid grey;
21 | padding:5px 10px 5px 10px;
22 | color:black;
23 | }
24 | .directMessageTimeContainer {
25 | display:inline-block;
26 | }
27 |
28 | /* Tooltip container */
29 | .directMessageTooltip {
30 | position: relative;
31 | }
32 |
33 | /* Tooltip text */
34 | .directMessageTooltip .directMessageTooltipText {
35 | visibility: hidden;
36 | background-color: black;
37 | color: #fff;
38 | text-align: center;
39 | padding: 5px 10px 5px 10px;
40 | border-radius: 6px;
41 |
42 | /* Position the tooltip text - see examples below! */
43 | position: absolute;
44 | top:0px;
45 | z-index: 1;
46 | }
47 |
48 | /* Show the tooltip text when you mouse over the tooltip container */
49 | .directMessageTooltip:hover .directMessageTooltipText {
50 | visibility: visible;
51 | }
52 |
--------------------------------------------------------------------------------
/src/renderer/css/editMyProfile.css:
--------------------------------------------------------------------------------
1 | .editProfileFieldContainer {
2 |
3 | }
4 | .editProfileLeftColContainer {
5 | display:inline-block;
6 | width:200px;
7 | text-align:right;
8 | }
9 | .editProfileRightColContainer {
10 | display:inline-block;
11 | width:400px;
12 | margin-left:10px;
13 | }
14 |
--------------------------------------------------------------------------------
/src/renderer/css/feed.css:
--------------------------------------------------------------------------------
1 | .mainFeedContainer {
2 | margin:0px 5px 0px 5px;
3 | }
4 | .eventContainer {
5 | margin-bottom:10px;
6 | }
7 | .smallAvatarContainer {
8 | display:inline-block;
9 | position:relative;
10 | width:50px;
11 | height:50px;
12 | }
13 | .smallAvatarBox {
14 | display:inline-block;
15 | background-color:white;
16 | border:1px solid black;
17 | border-radius:250px;
18 | width:95%;
19 | height:95%;
20 | margin: 0;
21 | position: absolute;
22 | top: 50%;
23 | left: 50%;
24 | transform: translate(-50%, -50%);
25 | }
26 | .smallAvatarBox_show {
27 | display:inline-block;
28 | background-color:white;
29 | border:1px solid black;
30 | border-radius:250px;
31 | width:95%;
32 | height:95%;
33 | margin: 0;
34 | position: absolute;
35 | top: 50%;
36 | left: 50%;
37 | transform: translate(-50%, -50%);
38 | }
39 | .smallAvatarBox_hide {
40 | display:none;
41 | background-color:white;
42 | border:1px solid black;
43 | border-radius:250px;
44 | width:95%;
45 | height:95%;
46 | margin: 0;
47 | position: absolute;
48 | top: 50%;
49 | left: 50%;
50 | transform: translate(-50%, -50%);
51 | }
52 | .eventMainBodyContainer {
53 | display:inline-block;
54 | width:calc(100% - 70px);
55 | min-height:50px;
56 | background-color:white;
57 | border-radius:10px;
58 | margin-left:5px;
59 | border:1px solid grey;
60 | padding:3px;
61 | }
62 | .eventNameAndTimeContainer {
63 | height:20px;
64 | padding:2px;
65 | font-size:12px;
66 | padding:2px;
67 | }
68 | .eventNameContainer {
69 | display:inline-block;
70 | color:black;
71 | margin-right:10px;
72 | max-width:80%;
73 | overflow:scroll;
74 | padding-left:3px;
75 | }
76 | .eventNameContainer:hover {
77 | border-radius:5px;
78 | padding-left:3px;
79 | background-color:grey;
80 | color:white;
81 | }
82 | .eventTimeContainer {
83 | display:inline-block;
84 | color:grey;
85 | float:right;
86 | }
87 | .eventContentContainer {
88 | padding:5px;
89 | text-decoration:none;
90 | font-size:16px;
91 | color:black;
92 | display:block;
93 | border-radius:5px;
94 | }
95 | .eventContentContainer:hover {
96 | background-color:grey;
97 | }
98 | .nameKnown {
99 | color:black;
100 | }
101 | .nameUnknown {
102 | color:grey;
103 | }
104 | .mainFeedTypeSelector {
105 | margin-bottom:30px;
106 | position:absolute;
107 | right:5px;
108 | }
109 | .numFollowingContainer_show {
110 | display:inline-block;
111 | margin-right:10px;
112 | }
113 | .numFollowingContainer_hide {
114 | display:none;
115 | margin-right:10px;
116 | }
117 | .numFFollowingContainer_show {
118 | display:inline-block;
119 | margin-right:10px;
120 | }
121 | .numFFollowingContainer_hide {
122 | display:none;
123 | margin-right:10px;
124 | }
125 | .eventActionButtonsContainer {
126 | padding:2px;
127 | }
128 | .singleActionButtonContainer {
129 | margin:0px 10px 0px 10px;
130 | padding:2px 10px 2px 10px;
131 | border-radius:5px;
132 | display:inline-block;
133 | }
134 | .singleActionButtonContainer:hover {
135 | background-color:grey;
136 | }
137 | .rootEventContainer {
138 | border-top:20px;
139 | border-bottom:20px;
140 | border-color:brown;
141 | border-style: solid;
142 | background-color:brown;
143 | border-radius:10px;
144 | }
145 | .expandedEventContainer {
146 | border-top:20px;
147 | border-bottom:20px;
148 | border-color:green;
149 | border-style: solid;
150 | background-color:green;
151 | border-radius:10px;
152 | }
153 | .replyContainer_show {
154 | border-radius:10px;
155 | margin-left:55px;
156 | margin-right:15px;
157 | margin-top:5px;
158 | vertical-align:bottom;
159 | }
160 | .replyContainer_hide {
161 | display:none;
162 | }
163 |
--------------------------------------------------------------------------------
/src/renderer/css/follows.css:
--------------------------------------------------------------------------------
1 | .followsNavLink {
2 | display:inline-block;
3 | text-decoration:none;
4 | }
5 | .followButton {
6 | border:1px solid black;
7 | border-radius:10px;
8 | font-size:12px;
9 | display:inline-block;
10 | padding:4px;
11 | margin:4px;
12 | background-color:#DFDFDF;
13 | color:black;
14 | }
15 | .unfollowButton {
16 | border:1px solid black;
17 | border-radius:10px;
18 | font-size:12px;
19 | display:inline-block;
20 | padding:4px;
21 | margin:4px;
22 | background-color:yellow;
23 | color:black;
24 | }
25 | .followButton:hover {
26 | background-color:yellow;
27 | }
28 | .unfollowButton:hover {
29 | background-color:#DFDFDF;
30 | }
31 | .followCountContainer {
32 | margin-top:10px;
33 | }
34 |
--------------------------------------------------------------------------------
/src/renderer/css/grapevine.css:
--------------------------------------------------------------------------------
1 | .userProfileGrapevineContainer {
2 | border-radius:10px;
3 | background-color:#EFEFEF;
4 | }
5 | .userProfileGrapevineContainer_hidden {
6 | display:none;
7 | }
8 | .leaveRatingButton {
9 | font-size:12px;
10 | display:inline-block;
11 | border:1px solid black;
12 | border-radius:5px;
13 | padding:4px;
14 | margin-left:10px;
15 | background-color:#DFDFDF;
16 | color:black;
17 | }
18 | .leaveRatingButton:hover {
19 | background-color:yellow;
20 | }
21 | .leaveRatingButton_hidden {
22 | display:none;
23 | }
24 | .grapevineSelector {
25 | margin-left:10px;
26 | position:absolute;
27 | right:50px;
28 | }
29 | .grapevineSelector_hidden {
30 | display:none;
31 | }
32 | .grapevineContainerLeftCol {
33 | display:inline-block;
34 | width:120px;
35 | padding-top:5px;
36 | position:relative;
37 | font-size:14px;
38 | text-align:left;
39 | color:purple;
40 | }
41 | .grapevineContainerLeftCol:hover {
42 | background-color:yellow;
43 | }
44 |
45 | /* Tooltip text */
46 | .grapevineContainerLeftCol .tooltiptext {
47 | display: none;
48 | color: #fff;
49 | background-color:purple;
50 | border:1px solid black;
51 | padding: 5px;
52 | border-radius: 6px;
53 | position: absolute;
54 | z-index: 2;
55 | top:-5px;
56 | left:110px;
57 | font-size:16px;
58 | width:350px;
59 | }
60 |
61 | /* Show the tooltip text when you mouse over the tooltip container */
62 | .grapevineContainerLeftCol:hover .tooltiptext {
63 | display: inline-block;
64 | }
65 | .grapevineItemContainer {
66 | margin-bottom:5px;
67 | position:relative;
68 | }
69 | .grapevineItemContainer_hidden {
70 | margin-bottom:5px;
71 | position:relative;
72 | display:none;
73 | }
74 |
--------------------------------------------------------------------------------
/src/renderer/css/grapevineSettings.css:
--------------------------------------------------------------------------------
1 | .grapevineSettingsItemContainer {
2 | margin-left:100px;
3 | margin-bottom:20px;
4 | }
5 | .grapevineSettingsItemLeftCol {
6 | display:inline-block;
7 | width:150px;
8 | font-size:22px;
9 | padding-top:5px;
10 |
11 | text-align:left;
12 | }
13 | .grapevineSettingsItemMainToggleCol {
14 | display:inline-block;
15 | width:150px;
16 | margin-left:10px;
17 | text-align:left;
18 | }
19 | .grapevineSettingsItemRatingNameCol {
20 | display:inline-block;
21 | width:90px;
22 | margin-left:10px;
23 | text-align:left;
24 | font-size:16px;
25 | }
26 | .grapevineSettingsItemRatingToggleButtonCol {
27 | display:inline-block;
28 | width:100px;
29 | margin-left:10px;
30 | text-align:left;
31 | }
32 |
--------------------------------------------------------------------------------
/src/renderer/css/grapevineToggleSwitch.css:
--------------------------------------------------------------------------------
1 | .grapevineSettingsToggleSwitchContainer {
2 | text-align: center;
3 | display:inline-block;
4 | }
5 | .toggle-switch {
6 | position: relative;
7 | width: 75px;
8 | display: inline-block;
9 | text-align: left;
10 | top: 0px;
11 | }
12 | .checkbox {
13 | display: none;
14 | }
15 | .label {
16 | display: block;
17 | overflow: hidden;
18 | cursor: pointer;
19 | border: 0 solid #bbb;
20 | border-radius: 20px;
21 | }
22 | .inner {
23 | display: block;
24 | width: 200%;
25 | margin-left: -100%;
26 | transition: margin 0.3s ease-in 0s;
27 | }
28 | .inner:before,
29 | .inner:after {
30 | float: left;
31 | width: 50%;
32 | height: 34px;
33 | padding: 0;
34 | line-height: 36px;
35 | color: #fff;
36 | font-weight: bold;
37 | box-sizing: border-box;
38 | }
39 | .inner:before {
40 | content: "ON";
41 | padding-left: 10px;
42 | background-color: #060;
43 | color: #fff;
44 | }
45 | .inner:after {
46 | content: "OFF";
47 | padding-right: 10px;
48 | background-color: #bbb;
49 | color: #fff;
50 | text-align: right;
51 | }
52 | .switch {
53 | display: block;
54 | width: 26px;
55 | height: 26px;
56 | margin: 4px;
57 | background: #fff;
58 | position: absolute;
59 | top: 0;
60 | bottom: 0;
61 | right: 40px;
62 | border: 0 solid #bbb;
63 | border-radius: 20px;
64 | transition: all 0.3s ease-in 0s;
65 | }
66 | .checkbox:checked + .label .inner {
67 | margin-left: 0;
68 | }
69 | .checkbox:checked + .label .switch {
70 | right: 0px;
71 | }
72 |
--------------------------------------------------------------------------------
/src/renderer/css/mastheads.css:
--------------------------------------------------------------------------------
1 | .mastheadContainer {
2 | overflow:hidden;
3 | position:relative;
4 | height:60px;
5 | }
6 | .mastheadLeftContainer {
7 | font-size:28px;
8 | color:#1B2631;
9 | position: absolute;
10 | top: 50%;
11 | left: 5px;
12 | transform: translate(0%, -50%);
13 | }
14 | .mastheadCenterContainer {
15 | font-size:18px;
16 | text-align:center;
17 | display:inline-block;
18 | margin-top:0px;
19 | margin-bottom:0px;
20 | color:#1B2631;
21 | position: absolute;
22 | top: 50%;
23 | left: 50%;
24 | transform: translate(-50%, -50%);
25 | }
26 | .mastheadRightContainer {
27 | font-size:32px;
28 | position: absolute;
29 | top: 50%;
30 | right: 5px;
31 | transform: translate(0%, -50%);
32 | color:purple;
33 | }
34 | .mastheadEmojiNavButton {
35 | width:50px;
36 | height:50px;
37 | margin-top:-20px;
38 | display:inline-block;
39 | color:#3f3f3f;
40 | margin-left:5px;
41 | font-size:56px;
42 | vertical-align:middle;
43 | text-align:center;
44 | text-decoration:none;
45 | }
46 | .mastheadEmojiNavButton:hover {
47 | color:orange;
48 | }
49 | .mastheadNavButton {
50 | width:35px;
51 | height:35px;
52 | text-align:center;
53 | display:inline-block;
54 | color:black;
55 | border:0px solid black;
56 | background-color:#EFEFEF;
57 | border-radius:3px;
58 | padding:5px;
59 | margin-left:10px;
60 | font-size:10px;
61 | vertical-align:middle;
62 | text-decoration:none;
63 | }
64 | .mastheadNavButton:hover {
65 | color:white;
66 | background-color:#1B2631;
67 | }
68 |
69 | .mastheadSubBanner {
70 | border-top:2px solid #1B2631;
71 | border-bottom:2px solid #1B2631;
72 | text-align:center;
73 | font-size:12px;
74 | color:grey;
75 | }
76 |
77 | .mastheadAvatarContainer {
78 | display:inline-block;
79 | position:relative;
80 | width:50px;
81 | height:50px;
82 | }
83 | .mastheadAvatarBox {
84 | display:inline-block;
85 | background-color:white;
86 | border:1px solid black;
87 | border-radius:250px;
88 | width:95%;
89 | height:95%;
90 | margin: 0;
91 | position: absolute;
92 | top: 50%;
93 | left: 50%;
94 | transform: translate(-50%, -50%);
95 | }
96 |
--------------------------------------------------------------------------------
/src/renderer/css/misc.css:
--------------------------------------------------------------------------------
1 | .goToUserProfileButton {
2 | text-decoration:none;
3 | }
4 | table#fetchingtable > tr > td.data {
5 | width:80px;
6 | padding:3px;
7 | border:1px solid black;
8 | }
9 | table#fetchingtable > tr > td {
10 | width:60px;
11 | padding:3px;
12 | }
13 | #connectedRelaysButton {
14 | display:inline-block;
15 | }
16 | #connectedRelaysButton:hover {
17 | background-color:yellow;
18 | cursor:pointer;
19 | }
20 |
--------------------------------------------------------------------------------
/src/renderer/css/myProfile.css:
--------------------------------------------------------------------------------
1 | .myProfileBox {
2 | position:relative;
3 | }
4 | .myProfileRightColumnContainer {
5 | display:inline-block;
6 | width:400px;
7 | height:200px;
8 | margin-left:10px;
9 | }
10 |
11 | .myProfileNameEditContainer {
12 | width:300px;
13 | height:50px;
14 | display:inline-block;
15 | }
16 | .myProfileNameContainer {
17 | font-size:18px;
18 | width:100%;
19 | height:50px;
20 | }
21 | .myProfileAboutContainer {
22 | width:100%;
23 | height:70px;
24 | }
25 | .myProfileAvatarContainer {
26 | display:inline-block;
27 | position:relative;
28 | width:250px;
29 | height:250px;
30 | }
31 | .myProfileAvatarImg {
32 | display:inline-block;
33 | background-color:blue;
34 | border:5px solid black;
35 | border-radius:250px;
36 | width:95%;
37 | height:95%;
38 | margin: 0;
39 | position: absolute;
40 | top: 50%;
41 | left: 50%;
42 | transform: translate(-50%, -50%);
43 | }
44 | .radioButtonDescriptor {
45 | margin-left:10px;
46 | font-size:18px;
47 | }
48 |
--------------------------------------------------------------------------------
/src/renderer/css/navbars.css:
--------------------------------------------------------------------------------
1 | .leftNavButton {
2 | width:80px;
3 | height:80px;
4 | border:1px solid #5f5f5f;
5 | display:inline-block;
6 | color:black;
7 | background-color:#EFEFEF;
8 | margin:0px 0px 5px 0px;
9 | border-radius:3px;
10 | padding:5px;
11 | font-size:12px;
12 | text-decoration:none;
13 | }
14 | .leftNavButton:hover {
15 | color:white;
16 | background-color:#1B2631;
17 | }
18 | .leftNavPanel {
19 | padding-top:5px;
20 | display:inline-block;
21 | background-color: #616A6B;
22 | height:100%;
23 | width:100px;
24 | vertical-align:top;
25 | text-align:center;
26 | white-space: normal;
27 | }
28 |
29 | .leftNav2Button {
30 | width:40px;
31 | height:40px;
32 | border:1px solid #5f5f5f;
33 | display:inline-block;
34 | color:black;
35 | background-color:#EFEFEF;
36 | margin:0px 0px 2px 0px;
37 | border-radius:2px;
38 | padding:2px;
39 | font-size:10px;
40 | text-decoration:none;
41 | }
42 | .leftNav2Button:hover {
43 | color:white;
44 | background-color:#1B2631;
45 | }
46 | .leftNav2Panel {
47 | padding-top:5px;
48 | display:inline-block;
49 | background-color: #BDC3C7;
50 | height:100%;
51 | width:52px;
52 | vertical-align:top;
53 | text-align:center;
54 | white-space: normal;
55 | }
56 |
--------------------------------------------------------------------------------
/src/renderer/css/newPost.css:
--------------------------------------------------------------------------------
1 | .newPostTextarea {
2 | width:100%;
3 | height:200px
4 | }
5 |
--------------------------------------------------------------------------------
/src/renderer/css/nfgGraphic.css:
--------------------------------------------------------------------------------
1 | .nfgGraphicContainer {
2 | border:1px dashed grey;
3 | display:inline-block;
4 | width:700px;
5 | height:500px;
6 | }
7 |
--------------------------------------------------------------------------------
/src/renderer/css/settings.css:
--------------------------------------------------------------------------------
1 | div.relays:before {
2 | content: attr(data-currentstate);
3 | }
4 | .singleProfileDataContainer {
5 | width:100%;
6 | margin-bottom:5px;
7 | position:relative;
8 | }
9 | .singleProfileDataContainer_inactive {
10 | border:1px dashed grey;
11 | padding:3px;
12 | background-color:#EFEFEF;
13 | }
14 | .singleProfileDataContainer_active {
15 | border:2px solid purple;
16 | padding:2px;
17 | background-color:white;
18 | }
19 | .relayInfoContainer {
20 | margin-bottom:2px;
21 | font-size:18px;
22 | padding:2px;
23 | }
24 | .relayUrlContainer {
25 | display:inline-block;
26 | margin-left:5px;
27 | width:400px;
28 | }
29 |
--------------------------------------------------------------------------------
/src/renderer/css/toggleSwitch1.css:
--------------------------------------------------------------------------------
1 | /* The switch - the box around the slider */
2 | .switch {
3 | position: relative;
4 | display: inline-block;
5 | width: 60px;
6 | height: 34px;
7 | }
8 |
9 | /* Hide default HTML checkbox */
10 | .switch input {
11 | opacity: 0;
12 | width: 0;
13 | height: 0;
14 | }
15 |
16 | /* The slider */
17 | .slider {
18 | position: absolute;
19 | cursor: pointer;
20 | top: 0;
21 | left: 0;
22 | right: 0;
23 | bottom: 0;
24 | background-color: #ccc;
25 | -webkit-transition: .4s;
26 | transition: .4s;
27 | }
28 |
29 | .slider:before {
30 | position: absolute;
31 | content: "";
32 | height: 26px;
33 | width: 26px;
34 | left: 4px;
35 | bottom: 4px;
36 | background-color: white;
37 | -webkit-transition: .4s;
38 | transition: .4s;
39 | }
40 |
41 | input:checked + .slider {
42 | background-color: #2196F3;
43 | }
44 |
45 | input:focus + .slider {
46 | box-shadow: 0 0 1px #2196F3;
47 | }
48 |
49 | input:checked + .slider:before {
50 | -webkit-transform: translateX(26px);
51 | -ms-transform: translateX(26px);
52 | transform: translateX(26px);
53 | }
54 |
55 | /* Rounded sliders */
56 | .slider.round {
57 | border-radius: 34px;
58 | }
59 |
60 | .slider.round:before {
61 | border-radius: 50%;
62 | }
63 |
--------------------------------------------------------------------------------
/src/renderer/css/userList.css:
--------------------------------------------------------------------------------
1 | .singleUserContainer {
2 | border:1px solid black;
3 | border-radius:5px;
4 | margin-bottom:5px;
5 | padding:5px;
6 | position:relative;
7 | background-color:#c6c6ff;
8 | }
9 | .userListSmallAvatarContainer {
10 | display:inline-block;
11 | position:relative;
12 | width:50px;
13 | height:50px;
14 | }
15 | .userListSmallAvatarBox {
16 | display:inline-block;
17 | background-color:white;
18 | border:1px solid black;
19 | border-radius:250px;
20 | width:95%;
21 | height:95%;
22 | margin: 0;
23 | position: absolute;
24 | top: 50%;
25 | left: 50%;
26 | transform: translate(-50%, -50%);
27 | }
28 | .singleUserMainBodyContainer {
29 | display:inline-block;
30 | width:calc(86% - 70px);
31 | min-height:50px;
32 | background-color:white;
33 | border-radius:5px;
34 | margin-left:5px;
35 | }
36 | .singleUserRightContainer {
37 | display:inline-block;
38 | width:15%;
39 | min-height:50px;
40 | border-radius:5px;
41 | margin-left:5px;
42 | }
43 |
--------------------------------------------------------------------------------
/src/renderer/css/userProfile.css:
--------------------------------------------------------------------------------
1 | .mainUserProfileBox {
2 | width:100%;
3 | margin-bottom:0px;
4 | }
5 | .largeAvatarContainer {
6 | display:inline-block;
7 | position:relative;
8 | width:250px;
9 | height:250px;
10 | }
11 | .mainProfilePageAvatarBox {
12 | display:inline-block;
13 | background-color:blue;
14 | border:5px solid black;
15 | border-radius:250px;
16 | width:95%;
17 | height:95%;
18 | margin: 0;
19 | position: absolute;
20 | top: 50%;
21 | left: 50%;
22 | transform: translate(-50%, -50%);
23 | }
24 | .mainUserProfileRightColumnContainer {
25 | display:inline-block;
26 | margin-left:10px;
27 | position:relative;
28 | }
29 | .mainUserProfileLeftColumnContainer {
30 | display:inline-block;
31 | width:250px;
32 | overflow:scroll;
33 | }
34 | .mainUserAboutContainer {
35 | width:600px;
36 | height:40px;
37 | overflow:scroll;
38 | }
39 | .mainUserNameContainer {
40 | font-size:18px;
41 | width:100%;
42 | overflow:scroll;
43 | margin-bottom:5px;
44 | }
45 | .userProfilePubkeyContainer {
46 | font-size:10px;
47 | color:brown;
48 | margin-bottom:5px;
49 | }
50 |
--------------------------------------------------------------------------------
/src/renderer/css/youTubeEmbed.css:
--------------------------------------------------------------------------------
1 | .video-responsive {
2 | overflow: hidden;
3 | padding-bottom: 56.25%;
4 | position: relative;
5 | height: 0;
6 | }
7 |
8 | .video-responsive iframe {
9 | left: 0;
10 | top: 0;
11 | height: 100%;
12 | width: 100%;
13 | position: absolute;
14 | }
15 |
--------------------------------------------------------------------------------
/src/renderer/errorBoundary.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class ErrorBoundary extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | this.state = { hasError: false };
7 | }
8 |
9 | static getDerivedStateFromError(error) {
10 | // Update state so the next render will show the fallback UI.
11 | return { hasError: true };
12 | }
13 |
14 | componentDidCatch(error, errorInfo) {
15 | // You can also log the error to an error reporting service
16 | logErrorToMyService(error, errorInfo);
17 | }
18 |
19 | render() {
20 | if (this.state.hasError) {
21 | // You can render any custom fallback UI
22 | return Something went wrong. ;
23 | }
24 |
25 | return this.props.children;
26 | }
27 | }
28 | export default ErrorBoundary
29 |
--------------------------------------------------------------------------------
/src/renderer/grapevine/landingPage/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { NavLink } from "react-router-dom";
4 | import Masthead from '../../mastheads/mainMasthead.js';
5 | import LeftNavbar from '../../navbars/leftNav.js';
6 | import * as MiscAppFxns from "../../lib/app/misc.ts";
7 | import * as StartupFxns from "../../lib/app/startup.ts";
8 |
9 | const jQuery = require("jquery");
10 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
11 | const cloneObj = MiscAppFxns.cloneObj
12 | const secsToTime = MiscAppFxns.secsToTime
13 | const timeout = MiscAppFxns.timeout
14 |
15 | export default class GrapevineLandingPage extends React.Component {
16 |
17 | constructor(props) {
18 | super(props);
19 | this.state = {
20 | events: []
21 | }
22 | }
23 |
24 | async componentDidMount() {
25 | updateMainColWidth();
26 | document.getElementById("mastheadCenterContainer").innerHTML = "grapevine landing page"
27 |
28 | await timeout(10)
29 | jQuery("#goToMainGrapevinePageButton").get(0).click()
30 | }
31 | render() {
32 | return (
33 | <>
34 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | go to main grapevine page
44 |
45 |
46 |
47 |
48 | >
49 | );
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/renderer/grapevine/nostrFollowGrapevine/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import Masthead from '../../mastheads/mainMasthead.js';
3 | import LeftNavbar from '../../navbars/leftNav.js';
4 | import * as MiscAppFxns from "../../lib/app/misc.ts";
5 | import NFG_Graphic1 from "./graphic";
6 | import {
7 | relayInit,
8 | generatePrivateKey,
9 | getPublicKey,
10 | getEventHash,
11 | signEvent,
12 | validateEvent,
13 | verifySignature,
14 | } from 'nostr-tools'
15 |
16 | import { asyncSql } from "../../index.tsx";
17 |
18 | const jQuery = require("jquery");
19 |
20 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
21 |
22 | const fetchFollowingNetworkInfo = async () => {
23 | var sql = ""
24 | sql += " SELECT * FROM followingNetwork WHERE id=1 "
25 | var aFollowingNetworkData = await asyncSql(sql);
26 | console.log("aFollowingNetworkData.length: "+aFollowingNetworkData.length)
27 | if (aFollowingNetworkData.length > 0) {
28 | const oFollowingNetworkData = aFollowingNetworkData[0]
29 | console.log("oFollowingNetworkData.id: "+oFollowingNetworkData.id)
30 | return oFollowingNetworkData;
31 | } else {
32 | return false;
33 | }
34 | }
35 |
36 | var numUpdates = 0;
37 | export default class ExtendedFollowerList extends React.Component {
38 | constructor(props) {
39 | super(props);
40 | this.state = {
41 | seed: window.myPubkey,
42 | pubkeys: {}
43 | }
44 | }
45 | async componentDidMount() {
46 | updateMainColWidth();
47 | document.getElementById("mastheadCenterContainer").innerHTML = "visualization of the social graph based on following"
48 |
49 | var oFollowingNetworkData = await fetchFollowingNetworkInfo()
50 | if (oFollowingNetworkData) {
51 | this.setState({
52 | seed: oFollowingNetworkData.seed,
53 | pubkeys: JSON.parse(oFollowingNetworkData.pubkeys)
54 | })
55 | } else {
56 | console.log("You need to initialize your first followingNetwork!")
57 | }
58 |
59 | }
60 | render() {
61 | return (
62 | <>
63 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
76 |
77 |
78 |
79 |
0
80 |
of {Object.keys(this.state.pubkeys).length}
81 | profiles loaded
82 |
83 |
84 |
85 |
86 |
87 | >
88 | );
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/renderer/grapevine/nostrFollowGrapevine/missingAvatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wds4/electron-react-boilerplate-nostr/e8d90f8b520381e54d80c77a919ea27e839c33ea/src/renderer/grapevine/nostrFollowGrapevine/missingAvatar.png
--------------------------------------------------------------------------------
/src/renderer/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 | Hello sovereign individual!
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/renderer/index.tsx:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client';
2 | import App from './App';
3 | // import { createStore } from 'redux'
4 | // import { Provider } from 'react-redux'
5 | // import store from './store'
6 |
7 | // const store = createStore(rootReducer)
8 |
9 | // wrap this in a function and don't call it until ipc-fetch-relays returns with data from sqlite3
10 | const startApp = () => {
11 | const container = document.getElementById('root')!;
12 | const root = createRoot(container);
13 | root.render( );
14 | };
15 |
16 | window.electron.ipcRenderer.once('ipc-fetch-relays', (relayUrls) => {
17 | console.log("ipc-fetch-relays; relayUrls: "+relayUrls);
18 | window.relayUrls = relayUrls;
19 | startApp()
20 | });
21 | window.electron.ipcRenderer.sendMessage('ipc-fetch-relays', ['ping']);
22 |
23 | // calling IPC exposed from preload script
24 | window.electron.ipcRenderer.once('ipc-example', (arg) => {
25 | // eslint-disable-next-line no-console
26 | console.log(arg);
27 | });
28 | window.electron.ipcRenderer.sendMessage('ipc-example', ['ping']);
29 |
30 | /*
31 | We will use asyncSql(sql) in src/renderer/pages/sqlDemoApp to send sql commmands and replies back and forth
32 | between the main process and the renderer process.
33 | */
34 | export const asyncSql = async (sql) => {
35 | const nonce = Math.floor(( Math.random() * 100000 ))
36 | console.log("asyncSql; nonce: "+nonce)
37 | return new Promise((resolve) => {
38 | window.electron.ipcRenderer.once('asynchronous-sql-reply-'+nonce, (arg) => {
39 | resolve(arg)
40 | });
41 | const data = [ sql, nonce ]
42 | window.electron.ipcRenderer.sendMessage('asynchronous-sql-command', data);
43 | })
44 | }
45 |
--------------------------------------------------------------------------------
/src/renderer/lib/app/misc.ts:
--------------------------------------------------------------------------------
1 |
2 | export const updateMainColWidth = () => {
3 | const menuColWidth = document.getElementById("menuCol").offsetWidth;
4 | const rootWidth = document.getElementById("root").offsetWidth;
5 | const newWidth = rootWidth - menuColWidth - 4;
6 | document.getElementById("mainCol").style.width = newWidth+"px";
7 |
8 | const mastheadHeight = document.getElementById("mastheadElem").offsetHeight;
9 | const rootHeight = document.getElementById("root").offsetHeight;
10 | const newHeight = rootHeight - mastheadHeight - 6;
11 | document.getElementById("mainPanel").style.height = newHeight+"px";
12 | }
13 |
14 | export const isValidObj = (x) => {
15 | // test if the input is a string that converts into JSON
16 | try {
17 | const obj1 = JSON.parse(x);
18 | return true;
19 | } catch (e1) {}
20 |
21 | // test if the input is already an object
22 | try {
23 | const obj2 = JSON.parse(JSON.stringify(x));
24 | return true;
25 | } catch (e2) {}
26 |
27 | return false;
28 | }
29 |
30 | export const cloneObj = (obj) => {
31 | return JSON.parse(JSON.stringify(obj));
32 | }
33 |
34 | export const secsToTime = (secs) => {
35 | var displayTime = "--";
36 | var currentTime = Math.floor(Date.now() / 1000);
37 | var age_secs = currentTime - secs;
38 | var age_mins = Math.floor(age_secs / 60);
39 | var age_hours = Math.floor(age_secs / (60 * 60) );
40 | var age_days = Math.floor(age_secs / (60 * 60 * 24) );
41 | var age_years = Math.floor(age_secs / (60 * 60 * 24 * 365) );
42 | var discoveredUnit = false;
43 | if ( (!discoveredUnit) && (age_secs < 60) ) {
44 | // if less than one minute,
45 | // display in seconds
46 | displayTime = age_secs + " s";
47 | discoveredUnit = true;
48 | }
49 | if ( (!discoveredUnit) && (age_secs < 60 * 60) ) {
50 | // if less than one hour,
51 | // display in minutes
52 | displayTime = age_mins + " m";
53 | discoveredUnit = true;
54 | }
55 | if ( (!discoveredUnit) && (age_secs < 24 * 60 * 60) ) {
56 | // if less than one day,
57 | // display in hours
58 | displayTime = age_hours + " h";
59 | discoveredUnit = true;
60 | }
61 | if ( (!discoveredUnit) && (age_secs < 365 * 24 * 60 * 60) ) {
62 | // if less than one year,
63 | // display in days
64 | displayTime = age_days + " d";
65 | discoveredUnit = true;
66 | }
67 | if (!discoveredUnit) {
68 | // else display in years
69 | displayTime = age_years + " y";
70 | discoveredUnit = true;
71 | }
72 |
73 | return displayTime;
74 | }
75 |
76 | export const timeout = (ms) => {
77 | return new Promise(resolve => setTimeout(resolve, ms));
78 | }
79 |
80 | export const fetchMySk = async () => {
81 | var sql = ""
82 | sql += "SELECT * FROM myProfile WHERE active=true"
83 | var aMyProfileData = await asyncSql(sql);
84 |
85 | if (aMyProfileData.length > 0) {
86 | var oMyProfileData = aMyProfileData[0]
87 |
88 | var myPk = oMyProfileData.pubkey;
89 | var mySk = oMyProfileData.privkey;
90 | return mySk
91 | }
92 | return false;
93 | }
94 |
95 | export const fetchMyPk = async () => {
96 | var sql = ""
97 | sql += "SELECT * FROM myProfile WHERE active=true"
98 | var aMyProfileData = await asyncSql(sql);
99 |
100 | if (aMyProfileData.length > 0) {
101 | var oMyProfileData = aMyProfileData[0]
102 |
103 | var myPk = oMyProfileData.pubkey;
104 | var mySk = oMyProfileData.privkey;
105 | return myPk
106 | }
107 | return false;
108 | }
109 |
110 | export const fetchRelays = async (which) => {
111 | var sql = ""
112 | sql += "SELECT * FROM relays "
113 | var aRelaysData = await asyncSql(sql);
114 |
115 | var aDefault = [];
116 | var aActive = [];
117 | var aAll = [];
118 | console.log("aRelaysData.length: "+aRelaysData.length)
119 | for (var r=0;r {
5 | const [count, setCount] = useState(0);
6 |
7 | useEffect(() => {
8 | setTimeout(() => {
9 | var foo = jQuery("#scoresCalculationTimer").data("status")
10 | // console.log("ScoresCalculationTimer; count: "+count+"; foo: "+foo)
11 | if (foo=="run") {
12 | jQuery("#calculateScoresSingleIterationButton").get(0).click()
13 | if (count%5 == 0) {
14 | jQuery("#changeUserCalcsDisplayButton").get(0).click()
15 | }
16 | }
17 | setCount((count) => count + 1);
18 | }, 200);
19 | }, [count]); // <- add empty brackets here
20 |
21 | return I've rendered {count} times!
;
22 | }
23 | export default Timer;
24 |
--------------------------------------------------------------------------------
/src/renderer/lib/app/toggleSwitch1.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export default class ToggleButtonOnOff extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | this.handleClick = this.handleClick.bind(this);
7 | this.state = {isOff: false};
8 | }
9 |
10 | handleClick() {
11 | this.setState({isOff:!this.state.isOff});
12 | }
13 |
14 | render() {
15 | const { isOff } = this.state;
16 | let title=this.state.isOff? "ON":"OFF";
17 | return (
18 | {title}
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/renderer/lib/nostr/eventValidation.js:
--------------------------------------------------------------------------------
1 | import * as MiscAppFxns from "../app/misc";
2 | import {
3 | validateEvent,
4 | verifySignature,
5 | } from "nostr-tools";
6 |
7 | const isValidObj = MiscAppFxns.isValidObj
8 |
9 | export const doesEventValidate = (event) => {
10 | let ok = false;
11 | let veryOk = false;
12 | if (isValidObj(event)) {
13 | ok = validateEvent(event)
14 | veryOk = verifySignature(event)
15 | }
16 | if ((ok) && (veryOk)) {
17 | return true;
18 | }
19 | return false;
20 | }
21 |
--------------------------------------------------------------------------------
/src/renderer/lib/nostr/getFollowing.js:
--------------------------------------------------------------------------------
1 | import {
2 | relayInit
3 | } from 'nostr-tools'
4 | import * as MiscAppFxns from "../../lib/app/misc.ts";
5 |
6 | const timeout = MiscAppFxns.timeout
7 | const relay = relayInit('wss://nostr-pub.wellorder.net')
8 |
9 | // ms is timeout in milliseconds
10 | export const getFollowing = async (pk, ms) => {
11 | await timeout(4000)
12 |
13 |
14 | var aFollowing = [];
15 |
16 | await relay.connect()
17 |
18 | await relay.on('connect', async () => {
19 | console.log(`getFollowing connected to ${relay.url}`)
20 | })
21 | relay.on('error', () => {
22 | console.log(`getFollowing failed to connect to ${relay.url}`)
23 | })
24 |
25 | let sub = relay.sub([
26 | {
27 | authors: [ pk ],
28 | since: 0,
29 | kinds: [3],
30 | }
31 | ])
32 | await sub.on('event', async (event) => {
33 | console.log('getFollowing we got the event we wanted:', event)
34 | const aTags = event.tags;
35 | return await aTags;
36 | // console.log("getFollowing event.tags A: "+JSON.stringify(aTags,null,4))
37 | sub.unsub();
38 | // console.log("getFollowing event.tags B: "+JSON.stringify(aTags,null,4))
39 | })
40 | sub.on('eose', () => {
41 | sub.unsub()
42 | })
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/renderer/lib/visjs/visjs-style.js:
--------------------------------------------------------------------------------
1 |
2 | export const groupOptions = {
3 | user: {
4 | shape: "circle",
5 | borderWidth: "3",
6 | color: {
7 | background: "white",
8 | border: "black"
9 | }
10 | }
11 | }
12 |
13 | export const edgeOptions = {
14 | follows: {
15 | polarity: "forward",
16 | color: "blue",
17 | width: "5",
18 | dashes: false,
19 | physics: true
20 | }
21 | }
22 |
23 | function editEdgeFunction() {
24 |
25 | }
26 | function deleteEdgeFunction() {
27 |
28 | }
29 | function deleteNodeFunction() {
30 |
31 | }
32 |
33 | export const options = {
34 | clickToUse: false,
35 | layout: {
36 | improvedLayout: false
37 | },
38 | interaction: {hover: true},
39 | manipulation: {
40 | enabled: false,
41 | },
42 | physics: {
43 | enabled: true
44 | },
45 | nodes:{
46 | margin: 10,
47 | borderWidth:1,
48 | color: { background: 'white', border: 'black' },
49 | widthConstraint: {
50 | minimum: 0,
51 | maximum: 100
52 | }
53 | },
54 | edges: {
55 | physics: false,
56 | arrows: {
57 | to: {
58 | enabled: true,
59 | scaleFactor: 4
60 | }
61 | }
62 | },
63 | groups: groupOptions
64 | };
65 |
--------------------------------------------------------------------------------
/src/renderer/mastheads/avatarElem.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import { useNostrEvents } from "nostr-react";
3 | import BlankAvatar from "./blankAvatar.png";
4 |
5 | const jQuery = require("jquery");
6 |
7 | const AvatarElem2 = (props) => {
8 |
9 | if (window.myProfile) {
10 | if (window.myProfile.picture_url) {
11 | return (
12 | <>
13 |
16 | >
17 | );
18 | } else {
19 | return (
20 | <>
21 |
24 | >
25 | );
26 | }
27 | } else {
28 | var { events } = useNostrEvents({
29 | filter: {
30 | authors: [ props.pubkey ],
31 | since: 0, // all new events from now
32 | kinds: [0],
33 | },
34 | });
35 | return (
36 | <>
37 | {events.map( (event) => {
38 | var pic_url = JSON.parse(event.content).picture;
39 | return (
40 | <>
41 |
44 | >
45 | )}
46 | )}
47 | >
48 | );
49 | }
50 | }
51 |
52 | export default class AvatarElem extends React.Component {
53 | constructor(props) {
54 | super(props);
55 | this.state = {
56 | }
57 | }
58 | async componentDidMount() {
59 | }
60 |
61 | render() {
62 | const pk = this.props.pubkey;
63 | return (
64 | <>
65 |
66 | >
67 | );
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/renderer/mastheads/blankAvatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wds4/electron-react-boilerplate-nostr/e8d90f8b520381e54d80c77a919ea27e839c33ea/src/renderer/mastheads/blankAvatar.png
--------------------------------------------------------------------------------
/src/renderer/mastheads/mainMasthead.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink } from "react-router-dom";
3 | import AvatarElem from "./avatarElem";
4 | import RelaysStatus from "../pages/components/relaysStatus"
5 |
6 | export default class Masthead extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | myPubkey: window.myPubkey
11 | }
12 | }
13 | async componentDidMount() {
14 | }
15 | render() {
16 | return (
17 | <>
18 |
19 |
20 |
21 | back button
22 |
23 | electron-nostr
24 |
25 |
26 |
27 | Home
28 |
29 |
30 |
31 |
32 | visjs hello world
33 |
34 |
35 | 🍇
36 | settings
37 |
38 |
39 | 🔍
40 | search
41 |
42 |
43 | ✏️
44 | post
45 |
46 |
47 |
48 |
49 |
50 | ⚙️
51 | settings
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | >
60 | );
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/renderer/navbars/leftNav.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { NavLink } from "react-router-dom";
3 |
4 | export default class LeftNavbar extends React.PureComponent {
5 | render() {
6 | return (
7 | <>
8 |
9 |
10 | 🏠
11 | main feed
12 |
13 |
14 |
15 | 🌎
16 | graph
17 |
18 |
19 | >
20 | );
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/renderer/navbars/leftNav2/settings.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { NavLink } from "react-router-dom";
3 |
4 | export default class LeftNavbar2_Settings extends React.PureComponent {
5 | render() {
6 | return (
7 | <>
8 |
9 |
10 | keys
11 |
12 |
13 |
14 | relays
15 |
16 |
17 |
18 | sql
19 |
20 |
21 |
22 | 🌐
23 |
24 |
25 | >
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/renderer/pages/components/actionButtons.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import { Link, NavLink } from "react-router-dom";
3 | import * as MiscAppFxns from "../../lib/app/misc.ts";
4 |
5 | const jQuery = require("jquery");
6 |
7 | const ActionButtons = ({event}) => {
8 | // var randomNumber = Math.random();
9 | if (!window.linkToReply_base) { window.linkToReply_base="Reply" }
10 |
11 | var linkToReply = "/"+window.linkToReply_base+"/"+event.id
12 | return (
13 | <>
14 |
15 |
16 | {
18 | window.expandedEvent = event;
19 |
20 | // doing the same inelegant workaround as I am for linkToThread_base -- see userPost.js
21 | if (window.linkToReply_base=="Reply") {
22 | window.linkToReply_base="Reply2"
23 | } else {
24 | window.linkToReply_base="Reply"
25 | }
26 | } }
27 | className="eventContentContainer"
28 | to={linkToReply}
29 | to_base={window.linkToReply_base}
30 | event={event}
31 | >
32 | 💬
33 |
34 |
35 | 🔁
36 | 🤍
37 | 💚
38 | 🤙
39 |
40 |
41 | {JSON.stringify(event,null,4)}
42 |
43 | >
44 | );
45 | }
46 | export default ActionButtons
47 |
--------------------------------------------------------------------------------
/src/renderer/pages/components/blankAvatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wds4/electron-react-boilerplate-nostr/e8d90f8b520381e54d80c77a919ea27e839c33ea/src/renderer/pages/components/blankAvatar.png
--------------------------------------------------------------------------------
/src/renderer/pages/components/followButton.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useNostr, dateToUnix } from "nostr-react";
3 | import { asyncSql } from "../../index.tsx";
4 | import {
5 | type Event as NostrEvent,
6 | getEventHash,
7 | getPublicKey,
8 | signEvent,
9 | validateEvent,
10 | verifySignature,
11 | } from "nostr-tools";
12 |
13 | import * as MiscAppFxns from "../../lib/app/misc.ts";
14 |
15 | const cloneObj = MiscAppFxns.cloneObj
16 |
17 | const jQuery = require("jquery");
18 |
19 | const fetchMyData = async () => {
20 | var sql = ""
21 | sql += "SELECT * FROM myProfile WHERE id=1"
22 | console.log("fetchMyData sql: "+sql)
23 | var aMyProfileData = await asyncSql(sql);
24 | console.log("fetchMyData aMyProfileData: "+JSON.stringify(aMyProfileData,null,4))
25 | if (aMyProfileData.length > 0) {
26 | var oMyProfileData = aMyProfileData[0]
27 |
28 | var myPk = oMyProfileData.pubkey;
29 | var mySk = oMyProfileData.privkey;
30 | var sMyFollowingList = oMyProfileData.following;
31 | if (!sMyFollowingList) { sMyFollowingList = "[]"; }
32 | var aMyFollowingList = JSON.parse(sMyFollowingList)
33 | return [ mySk, aMyFollowingList ]
34 | }
35 | return false;
36 | }
37 |
38 | const updateMyFollowingInSql = async (aFollowing_new) => {
39 | window.myProfile.following = aFollowing_new
40 | var sFollowing_new = JSON.stringify(aFollowing_new)
41 | var sql = "";
42 | sql += " UPDATE myProfile SET following = '"+sFollowing_new+"' WHERE id=1 "
43 | var result = await asyncSql(sql);
44 | }
45 |
46 | export default function FollowButton({pubkey,aFollowing}) {
47 | const { publish } = useNostr();
48 |
49 | var buttonHTML = "follow";
50 | var buttonClass = "followButton";
51 | var currentAction = "follow"
52 | // if (window.myProfile.following.includes(pubkey)) {
53 | if (aFollowing.includes(pubkey)) {
54 | buttonHTML = "unfollow";
55 | buttonClass = "unfollowButton";
56 | currentAction = "unfollow"
57 | }
58 |
59 | const onPublish = async () => {
60 | const [ privKey, aMyFollowingList_current ] = await fetchMyData();
61 |
62 | var aMyFollowingList_new = [];
63 |
64 | console.log("currentAction: "+currentAction)
65 |
66 | if (!window.myProfile.following.includes(pubkey)) {
67 | // If this pubkey is not currently in my following list, then add it
68 | for (var t=0;t
132 | onPublish()} className={buttonClass} data-currentaction={currentAction} >
133 | {buttonHTML}
134 |
135 | >
136 | );
137 |
138 | /*
139 | if (window.myProfile.following.includes(pubkey)) {
140 | return (
141 | <>
142 | onPublish()} className={buttonClass} data-currentaction={currentAction} style={{position:"absolute",right:"5px",top:"5px"}} >
143 | {buttonHTML}
144 |
145 | >
146 | );
147 | } else {
148 | return (
149 | <>
150 | onPublish()} className={buttonClass} data-currentaction={currentAction} style={{position:"absolute",right:"5px",top:"5px"}} >
151 | {buttonHTML}
152 |
153 | >
154 | );
155 | }
156 | */
157 | }
158 |
--------------------------------------------------------------------------------
/src/renderer/pages/components/relaysStatus.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import { useNostr } from "nostr-react";
3 | const jQuery = require("jquery");
4 |
5 | const RelaysStatusComponent = () => {
6 | const { updateRelayList, connectedRelays } = useNostr();
7 |
8 | /*
9 | const addRelayer = (url) => {
10 | updateRelayList([
11 | ...connectedRelays,
12 | url,
13 | ])
14 | }
15 | const url = "wss://brb.io";
16 | addRelayer(url)
17 | */
18 |
19 | return (
20 | <>
21 |
22 | connected relays: {connectedRelays.length}
23 |
24 |
25 |
26 | {connectedRelays.map( (oRelay,item)=>{
27 | var url = oRelay.url;
28 | return (
29 |
30 |
31 | {JSON.stringify(oRelay,null,4)}
32 |
33 | {item+1}: {url}
34 |
35 | )
36 | })}
37 |
38 |
39 | >
40 | )
41 | };
42 |
43 | export default class RelaysStatus extends React.Component {
44 | constructor(props) {
45 | super(props);
46 | this.state = {
47 | }
48 | }
49 |
50 | async componentDidMount() {
51 | jQuery("#connectedRelaysButton").click(function(){
52 | var currentStatus = jQuery(this).data("status");
53 | if (currentStatus=="closed") {
54 | jQuery(this).data("status","open");
55 | // jQuery("#connectedRelaysVerboseContainer").css("overflow","hidden")
56 | jQuery("#connectedRelaysVerboseContainer").animate({
57 | height:"80px",
58 | },200)
59 | }
60 | if (currentStatus=="open") {
61 | jQuery(this).data("status","closed");
62 | jQuery("#connectedRelaysVerboseContainer").animate({
63 | height:"0px",
64 | },200)
65 | // jQuery("#connectedRelaysVerboseContainer").css("overflow","scroll")
66 | }
67 | })
68 | }
69 | render() {
70 | return (
71 | <>
72 |
73 | >
74 | );
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/renderer/pages/components/userPosts.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import { Link, NavLink } from "react-router-dom";
3 | import { useNostrEvents, useProfile } from "nostr-react";
4 | import * as MiscAppFxns from "../../lib/app/misc.ts";
5 | import UserPost from "./userPost";
6 |
7 | const UserPosts = ({pubkey}) => {
8 | var { events } = useNostrEvents({
9 | filter: {
10 | authors: [ pubkey ],
11 | since: 0, // all new events from now
12 | kinds: [1],
13 | },
14 | });
15 | const currentPage = "userProfile";
16 | return (
17 | <>
18 | {events.length} posts
19 | {events.map( (event) => {
20 | return (
21 | <>
22 |
26 | >
27 | )}
28 | )}
29 | >
30 | )
31 | };
32 |
33 | export default UserPosts;
34 |
--------------------------------------------------------------------------------
/src/renderer/pages/components/youTubeEmbed.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | // src={`https://www.youtube.com/embed/${embedId}`}
5 | // vs:
6 | // src={`${extractedUrl}`}
7 | const YoutubeEmbed = ({ embedId, extractedUrl }) => {
8 | const testUrl = "https://www.youtube.com/watch?v=ljvpz2fEyVE"
9 | if (embedId) {
10 | return (
11 |
12 | VIDEO
21 |
22 | )
23 | } else {
24 | return (
25 | <>
26 | >
27 | )
28 | }
29 | };
30 | /*
31 | YoutubeEmbed.propTypes = {
32 | embedId: PropTypes.string.isRequired
33 | };
34 | */
35 | export default YoutubeEmbed;
36 |
--------------------------------------------------------------------------------
/src/renderer/pages/createPost/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Masthead from '../../mastheads/mainMasthead.js';
3 | import LeftNavbar from '../../navbars/leftNav.js';
4 | import * as MiscAppFxns from "../../lib/app/misc.ts";
5 | import PublishPost from "./publishPost"
6 |
7 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
8 |
9 | export default class Home extends React.Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {}
13 | }
14 | async componentDidMount() {
15 | updateMainColWidth();
16 | document.getElementById("mastheadCenterContainer").innerHTML = "create post"
17 | }
18 | render() {
19 | return (
20 | <>
21 |
24 |
32 | >
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/renderer/pages/createPost/publishPost.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useNostr, dateToUnix } from "nostr-react";
3 | import * as MiscAppFxns from "../../lib/app/misc.ts";
4 | import { asyncSql } from "../../index.tsx";
5 | import {
6 | type Event as NostrEvent,
7 | getEventHash,
8 | getPublicKey,
9 | signEvent,
10 | } from "nostr-tools";
11 |
12 | const fetchMySk = MiscAppFxns.fetchMySk
13 |
14 | const jQuery = require("jquery");
15 |
16 | export default function PublishPost() {
17 | const { publish } = useNostr();
18 |
19 | const onPost = async () => {
20 |
21 | const privKey = await fetchMySk();
22 |
23 | if (!privKey) {
24 | alert("no private key provided");
25 | return;
26 | }
27 |
28 | const message = jQuery("#newPostTextarea").val()
29 |
30 | if (!message) {
31 | alert("no message provided");
32 | return;
33 | }
34 |
35 | const event: NostrEvent = {
36 | content: message,
37 | kind: 1,
38 | tags: [],
39 | created_at: dateToUnix(),
40 | pubkey: getPublicKey(privKey),
41 | };
42 |
43 | event.id = getEventHash(event);
44 | event.sig = signEvent(event, privKey);
45 |
46 | publish(event);
47 | jQuery("#newPostTextarea").val("")
48 | jQuery("#successMessageContainer").html("Your message has been submitted to the nostr network!")
49 | jQuery("#newEventContainer").html("Here it is: "+JSON.stringify(event,null,4))
50 | jQuery("#newPostTextareaContainer").change(function(){
51 | jQuery("#successMessageContainer").html("")
52 | jQuery("#newEventContainer").html("")
53 | })
54 | };
55 |
56 | return (
57 |
58 |
59 |
60 |
61 |
Post a message!
62 |
63 |
65 |
66 |
67 | );
68 | }
69 |
--------------------------------------------------------------------------------
/src/renderer/pages/directMessageConversation/convoHistory-deprecated.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import { Link, NavLink } from "react-router-dom";
3 | import { useNostrEvents, useProfile } from "nostr-react";
4 | import * as MiscAppFxns from "../../lib/app/misc.ts";
5 | import DirectMessage from "./directMessage";
6 |
7 | const cloneObj = MiscAppFxns.cloneObj;
8 |
9 | const DirectMessageConversationHistory = ({pubkey,myPrivKey}) => {
10 | var { events } = useNostrEvents({
11 | filter: {
12 | authors: [ pubkey, window.myPubkey ],
13 | since: 0, // all new events from now
14 | kinds: [4],
15 | },
16 | });
17 | return (
18 | <>
19 | {events.forEach( event => {
20 | return (
21 |
25 | )
26 | }
27 | )}
28 | >
29 | )
30 | };
31 |
32 | export default DirectMessageConversationHistory;
33 |
--------------------------------------------------------------------------------
/src/renderer/pages/directMessageConversation/convoHistory.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import { Link, NavLink } from "react-router-dom";
3 | import { useNostrEvents, useProfile } from "nostr-react";
4 | import * as MiscAppFxns from "../../lib/app/misc.ts";
5 | import DirectMessage from "./directMessage";
6 | import SingleUserElem from "../followingList/singleUserElem";
7 |
8 | const cloneObj = MiscAppFxns.cloneObj;
9 |
10 | const DirectMessageConversationHistory = ({pubkey,myPrivKey}) => {
11 | var { events } = useNostrEvents({
12 | filter: {
13 | authors: [ pubkey, window.myPubkey ],
14 | since: 0, // all new events from now
15 | kinds: [4],
16 | },
17 | });
18 | return (
19 | <>
20 |
21 | {[...events].reverse().map( (event, index) => {
22 | return (
23 |
27 | )}
28 | )}
29 |
30 | >
31 | )
32 | };
33 |
34 | export default DirectMessageConversationHistory;
35 |
--------------------------------------------------------------------------------
/src/renderer/pages/directMessageConversation/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import Masthead from '../../mastheads/mainMasthead.js';
3 | import LeftNavbar from '../../navbars/leftNav.js';
4 | import * as MiscAppFxns from "../../lib/app/misc.ts";
5 | import SendDirectMessage from "./sendDirectMessage";
6 | import DirectMessageConversationHistory from "./convoHistory";
7 |
8 | const fetchMySk = MiscAppFxns.fetchMySk
9 | const timeout = MiscAppFxns.timeout
10 |
11 | const jQuery = require("jquery");
12 |
13 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
14 |
15 | export default class DirectMessageConversation extends React.Component {
16 | constructor(props) {
17 | super(props);
18 | this.state = {
19 | myPrivKey: null
20 | }
21 | }
22 | async componentDidMount() {
23 | updateMainColWidth();
24 | document.getElementById("mastheadCenterContainer").innerHTML = "DM convo"
25 | const myPrivKey = await fetchMySk();
26 | this.setState({myPrivKey:myPrivKey})
27 | }
28 | render() {
29 | return (
30 | <>
31 |
34 |
35 |
36 |
37 |
38 |
39 |
43 |
45 |
46 |
47 | >
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/renderer/pages/directMessageConversation/sendDirectMessage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useNostr, dateToUnix } from "nostr-react";
3 | import * as MiscAppFxns from "../../lib/app/misc.ts";
4 | import { asyncSql } from "../../index.tsx";
5 | import {
6 | type Event as NostrEvent,
7 | getEventHash,
8 | getPublicKey,
9 | signEvent,
10 | nip04,
11 | } from "nostr-tools";
12 |
13 | const fetchMySk = MiscAppFxns.fetchMySk
14 |
15 | const jQuery = require("jquery");
16 |
17 | export default function SendDirectMessage() {
18 | const pubkey_recipient = window.clickedPubKey;
19 | const { publish } = useNostr();
20 |
21 | const onPost = async () => {
22 |
23 | const privKey = await fetchMySk();
24 |
25 | if (!privKey) {
26 | alert("no private key provided");
27 | return;
28 | }
29 |
30 | const message = jQuery("#newPostTextarea").val()
31 |
32 | let ciphertext = await nip04.encrypt(privKey, pubkey_recipient, message)
33 |
34 | if (!message) {
35 | alert("no message provided");
36 | return;
37 | }
38 |
39 | const event: NostrEvent = {
40 | id: null,
41 | kind: 4,
42 | pubkey: getPublicKey(privKey),
43 | created_at: dateToUnix(),
44 | content: ciphertext,
45 | tags: [["p",pubkey_recipient]],
46 | sig: null,
47 | };
48 |
49 | event.id = getEventHash(event);
50 | event.sig = signEvent(event, privKey);
51 |
52 | publish(event);
53 | jQuery("#newPostTextarea").val("")
54 | jQuery("#successMessageContainer").html("Your message has been submitted to the nostr network!")
55 | jQuery("#newEventContainer").html("Here it is: "+JSON.stringify(event,null,4))
56 | jQuery("#newPostTextareaContainer").change(function(){
57 | jQuery("#successMessageContainer").html("")
58 | jQuery("#newEventContainer").html("")
59 | })
60 | };
61 |
62 | return (
63 |
64 |
65 |
66 |
67 |
Send this direct message!
68 |
69 |
71 |
72 |
73 | );
74 | }
75 |
--------------------------------------------------------------------------------
/src/renderer/pages/followingList/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import Masthead from '../../mastheads/mainMasthead.js';
3 | import LeftNavbar from '../../navbars/leftNav.js';
4 | import * as MiscAppFxns from "../../lib/app/misc.ts";
5 | import SingleUserElem from "./singleUserElem";
6 |
7 | import { useNostrEvents } from "nostr-react";
8 |
9 | import {
10 | relayInit,
11 | generatePrivateKey,
12 | getPublicKey,
13 | getEventHash,
14 | signEvent,
15 | validateEvent,
16 | verifySignature,
17 | } from 'nostr-tools'
18 |
19 | const timeout = MiscAppFxns.timeout
20 |
21 | const jQuery = require("jquery");
22 |
23 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
24 |
25 | const FollowerList2 = () => {
26 | var { events } = useNostrEvents({
27 | filter: {
28 | authors: [ window.clickedPubKey ],
29 | since: 0, // all new events from now
30 | kinds: [3],
31 | },
32 | });
33 |
34 | if (events.length > 0) {
35 | // need to make sure sort order is correct
36 | // const reversed = events.reverse()
37 | // events.sort((a, b) => parseFloat(a.created_at) - parseFloat(b.created_at));
38 | // event = events[events.length-1] // seems to get an older one
39 | event = events[0]
40 | var aFollowing = event.tags
41 | var aFollowing_ = [];
42 | // remove duplicates
43 | for (var x=0;x
54 |
55 |
56 | You are looking at the list of accounts this user is following:
57 |
58 |
59 |
60 |
61 | {JSON.stringify(event,null,4)}
62 |
63 |
64 |
65 | Following: {aFollowing_.length}
66 |
67 | {aFollowing_.map( (pk) => {
68 | // var pk = following[1]
69 | return (
70 | <>
71 |
72 | >
73 | )}
74 | )}
75 |
76 | >
77 | )
78 | } else {
79 | return (
80 | <>
81 | >
82 | )
83 | }
84 | };
85 |
86 | export default class FollowerList extends React.Component {
87 | constructor(props) {
88 | super(props);
89 | this.state = {
90 | events: []
91 | }
92 | }
93 | async componentDidMount() {
94 | updateMainColWidth();
95 | document.getElementById("mastheadCenterContainer").innerHTML = "following"
96 |
97 | this.setState({events: [] })
98 | this.forceUpdate();
99 | }
100 | render() {
101 | return (
102 | <>
103 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | >
115 | );
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/renderer/pages/grapevineSettings/grapevineIsInactive.js:
--------------------------------------------------------------------------------
1 | import React, { Component, useState } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Masthead from '../../mastheads/mainMasthead.js';
4 | import LeftNavbar from '../../navbars/leftNav.js';
5 | import * as MiscAppFxns from "../../lib/app/misc.ts";
6 | import * as StartupFxns from "../../lib/app/startup.ts";
7 | // import ToggleSwitch1 from "../../lib/app/toggleSwitch1";
8 | import ToggleSwitch from "./toggleSwitch";
9 |
10 | const jQuery = require("jquery");
11 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
12 | const cloneObj = MiscAppFxns.cloneObj
13 | const secsToTime = MiscAppFxns.secsToTime
14 | const timeout = MiscAppFxns.timeout
15 |
16 | const inactiveGrapevine = (props) => {
17 | return (
18 | <>
19 |
20 | The grapevine is currently in the inactive state. Maybe you should turn it on! This will allow you to:
21 |
rate users along multiple dimensions
22 | curate message streams in a variety of ways
23 | For starters, you may want to allow your Grapevine to manage your list of trusted nostr relays dynamically so you don't have to!
24 |
25 | >
26 | )
27 | }
28 |
29 | export default inactiveGrapevine
30 |
--------------------------------------------------------------------------------
/src/renderer/pages/grapevineSettings/settingsMainComp.js:
--------------------------------------------------------------------------------
1 | import React, { Component, useState } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Masthead from '../../mastheads/mainMasthead.js';
4 | import LeftNavbar from '../../navbars/leftNav.js';
5 | import * as MiscAppFxns from "../../lib/app/misc.ts";
6 | import * as StartupFxns from "../../lib/app/startup.ts";
7 | import ToggleSwitch1 from "../../lib/app/toggleSwitch1";
8 | import ToggleSwitch from "./toggleSwitch";
9 | import GrapevineIsActive from "./grapevineIsActive";
10 | import GrapevineIsInactive from "./grapevineIsInactive";
11 |
12 | const jQuery = require("jquery");
13 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
14 | const cloneObj = MiscAppFxns.cloneObj
15 | const secsToTime = MiscAppFxns.secsToTime
16 | const timeout = MiscAppFxns.timeout
17 |
18 | const grapevineSettingsMainComponent = ({activationState}) => {
19 | console.log("activationState: "+activationState)
20 | var activeBlockClassName = "block_show"
21 | var inactiveBlockClassName = "block_hide"
22 | if (!activationState) {
23 | activeBlockClassName = "block_hide"
24 | inactiveBlockClassName = "block_show"
25 | }
26 | return (
27 | <>
28 |
29 |
30 |
31 |
32 |
33 |
34 | >
35 | )
36 | }
37 |
38 | export default grapevineSettingsMainComponent
39 |
--------------------------------------------------------------------------------
/src/renderer/pages/grapevineSettings/toggleSwitch.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const ToggleSwitch = ({ label }) => {
4 | return (
5 |
6 |
7 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | );
16 | };
17 |
18 | export default ToggleSwitch;
19 |
--------------------------------------------------------------------------------
/src/renderer/pages/landingPage/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { NavLink } from "react-router-dom";
4 | import Masthead from '../../mastheads/mainMasthead.js';
5 | import LeftNavbar from '../../navbars/leftNav.js';
6 | import * as MiscAppFxns from "../../lib/app/misc.ts";
7 | import * as StartupFxns from "../../lib/app/startup.ts";
8 |
9 | import { useNostrEvents, dateToUnix } from "nostr-react";
10 |
11 | const jQuery = require("jquery");
12 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
13 | const cloneObj = MiscAppFxns.cloneObj
14 | const secsToTime = MiscAppFxns.secsToTime
15 | const timeout = MiscAppFxns.timeout
16 | const fetchRelays = MiscAppFxns.fetchRelays;
17 |
18 | export default class Home extends React.Component {
19 |
20 | constructor(props) {
21 | super(props);
22 | this.state = {
23 | events: []
24 | }
25 | }
26 |
27 | async componentDidMount() {
28 | updateMainColWidth();
29 | document.getElementById("mastheadCenterContainer").innerHTML = "landing page"
30 |
31 | // run all startup functions if not already run
32 | if (!window.init.initMiscGlobalVars) {
33 | StartupFxns.initMiscGlobalVars()
34 | }
35 | if (!window.init.grapevineSettings) {
36 | StartupFxns.setGrapevineDefaults()
37 | }
38 | if (!window.init.initMyProfileData) {
39 | await StartupFxns.initMyProfileData();
40 | }
41 | if (!window.init.fetchProfilesInfo) {
42 | await StartupFxns.fetchProfilesInfo()
43 | }
44 | if (!window.init.fetchExtendedFollowingList) {
45 | await StartupFxns.fetchExtendedFollowingList()
46 | }
47 |
48 | await timeout(10)
49 | jQuery("#mainFeedButton").get(0).click()
50 | }
51 | render() {
52 | return (
53 | <>
54 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | main feed
64 |
65 |
66 |
67 |
68 | >
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/renderer/pages/mainFeed/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Link, NavLink } from "react-router-dom";
4 | import Masthead from '../../mastheads/mainMasthead.js';
5 | import LeftNavbar from '../../navbars/leftNav.js';
6 | import * as MiscAppFxns from "../../lib/app/misc.ts";
7 | import MainFeedTypeSelector from "./mainFeedTypeSelector";
8 | import UserPost from "../components/userPost";
9 |
10 | import { useNostrEvents, useProfile, dateToUnix } from "nostr-react";
11 |
12 | const jQuery = require("jquery");
13 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
14 |
15 | const GlobalFeed = ( ) => {
16 | const now = useRef(new Date()); // Make sure current time isn't re-rendered
17 | const currentTime = dateToUnix(now.current)
18 |
19 | if (window.mainFeed.type=="firehose") {
20 | const howLongAgo = 30 * 60; // 60 * 60 = fetch messages as old as one hour
21 | const sinceAgo = currentTime - howLongAgo;
22 | var { events } = useNostrEvents({
23 | filter: {
24 | since: sinceAgo, // all new events from now
25 | kinds: [1],
26 | },
27 | });
28 | }
29 | var aAuthors = [];
30 | if (window.myProfile) {
31 | aAuthors = window.myProfile.following
32 | }
33 | var aExtendedAuthors = [];
34 | if (window.aExtendedAuthors) {
35 | aExtendedAuthors = window.aExtendedAuthors
36 | }
37 | // console.log("aAuthors: "+JSON.stringify(aAuthors))
38 | // console.log("aExtendedAuthors: "+JSON.stringify(aExtendedAuthors))
39 | // console.log("window.mainFeed.type: "+window.mainFeed.type)
40 | if (window.mainFeed.type=="following") {
41 | const howLongAgo = 2 * 24 * 60 * 60; // 60 * 60 = fetch messages as old as one hour
42 | const sinceAgo = currentTime - howLongAgo;
43 | var { events } = useNostrEvents({
44 | filter: {
45 | authors: aAuthors,
46 | since: sinceAgo, // all new events from now
47 | kinds: [1],
48 | },
49 | });
50 | }
51 | if (window.mainFeed.type=="ffollowing") {
52 | const howLongAgo = 2 * 24 * 60 * 60; // 60 * 60 = fetch messages as old as one hour
53 | const sinceAgo = currentTime - howLongAgo;
54 | var { events } = useNostrEvents({
55 | filter: {
56 | authors: aExtendedAuthors,
57 | since: sinceAgo, // all new events from now
58 | kinds: [1],
59 | },
60 | });
61 | }
62 | const currentPage="mainFeed";
63 | return (
64 | <>
65 |
70 |
71 | {events.map( (event) => {
72 | return (
73 | <>
74 |
78 | >
79 | )}
80 | )}
81 |
82 | >
83 | );
84 | };
85 |
86 | export default class Home extends React.Component {
87 |
88 | constructor(props) {
89 | super(props);
90 | this.state = {
91 | events: []
92 | }
93 | }
94 |
95 | async componentDidMount() {
96 | updateMainColWidth();
97 | document.getElementById("mastheadCenterContainer").innerHTML = "main feed"
98 | }
99 | render() {
100 | return (
101 | <>
102 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | user profile
112 |
113 |
114 |
115 |
116 |
117 | >
118 | );
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/renderer/pages/mainFeed/mainFeedTypeSelector.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import { NavLink } from "react-router-dom";
3 | import * as MiscAppFxns from "../../lib/app/misc.ts";
4 |
5 | const timeout = MiscAppFxns.timeout
6 |
7 | const jQuery = require("jquery");
8 |
9 | const MainFeedTypeSelector2 = (props) => {
10 |
11 | return (
12 | <>
13 |
14 | >
15 | );
16 | }
17 |
18 | export default class MainFeedTypeSelector extends React.Component {
19 | constructor(props) {
20 | super(props);
21 | this.state = {
22 | mainFeedType: window.mainFeed.type,
23 | isFirehoseSelected: false,
24 | isFollowingSelected: false,
25 | isFFollowingSelected: false,
26 | isGrapevineSelected: true,
27 | isOtherUserFollowingSelected: false,
28 | numFollowingContainerClass: "numFollowingContainer_show",
29 | numFFollowingContainerClass: "numFFollowingContainer_show",
30 | }
31 | }
32 | async componentDidMount() {
33 | if (window.mainFeed.type=="firehose") {
34 | this.setState({isFirehoseSelected:true,isFollowingSelected:false,isFFollowingSelected:false,isGrapevineSelected:false,isOtherUserFollowingSelected:false,numFollowingContainerClass:"numFollowingContainer_hide",numFFollowingContainerClass:"numFollowingContainer_hide"})
35 | this.forceUpdate();
36 | }
37 | if (window.mainFeed.type=="following") {
38 | this.setState({isFirehoseSelected:false,isFollowingSelected:true,isFFollowingSelected:false,isGrapevineSelected:false,isOtherUserFollowingSelected:false,numFollowingContainerClass:"numFollowingContainer_show",numFFollowingContainerClass:"numFollowingContainer_hide"})
39 | this.forceUpdate();
40 | }
41 | if (window.mainFeed.type=="ffollowing") {
42 | this.setState({isFirehoseSelected:false,isFollowingSelected:false,isFFollowingSelected:true,isGrapevineSelected:false,isOtherUserFollowingSelected:false,numFollowingContainerClass:"numFollowingContainer_hide",numFFollowingContainerClass:"numFollowingContainer_show"})
43 | this.forceUpdate();
44 | }
45 | if (window.mainFeed.type=="grapevine") {
46 | this.setState({isFirehoseSelected:false,isFollowingSelected:false,isFFollowingSelected:false,isGrapevineSelected:true,isOtherUserFollowingSelected:false,numFollowingContainerClass:"numFollowingContainer_hide",numFFollowingContainerClass:"numFollowingContainer_hide"})
47 | this.forceUpdate();
48 | }
49 | if (window.mainFeed.type=="otherUserFollowing") {
50 | this.setState({isFirehoseSelected:false,isFollowingSelected:false,isFFollowingSelected:false,isGrapevineSelected:false,isOtherUserFollowingSelected:true,numFollowingContainerClass:"numFollowingContainer_hide",numFFollowingContainerClass:"numFollowingContainer_hide"})
51 | this.forceUpdate();
52 | }
53 | jQuery("#mainFeedTypeSelector").change(async function(){
54 | var newType = jQuery("#mainFeedTypeSelector option:selected").data("type")
55 | console.log("mainFeedTypeSelector changed to: "+newType)
56 | window.mainFeed.type = newType
57 | await timeout(10)
58 | jQuery("#mainFeedButton").get(0).click()
59 | })
60 | }
61 |
62 | render() {
63 | const pubkey = this.props.pubkey;
64 | return (
65 | <>
66 |
67 |
68 | landing page
69 |
70 |
71 | You are following
72 | { window.clickedPubKey=window.myPubkey; } } to='/FollowingList' style={{marginLeft:"5px"}} >
73 | {this.props.following.length} users
74 | .
75 |
76 |
77 | Extended following list (your following list + their following lists) ({this.props.extendedFollowing.length} users)
78 |
79 |
80 | following list
81 | Extended ™ following list
82 | firehose
83 | _____'s following list
84 | grapevine
85 |
86 |
87 | >
88 | );
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/renderer/pages/mainFeed/nameElem_deprecated.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import Masthead from '../../mastheads/mainMasthead.js';
3 | import LeftNavbar from '../../navbars/leftNav.js';
4 | import * as MiscAppFxns from "../../lib/app/misc.ts";
5 | import { useNostrEvents } from "nostr-react";
6 |
7 | const jQuery = require("jquery");
8 |
9 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
10 | const secsToTime = MiscAppFxns.secsToTime
11 | const cloneObj = MiscAppFxns.cloneObj
12 |
13 | const NameElem2 = (props) => {
14 | var { events } = useNostrEvents({
15 | filter: {
16 | authors: [ props.pubkey ],
17 | since: 0, // all new events from now
18 | kinds: [0],
19 | },
20 | });
21 | return (
22 | <>
23 | {events.map( (event) => {
24 | var name = JSON.parse(event.content).name;
25 | return (
26 | <>
27 | {name}
28 | >
29 | )}
30 | )}
31 | >
32 | );
33 | }
34 |
35 | export default class NameElem extends React.Component {
36 | constructor(props) {
37 | super(props);
38 | this.state = {
39 | }
40 | }
41 | async componentDidMount() {
42 | }
43 | render() {
44 | const pubkey = this.props.pubkey;
45 | return (
46 | <>
47 |
48 | >
49 | );
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/renderer/pages/manageChannels/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Masthead from '../../mastheads/mainMasthead.js';
3 | import LeftNavbar from '../../navbars/leftNav.js';
4 | import * as MiscAppFxns from "../../lib/app/misc.ts";
5 |
6 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
7 |
8 | export default class Home extends React.Component {
9 | constructor(props) {
10 | super(props);
11 | this.state = {}
12 | }
13 | async componentDidMount() {
14 | updateMainColWidth();
15 | document.getElementById("mastheadCenterContainer").innerHTML = "manage channels"
16 | }
17 | render() {
18 | return (
19 | <>
20 |
23 |
24 |
25 |
26 |
27 |
28 |
manage channels
29 |
(under construction)
30 |
31 |
32 |
33 | >
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/renderer/pages/myProfile/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink } from "react-router-dom";
3 | import Masthead from '../../mastheads/mainMasthead.js';
4 | import LeftNavbar from '../../navbars/leftNav.js';
5 | import * as MiscAppFxns from "../../lib/app/misc.ts";
6 | import * as StartupFxns from "../../lib/app/startup.ts";
7 | import FollowCounts from "../userProfile/followCounts";
8 | import UserPosts from "../components/userPosts";
9 |
10 | import {nip19, generatePrivateKey, getPublicKey} from 'nostr-tools'
11 |
12 | import { asyncSql } from "../../index.tsx";
13 |
14 | import {
15 | dateToUnix,
16 | } from "@nostrgg/react";
17 |
18 | const jQuery = require("jquery");
19 |
20 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
21 | const generateNewKeys = StartupFxns.generateNewKeys;
22 |
23 | const populateMyProfileInfo = (oMyProfileData) => {
24 | var myPubkey_hex = oMyProfileData.pubkey;
25 | window.clickedPubKey = myPubkey_hex;
26 | var myPubkey_bech32 = nip19.npubEncode(myPubkey_hex)
27 | var myName = oMyProfileData.name;
28 | var myDisplayName = oMyProfileData.display_name;
29 | var myAbout = oMyProfileData.about;
30 | var picture_url = oMyProfileData.picture_url;
31 |
32 | jQuery("#myProfileNameContainer").html("@"+myName)
33 |
34 | jQuery("#myProfileDisplayNameContainer").html(myDisplayName)
35 |
36 | jQuery("#myProfileAboutContainer").html(myAbout)
37 |
38 | jQuery("#myBech32PubkeyContainer").html(myPubkey_bech32)
39 |
40 | const avatarHTML = " "
41 |
42 | jQuery("#myProfileAvatarContainer").html(avatarHTML)
43 | }
44 |
45 | const fetchMyProfileInfo = async (withEditing) => {
46 | var sql = ""
47 | sql += "SELECT * FROM myProfile WHERE active=1"
48 | var aMyProfileData = await asyncSql(sql);
49 |
50 | if (aMyProfileData.length==0) {
51 | const [sk,pk] = await generateNewKeys();
52 | var aMyProfileData = await asyncSql(sql);
53 | }
54 |
55 | if (aMyProfileData.length > 0) {
56 | var oMyProfileData = aMyProfileData[0]
57 | populateMyProfileInfo(oMyProfileData);
58 | }
59 |
60 | console.log("oMyProfileData: "+JSON.stringify(oMyProfileData,null,4))
61 |
62 | return oMyProfileData;
63 | }
64 |
65 | /*
66 | const generateMyKeys = async () => {
67 | let sk = generatePrivateKey() // `sk` is a hex string
68 | let pk = getPublicKey(sk) // `pk` is a hex string
69 | const currentTime = dateToUnix(new Date())
70 | console.log("sk: "+sk)
71 | console.log("pk: "+pk)
72 |
73 | var sql = "";
74 | sql += "INSERT OR IGNORE INTO myProfile (pubkey, privkey, created_at) VALUES ('"+pk+"', '"+sk+"', "+currentTime+") ";
75 |
76 | var result = await asyncSql(sql);
77 | console.log("result: "+JSON.stringify(result,null,4))
78 |
79 | return [sk,pk]
80 | }
81 | */
82 |
83 | export default class Home extends React.Component {
84 | constructor(props) {
85 | super(props);
86 | this.state = {}
87 | }
88 | async componentDidMount() {
89 | updateMainColWidth();
90 | document.getElementById("mastheadCenterContainer").innerHTML = "my profile"
91 |
92 | await fetchMyProfileInfo(false);
93 | }
94 | render() {
95 | return (
96 | <>
97 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
113 |
114 |
115 | edit my profile
116 |
117 |
118 |
119 |
120 |
121 | myProfileDisplayNameContainer
122 |
123 |
124 | myProfileNameContainer
125 |
126 |
127 |
128 |
129 | pubkey (hex): {window.myPubkey}
130 | pubkey (bech32):
131 |
132 |
133 |
134 | myProfileAboutContainer
135 |
136 |
137 |
138 |
139 |
142 |
143 |
144 |
145 | >
146 | );
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/renderer/pages/searchForUser/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { NavLink } from "react-router-dom";
4 | import Masthead from '../../mastheads/mainMasthead.js';
5 | import LeftNavbar from '../../navbars/leftNav.js';
6 | import * as MiscAppFxns from "../../lib/app/misc.ts";
7 | import * as StartupFxns from "../../lib/app/startup.ts";
8 |
9 | import { useNostrEvents, dateToUnix } from "nostr-react";
10 | import {nip19} from 'nostr-tools'
11 |
12 | const jQuery = require("jquery");
13 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
14 | const cloneObj = MiscAppFxns.cloneObj
15 | const secsToTime = MiscAppFxns.secsToTime
16 | const timeout = MiscAppFxns.timeout
17 |
18 | export default class SearchForUser extends React.Component {
19 |
20 | constructor(props) {
21 | super(props);
22 | this.state = {
23 | pk_search: "10ce7cb95c73fbcf16cadda151bfd052a014f76281fc2a9e862236d82918a155",
24 | // pk_search: "npub1u5njm6g5h5cpw4wy8xugu62e5s7f6fnysv0sj0z3a8rengt2zqhsxrldq3",
25 | npub_search: "npub1u5njm6g5h5cpw4wy8xugu62e5s7f6fnysv0sj0z3a8rengt2zqhsxrldq3" // my iOS
26 | }
27 | }
28 |
29 | async componentDidMount() {
30 | updateMainColWidth();
31 | document.getElementById("mastheadCenterContainer").innerHTML = "search for user"
32 | jQuery("#userPubkey").change(function(){
33 | var userPubkey = jQuery("#userPubkey").val()
34 | console.log("userPubkey: "+userPubkey)
35 | jQuery("#navLinkElem1").data("pubkey",userPubkey)
36 | })
37 | jQuery("#userNpub").change(function(){
38 | var userNpub = jQuery("#userNpub").val()
39 | console.log("userNpub: "+userNpub)
40 | jQuery("#navLinkElem2").data("npub",userNpub)
41 | let {type, data} = nip19.decode(userNpub)
42 | console.log("type: "+type)
43 | console.log("data: "+data)
44 | jQuery("#userPubkey").val(data)
45 | })
46 |
47 | jQuery("#navLinkElem").click(function(){
48 | window.clickedPubKey = jQuery("#userPubkey").val()
49 | })
50 | }
51 | render() {
52 | return (
53 | <>
54 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | Enter a pubkey:
64 |
65 |
66 |
67 |
68 | Enter a public account id (starts with npub):
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | Search
77 |
78 |
79 |
80 |
81 | >
82 | );
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/renderer/pages/settings/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Masthead from '../../mastheads/mainMasthead.js';
3 | import LeftNavbar from '../../navbars/leftNav.js';
4 | import LeftNavbar2 from '../../navbars/leftNav2/settings.js';
5 | import * as MiscAppFxns from "../../lib/app/misc.ts";
6 | import * as StartupFxns from "../../lib/app/startup.ts";
7 |
8 | const fetchMyPk = MiscAppFxns.fetchMyPk;
9 |
10 | import { useNostrEvents, dateToUnix } from "nostr-react";
11 |
12 | const jQuery = require("jquery");
13 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
14 | const cloneObj = MiscAppFxns.cloneObj
15 | const secsToTime = MiscAppFxns.secsToTime
16 | const timeout = MiscAppFxns.timeout
17 |
18 | export default class Settings extends React.Component {
19 |
20 | constructor(props) {
21 | super(props);
22 | this.state = {
23 | events: []
24 | }
25 | }
26 |
27 | async componentDidMount() {
28 | updateMainColWidth();
29 | document.getElementById("mastheadCenterContainer").innerHTML = "settings"
30 | }
31 | render() {
32 | return (
33 | <>
34 |
38 |
39 |
40 |
41 |
42 |
43 | Choose an option on the left
44 |
45 |
46 | >
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/renderer/pages/settings/profilekeys/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Masthead from '../../../mastheads/mainMasthead.js';
3 | import LeftNavbar from '../../../navbars/leftNav.js';
4 | import LeftNavbar2 from '../../../navbars/leftNav2/settings.js';
5 | import * as MiscAppFxns from "../../../lib/app/misc.ts";
6 | import * as StartupFxns from "../../../lib/app/startup.ts";
7 | import PkSettings from "./pk";
8 |
9 |
10 | const fetchMyPk = MiscAppFxns.fetchMyPk;
11 |
12 | import { useNostrEvents, dateToUnix } from "nostr-react";
13 |
14 | const jQuery = require("jquery");
15 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
16 | const cloneObj = MiscAppFxns.cloneObj
17 | const secsToTime = MiscAppFxns.secsToTime
18 | const timeout = MiscAppFxns.timeout
19 |
20 | export default class Home extends React.Component {
21 |
22 | constructor(props) {
23 | super(props);
24 | this.state = {
25 | events: []
26 | }
27 | }
28 |
29 | async componentDidMount() {
30 | updateMainColWidth();
31 | document.getElementById("mastheadCenterContainer").innerHTML = "settings: profile keys"
32 | }
33 | render() {
34 | return (
35 | <>
36 |
40 |
48 | >
49 | );
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/renderer/pages/settings/relays/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Masthead from '../../../mastheads/mainMasthead.js';
3 | import LeftNavbar from '../../../navbars/leftNav.js';
4 | import LeftNavbar2 from '../../../navbars/leftNav2/settings.js';
5 | import * as MiscAppFxns from "../../../lib/app/misc.ts";
6 | import * as StartupFxns from "../../../lib/app/startup.ts";
7 |
8 | import RelaysSettings from "./relays";
9 |
10 | const fetchMyPk = MiscAppFxns.fetchMyPk;
11 |
12 | import { useNostrEvents, dateToUnix } from "nostr-react";
13 |
14 | const jQuery = require("jquery");
15 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
16 | const cloneObj = MiscAppFxns.cloneObj
17 | const secsToTime = MiscAppFxns.secsToTime
18 | const timeout = MiscAppFxns.timeout
19 |
20 | export default class Home extends React.Component {
21 |
22 | constructor(props) {
23 | super(props);
24 | this.state = {
25 | events: []
26 | }
27 | }
28 |
29 | async componentDidMount() {
30 | updateMainColWidth();
31 | document.getElementById("mastheadCenterContainer").innerHTML = "settings: relays"
32 | }
33 | render() {
34 | return (
35 | <>
36 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | >
49 | );
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/renderer/pages/settings/relays/relays.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import * as MiscAppFxns from "../../../lib/app/misc.ts";
3 | import { asyncSql } from "../../../index.tsx";
4 |
5 | import {
6 | dateToUnix,
7 | } from "@nostrgg/react";
8 |
9 | const jQuery = require("jquery");
10 |
11 | const fetchRelays = MiscAppFxns.fetchRelays;
12 |
13 | const createKnownRelaysList = async () => {
14 |
15 | var aVerbose = await fetchRelays("verbose")
16 | var aActive = aVerbose[0];
17 | var aDefault = aVerbose[1];
18 | var aAll = aVerbose[2];
19 |
20 | jQuery("#knownRelaysContainer").html("")
21 |
22 | for (var z=0;z";
26 | nextRelayHTML += " ";
31 |
32 | nextRelayHTML += "";
33 | nextRelayHTML += url;
34 | nextRelayHTML += "
";
35 |
36 | nextRelayHTML += "";
40 | nextRelayHTML += "delete";
41 | nextRelayHTML += "
";
42 | nextRelayHTML += "";
43 | jQuery("#knownRelaysContainer").append(nextRelayHTML)
44 | }
45 | jQuery(".relayCheckbox").change(function(){
46 | var newState = jQuery(this).prop("checked")
47 | var z = jQuery(this).data("z")
48 | var url = jQuery(this).data("url")
49 | var sql = " UPDATE relays SET active="+newState+" WHERE url='"+url+"' "
50 | asyncSql(sql).then((result) => {
51 | jQuery("#updateStatusSuccess").html(url+" activity status has been updated to "+newState+". You currently need to restart the app for the changes to take effect.")
52 | });
53 | })
54 | jQuery(".deleteRelayButton").click(async function(){
55 | var z = jQuery(this).data("z")
56 | var url = jQuery(this).data("url")
57 | var sql = " DELETE FROM relays WHERE url='"+url+"' ";
58 | asyncSql(sql).then((result) => {
59 | jQuery("#deleteRelaySuccess").html(url+" has been deleted. You currently need to restart the app for the changes to take effect.")
60 | });
61 | })
62 | }
63 |
64 | export default class RelaysSettings extends React.Component {
65 |
66 | async componentDidMount() {
67 | await createKnownRelaysList()
68 | jQuery("#addRelayButton").click(async function(){
69 | var newRelayUrl = jQuery("#newRelayTextarea").val();
70 | var currentTime = dateToUnix(new Date())
71 | var sql = "";
72 | sql += "INSERT OR IGNORE INTO relays (url, default_app, active) VALUES ('"+newRelayUrl+"', false, false) ";
73 | console.log("addRelayButton sql: "+sql)
74 | asyncSql(sql).then((result) => {
75 | jQuery("#newRelayAddedSuccess").html("New relay added.")
76 | });
77 | })
78 | jQuery("#selectAllRelaysButton").click(function(){
79 | jQuery(".relayCheckbox").prop("checked",true)
80 | })
81 | jQuery("#deselectAllRelaysButton").click(function(){
82 | jQuery(".relayCheckbox").prop("checked",false)
83 | })
84 | jQuery("#resetDefaultRelaysButton").click(function(){
85 |
86 | })
87 | }
88 | render() {
89 | return (
90 | <>
91 |
92 |
93 |
94 | select all
95 |
96 |
97 | deselect all
98 |
99 |
100 | reset app defaults
101 |
102 |
103 |
104 |
105 |
106 |
107 |
109 |
110 | add a new relay
111 |
112 |
113 |
114 |
115 |
116 | >
117 | );
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/renderer/pages/settings/sql/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink } from "react-router-dom";
3 | import Masthead from '../../../mastheads/mainMasthead.js';
4 | import LeftNavbar from '../../../navbars/leftNav.js';
5 | import LeftNavbar2 from '../../../navbars/leftNav2/settings.js';
6 | import * as MiscAppFxns from "../../../lib/app/misc.ts";
7 | import SqlBody from './sql'
8 |
9 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
10 |
11 | export default class Home extends React.Component {
12 |
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | events: []
17 | }
18 | }
19 |
20 | async componentDidMount() {
21 | updateMainColWidth();
22 | document.getElementById("mastheadCenterContainer").innerHTML = "settings: sqlite3"
23 | }
24 | render() {
25 | return (
26 | <>
27 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | >
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/renderer/pages/settings/sql/sql.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { asyncSql } from "../../../index.tsx";
3 |
4 | function SqlBody() {
5 | const [message, setMessage] = useState('SELECT * FROM nostrProfiles');
6 | const [response, setResponse] = useState();
7 |
8 | function send(sql) {
9 | asyncSql(sql).then((result) => setResponse(result));
10 | }
11 |
12 | return (
13 | <>
14 | Test it out
15 |
16 |
17 | For demo purposes, myCoolTable is created at startup in src/main/main.ts. You can create more tables, add rows, or execute other sql commands below.
18 |
19 |
20 |
21 | Type in an sql command, or copy and paste one of the example sql commands below, and submit it to the main process (by clicking 'Send').
22 |
23 |
24 |
25 | SELECT * FROM myCoolTable
26 |
27 | SELECT sqlite_version()
28 |
29 | CREATE TABLE IF NOT EXISTS anotherCoolTable (id INTEGER PRIMARY KEY AUTOINCREMENT, itemName TEXT NULL)
30 |
31 | INSERT OR IGNORE INTO anotherCoolTable (itemName) VALUES ('foo')
32 |
33 | SELECT * FROM sqlite_schema
34 |
35 | SELECT * FROM nostrProfiles
36 |
37 |
38 |
41 |
42 |
43 | setMessage(value)}
47 | />
48 | send(message)}>
49 | Send
50 |
51 |
52 |
53 | The SQL response, transmitted via IRC from the main process to the renderer process (here), is an object in JSON:
54 |
55 |
56 | {(response && JSON.stringify(response, null, 2)) ||
57 | 'No query results yet!'}
58 |
59 | >
60 | );
61 | }
62 |
63 | export default SqlBody
64 |
--------------------------------------------------------------------------------
/src/renderer/pages/userList/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Masthead from '../../mastheads/mainMasthead.js';
3 | import LeftNavbar from '../../navbars/leftNav.js';
4 | import * as MiscAppFxns from "../../lib/app/misc.ts";
5 |
6 | import { asyncSql } from "../../index.tsx";
7 |
8 | const jQuery = require("jquery");
9 |
10 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
11 | const isValidObj = MiscAppFxns.isValidObj;
12 |
13 | const fetchProfilesInfo = async () => {
14 | var aProfileInfo = [];
15 |
16 | var sql = ""
17 | sql += "SELECT * FROM nostrProfiles "
18 |
19 | var aNostrProfilesData = await asyncSql(sql);
20 | console.log("aNostrProfilesData.length: "+aNostrProfilesData.length)
21 | // for (var n=0;n";
32 | if (name) {
33 | userHTML += "";
34 | userHTML += name;
35 | userHTML += "
";
36 | aProfileInfo[n].name = name;
37 | } else {
38 | aProfileInfo[n].name = "..."+pK.slice(-6);
39 | }
40 |
41 | if (picture_url) {
42 | aProfileInfo[n].picture_url = picture_url;
43 | userHTML += "";
44 | userHTML += picture_url;
45 | userHTML += "
";
46 | } else {
47 | aProfileInfo[n].picture_url = null;
48 | }
49 |
50 | if (content) {
51 | if (isValidObj(content)) {
52 | userHTML += "";
53 | userHTML += JSON.stringify(JSON.parse(content),null,4);
54 | userHTML += " ";
55 | }
56 | }
57 | userHTML += "";
58 | jQuery("#userListContainer").append(userHTML)
59 | }
60 |
61 | return aProfileInfo;
62 | }
63 |
64 | export default class Home extends React.Component {
65 | constructor(props) {
66 | super(props);
67 | this.state = {
68 | users: [
69 | ]
70 | }
71 | }
72 | async componentDidMount() {
73 | updateMainColWidth();
74 | document.getElementById("mastheadCenterContainer").innerHTML = "userList"
75 | const aProfileInfo = await fetchProfilesInfo();
76 | console.log("aProfileInfo.length: "+aProfileInfo.length)
77 | this.setState({users: aProfileInfo})
78 | this.forceUpdate();
79 | }
80 | render() {
81 | return (
82 | <>
83 |
86 |
87 |
88 |
89 |
90 |
91 |
userList
92 |
userListContainer
93 |
94 |
95 | >
96 | );
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/renderer/pages/userProfile/followCounts.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import { NavLink } from "react-router-dom";
3 | import Masthead from '../../mastheads/mainMasthead.js';
4 | import LeftNavbar from '../../navbars/leftNav.js';
5 | import * as MiscAppFxns from "../../lib/app/misc.ts";
6 | import { useNostrEvents } from "nostr-react";
7 |
8 | const jQuery = require("jquery");
9 |
10 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
11 | const secsToTime = MiscAppFxns.secsToTime
12 | const cloneObj = MiscAppFxns.cloneObj
13 |
14 | const FollowCounts2 = (props) => {
15 | var { events } = useNostrEvents({
16 | filter: {
17 | authors: [ props.pubkey ],
18 | since: 0, // all new events from now
19 | kinds: [3],
20 | },
21 | });
22 | // console.log("FollowCounts2 events: "+JSON.stringify(events,null,4))
23 | if (events.length > 0) {
24 | events.sort((a, b) => parseFloat(b.created_at) - parseFloat(a.created_at));
25 | var event_ = events[0];
26 | var aFollowing_ = event_.tags
27 | return (
28 | <>
29 |
30 |
31 | {aFollowing_.length}
32 | following
33 |
34 |
35 |
?
36 | followers
37 |
38 |
39 | >
40 | );
41 | } else {
42 | return (
43 | <>
44 |
45 |
46 | ?
47 | following
48 |
49 |
50 |
?
51 | followers
52 |
53 |
54 | >
55 | );
56 | }
57 | }
58 |
59 | export default class FollowCounts extends React.Component {
60 | constructor(props) {
61 | super(props);
62 | this.state = {
63 | }
64 | }
65 | async componentDidMount() {
66 | console.log("this.props.pubkey: "+this.props.pubkey)
67 | }
68 | render() {
69 | const pk = this.props.pubkey;
70 | return (
71 | <>
72 |
73 |
74 |
75 | >
76 | );
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/renderer/pages/userProfile/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import { NavLink } from "react-router-dom";
3 | import Masthead from '../../mastheads/mainMasthead.js';
4 | import LeftNavbar from '../../navbars/leftNav.js';
5 | import * as MiscAppFxns from "../../lib/app/misc.ts";
6 | import { useNostrEvents, useProfile } from "nostr-react";
7 | import UserPosts from "../components/userPosts";
8 | import UserInfo from "./userInfo";
9 |
10 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
11 |
12 | export default class Home extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | events: []
17 | }
18 | }
19 | async componentDidMount() {
20 | updateMainColWidth();
21 | document.getElementById("mastheadCenterContainer").innerHTML = "user profile"
22 | }
23 | render() {
24 | return (
25 | <>
26 |
29 |
30 |
31 |
32 |
33 |
34 |
36 |
39 |
40 |
41 | >
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/renderer/pages/userProfile/storeProfileLocallyButton.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import Masthead from '../../mastheads/mainMasthead.js';
3 | import LeftNavbar from '../../navbars/leftNav.js';
4 | import * as MiscAppFxns from "../../lib/app/misc.ts";
5 |
6 | const jQuery = require("jquery");
7 |
8 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
9 | const secsToTime = MiscAppFxns.secsToTime
10 |
11 | export default class StoreProfileLocallyButton extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | }
16 | }
17 | async componentDidMount() {
18 | // var pubkey = window.clickedPubKey;
19 | const pk = this.props.pubkey;
20 | jQuery("#storeProfileLocallyButton").click(function(){
21 | console.log("storeProfileLocallyButton; pubkey: "+pk)
22 | })
23 | }
24 | render() {
25 | return (
26 | <>
27 |
28 | Store Profile
29 |
30 | >
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/renderer/pages/userProfile/userInfo.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import { NavLink } from "react-router-dom";
3 | import FollowCounts from "./followCounts";
4 | import FollowButton from "../components/followButton";
5 | import LeaveGrapevineRatings from "./leaveGrapevineRatings";
6 | import { useNostrEvents, useProfile } from "nostr-react";
7 | import {nip19} from 'nostr-tools'
8 | import * as MiscAppFxns from "../../lib/app/misc.ts";
9 |
10 | const jQuery = require("jquery");
11 |
12 | const UserInfo = () => {
13 | var { events } = useNostrEvents({
14 | filter: {
15 | authors: [ window.clickedPubKey ],
16 | since: 0, // all new events from now
17 | kinds: [0],
18 | },
19 | });
20 | var npub = nip19.npubEncode(window.clickedPubKey)
21 | window.clickedAvatarHTML = "";
22 | window.clickedName = "..." + window.clickedPubKey.slice(-6);
23 | window.clickedAvatarUrl = "";
24 | if (events.length > 0) {
25 | // need to make sure sort order is correct
26 | events.sort((a, b) => parseFloat(b.created_at) - parseFloat(a.created_at));
27 | var event = events[0];
28 | var pic_url = JSON.parse(event.content).picture;
29 | window.clickedAvatarUrl = pic_url
30 | var picHTML = ' ';
31 | jQuery(".smallAvatarContainer").html(picHTML)
32 | var name = JSON.parse(event.content).name;
33 | window.clickedName = name;
34 | // jQuery(".eventNameContainer").html(name)
35 | return (
36 | <>
37 |
38 | {JSON.stringify(event,null,4)}
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | { JSON.parse(event.content).display_name }
53 |
54 |
55 | @{ JSON.parse(event.content).name }
56 |
57 |
58 |
59 |
60 | pubkey (hex): {window.clickedPubKey}
61 | pubkey (bech32): {npub}
62 |
63 |
64 |
77 |
78 |
79 | { JSON.parse(event.content).about }
80 |
81 |
82 |
83 |
84 |
85 | >
86 | );
87 | } else {
88 | return (
89 | <>
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | ... {window.clickedPubKey.slice(-6)}
104 |
105 |
106 |
107 |
108 | pubkey: {window.clickedPubKey}
109 |
110 |
111 |
112 | about
113 |
114 |
115 |
116 |
117 |
118 |
122 |
123 |
124 |
125 |
126 | >
127 | );
128 | }
129 | };
130 |
131 | export default UserInfo;
132 |
--------------------------------------------------------------------------------
/src/renderer/pages/visJsHelloWorld/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import Masthead from '../../mastheads/mainMasthead.js';
3 | import LeftNavbar from '../../navbars/leftNav.js';
4 | import * as MiscAppFxns from "../../lib/app/misc.ts";
5 | import { DataSet, Network} from 'vis-network/standalone/esm/vis-network';
6 | import * as VisStyleConstants from '../../lib/visjs/visjs-style';
7 |
8 | const jQuery = require("jquery");
9 |
10 | const updateMainColWidth = MiscAppFxns.updateMainColWidth;
11 |
12 | const groupOptions = VisStyleConstants.groupOptions;
13 |
14 | const options = VisStyleConstants.options;
15 |
16 | export var nodes = new DataSet([
17 | { id: 1, label: 'Node 1' },
18 | { id: 2, label: 'Node 2' },
19 | { id: 3, label: 'Node 3' },
20 | { id: 4, label: 'Node 4' },
21 | { id: 5, label: 'Node 5' },
22 | { id: 6, label: 'Node 6' },
23 | { id: 7, label: 'Node 7' },
24 | { id: 8, label: 'Node 8' },
25 | ]);
26 |
27 | export var edges = new DataSet([
28 | {from: 1, to: 2},
29 | {from: 1, to: 3},
30 | {from: 2, to: 4},
31 | {from: 1, to: 5},
32 | {from: 3, to: 6},
33 | {from: 2, to: 7},
34 | {from: 2, to: 8}
35 | ]);
36 |
37 | export var data = {
38 | nodes,
39 | edges
40 | };
41 |
42 | export var network = {};
43 |
44 | const NFG_Graphic = () => {
45 | /*
46 | var nodes_arr = [];
47 | var edges_arr = [];
48 |
49 | nodes = new DataSet(nodes_arr);
50 | edges = new DataSet(edges_arr);
51 | data = {
52 | nodes,
53 | edges
54 | };
55 | */
56 |
57 | var domNode = useRef(null);
58 |
59 | network = useRef(null);
60 |
61 | useEffect(
62 | () => {
63 | network.current = new Network(domNode.current, data, options);
64 | network.current.fit();
65 |
66 | network.current.on("click",function(params){
67 | var nodes_arr = params.nodes;
68 | var numNodes = nodes_arr.length;
69 | });
70 |
71 | // EDGES
72 | network.current.on("selectEdge",function(params){
73 | // console.log("selectEdge event triggered")
74 | var edges_arr = params.edges;
75 | var numEdges = edges_arr.length;
76 | if (numEdges==1) {
77 | var edgeID = edges_arr[0];
78 | }
79 | });
80 | network.current.on("deselectEdge",function(params){
81 | });
82 |
83 | // NODES
84 | network.current.on("selectNode",function(params){
85 | // console.log("selectNode event triggered")
86 | var nodes_arr = params.nodes;
87 | var numNodes = nodes_arr.length;
88 | if (numNodes==1) {
89 | var nodeID = nodes_arr[0];
90 | var node = nodes.get(nodeID);
91 | var name = node.name;
92 | // drawScoreCalculationPanel(nodeID)
93 | }
94 | });
95 | network.current.on("deselectNode",function(params){
96 | // jQuery("#usernameContainer").html("none")
97 | });
98 | },
99 | [domNode, network, data, options]
100 | );
101 |
102 | return (
103 | <>
104 |
105 | >
106 | );
107 | };
108 |
109 | export default class ExtendedFollowerList extends React.Component {
110 | constructor(props) {
111 | super(props);
112 | this.state = {
113 | }
114 | }
115 | async componentDidMount() {
116 | updateMainColWidth();
117 | document.getElementById("mastheadCenterContainer").innerHTML = "visjs hello world"
118 | }
119 | render() {
120 | return (
121 | <>
122 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | visjs hello world
132 |
133 |
134 |
135 |
136 | >
137 | );
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/renderer/preload.d.ts:
--------------------------------------------------------------------------------
1 | import { Channels } from 'main/preload';
2 |
3 | declare global {
4 | interface Window {
5 | electron: {
6 | ipcRenderer: {
7 | sendMessage(channel: Channels, args: unknown[]): void;
8 | on(
9 | channel: Channels,
10 | func: (...args: unknown[]) => void
11 | ): (() => void) | undefined;
12 | once(channel: Channels, func: (...args: unknown[]) => void): void;
13 | };
14 | };
15 | }
16 | }
17 |
18 | export {};
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "incremental": true,
4 | "target": "es2021",
5 | "module": "commonjs",
6 | "lib": ["dom", "es2021"],
7 | "jsx": "react-jsx",
8 | "strict": true,
9 | "sourceMap": true,
10 | "baseUrl": "./src",
11 | "moduleResolution": "node",
12 | "esModuleInterop": true,
13 | "allowSyntheticDefaultImports": true,
14 | "resolveJsonModule": true,
15 | "allowJs": true,
16 | "outDir": ".erb/dll"
17 | },
18 | "exclude": ["test", "release/build", "release/app/dist", ".erb/dll"]
19 | }
20 |
--------------------------------------------------------------------------------