mirror of
				https://akkoma.dev/lamp/akkoma-fe.git
				synced 2025-11-03 20:22:09 -05:00 
			
		
		
		
	Compare commits
	
		
			No commits in common. "589d8ea29fa86c875a1550244dc8a347c28abde7" and "4d91a7b2c36b3aff82d8988196ec1703709b93f8" have entirely different histories.
		
	
	
		
			589d8ea29f
			...
			4d91a7b2c3
		
	
		
							
								
								
									
										2
									
								
								.eslintignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.eslintignore
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
build/*.js
 | 
			
		||||
config/*.js
 | 
			
		||||
							
								
								
									
										30
									
								
								.eslintrc.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								.eslintrc.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
  root: true,
 | 
			
		||||
  parserOptions: {
 | 
			
		||||
    parser: '@babel/eslint-parser',
 | 
			
		||||
    sourceType: 'module'
 | 
			
		||||
  },
 | 
			
		||||
  // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
 | 
			
		||||
  extends: [
 | 
			
		||||
    'plugin:vue/recommended'
 | 
			
		||||
  ],
 | 
			
		||||
  // required to lint *.vue files
 | 
			
		||||
  plugins: [
 | 
			
		||||
    'vue',
 | 
			
		||||
    'import'
 | 
			
		||||
  ],
 | 
			
		||||
  // add your custom rules here
 | 
			
		||||
  rules: {
 | 
			
		||||
    // allow paren-less arrow functions
 | 
			
		||||
    'arrow-parens': 0,
 | 
			
		||||
    // allow async-await
 | 
			
		||||
    'generator-star-spacing': 0,
 | 
			
		||||
    // allow debugger during development
 | 
			
		||||
    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
 | 
			
		||||
    'vue/require-prop-types': 0,
 | 
			
		||||
    'vue/no-unused-vars': 0,
 | 
			
		||||
    'no-tabs': 0,
 | 
			
		||||
    'vue/multi-word-component-names': 0,
 | 
			
		||||
    'vue/no-reserved-component-names': 0
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								.node-version
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.node-version
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
7.2.1
 | 
			
		||||
@ -1 +0,0 @@
 | 
			
		||||
nodejs 20.12.2
 | 
			
		||||
@ -1,25 +1,24 @@
 | 
			
		||||
labels:
 | 
			
		||||
  platform: linux/amd64
 | 
			
		||||
 | 
			
		||||
steps:
 | 
			
		||||
platform: linux/amd64
 | 
			
		||||
pipeline:
 | 
			
		||||
  lint:
 | 
			
		||||
    when:
 | 
			
		||||
      event:
 | 
			
		||||
      - pull_request
 | 
			
		||||
    image: node:20
 | 
			
		||||
    image: node:18
 | 
			
		||||
    commands:
 | 
			
		||||
    - yarn
 | 
			
		||||
    - yarn lint
 | 
			
		||||
    #- yarn stylelint
 | 
			
		||||
 | 
			
		||||
  test:
 | 
			
		||||
    when:
 | 
			
		||||
      event:
 | 
			
		||||
      - pull_request
 | 
			
		||||
    image: node:20
 | 
			
		||||
    image: node:18
 | 
			
		||||
    commands:
 | 
			
		||||
    - apt update
 | 
			
		||||
    - apt install firefox-esr -y --no-install-recommends
 | 
			
		||||
    - yarn
 | 
			
		||||
    - yarn 
 | 
			
		||||
    - yarn unit
 | 
			
		||||
 | 
			
		||||
  build:
 | 
			
		||||
@ -29,7 +28,7 @@ steps:
 | 
			
		||||
      branch:
 | 
			
		||||
      - develop
 | 
			
		||||
      - stable
 | 
			
		||||
    image: node:20
 | 
			
		||||
    image: node:18
 | 
			
		||||
    commands:
 | 
			
		||||
    - yarn
 | 
			
		||||
    - yarn build
 | 
			
		||||
@ -41,18 +40,15 @@ steps:
 | 
			
		||||
      branch:
 | 
			
		||||
      - develop
 | 
			
		||||
      - stable
 | 
			
		||||
    image: node:20
 | 
			
		||||
    environment:
 | 
			
		||||
      SCW_ACCESS_KEY:
 | 
			
		||||
        from_secret: SCW_ACCESS_KEY
 | 
			
		||||
      SCW_SECRET_KEY:
 | 
			
		||||
        from_secret: SCW_SECRET_KEY
 | 
			
		||||
      SCW_DEFAULT_ORGANIZATION_ID:
 | 
			
		||||
        from_secret: SCW_DEFAULT_ORGANIZATION_ID
 | 
			
		||||
    image: node:18
 | 
			
		||||
    secrets:
 | 
			
		||||
    - SCW_ACCESS_KEY
 | 
			
		||||
    - SCW_SECRET_KEY
 | 
			
		||||
    - SCW_DEFAULT_ORGANIZATION_ID
 | 
			
		||||
    commands:
 | 
			
		||||
      - apt-get update && apt-get install -y rclone wget zip
 | 
			
		||||
      - wget https://github.com/scaleway/scaleway-cli/releases/download/v2.30.0/scaleway-cli_2.30.0_linux_amd64
 | 
			
		||||
      - mv scaleway-cli_2.30.0_linux_amd64 scaleway-cli
 | 
			
		||||
      - wget https://github.com/scaleway/scaleway-cli/releases/download/v2.5.1/scaleway-cli_2.5.1_linux_amd64
 | 
			
		||||
      - mv scaleway-cli_2.5.1_linux_amd64 scaleway-cli
 | 
			
		||||
      - chmod +x scaleway-cli
 | 
			
		||||
      - ./scaleway-cli object config install type=rclone
 | 
			
		||||
      - zip akkoma-fe.zip -r dist
 | 
			
		||||
@ -67,17 +63,15 @@ steps:
 | 
			
		||||
      - stable
 | 
			
		||||
    environment:
 | 
			
		||||
      CI: "true"
 | 
			
		||||
      SCW_ACCESS_KEY:
 | 
			
		||||
        from_secret: SCW_ACCESS_KEY
 | 
			
		||||
      SCW_SECRET_KEY:
 | 
			
		||||
        from_secret: SCW_SECRET_KEY
 | 
			
		||||
      SCW_DEFAULT_ORGANIZATION_ID:
 | 
			
		||||
        from_secret: SCW_DEFAULT_ORGANIZATION_ID
 | 
			
		||||
    image: python:3.10-slim
 | 
			
		||||
    secrets:
 | 
			
		||||
    - SCW_ACCESS_KEY
 | 
			
		||||
    - SCW_SECRET_KEY
 | 
			
		||||
    - SCW_DEFAULT_ORGANIZATION_ID
 | 
			
		||||
    commands:
 | 
			
		||||
    - apt-get update && apt-get install -y rclone wget git zip
 | 
			
		||||
    - wget https://github.com/scaleway/scaleway-cli/releases/download/v2.30.0/scaleway-cli_2.30.0_linux_amd64
 | 
			
		||||
    - mv scaleway-cli_2.30.0_linux_amd64 scaleway-cli
 | 
			
		||||
    - wget https://github.com/scaleway/scaleway-cli/releases/download/v2.5.1/scaleway-cli_2.5.1_linux_amd64
 | 
			
		||||
    - mv scaleway-cli_2.5.1_linux_amd64 scaleway-cli
 | 
			
		||||
    - chmod +x scaleway-cli
 | 
			
		||||
    - ./scaleway-cli object config install type=rclone
 | 
			
		||||
    - cd docs
 | 
			
		||||
@ -85,4 +79,4 @@ steps:
 | 
			
		||||
    - mkdocs build
 | 
			
		||||
    - zip -r docs.zip site/*
 | 
			
		||||
    - cd site
 | 
			
		||||
    - rclone copy . scaleway:akkoma-docs/frontend/$CI_COMMIT_BRANCH/ 
 | 
			
		||||
    - rclone copy . scaleway:akkoma-docs/frontend/$CI_COMMIT_BRANCH/
 | 
			
		||||
 | 
			
		||||
@ -20,8 +20,6 @@ To use Akkoma-FE in Akkoma, use the [frontend](https://docs.akkoma.dev/stable/ad
 | 
			
		||||
 | 
			
		||||
## Build Setup
 | 
			
		||||
 | 
			
		||||
Make sure you have [Node.js](https://nodejs.org/) installed. You can check `/.woodpecker.yml` for which node version the Akkoma CI currently uses.
 | 
			
		||||
 | 
			
		||||
``` bash
 | 
			
		||||
# install dependencies
 | 
			
		||||
corepack enable
 | 
			
		||||
 | 
			
		||||
@ -1,36 +1,36 @@
 | 
			
		||||
// https://github.com/shelljs/shelljs
 | 
			
		||||
require("./check-versions")();
 | 
			
		||||
require("shelljs/global");
 | 
			
		||||
env.NODE_ENV = "production";
 | 
			
		||||
require('./check-versions')()
 | 
			
		||||
require('shelljs/global')
 | 
			
		||||
env.NODE_ENV = 'production'
 | 
			
		||||
 | 
			
		||||
var path = require("path");
 | 
			
		||||
var config = require("../config");
 | 
			
		||||
var webpack = require("webpack");
 | 
			
		||||
var webpackConfig = require("./webpack.prod.conf");
 | 
			
		||||
var path = require('path')
 | 
			
		||||
var config = require('../config')
 | 
			
		||||
var ora = require('ora')
 | 
			
		||||
var webpack = require('webpack')
 | 
			
		||||
var webpackConfig = require('./webpack.prod.conf')
 | 
			
		||||
 | 
			
		||||
console.log(
 | 
			
		||||
  "  Tip:\n" +
 | 
			
		||||
    "  Built files are meant to be served over an HTTP server.\n" +
 | 
			
		||||
    "  Opening index.html over file:// won't work.\n",
 | 
			
		||||
);
 | 
			
		||||
  '  Tip:\n' +
 | 
			
		||||
  '  Built files are meant to be served over an HTTP server.\n' +
 | 
			
		||||
  '  Opening index.html over file:// won\'t work.\n'
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var assetsPath = path.join(
 | 
			
		||||
  config.build.assetsRoot,
 | 
			
		||||
  config.build.assetsSubDirectory,
 | 
			
		||||
);
 | 
			
		||||
rm("-rf", assetsPath);
 | 
			
		||||
mkdir("-p", assetsPath);
 | 
			
		||||
cp("-R", "static/*", assetsPath);
 | 
			
		||||
var spinner = ora('building for production...')
 | 
			
		||||
spinner.start()
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
  if (err) throw err;
 | 
			
		||||
  process.stdout.write(
 | 
			
		||||
    stats.toString({
 | 
			
		||||
      colors: true,
 | 
			
		||||
      modules: false,
 | 
			
		||||
      children: false,
 | 
			
		||||
      chunks: false,
 | 
			
		||||
      chunkModules: false,
 | 
			
		||||
    }) + "\n",
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
  spinner.stop()
 | 
			
		||||
  if (err) throw err
 | 
			
		||||
  process.stdout.write(stats.toString({
 | 
			
		||||
    colors: true,
 | 
			
		||||
    modules: false,
 | 
			
		||||
    children: false,
 | 
			
		||||
    chunks: false,
 | 
			
		||||
    chunkModules: false
 | 
			
		||||
  }) + '\n')
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ var path = require('path')
 | 
			
		||||
var express = require('express')
 | 
			
		||||
var webpack = require('webpack')
 | 
			
		||||
var opn = require('opn')
 | 
			
		||||
const { createProxyMiddleware } = require('http-proxy-middleware');
 | 
			
		||||
var proxyMiddleware = require('http-proxy-middleware')
 | 
			
		||||
var webpackConfig = process.env.NODE_ENV === 'testing'
 | 
			
		||||
  ? require('./webpack.prod.conf')
 | 
			
		||||
  : require('./webpack.dev.conf')
 | 
			
		||||
@ -36,13 +36,7 @@ Object.keys(proxyTable).forEach(function (context) {
 | 
			
		||||
  if (typeof options === 'string') {
 | 
			
		||||
    options = { target: options }
 | 
			
		||||
  }
 | 
			
		||||
  const targetUrl = new URL(options.target);
 | 
			
		||||
  // add path
 | 
			
		||||
  targetUrl.pathname = context;
 | 
			
		||||
  options.target = targetUrl.toString();
 | 
			
		||||
 | 
			
		||||
  console.log("Proxying", context, "to", options.target);
 | 
			
		||||
  app.use(context, createProxyMiddleware(options))
 | 
			
		||||
  app.use(proxyMiddleware(context, options))
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// handle fallback for HTML5 history API
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@ var config = require('../config')
 | 
			
		||||
var utils = require('./utils')
 | 
			
		||||
var projectRoot = path.resolve(__dirname, '../')
 | 
			
		||||
var { VueLoaderPlugin } = require('vue-loader')
 | 
			
		||||
const ESLintPlugin = require('eslint-webpack-plugin');
 | 
			
		||||
 | 
			
		||||
var env = process.env.NODE_ENV
 | 
			
		||||
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
 | 
			
		||||
@ -36,7 +35,6 @@ module.exports = {
 | 
			
		||||
    ],
 | 
			
		||||
    fallback: {
 | 
			
		||||
      "url": require.resolve("url/"),
 | 
			
		||||
      querystring: require.resolve("querystring-es3")
 | 
			
		||||
    },
 | 
			
		||||
    alias: {
 | 
			
		||||
      'static': path.resolve(__dirname, '../static'),
 | 
			
		||||
@ -49,6 +47,20 @@ module.exports = {
 | 
			
		||||
  module: {
 | 
			
		||||
    noParse: /node_modules\/localforage\/dist\/localforage.js/,
 | 
			
		||||
    rules: [
 | 
			
		||||
      {
 | 
			
		||||
        enforce: 'pre',
 | 
			
		||||
        test: /\.(js|vue)$/,
 | 
			
		||||
        include: projectRoot,
 | 
			
		||||
        exclude: /node_modules/,
 | 
			
		||||
        use: {
 | 
			
		||||
          loader: 'eslint-loader',
 | 
			
		||||
          options: {
 | 
			
		||||
            formatter: require('eslint-friendly-formatter'),
 | 
			
		||||
            sourceMap: config.build.productionSourceMap,
 | 
			
		||||
            extract: true
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        enforce: 'post',
 | 
			
		||||
        test: /\.(json5?|ya?ml)$/, // target json, json5, yaml and yml files
 | 
			
		||||
@ -106,9 +118,6 @@ module.exports = {
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  plugins: [
 | 
			
		||||
    new VueLoaderPlugin(),
 | 
			
		||||
    new ESLintPlugin({
 | 
			
		||||
      configType: 'flat'
 | 
			
		||||
    })
 | 
			
		||||
    new VueLoaderPlugin()
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,4 +2,5 @@ var { merge } = require('webpack-merge')
 | 
			
		||||
var devEnv = require('./dev.env')
 | 
			
		||||
 | 
			
		||||
module.exports = merge(devEnv, {
 | 
			
		||||
  NODE_ENV: '"testing"'
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -15,13 +15,12 @@ put a file that looks like this
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "myPack": "/static/stickers/myPack/"
 | 
			
		||||
  "myPack": "/static/stickers/myPack"
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This file is a mapping from name to pack directory location. It says "we have a pack called myPack, look for
 | 
			
		||||
it inside `/static/stickers/myPack`". You can add as many packs as you like in this manner.
 | 
			
		||||
Note that a single leading and a trailing slash are **required** to work correctly!
 | 
			
		||||
it at `/static/stickers/myPack`". You can add as many packs as you like in this manner.
 | 
			
		||||
 | 
			
		||||
## Creating the pack
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,31 +0,0 @@
 | 
			
		||||
const pluginVue = require('eslint-plugin-vue')
 | 
			
		||||
const pluginImport = require('eslint-plugin-import')
 | 
			
		||||
 | 
			
		||||
module.exports = [
 | 
			
		||||
  ...pluginVue.configs['flat/recommended'],
 | 
			
		||||
  {
 | 
			
		||||
    languageOptions: {
 | 
			
		||||
      parserOptions: {
 | 
			
		||||
        parser: '@babel/eslint-parser',
 | 
			
		||||
        sourceType: 'module'
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    rules: {
 | 
			
		||||
      // allow paren-less arrow functions
 | 
			
		||||
      'arrow-parens': 0,
 | 
			
		||||
      // allow async-await
 | 
			
		||||
      'generator-star-spacing': 0,
 | 
			
		||||
      // allow debugger during development
 | 
			
		||||
      'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
 | 
			
		||||
      'vue/require-prop-types': 0,
 | 
			
		||||
      'vue/no-unused-vars': 0,
 | 
			
		||||
      'no-tabs': 0,
 | 
			
		||||
      'vue/multi-word-component-names': 0,
 | 
			
		||||
      'vue/no-reserved-component-names': 0
 | 
			
		||||
    },
 | 
			
		||||
    ignores: [
 | 
			
		||||
      'build/*.js',
 | 
			
		||||
      'config/*.js'
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
@ -6,6 +6,7 @@
 | 
			
		||||
    <title>Akkoma</title>
 | 
			
		||||
    <link rel="stylesheet" href="/static/font/tiresias.css">
 | 
			
		||||
    <link rel="stylesheet" href="/static/font/css/lato.css">
 | 
			
		||||
    <link rel="stylesheet" href="/static/mfm.css">
 | 
			
		||||
    <link rel="stylesheet" href="/static/custom.css">
 | 
			
		||||
    <link rel="stylesheet" href="/static/theme-holder.css" id="theme-holder">
 | 
			
		||||
    <!--server-generated-meta-->
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										164
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										164
									
								
								package.json
									
									
									
									
									
								
							@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "pleroma_fe",
 | 
			
		||||
  "version": "3.12.0",
 | 
			
		||||
  "version": "3.10.0",
 | 
			
		||||
  "description": "A frontend for Akkoma instances",
 | 
			
		||||
  "author": "Roger Braun <roger@rogerbraun.net>",
 | 
			
		||||
  "private": true,
 | 
			
		||||
@ -12,118 +12,120 @@
 | 
			
		||||
    "e2e": "node test/e2e/runner.js",
 | 
			
		||||
    "test": "npm run unit && npm run e2e",
 | 
			
		||||
    "stylelint": "stylelint src/**/*.scss",
 | 
			
		||||
    "lint": "eslint src test/unit/specs test/e2e/specs",
 | 
			
		||||
    "lint-fix": "eslint --fix src test/unit/specs test/e2e/specs"
 | 
			
		||||
    "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs",
 | 
			
		||||
    "lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@babel/runtime": "7.17.8",
 | 
			
		||||
    "@chenfengyuan/vue-qrcode": "^2.0.0",
 | 
			
		||||
    "@chenfengyuan/vue-qrcode": "2.0.0",
 | 
			
		||||
    "@floatingghost/pinch-zoom-element": "^1.3.1",
 | 
			
		||||
    "@fortawesome/fontawesome-svg-core": "^6.5.2",
 | 
			
		||||
    "@fortawesome/free-regular-svg-icons": "^6.5.2",
 | 
			
		||||
    "@fortawesome/free-solid-svg-icons": "^6.5.2",
 | 
			
		||||
    "@fortawesome/vue-fontawesome": "^3.0.8",
 | 
			
		||||
    "@vuelidate/core": "^2.0.3",
 | 
			
		||||
    "@vuelidate/validators": "^2.0.4",
 | 
			
		||||
    "blurhash": "^2.0.5",
 | 
			
		||||
    "body-scroll-lock": "^3.1.5",
 | 
			
		||||
    "chromatism": "^3.0.0",
 | 
			
		||||
    "click-outside-vue3": "^4.0.1",
 | 
			
		||||
    "cropperjs": "^1.6.2",
 | 
			
		||||
    "diff": "^5.2.0",
 | 
			
		||||
    "escape-html": "^1.0.3",
 | 
			
		||||
    "@fortawesome/fontawesome-svg-core": "1.3.0",
 | 
			
		||||
    "@fortawesome/free-regular-svg-icons": "^6.1.2",
 | 
			
		||||
    "@fortawesome/free-solid-svg-icons": "^6.2.0",
 | 
			
		||||
    "@fortawesome/vue-fontawesome": "3.0.1",
 | 
			
		||||
    "@vuelidate/core": "^2.0.0",
 | 
			
		||||
    "@vuelidate/validators": "^2.0.0",
 | 
			
		||||
    "blurhash": "^2.0.4",
 | 
			
		||||
    "body-scroll-lock": "2.7.1",
 | 
			
		||||
    "chromatism": "3.0.0",
 | 
			
		||||
    "click-outside-vue3": "4.0.1",
 | 
			
		||||
    "cropperjs": "1.5.12",
 | 
			
		||||
    "diff": "3.5.0",
 | 
			
		||||
    "escape-html": "1.0.3",
 | 
			
		||||
    "iso-639-1": "^2.1.15",
 | 
			
		||||
    "js-cookie": "^3.0.1",
 | 
			
		||||
    "localforage": "^1.10.0",
 | 
			
		||||
    "localforage": "1.10.0",
 | 
			
		||||
    "parse-link-header": "^2.0.0",
 | 
			
		||||
    "phoenix": "^1.7.12",
 | 
			
		||||
    "punycode.js": "^2.3.1",
 | 
			
		||||
    "qrcode": "^1.5.3",
 | 
			
		||||
    "querystring-es3": "^0.2.1",
 | 
			
		||||
    "url": "^0.11.3",
 | 
			
		||||
    "vue": "^3.4.38",
 | 
			
		||||
    "vue-i18n": "^9.14.0",
 | 
			
		||||
    "vue-router": "^4.4.3",
 | 
			
		||||
    "vue-template-compiler": "^2.7.16",
 | 
			
		||||
    "vuex": "^4.1.0"
 | 
			
		||||
    "phoenix": "1.6.2",
 | 
			
		||||
    "punycode.js": "2.1.0",
 | 
			
		||||
    "qrcode": "1",
 | 
			
		||||
    "url": "^0.11.0",
 | 
			
		||||
    "vue": "^3.2.31",
 | 
			
		||||
    "vue-i18n": "^9.2.2",
 | 
			
		||||
    "vue-router": "4.0.14",
 | 
			
		||||
    "vue-template-compiler": "2.6.11",
 | 
			
		||||
    "vuex": "4.0.2"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@babel/core": "^7.24.6",
 | 
			
		||||
    "@babel/core": "7.17.8",
 | 
			
		||||
    "@babel/eslint-parser": "^7.19.1",
 | 
			
		||||
    "@babel/plugin-transform-runtime": "^7.24.6",
 | 
			
		||||
    "@babel/preset-env": "^7.24.6",
 | 
			
		||||
    "@babel/register": "^7.24.6",
 | 
			
		||||
    "@babel/plugin-transform-runtime": "7.17.0",
 | 
			
		||||
    "@babel/preset-env": "7.16.11",
 | 
			
		||||
    "@babel/register": "7.17.7",
 | 
			
		||||
    "@intlify/vue-i18n-loader": "^5.0.0",
 | 
			
		||||
    "@ungap/event-target": "^0.2.4",
 | 
			
		||||
    "@vue/babel-helper-vue-jsx-merge-props": "^1.4.0",
 | 
			
		||||
    "@vue/babel-plugin-jsx": "^1.2.2",
 | 
			
		||||
    "@ungap/event-target": "0.2.3",
 | 
			
		||||
    "@vue/babel-helper-vue-jsx-merge-props": "1.2.1",
 | 
			
		||||
    "@vue/babel-plugin-jsx": "1.1.1",
 | 
			
		||||
    "@vue/compiler-sfc": "^3.1.0",
 | 
			
		||||
    "@vue/test-utils": "^2.0.2",
 | 
			
		||||
    "autoprefixer": "^10.4.19",
 | 
			
		||||
    "autoprefixer": "6.7.7",
 | 
			
		||||
    "babel-loader": "^9.1.0",
 | 
			
		||||
    "babel-plugin-lodash": "^3.3.4",
 | 
			
		||||
    "babel-plugin-lodash": "3.3.4",
 | 
			
		||||
    "chai": "^4.3.7",
 | 
			
		||||
    "chalk": "^1.1.3",
 | 
			
		||||
    "chromedriver": "^119.0.1",
 | 
			
		||||
    "chalk": "1.1.3",
 | 
			
		||||
    "chromedriver": "^107.0.3",
 | 
			
		||||
    "connect-history-api-fallback": "^2.0.0",
 | 
			
		||||
    "cross-spawn": "^7.0.3",
 | 
			
		||||
    "css-loader": "^7.1.2",
 | 
			
		||||
    "css-loader": "^6.7.2",
 | 
			
		||||
    "custom-event-polyfill": "^1.0.7",
 | 
			
		||||
    "eslint": "^9.3.0",
 | 
			
		||||
    "eslint-config-standard": "^17.1.0",
 | 
			
		||||
    "eslint": "^7.32.0",
 | 
			
		||||
    "eslint-config-standard": "^17.0.0",
 | 
			
		||||
    "eslint-friendly-formatter": "^4.0.1",
 | 
			
		||||
    "eslint-plugin-import": "^2.29.1",
 | 
			
		||||
    "eslint-loader": "^4.0.2",
 | 
			
		||||
    "eslint-plugin-import": "^2.26.0",
 | 
			
		||||
    "eslint-plugin-node": "^11.1.0",
 | 
			
		||||
    "eslint-plugin-promise": "^6.2.0",
 | 
			
		||||
    "eslint-plugin-promise": "^6.1.1",
 | 
			
		||||
    "eslint-plugin-standard": "^5.0.0",
 | 
			
		||||
    "eslint-plugin-vue": "^9.26.0",
 | 
			
		||||
    "eslint-webpack-plugin": "^4.2.0",
 | 
			
		||||
    "eventsource-polyfill": "^0.9.6",
 | 
			
		||||
    "express": "^4.19.2",
 | 
			
		||||
    "eslint-plugin-vue": "^9.7.0",
 | 
			
		||||
    "eventsource-polyfill": "0.9.6",
 | 
			
		||||
    "express": "4.17.3",
 | 
			
		||||
    "file-loader": "^6.2.0",
 | 
			
		||||
    "function-bind": "^1.1.2",
 | 
			
		||||
    "function-bind": "1.1.1",
 | 
			
		||||
    "html-webpack-plugin": "^5.5.0",
 | 
			
		||||
    "http-proxy-middleware": "^3.0.0",
 | 
			
		||||
    "json-loader": "^0.5.7",
 | 
			
		||||
    "karma": "^6.4.3",
 | 
			
		||||
    "karma-coverage": "^2.2.1",
 | 
			
		||||
    "karma-firefox-launcher": "^2.1.3",
 | 
			
		||||
    "karma-mocha": "^2.0.1",
 | 
			
		||||
    "karma-mocha-reporter": "^2.2.5",
 | 
			
		||||
    "karma-sinon-chai": "^2.0.2",
 | 
			
		||||
    "karma-sourcemap-loader": "^0.4.0",
 | 
			
		||||
    "karma-spec-reporter": "^0.0.36",
 | 
			
		||||
    "http-proxy-middleware": "0.21.0",
 | 
			
		||||
    "inject-loader": "2.0.1",
 | 
			
		||||
    "isparta-loader": "2.0.0",
 | 
			
		||||
    "json-loader": "0.5.7",
 | 
			
		||||
    "karma": "6.3.17",
 | 
			
		||||
    "karma-coverage": "1.1.2",
 | 
			
		||||
    "karma-firefox-launcher": "1.3.0",
 | 
			
		||||
    "karma-mocha": "2.0.1",
 | 
			
		||||
    "karma-mocha-reporter": "2.2.5",
 | 
			
		||||
    "karma-sinon-chai": "2.0.2",
 | 
			
		||||
    "karma-sourcemap-loader": "0.3.8",
 | 
			
		||||
    "karma-spec-reporter": "0.0.33",
 | 
			
		||||
    "karma-webpack": "^5.0.0",
 | 
			
		||||
    "lodash": "^4.17.21",
 | 
			
		||||
    "lolex": "^6.0.0",
 | 
			
		||||
    "mini-css-extract-plugin": "^2.9.0",
 | 
			
		||||
    "mocha": "^10.4.0",
 | 
			
		||||
    "nightwatch": "^3.6.3",
 | 
			
		||||
    "opn": "^6.0.0",
 | 
			
		||||
    "lodash": "4.17.21",
 | 
			
		||||
    "lolex": "1.6.0",
 | 
			
		||||
    "mini-css-extract-plugin": "0.12.0",
 | 
			
		||||
    "mocha": "3.5.3",
 | 
			
		||||
    "nightwatch": "0.9.21",
 | 
			
		||||
    "opn": "4.0.2",
 | 
			
		||||
    "ora": "0.4.1",
 | 
			
		||||
    "postcss-html": "^1.5.0",
 | 
			
		||||
    "postcss-loader": "^8.1.1",
 | 
			
		||||
    "postcss-loader": "3.0.0",
 | 
			
		||||
    "postcss-sass": "^0.5.0",
 | 
			
		||||
    "raw-loader": "^4.0.2",
 | 
			
		||||
    "sass": "^1.77.2",
 | 
			
		||||
    "sass-loader": "^14.2.1",
 | 
			
		||||
    "selenium-server": "^3.141.59",
 | 
			
		||||
    "semver": "^7.6.2",
 | 
			
		||||
    "shelljs": "^0.8.5",
 | 
			
		||||
    "sinon": "^18.0.0",
 | 
			
		||||
    "sinon-chai": "^3.7.0",
 | 
			
		||||
    "raw-loader": "0.5.1",
 | 
			
		||||
    "sass": "^1.56.0",
 | 
			
		||||
    "sass-loader": "^13.2.0",
 | 
			
		||||
    "selenium-server": "2.53.1",
 | 
			
		||||
    "semver": "5.7.1",
 | 
			
		||||
    "shelljs": "0.8.5",
 | 
			
		||||
    "sinon": "2.4.1",
 | 
			
		||||
    "sinon-chai": "2.14.0",
 | 
			
		||||
    "stylelint": "^14.15.0",
 | 
			
		||||
    "stylelint-config-recommended-vue": "^1.4.0",
 | 
			
		||||
    "stylelint-config-standard": "^29.0.0",
 | 
			
		||||
    "stylelint-config-standard-scss": "^6.1.0",
 | 
			
		||||
    "stylelint-rscss": "^0.4.0",
 | 
			
		||||
    "url-loader": "^4.1.1",
 | 
			
		||||
    "vue-loader": "^17.4.2",
 | 
			
		||||
    "vue-style-loader": "^4.1.3",
 | 
			
		||||
    "webpack": "^5.91.0",
 | 
			
		||||
    "webpack-dev-middleware": "^7.2.1",
 | 
			
		||||
    "webpack-hot-middleware": "^2.26.1",
 | 
			
		||||
    "webpack-merge": "^5.10.0",
 | 
			
		||||
    "workbox-webpack-plugin": "^7.1.0"
 | 
			
		||||
    "vue-loader": "^17.0.0",
 | 
			
		||||
    "vue-style-loader": "^4.1.2",
 | 
			
		||||
    "webpack": "^5.75.0",
 | 
			
		||||
    "webpack-dev-middleware": "^5.3.3",
 | 
			
		||||
    "webpack-hot-middleware": "^2.25.1",
 | 
			
		||||
    "webpack-merge": "^5.8.0",
 | 
			
		||||
    "workbox-webpack-plugin": "^6.5.4"
 | 
			
		||||
  },
 | 
			
		||||
  "engines": {
 | 
			
		||||
    "node": ">= 16.0.0",
 | 
			
		||||
 | 
			
		||||
@ -59,8 +59,7 @@ export default {
 | 
			
		||||
        {
 | 
			
		||||
          '-reverse': this.reverseLayout,
 | 
			
		||||
          '-no-sticky-headers': this.noSticky,
 | 
			
		||||
          '-has-new-post-button': this.newPostButtonShown,
 | 
			
		||||
          '-wide-timeline': this.widenTimeline
 | 
			
		||||
          '-has-new-post-button': this.newPostButtonShown
 | 
			
		||||
        },
 | 
			
		||||
        '-' + this.layoutType
 | 
			
		||||
      ]
 | 
			
		||||
@ -94,9 +93,6 @@ export default {
 | 
			
		||||
    newPostButtonShown () {
 | 
			
		||||
      return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
 | 
			
		||||
    },
 | 
			
		||||
    widenTimeline () {
 | 
			
		||||
      return this.$store.getters.mergedConfig.widenTimeline
 | 
			
		||||
    },
 | 
			
		||||
    showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
 | 
			
		||||
    editingAvailable () { return this.$store.state.instance.editingAvailable },
 | 
			
		||||
    layoutType () { return this.$store.state.interface.layoutType },
 | 
			
		||||
 | 
			
		||||
@ -172,10 +172,6 @@ nav {
 | 
			
		||||
  background-color: rgba(0, 0, 0, 0.15);
 | 
			
		||||
  background-color: var(--underlay, rgba(0, 0, 0, 0.15));
 | 
			
		||||
  z-index: -1000;
 | 
			
		||||
 | 
			
		||||
  .-wide-timeline & {
 | 
			
		||||
    margin:0 calc(var(--columnGap) / -2);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.app-layout {
 | 
			
		||||
@ -191,17 +187,12 @@ nav {
 | 
			
		||||
  grid-template-rows: 1fr;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
  padding: 0 calc(var(--columnGap) / 2);
 | 
			
		||||
  align-content: flex-start;
 | 
			
		||||
  flex-wrap: wrap;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  min-height: 100vh;
 | 
			
		||||
  overflow-x: clip;
 | 
			
		||||
 | 
			
		||||
  &.-wide-timeline {
 | 
			
		||||
    --maxiColumn: minmax(var(--miniColumn), 1fr);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .column {
 | 
			
		||||
    --___columnMargin: var(--columnGap);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -183,12 +183,6 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
 | 
			
		||||
  copyInstanceOption('renderMisskeyMarkdown')
 | 
			
		||||
  copyInstanceOption('sidebarRight')
 | 
			
		||||
 | 
			
		||||
  if (config.backendCommitUrl)
 | 
			
		||||
    copyInstanceOption('backendCommitUrl')
 | 
			
		||||
 | 
			
		||||
  if (config.frontendCommitUrl)
 | 
			
		||||
    copyInstanceOption('frontendCommitUrl')
 | 
			
		||||
 | 
			
		||||
  return store.dispatch('setTheme', config['theme'])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./about.js"></script>
 | 
			
		||||
<script src="./about.js" ></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@
 | 
			
		||||
      :bound-to="{ x: 'container' }"
 | 
			
		||||
      remove-padding
 | 
			
		||||
    >
 | 
			
		||||
      <template #content>
 | 
			
		||||
      <template v-slot:content>
 | 
			
		||||
        <div class="dropdown-menu">
 | 
			
		||||
          <template v-if="relationship.following">
 | 
			
		||||
            <button
 | 
			
		||||
@ -71,7 +71,7 @@
 | 
			
		||||
          </button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
      <template #trigger>
 | 
			
		||||
      <template v-slot:trigger>
 | 
			
		||||
        <button class="button-unstyled ellipsis-button">
 | 
			
		||||
          <FAIcon
 | 
			
		||||
            class="icon"
 | 
			
		||||
@ -93,7 +93,7 @@
 | 
			
		||||
          keypath="user_card.block_confirm"
 | 
			
		||||
          tag="span"
 | 
			
		||||
        >
 | 
			
		||||
          <template #user>
 | 
			
		||||
          <template v-slot:user>
 | 
			
		||||
            <span
 | 
			
		||||
              v-text="user.screen_name_ui"
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
@ -16,14 +16,9 @@
 | 
			
		||||
 | 
			
		||||
  .attachment-wrapper {
 | 
			
		||||
    flex: 1 1 auto;
 | 
			
		||||
    min-height: 200px;
 | 
			
		||||
    height: 200px;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    align-content: center;
 | 
			
		||||
 | 
			
		||||
    .status-popover & {
 | 
			
		||||
      height: 200px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .description-container {
 | 
			
		||||
@ -120,22 +115,6 @@
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    padding-top: 0.5em;
 | 
			
		||||
 | 
			
		||||
    p {
 | 
			
		||||
      line-height: 1.5;
 | 
			
		||||
      padding: 0 0.5em;
 | 
			
		||||
      white-space: pre-line;
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      max-height: 200px;
 | 
			
		||||
      overflow-y: auto;
 | 
			
		||||
      scrollbar-color: var(--border) #0000;
 | 
			
		||||
 | 
			
		||||
      .status-popover & {
 | 
			
		||||
        text-overflow: ellipsis;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        height: 1lh;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -246,8 +246,8 @@
 | 
			
		||||
          ref="flash"
 | 
			
		||||
          class="flash"
 | 
			
		||||
          :src="attachment.large_thumb_url || attachment.url"
 | 
			
		||||
          @player-opened="setFlashLoaded(true)"
 | 
			
		||||
          @player-closed="setFlashLoaded(false)"
 | 
			
		||||
          @playerOpened="setFlashLoaded(true)"
 | 
			
		||||
          @playerClosed="setFlashLoaded(false)"
 | 
			
		||||
        />
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,7 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./avatar_list.js"></script>
 | 
			
		||||
<script src="./avatar_list.js" ></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@import '../../_variables.scss';
 | 
			
		||||
 | 
			
		||||
@ -22,12 +22,12 @@
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  emits: ['update:modelValue'],
 | 
			
		||||
  props: [
 | 
			
		||||
    'modelValue',
 | 
			
		||||
    'indeterminate',
 | 
			
		||||
    'disabled'
 | 
			
		||||
  ],
 | 
			
		||||
  emits: ['update:modelValue']
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,7 @@
 | 
			
		||||
      :model-value="present"
 | 
			
		||||
      :disabled="disabled"
 | 
			
		||||
      class="opt"
 | 
			
		||||
      @update:model-value="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
 | 
			
		||||
      @update:modelValue="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
 | 
			
		||||
    />
 | 
			
		||||
    <div class="input color-input-field">
 | 
			
		||||
      <input
 | 
			
		||||
@ -46,6 +46,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<style lang="scss" src="./color_input.scss"></style>
 | 
			
		||||
<script>
 | 
			
		||||
import Checkbox from '../checkbox/checkbox.vue'
 | 
			
		||||
import { hex2rgb } from '../../services/color_convert/color_convert.js'
 | 
			
		||||
@ -107,7 +108,6 @@ export default {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" src="./color_input.scss"></style>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
.color-control {
 | 
			
		||||
 | 
			
		||||
@ -25,8 +25,6 @@
 | 
			
		||||
  </dialog-modal>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./confirm_modal.js"></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
@import '../../_variables';
 | 
			
		||||
 | 
			
		||||
@ -37,3 +35,5 @@
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<script src="./confirm_modal.js"></script>
 | 
			
		||||
 | 
			
		||||
@ -267,11 +267,11 @@ const conversation = {
 | 
			
		||||
    },
 | 
			
		||||
    replies () {
 | 
			
		||||
      let i = 1
 | 
			
		||||
 | 
			
		||||
      // eslint-disable-next-line camelcase
 | 
			
		||||
      return reduce(this.conversation, (result, { id, in_reply_to_status_id }) => {
 | 
			
		||||
 | 
			
		||||
        /* eslint-disable camelcase */
 | 
			
		||||
        const irid = in_reply_to_status_id
 | 
			
		||||
 | 
			
		||||
        /* eslint-enable camelcase */
 | 
			
		||||
        if (irid) {
 | 
			
		||||
          result[irid] = result[irid] || []
 | 
			
		||||
          result[irid].push({
 | 
			
		||||
@ -414,14 +414,6 @@ const conversation = {
 | 
			
		||||
    },
 | 
			
		||||
    toggleExpanded () {
 | 
			
		||||
      this.expanded = !this.expanded
 | 
			
		||||
      const navHeight = document.getElementById("nav").offsetHeight
 | 
			
		||||
      const headingHeight = document.getElementsByClassName("timeline-heading")[0].offsetHeight
 | 
			
		||||
      document.documentElement.style.setProperty("--timeline-scroll-margin-top", `${navHeight + headingHeight}px`)
 | 
			
		||||
      this.$nextTick(() => {
 | 
			
		||||
        if (!this.expanded) {
 | 
			
		||||
          this.$el.scrollIntoView({ block: 'nearest' })
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    getConversationId (statusId) {
 | 
			
		||||
      const status = this.$store.state.statuses.allStatusesObject[statusId]
 | 
			
		||||
 | 
			
		||||
@ -91,7 +91,7 @@
 | 
			
		||||
              :controlled-set-media-playing="(newVal) => toggleStatusContentProperty(status.id, 'mediaPlaying', newVal)"
 | 
			
		||||
 | 
			
		||||
              @goto="setHighlight"
 | 
			
		||||
              @toggle-expanded="toggleExpanded"
 | 
			
		||||
              @toggleExpanded="toggleExpanded"
 | 
			
		||||
            />
 | 
			
		||||
            <div
 | 
			
		||||
              v-if="showOtherRepliesButtonBelowStatus && getReplies(status.id).length > 1"
 | 
			
		||||
@ -184,7 +184,7 @@
 | 
			
		||||
          :toggle-status-content-property="toggleStatusContentProperty"
 | 
			
		||||
 | 
			
		||||
          @goto="setHighlight"
 | 
			
		||||
          @toggle-expanded="toggleExpanded"
 | 
			
		||||
          @toggleExpanded="toggleExpanded"
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@ -278,7 +278,5 @@
 | 
			
		||||
  &.-expanded.status-fadein {
 | 
			
		||||
    margin: calc(var(--status-margin, $status-margin) / 2);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  scroll-margin-block-start: var(--timeline-scroll-margin-top);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@ -44,9 +44,9 @@
 | 
			
		||||
            />
 | 
			
		||||
          </router-link>
 | 
			
		||||
          <router-link
 | 
			
		||||
            v-if="publicTimelineVisible"
 | 
			
		||||
            :to="{ name: 'public-timeline' }"
 | 
			
		||||
            class="nav-icon"
 | 
			
		||||
            v-if="publicTimelineVisible"
 | 
			
		||||
          >
 | 
			
		||||
            <FAIcon
 | 
			
		||||
              fixed-width
 | 
			
		||||
@ -68,9 +68,9 @@
 | 
			
		||||
            />
 | 
			
		||||
          </router-link>
 | 
			
		||||
          <router-link
 | 
			
		||||
            v-if="federatedTimelineVisible"
 | 
			
		||||
            :to="{ name: 'public-external-timeline' }"
 | 
			
		||||
            class="nav-icon"
 | 
			
		||||
            v-if="federatedTimelineVisible"
 | 
			
		||||
          >
 | 
			
		||||
            <FAIcon
 | 
			
		||||
              fixed-width
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@
 | 
			
		||||
      class="btn button-default"
 | 
			
		||||
    >
 | 
			
		||||
      {{ $t('domain_mute_card.unmute') }}
 | 
			
		||||
      <template #progress>
 | 
			
		||||
      <template v-slot:progress>
 | 
			
		||||
        {{ $t('domain_mute_card.unmute_progress') }}
 | 
			
		||||
      </template>
 | 
			
		||||
    </ProgressButton>
 | 
			
		||||
@ -19,7 +19,7 @@
 | 
			
		||||
      class="btn button-default"
 | 
			
		||||
    >
 | 
			
		||||
      {{ $t('domain_mute_card.mute') }}
 | 
			
		||||
      <template #progress>
 | 
			
		||||
      <template v-slot:progress>
 | 
			
		||||
        {{ $t('domain_mute_card.mute_progress') }}
 | 
			
		||||
      </template>
 | 
			
		||||
    </ProgressButton>
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
  <Modal
 | 
			
		||||
    v-if="isFormVisible"
 | 
			
		||||
    class="edit-form-modal-view"
 | 
			
		||||
    @backdrop-clicked="closeModal"
 | 
			
		||||
    @backdropClicked="closeModal"
 | 
			
		||||
  >
 | 
			
		||||
    <div class="edit-form-modal-panel panel">
 | 
			
		||||
      <div class="panel-heading">
 | 
			
		||||
@ -11,10 +11,10 @@
 | 
			
		||||
      <PostStatusForm
 | 
			
		||||
        class="panel-body"
 | 
			
		||||
        v-bind="params"
 | 
			
		||||
        :disable-polls="true"
 | 
			
		||||
        :disable-visibility-selector="true"
 | 
			
		||||
        :post-handler="doEditStatus"
 | 
			
		||||
        @posted="closeModal"
 | 
			
		||||
        :disablePolls="true"
 | 
			
		||||
        :disableVisibilitySelector="true"
 | 
			
		||||
        :post-handler="doEditStatus"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  </Modal>
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
import StillImage from '../still-image/still-image.vue'
 | 
			
		||||
 | 
			
		||||
const EMOJI_SIZE = 32 + 8
 | 
			
		||||
const GROUP_TITLE_HEIGHT = 24
 | 
			
		||||
const BUFFER_SIZE = 3 * EMOJI_SIZE
 | 
			
		||||
@ -19,9 +17,6 @@ const EmojiGrid = {
 | 
			
		||||
      resizeObserver: null
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  components: {
 | 
			
		||||
    StillImage
 | 
			
		||||
  },
 | 
			
		||||
  mounted () {
 | 
			
		||||
    const rect = this.$refs.container.getBoundingClientRect()
 | 
			
		||||
    this.containerWidth = rect.width
 | 
			
		||||
 | 
			
		||||
@ -34,11 +34,10 @@
 | 
			
		||||
          @click.stop.prevent="onEmoji(item.emoji)"
 | 
			
		||||
        >
 | 
			
		||||
          <span v-if="!item.emoji.imageUrl">{{ item.emoji.replacement }}</span>
 | 
			
		||||
          <StillImage
 | 
			
		||||
          <img
 | 
			
		||||
            v-else
 | 
			
		||||
            :src="item.emoji.imageUrl"
 | 
			
		||||
            noStopGifs="true"
 | 
			
		||||
          />
 | 
			
		||||
          >
 | 
			
		||||
        </span>
 | 
			
		||||
      </template>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,5 @@
 | 
			
		||||
import Completion from '../../services/completion/completion.js'
 | 
			
		||||
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
 | 
			
		||||
import StillImage from '../still-image/still-image.vue'
 | 
			
		||||
import { take } from 'lodash'
 | 
			
		||||
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
 | 
			
		||||
 | 
			
		||||
@ -121,8 +120,7 @@ const EmojiInput = {
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  components: {
 | 
			
		||||
    EmojiPicker,
 | 
			
		||||
    StillImage
 | 
			
		||||
    EmojiPicker
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    padEmoji () {
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,6 @@
 | 
			
		||||
        ref="picker"
 | 
			
		||||
        show-keep-open
 | 
			
		||||
        :class="{ hide: !showPicker }"
 | 
			
		||||
        :visible="showPicker"
 | 
			
		||||
        :enable-sticker-picker="enableStickerPicker"
 | 
			
		||||
        class="emoji-picker-panel"
 | 
			
		||||
        @emoji="insert"
 | 
			
		||||
@ -44,15 +43,11 @@
 | 
			
		||||
          :class="{ highlighted: index === highlighted }"
 | 
			
		||||
          @click.stop.prevent="onClick($event, suggestion)"
 | 
			
		||||
        >
 | 
			
		||||
          <span
 | 
			
		||||
            v-if="!suggestion.mfm"
 | 
			
		||||
            class="image"
 | 
			
		||||
          >
 | 
			
		||||
            <StillImage
 | 
			
		||||
          <span v-if="!suggestion.mfm" class="image">
 | 
			
		||||
            <img
 | 
			
		||||
              v-if="suggestion.img"
 | 
			
		||||
              :src="suggestion.img"
 | 
			
		||||
              noStopGifs="true"
 | 
			
		||||
            />
 | 
			
		||||
            >
 | 
			
		||||
            <span v-else>{{ suggestion.replacement }}</span>
 | 
			
		||||
          </span>
 | 
			
		||||
          <div class="label">
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
const MFM_TAGS = ['bg', 'blur', 'bounce', 'center', 'fg', 'flip', 'font', 'jelly', 'jump', 'position', 'rainbow', 'rotate', 'scale', 'shake', 'sparkle', 'spin', 'tada', 'twitch', 'x2', 'x3', 'x4']
 | 
			
		||||
const MFM_TAGS = ['blur', 'bounce', 'flip', 'font', 'jelly', 'jump', 'rainbow', 'rotate', 'shake', 'sparkle', 'spin', 'tada', 'twitch', 'x2', 'x3', 'x4']
 | 
			
		||||
  .map(tag => ({ displayText: tag, detailText: '$[' + tag + ' ]', replacement: '$[' + tag + ' ]', mfm: true }))
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -122,14 +122,14 @@ export const suggestUsers = ({ dispatch, state }) => {
 | 
			
		||||
      const screenNameAlphabetically = a.screen_name > b.screen_name ? 1 : -1
 | 
			
		||||
 | 
			
		||||
      return diff + nameAlphabetically + screenNameAlphabetically
 | 
			
		||||
       
 | 
			
		||||
      /* eslint-disable camelcase */
 | 
			
		||||
    }).map(({ screen_name, screen_name_ui, name, profile_image_url_original }) => ({
 | 
			
		||||
      displayText: screen_name_ui,
 | 
			
		||||
      detailText: name,
 | 
			
		||||
      imageUrl: profile_image_url_original,
 | 
			
		||||
      replacement: '@' + screen_name + ' '
 | 
			
		||||
    }))
 | 
			
		||||
     
 | 
			
		||||
    /* eslint-enable camelcase */
 | 
			
		||||
 | 
			
		||||
    suggestions = newSuggestions || []
 | 
			
		||||
    return suggestions
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,6 @@
 | 
			
		||||
import { defineAsyncComponent } from 'vue'
 | 
			
		||||
import Checkbox from '../checkbox/checkbox.vue'
 | 
			
		||||
import EmojiGrid from '../emoji_grid/emoji_grid.vue'
 | 
			
		||||
import StillImage from '../still-image/still-image.vue'
 | 
			
		||||
import { library } from '@fortawesome/fontawesome-svg-core'
 | 
			
		||||
import {
 | 
			
		||||
  faBoxOpen,
 | 
			
		||||
@ -27,17 +26,12 @@ const EmojiPicker = {
 | 
			
		||||
      required: false,
 | 
			
		||||
      type: Boolean,
 | 
			
		||||
      default: false
 | 
			
		||||
    },
 | 
			
		||||
    visible: {
 | 
			
		||||
      required: false,
 | 
			
		||||
      type: Boolean,
 | 
			
		||||
      default: true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  data () {
 | 
			
		||||
    return {
 | 
			
		||||
      keyword: '',
 | 
			
		||||
      activeGroup: this.getDefaultGroup(),
 | 
			
		||||
      activeGroup: 'standard',
 | 
			
		||||
      showingStickers: false,
 | 
			
		||||
      keepOpen: false
 | 
			
		||||
    }
 | 
			
		||||
@ -45,8 +39,7 @@ const EmojiPicker = {
 | 
			
		||||
  components: {
 | 
			
		||||
    StickerPicker: defineAsyncComponent(() => import('../sticker_picker/sticker_picker.vue')),
 | 
			
		||||
    Checkbox,
 | 
			
		||||
    EmojiGrid,
 | 
			
		||||
    StillImage
 | 
			
		||||
    EmojiGrid
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    debouncedSearch: debounce(function (e) {
 | 
			
		||||
@ -89,11 +82,6 @@ const EmojiPicker = {
 | 
			
		||||
      return list.filter(emoji => {
 | 
			
		||||
        return (regex.test(emoji.displayText) || (!emoji.imageUrl && emoji.replacement === this.keyword))
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    getDefaultGroup () {
 | 
			
		||||
      if (!this.visible) return null
 | 
			
		||||
      const recentEmojis = this.$store.getters.recentEmojis
 | 
			
		||||
      return recentEmojis.length === 0 ? 'standard' : 'recent'
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
@ -160,13 +148,6 @@ const EmojiPicker = {
 | 
			
		||||
    stickerPickerEnabled () {
 | 
			
		||||
      return (this.$store.state.instance.stickers || []).length !== 0 && this.enableStickerPicker
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    visible (val, oldVal) {
 | 
			
		||||
      if (val && this.activeGroup === null) {
 | 
			
		||||
        this.activeGroup = this.getDefaultGroup()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -18,11 +18,10 @@
 | 
			
		||||
          @click.prevent="highlight(group.id)"
 | 
			
		||||
        >
 | 
			
		||||
          <span v-if="!group.first.imageUrl">{{ group.first.replacement }}</span>
 | 
			
		||||
          <StillImage
 | 
			
		||||
          <img
 | 
			
		||||
            v-else
 | 
			
		||||
            :src="group.first.imageUrl"
 | 
			
		||||
            noStopGifs="true"
 | 
			
		||||
          />
 | 
			
		||||
          >
 | 
			
		||||
        </span>
 | 
			
		||||
        <span
 | 
			
		||||
          v-if="stickerPickerEnabled"
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@
 | 
			
		||||
        @click="emojiOnClick(reaction.name, $event)"
 | 
			
		||||
        @mouseenter="fetchEmojiReactionsByIfMissing()"
 | 
			
		||||
      >
 | 
			
		||||
        <template
 | 
			
		||||
        <span
 | 
			
		||||
          v-if="reaction.url !== null"
 | 
			
		||||
        >
 | 
			
		||||
          <StillImage
 | 
			
		||||
@ -19,15 +19,16 @@
 | 
			
		||||
            :title="reaction.name"
 | 
			
		||||
            :alt="reaction.name"
 | 
			
		||||
            class="reaction-emoji"
 | 
			
		||||
            height="2.55em"
 | 
			
		||||
          />
 | 
			
		||||
          {{ reaction.count }}
 | 
			
		||||
        </template>
 | 
			
		||||
        <template v-else>
 | 
			
		||||
        </span>
 | 
			
		||||
        <span v-else>
 | 
			
		||||
          <span class="reaction-emoji unicode-emoji">
 | 
			
		||||
            {{ reaction.name }}
 | 
			
		||||
          </span>
 | 
			
		||||
          <span>{{ reaction.count }}</span>
 | 
			
		||||
        </template>
 | 
			
		||||
        </span>
 | 
			
		||||
      </button>
 | 
			
		||||
    </UserListPopover>
 | 
			
		||||
    <a
 | 
			
		||||
@ -41,7 +42,7 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./emoji_reactions.js"></script>
 | 
			
		||||
<script src="./emoji_reactions.js" ></script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@import '../../_variables.scss';
 | 
			
		||||
 | 
			
		||||
@ -52,26 +53,23 @@
 | 
			
		||||
  container-type: inline-size;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.unicode-emoji {
 | 
			
		||||
  font-size: 210%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.emoji-reaction {
 | 
			
		||||
  padding: 2px 0.5em;
 | 
			
		||||
  padding: 0 0.5em;
 | 
			
		||||
  margin-right: 0.5em;
 | 
			
		||||
  margin-top: 0.5em;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: end;
 | 
			
		||||
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  .reaction-emoji {
 | 
			
		||||
    width: auto;
 | 
			
		||||
    max-width: 96cqw;
 | 
			
		||||
    height: 2.55em !important;
 | 
			
		||||
    margin-right: 0.25em;
 | 
			
		||||
 | 
			
		||||
    &.still-image {
 | 
			
		||||
      height: 2.55em;
 | 
			
		||||
    }
 | 
			
		||||
    &.unicode-emoji {
 | 
			
		||||
      display: inline-block;
 | 
			
		||||
      font-size: 2.125em; // assuming default line height of 1.2rem and emojis that don't exceed line height
 | 
			
		||||
      line-height: 2.55rem;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  &:focus {
 | 
			
		||||
    outline: none;
 | 
			
		||||
@ -99,9 +97,9 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.button-default.picked-reaction {
 | 
			
		||||
  &, &:hover {
 | 
			
		||||
    box-shadow: inset 0 0 0 1px var(--accent, $fallback--link);
 | 
			
		||||
  }
 | 
			
		||||
  border: 1px solid var(--accent, $fallback--link);
 | 
			
		||||
  margin-left: -1px; // offset the border, can't use inset shadows either
 | 
			
		||||
  margin-right: calc(0.5em - 1px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@
 | 
			
		||||
    :bound-to="{ x: 'container' }"
 | 
			
		||||
    remove-padding
 | 
			
		||||
  >
 | 
			
		||||
    <template #content="{close}">
 | 
			
		||||
    <template v-slot:content="{close}">
 | 
			
		||||
      <div class="dropdown-menu">
 | 
			
		||||
        <button
 | 
			
		||||
          v-if="canMute && !status.thread_muted"
 | 
			
		||||
@ -172,7 +172,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </template>
 | 
			
		||||
    <template #trigger>
 | 
			
		||||
    <template v-slot:trigger>
 | 
			
		||||
      <button class="button-unstyled popover-trigger">
 | 
			
		||||
        <FAIcon
 | 
			
		||||
          class="fa-scale-110 fa-old-padding"
 | 
			
		||||
@ -205,7 +205,7 @@
 | 
			
		||||
  </Popover>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./extra_buttons.js"></script>
 | 
			
		||||
<script src="./extra_buttons.js" ></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@import '../../_variables.scss';
 | 
			
		||||
 | 
			
		||||
@ -35,7 +35,7 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./favorite_button.js"></script>
 | 
			
		||||
<script src="./favorite_button.js" ></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@import '../../_variables.scss';
 | 
			
		||||
 | 
			
		||||
@ -23,7 +23,7 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./features_panel.js"></script>
 | 
			
		||||
<script src="./features_panel.js" ></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
  .features-panel li {
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <basic-user-card
 | 
			
		||||
    v-if="show"
 | 
			
		||||
    :user="user"
 | 
			
		||||
  >
 | 
			
		||||
  <basic-user-card :user="user" v-if="show">
 | 
			
		||||
    <div class="follow-request-card-content-container">
 | 
			
		||||
      <button
 | 
			
		||||
        class="btn button-default"
 | 
			
		||||
 | 
			
		||||
@ -47,7 +47,7 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./font_control.js"></script>
 | 
			
		||||
<script src="./font_control.js" ></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@import '../../_variables.scss';
 | 
			
		||||
 | 
			
		||||
@ -88,8 +88,10 @@ const Gallery = {
 | 
			
		||||
      set(this.sizes, id, { width, height })
 | 
			
		||||
    },
 | 
			
		||||
    rowStyle (row) {
 | 
			
		||||
      if (!row.audio && !row.minimal && !row.grid) {
 | 
			
		||||
        return { 'aspect-ratio': `1/${(1 / (row.items.length + 0.6))}` }
 | 
			
		||||
      if (row.audio) {
 | 
			
		||||
        return { 'padding-bottom': '25%' } // fixed reduced height for audio
 | 
			
		||||
      } else if (!row.minimal && !row.grid) {
 | 
			
		||||
        return { 'padding-bottom': `${(100 / (row.items.length + 0.6))}%` }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    itemStyle (id, row) {
 | 
			
		||||
 | 
			
		||||
@ -31,8 +31,8 @@
 | 
			
		||||
            :description="descriptions && descriptions[attachment.id]"
 | 
			
		||||
            :hide-description="size === 'small' || tooManyAttachments && hidingLong"
 | 
			
		||||
            :style="itemStyle(attachment.id, row.items)"
 | 
			
		||||
            @set-media="onMedia"
 | 
			
		||||
            @natural-size-load="onNaturalSizeLoad"
 | 
			
		||||
            @setMedia="onMedia"
 | 
			
		||||
            @naturalSizeLoad="onNaturalSizeLoad"
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
@ -96,15 +96,9 @@
 | 
			
		||||
 | 
			
		||||
  .gallery-row {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    height: 0;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
    .Status & {
 | 
			
		||||
      max-height: 30em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.-audio {
 | 
			
		||||
      aspect-ratio: 4/1; // this is terrible, but it's how it was before so I'm not changing it >:(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &:not(:first-child) {
 | 
			
		||||
      margin-top: 0.5em;
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,6 @@
 | 
			
		||||
  </span>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./hashtag_link.js" />
 | 
			
		||||
<script src="./hashtag_link.js"/>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" src="./hashtag_link.scss" />
 | 
			
		||||
<style lang="scss" src="./hashtag_link.scss"/>
 | 
			
		||||
 | 
			
		||||
@ -10,4 +10,4 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./instance_specific_panel.js"></script>
 | 
			
		||||
<script src="./instance_specific_panel.js" ></script>
 | 
			
		||||
 | 
			
		||||
@ -42,7 +42,6 @@ export default {
 | 
			
		||||
@import '../../_variables.scss';
 | 
			
		||||
 | 
			
		||||
.list {
 | 
			
		||||
  min-height: 1em;
 | 
			
		||||
  &-item:not(:last-child) {
 | 
			
		||||
    border-bottom: 1px solid;
 | 
			
		||||
    border-bottom-color: $fallback--border;
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="panel-body">
 | 
			
		||||
        <p>{{ $t("about.bubble_instances_description") }}:</p>
 | 
			
		||||
        <p>{{ $t("about.bubble_instances_description")}}:</p>
 | 
			
		||||
        <ul>
 | 
			
		||||
          <li
 | 
			
		||||
            v-for="instance in bubbleInstances"
 | 
			
		||||
 | 
			
		||||
@ -90,7 +90,7 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./login_form.js"></script>
 | 
			
		||||
<script src="./login_form.js" ></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@import '../../_variables.scss';
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
  <Modal
 | 
			
		||||
    v-if="showing"
 | 
			
		||||
    class="media-modal-view"
 | 
			
		||||
    @backdrop-clicked="hideIfNotSwiped"
 | 
			
		||||
    @backdropClicked="hideIfNotSwiped"
 | 
			
		||||
  >
 | 
			
		||||
    <SwipeClick
 | 
			
		||||
      v-if="type === 'image'"
 | 
			
		||||
@ -24,15 +24,14 @@
 | 
			
		||||
        :min-scale="pinchZoomMinScale"
 | 
			
		||||
        :reset-to-min-scale-limit="pinchZoomScaleResetLimit"
 | 
			
		||||
      >
 | 
			
		||||
        <StillImage
 | 
			
		||||
        <img
 | 
			
		||||
          :class="{ loading }"
 | 
			
		||||
          class="modal-image"
 | 
			
		||||
          :src="currentMedia.url"
 | 
			
		||||
          :alt="currentMedia.description"
 | 
			
		||||
          :title="currentMedia.description"
 | 
			
		||||
          @load="onImageLoaded"
 | 
			
		||||
          noStopGifs="true"
 | 
			
		||||
        />
 | 
			
		||||
        >
 | 
			
		||||
      </PinchZoom>
 | 
			
		||||
    </SwipeClick>
 | 
			
		||||
    <VideoAttachment
 | 
			
		||||
 | 
			
		||||
@ -42,7 +42,7 @@ const mediaUpload = {
 | 
			
		||||
        .then((fileData) => {
 | 
			
		||||
          self.$emit('uploaded', fileData)
 | 
			
		||||
          self.decreaseUploadCount()
 | 
			
		||||
        }, (error) => {  
 | 
			
		||||
        }, (error) => { // eslint-disable-line handle-callback-err
 | 
			
		||||
          self.$emit('upload-failed', 'default')
 | 
			
		||||
          self.decreaseUploadCount()
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
@ -26,7 +26,7 @@
 | 
			
		||||
  </label>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./media_upload.js"></script>
 | 
			
		||||
<script src="./media_upload.js" ></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@import '../../_variables.scss';
 | 
			
		||||
 | 
			
		||||
@ -66,6 +66,6 @@
 | 
			
		||||
  </span>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./mention_link.js" />
 | 
			
		||||
<script src="./mention_link.js"/>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" src="./mention_link.scss" />
 | 
			
		||||
<style lang="scss" src="./mention_link.scss"/>
 | 
			
		||||
 | 
			
		||||
@ -37,5 +37,5 @@
 | 
			
		||||
    </span>
 | 
			
		||||
  </span>
 | 
			
		||||
</template>
 | 
			
		||||
<script src="./mentions_line.js"></script>
 | 
			
		||||
<script src="./mentions_line.js" ></script>
 | 
			
		||||
<style lang="scss" src="./mentions_line.scss" />
 | 
			
		||||
 | 
			
		||||
@ -69,4 +69,4 @@
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script src="./recovery_form.js"></script>
 | 
			
		||||
<script src="./recovery_form.js" ></script>
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,6 @@
 | 
			
		||||
          <input
 | 
			
		||||
            id="code"
 | 
			
		||||
            v-model="code"
 | 
			
		||||
            autocomplete="one-time-code"
 | 
			
		||||
            class="form-control"
 | 
			
		||||
          >
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@
 | 
			
		||||
      class="panel-heading"
 | 
			
		||||
      @click="toggleHidden"
 | 
			
		||||
    >
 | 
			
		||||
      <h4>{{ $t('moderation.reports.report') + ' ' + account.screen_name }}</h4>
 | 
			
		||||
      <h4>{{ $t('moderation.reports.report') + ' ' + this.account.screen_name }}</h4>
 | 
			
		||||
      <button
 | 
			
		||||
        v-if="isOpen"
 | 
			
		||||
        class="button-default"
 | 
			
		||||
@ -24,7 +24,7 @@
 | 
			
		||||
        class="button-default"
 | 
			
		||||
        @click.stop="updateReportState('open')"
 | 
			
		||||
      >
 | 
			
		||||
        {{ $t('moderation.reports.reopen') }}
 | 
			
		||||
          {{ $t('moderation.reports.reopen') }}
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div
 | 
			
		||||
@ -35,10 +35,7 @@
 | 
			
		||||
        <div v-if="content">
 | 
			
		||||
          {{ decode(content) }}
 | 
			
		||||
        </div>
 | 
			
		||||
        <i
 | 
			
		||||
          v-else
 | 
			
		||||
          class="faint"
 | 
			
		||||
        >
 | 
			
		||||
        <i v-else class="faint">
 | 
			
		||||
          {{ $t('moderation.reports.no_content') }}
 | 
			
		||||
        </i>
 | 
			
		||||
        <div class="report-author">
 | 
			
		||||
@ -46,12 +43,12 @@
 | 
			
		||||
            class="small-avatar"
 | 
			
		||||
            :user="actor"
 | 
			
		||||
          />
 | 
			
		||||
          {{ actor.screen_name }}
 | 
			
		||||
          {{ this.actor.screen_name }}
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div
 | 
			
		||||
        v-if="!hidden && statuses.length > 0"
 | 
			
		||||
        class="dropdown"
 | 
			
		||||
        v-if="!hidden && this.statuses.length > 0"
 | 
			
		||||
      >
 | 
			
		||||
        <button
 | 
			
		||||
          class="button button-unstyled dropdown-header"
 | 
			
		||||
@ -77,8 +74,8 @@
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div
 | 
			
		||||
        v-if="!hidden && notes.length > 0"
 | 
			
		||||
        class="dropdown"
 | 
			
		||||
        v-if="!hidden && this.notes.length > 0"
 | 
			
		||||
      >
 | 
			
		||||
        <button
 | 
			
		||||
          class="button button-unstyled dropdown-header"
 | 
			
		||||
@ -102,9 +99,9 @@
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="report-add-note">
 | 
			
		||||
        <textarea
 | 
			
		||||
          v-model.trim="note"
 | 
			
		||||
          rows="1"
 | 
			
		||||
          cols="1"
 | 
			
		||||
          v-model.trim="note"
 | 
			
		||||
          :placeholder="$t('moderation.reports.note_placeholder')"
 | 
			
		||||
        />
 | 
			
		||||
        <button
 | 
			
		||||
@ -137,7 +134,7 @@
 | 
			
		||||
        :offset="{ y: 5 }"
 | 
			
		||||
        remove-padding
 | 
			
		||||
      >
 | 
			
		||||
        <template #trigger>
 | 
			
		||||
        <template v-slot:trigger>
 | 
			
		||||
          <button
 | 
			
		||||
            class="btn button-default"
 | 
			
		||||
            :disabled="!tagPolicyEnabled"
 | 
			
		||||
@ -150,7 +147,7 @@
 | 
			
		||||
            />
 | 
			
		||||
          </button>
 | 
			
		||||
        </template>
 | 
			
		||||
        <template #content="{close}">
 | 
			
		||||
        <template v-slot:content="{close}">
 | 
			
		||||
          <div
 | 
			
		||||
            class="dropdown-menu"
 | 
			
		||||
            :disabled="!tagPolicyEnabled"
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@
 | 
			
		||||
          class="small-avatar"
 | 
			
		||||
          :user="user"
 | 
			
		||||
        />
 | 
			
		||||
        {{ user.screen_name }}
 | 
			
		||||
        {{ this.user.screen_name }}
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="header-right">
 | 
			
		||||
        <Timeago
 | 
			
		||||
 | 
			
		||||
@ -22,9 +22,6 @@ export default {
 | 
			
		||||
      default: false
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  emits: [
 | 
			
		||||
    'backdropClicked',
 | 
			
		||||
  ],
 | 
			
		||||
  computed: {
 | 
			
		||||
    classes () {
 | 
			
		||||
      return {
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@
 | 
			
		||||
      @show="setToggled(true)"
 | 
			
		||||
      @close="setToggled(false)"
 | 
			
		||||
    >
 | 
			
		||||
      <template #content>
 | 
			
		||||
      <template v-slot:content>
 | 
			
		||||
        <div class="dropdown-menu">
 | 
			
		||||
          <span v-if="user.is_local">
 | 
			
		||||
            <button
 | 
			
		||||
@ -122,7 +122,7 @@
 | 
			
		||||
          </span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
      <template #trigger>
 | 
			
		||||
      <template v-slot:trigger>
 | 
			
		||||
        <button
 | 
			
		||||
          class="btn button-default btn-block moderation-tools-button"
 | 
			
		||||
          :class="{ toggled }"
 | 
			
		||||
@ -137,11 +137,11 @@
 | 
			
		||||
        v-if="showDeleteUserDialog"
 | 
			
		||||
        :on-cancel="deleteUserDialog.bind(this, false)"
 | 
			
		||||
      >
 | 
			
		||||
        <template #header>
 | 
			
		||||
        <template v-slot:header>
 | 
			
		||||
          {{ $t('user_card.admin_menu.delete_user') }}
 | 
			
		||||
        </template>
 | 
			
		||||
        <p>{{ $t('user_card.admin_menu.delete_user_confirmation') }}</p>
 | 
			
		||||
        <template #footer>
 | 
			
		||||
        <template v-slot:footer>
 | 
			
		||||
          <button
 | 
			
		||||
            class="btn button-default"
 | 
			
		||||
            @click="deleteUserDialog(false)"
 | 
			
		||||
 | 
			
		||||
@ -102,7 +102,7 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./nav_panel.js"></script>
 | 
			
		||||
<script src="./nav_panel.js" ></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@import '../../_variables.scss';
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,6 @@ import UserCard from '../user_card/user_card.vue'
 | 
			
		||||
import Timeago from '../timeago/timeago.vue'
 | 
			
		||||
import RichContent from 'src/components/rich_content/rich_content.jsx'
 | 
			
		||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
 | 
			
		||||
import StillImage from '../still-image/still-image.vue'
 | 
			
		||||
import { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
 | 
			
		||||
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
 | 
			
		||||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
 | 
			
		||||
@ -51,8 +50,7 @@ const Notification = {
 | 
			
		||||
    Timeago,
 | 
			
		||||
    Status,
 | 
			
		||||
    RichContent,
 | 
			
		||||
    ConfirmModal,
 | 
			
		||||
    StillImage
 | 
			
		||||
    ConfirmModal
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    toggleUserExpanded () {
 | 
			
		||||
 | 
			
		||||
@ -101,8 +101,4 @@
 | 
			
		||||
    color: $fallback--cBlue;
 | 
			
		||||
    color: var(--cBlue, $fallback--cBlue);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .attachment-wrapper {
 | 
			
		||||
    min-height: unset;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -116,13 +116,12 @@
 | 
			
		||||
                  scope="global"
 | 
			
		||||
                  keypath="notifications.reacted_with"
 | 
			
		||||
                >
 | 
			
		||||
                  <still-image
 | 
			
		||||
                  <img
 | 
			
		||||
                    v-if="notification.emoji_url !== null"
 | 
			
		||||
                    class="notification-reaction-emoji"
 | 
			
		||||
                    :src="notification.emoji_url"
 | 
			
		||||
                    :title="notification.emoji"
 | 
			
		||||
                    :alt="notification.emoji"
 | 
			
		||||
                  />
 | 
			
		||||
                    :name="notification.emoji"
 | 
			
		||||
                  >
 | 
			
		||||
                  <span
 | 
			
		||||
                    v-else
 | 
			
		||||
                    class="emoji-reaction-emoji"
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@
 | 
			
		||||
    placement="bottom"
 | 
			
		||||
    :bound-to="{ x: 'container' }"
 | 
			
		||||
  >
 | 
			
		||||
    <template #content>
 | 
			
		||||
    <template v-slot:content>
 | 
			
		||||
      <div class="dropdown-menu">
 | 
			
		||||
        <button
 | 
			
		||||
          class="button-default dropdown-item"
 | 
			
		||||
@ -72,7 +72,7 @@
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </template>
 | 
			
		||||
    <template #trigger>
 | 
			
		||||
    <template v-slot:trigger>
 | 
			
		||||
      <button class="filter-trigger-button button-unstyled">
 | 
			
		||||
        <FAIcon icon="filter" />
 | 
			
		||||
      </button>
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,7 @@
 | 
			
		||||
      :model-value="present"
 | 
			
		||||
      :disabled="disabled"
 | 
			
		||||
      class="opt"
 | 
			
		||||
      @update:model-value="$emit('update:modelValue', !present ? fallback : undefined)"
 | 
			
		||||
      @update:modelValue="$emit('update:modelValue', !present ? fallback : undefined)"
 | 
			
		||||
    />
 | 
			
		||||
    <input
 | 
			
		||||
      :id="name"
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
  <pinch-zoom
 | 
			
		||||
    class="pinch-zoom-parent"
 | 
			
		||||
    v-bind="$attrs"
 | 
			
		||||
    v-on="$listeners"
 | 
			
		||||
  >
 | 
			
		||||
    <slot />
 | 
			
		||||
  </pinch-zoom>
 | 
			
		||||
 | 
			
		||||
@ -50,13 +50,6 @@ export default {
 | 
			
		||||
    totalVotesCount () {
 | 
			
		||||
      return this.poll.votes_count
 | 
			
		||||
    },
 | 
			
		||||
    totalFractionBase () {
 | 
			
		||||
      // Due to a backend bug, we might not have any voter count info for remote polls
 | 
			
		||||
      // in this case, fall back to count of votes even for multiple cjoice polls
 | 
			
		||||
      // to be able to at least display _something_
 | 
			
		||||
      const total_base = this.poll.multiple ? this.poll.voters_count : this.poll.votes_count
 | 
			
		||||
      return total_base > 0 ? total_base : this.poll.votes_count
 | 
			
		||||
    },
 | 
			
		||||
    containerClass () {
 | 
			
		||||
      return {
 | 
			
		||||
        loading: this.loading
 | 
			
		||||
@ -77,11 +70,10 @@ export default {
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    percentageForOption (count) {
 | 
			
		||||
      const total = this.totalFractionBase
 | 
			
		||||
      return total === 0 ? 0 : Math.round(count / total * 100)
 | 
			
		||||
      return this.totalVotesCount === 0 ? 0 : Math.round(count / this.totalVotesCount * 100)
 | 
			
		||||
    },
 | 
			
		||||
    resultTitle (option) {
 | 
			
		||||
      return `${option.votes_count}/${this.totalFractionBase} ${this.$t('polls.votes')}`
 | 
			
		||||
      return `${option.votes_count}/${this.totalVotesCount} ${this.$t('polls.votes')}`
 | 
			
		||||
    },
 | 
			
		||||
    fetchPoll () {
 | 
			
		||||
      this.$store.dispatch('refreshPoll', { id: this.statusId, pollId: this.poll.id })
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,6 @@
 | 
			
		||||
      <button
 | 
			
		||||
        v-if="options.length > 2"
 | 
			
		||||
        class="delete-option button-unstyled -hover-highlight"
 | 
			
		||||
        type="button"
 | 
			
		||||
        @click="deleteOption(index)"
 | 
			
		||||
      >
 | 
			
		||||
        <FAIcon icon="times" />
 | 
			
		||||
@ -33,7 +32,6 @@
 | 
			
		||||
    <button
 | 
			
		||||
      v-if="options.length < maxOptions"
 | 
			
		||||
      class="add-option faint button-unstyled -hover-highlight"
 | 
			
		||||
      type="button"
 | 
			
		||||
      @click="addOption"
 | 
			
		||||
    >
 | 
			
		||||
      <FAIcon
 | 
			
		||||
 | 
			
		||||
@ -9,13 +9,11 @@ import StatusContent from '../status_content/status_content.vue'
 | 
			
		||||
import fileTypeService from '../../services/file_type/file_type.service.js'
 | 
			
		||||
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
 | 
			
		||||
import { reject, map, uniqBy, debounce } from 'lodash'
 | 
			
		||||
import { usePostLanguageOptions } from 'src/lib/post_language'
 | 
			
		||||
import scopeUtils from 'src/lib/scope_utils.js'
 | 
			
		||||
import suggestor from '../emoji_input/suggestor.js'
 | 
			
		||||
import { mapGetters, mapState } from 'vuex'
 | 
			
		||||
import Checkbox from '../checkbox/checkbox.vue'
 | 
			
		||||
import Select from '../select/select.vue'
 | 
			
		||||
 | 
			
		||||
import iso6391 from 'iso-639-1'
 | 
			
		||||
 | 
			
		||||
import { library } from '@fortawesome/fontawesome-svg-core'
 | 
			
		||||
import {
 | 
			
		||||
@ -64,13 +62,6 @@ const deleteDraft = (draftKey) => {
 | 
			
		||||
	localStorage.setItem('drafts', JSON.stringify(draftData));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const interfaceToISOLanguage = (ilang) => {
 | 
			
		||||
    const sep = ilang.indexOf("_");
 | 
			
		||||
    return sep < 0 ?
 | 
			
		||||
        ilang :
 | 
			
		||||
        ilang.substr(0, sep);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const PostStatusForm = {
 | 
			
		||||
  props: [
 | 
			
		||||
    'statusId',
 | 
			
		||||
@ -86,7 +77,6 @@ const PostStatusForm = {
 | 
			
		||||
    'quoteId',
 | 
			
		||||
    'repliedUser',
 | 
			
		||||
    'attentions',
 | 
			
		||||
    'copyMessageLanguage',
 | 
			
		||||
    'copyMessageScope',
 | 
			
		||||
    'subject',
 | 
			
		||||
    'disableSubject',
 | 
			
		||||
@ -139,13 +129,6 @@ const PostStatusForm = {
 | 
			
		||||
      this.$refs.textarea.focus()
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  setup() {
 | 
			
		||||
    const {postLanguageOptions} = usePostLanguageOptions()
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      postLanguageOptions,
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  data () {
 | 
			
		||||
    const preset = this.$route.query.message
 | 
			
		||||
    let statusText = preset || ''
 | 
			
		||||
@ -155,7 +138,7 @@ const PostStatusForm = {
 | 
			
		||||
      statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const { postContentType: contentType, sensitiveByDefault, sensitiveIfSubject, alwaysShowSubjectInput } = this.$store.getters.mergedConfig
 | 
			
		||||
    const { postContentType: contentType, sensitiveByDefault, sensitiveIfSubject, interfaceLanguage, alwaysShowSubjectInput } = this.$store.getters.mergedConfig
 | 
			
		||||
 | 
			
		||||
    let statusParams = {
 | 
			
		||||
      spoilerText: this.subject || '',
 | 
			
		||||
@ -166,7 +149,7 @@ const PostStatusForm = {
 | 
			
		||||
      poll: {},
 | 
			
		||||
      mediaDescriptions: {},
 | 
			
		||||
      visibility: this.suggestedVisibility(),
 | 
			
		||||
      language: this.suggestedLanguage(),
 | 
			
		||||
      language: interfaceLanguage,
 | 
			
		||||
      contentType
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -181,7 +164,7 @@ const PostStatusForm = {
 | 
			
		||||
        poll: this.statusPoll || {},
 | 
			
		||||
        mediaDescriptions: this.statusMediaDescriptions || {},
 | 
			
		||||
        visibility: this.statusScope || this.suggestedVisibility(),
 | 
			
		||||
        language: this.statusLanguage || this.suggestedLanguage(),
 | 
			
		||||
        language: this.statusLanguage || interfaceLanguage,
 | 
			
		||||
        contentType: statusContentType
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@ -326,11 +309,13 @@ const PostStatusForm = {
 | 
			
		||||
    ...mapState({
 | 
			
		||||
      mobileLayout: state => state.interface.mobileLayout
 | 
			
		||||
    }),
 | 
			
		||||
    isoLanguages () {
 | 
			
		||||
      return iso6391.getAllCodes();
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    'newStatus': {
 | 
			
		||||
      deep: true,
 | 
			
		||||
      flush: 'sync',
 | 
			
		||||
      handler () {
 | 
			
		||||
        this.statusChanged()
 | 
			
		||||
      }
 | 
			
		||||
@ -343,22 +328,17 @@ const PostStatusForm = {
 | 
			
		||||
      this.saveDraft()
 | 
			
		||||
    },
 | 
			
		||||
    clearStatus () {
 | 
			
		||||
      const config = this.$store.getters.mergedConfig
 | 
			
		||||
      const newStatus = this.newStatus
 | 
			
		||||
      this.newStatus = {
 | 
			
		||||
        status: '',
 | 
			
		||||
        spoilerText: '',
 | 
			
		||||
        files: [],
 | 
			
		||||
        nsfw: !!config.sensitiveByDefault,
 | 
			
		||||
        visibility: this.suggestedVisibility(),
 | 
			
		||||
        contentType: config.postContentType,
 | 
			
		||||
        language: this.suggestedLanguage(),
 | 
			
		||||
        visibility: newStatus.visibility,
 | 
			
		||||
        contentType: newStatus.contentType,
 | 
			
		||||
        language: newStatus.language,
 | 
			
		||||
        poll: {},
 | 
			
		||||
        mediaDescriptions: {}
 | 
			
		||||
      }
 | 
			
		||||
      const scopeselector = this.$refs.scopeselector
 | 
			
		||||
      if (scopeselector) {
 | 
			
		||||
        scopeselector.currentScope = this.newStatus.visibility
 | 
			
		||||
      }
 | 
			
		||||
      this.pollFormVisible = false
 | 
			
		||||
      this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile()
 | 
			
		||||
      this.clearPollForm()
 | 
			
		||||
@ -518,7 +498,7 @@ const PostStatusForm = {
 | 
			
		||||
    addMediaFile (fileInfo) {
 | 
			
		||||
      this.newStatus.files.push(fileInfo)
 | 
			
		||||
 | 
			
		||||
      if (this.$store.getters.mergedConfig.sensitiveIfSubject && this.newStatus.spoilerText !== '' || !!this.$store.getters.mergedConfig.sensitiveByDefault) {
 | 
			
		||||
      if (this.$store.getters.mergedConfig.sensitiveIfSubject && this.newStatus.spoilerText !== '') {
 | 
			
		||||
        this.newStatus.nsfw = true
 | 
			
		||||
      }
 | 
			
		||||
      this.$emit('resize', { delayed: true })
 | 
			
		||||
@ -767,19 +747,16 @@ const PostStatusForm = {
 | 
			
		||||
    openProfileTab () {
 | 
			
		||||
      this.$store.dispatch('openSettingsModalTab', 'profile')
 | 
			
		||||
    },
 | 
			
		||||
    suggestedLanguage () {
 | 
			
		||||
      // Make sure the inherited language is actually valid
 | 
			
		||||
      if (this.postLanguageOptions.find(o => o.value === this.copyMessageLanguage)) {
 | 
			
		||||
        return this.copyMessageLanguage
 | 
			
		||||
      }
 | 
			
		||||
      const { postLanguage: defaultPostLanguage, interfaceLanguage } = this.$store.getters.mergedConfig
 | 
			
		||||
      const postLanguage = defaultPostLanguage || interfaceToISOLanguage(interfaceLanguage)
 | 
			
		||||
      return postLanguage
 | 
			
		||||
    },
 | 
			
		||||
    suggestedVisibility () {
 | 
			
		||||
      const maxScope = this.copyMessageScope
 | 
			
		||||
      const defaultScope = this.$store.state.users.currentUser.default_scope
 | 
			
		||||
      return scopeUtils.negotiate(defaultScope, maxScope)
 | 
			
		||||
      if (this.copyMessageScope) {
 | 
			
		||||
        if (this.copyMessageScope === 'direct') {
 | 
			
		||||
          return this.copyMessageScope
 | 
			
		||||
        }
 | 
			
		||||
        if (this.copyMessageScope !== 'public' && this.$store.state.users.currentUser.default_scope !== 'private') {
 | 
			
		||||
          return this.copyMessageScope
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return this.$store.state.users.currentUser.default_scope
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,6 @@
 | 
			
		||||
        >
 | 
			
		||||
          <button
 | 
			
		||||
            class="button-unstyled -link"
 | 
			
		||||
            type="button"
 | 
			
		||||
            @click="openProfileTab"
 | 
			
		||||
          >
 | 
			
		||||
            {{ $t('post_status.account_not_locked_warning_link') }}
 | 
			
		||||
@ -119,8 +118,8 @@
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
        <EmojiInput
 | 
			
		||||
          v-if="subjectVisible"
 | 
			
		||||
          ref="subject-emoji-input"
 | 
			
		||||
          v-if="subjectVisible"
 | 
			
		||||
          v-model="newStatus.spoilerText"
 | 
			
		||||
          enable-emoji-picker
 | 
			
		||||
          hide-emoji-button
 | 
			
		||||
@ -137,7 +136,6 @@
 | 
			
		||||
            class="form-post-subject"
 | 
			
		||||
            @input="onSubjectInput"
 | 
			
		||||
            @focus="focusSubjectInput()"
 | 
			
		||||
            @keydown.exact.enter.prevent
 | 
			
		||||
          >
 | 
			
		||||
        </EmojiInput>
 | 
			
		||||
        <i18n-t
 | 
			
		||||
@ -172,7 +170,7 @@
 | 
			
		||||
            cols="1"
 | 
			
		||||
            :disabled="posting && !optimisticPosting"
 | 
			
		||||
            class="form-post-body"
 | 
			
		||||
            :class="{ 'scrollable-form': !!maxHeight, '-has-subject': subjectVisible }"
 | 
			
		||||
            :class="{ 'scrollable-form': !!maxHeight }"
 | 
			
		||||
            @keydown.exact.enter="submitOnEnter && postStatus($event, newStatus)"
 | 
			
		||||
            @keydown.meta.enter="postStatus($event, newStatus)"
 | 
			
		||||
            @keydown.ctrl.enter="!submitOnEnter && postStatus($event, newStatus)"
 | 
			
		||||
@ -192,10 +190,8 @@
 | 
			
		||||
        <div
 | 
			
		||||
          v-if="!disableScopeSelector"
 | 
			
		||||
          class="visibility-tray"
 | 
			
		||||
          :class="{ 'visibility-tray-edit': isEdit }"
 | 
			
		||||
        >
 | 
			
		||||
          <scope-selector
 | 
			
		||||
            ref="scopeselector"
 | 
			
		||||
            v-if="!disableVisibilitySelector"
 | 
			
		||||
            :user-default="userDefaultScope"
 | 
			
		||||
            :original-scope="copyMessageScope"
 | 
			
		||||
@ -204,50 +200,47 @@
 | 
			
		||||
          />
 | 
			
		||||
 | 
			
		||||
          <div
 | 
			
		||||
            class="format-selector-container">
 | 
			
		||||
            <div
 | 
			
		||||
              class="format-selector"
 | 
			
		||||
              >
 | 
			
		||||
              <Select
 | 
			
		||||
                id="post-language"
 | 
			
		||||
                v-model="newStatus.language"
 | 
			
		||||
                class="form-control"
 | 
			
		||||
              >
 | 
			
		||||
                <option
 | 
			
		||||
                  v-for="language in postLanguageOptions"
 | 
			
		||||
                  :key="language.key"
 | 
			
		||||
                  :value="language.value"
 | 
			
		||||
                >
 | 
			
		||||
                  {{ language.label }}
 | 
			
		||||
                </option>
 | 
			
		||||
              </Select>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div
 | 
			
		||||
              v-if="postFormats.length > 1"
 | 
			
		||||
              class="text-format format-selector"
 | 
			
		||||
            class="language-selector"
 | 
			
		||||
            >
 | 
			
		||||
              <Select
 | 
			
		||||
                id="post-content-type"
 | 
			
		||||
                v-model="newStatus.contentType"
 | 
			
		||||
                class="form-control"
 | 
			
		||||
              >
 | 
			
		||||
                <option
 | 
			
		||||
                  v-for="postFormat in postFormats"
 | 
			
		||||
                  :key="postFormat"
 | 
			
		||||
                  :value="postFormat"
 | 
			
		||||
                >
 | 
			
		||||
                  {{ $t(`post_status.content_type["${postFormat}"]`) }}
 | 
			
		||||
                </option>
 | 
			
		||||
              </Select>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div
 | 
			
		||||
              v-if="postFormats.length === 1 && postFormats[0] !== 'text/plain'"
 | 
			
		||||
              class="text-format format-selector"
 | 
			
		||||
            <Select
 | 
			
		||||
              id="post-language"
 | 
			
		||||
              v-model="newStatus.language"
 | 
			
		||||
              class="form-control"
 | 
			
		||||
            >
 | 
			
		||||
              <span class="only-format">
 | 
			
		||||
                {{ $t(`post_status.content_type["${postFormats[0]}"]`) }}
 | 
			
		||||
              </span>
 | 
			
		||||
            </div>
 | 
			
		||||
              <option
 | 
			
		||||
                v-for="language in isoLanguages"
 | 
			
		||||
                :key="language"
 | 
			
		||||
                :value="language"
 | 
			
		||||
              >
 | 
			
		||||
                {{ language }}
 | 
			
		||||
              </option>
 | 
			
		||||
            </Select>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div
 | 
			
		||||
            v-if="postFormats.length > 1"
 | 
			
		||||
            class="text-format"
 | 
			
		||||
          >
 | 
			
		||||
            <Select
 | 
			
		||||
              id="post-content-type"
 | 
			
		||||
              v-model="newStatus.contentType"
 | 
			
		||||
              class="form-control"
 | 
			
		||||
            >
 | 
			
		||||
              <option
 | 
			
		||||
                v-for="postFormat in postFormats"
 | 
			
		||||
                :key="postFormat"
 | 
			
		||||
                :value="postFormat"
 | 
			
		||||
              >
 | 
			
		||||
                {{ $t(`post_status.content_type["${postFormat}"]`) }}
 | 
			
		||||
              </option>
 | 
			
		||||
            </Select>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div
 | 
			
		||||
            v-if="postFormats.length === 1 && postFormats[0] !== 'text/plain'"
 | 
			
		||||
            class="text-format"
 | 
			
		||||
          >
 | 
			
		||||
            <span class="only-format">
 | 
			
		||||
              {{ $t(`post_status.content_type["${postFormats[0]}"]`) }}
 | 
			
		||||
            </span>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
@ -275,7 +268,6 @@
 | 
			
		||||
          <button
 | 
			
		||||
            class="emoji-icon button-unstyled"
 | 
			
		||||
            :title="$t('emoji.add_emoji')"
 | 
			
		||||
            type="button"
 | 
			
		||||
            @click="showEmojiPicker"
 | 
			
		||||
          >
 | 
			
		||||
            <FAIcon icon="smile-beam" />
 | 
			
		||||
@ -285,7 +277,6 @@
 | 
			
		||||
            class="poll-icon button-unstyled"
 | 
			
		||||
            :class="{ selected: pollFormVisible }"
 | 
			
		||||
            :title="$t('polls.add_poll')"
 | 
			
		||||
            type="button"
 | 
			
		||||
            @click="togglePollForm"
 | 
			
		||||
          >
 | 
			
		||||
            <FAIcon icon="poll-h" />
 | 
			
		||||
@ -295,7 +286,6 @@
 | 
			
		||||
            class="spoiler-icon button-unstyled"
 | 
			
		||||
            :class="{ selected: subjectVisible }"
 | 
			
		||||
            :title="$t('post_status.toggle_content_warning')"
 | 
			
		||||
            type="button"
 | 
			
		||||
            @click="toggleSubjectVisible"
 | 
			
		||||
          >
 | 
			
		||||
            <FAIcon icon="eye-slash" />
 | 
			
		||||
@ -470,10 +460,6 @@
 | 
			
		||||
    align-items: baseline;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .visibility-tray-edit {
 | 
			
		||||
    justify-content: right;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .visibility-notice.edit-warning {
 | 
			
		||||
    > :first-child {
 | 
			
		||||
      margin-top: 0;
 | 
			
		||||
@ -484,12 +470,6 @@
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .format-selector-container {
 | 
			
		||||
    .format-selector {
 | 
			
		||||
      display: inline-block;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .media-upload-icon, .poll-icon, .emoji-icon, .spoiler-icon {
 | 
			
		||||
    font-size: 1.85em;
 | 
			
		||||
    line-height: 1.1;
 | 
			
		||||
@ -590,11 +570,6 @@
 | 
			
		||||
    line-height: 1.85;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .form-post-subject {
 | 
			
		||||
    border-bottom-left-radius: 0;
 | 
			
		||||
    border-bottom-right-radius: 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .form-post-body {
 | 
			
		||||
    // TODO: make a resizable textarea component?
 | 
			
		||||
    box-sizing: content-box; // needed for easier computation of dynamic size
 | 
			
		||||
@ -607,11 +582,6 @@
 | 
			
		||||
    min-height: calc(var(--post-line-height) * 1em);
 | 
			
		||||
    resize: none;
 | 
			
		||||
 | 
			
		||||
    &.-has-subject {
 | 
			
		||||
      border-top-left-radius: 0;
 | 
			
		||||
      border-top-right-radius: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.scrollable-form {
 | 
			
		||||
      overflow-y: auto;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
    v-if="isLoggedIn && !resettingForm"
 | 
			
		||||
    :is-open="modalActivated"
 | 
			
		||||
    class="post-form-modal-view"
 | 
			
		||||
    @backdrop-clicked="closeModal"
 | 
			
		||||
    @backdropClicked="closeModal"
 | 
			
		||||
  >
 | 
			
		||||
    <div class="post-form-modal-panel panel">
 | 
			
		||||
      <div class="panel-heading">
 | 
			
		||||
 | 
			
		||||
@ -8,13 +8,13 @@
 | 
			
		||||
    remove-padding
 | 
			
		||||
    @show="focusInput"
 | 
			
		||||
  >
 | 
			
		||||
    <template #content="{close}">
 | 
			
		||||
    <template v-slot:content="{close}">
 | 
			
		||||
      <EmojiPicker
 | 
			
		||||
        :enable-sticker-picker="false"
 | 
			
		||||
        @emoji="addReaction($event, close)"
 | 
			
		||||
      />
 | 
			
		||||
    </template>
 | 
			
		||||
    <template #trigger>
 | 
			
		||||
    <template v-slot:trigger>
 | 
			
		||||
      <button
 | 
			
		||||
        class="button-unstyled popover-trigger"
 | 
			
		||||
        :title="$t('tool_tip.add_reaction')"
 | 
			
		||||
@ -28,7 +28,7 @@
 | 
			
		||||
  </Popover>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./react_button.js"></script>
 | 
			
		||||
<script src="./react_button.js" ></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@import '../../_variables.scss';
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ export default {
 | 
			
		||||
  props: [ 'user' ],
 | 
			
		||||
  computed: {
 | 
			
		||||
    subscribeUrl () {
 | 
			
		||||
       
 | 
			
		||||
      // eslint-disable-next-line no-undef
 | 
			
		||||
      const serverUrl = new URL(this.user.statusnet_profile_url)
 | 
			
		||||
      return `${serverUrl.protocol}//${serverUrl.host}/main/ostatus`
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,4 @@
 | 
			
		||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
 | 
			
		||||
import ScopeSelector from '../scope_selector/scope_selector.vue'
 | 
			
		||||
import scopeUtils from 'src/lib/scope_utils.js'
 | 
			
		||||
import { library } from '@fortawesome/fontawesome-svg-core'
 | 
			
		||||
import { faRetweet } from '@fortawesome/free-solid-svg-icons'
 | 
			
		||||
 | 
			
		||||
@ -9,14 +7,12 @@ library.add(faRetweet)
 | 
			
		||||
const RetweetButton = {
 | 
			
		||||
  props: ['status', 'loggedIn', 'visibility'],
 | 
			
		||||
  components: {
 | 
			
		||||
    ConfirmModal,
 | 
			
		||||
    ScopeSelector
 | 
			
		||||
    ConfirmModal
 | 
			
		||||
  },
 | 
			
		||||
  data () {
 | 
			
		||||
    return {
 | 
			
		||||
      animated: false,
 | 
			
		||||
      showingConfirmDialog: false,
 | 
			
		||||
      retweetVisibility: this.$store.state.users.currentUser.default_scope
 | 
			
		||||
      showingConfirmDialog: false
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
@ -29,7 +25,7 @@ const RetweetButton = {
 | 
			
		||||
    },
 | 
			
		||||
    doRetweet () {
 | 
			
		||||
      if (!this.status.repeated) {
 | 
			
		||||
        this.$store.dispatch('retweet', { id: this.status.id, visibility: this.retweetVisibility })
 | 
			
		||||
        this.$store.dispatch('retweet', { id: this.status.id })
 | 
			
		||||
      } else {
 | 
			
		||||
        this.$store.dispatch('unretweet', { id: this.status.id })
 | 
			
		||||
      }
 | 
			
		||||
@ -44,9 +40,6 @@ const RetweetButton = {
 | 
			
		||||
    },
 | 
			
		||||
    hideConfirmDialog () {
 | 
			
		||||
      this.showingConfirmDialog = false
 | 
			
		||||
    },
 | 
			
		||||
    changeVis (visibility) {
 | 
			
		||||
      this.retweetVisibility = visibility
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
@ -61,15 +54,6 @@ const RetweetButton = {
 | 
			
		||||
    },
 | 
			
		||||
    remoteInteractionLink () {
 | 
			
		||||
      return this.$store.getters.remoteInteractionLink({ statusId: this.status.id })
 | 
			
		||||
    },
 | 
			
		||||
    userDefaultScope () {
 | 
			
		||||
      return this.$store.state.users.currentUser.default_scope
 | 
			
		||||
    },
 | 
			
		||||
    statusScope () {
 | 
			
		||||
      return this.status.visibility
 | 
			
		||||
    },
 | 
			
		||||
    initialScope () {
 | 
			
		||||
      return scopeUtils.negotiate(this.userDefaultScope, this.status.visibility)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -49,18 +49,12 @@
 | 
			
		||||
        @cancelled="hideConfirmDialog"
 | 
			
		||||
      >
 | 
			
		||||
        {{ $t('status.repeat_confirm') }}
 | 
			
		||||
        <scope-selector
 | 
			
		||||
          :user-default="userDefaultScope"
 | 
			
		||||
          :original-scope="statusScope"
 | 
			
		||||
          :initial-scope="initialScope"
 | 
			
		||||
          :on-scope-change="changeVis"
 | 
			
		||||
        />
 | 
			
		||||
      </confirm-modal>
 | 
			
		||||
    </teleport>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./retweet_button.js"></script>
 | 
			
		||||
<script src="./retweet_button.js" ></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@import '../../_variables.scss';
 | 
			
		||||
 | 
			
		||||
@ -121,19 +121,6 @@ export default {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const mfmStyleFromDataAttributes = (attributes) => {
 | 
			
		||||
      // CSS selectors can check if a data-* attribute is true, but can't use other values, so we want to add them to the style attribute
 | 
			
		||||
      // Here we turn e.g. `{'data-mfm-some': '1deg', 'data-mfm-thing': '5s'}` to "--mfm-some: 1deg;--mfm-thing: 5s;"
 | 
			
		||||
      // Note that we only add the value to `style` when they contain only letters, numbers, dot, or minus signs
 | 
			
		||||
      // At the moment of writing, this should be enough for legitimate purposes and reduces the chance of injection by using special characters
 | 
			
		||||
      // There is a special case for the `color` value, who is provided without `#`, but requires this in the `style` attribute
 | 
			
		||||
      return Object.keys(attributes).filter(
 | 
			
		||||
        (key) => key.startsWith('data-mfm-') && attributes[key] !== true && /^[a-zA-Z0-9.\-]*$/.test(attributes[key])
 | 
			
		||||
      ).map(
 | 
			
		||||
        (key) => '--mfm-' + key.substr(9) + (key === 'data-mfm-color' ? ': #' : ': ') + attributes[key] + ';'
 | 
			
		||||
      ).reduce((a,v) => a+v, '')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Processor to use with html_tree_converter
 | 
			
		||||
    const processItem = (item, index, array, what) => {
 | 
			
		||||
      // Handle text nodes - just add emoji
 | 
			
		||||
@ -204,15 +191,6 @@ export default {
 | 
			
		||||
            if (this.handleLinks && attrs?.['class']?.includes?.('h-card')) {
 | 
			
		||||
              return ['', children.map(processItem), '']
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let mfm_style = mfmStyleFromDataAttributes(attrs)
 | 
			
		||||
            if (mfm_style !== '') {
 | 
			
		||||
              return [
 | 
			
		||||
                opener.slice(0,-1) + ' style="' + mfm_style + '">',
 | 
			
		||||
                children.map(processItem),
 | 
			
		||||
                closer
 | 
			
		||||
              ]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (children !== undefined) {
 | 
			
		||||
 | 
			
		||||
@ -6,8 +6,6 @@ import {
 | 
			
		||||
  faGlobe
 | 
			
		||||
} from '@fortawesome/free-solid-svg-icons'
 | 
			
		||||
 | 
			
		||||
import scopeUtils from 'src/lib/scope_utils.js'
 | 
			
		||||
 | 
			
		||||
library.add(
 | 
			
		||||
  faEnvelope,
 | 
			
		||||
  faGlobe,
 | 
			
		||||
@ -15,11 +13,18 @@ library.add(
 | 
			
		||||
  faLockOpen
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const SCOPE_LEVELS = {
 | 
			
		||||
  'direct': 0,
 | 
			
		||||
  'private': 1,
 | 
			
		||||
  'local': 2,
 | 
			
		||||
  'unlisted': 2,
 | 
			
		||||
  'public': 3
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ScopeSelector = {
 | 
			
		||||
  props: [
 | 
			
		||||
    'showAll',
 | 
			
		||||
    'userDefault',
 | 
			
		||||
    // scope of parent object
 | 
			
		||||
    'originalScope',
 | 
			
		||||
    'initialScope',
 | 
			
		||||
    'onScopeChange'
 | 
			
		||||
@ -34,16 +39,16 @@ const ScopeSelector = {
 | 
			
		||||
      return !this.showPublic && !this.showUnlisted && !this.showPrivate && !this.showDirect
 | 
			
		||||
    },
 | 
			
		||||
    showPublic () {
 | 
			
		||||
      return this.shouldShow('public')
 | 
			
		||||
      return this.originalScope !== 'direct' && this.shouldShow('public')
 | 
			
		||||
    },
 | 
			
		||||
    showLocal () {
 | 
			
		||||
      return this.shouldShow('local')
 | 
			
		||||
      return this.originalScope !== 'direct' && this.shouldShow('local')
 | 
			
		||||
    },
 | 
			
		||||
    showUnlisted () {
 | 
			
		||||
      return this.shouldShow('unlisted')
 | 
			
		||||
      return this.originalScope !== 'direct' && this.shouldShow('unlisted')
 | 
			
		||||
    },
 | 
			
		||||
    showPrivate () {
 | 
			
		||||
      return this.shouldShow('private')
 | 
			
		||||
      return this.originalScope !== 'direct' && this.shouldShow('private')
 | 
			
		||||
    },
 | 
			
		||||
    showDirect () {
 | 
			
		||||
      return this.shouldShow('direct')
 | 
			
		||||
@ -60,10 +65,15 @@ const ScopeSelector = {
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    shouldShow (scope) {
 | 
			
		||||
      if (!this.originalScope)
 | 
			
		||||
      if (!this.originalScope) {
 | 
			
		||||
        return true
 | 
			
		||||
      else
 | 
			
		||||
        return scopeUtils.compare(scope, this.originalScope) <= 0
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this.originalScope === 'local') {
 | 
			
		||||
        return scope === 'direct' || scope === 'local'
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return SCOPE_LEVELS[scope] <= SCOPE_LEVELS[this.originalScope]
 | 
			
		||||
    },
 | 
			
		||||
    changeVis (scope) {
 | 
			
		||||
      this.currentScope = scope
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@
 | 
			
		||||
      :items="items"
 | 
			
		||||
      :get-key="getKey"
 | 
			
		||||
    >
 | 
			
		||||
      <template #item="{item}">
 | 
			
		||||
      <template v-slot:item="{item}">
 | 
			
		||||
        <div
 | 
			
		||||
          class="selectable-list-item-inner"
 | 
			
		||||
          :class="{ 'selectable-list-item-selected-inner': isSelected(item) }"
 | 
			
		||||
@ -41,7 +41,7 @@
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
      <template #empty>
 | 
			
		||||
      <template v-slot:empty>
 | 
			
		||||
        <slot name="empty" />
 | 
			
		||||
      </template>
 | 
			
		||||
    </List>
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@
 | 
			
		||||
    <Checkbox
 | 
			
		||||
      :model-value="state"
 | 
			
		||||
      :disabled="disabled"
 | 
			
		||||
      @update:model-value="update"
 | 
			
		||||
      @update:modelValue="update"
 | 
			
		||||
    >
 | 
			
		||||
      <span
 | 
			
		||||
        v-if="!!$slots.default"
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@
 | 
			
		||||
    <Select
 | 
			
		||||
      :model-value="state"
 | 
			
		||||
      :disabled="disabled"
 | 
			
		||||
      @update:model-value="update"
 | 
			
		||||
      @update:modelValue="update"
 | 
			
		||||
    >
 | 
			
		||||
      <option
 | 
			
		||||
        v-for="option in options"
 | 
			
		||||
 | 
			
		||||
@ -6,14 +6,14 @@
 | 
			
		||||
    <Popover
 | 
			
		||||
      trigger="hover"
 | 
			
		||||
    >
 | 
			
		||||
      <template #trigger>
 | 
			
		||||
      <template v-slot:trigger>
 | 
			
		||||
         
 | 
			
		||||
        <FAIcon
 | 
			
		||||
          icon="wrench"
 | 
			
		||||
          :aria-label="$t('settings.setting_changed')"
 | 
			
		||||
        />
 | 
			
		||||
      </template>
 | 
			
		||||
      <template #content>
 | 
			
		||||
      <template v-slot:content>
 | 
			
		||||
        <div class="modified-tooltip">
 | 
			
		||||
          {{ $t('settings.setting_changed') }}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@ -6,14 +6,14 @@
 | 
			
		||||
    <Popover
 | 
			
		||||
      trigger="hover"
 | 
			
		||||
    >
 | 
			
		||||
      <template #trigger>
 | 
			
		||||
      <template v-slot:trigger>
 | 
			
		||||
         
 | 
			
		||||
        <FAIcon
 | 
			
		||||
          icon="server"
 | 
			
		||||
          :aria-label="$t('settings.setting_server_side')"
 | 
			
		||||
        />
 | 
			
		||||
      </template>
 | 
			
		||||
      <template #content>
 | 
			
		||||
      <template v-slot:content>
 | 
			
		||||
        <div class="serverside-tooltip">
 | 
			
		||||
          {{ $t('settings.setting_server_side') }}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@ -69,7 +69,7 @@ const SettingsModal = {
 | 
			
		||||
      this.$store.dispatch('closeSettingsModal')
 | 
			
		||||
    },
 | 
			
		||||
    logout () {
 | 
			
		||||
      this.$router.replace(this.$store.state.instance.redirectRootNoLogin || '/main/all')
 | 
			
		||||
      this.$router.replace('/main/public')
 | 
			
		||||
      this.$store.dispatch('closeSettingsModal')
 | 
			
		||||
      this.$store.dispatch('logout')
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
@ -108,7 +108,7 @@
 | 
			
		||||
        <Checkbox
 | 
			
		||||
          :model-value="!!expertLevel"
 | 
			
		||||
          class="expertMode"
 | 
			
		||||
          @update:model-value="expertLevel = Number($event)"
 | 
			
		||||
          @update:modelValue="expertLevel = Number($event)"
 | 
			
		||||
        >
 | 
			
		||||
          {{ $t("settings.expert_mode") }}
 | 
			
		||||
        </Checkbox>
 | 
			
		||||
 | 
			
		||||
@ -72,7 +72,7 @@ const DataImportExportTab = {
 | 
			
		||||
        // check is it's a local user
 | 
			
		||||
        if (user && user.is_local) {
 | 
			
		||||
          // append the instance address
 | 
			
		||||
           
 | 
			
		||||
          // eslint-disable-next-line no-undef
 | 
			
		||||
          return user.screen_name + '@' + location.hostname
 | 
			
		||||
        }
 | 
			
		||||
        return user.screen_name
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,6 @@ import ScopeSelector from 'src/components/scope_selector/scope_selector.vue'
 | 
			
		||||
import IntegerSetting from '../helpers/integer_setting.vue'
 | 
			
		||||
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
 | 
			
		||||
 | 
			
		||||
import { usePostLanguageOptions } from 'src/lib/post_language'
 | 
			
		||||
import SharedComputedObject from '../helpers/shared_computed_object.js'
 | 
			
		||||
import ServerSideIndicator from '../helpers/server_side_indicator.vue'
 | 
			
		||||
import { library } from '@fortawesome/fontawesome-svg-core'
 | 
			
		||||
@ -18,11 +17,6 @@ library.add(
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const GeneralTab = {
 | 
			
		||||
  setup() {
 | 
			
		||||
    const {postLanguageOptions} = usePostLanguageOptions()
 | 
			
		||||
 | 
			
		||||
    return {postLanguageOptions}
 | 
			
		||||
  },
 | 
			
		||||
  data () {
 | 
			
		||||
    return {
 | 
			
		||||
      subjectLineOptions: ['email', 'noop', 'masto'].map(mode => ({
 | 
			
		||||
@ -124,12 +118,6 @@ const GeneralTab = {
 | 
			
		||||
        this.$store.dispatch('setOption', { name: 'translationLanguage', value: val })
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    postLanguage: {
 | 
			
		||||
      get: function () { return this.$store.getters.mergedConfig.postLanguage },
 | 
			
		||||
      set: function (val) {
 | 
			
		||||
        this.$store.dispatch('setOption', { name: 'postLanguage', value: val })
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    ...SharedComputedObject()
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
 | 
			
		||||
@ -44,6 +44,7 @@
 | 
			
		||||
          <template
 | 
			
		||||
            v-if="profilesExpanded"
 | 
			
		||||
          >
 | 
			
		||||
 | 
			
		||||
            <div
 | 
			
		||||
              v-for="profile in settingsProfiles"
 | 
			
		||||
              :key="profile.id"
 | 
			
		||||
@ -72,24 +73,15 @@
 | 
			
		||||
                </button>
 | 
			
		||||
              </template>
 | 
			
		||||
            </div>
 | 
			
		||||
            <button
 | 
			
		||||
              class="btn button-default"
 | 
			
		||||
              @click="refreshProfiles()"
 | 
			
		||||
            >
 | 
			
		||||
            <button class="btn button-default" @click="refreshProfiles()">
 | 
			
		||||
              {{ $t('settings.settings_profiles_refresh') }}
 | 
			
		||||
              <FAIcon
 | 
			
		||||
                icon="sync"
 | 
			
		||||
                @click="refreshProfiles()"
 | 
			
		||||
              />
 | 
			
		||||
              <FAIcon icon="sync" @click="refreshProfiles()" />
 | 
			
		||||
            </button>
 | 
			
		||||
            <h3>{{ $t('settings.settings_profile_creation') }}</h3>
 | 
			
		||||
            <label for="settings-profile-new-name">
 | 
			
		||||
              {{ $t('settings.settings_profile_creation_new_name_label') }}
 | 
			
		||||
            </label>
 | 
			
		||||
            <input
 | 
			
		||||
              id="settings-profile-new-name"
 | 
			
		||||
              v-model="newProfileName"
 | 
			
		||||
            >
 | 
			
		||||
            <input v-model="newProfileName" id="settings-profile-new-name">
 | 
			
		||||
            <button
 | 
			
		||||
              class="btn button-default"
 | 
			
		||||
              @click="createSettingsProfile"
 | 
			
		||||
@ -159,16 +151,6 @@
 | 
			
		||||
            {{ $t('settings.show_page_backgrounds') }}
 | 
			
		||||
          </BooleanSetting>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
          <BooleanSetting path="centerAlignBio">
 | 
			
		||||
            {{ $t('settings.center_align_bio') }}
 | 
			
		||||
          </BooleanSetting>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
          <BooleanSetting path="compactUserInfo">
 | 
			
		||||
            {{ $t('settings.compact_user_info') }}
 | 
			
		||||
          </BooleanSetting>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
          <BooleanSetting path="stopGifs">
 | 
			
		||||
            {{ $t('settings.stop_gifs') }}
 | 
			
		||||
@ -279,11 +261,6 @@
 | 
			
		||||
            {{ $t('settings.right_sidebar') }}
 | 
			
		||||
          </BooleanSetting>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
          <BooleanSetting path="widenTimeline">
 | 
			
		||||
            {{ $t('settings.widen_timeline') }}
 | 
			
		||||
          </BooleanSetting>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
          <ChoiceSetting
 | 
			
		||||
            v-if="user"
 | 
			
		||||
@ -608,15 +585,6 @@
 | 
			
		||||
            {{ $t('settings.post_status_content_type') }}
 | 
			
		||||
          </ChoiceSetting>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
          <ChoiceSetting
 | 
			
		||||
            id="postLanguage"
 | 
			
		||||
            path="postLanguage"
 | 
			
		||||
            :options="postLanguageOptions"
 | 
			
		||||
          >
 | 
			
		||||
            {{ $t('settings.post_language') }}
 | 
			
		||||
          </ChoiceSetting>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
          <BooleanSetting
 | 
			
		||||
            path="alwaysShowNewPostButton"
 | 
			
		||||
 | 
			
		||||
@ -85,7 +85,7 @@ const MutesAndBlocks = {
 | 
			
		||||
        // check is it's a local user
 | 
			
		||||
        if (user && user.is_local) {
 | 
			
		||||
          // append the instance address
 | 
			
		||||
           
 | 
			
		||||
          // eslint-disable-next-line no-undef
 | 
			
		||||
          return user.screen_name + '@' + location.hostname
 | 
			
		||||
        }
 | 
			
		||||
        return user.screen_name
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@
 | 
			
		||||
          :query="queryUserIds"
 | 
			
		||||
          :placeholder="$t('settings.search_user_to_block')"
 | 
			
		||||
        >
 | 
			
		||||
          <template #default="row">
 | 
			
		||||
          <template v-slot="row">
 | 
			
		||||
            <BlockCard
 | 
			
		||||
              :user-id="row.item"
 | 
			
		||||
            />
 | 
			
		||||
@ -21,7 +21,7 @@
 | 
			
		||||
        :refresh="true"
 | 
			
		||||
        :get-key="i => i"
 | 
			
		||||
      >
 | 
			
		||||
        <template #header="{selected}">
 | 
			
		||||
        <template v-slot:header="{selected}">
 | 
			
		||||
          <div class="bulk-actions">
 | 
			
		||||
            <ProgressButton
 | 
			
		||||
              v-if="selected.length > 0"
 | 
			
		||||
@ -29,7 +29,7 @@
 | 
			
		||||
              :click="() => blockUsers(selected)"
 | 
			
		||||
            >
 | 
			
		||||
              {{ $t('user_card.block') }}
 | 
			
		||||
              <template #progress>
 | 
			
		||||
              <template v-slot:progress>
 | 
			
		||||
                {{ $t('user_card.block_progress') }}
 | 
			
		||||
              </template>
 | 
			
		||||
            </ProgressButton>
 | 
			
		||||
@ -39,16 +39,16 @@
 | 
			
		||||
              :click="() => unblockUsers(selected)"
 | 
			
		||||
            >
 | 
			
		||||
              {{ $t('user_card.unblock') }}
 | 
			
		||||
              <template #progress>
 | 
			
		||||
              <template v-slot:progress>
 | 
			
		||||
                {{ $t('user_card.unblock_progress') }}
 | 
			
		||||
              </template>
 | 
			
		||||
            </ProgressButton>
 | 
			
		||||
          </div>
 | 
			
		||||
        </template>
 | 
			
		||||
        <template #item="{item}">
 | 
			
		||||
        <template v-slot:item="{item}">
 | 
			
		||||
          <BlockCard :user-id="item" />
 | 
			
		||||
        </template>
 | 
			
		||||
        <template #empty>
 | 
			
		||||
        <template v-slot:empty>
 | 
			
		||||
          {{ $t('settings.no_blocks') }}
 | 
			
		||||
        </template>
 | 
			
		||||
      </BlockList>
 | 
			
		||||
@ -63,7 +63,7 @@
 | 
			
		||||
              :query="queryUserIds"
 | 
			
		||||
              :placeholder="$t('settings.search_user_to_mute')"
 | 
			
		||||
            >
 | 
			
		||||
              <template #default="row">
 | 
			
		||||
              <template v-slot="row">
 | 
			
		||||
                <MuteCard
 | 
			
		||||
                  :user-id="row.item"
 | 
			
		||||
                />
 | 
			
		||||
@ -74,7 +74,7 @@
 | 
			
		||||
            :refresh="true"
 | 
			
		||||
            :get-key="i => i"
 | 
			
		||||
          >
 | 
			
		||||
            <template #header="{selected}">
 | 
			
		||||
            <template v-slot:header="{selected}">
 | 
			
		||||
              <div class="bulk-actions">
 | 
			
		||||
                <ProgressButton
 | 
			
		||||
                  v-if="selected.length > 0"
 | 
			
		||||
@ -82,7 +82,7 @@
 | 
			
		||||
                  :click="() => muteUsers(selected)"
 | 
			
		||||
                >
 | 
			
		||||
                  {{ $t('user_card.mute') }}
 | 
			
		||||
                  <template #progress>
 | 
			
		||||
                  <template v-slot:progress>
 | 
			
		||||
                    {{ $t('user_card.mute_progress') }}
 | 
			
		||||
                  </template>
 | 
			
		||||
                </ProgressButton>
 | 
			
		||||
@ -92,16 +92,16 @@
 | 
			
		||||
                  :click="() => unmuteUsers(selected)"
 | 
			
		||||
                >
 | 
			
		||||
                  {{ $t('user_card.unmute') }}
 | 
			
		||||
                  <template #progress>
 | 
			
		||||
                  <template v-slot:progress>
 | 
			
		||||
                    {{ $t('user_card.unmute_progress') }}
 | 
			
		||||
                  </template>
 | 
			
		||||
                </ProgressButton>
 | 
			
		||||
              </div>
 | 
			
		||||
            </template>
 | 
			
		||||
            <template #item="{item}">
 | 
			
		||||
            <template v-slot:item="{item}">
 | 
			
		||||
              <MuteCard :user-id="item" />
 | 
			
		||||
            </template>
 | 
			
		||||
            <template #empty>
 | 
			
		||||
            <template v-slot:empty>
 | 
			
		||||
              {{ $t('settings.no_mutes') }}
 | 
			
		||||
            </template>
 | 
			
		||||
          </MuteList>
 | 
			
		||||
@ -114,7 +114,7 @@
 | 
			
		||||
              :query="queryKnownDomains"
 | 
			
		||||
              :placeholder="$t('settings.type_domains_to_mute')"
 | 
			
		||||
            >
 | 
			
		||||
              <template #default="row">
 | 
			
		||||
              <template v-slot="row">
 | 
			
		||||
                <DomainMuteCard
 | 
			
		||||
                  :domain="row.item"
 | 
			
		||||
                />
 | 
			
		||||
@ -125,7 +125,7 @@
 | 
			
		||||
            :refresh="true"
 | 
			
		||||
            :get-key="i => i"
 | 
			
		||||
          >
 | 
			
		||||
            <template #header="{selected}">
 | 
			
		||||
            <template v-slot:header="{selected}">
 | 
			
		||||
              <div class="bulk-actions">
 | 
			
		||||
                <ProgressButton
 | 
			
		||||
                  v-if="selected.length > 0"
 | 
			
		||||
@ -133,16 +133,16 @@
 | 
			
		||||
                  :click="() => unmuteDomains(selected)"
 | 
			
		||||
                >
 | 
			
		||||
                  {{ $t('domain_mute_card.unmute') }}
 | 
			
		||||
                  <template #progress>
 | 
			
		||||
                  <template v-slot:progress>
 | 
			
		||||
                    {{ $t('domain_mute_card.unmute_progress') }}
 | 
			
		||||
                  </template>
 | 
			
		||||
                </ProgressButton>
 | 
			
		||||
              </div>
 | 
			
		||||
            </template>
 | 
			
		||||
            <template #item="{item}">
 | 
			
		||||
            <template v-slot:item="{item}">
 | 
			
		||||
              <DomainMuteCard :domain="item" />
 | 
			
		||||
            </template>
 | 
			
		||||
            <template #empty>
 | 
			
		||||
            <template v-slot:empty>
 | 
			
		||||
              {{ $t('settings.no_mutes') }}
 | 
			
		||||
            </template>
 | 
			
		||||
          </DomainMuteList>
 | 
			
		||||
 | 
			
		||||
@ -130,7 +130,7 @@ const ProfileTab = {
 | 
			
		||||
        note: this.newBio,
 | 
			
		||||
        locked: this.newLocked,
 | 
			
		||||
        // Backend notation.
 | 
			
		||||
         
 | 
			
		||||
        /* eslint-disable camelcase */
 | 
			
		||||
        display_name: this.newName,
 | 
			
		||||
        fields_attributes: this.newFields.filter(el => el != null),
 | 
			
		||||
        bot: this.bot,
 | 
			
		||||
@ -138,7 +138,7 @@ const ProfileTab = {
 | 
			
		||||
        status_ttl_days: this.expirePosts ? this.newPostTTLDays : -1,
 | 
			
		||||
        permit_followback: this.permit_followback,
 | 
			
		||||
        accepts_direct_messages_from: this.userAcceptsDirectMessagesFrom
 | 
			
		||||
         
 | 
			
		||||
        /* eslint-enable camelcase */
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this.emailLanguage) {
 | 
			
		||||
@ -187,7 +187,7 @@ const ProfileTab = {
 | 
			
		||||
        })
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
       
 | 
			
		||||
      // eslint-disable-next-line no-undef
 | 
			
		||||
      const reader = new FileReader()
 | 
			
		||||
      reader.onload = ({ target }) => {
 | 
			
		||||
        const img = target.result
 | 
			
		||||
 | 
			
		||||
@ -110,9 +110,11 @@
 | 
			
		||||
          max="730"
 | 
			
		||||
          class="expire-posts-days"
 | 
			
		||||
          :placeholder="$t('settings.expire_posts_input_placeholder')"
 | 
			
		||||
        >
 | 
			
		||||
        />
 | 
			
		||||
      </p>
 | 
			
		||||
      <p>
 | 
			
		||||
 | 
			
		||||
      </p>
 | 
			
		||||
      <p />
 | 
			
		||||
      <p>
 | 
			
		||||
        <interface-language-switcher
 | 
			
		||||
          :prompt-text="$t('settings.email_language')"
 | 
			
		||||
 | 
			
		||||
@ -1,25 +1,22 @@
 | 
			
		||||
import { extractCommit } from 'src/services/version/version.service'
 | 
			
		||||
 | 
			
		||||
function joinURL(base, subpath) {
 | 
			
		||||
  return URL.parse(subpath, base)?.href || "invalid base URL"
 | 
			
		||||
}
 | 
			
		||||
const pleromaFeCommitUrl = 'https://akkoma.dev/AkkomaGang/pleroma-fe/commit/'
 | 
			
		||||
const pleromaBeCommitUrl = 'https://akkoma.dev/AkkomaGang/akkoma/commit/'
 | 
			
		||||
 | 
			
		||||
const VersionTab = {
 | 
			
		||||
  data () {
 | 
			
		||||
    const instance = this.$store.state.instance
 | 
			
		||||
    return {
 | 
			
		||||
      backendCommitUrl: instance.backendCommitUrl,
 | 
			
		||||
      backendVersion: instance.backendVersion,
 | 
			
		||||
      frontendCommitUrl: instance.frontendCommitUrl,
 | 
			
		||||
      frontendVersion: instance.frontendVersion
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    frontendVersionLink () {
 | 
			
		||||
      return joinURL(this.frontendCommitUrl, this.frontendVersion)
 | 
			
		||||
      return pleromaFeCommitUrl + this.frontendVersion
 | 
			
		||||
    },
 | 
			
		||||
    backendVersionLink () {
 | 
			
		||||
      return joinURL(this.backendCommitUrl, extractCommit(this.backendVersion))
 | 
			
		||||
      return pleromaBeCommitUrl + extractCommit(this.backendVersion)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -215,7 +215,7 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./shadow_control.js"></script>
 | 
			
		||||
<script src="./shadow_control.js" ></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@import '../../_variables.scss';
 | 
			
		||||
 | 
			
		||||
@ -218,7 +218,7 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./side_drawer.js"></script>
 | 
			
		||||
<script src="./side_drawer.js" ></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@import '../../_variables.scss';
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script src="./staff_panel.js"></script>
 | 
			
		||||
<script src="./staff_panel.js" ></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -266,16 +266,6 @@
 | 
			
		||||
      color: $fallback--cGreen;
 | 
			
		||||
      color: var(--cGreen, $fallback--cGreen);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .right-side {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      gap: 0.3em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .repeat-tooltip {
 | 
			
		||||
      flex-shrink: 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .repeater-avatar {
 | 
			
		||||
 | 
			
		||||
@ -83,7 +83,7 @@
 | 
			
		||||
          :user="statusoid.user"
 | 
			
		||||
        />
 | 
			
		||||
        <div class="right-side faint">
 | 
			
		||||
          <div
 | 
			
		||||
          <span
 | 
			
		||||
            class="status-username repeater-name"
 | 
			
		||||
            :title="retweeter"
 | 
			
		||||
          >
 | 
			
		||||
@ -100,19 +100,14 @@
 | 
			
		||||
              v-else
 | 
			
		||||
              :to="retweeterProfileLink"
 | 
			
		||||
            >{{ retweeter }}</router-link>
 | 
			
		||||
          </div>
 | 
			
		||||
          </span>
 | 
			
		||||
          {{ ' ' }}
 | 
			
		||||
 | 
			
		||||
          <div
 | 
			
		||||
            class="repeat-tooltip"
 | 
			
		||||
          >
 | 
			
		||||
             <FAIcon
 | 
			
		||||
               icon="retweet"
 | 
			
		||||
               class="repeat-icon"
 | 
			
		||||
               :title="$t('tool_tip.repeat')"
 | 
			
		||||
             />
 | 
			
		||||
             {{ $t('timeline.repeated') }}
 | 
			
		||||
          </div>
 | 
			
		||||
          <FAIcon
 | 
			
		||||
            icon="retweet"
 | 
			
		||||
            class="repeat-icon"
 | 
			
		||||
            :title="$t('tool_tip.repeat')"
 | 
			
		||||
          />
 | 
			
		||||
          {{ $t('timeline.repeated') }}
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@ -373,7 +368,7 @@
 | 
			
		||||
              :controlled-toggle-showing-long-subject="controlledToggleShowingLongSubject"
 | 
			
		||||
              @mediaplay="addMediaPlaying($event)"
 | 
			
		||||
              @mediapause="removeMediaPlaying($event)"
 | 
			
		||||
              @parse-ready="setHeadTailLinks"
 | 
			
		||||
              @parseReady="setHeadTailLinks"
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
@ -481,8 +476,8 @@
 | 
			
		||||
            />
 | 
			
		||||
            <extra-buttons
 | 
			
		||||
              :status="status"
 | 
			
		||||
              @on-error="showError"
 | 
			
		||||
              @on-success="clearError"
 | 
			
		||||
              @onError="showError"
 | 
			
		||||
              @onSuccess="clearError"
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
@ -519,7 +514,6 @@
 | 
			
		||||
          :reply-to="status.id"
 | 
			
		||||
          :attentions="status.attentions"
 | 
			
		||||
          :replied-user="status.user"
 | 
			
		||||
          :copy-message-language="status.language"
 | 
			
		||||
          :copy-message-scope="status.visibility"
 | 
			
		||||
          :subject="replySubject"
 | 
			
		||||
          @posted="toggleReplying"
 | 
			
		||||
@ -534,7 +528,6 @@
 | 
			
		||||
          :quote-id="status.id"
 | 
			
		||||
          :attentions="[status.user]"
 | 
			
		||||
          :replied-user="status.user"
 | 
			
		||||
          :copy-message-language="status.language"
 | 
			
		||||
          :copy-message-scope="status.visibility"
 | 
			
		||||
          :subject="replySubject"
 | 
			
		||||
          @posted="toggleQuoting"
 | 
			
		||||
 | 
			
		||||
@ -41,8 +41,7 @@ const StatusContent = {
 | 
			
		||||
      postLength: this.status.text.length,
 | 
			
		||||
      parseReadyDone: false,
 | 
			
		||||
      renderMisskeyMarkdown,
 | 
			
		||||
      translateFrom: null,
 | 
			
		||||
      translating: false
 | 
			
		||||
      translateFrom: null
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
@ -136,10 +135,7 @@ const StatusContent = {
 | 
			
		||||
    },
 | 
			
		||||
    translateStatus () {
 | 
			
		||||
      const translateTo = this.$store.getters.mergedConfig.translationLanguage || this.$store.state.instance.interfaceLanguage
 | 
			
		||||
      this.translating = true
 | 
			
		||||
      this.$store.dispatch(
 | 
			
		||||
        'translateStatus', { id: this.status.id, language: translateTo, from: this.translateFrom }
 | 
			
		||||
      ).finally(() => { this.translating = false })
 | 
			
		||||
      this.$store.dispatch('translateStatus', { id: this.status.id, language: translateTo, from: this.translateFrom })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user