diff --git a/.gitattributes b/.gitattributes index 1bea4dc8f..c5b9ea10e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1 @@ -/build/commit_hash.js export-subst +/build/webpack.prod.conf.js export-subst diff --git a/.gitignore b/.gitignore index 01ffda9a8..0d5befd28 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,5 @@ test/e2e/reports selenium-debug.log .idea/ config/local.json -src/assets/emoji.json +static/emoji.json logs/ -__screenshots__/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 99c85dd36..de3beccca 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -50,15 +50,10 @@ test: APT_CACHE_DIR: apt-cache script: - mkdir -pv $APT_CACHE_DIR && apt-get -qq update + - apt install firefox-esr -y --no-install-recommends + - firefox --version - yarn - - yarn playwright install firefox - - yarn playwright install-deps - - yarn unit-ci - artifacts: - # When the test fails, upload screenshots for better context on why it fails - paths: - - test/**/__screenshots__ - when: on_failure + - yarn unit build: stage: build diff --git a/build/build.js b/build/build.js new file mode 100644 index 000000000..e84122888 --- /dev/null +++ b/build/build.js @@ -0,0 +1,43 @@ +// https://github.com/shelljs/shelljs +import('./check-versions.mjs').then(m => m.default()) +require('shelljs/global') +env.NODE_ENV = 'production' + +var path = require('path') +var config = require('../config') +var ora = require('ora') +var webpack = require('webpack') +var webpackConfig = require('./webpack.prod.conf') + +console.info( + ' Tip:\n' + + ' Built files are meant to be served over an HTTP server.\n' + + ' Opening index.html over file:// won\'t work.\n' +) + +var spinner = ora('building for production...') +spinner.start() + +var updateEmoji = require('./update-emoji').updateEmoji +updateEmoji() + +var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory) +rm('-rf', assetsPath) +mkdir('-p', assetsPath) +cp('-R', 'static/*', assetsPath) + +webpack(webpackConfig, function (err, stats) { + spinner.stop() + if (err) throw err + process.stdout.write(stats.toString({ + colors: true, + modules: false, + children: false, + chunks: false, + chunkModules: false + }) + '\n') + if (stats.hasErrors()) { + console.error('See above for errors.') + process.exit(1) + } +}) diff --git a/build/commit_hash.js b/build/commit_hash.js deleted file mode 100644 index c104af5d9..000000000 --- a/build/commit_hash.js +++ /dev/null @@ -1,18 +0,0 @@ -import childProcess from 'child_process' - -export const getCommitHash = (() => { - const subst = "$Format:%h$" - if(!subst.match(/Format:/)) { - return subst - } else { - try { - return childProcess - .execSync('git rev-parse --short HEAD') - .toString() - .trim() - } catch (e) { - console.error('Failed run git:', e) - return 'UNKNOWN' - } - } -}) diff --git a/build/copy_plugin.js b/build/copy_plugin.js deleted file mode 100644 index a783fe7ff..000000000 --- a/build/copy_plugin.js +++ /dev/null @@ -1,40 +0,0 @@ -import serveStatic from 'serve-static' -import { resolve } from 'node:path' -import { cp } from 'node:fs/promises' - -const getPrefix = s => { - const padEnd = s.endsWith('/') ? s : s + '/' - return padEnd.startsWith('/') ? padEnd : '/' + padEnd -} - -const copyPlugin = ({ inUrl, inFs }) => { - const prefix = getPrefix(inUrl) - const subdir = prefix.slice(1) - let copyTarget - const handler = serveStatic(inFs) - - return [{ - name: 'copy-plugin-serve', - apply: 'serve', - configureServer (server) { - server.middlewares.use(prefix, handler) - } - }, { - name: 'copy-plugin-build', - apply: 'build', - configResolved (config) { - copyTarget = resolve(config.root, config.build.outDir, subdir) - }, - closeBundle: { - order: 'post', - sequential: true, - async handler () { - console.log(`Copying '${inFs}' to ${copyTarget}...`) - await cp(inFs, copyTarget, { recursive: true }) - console.log('Done.') - } - } - }] -} - -export default copyPlugin diff --git a/build/dev-client.js b/build/dev-client.js new file mode 100644 index 000000000..18aa1e219 --- /dev/null +++ b/build/dev-client.js @@ -0,0 +1,9 @@ +/* eslint-disable */ +require('eventsource-polyfill') +var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') + +hotClient.subscribe(function (event) { + if (event.action === 'reload') { + window.location.reload() + } +}) diff --git a/build/dev-server.js b/build/dev-server.js new file mode 100644 index 000000000..8e3b3a12c --- /dev/null +++ b/build/dev-server.js @@ -0,0 +1,82 @@ +import('./check-versions.mjs').then(m => m.default()) +var config = require('../config') +if (!process.env.NODE_ENV) process.env.NODE_ENV = config.dev.env +var path = require('path') +var express = require('express') +var webpack = require('webpack') +var opn = require('opn') +var proxyMiddleware = require('http-proxy-middleware') +var webpackConfig = process.env.NODE_ENV === 'testing' + ? require('./webpack.prod.conf') + : require('./webpack.dev.conf') + +var updateEmoji = require('./update-emoji').updateEmoji +updateEmoji() + +// default port where dev server listens for incoming traffic +var port = process.env.PORT || config.dev.port +// Define HTTP proxies to your custom API backend +// https://github.com/chimurai/http-proxy-middleware +var proxyTable = config.dev.proxyTable + +var app = express() +var compiler = webpack(webpackConfig) + +var devMiddleware = require('webpack-dev-middleware')(compiler, { + publicPath: webpackConfig.output.publicPath, + writeToDisk: true, + stats: { + colors: true, + chunks: false + } +}) + +var hotMiddleware = require('webpack-hot-middleware')(compiler) + +// FIXME: The statement below gives error about hooks being required in webpack 5. +// force page reload when html-webpack-plugin template changes +// compiler.plugin('compilation', function (compilation) { +// compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { +// // FIXME: This supposed to reload whole page when index.html is changed, +// // however now it reloads entire page on every breath, i suppose the order +// // of plugins changed or something. It's a minor thing and douesn't hurt +// // disabling it, constant reloads hurt much more + +// // hotMiddleware.publish({ action: 'reload' }) +// // cb() +// }) +// }) + +// proxy api requests +Object.keys(proxyTable).forEach(function (context) { + var options = proxyTable[context] + if (typeof options === 'string') { + options = { target: options } + } + options.pathFilter = context + app.use(proxyMiddleware.createProxyMiddleware(options)) +}) + +// handle fallback for HTML5 history API +app.use(require('connect-history-api-fallback')()) + +// serve webpack bundle output +app.use(devMiddleware) + +// enable hot-reload and state-preserving +// compilation error display +app.use(hotMiddleware) + +// serve pure static assets +var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) +app.use(staticPath, express.static('./static')) + +module.exports = app.listen(port, function (err) { + if (err) { + console.error(err) + return + } + var uri = 'http://localhost:' + port + console.info('Listening at ' + uri + '\n') + // opn(uri) +}) diff --git a/build/service_worker_messages.js b/build/service_worker_messages.js deleted file mode 100644 index c078e8563..000000000 --- a/build/service_worker_messages.js +++ /dev/null @@ -1,30 +0,0 @@ -import { languages, langCodeToJsonName } from '../src/i18n/languages.js' -import { readFile } from 'node:fs/promises' -import { dirname, resolve } from 'node:path' -import { fileURLToPath } from 'node:url' - -const i18nDir = resolve( - dirname(dirname(fileURLToPath(import.meta.url))), - 'src/i18n' -) - -export const i18nFiles = languages.reduce((acc, lang) => { - const name = langCodeToJsonName(lang) - const file = resolve(i18nDir, name + '.json') - acc[lang] = file - return acc -}, {}) - -export const generateServiceWorkerMessages = async () => { - const msgArray = await Promise.all(Object.entries(i18nFiles).map(async ([lang, file]) => { - const fileContent = await readFile(file, 'utf-8') - const msg = { - notifications: JSON.parse(fileContent).notifications || {} - } - return [lang, msg] - })) - return msgArray.reduce((acc, [lang, msg]) => { - acc[lang] = msg - return acc - }, {}) -} diff --git a/build/sw_plugin.js b/build/sw_plugin.js deleted file mode 100644 index 90ab856ad..000000000 --- a/build/sw_plugin.js +++ /dev/null @@ -1,163 +0,0 @@ -import { fileURLToPath } from 'node:url' -import { dirname, resolve } from 'node:path' -import { readFile } from 'node:fs/promises' -import { build } from 'vite' -import * as esbuild from 'esbuild' -import { generateServiceWorkerMessages, i18nFiles } from './service_worker_messages.js' - -const getSWMessagesAsText = async () => { - const messages = await generateServiceWorkerMessages() - return `export default ${JSON.stringify(messages, undefined, 2)}` -} -const projectRoot = dirname(dirname(fileURLToPath(import.meta.url))) - -export const devSwPlugin = ({ - swSrc, - swDest, - transformSW, - alias -}) => { - const swFullSrc = resolve(projectRoot, swSrc) - const esbuildAlias = {} - Object.entries(alias).forEach(([source, dest]) => { - esbuildAlias[source] = dest.startsWith('/') ? projectRoot + dest : dest - }) - - return { - name: 'dev-sw-plugin', - apply: 'serve', - configResolved (conf) { - }, - resolveId (id) { - const name = id.startsWith('/') ? id.slice(1) : id - if (name === swDest) { - return swFullSrc - } - return null - }, - async load (id) { - if (id === swFullSrc) { - return readFile(swFullSrc, 'utf-8') - } - return null - }, - /** - * vite does not bundle the service worker - * during dev, and firefox does not support ESM as service worker - * https://bugzilla.mozilla.org/show_bug.cgi?id=1360870 - */ - async transform (code, id) { - if (id === swFullSrc && transformSW) { - const res = await esbuild.build({ - entryPoints: [swSrc], - bundle: true, - write: false, - outfile: 'sw-pleroma.js', - alias: esbuildAlias, - plugins: [{ - name: 'vite-like-root-resolve', - setup (b) { - b.onResolve( - { filter: new RegExp(/^\//) }, - args => ({ - path: resolve(projectRoot, args.path.slice(1)) - }) - ) - } - }, { - name: 'sw-messages', - setup (b) { - b.onResolve( - { filter: new RegExp('^' + swMessagesName + '$') }, - args => ({ - path: args.path, - namespace: 'sw-messages' - })) - b.onLoad( - { filter: /.*/, namespace: 'sw-messages' }, - async () => ({ - contents: await getSWMessagesAsText() - })) - } - }] - }) - const text = res.outputFiles[0].text - return text - } - } - } -} - -// Idea taken from -// https://github.com/vite-pwa/vite-plugin-pwa/blob/main/src/plugins/build.ts -// rollup does not support compiling to iife if we want to code-split; -// however, we must compile the service worker to iife because of browser support. -// Run another vite build just for the service worker targeting iife at -// the end of the build. -export const buildSwPlugin = ({ - swSrc, - swDest, -}) => { - let config - return { - name: 'build-sw-plugin', - enforce: 'post', - apply: 'build', - configResolved (resolvedConfig) { - config = { - define: resolvedConfig.define, - resolve: resolvedConfig.resolve, - plugins: [swMessagesPlugin()], - publicDir: false, - build: { - ...resolvedConfig.build, - lib: { - entry: swSrc, - formats: ['iife'], - name: 'sw_pleroma' - }, - emptyOutDir: false, - rollupOptions: { - output: { - entryFileNames: swDest - } - } - }, - configFile: false - } - }, - closeBundle: { - order: 'post', - sequential: true, - async handler () { - console.log('Building service worker for production') - await build(config) - } - } - } -} - -const swMessagesName = 'virtual:pleroma-fe/service_worker_messages' -const swMessagesNameResolved = '\0' + swMessagesName - -export const swMessagesPlugin = () => { - return { - name: 'sw-messages-plugin', - resolveId (id) { - if (id === swMessagesName) { - Object.values(i18nFiles).forEach(f => { - this.addWatchFile(f) - }) - return swMessagesNameResolved - } else { - return null - } - }, - async load (id) { - if (id === swMessagesNameResolved) { - return await getSWMessagesAsText() - } - return null - } - } -} diff --git a/build/update-emoji.js b/build/update-emoji.js index 5d578ba61..9f4b4e67a 100644 --- a/build/update-emoji.js +++ b/build/update-emoji.js @@ -1,23 +1,27 @@ -import emojis from '@kazvmoe-infra/unicode-emoji-json/data-by-group.json' with { type: 'json' } -import fs from 'fs' +module.exports = { + updateEmoji () { + const emojis = require('@kazvmoe-infra/unicode-emoji-json/data-by-group') + const fs = require('fs') -Object.keys(emojis) - .map(k => { - emojis[k].map(e => { - delete e.unicode_version - delete e.emoji_version - delete e.skin_tone_support_unicode_version - }) - }) + Object.keys(emojis) + .map(k => { + emojis[k].map(e => { + delete e.unicode_version + delete e.emoji_version + delete e.skin_tone_support_unicode_version + }) + }) -const res = {} -Object.keys(emojis) - .map(k => { - const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase() - res[groupId] = emojis[k] - }) + const res = {} + Object.keys(emojis) + .map(k => { + const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase() + res[groupId] = emojis[k] + }) -console.info('Updating emojis...') -fs.writeFileSync('src/assets/emoji.json', JSON.stringify(res)) -console.info('Done.') + console.info('Updating emojis...') + fs.writeFileSync('static/emoji.json', JSON.stringify(res)) + console.info('Done.') + } +} diff --git a/build/utils.js b/build/utils.js new file mode 100644 index 000000000..02fa2f724 --- /dev/null +++ b/build/utils.js @@ -0,0 +1,55 @@ +var path = require('path') +var config = require('../config') +var sass = require('sass') +var MiniCssExtractPlugin = require('mini-css-extract-plugin') + +exports.assetsPath = function (_path) { + var assetsSubDirectory = process.env.NODE_ENV === 'production' + ? config.build.assetsSubDirectory + : config.dev.assetsSubDirectory + return path.posix.join(assetsSubDirectory, _path) +} + +exports.cssLoaders = function (options) { + options = options || {} + + function generateLoaders (loaders) { + // Extract CSS when that option is specified + // (which is the case during production build) + if (options.extract) { + return [MiniCssExtractPlugin.loader].concat(loaders) + } else { + return ['vue-style-loader'].concat(loaders) + } + } + + // http://vuejs.github.io/vue-loader/configurations/extract-css.html + return [ + { + test: /\.(post)?css$/, + use: generateLoaders(['css-loader', 'postcss-loader']), + }, + { + test: /\.less$/, + use: generateLoaders(['css-loader', 'postcss-loader', 'less-loader']), + }, + { + test: /\.scss$/, + use: generateLoaders([ + 'css-loader', + 'postcss-loader', + { + loader: 'sass-loader', + options: { + api: 'modern' + } + } + ]) + } + ] +} + +// Generate loaders for standalone style files (outside of .vue) +exports.styleLoaders = function (options) { + return exports.cssLoaders(options) +} diff --git a/build/webpack.base.conf.js b/build/webpack.base.conf.js new file mode 100644 index 000000000..72b4ac0a6 --- /dev/null +++ b/build/webpack.base.conf.js @@ -0,0 +1,130 @@ +var path = require('path') +var config = require('../config') +var utils = require('./utils') +var projectRoot = path.resolve(__dirname, '../') +var ServiceWorkerWebpackPlugin = require('serviceworker-webpack5-plugin') +var CopyPlugin = require('copy-webpack-plugin'); +var { VueLoaderPlugin } = require('vue-loader') +var ESLintPlugin = require('eslint-webpack-plugin'); +var StylelintPlugin = require('stylelint-webpack-plugin'); + +var env = process.env.NODE_ENV +// check env & config/index.js to decide weither to enable CSS Sourcemaps for the +// various preprocessor loaders added to vue-loader at the end of this file +var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap) +var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap) +var useCssSourceMap = cssSourceMapDev || cssSourceMapProd + +var now = Date.now() + +module.exports = { + entry: { + app: './src/main.js' + }, + output: { + path: config.build.assetsRoot, + publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath, + filename: '[name].js', + chunkFilename: '[name].js' + }, + optimization: { + splitChunks: { + chunks: 'all' + } + }, + resolve: { + extensions: ['.mjs', '.js', '.jsx', '.vue'], + modules: [ + path.join(__dirname, '../node_modules') + ], + alias: { + 'static': path.resolve(__dirname, '../static'), + 'src': path.resolve(__dirname, '../src'), + 'assets': path.resolve(__dirname, '../src/assets'), + 'components': path.resolve(__dirname, '../src/components'), + 'vue-i18n': 'vue-i18n/dist/vue-i18n.runtime.esm-bundler.js' + }, + fallback: { + 'querystring': require.resolve('querystring-es3'), + 'url': require.resolve('url/') + } + }, + module: { + noParse: /node_modules\/localforage\/dist\/localforage.js/, + rules: [ + { + enforce: 'post', + test: /\.(json5?|ya?ml)$/, // target json, json5, yaml and yml files + type: 'javascript/auto', + loader: '@intlify/vue-i18n-loader', + include: [ // Use `Rule.include` to specify the files of locale messages to be pre-compiled + path.resolve(__dirname, '../src/i18n') + ] + }, + { + test: /\.vue$/, + loader: 'vue-loader', + options: { + compilerOptions: { + isCustomElement(tag) { + if (tag === 'pinch-zoom') { + return true + } + return false + } + } + } + }, + { + test: /\.jsx?$/, + include: projectRoot, + exclude: /node_modules\/(?!tributejs)/, + use: 'babel-loader' + }, + { + test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, + type: 'asset', + generator: { + filename: utils.assetsPath('img/[name].[hash:7][ext]') + } + }, + { + test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, + type: 'asset', + generator: { + filename: utils.assetsPath('fonts/[name].[hash:7][ext]') + } + }, + { + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto' + } + ] + }, + plugins: [ + new ServiceWorkerWebpackPlugin({ + entry: path.join(__dirname, '..', 'src/sw.js'), + filename: 'sw-pleroma.js' + }), + new ESLintPlugin({ + formatter: require('eslint-formatter-friendly'), + overrideConfigFile: path.resolve(__dirname, '..', 'eslint.config.mjs'), + configType: 'flat' + }), + new StylelintPlugin({}), + new VueLoaderPlugin(), + // This copies Ruffle's WASM to a directory so that JS side can access it + new CopyPlugin({ + patterns: [ + { + from: "node_modules/@ruffle-rs/ruffle/**/*", + to: "static/ruffle/[name][ext]" + }, + ], + options: { + concurrency: 100, + }, + }) + ] +} diff --git a/build/webpack.dev.conf.js b/build/webpack.dev.conf.js new file mode 100644 index 000000000..2369e0e87 --- /dev/null +++ b/build/webpack.dev.conf.js @@ -0,0 +1,38 @@ +var config = require('../config') +var webpack = require('webpack') +var merge = require('webpack-merge') +var utils = require('./utils') +var baseWebpackConfig = require('./webpack.base.conf') +var HtmlWebpackPlugin = require('html-webpack-plugin') + +// add hot-reload related code to entry chunks +Object.keys(baseWebpackConfig.entry).forEach(function (name) { + baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) +}) + +module.exports = merge(baseWebpackConfig, { + module: { + rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) + }, + mode: 'development', + // eval-source-map is faster for development + devtool: 'eval-source-map', + plugins: [ + new webpack.DefinePlugin({ + 'process.env': config.dev.env, + 'COMMIT_HASH': JSON.stringify('DEV'), + 'DEV_OVERRIDES': JSON.stringify(config.dev.settings), + '__VUE_OPTIONS_API__': true, + '__VUE_PROD_DEVTOOLS__': false, + '__VUE_PROD_HYDRATION_MISMATCH_DETAILS__': false + }), + // https://github.com/glenjamin/webpack-hot-middleware#installation--usage + new webpack.HotModuleReplacementPlugin(), + // https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', + template: 'index.html', + inject: true + }) + ] +}) diff --git a/build/webpack.prod.conf.js b/build/webpack.prod.conf.js new file mode 100644 index 000000000..4282e4bd0 --- /dev/null +++ b/build/webpack.prod.conf.js @@ -0,0 +1,105 @@ +var path = require('path') +var config = require('../config') +var utils = require('./utils') +var webpack = require('webpack') +var merge = require('webpack-merge') +var baseWebpackConfig = require('./webpack.base.conf') +var MiniCssExtractPlugin = require('mini-css-extract-plugin') +const CssMinimizerPlugin = require("css-minimizer-webpack-plugin") +var HtmlWebpackPlugin = require('html-webpack-plugin') +var env = process.env.NODE_ENV === 'testing' + ? require('../config/test.env') + : config.build.env + +let commitHash = (() => { + const subst = "$Format:%h$"; + if(!subst.match(/Format:/)) { + return subst; + } else { + return require('child_process') + .execSync('git rev-parse --short HEAD') + .toString(); + } +})(); + +var webpackConfig = merge(baseWebpackConfig, { + mode: 'production', + module: { + rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, extract: true }) + }, + devtool: config.build.productionSourceMap ? 'source-map' : false, + optimization: { + minimize: true, + splitChunks: { + chunks: 'all' + }, + minimizer: [ + `...`, + new CssMinimizerPlugin() + ] + }, + output: { + path: config.build.assetsRoot, + filename: utils.assetsPath('js/[name].[chunkhash].js'), + chunkFilename: utils.assetsPath('js/[name].[chunkhash].js') + }, + plugins: [ + // http://vuejs.github.io/vue-loader/workflow/production.html + new webpack.DefinePlugin({ + 'process.env': env, + 'COMMIT_HASH': JSON.stringify(commitHash), + 'DEV_OVERRIDES': JSON.stringify(undefined), + '__VUE_OPTIONS_API__': true, + '__VUE_PROD_DEVTOOLS__': false, + '__VUE_PROD_HYDRATION_MISMATCH_DETAILS__': false + }), + // extract css into its own file + new MiniCssExtractPlugin({ + filename: utils.assetsPath('css/[name].[contenthash].css') + }), + // generate dist index.html with correct asset hash for caching. + // you can customize output by editing /index.html + // see https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: process.env.NODE_ENV === 'testing' + ? 'index.html' + : config.build.index, + template: 'index.html', + inject: true, + minify: { + removeComments: true, + collapseWhitespace: true, + removeAttributeQuotes: true, + ignoreCustomComments: [/server-generated-meta/] + // more options: + // https://github.com/kangax/html-minifier#options-quick-reference + } + }), + // split vendor js into its own file + // extract webpack runtime and module manifest to its own file in order to + // prevent vendor hash from being updated whenever app bundle is updated + // new webpack.optimize.SplitChunksPlugin({ + // name: ['app', 'vendor'] + // }), + ] +}) + +if (config.build.productionGzip) { + var CompressionWebpackPlugin = require('compression-webpack-plugin') + + webpackConfig.plugins.push( + new CompressionWebpackPlugin({ + asset: '[path].gz[query]', + algorithm: 'gzip', + test: new RegExp( + '\\.(' + + config.build.productionGzipExtensions.join('|') + + ')$' + ), + threshold: 10240, + minRatio: 0.8 + }) + ) +} + +module.exports = webpackConfig diff --git a/changelog.d/boot-improvements.change b/changelog.d/boot-improvements.change deleted file mode 100644 index 8007d2332..000000000 --- a/changelog.d/boot-improvements.change +++ /dev/null @@ -1 +0,0 @@ -Speed up initial boot. diff --git a/changelog.d/cover-image-path.change b/changelog.d/cover-image-path.change deleted file mode 100644 index 2c9c4d1f3..000000000 --- a/changelog.d/cover-image-path.change +++ /dev/null @@ -1 +0,0 @@ -BREAKING: static/img/nsfw.2958239.png is now static/img/nsfw.DepQPhG0.png, which may affect people who specify exactly this path as the cover image diff --git a/changelog.d/draft-save.fix b/changelog.d/draft-save.fix deleted file mode 100644 index 7e3097e04..000000000 --- a/changelog.d/draft-save.fix +++ /dev/null @@ -1 +0,0 @@ -Fix draft saving when auto-save is off diff --git a/changelog.d/migrate-bookmark-folders-store-pinia.skip b/changelog.d/migrate-bookmark-folders-store-pinia.skip deleted file mode 100644 index e69de29bb..000000000 diff --git a/changelog.d/no-non-esm-script.remove b/changelog.d/no-non-esm-script.remove deleted file mode 100644 index db63f90e2..000000000 --- a/changelog.d/no-non-esm-script.remove +++ /dev/null @@ -1 +0,0 @@ -BREAKING: drop support for browsers that do not support ` +