mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 12:30:12 +02:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 27dce55ca2 | |||
| 830f453642 | |||
| 8f48824955 | |||
| fbc23d545c | |||
| 15eb4a70aa | |||
| 3a13301e23 | |||
| eb5ad541d9 | |||
| 68df672953 |
@@ -1,3 +0,0 @@
|
||||
node_modules
|
||||
Dockerfile
|
||||
docker-compose.*
|
||||
+6
-3
@@ -1,9 +1,12 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-console": "off",
|
||||
"global-require": "off",
|
||||
"import/no-dynamic-require": "off"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Base webpack config used across other specific configs
|
||||
*/
|
||||
|
||||
import webpack from 'webpack';
|
||||
import { dependencies as externals } from '../../release/app/package.json';
|
||||
import webpackPaths from './webpack.paths';
|
||||
import { TsconfigPathsPlugin } from 'tsconfig-paths-webpack-plugin';
|
||||
|
||||
const createStyledComponentsTransformer = require('typescript-plugin-styled-components').default;
|
||||
|
||||
const styledComponentsTransformer = createStyledComponentsTransformer();
|
||||
|
||||
const configuration: webpack.Configuration = {
|
||||
externals: [...Object.keys(externals || {})],
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
exclude: /node_modules/,
|
||||
test: /\.[jt]sx?$/,
|
||||
use: {
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
// Remove this line to enable type checking in webpack builds
|
||||
transpileOnly: true,
|
||||
getCustomTransformers: () => ({ before: [styledComponentsTransformer] }),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
output: {
|
||||
// https://github.com/webpack/webpack/issues/1114
|
||||
library: {
|
||||
type: 'commonjs2',
|
||||
},
|
||||
|
||||
path: webpackPaths.srcPath,
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.EnvironmentPlugin({
|
||||
NODE_ENV: 'production',
|
||||
}),
|
||||
],
|
||||
|
||||
/**
|
||||
* Determine the array of extensions that should be used to resolve modules.
|
||||
*/
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
|
||||
fallback: {
|
||||
child_process: false,
|
||||
},
|
||||
plugins: [new TsconfigPathsPlugin({ baseUrl: webpackPaths.srcPath })],
|
||||
modules: [webpackPaths.srcPath, 'node_modules'],
|
||||
},
|
||||
|
||||
stats: 'errors-only',
|
||||
};
|
||||
|
||||
export default configuration;
|
||||
@@ -0,0 +1,3 @@
|
||||
/* eslint import/no-unresolved: off, import/no-self-import: off */
|
||||
|
||||
module.exports = require('./webpack.config.renderer.dev').default;
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Webpack config for production electron main process
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import TerserPlugin from 'terser-webpack-plugin';
|
||||
import webpack from 'webpack';
|
||||
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
||||
import { merge } from 'webpack-merge';
|
||||
|
||||
import checkNodeEnv from '../scripts/check-node-env';
|
||||
import deleteSourceMaps from '../scripts/delete-source-maps';
|
||||
import baseConfig from './webpack.config.base';
|
||||
import webpackPaths from './webpack.paths';
|
||||
|
||||
checkNodeEnv('production');
|
||||
deleteSourceMaps();
|
||||
|
||||
const devtoolsConfig =
|
||||
process.env.DEBUG_PROD === 'true'
|
||||
? {
|
||||
devtool: 'source-map',
|
||||
}
|
||||
: {};
|
||||
|
||||
const configuration: webpack.Configuration = {
|
||||
...devtoolsConfig,
|
||||
|
||||
mode: 'production',
|
||||
|
||||
target: 'electron-main',
|
||||
|
||||
entry: {
|
||||
main: path.join(webpackPaths.srcMainPath, 'main.ts'),
|
||||
preload: path.join(webpackPaths.srcMainPath, 'preload.ts'),
|
||||
},
|
||||
|
||||
output: {
|
||||
path: webpackPaths.distMainPath,
|
||||
filename: '[name].js',
|
||||
},
|
||||
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
parallel: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
|
||||
}),
|
||||
|
||||
/**
|
||||
* Create global constants which can be configured at compile time.
|
||||
*
|
||||
* Useful for allowing different behaviour between development builds and
|
||||
* release builds
|
||||
*
|
||||
* NODE_ENV should be production so that modules do not perform certain
|
||||
* development checks
|
||||
*/
|
||||
new webpack.EnvironmentPlugin({
|
||||
NODE_ENV: 'production',
|
||||
DEBUG_PROD: false,
|
||||
START_MINIMIZED: false,
|
||||
}),
|
||||
],
|
||||
|
||||
/**
|
||||
* Disables webpack processing of __dirname and __filename.
|
||||
* If you run the bundle in node.js it falls back to these values of node.js.
|
||||
* https://github.com/webpack/webpack/issues/2010
|
||||
*/
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false,
|
||||
},
|
||||
};
|
||||
|
||||
export default merge(baseConfig, configuration);
|
||||
@@ -0,0 +1,70 @@
|
||||
import path from 'path';
|
||||
|
||||
import webpack from 'webpack';
|
||||
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
||||
import { merge } from 'webpack-merge';
|
||||
|
||||
import checkNodeEnv from '../scripts/check-node-env';
|
||||
import baseConfig from './webpack.config.base';
|
||||
import webpackPaths from './webpack.paths';
|
||||
|
||||
// When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's
|
||||
// at the dev webpack config is not accidentally run in a production environment
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
checkNodeEnv('development');
|
||||
}
|
||||
|
||||
const configuration: webpack.Configuration = {
|
||||
devtool: 'inline-source-map',
|
||||
|
||||
mode: 'development',
|
||||
|
||||
target: 'electron-preload',
|
||||
|
||||
entry: path.join(webpackPaths.srcMainPath, 'preload.ts'),
|
||||
|
||||
output: {
|
||||
path: webpackPaths.dllPath,
|
||||
filename: 'preload.js',
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
|
||||
}),
|
||||
|
||||
/**
|
||||
* Create global constants which can be configured at compile time.
|
||||
*
|
||||
* Useful for allowing different behaviour between development builds and
|
||||
* release builds
|
||||
*
|
||||
* NODE_ENV should be production so that modules do not perform certain
|
||||
* development checks
|
||||
*
|
||||
* By default, use 'development' as NODE_ENV. This can be overriden with
|
||||
* 'staging', for example, by changing the ENV variables in the npm scripts
|
||||
*/
|
||||
new webpack.EnvironmentPlugin({
|
||||
NODE_ENV: 'development',
|
||||
}),
|
||||
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
debug: true,
|
||||
}),
|
||||
],
|
||||
|
||||
/**
|
||||
* Disables webpack processing of __dirname and __filename.
|
||||
* If you run the bundle in node.js it falls back to these values of node.js.
|
||||
* https://github.com/webpack/webpack/issues/2010
|
||||
*/
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false,
|
||||
},
|
||||
|
||||
watch: true,
|
||||
};
|
||||
|
||||
export default merge(baseConfig, configuration);
|
||||
@@ -0,0 +1,127 @@
|
||||
import 'webpack-dev-server';
|
||||
import path from 'path';
|
||||
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import webpack from 'webpack';
|
||||
import { merge } from 'webpack-merge';
|
||||
|
||||
import checkNodeEnv from '../scripts/check-node-env';
|
||||
import baseConfig from './webpack.config.base';
|
||||
import webpackPaths from './webpack.paths';
|
||||
|
||||
const { version } = require('../../package.json');
|
||||
|
||||
// When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's
|
||||
// at the dev webpack config is not accidentally run in a production environment
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
checkNodeEnv('development');
|
||||
}
|
||||
|
||||
const configuration: webpack.Configuration = {
|
||||
devtool: 'inline-source-map',
|
||||
|
||||
mode: 'development',
|
||||
|
||||
target: ['web'],
|
||||
|
||||
entry: {
|
||||
remote: path.join(webpackPaths.srcRemotePath, 'index.tsx'),
|
||||
worker: path.join(webpackPaths.srcRemotePath, 'service-worker.ts'),
|
||||
},
|
||||
|
||||
output: {
|
||||
path: webpackPaths.dllPath,
|
||||
publicPath: '/',
|
||||
filename: '[name].js',
|
||||
library: {
|
||||
type: 'umd',
|
||||
},
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.s?css$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: true,
|
||||
sourceMap: true,
|
||||
importLoaders: 1,
|
||||
},
|
||||
},
|
||||
'sass-loader',
|
||||
],
|
||||
include: /\.module\.s?(c|a)ss$/,
|
||||
},
|
||||
{
|
||||
test: /\.s?css$/,
|
||||
use: ['style-loader', 'css-loader', 'sass-loader'],
|
||||
exclude: /\.module\.s?(c|a)ss$/,
|
||||
},
|
||||
// Fonts
|
||||
{
|
||||
test: /\.(woff|woff2|eot|ttf|otf)$/i,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
// Images
|
||||
{
|
||||
test: /\.(png|svg|jpg|jpeg|gif)$/i,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
|
||||
/**
|
||||
* Create global constants which can be configured at compile time.
|
||||
*
|
||||
* Useful for allowing different behaviour between development builds and
|
||||
* release builds
|
||||
*
|
||||
* NODE_ENV should be production so that modules do not perform certain
|
||||
* development checks
|
||||
*
|
||||
* By default, use 'development' as NODE_ENV. This can be overriden with
|
||||
* 'staging', for example, by changing the ENV variables in the npm scripts
|
||||
*/
|
||||
new webpack.EnvironmentPlugin({
|
||||
NODE_ENV: 'development',
|
||||
}),
|
||||
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
debug: true,
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
filename: path.join('index.html'),
|
||||
template: path.join(webpackPaths.srcRemotePath, 'index.ejs'),
|
||||
favicon: path.join(webpackPaths.assetsPath, 'icons', 'favicon.ico'),
|
||||
minify: {
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
},
|
||||
isBrowser: true,
|
||||
env: process.env.NODE_ENV,
|
||||
isDevelopment: process.env.NODE_ENV !== 'production',
|
||||
nodeModules: webpackPaths.appNodeModulesPath,
|
||||
templateParameters: {
|
||||
version,
|
||||
prod: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false,
|
||||
},
|
||||
|
||||
watch: true,
|
||||
};
|
||||
|
||||
export default merge(baseConfig, configuration);
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Build config for electron renderer process
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
||||
import TerserPlugin from 'terser-webpack-plugin';
|
||||
import webpack from 'webpack';
|
||||
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
||||
import { merge } from 'webpack-merge';
|
||||
|
||||
import checkNodeEnv from '../scripts/check-node-env';
|
||||
import deleteSourceMaps from '../scripts/delete-source-maps';
|
||||
import baseConfig from './webpack.config.base';
|
||||
import webpackPaths from './webpack.paths';
|
||||
|
||||
const { version } = require('../../package.json');
|
||||
|
||||
checkNodeEnv('production');
|
||||
deleteSourceMaps();
|
||||
|
||||
const devtoolsConfig =
|
||||
process.env.DEBUG_PROD === 'true'
|
||||
? {
|
||||
devtool: 'source-map',
|
||||
}
|
||||
: {};
|
||||
|
||||
const configuration: webpack.Configuration = {
|
||||
...devtoolsConfig,
|
||||
|
||||
mode: 'production',
|
||||
|
||||
target: ['web'],
|
||||
|
||||
entry: {
|
||||
remote: path.join(webpackPaths.srcRemotePath, 'index.tsx'),
|
||||
worker: path.join(webpackPaths.srcRemotePath, 'service-worker.ts'),
|
||||
},
|
||||
|
||||
output: {
|
||||
path: webpackPaths.distRemotePath,
|
||||
publicPath: './',
|
||||
filename: '[name].js',
|
||||
library: {
|
||||
type: 'umd',
|
||||
},
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.s?(a|c)ss$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: true,
|
||||
sourceMap: true,
|
||||
importLoaders: 1,
|
||||
},
|
||||
},
|
||||
'sass-loader',
|
||||
],
|
||||
include: /\.module\.s?(c|a)ss$/,
|
||||
},
|
||||
{
|
||||
test: /\.s?(a|c)ss$/,
|
||||
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
|
||||
exclude: /\.module\.s?(c|a)ss$/,
|
||||
},
|
||||
// Fonts
|
||||
{
|
||||
test: /\.(woff|woff2|eot|ttf|otf)$/i,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
// Images
|
||||
{
|
||||
test: /\.(png|svg|jpg|jpeg|gif)$/i,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
parallel: true,
|
||||
}),
|
||||
new CssMinimizerPlugin(),
|
||||
],
|
||||
},
|
||||
|
||||
plugins: [
|
||||
/**
|
||||
* Create global constants which can be configured at compile time.
|
||||
*
|
||||
* Useful for allowing different behaviour between development builds and
|
||||
* release builds
|
||||
*
|
||||
* NODE_ENV should be production so that modules do not perform certain
|
||||
* development checks
|
||||
*/
|
||||
new webpack.EnvironmentPlugin({
|
||||
NODE_ENV: 'production',
|
||||
DEBUG_PROD: false,
|
||||
}),
|
||||
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'remote.css',
|
||||
}),
|
||||
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: path.join(webpackPaths.srcRemotePath, 'index.ejs'),
|
||||
favicon: path.join(webpackPaths.assetsPath, 'icons', 'favicon.ico'),
|
||||
minify: {
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
},
|
||||
isBrowser: true,
|
||||
env: process.env.NODE_ENV,
|
||||
isDevelopment: process.env.NODE_ENV !== 'production',
|
||||
templateParameters: {
|
||||
version,
|
||||
prod: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default merge(baseConfig, configuration);
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Builds the DLL for development electron renderer process
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import webpack from 'webpack';
|
||||
import { merge } from 'webpack-merge';
|
||||
|
||||
import { dependencies } from '../../package.json';
|
||||
import checkNodeEnv from '../scripts/check-node-env';
|
||||
import baseConfig from './webpack.config.base';
|
||||
import webpackPaths from './webpack.paths';
|
||||
|
||||
checkNodeEnv('development');
|
||||
|
||||
const dist = webpackPaths.dllPath;
|
||||
|
||||
const configuration: webpack.Configuration = {
|
||||
context: webpackPaths.rootPath,
|
||||
|
||||
devtool: 'eval',
|
||||
|
||||
mode: 'development',
|
||||
|
||||
target: 'electron-renderer',
|
||||
|
||||
externals: ['fsevents', 'crypto-browserify'],
|
||||
|
||||
/**
|
||||
* Use `module` from `webpack.config.renderer.dev.js`
|
||||
*/
|
||||
module: require('./webpack.config.renderer.dev').default.module,
|
||||
|
||||
entry: {
|
||||
renderer: Object.keys(dependencies || {}),
|
||||
},
|
||||
|
||||
output: {
|
||||
path: dist,
|
||||
filename: '[name].dev.dll.js',
|
||||
library: {
|
||||
name: 'renderer',
|
||||
type: 'var',
|
||||
},
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.DllPlugin({
|
||||
path: path.join(dist, '[name].json'),
|
||||
name: '[name]',
|
||||
}),
|
||||
|
||||
/**
|
||||
* Create global constants which can be configured at compile time.
|
||||
*
|
||||
* Useful for allowing different behaviour between development builds and
|
||||
* release builds
|
||||
*
|
||||
* NODE_ENV should be production so that modules do not perform certain
|
||||
* development checks
|
||||
*/
|
||||
new webpack.EnvironmentPlugin({
|
||||
NODE_ENV: 'development',
|
||||
}),
|
||||
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
debug: true,
|
||||
options: {
|
||||
context: webpackPaths.srcPath,
|
||||
output: {
|
||||
path: webpackPaths.dllPath,
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default merge(baseConfig, configuration);
|
||||
@@ -0,0 +1,198 @@
|
||||
import 'webpack-dev-server';
|
||||
import { execSync, spawn } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
|
||||
import chalk from 'chalk';
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import webpack from 'webpack';
|
||||
import { merge } from 'webpack-merge';
|
||||
|
||||
import checkNodeEnv from '../scripts/check-node-env';
|
||||
import baseConfig from './webpack.config.base';
|
||||
import webpackPaths from './webpack.paths';
|
||||
|
||||
// When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's
|
||||
// at the dev webpack config is not accidentally run in a production environment
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
checkNodeEnv('development');
|
||||
}
|
||||
|
||||
const port = process.env.PORT || 4343;
|
||||
const manifest = path.resolve(webpackPaths.dllPath, 'renderer.json');
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const requiredByDLLConfig = module.parent!.filename.includes('webpack.config.renderer.dev.dll');
|
||||
|
||||
/**
|
||||
* Warn if the DLL is not built
|
||||
*/
|
||||
if (!requiredByDLLConfig && !(fs.existsSync(webpackPaths.dllPath) && fs.existsSync(manifest))) {
|
||||
console.log(
|
||||
chalk.black.bgYellow.bold(
|
||||
'The DLL files are missing. Sit back while we build them for you with "npm run build-dll"',
|
||||
),
|
||||
);
|
||||
execSync('npm run postinstall');
|
||||
}
|
||||
|
||||
const configuration: webpack.Configuration = {
|
||||
devtool: 'inline-source-map',
|
||||
|
||||
mode: 'development',
|
||||
|
||||
target: ['web', 'electron-renderer'],
|
||||
|
||||
entry: [
|
||||
`webpack-dev-server/client?http://localhost:${port}/dist`,
|
||||
'webpack/hot/only-dev-server',
|
||||
path.join(webpackPaths.srcRendererPath, 'index.tsx'),
|
||||
],
|
||||
|
||||
output: {
|
||||
path: webpackPaths.distRendererPath,
|
||||
publicPath: '/',
|
||||
filename: 'renderer.dev.js',
|
||||
library: {
|
||||
type: 'umd',
|
||||
},
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.s?css$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: {
|
||||
localIdentName: '[name]__[local]--[hash:base64:5]',
|
||||
exportLocalsConvention: 'camelCaseOnly',
|
||||
},
|
||||
sourceMap: true,
|
||||
importLoaders: 1,
|
||||
},
|
||||
},
|
||||
'sass-loader',
|
||||
],
|
||||
include: /\.module\.s?(c|a)ss$/,
|
||||
},
|
||||
{
|
||||
test: /\.s?css$/,
|
||||
use: ['style-loader', 'css-loader', 'sass-loader'],
|
||||
exclude: /\.module\.s?(c|a)ss$/,
|
||||
},
|
||||
// Fonts
|
||||
{
|
||||
test: /\.(woff|woff2|eot|ttf|otf)$/i,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
// Images
|
||||
{
|
||||
test: /\.(png|svg|jpg|jpeg|gif)$/i,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
...(requiredByDLLConfig
|
||||
? []
|
||||
: [
|
||||
new webpack.DllReferencePlugin({
|
||||
context: webpackPaths.dllPath,
|
||||
manifest: require(manifest),
|
||||
sourceType: 'var',
|
||||
}),
|
||||
]),
|
||||
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
|
||||
/**
|
||||
* Create global constants which can be configured at compile time.
|
||||
*
|
||||
* Useful for allowing different behaviour between development builds and
|
||||
* release builds
|
||||
*
|
||||
* NODE_ENV should be production so that modules do not perform certain
|
||||
* development checks
|
||||
*
|
||||
* By default, use 'development' as NODE_ENV. This can be overriden with
|
||||
* 'staging', for example, by changing the ENV variables in the npm scripts
|
||||
*/
|
||||
new webpack.EnvironmentPlugin({
|
||||
NODE_ENV: 'development',
|
||||
}),
|
||||
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
debug: true,
|
||||
}),
|
||||
|
||||
new ReactRefreshWebpackPlugin(),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
filename: path.join('index.html'),
|
||||
template: path.join(webpackPaths.srcRendererPath, 'index.ejs'),
|
||||
minify: {
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
},
|
||||
isBrowser: false,
|
||||
env: process.env.NODE_ENV,
|
||||
isDevelopment: process.env.NODE_ENV !== 'production',
|
||||
nodeModules: webpackPaths.appNodeModulesPath,
|
||||
}),
|
||||
],
|
||||
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false,
|
||||
},
|
||||
|
||||
devServer: {
|
||||
port,
|
||||
compress: true,
|
||||
hot: true,
|
||||
headers: { 'Access-Control-Allow-Origin': '*' },
|
||||
static: {
|
||||
publicPath: '/',
|
||||
},
|
||||
historyApiFallback: {
|
||||
verbose: true,
|
||||
},
|
||||
setupMiddlewares(middlewares) {
|
||||
console.log('Starting preload.js builder...');
|
||||
const preloadProcess = spawn('npm', ['run', 'start:preload'], {
|
||||
shell: true,
|
||||
stdio: 'inherit',
|
||||
})
|
||||
.on('close', (code: number) => process.exit(code!))
|
||||
.on('error', (spawnError) => console.error(spawnError));
|
||||
|
||||
console.log('Starting remote.js builder...');
|
||||
const remoteProcess = spawn('npm', ['run', 'start:remote'], {
|
||||
shell: true,
|
||||
stdio: 'inherit',
|
||||
})
|
||||
.on('close', (code: number) => process.exit(code!))
|
||||
.on('error', (spawnError) => console.error(spawnError));
|
||||
|
||||
console.log('Starting Main Process...');
|
||||
spawn('npm', ['run', 'start:main'], {
|
||||
shell: true,
|
||||
stdio: 'inherit',
|
||||
})
|
||||
.on('close', (code: number) => {
|
||||
preloadProcess.kill();
|
||||
remoteProcess.kill();
|
||||
process.exit(code!);
|
||||
})
|
||||
.on('error', (spawnError) => console.error(spawnError));
|
||||
return middlewares;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default merge(baseConfig, configuration);
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Build config for electron renderer process
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
||||
import TerserPlugin from 'terser-webpack-plugin';
|
||||
import webpack from 'webpack';
|
||||
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
||||
import { merge } from 'webpack-merge';
|
||||
|
||||
import checkNodeEnv from '../scripts/check-node-env';
|
||||
import deleteSourceMaps from '../scripts/delete-source-maps';
|
||||
import baseConfig from './webpack.config.base';
|
||||
import webpackPaths from './webpack.paths';
|
||||
|
||||
checkNodeEnv('production');
|
||||
deleteSourceMaps();
|
||||
|
||||
const devtoolsConfig =
|
||||
process.env.DEBUG_PROD === 'true'
|
||||
? {
|
||||
devtool: 'source-map',
|
||||
}
|
||||
: {};
|
||||
|
||||
const configuration: webpack.Configuration = {
|
||||
...devtoolsConfig,
|
||||
|
||||
mode: 'production',
|
||||
|
||||
target: ['web', 'electron-renderer'],
|
||||
|
||||
entry: [path.join(webpackPaths.srcRendererPath, 'index.tsx')],
|
||||
|
||||
output: {
|
||||
path: webpackPaths.distRendererPath,
|
||||
publicPath: './',
|
||||
filename: 'renderer.js',
|
||||
library: {
|
||||
type: 'umd',
|
||||
},
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.s?(a|c)ss$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: {
|
||||
localIdentName: '[name]__[local]--[hash:base64:5]',
|
||||
exportLocalsConvention: 'camelCaseOnly',
|
||||
},
|
||||
sourceMap: true,
|
||||
importLoaders: 1,
|
||||
},
|
||||
},
|
||||
'sass-loader',
|
||||
],
|
||||
include: /\.module\.s?(c|a)ss$/,
|
||||
},
|
||||
{
|
||||
test: /\.s?(a|c)ss$/,
|
||||
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
|
||||
exclude: /\.module\.s?(c|a)ss$/,
|
||||
},
|
||||
// Fonts
|
||||
{
|
||||
test: /\.(woff|woff2|eot|ttf|otf)$/i,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
// Images
|
||||
{
|
||||
test: /\.(png|svg|jpg|jpeg|gif)$/i,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
parallel: true,
|
||||
}),
|
||||
new CssMinimizerPlugin(),
|
||||
],
|
||||
},
|
||||
|
||||
plugins: [
|
||||
/**
|
||||
* Create global constants which can be configured at compile time.
|
||||
*
|
||||
* Useful for allowing different behaviour between development builds and
|
||||
* release builds
|
||||
*
|
||||
* NODE_ENV should be production so that modules do not perform certain
|
||||
* development checks
|
||||
*/
|
||||
new webpack.EnvironmentPlugin({
|
||||
NODE_ENV: 'production',
|
||||
DEBUG_PROD: false,
|
||||
}),
|
||||
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'style.css',
|
||||
}),
|
||||
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: path.join(webpackPaths.srcRendererPath, 'index.ejs'),
|
||||
minify: {
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
},
|
||||
isBrowser: false,
|
||||
isDevelopment: process.env.NODE_ENV !== 'production',
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default merge(baseConfig, configuration);
|
||||
@@ -0,0 +1,144 @@
|
||||
import 'webpack-dev-server';
|
||||
import path from 'path';
|
||||
|
||||
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import webpack from 'webpack';
|
||||
import { merge } from 'webpack-merge';
|
||||
|
||||
import checkNodeEnv from '../scripts/check-node-env';
|
||||
import baseConfig from './webpack.config.base';
|
||||
import webpackPaths from './webpack.paths';
|
||||
|
||||
// When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's
|
||||
// at the dev webpack config is not accidentally run in a production environment
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
checkNodeEnv('development');
|
||||
}
|
||||
|
||||
const port = process.env.PORT || 4343;
|
||||
|
||||
const configuration: webpack.Configuration = {
|
||||
devtool: 'inline-source-map',
|
||||
|
||||
mode: 'development',
|
||||
|
||||
target: ['web', 'electron-renderer'],
|
||||
|
||||
entry: [
|
||||
`webpack-dev-server/client?http://localhost:${port}/dist`,
|
||||
'webpack/hot/only-dev-server',
|
||||
path.join(webpackPaths.srcRendererPath, 'index.tsx'),
|
||||
],
|
||||
|
||||
output: {
|
||||
path: webpackPaths.distRendererPath,
|
||||
publicPath: '/',
|
||||
filename: 'renderer.dev.js',
|
||||
library: {
|
||||
type: 'umd',
|
||||
},
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.s?css$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: {
|
||||
localIdentName: '[name]__[local]--[hash:base64:5]',
|
||||
exportLocalsConvention: 'camelCaseOnly',
|
||||
},
|
||||
sourceMap: true,
|
||||
importLoaders: 1,
|
||||
},
|
||||
},
|
||||
'sass-loader',
|
||||
],
|
||||
include: /\.module\.s?(c|a)ss$/,
|
||||
},
|
||||
{
|
||||
test: /\.s?css$/,
|
||||
use: ['style-loader', 'css-loader', 'sass-loader'],
|
||||
exclude: /\.module\.s?(c|a)ss$/,
|
||||
},
|
||||
// Fonts
|
||||
{
|
||||
test: /\.(woff|woff2|eot|ttf|otf)$/i,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
// Images
|
||||
{
|
||||
test: /\.(png|svg|jpg|jpeg|gif)$/i,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
|
||||
/**
|
||||
* Create global constants which can be configured at compile time.
|
||||
*
|
||||
* Useful for allowing different behaviour between development builds and
|
||||
* release builds
|
||||
*
|
||||
* NODE_ENV should be production so that modules do not perform certain
|
||||
* development checks
|
||||
*
|
||||
* By default, use 'development' as NODE_ENV. This can be overriden with
|
||||
* 'staging', for example, by changing the ENV variables in the npm scripts
|
||||
*/
|
||||
new webpack.EnvironmentPlugin({
|
||||
NODE_ENV: 'development',
|
||||
}),
|
||||
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
debug: true,
|
||||
}),
|
||||
|
||||
new ReactRefreshWebpackPlugin(),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
filename: path.join('index.html'),
|
||||
template: path.join(webpackPaths.srcRendererPath, 'index.ejs'),
|
||||
favicon: path.join(webpackPaths.assetsPath, 'icons', 'favicon.ico'),
|
||||
minify: {
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
},
|
||||
isBrowser: false,
|
||||
env: process.env.NODE_ENV,
|
||||
isDevelopment: process.env.NODE_ENV !== 'production',
|
||||
nodeModules: webpackPaths.appNodeModulesPath,
|
||||
}),
|
||||
],
|
||||
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false,
|
||||
},
|
||||
|
||||
devServer: {
|
||||
port,
|
||||
compress: true,
|
||||
hot: true,
|
||||
headers: { 'Access-Control-Allow-Origin': '*' },
|
||||
static: {
|
||||
publicPath: '/',
|
||||
},
|
||||
historyApiFallback: {
|
||||
verbose: true,
|
||||
},
|
||||
setupMiddlewares(middlewares) {
|
||||
return middlewares;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default merge(baseConfig, configuration);
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Build config for electron renderer process
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
||||
import TerserPlugin from 'terser-webpack-plugin';
|
||||
import webpack from 'webpack';
|
||||
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
|
||||
import { merge } from 'webpack-merge';
|
||||
|
||||
import checkNodeEnv from '../scripts/check-node-env';
|
||||
import deleteSourceMaps from '../scripts/delete-source-maps';
|
||||
import baseConfig from './webpack.config.base';
|
||||
import webpackPaths from './webpack.paths';
|
||||
|
||||
checkNodeEnv('production');
|
||||
deleteSourceMaps();
|
||||
|
||||
const devtoolsConfig =
|
||||
process.env.DEBUG_PROD === 'true'
|
||||
? {
|
||||
devtool: 'source-map',
|
||||
}
|
||||
: {};
|
||||
|
||||
const configuration: webpack.Configuration = {
|
||||
...devtoolsConfig,
|
||||
|
||||
mode: 'production',
|
||||
|
||||
target: ['web'],
|
||||
|
||||
entry: [path.join(webpackPaths.srcRendererPath, 'index.tsx')],
|
||||
|
||||
output: {
|
||||
path: webpackPaths.distWebPath,
|
||||
publicPath: 'auto',
|
||||
filename: 'renderer.js',
|
||||
library: {
|
||||
type: 'umd',
|
||||
},
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.s?(a|c)ss$/,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: {
|
||||
localIdentName: '[name]__[local]--[hash:base64:5]',
|
||||
exportLocalsConvention: 'camelCaseOnly',
|
||||
},
|
||||
sourceMap: true,
|
||||
importLoaders: 1,
|
||||
},
|
||||
},
|
||||
'sass-loader',
|
||||
],
|
||||
include: /\.module\.s?(c|a)ss$/,
|
||||
},
|
||||
{
|
||||
test: /\.s?(a|c)ss$/,
|
||||
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
|
||||
exclude: /\.module\.s?(c|a)ss$/,
|
||||
},
|
||||
// Fonts
|
||||
{
|
||||
test: /\.(woff|woff2|eot|ttf|otf)$/i,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
// Images
|
||||
{
|
||||
test: /\.(png|svg|jpg|jpeg|gif)$/i,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
parallel: true,
|
||||
}),
|
||||
new CssMinimizerPlugin(),
|
||||
],
|
||||
},
|
||||
|
||||
plugins: [
|
||||
/**
|
||||
* Create global constants which can be configured at compile time.
|
||||
*
|
||||
* Useful for allowing different behaviour between development builds and
|
||||
* release builds
|
||||
*
|
||||
* NODE_ENV should be production so that modules do not perform certain
|
||||
* development checks
|
||||
*/
|
||||
new webpack.EnvironmentPlugin({
|
||||
NODE_ENV: 'production',
|
||||
DEBUG_PROD: false,
|
||||
}),
|
||||
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'style.css',
|
||||
}),
|
||||
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
|
||||
}),
|
||||
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: path.join(webpackPaths.srcRendererPath, 'index.ejs'),
|
||||
favicon: path.join(webpackPaths.assetsPath, 'icons', 'favicon.ico'),
|
||||
minify: {
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
},
|
||||
isBrowser: false,
|
||||
isDevelopment: process.env.NODE_ENV !== 'production',
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default merge(baseConfig, configuration);
|
||||
@@ -0,0 +1,46 @@
|
||||
const path = require('path');
|
||||
|
||||
const rootPath = path.join(__dirname, '../..');
|
||||
|
||||
const dllPath = path.join(__dirname, '../dll');
|
||||
|
||||
const srcPath = path.join(rootPath, 'src');
|
||||
const assetsPath = path.join(rootPath, 'assets');
|
||||
const srcMainPath = path.join(srcPath, 'main');
|
||||
const srcRemotePath = path.join(srcPath, 'remote');
|
||||
const srcRendererPath = path.join(srcPath, 'renderer');
|
||||
|
||||
const releasePath = path.join(rootPath, 'release');
|
||||
const appPath = path.join(releasePath, 'app');
|
||||
const appPackagePath = path.join(appPath, 'package.json');
|
||||
const appNodeModulesPath = path.join(appPath, 'node_modules');
|
||||
const srcNodeModulesPath = path.join(srcPath, 'node_modules');
|
||||
|
||||
const distPath = path.join(appPath, 'dist');
|
||||
const distMainPath = path.join(distPath, 'main');
|
||||
const distRemotePath = path.join(distPath, 'remote');
|
||||
const distRendererPath = path.join(distPath, 'renderer');
|
||||
const distWebPath = path.join(distPath, 'web');
|
||||
|
||||
const buildPath = path.join(releasePath, 'build');
|
||||
|
||||
export default {
|
||||
assetsPath,
|
||||
rootPath,
|
||||
dllPath,
|
||||
srcPath,
|
||||
srcMainPath,
|
||||
srcRemotePath,
|
||||
srcRendererPath,
|
||||
releasePath,
|
||||
appPath,
|
||||
appPackagePath,
|
||||
appNodeModulesPath,
|
||||
srcNodeModulesPath,
|
||||
distPath,
|
||||
distMainPath,
|
||||
distRemotePath,
|
||||
distRendererPath,
|
||||
distWebPath,
|
||||
buildPath,
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export default 'test-file-stub';
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-console": "off",
|
||||
"global-require": "off",
|
||||
"import/no-dynamic-require": "off",
|
||||
"import/no-extraneous-dependencies": "off"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Check if the renderer and main bundles are built
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
import fs from 'fs';
|
||||
import webpackPaths from '../configs/webpack.paths';
|
||||
|
||||
const mainPath = path.join(webpackPaths.distMainPath, 'main.js');
|
||||
const remotePath = path.join(webpackPaths.distMainPath, 'remote.js');
|
||||
const rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js');
|
||||
|
||||
if (!fs.existsSync(mainPath)) {
|
||||
throw new Error(
|
||||
chalk.whiteBright.bgRed.bold(
|
||||
'The main process is not built yet. Build it by running "npm run build:main"',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(remotePath)) {
|
||||
throw new Error(
|
||||
chalk.whiteBright.bgRed.bold(
|
||||
'The remote process is not built yet. Build it by running "npm run build:remote"',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(rendererPath)) {
|
||||
throw new Error(
|
||||
chalk.whiteBright.bgRed.bold(
|
||||
'The renderer process is not built yet. Build it by running "npm run build:renderer"',
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import fs from 'fs';
|
||||
import chalk from 'chalk';
|
||||
import { execSync } from 'child_process';
|
||||
import { dependencies } from '../../package.json';
|
||||
|
||||
if (dependencies) {
|
||||
const dependenciesKeys = Object.keys(dependencies);
|
||||
const nativeDeps = fs
|
||||
.readdirSync('node_modules')
|
||||
.filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`));
|
||||
if (nativeDeps.length === 0) {
|
||||
process.exit(0);
|
||||
}
|
||||
try {
|
||||
// Find the reason for why the dependency is installed. If it is installed
|
||||
// because of a devDependency then that is okay. Warn when it is installed
|
||||
// because of a dependency
|
||||
const { dependencies: dependenciesObject } = JSON.parse(
|
||||
execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString()
|
||||
);
|
||||
const rootDependencies = Object.keys(dependenciesObject);
|
||||
const filteredRootDependencies = rootDependencies.filter((rootDependency) =>
|
||||
dependenciesKeys.includes(rootDependency)
|
||||
);
|
||||
if (filteredRootDependencies.length > 0) {
|
||||
const plural = filteredRootDependencies.length > 1;
|
||||
console.log(`
|
||||
${chalk.whiteBright.bgYellow.bold(
|
||||
'Webpack does not work with native dependencies.'
|
||||
)}
|
||||
${chalk.bold(filteredRootDependencies.join(', '))} ${
|
||||
plural ? 'are native dependencies' : 'is a native dependency'
|
||||
} and should be installed inside of the "./release/app" folder.
|
||||
First, uninstall the packages from "./package.json":
|
||||
${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')}
|
||||
${chalk.bold(
|
||||
'Then, instead of installing the package to the root "./package.json":'
|
||||
)}
|
||||
${chalk.whiteBright.bgRed.bold('npm install your-package')}
|
||||
${chalk.bold('Install the package to "./release/app/package.json"')}
|
||||
${chalk.whiteBright.bgGreen.bold(
|
||||
'cd ./release/app && npm install your-package'
|
||||
)}
|
||||
Read more about native dependencies at:
|
||||
${chalk.bold(
|
||||
'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure'
|
||||
)}
|
||||
`);
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Native dependencies could not be checked');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import chalk from 'chalk';
|
||||
|
||||
export default function checkNodeEnv(expectedEnv) {
|
||||
if (!expectedEnv) {
|
||||
throw new Error('"expectedEnv" not set');
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== expectedEnv) {
|
||||
console.log(
|
||||
chalk.whiteBright.bgRed.bold(
|
||||
`"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config`
|
||||
)
|
||||
);
|
||||
process.exit(2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import chalk from 'chalk';
|
||||
import detectPort from 'detect-port';
|
||||
|
||||
const port = process.env.PORT || '4343';
|
||||
|
||||
detectPort(port, (err, availablePort) => {
|
||||
if (port !== String(availablePort)) {
|
||||
throw new Error(
|
||||
chalk.whiteBright.bgRed.bold(
|
||||
`Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start`
|
||||
)
|
||||
);
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import rimraf from 'rimraf';
|
||||
import process from 'process';
|
||||
import webpackPaths from '../configs/webpack.paths';
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const commandMap = {
|
||||
dist: webpackPaths.distPath,
|
||||
release: webpackPaths.releasePath,
|
||||
dll: webpackPaths.dllPath,
|
||||
};
|
||||
|
||||
args.forEach((x) => {
|
||||
const pathToRemove = commandMap[x];
|
||||
if (pathToRemove !== undefined) {
|
||||
rimraf.sync(pathToRemove);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import path from 'path';
|
||||
import rimraf from 'rimraf';
|
||||
import webpackPaths from '../configs/webpack.paths';
|
||||
|
||||
export default function deleteSourceMaps() {
|
||||
rimraf.sync(path.join(webpackPaths.distMainPath, '*.js.map'));
|
||||
rimraf.sync(path.join(webpackPaths.distRemotePath, '*.js.map'));
|
||||
rimraf.sync(path.join(webpackPaths.distRendererPath, '*.js.map'));
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { execSync } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import { dependencies } from '../../release/app/package.json';
|
||||
import webpackPaths from '../configs/webpack.paths';
|
||||
|
||||
if (
|
||||
Object.keys(dependencies || {}).length > 0 &&
|
||||
fs.existsSync(webpackPaths.appNodeModulesPath)
|
||||
) {
|
||||
const electronRebuildCmd =
|
||||
'../../node_modules/.bin/electron-rebuild --force --types prod,dev,optional --module-dir .';
|
||||
const cmd =
|
||||
process.platform === 'win32'
|
||||
? electronRebuildCmd.replace(/\//g, '\\')
|
||||
: electronRebuildCmd;
|
||||
execSync(cmd, {
|
||||
cwd: webpackPaths.appPath,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import fs from 'fs';
|
||||
import webpackPaths from '../configs/webpack.paths';
|
||||
|
||||
const { srcNodeModulesPath } = webpackPaths;
|
||||
const { appNodeModulesPath } = webpackPaths;
|
||||
|
||||
if (!fs.existsSync(srcNodeModulesPath) && fs.existsSync(appNodeModulesPath)) {
|
||||
fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction');
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
const { notarize } = require('electron-notarize');
|
||||
const { build } = require('../../package.json');
|
||||
|
||||
exports.default = async function notarizeMacos(context) {
|
||||
const { electronPlatformName, appOutDir } = context;
|
||||
if (electronPlatformName !== 'darwin') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.env.CI !== 'true') {
|
||||
console.warn('Skipping notarizing step. Packaging is not running in CI');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!('APPLE_ID' in process.env && 'APPLE_ID_PASS' in process.env)) {
|
||||
console.warn(
|
||||
'Skipping notarizing step. APPLE_ID and APPLE_ID_PASS env variables must be set'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const appName = context.packager.appInfo.productFilename;
|
||||
|
||||
await notarize({
|
||||
appBundleId: build.appId,
|
||||
appPath: `${appOutDir}/${appName}.app`,
|
||||
appleId: process.env.APPLE_ID,
|
||||
appleIdPassword: process.env.APPLE_ID_PASS,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
.eslintcache
|
||||
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
node_modules
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
src/i18n
|
||||
release/app/dist
|
||||
release/build
|
||||
.erb/dll
|
||||
|
||||
.idea
|
||||
npm-debug.log.*
|
||||
*.css.d.ts
|
||||
*.sass.d.ts
|
||||
*.scss.d.ts
|
||||
|
||||
# eslint ignores hidden directories by default:
|
||||
# https://github.com/eslint/eslint/issues/8429
|
||||
!.erb
|
||||
@@ -0,0 +1,97 @@
|
||||
module.exports = {
|
||||
extends: ['erb', 'plugin:typescript-sort-keys/recommended'],
|
||||
ignorePatterns: ['.erb/*', 'server'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
createDefaultProgram: true,
|
||||
ecmaVersion: 12,
|
||||
parser: '@typescript-eslint/parser',
|
||||
project: './tsconfig.json',
|
||||
sourceType: 'module',
|
||||
tsconfigRootDir: './',
|
||||
},
|
||||
plugins: ['@typescript-eslint', 'import', 'sort-keys-fix'],
|
||||
rules: {
|
||||
'@typescript-eslint/naming-convention': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-shadow': ['off'],
|
||||
'@typescript-eslint/no-unused-vars': ['error'],
|
||||
'@typescript-eslint/no-use-before-define': ['error'],
|
||||
'default-case': 'off',
|
||||
'import/extensions': 'off',
|
||||
'import/no-absolute-path': 'off',
|
||||
// A temporary hack related to IDE not resolving correct package.json
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
'import/no-unresolved': 'error',
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
alphabetize: {
|
||||
caseInsensitive: true,
|
||||
order: 'asc',
|
||||
},
|
||||
groups: ['builtin', 'external', 'internal', ['parent', 'sibling']],
|
||||
'newlines-between': 'never',
|
||||
pathGroups: [
|
||||
{
|
||||
group: 'external',
|
||||
pattern: 'react',
|
||||
position: 'before',
|
||||
},
|
||||
],
|
||||
pathGroupsExcludedImportTypes: ['react'],
|
||||
},
|
||||
],
|
||||
'import/prefer-default-export': 'off',
|
||||
'jsx-a11y/click-events-have-key-events': 'off',
|
||||
'jsx-a11y/interactive-supports-focus': 'off',
|
||||
'jsx-a11y/media-has-caption': 'off',
|
||||
'no-await-in-loop': 'off',
|
||||
'no-console': 'off',
|
||||
'no-nested-ternary': 'off',
|
||||
'no-restricted-syntax': 'off',
|
||||
'no-shadow': 'off',
|
||||
'no-underscore-dangle': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
'prefer-destructuring': 'off',
|
||||
'react/function-component-definition': 'off',
|
||||
'react/jsx-filename-extension': [2, { extensions: ['.js', '.jsx', '.ts', '.tsx'] }],
|
||||
'react/jsx-no-useless-fragment': 'off',
|
||||
'react/jsx-props-no-spreading': 'off',
|
||||
'react/jsx-sort-props': [
|
||||
'error',
|
||||
{
|
||||
callbacksLast: true,
|
||||
ignoreCase: false,
|
||||
noSortAlphabetically: false,
|
||||
reservedFirst: true,
|
||||
shorthandFirst: true,
|
||||
shorthandLast: false,
|
||||
},
|
||||
],
|
||||
'react/no-array-index-key': 'off',
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'react/require-default-props': 'off',
|
||||
'sort-keys-fix/sort-keys-fix': 'warn',
|
||||
},
|
||||
settings: {
|
||||
'import/parsers': {
|
||||
'@typescript-eslint/parser': ['.ts', '.tsx'],
|
||||
},
|
||||
'import/resolver': {
|
||||
// See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below
|
||||
node: {
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
typescript: {
|
||||
alwaysTryTypes: true,
|
||||
project: './tsconfig.json',
|
||||
},
|
||||
webpack: {
|
||||
config: require.resolve('./.erb/configs/webpack.config.eslint.ts'),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
name: Feature request
|
||||
description: Request a feature to be added to Feishin
|
||||
title: '[Feature]: '
|
||||
labels: ['enhancement']
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: check-duplicate
|
||||
attributes:
|
||||
label: I have already checked through the existing feature requests and found no duplicates
|
||||
options:
|
||||
- label: 'Yes'
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: server-specific
|
||||
attributes:
|
||||
label: Is this a server-specific feature?
|
||||
options:
|
||||
- Not server-specific
|
||||
- OpenSubsonic
|
||||
- Jellyfin
|
||||
- Navidrome
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: What do you want to be added?
|
||||
placeholder: I would like to see [...]
|
||||
validations:
|
||||
required: true
|
||||
@@ -1,74 +0,0 @@
|
||||
name: Bug report
|
||||
description: You're having technical issues.
|
||||
title: '[Bug]: '
|
||||
labels: ['bug']
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: check-duplicate
|
||||
attributes:
|
||||
label: I have already checked through the existing bug reports and found no duplicates
|
||||
options:
|
||||
- label: 'Yes'
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: App Version
|
||||
description: What version of the app are you running?
|
||||
placeholder: ex. 1.0.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: server-version
|
||||
attributes:
|
||||
label: Music Server and Version
|
||||
description: What music server are you using?
|
||||
placeholder: ex. Navidrome v0.55.0, LMS v3.67.0, Jellyfin v10.10.7, etc.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: environments
|
||||
attributes:
|
||||
label: What local environments are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- Desktop Windows
|
||||
- Desktop macOS
|
||||
- Desktop Linux
|
||||
- Web Firefox
|
||||
- Web Chrome
|
||||
- Web Safari
|
||||
- Web Microsoft Edge
|
||||
- Other (please specify in the next field)
|
||||
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: Also tell us, what did you expect to happen?
|
||||
placeholder: Include screenshots and error logs if possible. The browser devtools can be opened using CTRL + SHIFT + I (Windows/Linux) or CMD + SHIFT + I (macOS).
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: How can we reproduce this issue? Are there any specific settings that are enabled that could be the cause?
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code.
|
||||
render: shell
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: You're having technical issues. 🐞
|
||||
labels: 'bug'
|
||||
---
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
<!--- What should have happened? -->
|
||||
|
||||
## Current Behavior
|
||||
|
||||
<!--- What went wrong? -->
|
||||
<!-- Add screenshots to help explain your problem -->
|
||||
<!-- (Open the browser dev tools in the menu or using CTRL + SHIFT + I) -->
|
||||
|
||||
## Steps to Reproduce
|
||||
|
||||
<!-- Add relevant code and/or a live example -->
|
||||
<!-- Add stack traces -->
|
||||
|
||||
1.
|
||||
|
||||
2.
|
||||
|
||||
3.
|
||||
|
||||
4.
|
||||
|
||||
## Possible Solution (Not obligatory)
|
||||
|
||||
<!--- Suggest a reason for the bug or how to fix it. -->
|
||||
|
||||
## Context
|
||||
|
||||
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
||||
|
||||
## Your Environment
|
||||
|
||||
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
||||
|
||||
- Application version (e.g. v0.1.0) :
|
||||
- Operating System and version (e.g. Windows 10) :
|
||||
- Server and version (e.g. Navidrome v0.48.0) :
|
||||
- Node version (if developing locally) :
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question.❓
|
||||
labels: 'question'
|
||||
---
|
||||
|
||||
<!-- Question issues will be closed. -->
|
||||
<!-- Ask questions in the discussions tab: Please use discussions https://github.com/jeffvli/feishin/discussions -->
|
||||
<!-- Or join the Discord/Matrix servers: https://discord.gg/FVKpcMDy5f https://matrix.to/#/#sonixd:matrix.org -->
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Request a feature to be added to Feishin 🎉
|
||||
labels: 'enhancement'
|
||||
---
|
||||
|
||||
## What do you want to be added?
|
||||
|
||||
## Additional context
|
||||
|
||||
<!-- Is this a server-specific feature? (e.g. Jellyfin only). -->
|
||||
@@ -1,11 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Questions or help
|
||||
url: https://github.com/jeffvli/feishin/discussions
|
||||
about: Ask questions or get help in the discussions section
|
||||
- name: Discord Community
|
||||
url: https://discord.gg/FVKpcMDy5f
|
||||
about: The discord/matrix servers are bridged so you can join whichever you prefer
|
||||
- name: Matrix Community
|
||||
url: https://matrix.to/#/#sonixd:matrix.org
|
||||
about: The discord/matrix servers are bridged so you can join whichever you prefer
|
||||
@@ -1,381 +0,0 @@
|
||||
name: Publish Beta (Manual)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Semantic version number (e.g., 1.0.0) - beta suffix will be added automatically'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Node and PNPM
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Validate and set version with incrementing beta suffix
|
||||
id: version
|
||||
shell: pwsh
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
$inputVersion = "${{ github.event.inputs.version }}"
|
||||
Write-Host "Input version: $inputVersion"
|
||||
|
||||
if ($inputVersion -eq "" -or $inputVersion -eq "null") {
|
||||
# No input version provided, auto-increment patch version
|
||||
Write-Host "No version provided, auto-incrementing patch version..."
|
||||
|
||||
# Get current version from package.json
|
||||
$currentVersion = (Get-Content package.json | ConvertFrom-Json).version
|
||||
Write-Host "Current version: $currentVersion"
|
||||
|
||||
# Remove any existing suffix (like -beta) to get clean semantic version
|
||||
$cleanVersion = $currentVersion -replace '-.*$', ''
|
||||
|
||||
# Extract major, minor, patch components
|
||||
$versionParts = $cleanVersion.Split('.')
|
||||
if ($versionParts.Length -ne 3) {
|
||||
Write-Error "Current version format is invalid: $cleanVersion"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$major = [int]$versionParts[0]
|
||||
$minor = [int]$versionParts[1]
|
||||
$patch = [int]$versionParts[2]
|
||||
|
||||
# Increment patch version
|
||||
$newPatch = $patch + 1
|
||||
$inputVersion = "$major.$minor.$newPatch"
|
||||
Write-Host "Auto-generated version: $inputVersion"
|
||||
} else {
|
||||
# Validate semantic version format (major.minor.patch)
|
||||
$versionPattern = '^\d+\.\d+\.\d+$'
|
||||
if ($inputVersion -notmatch $versionPattern) {
|
||||
Write-Error "Invalid version format. Expected semantic version (e.g., 1.0.0), got: $inputVersion"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Check for existing beta releases with the same base version
|
||||
Write-Host "Checking for existing beta releases with base version: $inputVersion"
|
||||
$existingReleases = gh release list --limit 100 --json tagName,isPrerelease | ConvertFrom-Json | Where-Object { $_.isPrerelease -eq $true }
|
||||
|
||||
$maxBetaNumber = 0
|
||||
|
||||
foreach ($release in $existingReleases) {
|
||||
$tagName = $release.tagName
|
||||
Write-Host "Checking tag: $tagName"
|
||||
|
||||
# Extract beta number from tag name (format: v1.0.0-beta.1)
|
||||
if ($tagName -match "v$([regex]::Escape($inputVersion))-beta\.(\d+)$") {
|
||||
$betaNumber = [int]$matches[1]
|
||||
Write-Host "Found beta release with number: $betaNumber"
|
||||
|
||||
if ($betaNumber -gt $maxBetaNumber) {
|
||||
$maxBetaNumber = $betaNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Calculate next beta number
|
||||
$nextBetaNumber = $maxBetaNumber + 1
|
||||
Write-Host "Next beta number: $nextBetaNumber"
|
||||
|
||||
# Create beta suffix with incrementing number
|
||||
$betaSuffix = "beta.$nextBetaNumber"
|
||||
$versionWithBeta = "$inputVersion-$betaSuffix"
|
||||
Write-Host "Setting version to: $versionWithBeta"
|
||||
|
||||
# Update package.json
|
||||
$packageJson = Get-Content package.json | ConvertFrom-Json
|
||||
$packageJson.version = $versionWithBeta
|
||||
$packageJson | ConvertTo-Json -Depth 10 | Set-Content package.json
|
||||
|
||||
Write-Host "Updated package.json version to: $versionWithBeta"
|
||||
|
||||
# Set output for other jobs
|
||||
echo "version=$versionWithBeta" >> $env:GITHUB_OUTPUT
|
||||
|
||||
publish:
|
||||
needs: prepare
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, macos-latest, ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Node and PNPM
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Set version from prepare job
|
||||
shell: pwsh
|
||||
run: |
|
||||
$versionWithBeta = "${{ needs.prepare.outputs.version }}"
|
||||
Write-Host "Setting version from prepare job: $versionWithBeta"
|
||||
|
||||
# Update package.json with the version from prepare job
|
||||
$packageJson = Get-Content package.json | ConvertFrom-Json
|
||||
$packageJson.version = $versionWithBeta
|
||||
$packageJson | ConvertTo-Json -Depth 10 | Set-Content package.json
|
||||
|
||||
Write-Host "Updated package.json version to: $versionWithBeta"
|
||||
|
||||
- name: Build and Publish releases (Windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: nick-invision/retry@v2.8.2
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
retry_on: error
|
||||
command: |
|
||||
pnpm run publish:win:beta
|
||||
on_retry_command: pnpm cache delete
|
||||
|
||||
- name: Build and Publish releases (macOS)
|
||||
if: matrix.os == 'macos-latest'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: nick-invision/retry@v2.8.2
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
retry_on: error
|
||||
command: |
|
||||
pnpm run publish:mac:beta
|
||||
on_retry_command: pnpm cache delete
|
||||
|
||||
- name: Build and Publish releases (Linux)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: nick-invision/retry@v2.8.2
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
retry_on: error
|
||||
command: |
|
||||
pnpm run publish:linux:beta
|
||||
on_retry_command: pnpm cache delete
|
||||
|
||||
- name: Build and Publish releases (Linux ARM64)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: nick-invision/retry@v2.8.2
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
retry_on: error
|
||||
command: |
|
||||
pnpm run publish:linux-arm64:beta
|
||||
on_retry_command: pnpm cache delete
|
||||
|
||||
edit-release:
|
||||
needs: [prepare, publish]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Edit release with commits and title
|
||||
shell: pwsh
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
# Get the version from the prepare job
|
||||
$versionWithBeta = "${{ needs.prepare.outputs.version }}"
|
||||
$tagVersion = "v" + $versionWithBeta
|
||||
Write-Host "Editing release for tag: $tagVersion"
|
||||
|
||||
# Check if release exists
|
||||
$releaseExists = gh release view $tagVersion 2>$null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Host "Found release with tag $tagVersion"
|
||||
|
||||
# Get current release notes
|
||||
|
||||
# Find the latest non-prerelease tag
|
||||
Write-Host "Finding latest non-prerelease tag..."
|
||||
$latestNonPrerelease = gh release list --limit 100 --json tagName,isPrerelease | ConvertFrom-Json | Where-Object { $_.isPrerelease -eq $false -and $_.tagName -ne $tagVersion } | Select-Object -First 1
|
||||
|
||||
if ($latestNonPrerelease) {
|
||||
$latestTag = $latestNonPrerelease.tagName
|
||||
Write-Host "Latest non-prerelease tag: $latestTag"
|
||||
|
||||
# Get commits between latest non-prerelease and current HEAD
|
||||
Write-Host "Getting commits between $latestTag and HEAD..."
|
||||
|
||||
# Use proper git range syntax and handle PowerShell string interpolation
|
||||
$gitRange = "$latestTag..HEAD"
|
||||
Write-Host "Git range: $gitRange"
|
||||
|
||||
# Get commits using proper git command with datetime
|
||||
$commits = git log --oneline --pretty=format:"%ad|%s|%h" --date=short $gitRange
|
||||
|
||||
# Check if commits exist
|
||||
if ($commits -and $commits.Trim() -ne "") {
|
||||
Write-Host "Found commits:"
|
||||
Write-Host $commits
|
||||
|
||||
# Group commits by date
|
||||
$groupedCommits = @{}
|
||||
foreach ($line in $commits) {
|
||||
if ($line.Trim() -ne "") {
|
||||
$parts = $line.Split('|')
|
||||
$date = $parts[0]
|
||||
$message = $parts[1]
|
||||
$hash = $parts[2]
|
||||
|
||||
if (-not $groupedCommits.ContainsKey($date)) {
|
||||
$groupedCommits[$date] = @()
|
||||
}
|
||||
$groupedCommits[$date] += "- $message ($hash)"
|
||||
}
|
||||
}
|
||||
|
||||
# Build formatted release notes grouped by date
|
||||
$commitNotes = "## Changes since $latestTag`n`n"
|
||||
$sortedDates = $groupedCommits.Keys | Sort-Object -Descending
|
||||
foreach ($date in $sortedDates) {
|
||||
$commitNotes += "### $date`n"
|
||||
foreach ($commit in $groupedCommits[$date]) {
|
||||
$commitNotes += "$commit`n"
|
||||
}
|
||||
$commitNotes += "`n"
|
||||
}
|
||||
|
||||
$releaseNotes = $commitNotes
|
||||
} else {
|
||||
Write-Host "No commits found between $latestTag and HEAD"
|
||||
Write-Host "Trying alternative approach..."
|
||||
|
||||
# Alternative: get commits since the tag (not range) with datetime
|
||||
$commits = git log --oneline --pretty=format:"%ad|%s|%h" --date=short $latestTag.. --not $latestTag
|
||||
|
||||
if ($commits -and $commits.Trim() -ne "") {
|
||||
Write-Host "Found commits with alternative method:"
|
||||
Write-Host $commits
|
||||
|
||||
# Group commits by date
|
||||
$groupedCommits = @{}
|
||||
foreach ($line in $commits) {
|
||||
if ($line.Trim() -ne "") {
|
||||
$parts = $line.Split('|')
|
||||
$date = $parts[0]
|
||||
$message = $parts[1]
|
||||
$hash = $parts[2]
|
||||
|
||||
if (-not $groupedCommits.ContainsKey($date)) {
|
||||
$groupedCommits[$date] = @()
|
||||
}
|
||||
$groupedCommits[$date] += "- $message ($hash)"
|
||||
}
|
||||
}
|
||||
|
||||
# Build formatted release notes grouped by date
|
||||
$commitNotes = "## Changes since $latestTag`n`n"
|
||||
$sortedDates = $groupedCommits.Keys | Sort-Object -Descending
|
||||
foreach ($date in $sortedDates) {
|
||||
$commitNotes += "### $date`n"
|
||||
foreach ($commit in $groupedCommits[$date]) {
|
||||
$commitNotes += "$commit`n"
|
||||
}
|
||||
$commitNotes += "`n"
|
||||
}
|
||||
|
||||
$releaseNotes = $commitNotes
|
||||
} else {
|
||||
Write-Host "Still no commits found, using basic release notes"
|
||||
$releaseNotes = "## Beta Release`n`nThis is a beta release."
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Host "No non-prerelease tags found, using basic release notes"
|
||||
$releaseNotes = "## Beta Release`n`nThis is a beta release."
|
||||
}
|
||||
|
||||
# Prepend beta update instructions to release notes
|
||||
$betaInstructions = "To receive automatic beta updates, set the release channel to ``Beta`` under ``Advanced`` settings.`n`n"
|
||||
$releaseNotes = $betaInstructions + $releaseNotes
|
||||
|
||||
# Update the release with new title and notes
|
||||
Write-Host "Updating release with title 'Beta' and new notes..."
|
||||
gh release edit $tagVersion --title "Beta" --notes "$releaseNotes"
|
||||
Write-Host "Successfully updated release title to 'Beta' and added commit notes"
|
||||
} else {
|
||||
Write-Host "No release found with tag $tagVersion"
|
||||
}
|
||||
- name: Set release as prerelease
|
||||
shell: pwsh
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
# Get the version from the prepare job
|
||||
$versionWithBeta = "${{ needs.prepare.outputs.version }}"
|
||||
$tagVersion = "v" + $versionWithBeta
|
||||
Write-Host "Setting release as prerelease for tag: $tagVersion"
|
||||
gh release edit $tagVersion --prerelease --draft=false
|
||||
Write-Host "Successfully set release as prerelease"
|
||||
|
||||
cleanup:
|
||||
needs: [prepare, publish, edit-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Delete existing prereleases
|
||||
shell: pwsh
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
# Get the current version that was just created
|
||||
$versionWithBeta = "${{ needs.prepare.outputs.version }}"
|
||||
Write-Host "Current release version: $versionWithBeta"
|
||||
|
||||
# Find and delete any old prereleases (excluding the current one)
|
||||
Write-Host "Deleting old prereleases..."
|
||||
Write-Host "Searching for releases with isPrerelease 'true'..."
|
||||
|
||||
$betaReleases = gh release list --limit 100 --json tagName,isPrerelease,name | ConvertFrom-Json | Where-Object { $_.isPrerelease -eq $true }
|
||||
|
||||
if ($betaReleases) {
|
||||
Write-Host "Found $($betaReleases.Count) release(s) with isPrerelease 'true':"
|
||||
foreach ($release in $betaReleases) {
|
||||
$tagName = $release.tagName
|
||||
# Skip the current release
|
||||
if ($tagName -ne "v$versionWithBeta") {
|
||||
Write-Host " - Tag: $tagName, Title: $($release.name)"
|
||||
gh release delete $tagName --yes --cleanup-tag
|
||||
Write-Host " Deleted release with tag: $tagName"
|
||||
} else {
|
||||
Write-Host " - Skipping current release: $tagName"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Host "No releases found with isPrerelease 'true'"
|
||||
}
|
||||
@@ -3,7 +3,6 @@ name: Publish Docker to GHCR
|
||||
permissions: write-all
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
@@ -38,10 +37,6 @@ jobs:
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||
with:
|
||||
@@ -49,7 +44,3 @@ jobs:
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm/v7
|
||||
linux/arm64/v8
|
||||
|
||||
@@ -24,13 +24,11 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
tags: |
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||
with:
|
||||
@@ -38,7 +36,3 @@ jobs:
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm/v7
|
||||
linux/arm64/v8
|
||||
|
||||
@@ -14,15 +14,17 @@ jobs:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Node and PNPM
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
- name: Install Node and NPM
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
version: 9
|
||||
node-version: 16
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
run: |
|
||||
npm install --legacy-peer-deps
|
||||
|
||||
- name: Build and Publish releases
|
||||
- name: Publish releases
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: nick-invision/retry@v2.8.2
|
||||
@@ -31,17 +33,7 @@ jobs:
|
||||
max_attempts: 3
|
||||
retry_on: error
|
||||
command: |
|
||||
pnpm run publish:linux
|
||||
on_retry_command: pnpm cache delete
|
||||
|
||||
- name: Build and Publish releases (arm64)
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: nick-invision/retry@v2.8.2
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
retry_on: error
|
||||
command: |
|
||||
pnpm run publish:linux-arm64
|
||||
on_retry_command: pnpm cache delete
|
||||
npm run postinstall
|
||||
npm run build
|
||||
npm exec electron-builder -- --publish always --linux
|
||||
on_retry_command: npm cache clean --force
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Publish macOS (Manual)
|
||||
name: Publish Windows and macOS (Manual)
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
@@ -14,15 +14,17 @@ jobs:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Node and PNPM
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
- name: Install Node and NPM
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
version: 9
|
||||
node-version: 16
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
run: |
|
||||
npm install --legacy-peer-deps
|
||||
|
||||
- name: Build and Publish releases
|
||||
- name: Publish releases
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: nick-invision/retry@v2.8.2
|
||||
@@ -31,5 +33,7 @@ jobs:
|
||||
max_attempts: 3
|
||||
retry_on: error
|
||||
command: |
|
||||
pnpm run publish:mac
|
||||
on_retry_command: pnpm cache delete
|
||||
npm run postinstall
|
||||
npm run build
|
||||
npm exec electron-builder -- --publish always --win --mac
|
||||
on_retry_command: npm cache clean --force
|
||||
|
||||
@@ -4,105 +4,57 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- development
|
||||
paths:
|
||||
- 'src/**'
|
||||
|
||||
jobs:
|
||||
wait-for-lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Wait for Test workflow to complete
|
||||
uses: lewagon/wait-on-check-action@v1.4.1
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
check-name: 'lint'
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
wait-interval: 10
|
||||
allowed-conclusions: success
|
||||
|
||||
publish:
|
||||
needs: wait-for-lint
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||
os: [macos-latest]
|
||||
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node and PNPM
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
- name: Install Node and NPM
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
version: 9
|
||||
node-version: 16
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
run: |
|
||||
npm install --legacy-peer-deps
|
||||
|
||||
- name: Build for Windows
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
- name: Build releases
|
||||
uses: nick-invision/retry@v2.8.2
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
retry_on: error
|
||||
command: |
|
||||
pnpm run package:win:pr
|
||||
npm run postinstall
|
||||
npm run build
|
||||
npm run package:pr
|
||||
on_retry_command: npm cache clean --force
|
||||
|
||||
- name: Build for Linux
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
uses: nick-invision/retry@v2.8.2
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
retry_on: error
|
||||
command: |
|
||||
pnpm run package:linux:pr
|
||||
|
||||
- name: Build for MacOS
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
uses: nick-invision/retry@v2.8.2
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
retry_on: error
|
||||
command: |
|
||||
pnpm run package:mac:pr
|
||||
|
||||
- name: Zip Windows Binaries
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
shell: pwsh
|
||||
run: |
|
||||
Compress-Archive -Path "dist/*.exe" -DestinationPath "dist/windows-binaries.zip" -Force
|
||||
|
||||
- name: Zip Linux Binaries
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
run: |
|
||||
zip -r dist/linux-binaries.zip dist/*.{AppImage,deb,rpm}
|
||||
|
||||
- name: Zip MacOS Binaries
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
run: |
|
||||
zip -r dist/macos-binaries.zip dist/*.dmg
|
||||
|
||||
- name: Upload Windows Binaries
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: windows-binaries
|
||||
path: dist/windows-binaries.zip
|
||||
path: |
|
||||
release/build/*.exe
|
||||
|
||||
- name: Upload Linux Binaries
|
||||
if: ${{ matrix.os == 'ubuntu-latest' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: linux-binaries
|
||||
path: dist/linux-binaries.zip
|
||||
path: |
|
||||
release/build/*.AppImage
|
||||
release/build/*.deb
|
||||
release/build/*.rpm
|
||||
|
||||
- name: Upload MacOS Binaries
|
||||
if: ${{ matrix.os == 'macos-latest' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: macos-binaries
|
||||
path: dist/macos-binaries.zip
|
||||
path: |
|
||||
release/build/*.dmg
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
name: Publish Windows (Manual)
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest]
|
||||
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Node and PNPM
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build and Publish releases
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: nick-invision/retry@v2.8.2
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
retry_on: error
|
||||
command: |
|
||||
pnpm run publish:win
|
||||
on_retry_command: pnpm cache delete
|
||||
@@ -1,21 +0,0 @@
|
||||
name: Publish release to WinGet
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag_name:
|
||||
description: "Specific tag name"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: vedantmgoyal9/winget-releaser@main
|
||||
with:
|
||||
identifier: jeffvli.Feishin
|
||||
installers-regex: 'Feishin-*-win-x64\.exe'
|
||||
token: ${{ secrets.WINGET_ACC_TOKEN }}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
name: Publish (Manual)
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, macos-latest, ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Node and PNPM
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build and Publish releases (Windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: nick-invision/retry@v2.8.2
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
retry_on: error
|
||||
command: |
|
||||
pnpm run publish:win
|
||||
on_retry_command: pnpm cache delete
|
||||
|
||||
- name: Build and Publish releases (macOS)
|
||||
if: matrix.os == 'macos-latest'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: nick-invision/retry@v2.8.2
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
retry_on: error
|
||||
command: |
|
||||
pnpm run publish:mac
|
||||
on_retry_command: pnpm cache delete
|
||||
|
||||
- name: Build and Publish releases (Linux)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: nick-invision/retry@v2.8.2
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
retry_on: error
|
||||
command: |
|
||||
pnpm run publish:linux
|
||||
on_retry_command: pnpm cache delete
|
||||
|
||||
- name: Build and Publish releases (Linux ARM64)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: nick-invision/retry@v2.8.2
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
retry_on: error
|
||||
command: |
|
||||
pnpm run publish:linux-arm64
|
||||
on_retry_command: pnpm cache delete
|
||||
@@ -1,47 +0,0 @@
|
||||
name: 'Close stale issues and PRs'
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
stale:
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
process-only: 'issues, prs'
|
||||
issue-inactive-days: 120
|
||||
pr-inactive-days: 120
|
||||
log-output: true
|
||||
add-issue-labels: 'frozen-due-to-age'
|
||||
add-pr-labels: 'frozen-due-to-age'
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
operations-per-run: 999
|
||||
days-before-issue-stale: 180
|
||||
days-before-pr-stale: 180
|
||||
days-before-issue-close: 30
|
||||
days-before-pr-close: 30
|
||||
stale-issue-message: >
|
||||
This issue has been automatically marked as stale because it has not had recent activity. The resources of the Feishin team are limited, and so we are asking for your help.
|
||||
|
||||
If this is a **bug** and you can still reproduce this error on the <code>development</code> branch, please reply with all of the information you have about it in order to keep the issue open.
|
||||
|
||||
This issue will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions.
|
||||
|
||||
|
||||
stale-pr-message: >
|
||||
This PR has been automatically marked as stale because it has not had recent activity. The resources of the Feishin team are limited, and so we are asking for your help.
|
||||
|
||||
This PR will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions.
|
||||
|
||||
|
||||
stale-issue-label: 'stale'
|
||||
exempt-issue-labels: 'keep,security'
|
||||
stale-pr-label: 'stale'
|
||||
exempt-pr-labels: 'keep,security'
|
||||
@@ -3,20 +3,32 @@ name: Test
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
release:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Node.js and PNPM
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
- name: Install Node.js and NPM
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
version: 9
|
||||
node-version: 16
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: npm install
|
||||
run: |
|
||||
npm install --legacy-peer-deps
|
||||
|
||||
- name: Lint Files
|
||||
run: pnpm run lint
|
||||
- name: npm test
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
npm run lint
|
||||
npm run package
|
||||
npm exec tsc
|
||||
npm test
|
||||
|
||||
+30
-6
@@ -1,7 +1,31 @@
|
||||
node_modules
|
||||
dist
|
||||
out
|
||||
.DS_Store
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
.eslintcache
|
||||
*.log*
|
||||
release
|
||||
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
node_modules
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
release/app/dist
|
||||
release/build
|
||||
.erb/dll
|
||||
|
||||
.idea
|
||||
npm-debug.log.*
|
||||
*.css.d.ts
|
||||
*.sass.d.ts
|
||||
*.scss.d.ts
|
||||
|
||||
.env*
|
||||
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
@@ -1,6 +0,0 @@
|
||||
out
|
||||
dist
|
||||
pnpm-lock.yaml
|
||||
LICENSE.md
|
||||
tsconfig.json
|
||||
tsconfig.*.json
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 4,
|
||||
"useTabs": false,
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/*.css", "**/*.scss", "**/*.html"],
|
||||
"options": {
|
||||
"singleQuote": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "always",
|
||||
"proseWrap": "never",
|
||||
"htmlWhitespaceSensitivity": "strict",
|
||||
"endOfLine": "lf",
|
||||
"singleAttributePerLine": true
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
singleQuote: true
|
||||
semi: true
|
||||
printWidth: 100
|
||||
tabWidth: 4
|
||||
trailingComma: all
|
||||
useTabs: false
|
||||
arrowParens: always
|
||||
proseWrap: never
|
||||
htmlWhitespaceSensitivity: strict
|
||||
endOfLine: lf
|
||||
singleAttributePerLine: false
|
||||
bracketSpacing: true
|
||||
plugins:
|
||||
- prettier-plugin-packagejson
|
||||
+7
-9
@@ -1,19 +1,17 @@
|
||||
{
|
||||
"customSyntax": "postcss-styled-syntax",
|
||||
"extends": [
|
||||
"stylelint-config-standard",
|
||||
"stylelint-config-css-modules",
|
||||
"stylelint-config-styled-components",
|
||||
"stylelint-config-recess-order"
|
||||
],
|
||||
"rules": {
|
||||
"block-no-empty": null,
|
||||
"declaration-empty-line-before": null,
|
||||
"declaration-block-no-redundant-longhand-properties": null,
|
||||
"selector-class-pattern": null,
|
||||
"selector-type-case": ["lower", { "ignoreTypes": ["/^\\$\\w+/"] }],
|
||||
"selector-type-no-unknown": [true, { "ignoreTypes": ["/-styled-mixin/", "/^\\$\\w+/"] }],
|
||||
"declaration-block-no-shorthand-property-overrides": null,
|
||||
"declaration-block-no-redundant-longhand-properties": null,
|
||||
"at-rule-no-unknown": [true, { "ignoreAtRules": ["mixin", "value"] }],
|
||||
"function-no-unknown": [true, { "ignoreFunctions": ["darken", "alpha", "lighten"] }],
|
||||
"declaration-property-value-no-unknown": null,
|
||||
"no-descending-specificity": null,
|
||||
"no-empty-source": null
|
||||
"declaration-colon-newline-after": null,
|
||||
"property-no-vendor-prefix": null
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+8
-1
@@ -1,3 +1,10 @@
|
||||
{
|
||||
"recommendations": ["dbaeumer.vscode-eslint"]
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"EditorConfig.EditorConfig",
|
||||
"stylelint.vscode-stylelint",
|
||||
"esbenp.prettier-vscode",
|
||||
"clinyong.vscode-css-modules",
|
||||
"Huuums.vscode-fast-folder-structure"
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+26
-37
@@ -1,39 +1,28 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug Main Process",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd"
|
||||
},
|
||||
"runtimeArgs": ["--sourcemap"],
|
||||
"env": {
|
||||
"REMOTE_DEBUGGING_PORT": "9222"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Debug Renderer Process",
|
||||
"port": 9222,
|
||||
"request": "attach",
|
||||
"type": "chrome",
|
||||
"webRoot": "${workspaceFolder}/src/renderer",
|
||||
"timeout": 60000,
|
||||
"presentation": {
|
||||
"hidden": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Debug All",
|
||||
"configurations": ["Debug Main Process", "Debug Renderer Process"],
|
||||
"presentation": {
|
||||
"order": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Electron: Main",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"protocol": "inspector",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": ["run start:main --inspect=5858 --remote-debugging-port=9223"],
|
||||
"preLaunchTask": "Start Webpack Dev"
|
||||
},
|
||||
{
|
||||
"name": "Electron: Renderer",
|
||||
"type": "chrome",
|
||||
"request": "attach",
|
||||
"port": 9223,
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"timeout": 15000
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Electron: All",
|
||||
"configurations": ["Electron: Main", "Electron: Renderer"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+20
-32
@@ -1,28 +1,26 @@
|
||||
{
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"files.associations": {
|
||||
".eslintrc": "jsonc",
|
||||
".prettierrc": "jsonc",
|
||||
".eslintignore": "ignore"
|
||||
},
|
||||
"eslint.validate": ["typescript", "typescriptreact"],
|
||||
"eslint.workingDirectories": [{ "directory": "./", "changeProcessCWD": true }],
|
||||
"typescript.tsserver.experimental.enableProjectDiagnostics": false,
|
||||
"eslint.validate": ["typescript"],
|
||||
"eslint.workingDirectories": [
|
||||
{ "directory": "./", "changeProcessCWD": true },
|
||||
{ "directory": "./server", "changeProcessCWD": true }
|
||||
],
|
||||
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.fixAll.stylelint": "explicit",
|
||||
"source.organizeImports": "never",
|
||||
"source.formatDocument": "explicit"
|
||||
"source.fixAll.eslint": true,
|
||||
"source.fixAll.stylelint": true,
|
||||
"source.organizeImports": false,
|
||||
"source.formatDocument": true
|
||||
},
|
||||
"css.validate": true,
|
||||
"less.validate": false,
|
||||
"scss.validate": true,
|
||||
"scss.lint.unknownAtRules": "warning",
|
||||
"scss.lint.unknownProperties": "warning",
|
||||
"javascript.validate.enable": false,
|
||||
"javascript.format.enable": false,
|
||||
"typescript.format.enable": false,
|
||||
@@ -35,24 +33,14 @@
|
||||
"npm-debug.log.*": true,
|
||||
"test/**/__snapshots__": true,
|
||||
"package-lock.json": true,
|
||||
"*.{css,sass,scss}.d.ts": true,
|
||||
"out/**/*": true,
|
||||
"dist/**/*": true
|
||||
"*.{css,sass,scss}.d.ts": true
|
||||
},
|
||||
"i18n-ally.localesPaths": ["src/i18n", "src/i18n/locales"],
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib",
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
"stylelint.config": null,
|
||||
"stylelint.validate": ["css", "postcss"],
|
||||
"stylelint.validate": ["css", "scss", "typescript", "typescriptreact"],
|
||||
"typescript.updateImportsOnFileMove.enabled": "always",
|
||||
"typescript.preferences.autoImportFileExcludePatterns": [
|
||||
"@mantine/core",
|
||||
"@mantine/modals",
|
||||
"@mantine/dates",
|
||||
"@mantine/hooks",
|
||||
"@mantine/form",
|
||||
"@radix-ui/react-context-menu"
|
||||
],
|
||||
"[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
||||
"[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
||||
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": true,
|
||||
"folderTemplates.structures": [
|
||||
@@ -65,14 +53,14 @@
|
||||
"template": "Functional Component with CSS Modules"
|
||||
},
|
||||
{
|
||||
"fileName": "<FTName | kebabcase>.module.css"
|
||||
"fileName": "<FTName | kebabcase>.module.scss"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"folderTemplates.fileTemplates": {
|
||||
"Functional Component with CSS Modules": [
|
||||
"import styles from './<FTName | kebabcase>.module.css';",
|
||||
"import styles from './<FTName | kebabcase>.module.scss';",
|
||||
"",
|
||||
"interface <FTName | pascalcase>Props {}",
|
||||
"",
|
||||
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"label": "Start Webpack Dev",
|
||||
"script": "start:renderer",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
"isBackground": true,
|
||||
"problemMatcher": {
|
||||
"owner": "custom",
|
||||
"pattern": {
|
||||
"regexp": "____________"
|
||||
},
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": "Compiling\\.\\.\\.$",
|
||||
"endsPattern": "(Compiled successfully|Failed to compile)\\.$"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+6
-13
@@ -1,23 +1,16 @@
|
||||
# --- Builder stage
|
||||
FROM node:23-alpine as builder
|
||||
FROM node:18-alpine as builder
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
|
||||
# Copy package.json first to cache node_modules
|
||||
COPY package.json pnpm-lock.yaml .
|
||||
|
||||
RUN npm install -g pnpm
|
||||
|
||||
RUN pnpm install
|
||||
|
||||
# Copy code and build with cached modules
|
||||
COPY . .
|
||||
RUN pnpm run build:web
|
||||
# Scripts include electron-specific dependencies, which we don't need
|
||||
RUN npm install --legacy-peer-deps --ignore-scripts
|
||||
RUN npm run build:web
|
||||
|
||||
# --- Production stage
|
||||
FROM nginx:alpine-slim
|
||||
|
||||
COPY --chown=nginx:nginx --from=builder /app/out/web /usr/share/nginx/html
|
||||
COPY ./settings.js.template /etc/nginx/templates/settings.js.template
|
||||
COPY --chown=nginx:nginx --from=builder /app/release/app/dist/web /usr/share/nginx/html
|
||||
COPY ng.conf.template /etc/nginx/templates/default.conf.template
|
||||
|
||||
ENV PUBLIC_PATH="/"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<img src="assets/icons/icon.png" alt="logo" title="feishin" align="right" height="60px" width="60px" />
|
||||
<img src="assets/icons/icon.png" alt="logo" title="feishin" align="right" height="60px" />
|
||||
|
||||
# Feishin
|
||||
|
||||
@@ -27,19 +27,17 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
Rewrite of [Sonixd](https://github.com/jeffvli/sonixd).
|
||||
|
||||
## Features
|
||||
|
||||
- [x] MPV player backend
|
||||
- [x] Web player backend
|
||||
- [x] Modern UI
|
||||
- [x] Scrobble playback to your server
|
||||
- [x] Smart playlist editor (Navidrome)
|
||||
- [x] Synchronized and unsynchronized lyrics support
|
||||
- [ ] [Request a feature](https://github.com/jeffvli/feishin/issues) or [view taskboard](https://github.com/users/jeffvli/projects/5/views/1)
|
||||
- [x] MPV player backend
|
||||
- [x] Web player backend
|
||||
- [x] Modern UI
|
||||
- [x] Scrobble playback to your server
|
||||
- [x] Smart playlist editor (Navidrome)
|
||||
- [x] Synchronized and unsynchronized lyrics support
|
||||
- [ ] [Request a feature](https://github.com/jeffvli/feishin/issues) or [view taskboard](https://github.com/users/jeffvli/projects/5/views/1)
|
||||
|
||||
## Screenshots
|
||||
|
||||
@@ -51,36 +49,8 @@ Rewrite of [Sonixd](https://github.com/jeffvli/sonixd).
|
||||
|
||||
Download the [latest desktop client](https://github.com/jeffvli/feishin/releases). The desktop client is the recommended way to use Feishin. It supports both the MPV and web player backends, as well as includes built-in fetching for lyrics.
|
||||
|
||||
#### macOS Notes
|
||||
|
||||
If you're using a device running macOS 12 (Monterey) or higher, [check here](https://github.com/jeffvli/feishin/issues/104#issuecomment-1553914730) for instructions on how to remove the app from quarantine.
|
||||
|
||||
For media keys to work, you will be prompted to allow Feishin to be a Trusted Accessibility Client. After allowing, you will need to restart Feishin for the privacy settings to take effect.
|
||||
|
||||
#### Linux Notes
|
||||
|
||||
We provide a small install script to download the latest `.AppImage`, make it executable, and also download the icons required by Desktop Environments. Finally, it generates a `.desktop` file to add Feishin to your Application Launcher.
|
||||
|
||||
Simply run the installer like this:
|
||||
```sh
|
||||
dir=/your/application/directory
|
||||
curl 'https://raw.githubusercontent.com/jeffvli/feishin/refs/heads/development/install-feishin-appimage' | sh -s -- "$dir"
|
||||
```
|
||||
|
||||
The script also has an option to add launch arguments to run Feishin in native Wayland mode. Note that this is experimental in Electron and therefore not officially supported. If you want to use it, run this instead:
|
||||
```sh
|
||||
dir=/your/application/directory
|
||||
curl 'https://raw.githubusercontent.com/jeffvli/feishin/refs/heads/development/install-feishin-appimage' | sh -s -- "$dir" wayland-native
|
||||
```
|
||||
|
||||
It also provides a simple uninstall routine, removing the downloaded files:
|
||||
```sh
|
||||
dir=/your/application/directory
|
||||
curl 'https://raw.githubusercontent.com/jeffvli/feishin/refs/heads/development/install-feishin-appimage' | sh -s -- "$dir" remove
|
||||
```
|
||||
|
||||
The entry should show up in your Application Launcher immediately. If it does not, simply log out, wait 10 seconds, and log back in. Your Desktop Environment may alternatively provide a way to reload entries.
|
||||
|
||||
### Web and Docker
|
||||
|
||||
Visit [https://feishin.vercel.app](https://feishin.vercel.app) to use the hosted web version of Feishin. The web client only supports the web player backend.
|
||||
@@ -89,32 +59,11 @@ Feishin is also available as a Docker image. The images are hosted via `ghcr.io`
|
||||
|
||||
```bash
|
||||
# Run the latest version
|
||||
docker run --name feishin -p 9180:9180 ghcr.io/jeffvli/feishin:latest
|
||||
docker run --name feishin --port 9180:9180 ghcr.io/jeffvli/feishin:latest
|
||||
|
||||
# Build the image locally
|
||||
docker build -t feishin .
|
||||
docker run --name feishin -p 9180:9180 feishin
|
||||
```
|
||||
|
||||
#### Docker Compose
|
||||
|
||||
To install via Docker Compose, use the following snippet. This also works on Portainer.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
feishin:
|
||||
container_name: feishin
|
||||
image: 'ghcr.io/jeffvli/feishin:latest'
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- SERVER_NAME=jellyfin # pre-defined server name
|
||||
- SERVER_LOCK=true # When true AND name/type/url are set, only username/password can be toggled
|
||||
- SERVER_TYPE=jellyfin # the allowed types are: jellyfin, navidrome, subsonic. These values are case insensitive
|
||||
- SERVER_URL= # http://address:port or https://address:port
|
||||
- ANALYTICS_DISABLED=true # Set to true to disable Umami analytics tracking
|
||||
ports:
|
||||
- 9180:9180
|
||||
# Alternatively, to restrict to only localhost, - 127.0.0.1:9180:8190
|
||||
docker run --name feishin --port 9180:9180 feishin
|
||||
```
|
||||
|
||||
### Configuration
|
||||
@@ -123,15 +72,10 @@ services:
|
||||
|
||||
2. After restarting the app, you will be prompted to select a server. Click the `Open menu` button and select `Manage servers`. Click the `Add server` button in the popup and fill out all applicable details. You will need to enter the full URL to your server, including the protocol and port if applicable (e.g. `https://navidrome.my-server.com` or `http://192.168.0.1:4533`).
|
||||
|
||||
- **Navidrome** - For the best experience, select "Save password" when creating the server and configure the `SessionTimeout` setting in your Navidrome config to a larger value (e.g. 72h).
|
||||
- **Linux users** - The default password store uses `libsecret`. `kwallet4/5/6` are also supported, but must be explicitly set in Settings > Window > Passwords/secret store.
|
||||
- **Navidrome** - For the best experience, select "Save password" when creating the server and configure the `SessionTimeout` setting in your Navidrome config to a larger value (e.g. 72h).
|
||||
|
||||
3. _Optional_ - If you want to host Feishin on a subpath (not `/`), then pass in the following environment variable: `PUBLIC_PATH=PATH`. For example, to host on `/feishin`, pass in `PUBLIC_PATH=/feishin`.
|
||||
|
||||
4. _Optional_ - To hard code the server url, pass the following environment variables: `SERVER_NAME`, `SERVER_TYPE` (one of `jellyfin` or `navidrome` or `subsonic`), `SERVER_URL`. To prevent users from changing these settings, pass `SERVER_LOCK=true`. This can only be set if all three of the previous values are set.
|
||||
|
||||
5. _Optional_ - To disable Umami analytics tracking in the Docker/web version, set the environment variable `ANALYTICS_DISABLED=true`. When enabled, the analytics script will not be loaded and all tracking will be disabled.
|
||||
|
||||
## FAQ
|
||||
|
||||
### MPV is either not working or is rapidly switching between pause/play states
|
||||
@@ -140,65 +84,18 @@ First thing to do is check that your MPV binary path is correct. Navigate to the
|
||||
|
||||
### What music servers does Feishin support?
|
||||
|
||||
Feishin supports any music server that implements a [Navidrome](https://www.navidrome.org/), [Jellyfin](https://jellyfin.org/), or [OpenSubsonic compatible](https://opensubsonic.netlify.app/) API.
|
||||
Feishin supports any music server that implements a [Navidrome](https://www.navidrome.org/) or [Jellyfin](https://jellyfin.org/) API. **Subsonic API is not currently supported**. This will likely be added in [later when the new Subsonic API is decided on](https://support.symfonium.app/t/subsonic-servers-participation/1233).
|
||||
|
||||
- [Navidrome](https://github.com/navidrome/navidrome)
|
||||
- [Jellyfin](https://github.com/jellyfin/jellyfin)
|
||||
- [OpenSubsonic](https://opensubsonic.netlify.app/) compatible servers, such as...
|
||||
- [Airsonic-Advanced](https://github.com/airsonic-advanced/airsonic-advanced)
|
||||
- [Ampache](https://ampache.org)
|
||||
- [Astiga](https://asti.ga/)
|
||||
- [Funkwhale](https://www.funkwhale.audio/)
|
||||
- [Gonic](https://github.com/sentriz/gonic)
|
||||
- [LMS](https://github.com/epoupon/lms)
|
||||
- [Nextcloud Music](https://apps.nextcloud.com/apps/music)
|
||||
- [Supysonic](https://github.com/spl0k/supysonic)
|
||||
- [Qm-Music](https://github.com/chenqimiao/qm-music)
|
||||
- More (?)
|
||||
|
||||
### I have the issue "The SUID sandbox helper binary was found, but is not configured correctly" on Linux
|
||||
|
||||
This happens when you have user (unprivileged) namespaces disabled (`sysctl kernel.unprivileged_userns_clone` returns 0). You can fix this by either enabling unprivileged namespaces, or by making the `chrome-sandbox` Setuid.
|
||||
|
||||
```bash
|
||||
chmod 4755 chrome-sandbox
|
||||
sudo chown root:root chrome-sandbox
|
||||
```
|
||||
|
||||
Ubuntu 24.04 specifically introduced breaking changes that affect how namespaces work. Please see https://discourse.ubuntu.com/t/ubuntu-24-04-lts-noble-numbat-release-notes/39890#:~:text=security%20improvements%20 for possible fixes.
|
||||
- [Navidrome](https://github.com/navidrome/navidrome)
|
||||
- [Jellyfin](https://github.com/jellyfin/jellyfin)
|
||||
- [Funkwhale](https://funkwhale.audio/) - TBD
|
||||
- Subsonic-compatible servers - TBD
|
||||
|
||||
## Development
|
||||
|
||||
Built and tested using Node `v23.11.0`.
|
||||
Built and tested using Node `v16.15.0`.
|
||||
|
||||
This project is built off of [electron-vite](https://github.com/alex8088/electron-vite)
|
||||
|
||||
- `pnpm run dev` - Start the development server
|
||||
- `pnpm run dev:watch` - Start the development server in watch mode (for main / preload HMR)
|
||||
- `pnpm run start` - Starts the app in production preview mode
|
||||
- `pnpm run build` - Builds the app for desktop
|
||||
- `pnpm run build:electron` - Build the electron app (main, preload, and renderer)
|
||||
- `pnpm run build:remote` - Build the remote app (remote)
|
||||
- `pnpm run build:web` - Build the standalone web app (renderer)
|
||||
- `pnpm run package` - Package the project
|
||||
- `pnpm run package:dev` - Package the project for development locally
|
||||
- `pnpm run package:linux` - Package the project for Linux locally
|
||||
- `pnpm run package:mac` - Package the project for Mac locally
|
||||
- `pnpm run package:win` - Package the project for Windows locally
|
||||
- `pnpm run publish:linux` - Publish the project for Linux
|
||||
- `pnpm run publish:linux:beta` - Publish the project for Linux (beta channel)
|
||||
- `pnpm run publish:linux-arm64` - Publish the project for Linux ARM64
|
||||
- `pnpm run publish:linux-arm64:beta` - Publish the project for Linux ARM64 (beta channel)
|
||||
- `pnpm run publish:mac` - Publish the project for Mac
|
||||
- `pnpm run publish:mac:beta` - Publish the project for Mac (beta channel)
|
||||
- `pnpm run publish:win` - Publish the project for Windows
|
||||
- `pnpm run publish:win:beta` - Publish the project for Windows (beta channel)
|
||||
- `pnpm run typecheck` - Type check the project
|
||||
- `pnpm run typecheck:node` - Type check the project with tsconfig.node.json
|
||||
- `pnpm run typecheck:web` - Type check the project with tsconfig.web.json
|
||||
- `pnpm run lint` - Lint the project
|
||||
- `pnpm run lint:fix` - Lint the project and fix linting errors
|
||||
- `pnpm run i18next` - Generate i18n files
|
||||
This project is built off of [electron-react-boilerplate](https://github.com/electron-react-boilerplate/electron-react-boilerplate) v4.6.0.
|
||||
|
||||
## Translation
|
||||
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
provider: generic
|
||||
url: https://example.com/auto-updates
|
||||
updaterCacheDirName: feishin-updater
|
||||
@@ -1,13 +0,0 @@
|
||||
services:
|
||||
feishin:
|
||||
container_name: feishin
|
||||
image: 'ghcr.io/jeffvli/feishin:latest'
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- SERVER_NAME=jellyfin # pre-defined server name
|
||||
- SERVER_LOCK=true # When true AND name/type/url are set, only username/password can be toggled
|
||||
- SERVER_TYPE=jellyfin # the allowed types are: jellyfin, navidrome, subsonic. These values are case insensitive
|
||||
- SERVER_URL= # http://address:port or https://address:port
|
||||
ports:
|
||||
- 9180:9180
|
||||
# Alternatively, to restrict to only localhost, - 127.0.0.1:9180:8190
|
||||
@@ -1,59 +0,0 @@
|
||||
appId: org.jeffvli.feishin
|
||||
productName: Feishin
|
||||
artifactName: ${productName}-${version}-${os}-${arch}.${ext}
|
||||
electronVersion: 39.2.7
|
||||
directories:
|
||||
buildResources: assets
|
||||
files:
|
||||
- 'out/**/*'
|
||||
- 'package.json'
|
||||
extraResources:
|
||||
- assets/**
|
||||
asarUnpack:
|
||||
- resources/**
|
||||
win:
|
||||
target:
|
||||
- zip
|
||||
- nsis
|
||||
icon: assets/icons/icon.png
|
||||
|
||||
nsis:
|
||||
allowToChangeInstallationDirectory: true
|
||||
oneClick: false
|
||||
shortcutName: ${productName}
|
||||
uninstallDisplayName: ${productName}
|
||||
createDesktopShortcut: always
|
||||
|
||||
mac:
|
||||
target:
|
||||
target: default
|
||||
arch:
|
||||
- arm64
|
||||
- x64
|
||||
icon: assets/icons/icon.icns
|
||||
type: distribution
|
||||
hardenedRuntime: true
|
||||
entitlements: assets/entitlements.mac.plist
|
||||
entitlementsInherit: assets/entitlements.mac.plist
|
||||
gatekeeperAssess: false
|
||||
notarize: false
|
||||
|
||||
dmg:
|
||||
contents: [{ x: 130, y: 220 }, { x: 410, y: 220, type: link, path: /Applications }]
|
||||
|
||||
linux:
|
||||
target:
|
||||
- AppImage
|
||||
- deb
|
||||
- tar.xz
|
||||
category: AudioVideo;Audio;Player
|
||||
icon: assets/icons/icon.png
|
||||
artifactName: ${productName}-${os}-${arch}.${ext}
|
||||
|
||||
npmRebuild: false
|
||||
publish:
|
||||
provider: github
|
||||
owner: jeffvli
|
||||
repo: feishin
|
||||
channel: beta
|
||||
releaseType: draft
|
||||
@@ -1,59 +0,0 @@
|
||||
appId: org.jeffvli.feishin
|
||||
productName: Feishin
|
||||
artifactName: ${productName}-${version}-${os}-${arch}.${ext}
|
||||
electronVersion: 39.2.7
|
||||
directories:
|
||||
buildResources: assets
|
||||
files:
|
||||
- 'out/**/*'
|
||||
- 'package.json'
|
||||
extraResources:
|
||||
- assets/**
|
||||
asarUnpack:
|
||||
- resources/**
|
||||
win:
|
||||
target:
|
||||
- zip
|
||||
- nsis
|
||||
icon: assets/icons/icon.png
|
||||
|
||||
nsis:
|
||||
allowToChangeInstallationDirectory: true
|
||||
oneClick: false
|
||||
shortcutName: ${productName}
|
||||
uninstallDisplayName: ${productName}
|
||||
createDesktopShortcut: always
|
||||
|
||||
mac:
|
||||
target:
|
||||
target: default
|
||||
arch:
|
||||
- arm64
|
||||
- x64
|
||||
icon: assets/icons/icon.icns
|
||||
type: distribution
|
||||
hardenedRuntime: true
|
||||
entitlements: assets/entitlements.mac.plist
|
||||
entitlementsInherit: assets/entitlements.mac.plist
|
||||
gatekeeperAssess: false
|
||||
notarize: false
|
||||
|
||||
dmg:
|
||||
contents: [{ x: 130, y: 220 }, { x: 410, y: 220, type: link, path: /Applications }]
|
||||
|
||||
linux:
|
||||
target:
|
||||
- AppImage
|
||||
- deb
|
||||
- tar.xz
|
||||
category: AudioVideo;Audio;Player
|
||||
icon: assets/icons/icon.png
|
||||
artifactName: ${productName}-${os}-${arch}.${ext}
|
||||
|
||||
npmRebuild: false
|
||||
publish:
|
||||
provider: github
|
||||
owner: jeffvli
|
||||
repo: feishin
|
||||
channel: latest
|
||||
releaseType: draft
|
||||
@@ -1,71 +0,0 @@
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { externalizeDepsPlugin, UserConfig } from 'electron-vite';
|
||||
import { resolve } from 'path';
|
||||
import conditionalImportPlugin from 'vite-plugin-conditional-import';
|
||||
import dynamicImportPlugin from 'vite-plugin-dynamic-import';
|
||||
import { ViteEjsPlugin } from 'vite-plugin-ejs';
|
||||
|
||||
const currentOSEnv = process.platform;
|
||||
|
||||
const config: UserConfig = {
|
||||
main: {
|
||||
build: {
|
||||
rollupOptions: {
|
||||
external: ['source-map-support'],
|
||||
},
|
||||
sourcemap: true,
|
||||
},
|
||||
define: {
|
||||
'import.meta.env.IS_LINUX': JSON.stringify(currentOSEnv === 'linux'),
|
||||
'import.meta.env.IS_MACOS': JSON.stringify(currentOSEnv === 'darwin'),
|
||||
'import.meta.env.IS_WIN': JSON.stringify(currentOSEnv === 'win32'),
|
||||
},
|
||||
plugins: [
|
||||
externalizeDepsPlugin(),
|
||||
dynamicImportPlugin(),
|
||||
conditionalImportPlugin({
|
||||
currentEnv: currentOSEnv,
|
||||
envs: ['win32', 'linux', 'darwin'],
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'/@/main': resolve('src/main'),
|
||||
'/@/shared': resolve('src/shared'),
|
||||
},
|
||||
},
|
||||
},
|
||||
preload: {
|
||||
plugins: [externalizeDepsPlugin()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'/@/preload': resolve('src/preload'),
|
||||
'/@/shared': resolve('src/shared'),
|
||||
},
|
||||
},
|
||||
},
|
||||
renderer: {
|
||||
build: {
|
||||
cssMinify: 'esbuild',
|
||||
minify: 'esbuild',
|
||||
sourcemap: true,
|
||||
},
|
||||
css: {
|
||||
modules: {
|
||||
generateScopedName: 'fs-[name]-[local]',
|
||||
localsConvention: 'camelCase',
|
||||
},
|
||||
},
|
||||
plugins: [react(), ViteEjsPlugin({ web: false })],
|
||||
resolve: {
|
||||
alias: {
|
||||
'/@/i18n': resolve('src/i18n'),
|
||||
'/@/remote': resolve('src/remote'),
|
||||
'/@/renderer': resolve('src/renderer'),
|
||||
'/@/shared': resolve('src/shared'),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,55 +0,0 @@
|
||||
import eslintConfigPrettier from '@electron-toolkit/eslint-config-prettier';
|
||||
import tseslint from '@electron-toolkit/eslint-config-ts';
|
||||
import perfectionist from 'eslint-plugin-perfectionist';
|
||||
import eslintPluginReact from 'eslint-plugin-react';
|
||||
import eslintPluginReactHooks from 'eslint-plugin-react-hooks';
|
||||
import eslintPluginReactRefresh from 'eslint-plugin-react-refresh';
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['**/node_modules', '**/dist', '**/out'] },
|
||||
tseslint.configs.recommended,
|
||||
perfectionist.configs['recommended-natural'],
|
||||
eslintPluginReact.configs.flat.recommended,
|
||||
eslintPluginReact.configs.flat['jsx-runtime'],
|
||||
{
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
plugins: {
|
||||
'react-hooks': eslintPluginReactHooks,
|
||||
'react-refresh': eslintPluginReactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...eslintPluginReactHooks.configs.recommended.rules,
|
||||
...eslintPluginReactRefresh.configs.vite.rules,
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-duplicate-enum-values': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
curly: ['error', 'all'],
|
||||
indent: [
|
||||
'error',
|
||||
'tab',
|
||||
{
|
||||
offsetTernaryExpressions: true,
|
||||
SwitchCase: 1,
|
||||
},
|
||||
],
|
||||
'no-unused-vars': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
quotes: ['error', 'single'],
|
||||
'react-hooks/refs': 'off',
|
||||
'react-hooks/set-state-in-effect': 'off',
|
||||
'react-refresh/only-export-components': 'off',
|
||||
'react/display-name': 'off',
|
||||
semi: ['error', 'always'],
|
||||
'single-attribute-per-line': 'off',
|
||||
},
|
||||
},
|
||||
eslintConfigPrettier,
|
||||
);
|
||||
@@ -1,13 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Name=Feishin
|
||||
GenericName=Music player
|
||||
Exec=${FEISHIN_DESKTOP_EXECUTABLE} ${FEISHIN_DESKTOP_ARGS}
|
||||
TryExec=${FEISHIN_DESKTOP_EXECUTABLE}
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Icon=org.jeffvli.feishin
|
||||
StartupWMClass=feishin
|
||||
SingleMainWindow=true
|
||||
Categories=AudioVideo;Audio;Player;Music;
|
||||
Keywords=Navidrome;Jellyfin;Subsonic;OpenSubsonic
|
||||
Comment=A player for your self-hosted music server
|
||||
@@ -1,79 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
if [ "$#" -lt 1 ]; then
|
||||
echo "Usage: $0 <installation-directory> <option>"
|
||||
echo "Options:"
|
||||
echo " wayland-native Enable native Wayland support"
|
||||
echo " remove Remove Feishin AppImage and desktop entries"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dir="$(readlink -f "${1}")"
|
||||
arg="${2:-""}"
|
||||
arch="$(uname -m)"
|
||||
|
||||
if [ "$arg" != "wayland-native" ] && [ "$arg" != "remove" ] && [ "$arg" != "" ]; then
|
||||
echo "Invalid option: $arg"
|
||||
echo "Valid options are: wayland-native, remove"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${arch}" != "x86_64" ] && [ "${arch}" != "aarch64" ]; then
|
||||
echo "CPU architecture not recognised (not x86_64 or aarch64). Aborting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# workaround if we're not renaming the artifact
|
||||
if [ "${arch}" = "aarch64" ]; then
|
||||
arch="arm64"
|
||||
fi
|
||||
|
||||
if [ ! -d "${dir}" ]; then
|
||||
echo "${dir} is not a directory or does not exist. Please provide an existing directory."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
localShare="${XDG_DATA_HOME:-$HOME/.local/share}"
|
||||
localShareIcons="${localShare}/icons/hicolor"
|
||||
|
||||
if [ "${arg}" = "remove" ]; then
|
||||
rm -v \
|
||||
"${localShareIcons}/512x512/apps/org.jeffvli.feishin.png" \
|
||||
"${localShareIcons}/256x256/apps/org.jeffvli.feishin.png" \
|
||||
"${localShareIcons}/128x128/apps/org.jeffvli.feishin.png" \
|
||||
"${localShareIcons}/64x64/apps/org.jeffvli.feishin.png" \
|
||||
"${localShareIcons}/32x32/apps/org.jeffvli.feishin.png" \
|
||||
"${localShare}/applications/org.jeffvli.feishin.desktop" \
|
||||
"${dir}/Feishin-linux-${arch}.AppImage"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
curl --fail -L --create-dirs --write-out '%{filename_effective}\n' \
|
||||
-o "${dir}/Feishin-linux-${arch}.AppImage" "https://github.com/jeffvli/feishin/releases/latest/download/Feishin-linux-${arch}.AppImage" \
|
||||
-o "${localShareIcons}/512x512/apps/org.jeffvli.feishin.png" 'https://github.com/jeffvli/feishin/blob/development/assets/icons/512x512.png?raw=true' \
|
||||
-o "${localShareIcons}/256x256/apps/org.jeffvli.feishin.png" 'https://github.com/jeffvli/feishin/blob/development/assets/icons/256x256.png?raw=true' \
|
||||
-o "${localShareIcons}/128x128/apps/org.jeffvli.feishin.png" 'https://github.com/jeffvli/feishin/blob/development/assets/icons/128x128.png?raw=true' \
|
||||
-o "${localShareIcons}/64x64/apps/org.jeffvli.feishin.png" 'https://github.com/jeffvli/feishin/blob/development/assets/icons/64x64.png?raw=true' \
|
||||
-o "${localShareIcons}/32x32/apps/org.jeffvli.feishin.png" 'https://github.com/jeffvli/feishin/blob/development/assets/icons/32x32.png?raw=true'
|
||||
chmod -v u+x "${dir}/Feishin-linux-${arch}.AppImage"
|
||||
|
||||
waylandFlags=""
|
||||
if [ "${arg}" = "wayland-native" ]; then
|
||||
waylandFlags="--enable-features=UseOzonePlatform,WaylandWindowDecorations --ozone-platform-hint=auto"
|
||||
fi
|
||||
|
||||
# this is for Debian-based kernels and ALT respectively
|
||||
# https://unix.stackexchange.com/a/303214/145722
|
||||
sandboxFlag=""
|
||||
if [ "$(sysctl kernel.unprivileged_userns_clone 2>/dev/null)" = "0" ] \
|
||||
|| [ "$(sysctl kernel.userns_restrict 2>/dev/null)" = "1" ]; then
|
||||
sandboxFlag="--no-sandbox"
|
||||
fi
|
||||
|
||||
mkdir -pv "${localShare}/applications"
|
||||
|
||||
export FEISHIN_DESKTOP_EXECUTABLE="${dir}/Feishin-linux-${arch}.AppImage"
|
||||
export FEISHIN_DESKTOP_ARGS="${sandboxFlag} ${waylandFlags}"
|
||||
curl --fail https://raw.githubusercontent.com/jeffvli/feishin/refs/heads/development/feishin.desktop.tmpl | envsubst > "${localShare}/applications/org.jeffvli.feishin.desktop"
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
+1
-9
@@ -16,12 +16,4 @@ server {
|
||||
alias /usr/share/nginx/html/;
|
||||
try_files $uri $uri/ /index.html =404;
|
||||
}
|
||||
|
||||
location ${PUBLIC_PATH}settings.js {
|
||||
alias /etc/nginx/conf.d/settings.js;
|
||||
}
|
||||
|
||||
location ${PUBLIC_PATH}/settings.js {
|
||||
alias /etc/nginx/conf.d/settings.js;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>org.jeffvli.feishin</id>
|
||||
<name>Feishin</name>
|
||||
<summary>Jellyfin, Navidrome, and OpenSubsonic Compatible Music Player</summary>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-3.0-only</project_license>
|
||||
<content_rating type="oars-1.1"/>
|
||||
<description>
|
||||
<p>A modern, cross-platform music player for Jellyfin, Navidrome, and OpenSubsonic servers.</p>
|
||||
<p>Features</p>
|
||||
<ul>
|
||||
<li>MPV player backend</li>
|
||||
<li>Web player backend</li>
|
||||
<li>Jellyfin server support</li>
|
||||
<li>Navidrome server support</li>
|
||||
<li>OpenSubsonic server support</li>
|
||||
<li>Modern UI</li>
|
||||
<li>Scrobble playback to your server</li>
|
||||
<li>Smart playlist editor (Navidrome)</li>
|
||||
<li>Synchronized and unsynchronized lyrics support</li>
|
||||
</ul>
|
||||
</description>
|
||||
<developer id="org.jeffvli">
|
||||
<name>jeffvli</name>
|
||||
</developer>
|
||||
<launchable type="desktop-id">org.jeffvli.feishin.desktop</launchable>
|
||||
<url type="homepage">https://github.com/jeffvli/feishin</url>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>The main menu</caption>
|
||||
<image type="source">https://raw.githubusercontent.com/jeffvli/feishin/development/media/preview_home.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Browsing an album</caption>
|
||||
<image type="source">https://raw.githubusercontent.com/jeffvli/feishin/development/media/preview_album_detail.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<caption>Smart playlist creation</caption>
|
||||
<image type="source">https://raw.githubusercontent.com/jeffvli/feishin/development/media/preview_smart_playlist.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<categories>
|
||||
<category>AudioVideo</category>
|
||||
<category>Audio</category>
|
||||
<category>Player</category>
|
||||
<category>Music</category>
|
||||
</categories>
|
||||
<releases>
|
||||
<release date="2025-10-13" type="stable" version="0.21.2"></release>
|
||||
<release date="2025-10-13" type="stable" version="0.21.1"></release>
|
||||
<release date="2025-10-13" type="stable" version="0.21.0"></release>
|
||||
<release date="2025-09-11" type="stable" version="0.20.1"></release>
|
||||
<release date="2025-09-07" type="stable" version="0.20.0"></release>
|
||||
<release date="2025-07-31" type="stable" version="0.19.0"></release>
|
||||
<release date="2025-07-08" type="stable" version="0.18.0"></release>
|
||||
<release date="2025-06-30" type="stable" version="0.17.0"></release>
|
||||
<release date="2025-06-26" type="stable" version="0.16.0"></release>
|
||||
<release date="2025-06-25" type="stable" version="0.15.1"></release>
|
||||
<release date="2025-06-25" type="stable" version="0.15.0"></release>
|
||||
<release date="2025-06-03" type="stable" version="0.14.0"></release>
|
||||
<release date="2025-05-26" type="stable" version="0.13.0"></release>
|
||||
<release date="2025-05-13" type="stable" version="0.12.7"></release>
|
||||
<release date="2025-05-08" type="stable" version="0.12.6"></release>
|
||||
<release date="2025-05-07" type="stable" version="0.12.5"></release>
|
||||
<release date="2025-03-10" type="stable" version="0.12.3"></release>
|
||||
<release date="2025-01-25" type="stable" version="0.12.2"></release>
|
||||
<release date="2024-11-20" type="stable" version="0.12.1"></release>
|
||||
<release date="2024-11-19" type="stable" version="0.12.0"></release>
|
||||
<release date="2024-10-15" type="stable" version="0.11.1"></release>
|
||||
<release date="2024-10-10" type="stable" version="0.11.0"></release>
|
||||
<release date="2024-09-29" type="stable" version="0.10.1"></release>
|
||||
<release date="2024-09-27" type="stable" version="0.10.0"></release>
|
||||
<release date="2024-09-11" type="stable" version="0.9.0"></release>
|
||||
<release date="2024-09-04" type="stable" version="0.8.1"></release>
|
||||
<release date="2024-09-03" type="stable" version="0.8.0"></release>
|
||||
<release date="2024-07-30" type="stable" version="0.7.3"></release>
|
||||
<release date="2024-07-30" type="stable" version="0.7.2"></release>
|
||||
<release date="2024-05-07" type="stable" version="0.7.1"></release>
|
||||
<release date="2024-05-07" type="stable" version="0.7.0"></release>
|
||||
<release date="2024-03-13" type="stable" version="0.6.1"></release>
|
||||
<release date="2024-03-06" type="stable" version="0.6.0"></release>
|
||||
<release date="2023-12-14" type="stable" version="0.5.3"></release>
|
||||
<release date="2023-11-18" type="stable" version="0.5.2"></release>
|
||||
<release date="2023-11-02" type="stable" version="0.5.1"></release>
|
||||
<release date="2023-10-31" type="stable" version="0.5.0"></release>
|
||||
<release date="2023-10-08" type="stable" version="0.4.1"></release>
|
||||
<release date="2023-09-25" type="stable" version="0.4.0"></release>
|
||||
<release date="2023-08-08" type="stable" version="0.3.0"></release>
|
||||
<release date="2023-06-14" type="stable" version="0.2.0"></release>
|
||||
<release date="2023-05-22" type="stable" version="0.1.1"></release>
|
||||
<release date="2023-05-22" type="stable" version="0.1.0"></release>
|
||||
<release date="2023-04-03" type="development" version="0.0.1-alpha6"></release>
|
||||
<release date="2023-02-09" type="development" version="0.0.1-alpha5"></release>
|
||||
<release date="2023-01-16" type="development" version="0.0.1-alpha4"></release>
|
||||
<release date="2023-01-03" type="development" version="0.0.1-alpha3"></release>
|
||||
<release date="2022-12-30" type="development" version="0.0.1-alpha2"></release>
|
||||
<release date="2022-11-21" type="development" version="0.0.1-alpha1"></release>
|
||||
</releases>
|
||||
</component>
|
||||
Generated
+30813
-11821
File diff suppressed because it is too large
Load Diff
+312
-164
@@ -1,186 +1,334 @@
|
||||
{
|
||||
"name": "feishin",
|
||||
"version": "1.1.0",
|
||||
"description": "A modern self-hosted music player.",
|
||||
"productName": "Feishin",
|
||||
"description": "Feishin music server",
|
||||
"version": "0.4.1",
|
||||
"scripts": {
|
||||
"build": "concurrently \"npm run build:main\" \"npm run build:renderer\" \"npm run build:remote\"",
|
||||
"build:main": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.main.prod.ts",
|
||||
"build:remote": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.remote.prod.ts",
|
||||
"build:renderer": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.prod.ts",
|
||||
"build:web": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.web.prod.ts",
|
||||
"build:docker": "npm run build:web && docker build -t jeffvli/feishin .",
|
||||
"rebuild": "electron-rebuild --parallel --types prod,dev,optional --module-dir release/app",
|
||||
"lint": "concurrently \"npm run lint:code\" \"npm run lint:styles\"",
|
||||
"lint:code": "cross-env NODE_ENV=development eslint . --ext .js,.jsx,.ts,.tsx --fix",
|
||||
"lint:styles": "npx stylelint **/*.tsx --fix",
|
||||
"package": "ts-node ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never",
|
||||
"package:pr": "ts-node ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never --win --mac --linux",
|
||||
"package:dev": "ts-node ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never --dir",
|
||||
"postinstall": "ts-node .erb/scripts/check-native-dep.js && electron-builder install-app-deps && cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.dev.dll.ts",
|
||||
"start": "ts-node ./.erb/scripts/check-port-in-use.js && npm run start:renderer",
|
||||
"start:main": "cross-env NODE_ENV=development electron -r ts-node/register/transpile-only ./src/main/main.ts",
|
||||
"start:preload": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts",
|
||||
"start:remote": "cross-env NODE_ENV=developemnt TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.remote.dev.ts",
|
||||
"start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts",
|
||||
"start:web": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.web.ts",
|
||||
"test": "jest",
|
||||
"prepare": "husky install",
|
||||
"i18next": "i18next -c src/i18n/i18next-parser.config.js",
|
||||
"prod:buildserver": "pwsh -c \"./scripts/server-build.ps1\"",
|
||||
"prod:publishserver": "pwsh -c \"./scripts/server-publish.ps1\""
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
"cross-env NODE_ENV=development eslint --cache"
|
||||
],
|
||||
"*.json,.{eslintrc,prettierrc}": [
|
||||
"prettier --ignore-path .eslintignore --parser json --write"
|
||||
],
|
||||
"*.{css,scss}": [
|
||||
"prettier --ignore-path .eslintignore --single-quote --write"
|
||||
],
|
||||
"*.{html,md,yml}": [
|
||||
"prettier --ignore-path .eslintignore --single-quote --write"
|
||||
]
|
||||
},
|
||||
"build": {
|
||||
"productName": "Feishin",
|
||||
"appId": "org.jeffvli.feishin",
|
||||
"artifactName": "${productName}-${version}-${os}-${arch}.${ext}",
|
||||
"asar": true,
|
||||
"asarUnpack": "**\\*.{node,dll}",
|
||||
"files": [
|
||||
"dist",
|
||||
"node_modules",
|
||||
"package.json"
|
||||
],
|
||||
"afterSign": ".erb/scripts/notarize.js",
|
||||
"electronVersion": "25.8.1",
|
||||
"mac": {
|
||||
"target": {
|
||||
"target": "default",
|
||||
"arch": [
|
||||
"arm64",
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
"icon": "assets/icons/icon.icns",
|
||||
"type": "distribution",
|
||||
"hardenedRuntime": true,
|
||||
"entitlements": "assets/entitlements.mac.plist",
|
||||
"entitlementsInherit": "assets/entitlements.mac.plist",
|
||||
"gatekeeperAssess": false
|
||||
},
|
||||
"dmg": {
|
||||
"contents": [
|
||||
{
|
||||
"x": 130,
|
||||
"y": 220
|
||||
},
|
||||
{
|
||||
"x": 410,
|
||||
"y": 220,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
}
|
||||
]
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
"nsis",
|
||||
"zip"
|
||||
],
|
||||
"icon": "assets/icons/icon.ico"
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
"AppImage",
|
||||
"tar.xz"
|
||||
],
|
||||
"icon": "assets/icons/icon.png",
|
||||
"category": "Development"
|
||||
},
|
||||
"directories": {
|
||||
"app": "release/app",
|
||||
"buildResources": "assets",
|
||||
"output": "release/build"
|
||||
},
|
||||
"extraResources": [
|
||||
"./assets/**"
|
||||
],
|
||||
"publish": {
|
||||
"provider": "github",
|
||||
"owner": "jeffvli",
|
||||
"repo": "feishin"
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/jeffvli/feishin.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "jeffvli",
|
||||
"url": "https://github.com/jeffvli/"
|
||||
},
|
||||
"contributors": [],
|
||||
"license": "GPL-3.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/jeffvli/feishin/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"subsonic",
|
||||
"navidrome",
|
||||
"airsonic",
|
||||
"jellyfin",
|
||||
"react",
|
||||
"electron"
|
||||
],
|
||||
"homepage": "https://github.com/jeffvli/feishin",
|
||||
"bugs": {
|
||||
"url": "https://github.com/jeffvli/feishin/issues"
|
||||
"jest": {
|
||||
"testURL": "http://localhost/",
|
||||
"testEnvironment": "jsdom",
|
||||
"transform": {
|
||||
"\\.(ts|tsx|js|jsx)$": "ts-jest"
|
||||
},
|
||||
"moduleNameMapper": {
|
||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/.erb/mocks/fileMock.js",
|
||||
"\\.(css|less|sass|scss)$": "identity-obj-proxy"
|
||||
},
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"jsx",
|
||||
"ts",
|
||||
"tsx",
|
||||
"json"
|
||||
],
|
||||
"moduleDirectories": [
|
||||
"node_modules",
|
||||
"release/app/node_modules"
|
||||
],
|
||||
"testPathIgnorePatterns": [
|
||||
"release/app/dist"
|
||||
],
|
||||
"setupFiles": [
|
||||
"./.erb/scripts/check-build-exists.ts"
|
||||
]
|
||||
},
|
||||
"license": "GPL-3.0",
|
||||
"author": {
|
||||
"name": "jeffvli",
|
||||
"email": "feishin@users.noreply.github.com",
|
||||
"url": "https://github.com/jeffvli/"
|
||||
},
|
||||
"main": "./out/main/index.js",
|
||||
"scripts": {
|
||||
"build": "pnpm run build:electron && pnpm run build:remote",
|
||||
"build:electron": "electron-vite build",
|
||||
"build:remote": "vite build --config remote.vite.config.ts",
|
||||
"build:web": "vite build --config web.vite.config.ts",
|
||||
"dev": "electron-vite dev",
|
||||
"dev:remote": "vite dev --config remote.vite.config.ts",
|
||||
"dev:watch": "electron-vite dev --watch",
|
||||
"i18next": "i18next -c src/i18n/i18next-parser.config.js",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
"lint": "pnpm run lint-code && pnpm run lint-styles",
|
||||
"lint-code": "eslint --max-warnings=0 --cache .",
|
||||
"lint-code:fix": "eslint --cache --fix .",
|
||||
"lint-styles": "stylelint --max-warnings=0 'src/**/*.{css,scss}'",
|
||||
"lint-styles:fix": "stylelint 'src/**/*.{css,scss}' --fix",
|
||||
"lint:fix": "pnpm run lint-code:fix && pnpm run lint-styles:fix",
|
||||
"package": "pnpm run build && electron-builder",
|
||||
"package:dev": "pnpm run build && electron-builder --dir",
|
||||
"package:linux": "pnpm run build && electron-builder --linux",
|
||||
"package:linux-arm64:pr": "pnpm run build && electron-builder --linux --arm64 --publish never",
|
||||
"package:linux:pr": "pnpm run build && electron-builder --linux --publish never",
|
||||
"package:mac": "pnpm run build && electron-builder --mac",
|
||||
"package:mac:pr": "pnpm run build && electron-builder --mac --publish never",
|
||||
"package:win": "pnpm run build && electron-builder --win",
|
||||
"package:win:pr": "pnpm run build && electron-builder --win --publish never",
|
||||
"publish:linux": "pnpm run build && electron-builder --publish always --linux",
|
||||
"publish:linux-arm64": "pnpm run build && electron-builder --publish always --linux --arm64",
|
||||
"publish:linux-arm64:beta": "pnpm run build && electron-builder --config electron-builder-beta.yml --publish always --linux --arm64",
|
||||
"publish:linux:beta": "pnpm run build && electron-builder --config electron-builder-beta.yml --publish always --linux",
|
||||
"publish:mac": "pnpm run build && electron-builder --publish always --mac",
|
||||
"publish:mac:beta": "pnpm run build && electron-builder --config electron-builder-beta.yml --publish always --mac",
|
||||
"publish:win": "pnpm run build && electron-builder --publish always --win",
|
||||
"publish:win:beta": "pnpm run build && electron-builder --config electron-builder-beta.yml --publish always --win",
|
||||
"start": "electron-vite preview",
|
||||
"typecheck": "pnpm run typecheck:node && pnpm run typecheck:web",
|
||||
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
|
||||
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
|
||||
"version": "pnpm version --no-git-tag-version",
|
||||
"postversion": "node ./scripts/update-app-stream.mjs"
|
||||
"devDependencies": {
|
||||
"@electron/rebuild": "^3.2.10",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "0.5.5",
|
||||
"@stylelint/postcss-css-in-js": "^0.38.0",
|
||||
"@teamsupercell/typings-for-css-modules-loader": "^2.5.1",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.0.0",
|
||||
"@types/electron-localshortcut": "^3.1.0",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@types/lodash": "^4.14.188",
|
||||
"@types/md5": "^2.3.2",
|
||||
"@types/node": "^17.0.23",
|
||||
"@types/react": "^18.0.25",
|
||||
"@types/react-dom": "^18.0.8",
|
||||
"@types/react-test-renderer": "^17.0.1",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@types/react-window-infinite-loader": "^1.0.6",
|
||||
"@types/styled-components": "^5.1.26",
|
||||
"@types/terser-webpack-plugin": "^5.0.4",
|
||||
"@types/webpack-bundle-analyzer": "^4.4.1",
|
||||
"@types/webpack-env": "^1.16.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.47.0",
|
||||
"@typescript-eslint/parser": "^5.47.0",
|
||||
"browserslist-config-erb": "^0.0.3",
|
||||
"chalk": "^4.1.2",
|
||||
"concurrently": "^7.1.0",
|
||||
"core-js": "^3.21.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.7.1",
|
||||
"css-minimizer-webpack-plugin": "^3.4.1",
|
||||
"detect-port": "^1.3.0",
|
||||
"electron": "^25.8.1",
|
||||
"electron-builder": "^24.6.3",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-notarize": "^1.2.1",
|
||||
"electronmon": "^2.0.2",
|
||||
"eslint": "^8.30.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-erb": "^4.0.3",
|
||||
"eslint-import-resolver-typescript": "^2.7.1",
|
||||
"eslint-import-resolver-webpack": "^0.13.2",
|
||||
"eslint-plugin-compat": "^4.0.2",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jest": "^26.1.3",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"eslint-plugin-react": "^7.29.4",
|
||||
"eslint-plugin-react-hooks": "^4.4.0",
|
||||
"eslint-plugin-sort-keys-fix": "^1.1.2",
|
||||
"eslint-plugin-typescript-sort-keys": "^2.1.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"husky": "^7.0.4",
|
||||
"i18next-parser": "^6.6.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^27.5.1",
|
||||
"lint-staged": "^12.3.7",
|
||||
"mini-css-extract-plugin": "^2.6.0",
|
||||
"postcss-scss": "^4.0.4",
|
||||
"postcss-styled-syntax": "^0.5.0",
|
||||
"postcss-syntax": "^0.36.2",
|
||||
"prettier": "^2.6.2",
|
||||
"react-refresh": "^0.12.0",
|
||||
"react-refresh-typescript": "^2.0.4",
|
||||
"react-test-renderer": "^18.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"sass": "^1.49.11",
|
||||
"sass-loader": "^12.6.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"stylelint": "^15.10.3",
|
||||
"stylelint-config-css-modules": "^4.3.0",
|
||||
"stylelint-config-recess-order": "^4.3.0",
|
||||
"stylelint-config-standard": "^34.0.0",
|
||||
"stylelint-config-standard-scss": "^4.0.0",
|
||||
"stylelint-config-styled-components": "^0.1.1",
|
||||
"terser-webpack-plugin": "^5.3.1",
|
||||
"ts-jest": "^27.1.4",
|
||||
"ts-loader": "^9.2.8",
|
||||
"ts-node": "^10.7.0",
|
||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
||||
"typescript": "^5.2.2",
|
||||
"typescript-plugin-styled-components": "^3.0.0",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.71.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "^4.8.0",
|
||||
"webpack-merge": "^5.8.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "1.7.7",
|
||||
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "^2.1.2",
|
||||
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0",
|
||||
"@electron-toolkit/preload": "^3.0.1",
|
||||
"@electron-toolkit/utils": "^4.0.0",
|
||||
"@mantine/colors-generator": "^8.3.8",
|
||||
"@mantine/core": "^8.3.8",
|
||||
"@mantine/dates": "^8.3.8",
|
||||
"@mantine/form": "^8.3.8",
|
||||
"@mantine/hooks": "^8.3.8",
|
||||
"@mantine/modals": "^8.3.8",
|
||||
"@mantine/notifications": "^8.3.8",
|
||||
"@radix-ui/react-context-menu": "^2.2.16",
|
||||
"@tanstack/react-query": "^5.90.9",
|
||||
"@tanstack/react-query-devtools": "^5.90.2",
|
||||
"@tanstack/react-query-persist-client": "^5.90.11",
|
||||
"@ts-rest/core": "^3.52.1",
|
||||
"@wavesurfer/react": "^1.0.11",
|
||||
"@xhayper/discord-rpc": "^1.3.0",
|
||||
"audiomotion-analyzer": "^4.5.1",
|
||||
"axios": "^1.13.2",
|
||||
"butterchurn": "^2.6.7",
|
||||
"butterchurn-presets": "^2.4.7",
|
||||
"cheerio": "^1.1.2",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"dayjs": "^1.11.19",
|
||||
"dompurify": "^3.3.0",
|
||||
"@ag-grid-community/client-side-row-model": "^28.2.1",
|
||||
"@ag-grid-community/core": "^28.2.1",
|
||||
"@ag-grid-community/infinite-row-model": "^28.2.1",
|
||||
"@ag-grid-community/react": "^28.2.1",
|
||||
"@ag-grid-community/styles": "^28.2.1",
|
||||
"@emotion/react": "^11.10.4",
|
||||
"@mantine/core": "^6.0.17",
|
||||
"@mantine/dates": "^6.0.17",
|
||||
"@mantine/form": "^6.0.17",
|
||||
"@mantine/hooks": "^6.0.17",
|
||||
"@mantine/modals": "^6.0.17",
|
||||
"@mantine/notifications": "^6.0.17",
|
||||
"@mantine/utils": "^6.0.17",
|
||||
"@tanstack/react-query": "^4.32.1",
|
||||
"@tanstack/react-query-devtools": "^4.32.1",
|
||||
"@tanstack/react-query-persist-client": "^4.32.1",
|
||||
"@ts-rest/core": "^3.23.0",
|
||||
"@xhayper/discord-rpc": "^1.0.24",
|
||||
"axios": "^1.4.0",
|
||||
"clsx": "^2.0.0",
|
||||
"cmdk": "^0.2.0",
|
||||
"dayjs": "^1.11.6",
|
||||
"electron-debug": "^3.2.0",
|
||||
"electron-localshortcut": "^3.2.1",
|
||||
"electron-log": "^5.4.3",
|
||||
"electron-store": "^8.2.0",
|
||||
"electron-updater": "^6.6.2",
|
||||
"fast-average-color": "^9.5.0",
|
||||
"fast-xml-parser": "^5.3.2",
|
||||
"format-duration": "^3.0.2",
|
||||
"fuse.js": "^7.1.0",
|
||||
"i18next": "^25.6.2",
|
||||
"icecast-metadata-stats": "^0.1.12",
|
||||
"idb-keyval": "^6.2.2",
|
||||
"immer": "^10.2.0",
|
||||
"electron-log": "^4.4.6",
|
||||
"electron-store": "^8.1.0",
|
||||
"electron-updater": "^4.6.5",
|
||||
"fast-average-color": "^9.3.0",
|
||||
"format-duration": "^2.0.0",
|
||||
"framer-motion": "^10.13.0",
|
||||
"fuse.js": "^6.6.2",
|
||||
"history": "^5.3.0",
|
||||
"i18next": "^21.10.0",
|
||||
"idb-keyval": "^6.2.1",
|
||||
"immer": "^9.0.21",
|
||||
"is-electron": "^2.2.2",
|
||||
"lodash": "^4.17.21",
|
||||
"md5": "^2.3.0",
|
||||
"motion": "^12.23.24",
|
||||
"mpris-service": "^2.1.2",
|
||||
"nanoid": "^3.3.11",
|
||||
"node-mpv": "github:jeffvli/Node-MPV#32b4d64395289ad710c41d481d2707a7acfc228f",
|
||||
"nuqs": "^2.7.1",
|
||||
"overlayscrollbars": "^2.11.1",
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
"qs": "^6.14.0",
|
||||
"react": "^19.1.0",
|
||||
"react-call": "^1.8.1",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-error-boundary": "^5.0.0",
|
||||
"react-i18next": "^16.3.3",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-image": "^4.1.0",
|
||||
"react-loading-skeleton": "^3.5.0",
|
||||
"react-player": "^2.16.0",
|
||||
"react-router": "^7.9.6",
|
||||
"react-split-pane": "^3.0.4",
|
||||
"react-virtualized-auto-sizer": "^1.0.26",
|
||||
"react-window": "1.8.11",
|
||||
"react-window-v2": "npm:react-window@^2.2.3",
|
||||
"semver": "^7.5.4",
|
||||
"string-to-color": "^2.2.2",
|
||||
"wavesurfer.js": "^7.11.1",
|
||||
"ws": "^8.18.2",
|
||||
"zod": "^3.22.3",
|
||||
"zustand": "^5.0.5"
|
||||
"memoize-one": "^6.0.0",
|
||||
"nanoid": "^3.3.3",
|
||||
"net": "^1.0.2",
|
||||
"node-mpv": "github:jeffvli/Node-MPV",
|
||||
"overlayscrollbars": "^2.2.1",
|
||||
"overlayscrollbars-react": "^0.5.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^3.1.4",
|
||||
"react-i18next": "^11.18.6",
|
||||
"react-icons": "^4.10.1",
|
||||
"react-player": "^2.11.0",
|
||||
"react-router": "^6.16.0",
|
||||
"react-router-dom": "^6.16.0",
|
||||
"react-simple-img": "^3.0.0",
|
||||
"react-virtualized-auto-sizer": "^1.0.17",
|
||||
"react-window": "^1.8.9",
|
||||
"react-window-infinite-loader": "^1.0.9",
|
||||
"styled-components": "^6.0.8",
|
||||
"swiper": "^9.3.1",
|
||||
"zod": "^3.21.4",
|
||||
"zustand": "^4.3.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-toolkit/eslint-config-prettier": "^3.0.0",
|
||||
"@electron-toolkit/eslint-config-ts": "^3.0.0",
|
||||
"@electron-toolkit/tsconfig": "^2.0.0",
|
||||
"@types/electron-localshortcut": "^3.1.0",
|
||||
"@types/lodash": "^4.17.18",
|
||||
"@types/md5": "^2.3.5",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/react": "^19.2.5",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/react-window": "^1.8.8",
|
||||
"@types/source-map-support": "^0.5.10",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"concurrently": "^9.2.1",
|
||||
"cross-env": "^10.1.0",
|
||||
"electron": "^39.2.7",
|
||||
"electron-builder": "^26.0.12",
|
||||
"electron-devtools-installer": "^4.0.0",
|
||||
"electron-vite": "^4.0.1",
|
||||
"eslint": "^9.24.0",
|
||||
"eslint-plugin-perfectionist": "^4.13.0",
|
||||
"eslint-plugin-prettier": "^5.4.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.24",
|
||||
"i18next-parser": "^9.3.0",
|
||||
"postcss-preset-mantine": "^1.18.0",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-packagejson": "^2.5.19",
|
||||
"stylelint": "^16.25.0",
|
||||
"stylelint-config-css-modules": "^4.5.1",
|
||||
"stylelint-config-recess-order": "^7.4.0",
|
||||
"stylelint-config-standard": "^39.0.1",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^7.2.2",
|
||||
"vite-plugin-conditional-import": "^0.1.7",
|
||||
"vite-plugin-dynamic-import": "^1.6.0",
|
||||
"vite-plugin-ejs": "^1.7.0",
|
||||
"vite-plugin-pwa": "^1.1.0"
|
||||
"resolutions": {
|
||||
"styled-components": "^6"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"electron",
|
||||
"esbuild"
|
||||
"devEngines": {
|
||||
"node": ">=14.x",
|
||||
"npm": ">=7.x"
|
||||
},
|
||||
"browserslist": [],
|
||||
"electronmon": {
|
||||
"patterns": [
|
||||
"!server",
|
||||
"!src/renderer"
|
||||
]
|
||||
},
|
||||
"productName": "feishin"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
-12039
File diff suppressed because it is too large
Load Diff
@@ -1,16 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
'postcss-preset-mantine': {},
|
||||
'postcss-simple-vars': {
|
||||
variables: {
|
||||
'mantine-breakpoint-2xl': '120em',
|
||||
'mantine-breakpoint-3xl': '160em',
|
||||
'mantine-breakpoint-lg': '75em',
|
||||
'mantine-breakpoint-md': '62em',
|
||||
'mantine-breakpoint-sm': '48em',
|
||||
'mantine-breakpoint-xl': '88em',
|
||||
'mantine-breakpoint-xs': '36em',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
Generated
+2331
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "feishin",
|
||||
"version": "0.4.1",
|
||||
"description": "",
|
||||
"main": "./dist/main/main.js",
|
||||
"author": {
|
||||
"name": "jeffvli",
|
||||
"url": "https://github.com/jeffvli/"
|
||||
},
|
||||
"scripts": {
|
||||
"electron-rebuild": "node -r ts-node/register ../../.erb/scripts/electron-rebuild.js",
|
||||
"link-modules": "node -r ts-node/register ../../.erb/scripts/link-modules.ts",
|
||||
"postinstall": "npm run electron-rebuild && npm run link-modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"mpris-service": "^2.1.2",
|
||||
"ws": "^8.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "25.3.0"
|
||||
},
|
||||
"license": "GPL-3.0"
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import react from '@vitejs/plugin-react';
|
||||
import path from 'path';
|
||||
import { defineConfig, normalizePath } from 'vite';
|
||||
import { ViteEjsPlugin } from 'vite-plugin-ejs';
|
||||
|
||||
import { version } from './package.json';
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
cssMinify: 'esbuild',
|
||||
emptyOutDir: true,
|
||||
minify: 'esbuild',
|
||||
outDir: path.resolve(__dirname, './out/remote'),
|
||||
rollupOptions: {
|
||||
input: {
|
||||
favicon: normalizePath(path.resolve(__dirname, './assets/icons/favicon.ico')),
|
||||
index: normalizePath(path.resolve(__dirname, './src/remote/index.html')),
|
||||
manifest: normalizePath(path.resolve(__dirname, './src/remote/manifest.json')),
|
||||
remote: normalizePath(path.resolve(__dirname, './src/remote/index.tsx')),
|
||||
worker: normalizePath(path.resolve(__dirname, './src/remote/service-worker.ts')),
|
||||
},
|
||||
output: {
|
||||
assetFileNames: '[name].[ext]',
|
||||
chunkFileNames: '[name].js',
|
||||
entryFileNames: '[name].js',
|
||||
},
|
||||
},
|
||||
sourcemap: true,
|
||||
},
|
||||
css: {
|
||||
modules: {
|
||||
generateScopedName: 'fs-[name]-[local]',
|
||||
localsConvention: 'camelCase',
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
react(),
|
||||
ViteEjsPlugin({
|
||||
prod: process.env.NODE_ENV === 'production',
|
||||
root: normalizePath(path.resolve(__dirname, './src/remote')),
|
||||
version,
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'/@/i18n': path.resolve(__dirname, './src/i18n'),
|
||||
'/@/remote': path.resolve(__dirname, './src/remote'),
|
||||
'/@/renderer': path.resolve(__dirname, './src/renderer'),
|
||||
'/@/shared': path.resolve(__dirname, './src/shared'),
|
||||
},
|
||||
},
|
||||
root: path.resolve(__dirname, './src/remote'),
|
||||
});
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB |
@@ -1,35 +0,0 @@
|
||||
import { XMLBuilder, XMLParser } from 'fast-xml-parser';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length > 3) {
|
||||
console.error('Usage: node update-app-stream.js [package-file] [date] [metainfo-file]');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const packageFile = args[0] || path.resolve(process.cwd(), 'package.json');
|
||||
|
||||
const packageContent = fs.readFileSync(packageFile, 'utf8');
|
||||
const packageJson = JSON.parse(packageContent);
|
||||
const version = packageJson.version;
|
||||
|
||||
const time = Math.floor((Date.parse(args[1]) || Date.now()) / 1000);
|
||||
const metainfoFile = args[2] || path.resolve(process.cwd(), 'org.jeffvli.feishin.metainfo.xml');
|
||||
|
||||
const parser = new XMLParser({ ignoreAttributes: false });
|
||||
const metainfoContent = fs.readFileSync(metainfoFile, 'utf8');
|
||||
const metainfo = parser.parse(metainfoContent);
|
||||
|
||||
if (!metainfo.component.releases.release.find((release) => release['@_version'] === version)) {
|
||||
metainfo.component.releases.release.unshift({
|
||||
'@_date': new Date(time * 1000).toISOString().split('T')[0],
|
||||
'@_type': version.includes('-') ? 'development' : 'stable',
|
||||
'@_version': version,
|
||||
});
|
||||
}
|
||||
|
||||
const builder = new XMLBuilder({ format: true, ignoreAttributes: false, indentBy: ' ' });
|
||||
fs.writeFileSync(metainfoFile, builder.build(metainfo), 'utf8');
|
||||
|
||||
console.log(`Updated ${metainfoFile} with version ${version}`);
|
||||
@@ -1 +0,0 @@
|
||||
"use strict";window.SERVER_URL="${SERVER_URL}";window.SERVER_NAME="${SERVER_NAME}";window.SERVER_TYPE="${SERVER_TYPE}";window.SERVER_LOCK=${SERVER_LOCK};window.ANALYTICS_DISABLED="${ANALYTICS_DISABLED}";
|
||||
@@ -0,0 +1,9 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { render } from '@testing-library/react';
|
||||
import { App } from '../renderer/app';
|
||||
|
||||
describe('App', () => {
|
||||
it('should render', () => {
|
||||
expect(render(<App />)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
+9
-184
@@ -1,65 +1,10 @@
|
||||
import { PostProcessorModule, TOptions } from 'i18next';
|
||||
import { PostProcessorModule } from 'i18next';
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
import ar from './locales/ar.json';
|
||||
import ca from './locales/ca.json';
|
||||
import cs from './locales/cs.json';
|
||||
import de from './locales/de.json';
|
||||
import en from './locales/en.json';
|
||||
import es from './locales/es.json';
|
||||
import eu from './locales/eu.json';
|
||||
import fa from './locales/fa.json';
|
||||
import fi from './locales/fi.json';
|
||||
import fr from './locales/fr.json';
|
||||
import hu from './locales/hu.json';
|
||||
import id from './locales/id.json';
|
||||
import it from './locales/it.json';
|
||||
import ja from './locales/ja.json';
|
||||
import ko from './locales/ko.json';
|
||||
import nbNO from './locales/nb-NO.json';
|
||||
import nl from './locales/nl.json';
|
||||
import pl from './locales/pl.json';
|
||||
import ptBr from './locales/pt-BR.json';
|
||||
import pt from './locales/pt.json';
|
||||
import ru from './locales/ru.json';
|
||||
import sl from './locales/sl.json';
|
||||
import sr from './locales/sr.json';
|
||||
import sv from './locales/sv.json';
|
||||
import ta from './locales/ta.json';
|
||||
import tr from './locales/tr.json';
|
||||
import zhHans from './locales/zh-Hans.json';
|
||||
import zhHant from './locales/zh-Hant.json';
|
||||
|
||||
const resources = {
|
||||
ar: { translation: ar },
|
||||
ca: { translation: ca },
|
||||
cs: { translation: cs },
|
||||
de: { translation: de },
|
||||
en: { translation: en },
|
||||
es: { translation: es },
|
||||
eu: { translation: eu },
|
||||
fa: { translation: fa },
|
||||
fi: { translation: fi },
|
||||
fr: { translation: fr },
|
||||
hu: { translation: hu },
|
||||
id: { translation: id },
|
||||
it: { translation: it },
|
||||
ja: { translation: ja },
|
||||
ko: { translation: ko },
|
||||
'nb-NO': { translation: nbNO },
|
||||
nl: { translation: nl },
|
||||
pl: { translation: pl },
|
||||
pt: { translation: pt },
|
||||
'pt-BR': { translation: ptBr },
|
||||
ru: { translation: ru },
|
||||
sl: { translation: sl },
|
||||
sr: { translation: sr },
|
||||
sv: { translation: sv },
|
||||
ta: { translation: ta },
|
||||
tr: { translation: tr },
|
||||
'zh-Hans': { translation: zhHans },
|
||||
'zh-Hant': { translation: zhHant },
|
||||
};
|
||||
|
||||
export const languages = [
|
||||
@@ -67,166 +12,46 @@ export const languages = [
|
||||
label: 'English',
|
||||
value: 'en',
|
||||
},
|
||||
{
|
||||
label: 'العربية',
|
||||
value: 'ar',
|
||||
},
|
||||
{
|
||||
label: 'Català',
|
||||
value: 'ca',
|
||||
},
|
||||
{
|
||||
label: 'Čeština',
|
||||
value: 'cs',
|
||||
},
|
||||
{
|
||||
label: 'Deutsch',
|
||||
value: 'de',
|
||||
},
|
||||
{
|
||||
label: 'Español',
|
||||
value: 'es',
|
||||
},
|
||||
{
|
||||
label: 'Basque',
|
||||
value: 'eu',
|
||||
},
|
||||
{
|
||||
label: 'Français',
|
||||
value: 'fr',
|
||||
},
|
||||
{
|
||||
label: 'Bahasa Indonesia',
|
||||
value: 'id',
|
||||
},
|
||||
{
|
||||
label: 'Suomeksi',
|
||||
value: 'fi',
|
||||
},
|
||||
{
|
||||
label: 'Magyar',
|
||||
value: 'hu',
|
||||
},
|
||||
{
|
||||
label: 'Italiano',
|
||||
value: 'it',
|
||||
},
|
||||
{
|
||||
label: '日本語',
|
||||
value: 'ja',
|
||||
},
|
||||
{
|
||||
label: '한국어',
|
||||
value: 'ko',
|
||||
},
|
||||
{
|
||||
label: 'Nederlands',
|
||||
value: 'nl',
|
||||
},
|
||||
{
|
||||
label: 'Norsk (Bokmål)',
|
||||
value: 'nb-NO',
|
||||
},
|
||||
{
|
||||
label: 'فارسی',
|
||||
value: 'fa',
|
||||
},
|
||||
{
|
||||
label: 'Português',
|
||||
value: 'pt',
|
||||
},
|
||||
{
|
||||
label: 'Português (Brasil)',
|
||||
value: 'pt-BR',
|
||||
},
|
||||
{
|
||||
label: 'Polski',
|
||||
value: 'pl',
|
||||
},
|
||||
{
|
||||
label: 'Русский',
|
||||
value: 'ru',
|
||||
},
|
||||
{
|
||||
label: 'Slovenščina',
|
||||
value: 'sl',
|
||||
},
|
||||
{
|
||||
label: 'Srpski',
|
||||
value: 'sr',
|
||||
},
|
||||
{
|
||||
label: 'Svenska',
|
||||
value: 'sv',
|
||||
},
|
||||
{
|
||||
label: 'Tamil',
|
||||
value: 'ta',
|
||||
},
|
||||
{
|
||||
label: 'Türkçe',
|
||||
value: 'tr',
|
||||
},
|
||||
{
|
||||
label: '简体中文',
|
||||
value: 'zh-Hans',
|
||||
},
|
||||
{
|
||||
label: '繁體中文',
|
||||
value: 'zh-Hant',
|
||||
},
|
||||
];
|
||||
|
||||
const lowerCasePostProcessor: PostProcessorModule = {
|
||||
type: 'postProcessor',
|
||||
name: 'lowerCase',
|
||||
process: (value: string) => {
|
||||
return value.toLocaleLowerCase();
|
||||
},
|
||||
type: 'postProcessor',
|
||||
};
|
||||
|
||||
const upperCasePostProcessor: PostProcessorModule = {
|
||||
type: 'postProcessor',
|
||||
name: 'upperCase',
|
||||
process: (value: string) => {
|
||||
return value.toLocaleUpperCase();
|
||||
},
|
||||
type: 'postProcessor',
|
||||
};
|
||||
|
||||
const titleCasePostProcessor: PostProcessorModule = {
|
||||
type: 'postProcessor',
|
||||
name: 'titleCase',
|
||||
process: (value: string) => {
|
||||
return value.replace(/\S\S*/g, (txt) => {
|
||||
return txt.charAt(0).toLocaleUpperCase() + txt.slice(1).toLowerCase();
|
||||
return value.replace(/\w\S*/g, (txt) => {
|
||||
return txt.charAt(0).toUpperCase() + txt.slice(1).toLowerCase();
|
||||
});
|
||||
},
|
||||
type: 'postProcessor',
|
||||
};
|
||||
|
||||
const ignoreSentenceCaseLanguages = ['de'];
|
||||
|
||||
const sentenceCasePostProcessor: PostProcessorModule = {
|
||||
type: 'postProcessor',
|
||||
name: 'sentenceCase',
|
||||
process: (
|
||||
value: string,
|
||||
_key: string,
|
||||
_options: TOptions<Record<string, string>>,
|
||||
translator: any,
|
||||
) => {
|
||||
process: (value: string) => {
|
||||
const sentences = value.split('. ');
|
||||
|
||||
return sentences
|
||||
.map((sentence) => {
|
||||
return (
|
||||
sentence.charAt(0).toLocaleUpperCase() +
|
||||
(!ignoreSentenceCaseLanguages.includes(translator.language)
|
||||
? sentence.slice(1).toLocaleLowerCase()
|
||||
: sentence.slice(1))
|
||||
);
|
||||
return sentence.charAt(0).toUpperCase() + sentence.slice(1).toLocaleLowerCase();
|
||||
})
|
||||
.join('. ');
|
||||
},
|
||||
type: 'postProcessor',
|
||||
};
|
||||
i18n.use(lowerCasePostProcessor)
|
||||
.use(upperCasePostProcessor)
|
||||
|
||||
@@ -5,9 +5,7 @@ module.exports = {
|
||||
createOldCatalogs: true,
|
||||
customValueTemplate: null,
|
||||
defaultNamespace: 'translation',
|
||||
defaultValue: function (locale, namespace, key) {
|
||||
return key;
|
||||
},
|
||||
defaultValue: '',
|
||||
failOnUpdate: false,
|
||||
failOnWarnings: false,
|
||||
i18nextOptions: null,
|
||||
@@ -39,6 +37,8 @@ module.exports = {
|
||||
output: 'src/renderer/i18n/locales/$LOCALE.json',
|
||||
pluralSeparator: '_',
|
||||
resetDefaultValueLocale: 'en',
|
||||
skipDefaultValues: false,
|
||||
sort: true,
|
||||
useKeysAsDefaultValue: true,
|
||||
verbose: false,
|
||||
};
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
{
|
||||
"action": {
|
||||
"addToFavorites": "إضافة الى $t(entity.favorite_other)",
|
||||
"addToPlaylist": "إضافة الى $t(entity.playlist_one)",
|
||||
"clearQueue": "مسح قائمة الإنتظار",
|
||||
"createPlaylist": "إنشاء $t(entity.playlist_one)",
|
||||
"deletePlaylist": "حذف $t(entity.playlist_one)",
|
||||
"deselectAll": "إلغاء تحديد الكل",
|
||||
"editPlaylist": "تعديل $t(entity.playlist_one)",
|
||||
"goToPage": "اذهب الى صفحة",
|
||||
"moveToNext": "الذهاب الى التالي",
|
||||
"moveToBottom": "الذهاب الى الأسفل",
|
||||
"moveToTop": "الذهاب الى الأعلى",
|
||||
"refresh": "$t(common.refresh)",
|
||||
"removeFromFavorites": "حذف من $t(entity.favorite_other)",
|
||||
"removeFromPlaylist": "حذف من $t(entity.playlist_one)",
|
||||
"removeFromQueue": "حذف من قائمة الإنتظار",
|
||||
"setRating": "تحديد التقييم",
|
||||
"toggleSmartPlaylistEditor": "تشغيل / إطفاء وضع التعديل لـ $t(entity.smartPlaylist)",
|
||||
"viewPlaylists": "إظهار $t(entity.playlist_other)",
|
||||
"openIn": {
|
||||
"lastfm": "فتح في Last.fm",
|
||||
"musicbrainz": "فتح في MusicBrainz"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"action_zero": "عملية",
|
||||
"action_one": "عملية",
|
||||
"action_two": "عمليتين",
|
||||
"action_few": "عمليات",
|
||||
"action_many": "عمليات",
|
||||
"action_other": "عمليات",
|
||||
"add": "إضافة",
|
||||
"additionalParticipants": "مشاركين إضافيين",
|
||||
"newVersion": "تم تثبيت تحديث جديد {{version}}",
|
||||
"viewReleaseNotes": "عرض معلومات الإصدار",
|
||||
"albumGain": "مستوى صوت الألبوم",
|
||||
"albumPeak": "اعلى مستوى للألبوم",
|
||||
"areYouSure": "هل أنت متأكد؟",
|
||||
"ascending": "تصاعدي",
|
||||
"backward": "خلف",
|
||||
"biography": "سيرة",
|
||||
"bitDepth": "عمق البت",
|
||||
"bitrate": "معدل البت (البت ريت)",
|
||||
"bpm": "نبضة في الدقيقة",
|
||||
"cancel": "إلغاء",
|
||||
"center": "منتصف",
|
||||
"channel_zero": "قناة",
|
||||
"channel_one": "قناة",
|
||||
"channel_two": "قناتين",
|
||||
"channel_few": "قنوات",
|
||||
"channel_many": "قنوات",
|
||||
"channel_other": "قنوات",
|
||||
"clear": "مسح",
|
||||
"close": "إغلاق",
|
||||
"codec": "كوديك",
|
||||
"collapse": "طي",
|
||||
"comingSoon": "قريبًا…",
|
||||
"configure": "تعديل",
|
||||
"confirm": "تأكيد",
|
||||
"create": "إنشاء",
|
||||
"currentSong": "$t(entity.track_one) الحالي",
|
||||
"decrease": "تنقيص",
|
||||
"delete": "حذف",
|
||||
"descending": "تنازلي",
|
||||
"description": "وصف",
|
||||
"disable": "تعطيل",
|
||||
"disc": "قرص",
|
||||
"dismiss": "إخفاء",
|
||||
"duration": "مدة",
|
||||
"edit": "تعديل",
|
||||
"enable": "تفعيل",
|
||||
"expand": "توسيع",
|
||||
"favorite": "مفضلة",
|
||||
"filter_zero": "فلتر",
|
||||
"filter_one": "فلتر",
|
||||
"filter_two": "فلاتر",
|
||||
"filter_few": "فلاتر",
|
||||
"filter_many": "فلاتر",
|
||||
"filter_other": "فلاتر",
|
||||
"filters": "فلاتر",
|
||||
"forceRestartRequired": "اعد التشغيل لتطبيق التعديلات... اغلق التنبية لإعادة التشغيل",
|
||||
"forward": "امام",
|
||||
"gap": "فجوة",
|
||||
"home": "الرئيسية",
|
||||
"increase": "زيادة",
|
||||
"left": "يسار",
|
||||
"limit": "حد",
|
||||
"manage": "إدارة",
|
||||
"maximize": "تكبير",
|
||||
"menu": "القائمة",
|
||||
"minimize": "تصغير",
|
||||
"modified": "تم تعديله",
|
||||
"mbid": "معرف MusicBrainz",
|
||||
"name": "إسم",
|
||||
"no": "لا",
|
||||
"none": "لا شي",
|
||||
"noResultsFromQuery": "لا توجد نتائج",
|
||||
"note": "ملاحظة",
|
||||
"ok": "نعم",
|
||||
"owner": "المالك",
|
||||
"path": "المسار",
|
||||
"playerMustBePaused": "يجب إيقاف المشغل",
|
||||
"preview": "معاينة",
|
||||
"previousSong": "$t(entity.track_one) السابق",
|
||||
"quit": "خروج",
|
||||
"random": "عشوائي",
|
||||
"rating": "التقييم",
|
||||
"refresh": "تحديث",
|
||||
"reload": "تحديث",
|
||||
"reset": "إعادة تعيين",
|
||||
"resetToDefault": "إعادة تعيين الى الافتراضي",
|
||||
"restartRequired": "يجب إعادة التشغيل",
|
||||
"right": "يمين",
|
||||
"sampleRate": "معدل العينة (sample rate)",
|
||||
"save": "حفظ",
|
||||
"saveAndReplace": "حفظ واستبدال",
|
||||
"saveAs": "حفظ بإسم",
|
||||
"search": "بحث",
|
||||
"setting": "إعداد",
|
||||
"share": "نشر",
|
||||
"size": "حجم",
|
||||
"sortOrder": "الترتيب",
|
||||
"tags": "العلامات",
|
||||
"title": "العنوان",
|
||||
"trackNumber": "رقم المسار",
|
||||
"trackGain": "مستوى صوت المسار",
|
||||
"trackPeak": "اعلى مستوى للمسار",
|
||||
"translation": "الترجمة",
|
||||
"unknown": "غير معروف",
|
||||
"version": "الإصدار",
|
||||
"year": "السنة",
|
||||
"yes": "نعم"
|
||||
},
|
||||
"entity": {
|
||||
"album_zero": "الالبوم",
|
||||
"album_one": "الالبوم",
|
||||
"album_two": "الالبومين",
|
||||
"album_few": "الالبومات",
|
||||
"album_many": "الالبومات",
|
||||
"album_other": "الالبومات",
|
||||
"albumArtist_zero": "فنان الالبوم",
|
||||
"albumArtist_one": "فنان الالبوم",
|
||||
"albumArtist_two": "فنان الالبومين",
|
||||
"albumArtist_few": "فنان الالبومات",
|
||||
"albumArtist_many": "فنان الالبومات",
|
||||
"albumArtist_other": "فنان الالبومات"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+81
-738
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,910 +0,0 @@
|
||||
{
|
||||
"action": {
|
||||
"deselectAll": "deshautatu dena",
|
||||
"editPlaylist": "editatu $t(entity.playlist_one)",
|
||||
"goToPage": "joan orrira",
|
||||
"moveToNext": "mugitu hurrengora",
|
||||
"moveToBottom": "mugitu behera",
|
||||
"moveToTop": "mugitu gora",
|
||||
"refresh": "$t(common.refresh)",
|
||||
"removeFromFavorites": "kendu $t(entity.favorite_other)-(e)tik",
|
||||
"removeFromPlaylist": "kendu $t(entity.playlist_one)-(e)tik",
|
||||
"removeFromQueue": "kendu ilaratik",
|
||||
"setRating": "ezarri balorazioa",
|
||||
"toggleSmartPlaylistEditor": "txandakatu $t(entity.smartPlaylist) editorea",
|
||||
"viewPlaylists": "ikusi $t(entity.playlist_other)",
|
||||
"openIn": {
|
||||
"lastfm": "Ireki Last.fm-n",
|
||||
"musicbrainz": "Ireki MusicBrainz-en"
|
||||
},
|
||||
"clearQueue": "garbitu ilara",
|
||||
"createPlaylist": "sortu $t(entity.playlist_one)",
|
||||
"deletePlaylist": "ezabatu $t(entity.playlist_one)",
|
||||
"addToFavorites": "gehitu $t(entity.favorite_other)-(e)ra",
|
||||
"addToPlaylist": "gehitu $t(entity.playlist_one)-(e)ra",
|
||||
"createRadioStation": "sortu $t(entity.radioStation_one)",
|
||||
"deleteRadioStation": "ezabatu $t(entity.radioStation_one)",
|
||||
"viewMore": "ikusi gehiago",
|
||||
"shuffle": "nahastu"
|
||||
},
|
||||
"common": {
|
||||
"add": "gehitu",
|
||||
"additionalParticipants": "partaide gehigarriak",
|
||||
"newVersion": "bertsio berri bat instalatu da ({{version}})",
|
||||
"viewReleaseNotes": "ikusi argitalpen oharrak",
|
||||
"areYouSure": "ziur zaude?",
|
||||
"ascending": "goranzkoa",
|
||||
"backward": "atzeraka",
|
||||
"biography": "biografia",
|
||||
"close": "itxi",
|
||||
"codec": "kodeka",
|
||||
"collapse": "tolestu",
|
||||
"configure": "konfiguratu",
|
||||
"confirm": "berretsi",
|
||||
"create": "sortu",
|
||||
"currentSong": "uneko $t(entity.track_one)",
|
||||
"decrease": "gutxitu",
|
||||
"delete": "ezabatu",
|
||||
"descending": "beheranzkoa",
|
||||
"description": "deskripzioa",
|
||||
"disable": "desgaitu",
|
||||
"disc": "diskoa",
|
||||
"dismiss": "baztertu",
|
||||
"duration": "iraupena",
|
||||
"edit": "editatu",
|
||||
"enable": "gaitu",
|
||||
"expand": "zabaldu",
|
||||
"favorite": "gogokoa",
|
||||
"filter_one": "iragazkia",
|
||||
"filter_other": "iragazkiak",
|
||||
"filters": "iragazkiak",
|
||||
"forceRestartRequired": "berreabiarazi aldaketak aplikatzeko... itxi notifikazioa berreabiarazteko",
|
||||
"setting": "ezarpena",
|
||||
"share": "partekatu",
|
||||
"action_one": "ekintza",
|
||||
"action_other": "ekintzak",
|
||||
"unknown": "ezezaguna",
|
||||
"version": "bertsioa",
|
||||
"year": "urtea",
|
||||
"yes": "bai",
|
||||
"bitrate": "bit-emaria",
|
||||
"bpm": "bpm",
|
||||
"cancel": "utzi",
|
||||
"center": "lerrokatu",
|
||||
"channel_one": "kanala",
|
||||
"channel_other": "kanalak",
|
||||
"clear": "garbitu",
|
||||
"forward": "aurrerantz",
|
||||
"home": "etxea",
|
||||
"increase": "handitu",
|
||||
"left": "ezkerra",
|
||||
"limit": "mugatu",
|
||||
"manage": "kudeatu",
|
||||
"maximize": "maximizatu",
|
||||
"menu": "menua",
|
||||
"minimize": "minimizatu",
|
||||
"modified": "aldatuta",
|
||||
"mbid": "MusicBrainz IDa",
|
||||
"name": "izena",
|
||||
"no": "ez",
|
||||
"none": "bat ere ez",
|
||||
"noResultsFromQuery": "kontsultak ez du emaitzik itzuli",
|
||||
"note": "oharra",
|
||||
"ok": "ados",
|
||||
"owner": "jabea",
|
||||
"path": "bidea",
|
||||
"playerMustBePaused": "erreproduzitzailea pausatuta egon behar da",
|
||||
"preview": "aurrebista",
|
||||
"previousSong": "aurreko $t(entity.track_one)",
|
||||
"quit": "irten",
|
||||
"random": "ausazkoa",
|
||||
"rating": "balorazioa",
|
||||
"refresh": "freskatu",
|
||||
"reload": "birkargatu",
|
||||
"reset": "berrerazi",
|
||||
"right": "eskuina",
|
||||
"save": "gorde",
|
||||
"search": "bilatu",
|
||||
"size": "tamaina",
|
||||
"sortOrder": "ordena",
|
||||
"tags": "etiketak",
|
||||
"title": "tituloa",
|
||||
"trackNumber": "pista",
|
||||
"translation": "itzulpena",
|
||||
"albumGain": "album irabazpena",
|
||||
"bitDepth": "bit-sakonera",
|
||||
"resetToDefault": "lehenetsitako egoerara berrezarri",
|
||||
"restartRequired": "berrabiarazi behar da",
|
||||
"sampleRate": "laginketa-tasa",
|
||||
"saveAndReplace": "gorde eta ordezkatu",
|
||||
"saveAs": "gorde honela",
|
||||
"trackGain": "pista irabazpena",
|
||||
"comingSoon": "laster…",
|
||||
"trackPeak": "pistaren gailurra",
|
||||
"albumPeak": "albumaren gailurra",
|
||||
"gap": "hutsunea",
|
||||
"explicitStatus": "egoera esplizitua",
|
||||
"explicit": "esplizitua",
|
||||
"clean": "garbia",
|
||||
"private": "pribatua",
|
||||
"public": "publikoa",
|
||||
"releaseType": "argitalpen mota",
|
||||
"countSelected": "{{count}} hautatuta",
|
||||
"view": "ikuspegia",
|
||||
"externalLinks": "kanpoko estekak",
|
||||
"faster": "azkarrago",
|
||||
"noFilters": "ez dago iragazkirik konfiguratuta",
|
||||
"retry": "saiatu berriro",
|
||||
"slower": "motelago",
|
||||
"itemsMore": "{{count}} gehiago",
|
||||
"sort": "ordenatu"
|
||||
},
|
||||
"player": {
|
||||
"repeat": "errepikatu",
|
||||
"play": "erreproduzitu",
|
||||
"previous": "aurrekoa",
|
||||
"pause": "pausatu",
|
||||
"favorite": "gogokoa",
|
||||
"mute": "isilarazi",
|
||||
"muted": "isilduta",
|
||||
"next": "hurrengoa",
|
||||
"skip": "saltatu",
|
||||
"stop": "gelditu",
|
||||
"unfavorite": "kendu gogokoetatik",
|
||||
"addLast": "gehitu azkena",
|
||||
"addNext": "gehitu hurrengoa",
|
||||
"playbackFetchInProgress": "abestiak kargatzen…",
|
||||
"playbackSpeed": "erreprodukzio-abiadura",
|
||||
"playRandom": "erreproduzitu auzaz",
|
||||
"playbackFetchNoResults": "ez da abestirik aurkitu",
|
||||
"playSimilarSongs": "erreproduzitu antzeko abestiak",
|
||||
"queue_clear": "garbitu ilara",
|
||||
"queue_moveToBottom": "gora eraman hautatutakoak",
|
||||
"queue_moveToTop": "behera eraman hautatutakoak",
|
||||
"queue_remove": "kendu hautatutakoak",
|
||||
"repeat_all": "errepikatu dena",
|
||||
"repeat_off": "errepikapena desgaituta",
|
||||
"shuffle": "erreproduzitu (ausaz)",
|
||||
"shuffle_off": "auza desgaituta",
|
||||
"skip_back": "saltatu atzeraka",
|
||||
"skip_forward": "saltatu aurreraka",
|
||||
"toggleFullscreenPlayer": "txandakatu pantaila osoko erreproduzitzailea",
|
||||
"viewQueue": "ikusi ilara",
|
||||
"playbackFetchCancel": "honek denbora pixka bat behar du... itxi jakinarazpena bertan behera uzteko",
|
||||
"lyrics": "letrak",
|
||||
"queueType": "ilara mota",
|
||||
"queueType_default": "lehenetsia",
|
||||
"queueType_priority": "lehentasuna",
|
||||
"restoreQueueFromServer": "berrezarri ilara zerbitzaritik",
|
||||
"saveQueueToServer": "gorde ilara zerbitzarira"
|
||||
},
|
||||
"table": {
|
||||
"config": {
|
||||
"view": {
|
||||
"table": "taula",
|
||||
"list": "zerrenda",
|
||||
"grid": "sareta"
|
||||
},
|
||||
"general": {
|
||||
"gap": "$t(common.gap)",
|
||||
"size": "$t(common.size)",
|
||||
"tableColumns": "taula zutabeak",
|
||||
"itemSize": "elementuaren tamaina (px)",
|
||||
"followCurrentSong": "jarraitu uneko abestia",
|
||||
"size_default": "lehenetsia"
|
||||
},
|
||||
"label": {
|
||||
"actions": "$t(common.action_other)",
|
||||
"album": "$t(entity.album_one)",
|
||||
"albumArtist": "$t(entity.albumArtist_one)",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"biography": "$t(common.biography)",
|
||||
"bitrate": "$t(common.bitrate)",
|
||||
"bpm": "$t(common.bpm)",
|
||||
"channels": "$t(common.channel_other)",
|
||||
"codec": "$t(common.codec)",
|
||||
"duration": "$t(common.duration)",
|
||||
"favorite": "$t(common.favorite)",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"note": "$t(common.note)",
|
||||
"owner": "$t(common.owner)",
|
||||
"path": "$t(common.path)",
|
||||
"rating": "$t(common.rating)",
|
||||
"size": "$t(common.size)",
|
||||
"songCount": "$t(entity.track_other)",
|
||||
"title": "$t(common.title)",
|
||||
"year": "$t(common.year)",
|
||||
"titleCombined": "$t(common.title) (batuta)",
|
||||
"releaseDate": "argitalpen data",
|
||||
"playCount": "erreprodukzio kopurua",
|
||||
"lastPlayed": "azken aldiz entzunda",
|
||||
"discNumber": "disko zenbakia",
|
||||
"dateAdded": "gehitze data",
|
||||
"albumCount": "$t(entity.album_other)",
|
||||
"image": "irudia",
|
||||
"bitDepth": "$t(common.bitDepth)",
|
||||
"sampleRate": "$t(common.sampleRate)"
|
||||
}
|
||||
},
|
||||
"column": {
|
||||
"album": "albuma",
|
||||
"albumCount": "$t(entity.album_other)",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"biography": "biografia",
|
||||
"bitrate": "bit-emaria",
|
||||
"channels": "$t(common.channel_other)",
|
||||
"codec": "$t(common.codec)",
|
||||
"discNumber": "diskoa",
|
||||
"favorite": "gogokoa",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"path": "bidea",
|
||||
"rating": "balorazioa",
|
||||
"releaseYear": "urtea",
|
||||
"size": "$t(common.size)",
|
||||
"songCount": "$t(entity.track_other)",
|
||||
"title": "tituloa",
|
||||
"trackNumber": "pista",
|
||||
"bpm": "bpm",
|
||||
"comment": "iruzkina",
|
||||
"playCount": "erreprodukzioak",
|
||||
"releaseDate": "argitalpen data",
|
||||
"lastPlayed": "azken aldiz entzundakoa",
|
||||
"dateAdded": "gehitutako data",
|
||||
"albumArtist": "albumeko artista",
|
||||
"owner": "jabea",
|
||||
"bitDepth": "$t(common.bitDepth)",
|
||||
"sampleRate": "$t(common.sampleRate)"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"album_one": "albuma",
|
||||
"album_other": "albumak",
|
||||
"albumArtist_one": "albumaren artista",
|
||||
"albumArtist_other": "albumaren artistak",
|
||||
"albumArtistCount_one": "album artista {{count}}",
|
||||
"albumArtistCount_other": "{{count}} album artista",
|
||||
"albumWithCount_one": "album {{count}}",
|
||||
"albumWithCount_other": "{{count}} album",
|
||||
"artist_one": "artista",
|
||||
"artist_other": "artistak",
|
||||
"artistWithCount_one": "artista {{count}}",
|
||||
"artistWithCount_other": "{{count}} artista",
|
||||
"favorite_one": "gogokoa",
|
||||
"favorite_other": "gogokoak",
|
||||
"folder_one": "karpeta",
|
||||
"folder_other": "karpetak",
|
||||
"folderWithCount_one": "karpeta {{count}}",
|
||||
"folderWithCount_other": "{{count}} karpeta",
|
||||
"genre_one": "generoa",
|
||||
"genre_other": "generoak",
|
||||
"genreWithCount_one": "genero {{count}}generoa",
|
||||
"genreWithCount_other": "{{count}} genero",
|
||||
"playlist_one": "erreprodukzio-zerrenda",
|
||||
"playlist_other": "erreprodukzio-zerrendak",
|
||||
"play_one": "erreprodukzio {{count}}",
|
||||
"play_other": "{{count}} erreprodukzio",
|
||||
"playlistWithCount_one": "erreprodukzio-zerrenda {{count}}",
|
||||
"playlistWithCount_other": "{{count}} erreprodukzio-zerrenda",
|
||||
"smartPlaylist": "$t(entity.playlist_one) adimentsua",
|
||||
"track_one": "pista",
|
||||
"track_other": "pistak",
|
||||
"song_one": "abestia",
|
||||
"song_other": "abestiak",
|
||||
"trackWithCount_one": "pista {{count}}",
|
||||
"trackWithCount_other": "{{count}} pista"
|
||||
},
|
||||
"error": {
|
||||
"apiRouteError": "ezin izan da eskaera bideratu",
|
||||
"audioDeviceFetchError": "errore bat gertatu da audio gailuak lortzen saiatzean",
|
||||
"authenticationFailed": "autentifikazioa huts egin du",
|
||||
"badValue": "\"{{value}}\" aukera baliogabea. Balio hau ez da gehiago existitzen.",
|
||||
"credentialsRequired": "kredentzialak beharrezkoak dira",
|
||||
"endpointNotImplementedError": "{{endpoint}} amaiera-puntua ez dago {{serverType}}-(e)rako inplementatuta",
|
||||
"genericError": "errore bat gertatu da",
|
||||
"invalidServer": "zerbitzari baliogabea",
|
||||
"localFontAccessDenied": "tokiko letra-tipoetarako sarbidea ukatuta",
|
||||
"mpvRequired": "MPV beharrezkoa da",
|
||||
"networkError": "sareko errore bat gertatu da",
|
||||
"openError": "ezin izan da fitxategia ireki",
|
||||
"playbackError": "errore bat gertatu da multimedia erreproduzitzen saiatzean",
|
||||
"remoteDisableError": "errore bat gertatu da urruneko zerbitzaria $t(common.disable) desgaitzen saiatzean",
|
||||
"remoteEnableError": "errore bat gertatu da urruneko zerbitzaria $t(common.enable) gaitzen saiatzean",
|
||||
"remotePortError": "errore bat gertatu da urruneko zerbitzariaren ataka ezartzen saiatzean",
|
||||
"remotePortWarning": "Berrabiarazi zerbitzaria portu berria aplikatzeko",
|
||||
"serverNotSelectedError": "ez da zerbitzaririk hautatu",
|
||||
"serverRequired": "zerbitzaria beharrezkoa da",
|
||||
"sessionExpiredError": "zure saioa iraungi da",
|
||||
"badAlbum": "Orrialde hau ikusten ari zara abesti hau album batekoa ez delako. Ziurrenik arazo hau ikusten ari zara zure musika karpetaren goiko mailan abesti bat baduzu. Jellyfinek abestiak karpeta batean badaude taldekatzen ditu bakarrik",
|
||||
"loginRateError": "Saioa hasteko saiakera gehiegi egin dira, saiatu berriro segundo batzuk barru",
|
||||
"notificationDenied": "jakinarazpenetarako baimenak ukatu dira. Ezarpen honek ez du eraginik",
|
||||
"systemFontError": "errore bat gertatu da sistemaren letra-tipoak lortzen saiatzean",
|
||||
"noNetwork": "zerbitzaria ez dago erabilgarri",
|
||||
"noNetworkDescription": "ezin izan da zerbitzari honetara konektatu",
|
||||
"saveQueueFailed": "huts egin du ilara gordetzean"
|
||||
},
|
||||
"filter": {
|
||||
"disc": "diskoa",
|
||||
"duration": "iraupena",
|
||||
"id": "id-a",
|
||||
"isPublic": "publikoa da",
|
||||
"name": "izena",
|
||||
"note": "oharra",
|
||||
"owner": "$t(common.owner)",
|
||||
"path": "bidea",
|
||||
"random": "ausazkoa",
|
||||
"rating": "balorazioa",
|
||||
"trackNumber": "pista",
|
||||
"album": "$t(entity.album_one)",
|
||||
"albumArtist": "$t(entity.albumArtist_one)",
|
||||
"artist": "$t(entity.artist_one)",
|
||||
"biography": "biografia",
|
||||
"bitrate": "bit-emaria",
|
||||
"bpm": "bpm-ak",
|
||||
"channels": "$t(common.channel_other)",
|
||||
"comment": "iruzkina",
|
||||
"favorited": "gogoko gisa markatua",
|
||||
"genre": "$t(entity.genre_one)",
|
||||
"search": "bilatu",
|
||||
"title": "tituloa",
|
||||
"albumCount": "$t(entity.album_other) kopurua",
|
||||
"communityRating": "komunitatearen balorazioa",
|
||||
"criticRating": "kritikarien balorazioa",
|
||||
"dateAdded": "gehitutako data",
|
||||
"isCompilation": "konpilazioa da",
|
||||
"isFavorited": "gogokoetan dago",
|
||||
"isRated": "baloratua dago",
|
||||
"isRecentlyPlayed": "duela gutxi entzundakoa",
|
||||
"lastPlayed": "azken aldiz entzundakoa",
|
||||
"mostPlayed": "gehien entzundakoa",
|
||||
"playCount": "erreprodukzio kopurua",
|
||||
"recentlyAdded": "duela gutxi gehitutakoa",
|
||||
"recentlyPlayed": "duela gutxi entzundakoa",
|
||||
"recentlyUpdated": "duela gutxi eguneratua",
|
||||
"songCount": "abesti kopurua",
|
||||
"releaseDate": "argitalpen data",
|
||||
"releaseYear": "argitalpen urtea",
|
||||
"toYear": "urtera arte",
|
||||
"fromYear": "urtetik aurrera",
|
||||
"explicitStatus": "$t(common.explicitStatus)"
|
||||
},
|
||||
"setting": {
|
||||
"hotkey_playbackPause": "pausatu",
|
||||
"hotkey_playbackPlay": "erreproduzitu",
|
||||
"playbackStyle_optionNormal": "normala",
|
||||
"playButtonBehavior_optionPlay": "$t(player.play)",
|
||||
"playButtonBehavior_optionPlayShuffled": "$t(player.shuffle)",
|
||||
"replayGainMode_optionAlbum": "$t(entity.album_one)",
|
||||
"replayGainMode_optionNone": "$t(common.none)",
|
||||
"replayGainMode_optionTrack": "$t(entity.track_one)",
|
||||
"font": "letra-tipoa",
|
||||
"hotkey_playbackStop": "gelditu",
|
||||
"buttonSize_description": "erreproduzitzailearen barrako botoien tamaina",
|
||||
"clearCache": "garbitu nabigatzailearen katxea",
|
||||
"clearQueryCache": "garbitu feishinen katxea",
|
||||
"clearCacheSuccess": "katxea behar bezala garbitu da",
|
||||
"contextMenu": "testuinguru-menuaren konfigurazioa (klik eskuineko botoiarekin)",
|
||||
"customCssEnable": "gaitu css pertsonalizatua",
|
||||
"customCssEnable_description": "css pertsonalizatua idazteko aukera eman",
|
||||
"customCss": "css pertsonalizatua",
|
||||
"customFontPath": "letra-tipo pertsonalizatuaren bidea",
|
||||
"customFontPath_description": "aplikazioan erabiliko den letra-tipo pertsonalizatuaren bidea ezartzen du",
|
||||
"disableAutomaticUpdates": "desgaitu eguneratze automatikoak",
|
||||
"discordApplicationId": "{{discord}} aplikazioaren IDa",
|
||||
"followLyric": "jarraitu uneko letra",
|
||||
"font_description": "aplikazioan erabiliko den letra-tipoa ezartzen du",
|
||||
"fontType": "letra-tipo mota",
|
||||
"fontType_optionCustom": "letra-tipo pertsonalizatua",
|
||||
"fontType_optionSystem": "sistemaren letra-tipoa",
|
||||
"gaplessAudio_optionWeak": "ahula (gomendatua)",
|
||||
"homeConfiguration": "hasierako orriaren konfigurazioa",
|
||||
"hotkey_favoriteCurrentSong": "$t(common.currentSong) gogokoa",
|
||||
"hotkey_favoritePreviousSong": "$t(common.previousSong) gogokoa",
|
||||
"hotkey_navigateHome": "nabigatu etxera",
|
||||
"hotkey_playbackNext": "hurrengo pista",
|
||||
"hotkey_playbackPlayPause": "erreproduzitu / pausatu",
|
||||
"hotkey_playbackPrevious": "aurreko pista",
|
||||
"hotkey_skipBackward": "saltatu atzeraka",
|
||||
"hotkey_skipForward": "saltatu aurrerantz",
|
||||
"hotkey_toggleCurrentSongFavorite": "txandakatu $t(common.currentSong) gogokoa",
|
||||
"hotkey_toggleFullScreenPlayer": "txandakatu pantaila osoko erreproduzitzailea",
|
||||
"hotkey_togglePreviousSongFavorite": "txandakatu $t(common.previousSong) gogokoa",
|
||||
"hotkey_toggleQueue": "txandakatu ilara",
|
||||
"hotkey_toggleRepeat": "txandakatu errepikapena",
|
||||
"hotkey_toggleShuffle": "txandakatu auzazkoa",
|
||||
"hotkey_unfavoriteCurrentSong": "kendu $t(common.currentSong) gogokoetatik",
|
||||
"hotkey_unfavoritePreviousSong": "kendu $t(common.previousSong) gogokoetatik",
|
||||
"hotkey_volumeDown": "bolumena jaitsi",
|
||||
"hotkey_volumeMute": "isilarazi bolumena",
|
||||
"hotkey_volumeUp": "bolumena igo",
|
||||
"hotkey_zoomIn": "hurbildu",
|
||||
"hotkey_zoomOut": "txikiagotu",
|
||||
"language_description": "aplikazioaren hizkuntza ezartzen du ($t(common.restartRequired))",
|
||||
"lastfm": "erakutsi last.fm estekak",
|
||||
"lastfm_description": "erakutsi Last.fm-rako estekak artista/album orrialdeetan",
|
||||
"lastfmApiKey": "{{lastfm}} API gakoa",
|
||||
"lastfmApiKey_description": "{{lastfm}}-ren API gakoa. Azaleko arterako beharrezkoa.",
|
||||
"lyricFetch": "eskuratu letrak internetetik",
|
||||
"lyricFetch_description": "Eskuratu letrak hainbat internet iturrietatik",
|
||||
"audioExclusiveMode_description": "gaitu irteera esklusiboko modua. Modu honetan, sistema normalean blokeatuta egoten da, eta mpv-k bakarrik atera ahal izango du audioa",
|
||||
"audioDevice_description": "aukeratu erreproduzitzeko erabiliko den audio gailua (web erreproduzitzailea bakarrik)",
|
||||
"audioPlayer": "audio erreproduzitzailea",
|
||||
"audioPlayer_description": "aukeratu erabiliko den audio erreproduzitzailea",
|
||||
"buttonSize": "erreproduzitzaile barrako botoien tamaina",
|
||||
"crossfadeDuration": "crossfade iraupena",
|
||||
"crossfadeDuration_description": "crossfade efektuaren iraupena ezartzen du",
|
||||
"crossfadeStyle_description": "aukeratu audio erreproduzitzailearentzat erabiliko den crossfade estiloa",
|
||||
"disableLibraryUpdateOnStartup": "desgaitu bertsio berrien egiaztapena abiaraztean",
|
||||
"discordApplicationId_description": "{{discord}} jarduera-egoeraren aplikazioaren IDa (lehenetsia {{defaultId}} da)",
|
||||
"discordPausedStatus": "erakutsi jarduera-egoera pausatuta dagoenean",
|
||||
"discordPausedStatus_description": "gaituta dagoenean, egoera agertuko da erreproduzitzailea pausatuta dagoenean",
|
||||
"discordIdleStatus": "erakutsi inaktibo jarduera-egoeran",
|
||||
"discordIdleStatus_description": "gaituta dagoenean, eguneratu egoera erreproduzitzailea inaktibo dagoen bitartean",
|
||||
"discordListening_description": "erakutsi egoera entzuten bezala erreproduzitzen ordez",
|
||||
"discordListening": "erakutsi egoera entzuten bezala",
|
||||
"discordRichPresence_description": "gaitu erreprodukzioa egoera {{discord}}-en jarduera-egoeran. Irudi gakoak hauek dira: {{icon}}, {{playing}}, eta {{paused}}",
|
||||
"discordServeImage": "zerbitzatu {{discord}} irudiak zerbitzaritik",
|
||||
"discordServeImage_description": "partekatu {{discord}} jarduera-egoerarentzako azala artea zerbitzaritik bertatik, Jellyfin eta Navidrome-rentzat bakarrik eskuragarri. {{discord}}-(e)k bot bat erabiltzen du irudiak eskuratzeko, beraz, zure zerbitzaria internet publikotik eskuragarri egon behar da",
|
||||
"discordUpdateInterval": "{{discord}} jarduera-egoera eguneraketa tartea",
|
||||
"discordLinkType_none": "$t(common.none)",
|
||||
"albumBackground": "albumaren atzeko planoaren irudia",
|
||||
"albumBackground_description": "albumaren azala artea duten album orrietarako atzeko plano irudi bat gehitzen du",
|
||||
"albumBackgroundBlur": "albumaren atzeko planoaren irudiaren lausotze tamaina",
|
||||
"discordLinkType_description": "{{lastfm}} edo {{musicbrainz}}-(e)rako kanpoko estekak gehitzen ditu abesti eta artista eremuetan {{discord}} jarduera-egoeran. {{musicbrainz}} da zehatzena, baina etiketak behar ditu eta ez ditu artistaren estekak ematen, {{lastfm}}-k beti esteka bat eman beharko lukeen bitartean. ez du sareko eskaera gehigarririk egiten",
|
||||
"playButtonBehavior_optionAddNext": "$t(player.addNext)",
|
||||
"scrobble": "scrobble",
|
||||
"sidePlayQueueStyle_optionAttached": "erantsita",
|
||||
"sidePlayQueueStyle_optionDetached": "bereizita",
|
||||
"theme": "gaia",
|
||||
"audioDevice": "audio gailua",
|
||||
"discordDisplayType_songname": "abesti izena",
|
||||
"discordDisplayType_artistname": "artista izena(k)",
|
||||
"fontType_optionBuiltIn": "barneko letra-tipoa",
|
||||
"hotkey_globalSearch": "bilaketa globala",
|
||||
"albumBackgroundBlur_description": "albumaren atzeko planoaren irudiari aplikatzen zaion lausotze-kopurua doitzen du",
|
||||
"artistBackground": "artistaren atzeko planoaren irudia",
|
||||
"artistBackgroundBlur": "artistaren atzeko planoko irudiaren lausotze-tamaina",
|
||||
"artistBackgroundBlur_description": "artistaren atzeko planoaren irudiari aplikatzen zaion lausotze-kopurua doitzen du",
|
||||
"artistConfiguration": "albumaren artistaren konfigurazio orria",
|
||||
"artistConfiguration_description": "konfiguratu zein elementu erakusten diren eta zein ordenatan albumaren artistaren orrian",
|
||||
"audioExclusiveMode": "audio esklusiboko modua",
|
||||
"releaseChannel_optionLatest": "azken bertsioa",
|
||||
"releaseChannel_optionBeta": "beta",
|
||||
"releaseChannel": "argitalpen kanala",
|
||||
"releaseChannel_description": "aukeratu argitalpen egonkorren edo beta artean eguneratze automatikoak lortzeko",
|
||||
"discordUpdateInterval_description": "eguneratze bakoitzaren arteko denbora segundotan (gutxienez 15 segundo)",
|
||||
"discordDisplayType": "{{discord}} jarduera-pantailaren mota",
|
||||
"discordDisplayType_description": "zure egoeran entzuten ari zarena aldatzen du",
|
||||
"discordLinkType": "{{discord}} egoera estekak",
|
||||
"fontType_description": "barneko letra-tipoa feishinek eskaintzen dituen letra-tipoetako bat aukeratzen du. sistemaren letra-tipoa zure sistema eragileak eskaintzen duen edozein letra-tipo hautatzeko aukera ematen dizu. pertsonalizatua zure letra-tipoa eskaintzeko aukera ematen dizu",
|
||||
"homeConfiguration_description": "konfiguratu zein elementu erakusten diren hasierako orrian eta zein ordenatan",
|
||||
"homeFeature": "etxeko karrusela nabarmendua",
|
||||
"homeFeature_description": "hasierako orrian karrusel nabarmen handia erakutsi behar den ala ez kontrolatzen du",
|
||||
"hotkey_localSearch": "orrian bilatu",
|
||||
"hotkey_rate0": "garbitu balorazioa",
|
||||
"hotkey_rate1": "1 izarretako balorazioa",
|
||||
"hotkey_rate2": "2 izarretako balorazioa",
|
||||
"hotkey_rate3": "3 izarretako balorazioa",
|
||||
"hotkey_rate4": "4 izarretako balorazioa",
|
||||
"hotkey_rate5": "5 izarretako balorazioa",
|
||||
"zoom_description": "aplikazioaren zoom ehunekoa ezartzen du",
|
||||
"zoom": "zoom ehunekoa",
|
||||
"windowBarStyle_description": "aukeratu leiho-barraren estiloa",
|
||||
"windowBarStyle": "leiho-barra estiloa",
|
||||
"webAudio": "erabili web audioa",
|
||||
"useSystemTheme_description": "jarraitu sistemak definitutako argi edo iluntasun lehentasuna",
|
||||
"useSystemTheme": "erabili sistemaren gaia",
|
||||
"translationTargetLanguage_description": "itzulpenerako helburu-hizkuntza",
|
||||
"translationTargetLanguage": "itzulpenerako helburu-hizkuntza",
|
||||
"translationApiKey": "itzulpen api gakoa",
|
||||
"translationApiProvider_description": "itzulpenerako api hornitzailea",
|
||||
"translationApiProvider": "itzulpen api hornitzailea",
|
||||
"mediaSession": "gaitu multimedia saioa",
|
||||
"themeLight_description": "aplikaziorako erabiliko den gaia argia ezartzen du",
|
||||
"themeLight": "gaia (argia)",
|
||||
"themeDark_description": "aplikaziorako erabiliko den gai iluna ezartzen du",
|
||||
"themeDark": "gaia (iluna)",
|
||||
"theme_description": "aplikaziorako erabiliko den gaia ezartzen du",
|
||||
"externalLinks": "kanpoko estekak erakutsi",
|
||||
"externalLinks_description": "kanpoko estekak (Last.fm, MusicBrainz) artista/album orrietan erakustea gaitzen du",
|
||||
"exitToTray": "irten erretilura",
|
||||
"playButtonBehavior_optionAddLast": "$t(player.addLast)",
|
||||
"replayGainMode": "{{ReplayGain}} modua",
|
||||
"sidebarConfiguration": "alboko barraren konfigurazioa",
|
||||
"skipDuration": "saltoaren iraupena",
|
||||
"savePlayQueue": "gorde erreprodukzio ilara",
|
||||
"playbackStyle_optionCrossFade": "crossfade-a",
|
||||
"applicationHotkeys": "aplikazioaren laster-teklak",
|
||||
"applicationHotkeys_description": "konfiguratu aplikazioaren laster-teklak. txandakatu kontrol-laukia laster-tekla orokor bezala ezartzeko (mahaigainerako soilik)",
|
||||
"artistBackground_description": "artistaren artelanak dituzten artista-orrietarako atzeko planoko irudi bat gehitzen du",
|
||||
"discordLinkType_mbz_lastfm": "{{musicbrainz}} {{lastfm}} alternatiba gisa erabiliz",
|
||||
"globalMediaHotkeys": "laster-tekla multimedia globalak",
|
||||
"globalMediaHotkeys_description": "gaitu edo desgaitu zure sistemaren laster-tekla multimedien erabilera erreprodukzioa kontrolatzeko",
|
||||
"accentColor_description": "aplikazioaren azentu-kolorea ezartzen du",
|
||||
"accentColor": "azentu-kolorea",
|
||||
"clearCache_description": "feishinen 'garbiketa gogorra'. feishinen katxea garbitzeaz gain, hustu nabigatzailearen katxea (gordetako irudiak eta bestelako aktiboak). zerbitzari-kredentzialak eta ezarpenak gorde egiten dira",
|
||||
"clearQueryCache_description": "feishinen 'garbiketa ahula'. honek erreprodukzio-zerrendak eta pisten metadatuak freskatuko ditu eta gordetako letrak berrezarriko ditu. ezarpenak, zerbitzari-kredentzialak eta katxetutako irudiak gorde egiten dira",
|
||||
"exitToTray_description": "irten aplikaziotik sistemaren erretilura",
|
||||
"followLyric_description": "mugitu letra uneko erreprodukzio-posiziora",
|
||||
"preferLocalLyrics": "nahiago izan letra lokalak",
|
||||
"preferLocalLyrics_description": "nahiago izan letra lokalak urrunekoak baino eskuragarri daudenean",
|
||||
"hotkey_browserBack": "nabigatzailean atzeraka",
|
||||
"hotkey_browserForward": "nabigatzailean aurreraka",
|
||||
"imageAspectRatio": "erabili jatorrizko azaleko artearen aspektu-erlazioa",
|
||||
"lyricFetchProvider": "letrak eskuratzeko hornitzaileak",
|
||||
"lyricFetchProvider_description": "aukeratu letrak eskuratzeko hornitzaileak. hornitzaileen ordena kontsultatuko diren ordena da",
|
||||
"minimizeToTray": "minimizatu erretilura",
|
||||
"minimizeToTray_description": "minimizatu aplikazioa sistemaren erretilura",
|
||||
"minimumScrobblePercentage": "scrobble iraupen minimoa (ehunekoa)",
|
||||
"minimumScrobblePercentage_description": "erreproduzitu behar den abestiaren gutxieneko ehunekoa scrobble egin aurretik",
|
||||
"minimumScrobbleSeconds": "gutxieneko scrobble-a (segundoak)",
|
||||
"minimumScrobbleSeconds_description": "erreproduzitu behar den abestiaren gutxieneko iraupena segundotan scrobble egin aurretik",
|
||||
"mpvExecutablePath": "mpv exekutagarriaren bidea",
|
||||
"mpvExecutablePath_description": "ezartzen du mpv exekutagarriaren bidea. hutsik uzten bada, bide lehenetsia erabiliko da",
|
||||
"mpvExtraParameters_help": "lerro bakoitzeko bat",
|
||||
"musicbrainz": "erakutsi MusicBrainz estekak",
|
||||
"musicbrainz_description": "erakutsi MusicbBrainz-erako estekak artista/album orrietan, MusicBrainz ID existitzen den lekuetan",
|
||||
"neteaseTranslation": "Gaitu NetEase itzulpenak",
|
||||
"neteaseTranslation_description": "Gaituta dagoenean, NetEase-tik itzulitako letrak eskuratu eta bistaratzen ditu, eskuragarri badaude",
|
||||
"playbackStyle": "erreprodukzio estiloa",
|
||||
"playbackStyle_description": "aukeratu audio erreproduzitzailearentzat erabiliko den erreprodukzio estiloa",
|
||||
"playButtonBehavior": "erreprodukzio botoiaren portaera",
|
||||
"playButtonBehavior_description": "ezartzen du erreprodukzio botoiaren portaera lehenetsia abestiak ilaran gehitzean",
|
||||
"gaplessAudio": "hutsune gabeko audioa",
|
||||
"gaplessAudio_description": "ezartzen du hutsunik gabeko audio ezarpena mpv-rako",
|
||||
"passwordStore": "pasahitzak/biltegi sekretua",
|
||||
"playerbarOpenDrawer": "txandakatu erreproduzitzailearen barra pantaila osora",
|
||||
"playerbarOpenDrawer_description": "aukera ematen du erreproduzitzailearen barran klik egiteak pantaila osoko erreproduzitzailea irekitzeko",
|
||||
"customCssNotice": "Abisua: garbiketa batzuk dauden arren (url() eta content: debekatuz), css pertsonalizatua erabiltzeak arriskuak sor ditzake interfazea aldatuz gero",
|
||||
"customCss_description": "css eduki pertsonalizatua. Oharra: edukia eta urruneko URLak debekatutako propietateak dira. Zure edukiaren aurrebista erakusten da behean. Ezarri ez dituzun eremu gehigarriak daude garbiketa dela eta",
|
||||
"enableRemote": "gaitu urruneko kontrol zerbitzaria",
|
||||
"enableRemote_description": "urruneko kontrol zerbitzariari beste gailu batzuei aplikazioa kontrolatzeko aukera ematen dio",
|
||||
"imageAspectRatio_description": "gaituta badago, azaleko artea jatorrizko aspektu-erlazioa erabiliz erakutsiko da. 1:1 ez den arterako, gainerako espazioa hutsik egongo da",
|
||||
"crossfadeStyle": "crossfade estiloa",
|
||||
"discordRichPresence": "{{discord}}-en jarduera-egoera",
|
||||
"enableAutoTranslation": "gaitu itzulpen automatikoa",
|
||||
"exportImportSettings_control_exportText": "esportatu ezarpenak",
|
||||
"exportImportSettings_control_importText": "inportatu ezarpenak",
|
||||
"exportImportSettings_control_title": "inportatu / esportatu ezarpenak",
|
||||
"exportImportSettings_importBtn": "inportatu ezarpenak",
|
||||
"exportImportSettings_importModalTitle": "inportatu feishin ezarpenak",
|
||||
"autoDJ_itemCount": "elementu kopurua",
|
||||
"language": "hizkuntza",
|
||||
"queryBuilderCustomFields_inputTag": "etiketa",
|
||||
"logLevel_optionError": "errore bat",
|
||||
"logLevel_optionInfo": "informazioa",
|
||||
"imageResolution_optionTable": "taula",
|
||||
"imageResolution_optionSidebar": "alboko barra",
|
||||
"replayGainClipping": "{{ReplayGain}} mozketa",
|
||||
"replayGainFallback": "{{ReplayGain}} ordezko aukera",
|
||||
"trayEnabled": "erakutsi erretilua"
|
||||
},
|
||||
"form": {
|
||||
"addServer": {
|
||||
"input_password": "pasahitza",
|
||||
"input_url": "url-a",
|
||||
"input_username": "erabiltzaile-izena",
|
||||
"error_savePassword": "errore bat gertatu da pasahitza gordetzen saiatzean",
|
||||
"input_name": "zerbitzari izena",
|
||||
"input_savePassword": "pasahitza gorde",
|
||||
"title": "zerbitzaria gehitu",
|
||||
"ignoreCors": "alde batera utzi cors $t(common.restartRequired)",
|
||||
"ignoreSsl": "alde batera utzi ssl $t(common.restartRequired)",
|
||||
"input_legacyAuthentication": "gaitu zaharkitutako autentifikazioa",
|
||||
"success": "zerbitzaria behar bezala gehitu da",
|
||||
"input_preferInstantMix": "nahiago izan berehalako nahasketa",
|
||||
"input_preferInstantMixDescription": "erabili berehalako nahasketa soilik antzeko abestiak lortzeko. erabilgarria portaera hau aldatzen duten pluginak badituzu"
|
||||
},
|
||||
"addToPlaylist": {
|
||||
"input_playlists": "$t(entity.playlist_other)",
|
||||
"success": "$t(entity.trackWithCount, {\"count\": {{message}} }) gehitu da $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })-ra",
|
||||
"input_skipDuplicates": "saltatu bikoiztuak",
|
||||
"title": "gehitu $t(entity.playlist_one)-(a)ri",
|
||||
"create": "sortu $t(entity.playlist_one) {{playlist}}",
|
||||
"searchOrCreate": "bilatu $t(entity.playlist_other) edo idatzi berri bat sortzeko"
|
||||
},
|
||||
"createPlaylist": {
|
||||
"input_description": "$t(common.description)",
|
||||
"input_name": "$t(common.name)",
|
||||
"input_owner": "$t(common.owner)",
|
||||
"input_public": "publikoa",
|
||||
"title": "$t(entity.playlist_one) sortu",
|
||||
"success": "$t(entity.playlist_one) behar bezala sortu da"
|
||||
},
|
||||
"lyricSearch": {
|
||||
"input_artist": "$t(entity.artist_one)",
|
||||
"input_name": "$t(common.name)",
|
||||
"title": "letra bilatu"
|
||||
},
|
||||
"shareItem": {
|
||||
"description": "deskripzioa",
|
||||
"setExpiration": "iraungitze-data ezarri",
|
||||
"success": "partekatzeko esteka arbelera kopiatu da (edo egin klik hemen irekitzeko)",
|
||||
"expireInvalid": "iraungitze-data etorkizunean izan behar da",
|
||||
"allowDownloading": "baimendu deskargatzea",
|
||||
"createFailed": "partekatzea sortzeak huts egin du (partekatzea gaituta al dago?)"
|
||||
},
|
||||
"deletePlaylist": {
|
||||
"success": "$t(entity.playlist_one) behar bezala ezabatu da",
|
||||
"title": "$t(entity.playlist_one) ezabatu",
|
||||
"input_confirm": "idatzi $t(entity.playlist_one)-(a)ren izena berresteko"
|
||||
},
|
||||
"editPlaylist": {
|
||||
"success": "$t(entity.playlist_one) behar bezala eguneratu da",
|
||||
"title": "$t(entity.playlist_one) editatu",
|
||||
"publicJellyfinNote": "Arrazoiren batengatik, Jellyfin ez du erakusten erreprodukzio-zerrendak publikoak diren edo ez. Hau publiko izaten jarraitzea nahi baduzu, hautatu sarrera hau",
|
||||
"editNote": "ez da gomendatzen eskuzko edizioak egitea erreprodukzio-zerrenda handietarako. ziur zaude onartzen duzula lehendik dagoen erreprodukzio-zerrenda gainidazteagatik datuak galtzeko arriskua?"
|
||||
},
|
||||
"queryEditor": {
|
||||
"title": "kontsulta editorea",
|
||||
"input_optionMatchAll": "guztiak bat etorri",
|
||||
"input_optionMatchAny": "edozeinekin bat etorri"
|
||||
},
|
||||
"updateServer": {
|
||||
"success": "zerbitzaria behar bezala eguneratu da",
|
||||
"title": "zerbitzaria eguneratu"
|
||||
},
|
||||
"privateMode": {
|
||||
"title": "modu pribatua",
|
||||
"enabled": "modu pribatua gaituta, erreprodukzio egoera kanpoko integrazioetatik ezkutatuta dago orain",
|
||||
"disabled": "modu pribatua desgaituta, erreprodukzio egoera ikusgai dago orain gaitutako kanpoko integrazioentzat"
|
||||
},
|
||||
"largeFetchConfirmation": {
|
||||
"title": "gehitu elementuak ilaran"
|
||||
},
|
||||
"createRadioStation": {
|
||||
"input_homepageUrl": "hasierako orriaren URLa",
|
||||
"input_name": "izena"
|
||||
},
|
||||
"lyricsExport": {
|
||||
"export": "esportatu letrak",
|
||||
"input_synced": "esportatu sinkronizatutako letrak",
|
||||
"input_offset": "$t(setting.lyricOffset)"
|
||||
},
|
||||
"shuffleAll": {
|
||||
"input_genre": "$t(entity.genre_one)"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"albumArtistList": {
|
||||
"title": "$t(entity.albumArtist_other)"
|
||||
},
|
||||
"albumDetail": {
|
||||
"released": "argitaratuta",
|
||||
"moreFromArtist": "$t(entity.artist_one) honetatik gehiago",
|
||||
"moreFromGeneric": "{{item}}-(e)tik gehiago"
|
||||
},
|
||||
"albumList": {
|
||||
"title": "$t(entity.album_other)",
|
||||
"genreAlbums": "\"{{genre}}\" $t(entity.album_other)",
|
||||
"artistAlbums": "{{artist}}-(a)ren albumak"
|
||||
},
|
||||
"appMenu": {
|
||||
"quit": "$t(common.quit)",
|
||||
"settings": "$t(common.setting_other)",
|
||||
"collapseSidebar": "tolestu alboko barra",
|
||||
"expandSidebar": "zabaldu alboko barra",
|
||||
"goBack": "atzera",
|
||||
"goForward": "aurrera",
|
||||
"manageServers": "kudeatu zerbitzariak",
|
||||
"privateModeOff": "itzali modu pribatua",
|
||||
"privateModeOn": "aktibatu modu pribatua",
|
||||
"selectServer": "aukeratu zerbitzaria",
|
||||
"version": "bertsioa {{version}}",
|
||||
"openBrowserDevtools": "ireki nabigatzailearen garapen tresnak"
|
||||
},
|
||||
"manageServers": {
|
||||
"url": "URLa",
|
||||
"username": "erabiltzaile-izena",
|
||||
"title": "kudeatu zerbitzariak",
|
||||
"serverDetails": "zerbitzariaren xehetasunak",
|
||||
"editServerDetailsTooltip": "editatu zerbitzariaren xehetasunak",
|
||||
"removeServer": "kendu zerbitzaria"
|
||||
},
|
||||
"contextMenu": {
|
||||
"addFavorite": "$t(action.addToFavorites)",
|
||||
"addLast": "$t(player.addLast)",
|
||||
"addNext": "$t(player.addNext)",
|
||||
"addToFavorites": "$t(action.addToFavorites)",
|
||||
"addToPlaylist": "$t(action.addToPlaylist)",
|
||||
"createPlaylist": "$t(action.createPlaylist)",
|
||||
"deletePlaylist": "$t(action.deletePlaylist)",
|
||||
"deselectAll": "$t(action.deselectAll)",
|
||||
"download": "deskargatu",
|
||||
"moveToNext": "$t(action.moveToNext)",
|
||||
"moveToBottom": "$t(action.moveToBottom)",
|
||||
"moveToTop": "$t(action.moveToTop)",
|
||||
"play": "$t(player.play)",
|
||||
"playSimilarSongs": "$t(player.playSimilarSongs)",
|
||||
"removeFromFavorites": "$t(action.removeFromFavorites)",
|
||||
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
|
||||
"removeFromQueue": "$t(action.removeFromQueue)",
|
||||
"setRating": "$t(action.setRating)",
|
||||
"playShuffled": "$t(player.shuffle)",
|
||||
"numberSelected": "{{count}} hautatuta",
|
||||
"shareItem": "partekatu elementua",
|
||||
"goToAlbum": "joan $t(entity.album_one)-(e)ra",
|
||||
"goToAlbumArtist": "joan albumera",
|
||||
"showDetails": "informazioa lortu",
|
||||
"moveItems": "$t(action.moveItems)"
|
||||
},
|
||||
"fullscreenPlayer": {
|
||||
"config": {
|
||||
"opacity": "opakotasuna",
|
||||
"synchronized": "sinkronizatuta",
|
||||
"unsynchronized": "sinkronizatu gabe",
|
||||
"dynamicIsImage": "gaitu atzeko planoaren irudia",
|
||||
"followCurrentLyric": "jarraitu uneko letra",
|
||||
"lyricSize": "letraren tamaina",
|
||||
"dynamicBackground": "atzeko plano dinamikoa",
|
||||
"dynamicImageBlur": "irudiaren lausotze tamaina",
|
||||
"lyricAlignment": "letraren lerrokatzea",
|
||||
"showLyricMatch": "erakutsi letren bat-etortzea",
|
||||
"showLyricProvider": "erakutsi letra hornitzailea",
|
||||
"lyricOffset": "letra-desplazamendua (ms)",
|
||||
"lyricGap": "letra hutsunea",
|
||||
"useImageAspectRatio": "erabili irudiaren aspektu-erlazioa"
|
||||
},
|
||||
"lyrics": "letrak",
|
||||
"related": "erlazionatuta",
|
||||
"upNext": "hurrengoa",
|
||||
"visualizer": "bistaratzailea",
|
||||
"noLyrics": "ez da letrarik aurkitu"
|
||||
},
|
||||
"genreList": {
|
||||
"title": "$t(entity.genre_other)",
|
||||
"showAlbums": "erakutsi $t(entity.genre_one) $t(entity.album_other)",
|
||||
"showTracks": "erakutsi $t(entity.genre_one) $t(entity.track_other)"
|
||||
},
|
||||
"globalSearch": {
|
||||
"title": "komandoak",
|
||||
"commands": {
|
||||
"goToPage": "joan orrira",
|
||||
"searchFor": "bilatu {{query}}",
|
||||
"serverCommands": "zerbitzariaren komandoak"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"title": "$t(common.home)",
|
||||
"mostPlayed": "gehien entzundakoak",
|
||||
"newlyAdded": "azken aldian gehitutako argitalpenak",
|
||||
"recentlyPlayed": "azken aldian entzundakoak",
|
||||
"recentlyReleased": "azken aldian argitaratutak",
|
||||
"explore": "arakatu zure liburutegitik",
|
||||
"genres": "$t(entity.genre_other)"
|
||||
},
|
||||
"playlistList": {
|
||||
"title": "$t(entity.playlist_other)"
|
||||
},
|
||||
"setting": {
|
||||
"advanced": "aurreratua",
|
||||
"generalTab": "orokorra",
|
||||
"playbackTab": "erreprodukzioa",
|
||||
"windowTab": "leihoa",
|
||||
"hotkeysTab": "laster-teklak",
|
||||
"cache": "katxea",
|
||||
"application": "aplikazioa",
|
||||
"theme": "gaia",
|
||||
"sidebar": "alboko barra",
|
||||
"exportImport": "inportatu/esportatu",
|
||||
"scrobble": "scrobble",
|
||||
"audio": "audioa",
|
||||
"lyrics": "letrak",
|
||||
"discord": "discord",
|
||||
"playerFilters": "erreproduzitzailearen iragazkiak",
|
||||
"updates": "eguneraketa"
|
||||
},
|
||||
"sidebar": {
|
||||
"albumArtists": "$t(entity.albumArtist_other)",
|
||||
"albums": "$t(entity.album_other)",
|
||||
"artists": "$t(entity.artist_other)",
|
||||
"folders": "$t(entity.folder_other)",
|
||||
"genres": "$t(entity.genre_other)",
|
||||
"home": "$t(common.home)",
|
||||
"playlists": "$t(entity.playlist_other)",
|
||||
"search": "$t(common.search)",
|
||||
"settings": "$t(common.setting_other)",
|
||||
"tracks": "$t(entity.track_other)",
|
||||
"myLibrary": "nire liburutegia",
|
||||
"nowPlaying": "orain erreproduzitzen",
|
||||
"shared": "partekatutako $t(entity.playlist_other)",
|
||||
"favorites": "$t(entity.favorite_other)",
|
||||
"radio": "$t(entity.radioStation_other)"
|
||||
},
|
||||
"trackList": {
|
||||
"title": "$t(entity.track_other)",
|
||||
"genreTracks": "\"{{genre}}\" $t(entity.track_other)",
|
||||
"artistTracks": "{{artist}}-(r)en abestiak"
|
||||
},
|
||||
"albumArtistDetail": {
|
||||
"about": "{{artist}}-(r)i buruz",
|
||||
"relatedArtists": "erlazionatutako $t(entity.artist_other)",
|
||||
"topSongs": "abesti nagusiak",
|
||||
"topSongsFrom": "{{title}}-(a)ren abesti nagusiak",
|
||||
"viewAll": "ikusi guztiak",
|
||||
"viewAllTracks": "ikusi $t(entity.track_other) guztiak",
|
||||
"appearsOn": "agertzen da hemen",
|
||||
"recentReleases": "azken argitalpenak",
|
||||
"viewDiscography": "ikusi diskografia"
|
||||
},
|
||||
"itemDetail": {
|
||||
"copyPath": "kopiatu bidea arbelean",
|
||||
"openFile": "erakutsi pista fitxategi-kudeatzailean",
|
||||
"copiedPath": "bidea behar bezala kopiatu da"
|
||||
},
|
||||
"playlist": {
|
||||
"reorder": "berrantolaketa IDaren arabera ordenatzean bakarrik gaituta dago"
|
||||
},
|
||||
"folderList": {
|
||||
"title": "$t(entity.folder_other)"
|
||||
},
|
||||
"favorites": {
|
||||
"title": "$t(entity.favorite_other)"
|
||||
}
|
||||
},
|
||||
"releaseType": {
|
||||
"primary": {
|
||||
"album": "$t(entity.album_one)",
|
||||
"other": "bestelakoa",
|
||||
"ep": "ep"
|
||||
},
|
||||
"secondary": {
|
||||
"compilation": "konpilazioa",
|
||||
"audiobook": "audioliburua",
|
||||
"interview": "elkarrizketa",
|
||||
"remix": "nahasketa"
|
||||
}
|
||||
},
|
||||
"datetime": {
|
||||
"minuteShort": "m",
|
||||
"secondShort": "s",
|
||||
"hourShort": "h",
|
||||
"dayShort": "d"
|
||||
},
|
||||
"queryBuilder": {
|
||||
"customTags": "etiketa pertsonalizatutak"
|
||||
},
|
||||
"filterOperator": {
|
||||
"is": "da"
|
||||
},
|
||||
"visualizer": {
|
||||
"general": "Orokorra",
|
||||
"mode": "Modua",
|
||||
"vertical": "Bertikala",
|
||||
"horizontal": "Horizontala",
|
||||
"position": "Posizioa",
|
||||
"level": "Maila",
|
||||
"remove": "Kendu",
|
||||
"custom": "Pertsonalizatua",
|
||||
"builtIn": "Barneratua",
|
||||
"colors": "Koloreak",
|
||||
"gradient": "Gradientea",
|
||||
"fft": "FFT",
|
||||
"sensitivity": "Sentikortasuna",
|
||||
"smoothing": "Leuntzea",
|
||||
"gravity": "Grabitatea",
|
||||
"radial": "Erradiala",
|
||||
"radius": "Erradioa",
|
||||
"mirror": "Ispilua",
|
||||
"options": {
|
||||
"colorMode": {
|
||||
"gradient": "Gradientea",
|
||||
"barIndex": "Barra-indizea",
|
||||
"barLevel": "Barra-maila"
|
||||
},
|
||||
"gradient": {
|
||||
"classic": "Klasikoa",
|
||||
"prism": "Prisma",
|
||||
"rainbow": "Ostadarra"
|
||||
},
|
||||
"weightingFilter": {
|
||||
"none": "Bat ere ez",
|
||||
"a": "A",
|
||||
"b": "B",
|
||||
"c": "C",
|
||||
"d": "D",
|
||||
"z": "Z"
|
||||
}
|
||||
},
|
||||
"opacity": "Opakotasuna"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user