Compare commits
No commits in common. "bnakkoma" and "develop" have entirely different histories.
189 changed files with 9108 additions and 10215 deletions
|
@ -1,13 +0,0 @@
|
|||
.DS_Store
|
||||
node_modules/
|
||||
dist/
|
||||
npm-debug.log
|
||||
test/unit/coverage
|
||||
test/e2e/reports
|
||||
selenium-debug.log
|
||||
.idea/
|
||||
config/local.json
|
||||
config/local.*.json
|
||||
docs/site/
|
||||
.vscode/
|
||||
akkoma-fe.zip
|
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
|
||||
}
|
||||
}
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -9,5 +9,4 @@ selenium-debug.log
|
|||
config/local.json
|
||||
config/local.*.json
|
||||
docs/site/
|
||||
.vscode/
|
||||
akkoma-fe.zip
|
||||
.vscode/
|
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,15 +40,15 @@ steps:
|
|||
branch:
|
||||
- develop
|
||||
- stable
|
||||
image: node:20
|
||||
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
|
||||
|
@ -71,8 +70,8 @@ steps:
|
|||
- 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
|
||||
|
@ -80,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/
|
||||
|
|
24
Dockerfile
24
Dockerfile
|
@ -1,24 +0,0 @@
|
|||
FROM node:22.6.0-alpine3.20 as build
|
||||
|
||||
RUN apk add --no-cache \
|
||||
git \
|
||||
chromium-chromedriver
|
||||
|
||||
# use chromedriver from apk
|
||||
ENV CHROMEDRIVER_FILEPATH=/usr/bin/chromedriver
|
||||
|
||||
WORKDIR /app/
|
||||
|
||||
ARG NODE_ENV=production
|
||||
ENV YARN_CACHE_FOLDER=/.yarn/
|
||||
|
||||
COPY . /app/
|
||||
|
||||
RUN \
|
||||
--mount=type=cache,target=/.yarn \
|
||||
NODE_ENV=development yarn install \
|
||||
&& NODE_ENV=${NODE_ENV} yarn run build
|
||||
|
||||
FROM scratch as result
|
||||
|
||||
COPY --from=build /app/dist/ /
|
26
Makefile
26
Makefile
|
@ -1,26 +0,0 @@
|
|||
RUNTIME ?= docker
|
||||
BUILD_DIR ?= ./dist/
|
||||
OUTFILE_ZIP ?= ./akkoma-fe.zip
|
||||
NODE_ENV ?= production
|
||||
|
||||
.PHONY: all
|
||||
all: build-fe package
|
||||
|
||||
.PHONY: build-fe
|
||||
build-fe:
|
||||
ifeq ("$(wildcard $(BUILD_DIR))","")
|
||||
mkdir $(BUILD_DIR)
|
||||
else
|
||||
rm -rf $(BUILD_DIR)
|
||||
mkdir $(BUILD_DIR)
|
||||
endif
|
||||
$(RUNTIME) build --build-arg NODE_ENV=$(NODE_ENV) --output type=local,dest=./dist/ .
|
||||
|
||||
.PHONY: package
|
||||
package:
|
||||
ifneq ("$(wildcard $(OUTFILE_ZIP))","")
|
||||
rm $(OUTFILE_ZIP)
|
||||
endif
|
||||
zip -r -9 $(OUTFILE_ZIP) $(BUILD_DIR)
|
||||
|
||||
# vim:set noexpandtab:
|
80
README.md
80
README.md
|
@ -1,76 +1,28 @@
|
|||
# BNAkkoma: Brand New Akkoma
|
||||
# Akkoma-FE
|
||||
|
||||
<small>It's not that new. This is just a cheap pun on the title of a [furry anime](https://en.wikipedia.org/wiki/BNA:_Brand_New_Animal).</small>
|
||||
|
||||
Also keep in mind that B in BNAkkoma stands for 'bleeding-edge', **features can be added, changed or removed at any time!**
|
||||
|
||||
If you are experiencing any strange quirks, make sure both your frontend and backend are up to date.
|
||||
|
||||
If your software are up to date but the bug is still there, ping me on the fediverse at '@itepechi@fedi.itepechi.me'.
|
||||
|
||||
## What is this?
|
||||
|
||||
This is a fork of [AkkomaGang/akkoma-fe](https://akkoma.dev/AkkomaGang/akkoma-fe/), with a custom theme and Makefile.
|
||||
|
||||
The differences from the upstream repository are described below:
|
||||
|
||||
- Added a Makefile where you can build the client inside a Docker container
|
||||
- Refactored some components and styles
|
||||
- Fixed a lot of broken CSS rules and misaligned elements
|
||||
- Added and changed the default theme to a custom one
|
||||
- Added support for setting the default post language
|
||||
- Stole some commits from the original Pleroma frontend
|
||||
- Added OpenSearch protocol support
|
||||
- Added the Media tab to the search page (requires appropriate backend)
|
||||
- Added 'Limit to Following' filter to the search page (requires appropriate backend)
|
||||
- Added 'Limit to Local' filter to the search page (requires appropriate backend)
|
||||
- Implemented lazy loading of search results
|
||||
- More than 200 (!) Japanese translations have been fixed
|
||||
- Removed some themes to save network bandwidth
|
||||
- Improved PWA support, sort of
|
||||
|
||||
Although this frontend application is designed to work with the BNAkkoma backend, it remains compatible with the original version of Akkoma by only enabling additional features when the backend server returns a corresponding flag.
|
||||
|
||||
### How to build
|
||||
|
||||
**Requires 2GB+ memory.**
|
||||
|
||||
```sh
|
||||
# Docker
|
||||
make
|
||||
|
||||
# Podman
|
||||
make RUNTIME=podman
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Akkoma-FE
|
||||
|
||||
 
|
||||
 
|
||||
|
||||
This is a fork of Akkoma-FE from the Pleroma project, with support for new Akkoma features such as:
|
||||
|
||||
- MFM support via [marked-mfm](https://akkoma.dev/sfr/marked-mfm)
|
||||
- Custom emoji reactions
|
||||
|
||||
## For Translators
|
||||
# For Translators
|
||||
|
||||
The [Weblate UI](https://translate.akkoma.dev/projects/akkoma/pleroma-fe/) is recommended for adding or modifying translations for Akkoma-FE.
|
||||
The [Weblate UI](https://translate.akkoma.dev/projects/akkoma/pleroma-fe/) is recommended for adding or modifying translations for Akkoma-FE.
|
||||
|
||||
Alternatively, edit/create `src/i18n/$LANGUAGE_CODE.json` (where `$LANGUAGE_CODE` is the [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) for your language), then add your language to [src/i18n/messages.js](https://akkoma.dev/AkkomaGang/pleroma-fe/src/branch/develop/src/i18n/messages.js) if it doesn't already exist there.
|
||||
|
||||
Akkoma-FE will set your language by your browser locale, but you can temporarily force it in the code by changing the locale in main.js.
|
||||
|
||||
## FOR ADMINS
|
||||
# FOR ADMINS
|
||||
|
||||
To use Akkoma-FE in Akkoma, use the [frontend](https://docs.akkoma.dev/stable/administration/CLI_tasks/frontend/) CLI task to install Akkoma-FE, then modify your configuration as described in the [Frontend Management](https://docs.akkoma.dev/stable/configuration/frontend_management/) doc.
|
||||
|
||||
### Build Setup
|
||||
## Build Setup
|
||||
|
||||
```bash
|
||||
``` bash
|
||||
# install dependencies
|
||||
corepack enable
|
||||
npm install -g yarn
|
||||
yarn
|
||||
|
||||
# serve with hot reload at localhost:8080
|
||||
|
@ -83,21 +35,21 @@ npm run build
|
|||
npm run unit
|
||||
```
|
||||
|
||||
## For Contributors:
|
||||
# For Contributors:
|
||||
|
||||
You can create file `/config/local.json` (see [example](https://akkoma.dev/AkkomaGang/akkoma-fe/src/branch/develop/config/local.example.json)) to enable some convenience dev options:
|
||||
You can create file `/config/local.json` (see [example](https://git.pleroma.social/pleroma/pleroma-fe/blob/develop/config/local.example.json)) to enable some convenience dev options:
|
||||
|
||||
- `target`: makes local dev server redirect to some existing instance's BE instead of local BE, useful for testing things in near-production environment and searching for real-life use-cases.
|
||||
- `staticConfigPreference`: makes FE's `/static/config.json` take preference of BE-served `/api/statusnet/config.json`. Only works in dev mode.
|
||||
* `target`: makes local dev server redirect to some existing instance's BE instead of local BE, useful for testing things in near-production environment and searching for real-life use-cases.
|
||||
* `staticConfigPreference`: makes FE's `/static/config.json` take preference of BE-served `/api/statusnet/config.json`. Only works in dev mode.
|
||||
|
||||
FE Build process also leaves current commit hash in global variable `___pleromafe_commit_hash` so that you can easily see which pleroma-fe commit instance is running, also helps pinpointing which commit was used when FE was bundled into BE.
|
||||
|
||||
## Configuration
|
||||
# Configuration
|
||||
|
||||
Edit config.json for configuration.
|
||||
|
||||
### Options
|
||||
## Options
|
||||
|
||||
#### Login methods
|
||||
### Login methods
|
||||
|
||||
`loginMethod` can be set to either `password` (the default) or `token`, which will use the full oauth redirection flow, which is useful for SSO situations.
|
||||
```loginMethod``` can be set to either ```password``` (the default) or ```token```, which will use the full oauth redirection flow, which is useful for SSO situations.
|
||||
|
|
|
@ -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()
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"target": "https://otp.akkoma.dev/",
|
||||
"target": "https://pleroma.soykaf.com/",
|
||||
"staticConfigPreference": false
|
||||
}
|
||||
|
|
|
@ -2,4 +2,5 @@ var { merge } = require('webpack-merge')
|
|||
var devEnv = require('./dev.env')
|
||||
|
||||
module.exports = merge(devEnv, {
|
||||
NODE_ENV: '"testing"'
|
||||
})
|
||||
|
|
|
@ -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'
|
||||
]
|
||||
}
|
||||
]
|
|
@ -11,7 +11,6 @@
|
|||
<link rel="stylesheet" href="/static/theme-holder.css" id="theme-holder">
|
||||
<!--server-generated-meta-->
|
||||
<link rel="icon" type="image/png" href="/favicon.png">
|
||||
<link rel="apple-touch-icon" href="/static/apple-touch-icon.png">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
</head>
|
||||
<body class="hidden">
|
||||
|
|
166
package.json
166
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "pleroma_fe",
|
||||
"version": "3.10.0+bnakkoma",
|
||||
"description": "An extended frontend for Akkoma instances",
|
||||
"version": "3.10.0",
|
||||
"description": "A frontend for Akkoma instances",
|
||||
"author": "Roger Braun <roger@rogerbraun.net>",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
@ -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",
|
||||
|
|
|
@ -64,11 +64,6 @@ export default {
|
|||
'-' + this.layoutType
|
||||
]
|
||||
},
|
||||
pageBackground () {
|
||||
return this.mergedConfig.displayPageBackgrounds
|
||||
? this.$store.state.users.displayBackground
|
||||
: null
|
||||
},
|
||||
currentUser () { return this.$store.state.users.currentUser },
|
||||
userBackground () { return this.currentUser.background_image },
|
||||
instanceBackground () {
|
||||
|
@ -76,7 +71,7 @@ export default {
|
|||
? null
|
||||
: this.$store.state.instance.background
|
||||
},
|
||||
background () { return this.pageBackground || this.userBackground || this.instanceBackground },
|
||||
background () { return this.userBackground || this.instanceBackground },
|
||||
bgStyle () {
|
||||
if (this.background) {
|
||||
return {
|
||||
|
|
17
src/App.scss
17
src/App.scss
|
@ -8,7 +8,7 @@
|
|||
}
|
||||
|
||||
html {
|
||||
font-size: 0.875rem;
|
||||
font-size: 14px;
|
||||
// overflow-x: clip causes my browser's tab to crash with SIGILL lul
|
||||
}
|
||||
|
||||
|
@ -665,12 +665,8 @@ option {
|
|||
}
|
||||
|
||||
.alert {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
margin: 0 0.5em;
|
||||
padding: 0.35em;
|
||||
margin: 0 0.35em;
|
||||
padding: 0 0.25em;
|
||||
border-radius: $fallback--tooltipRadius;
|
||||
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
|
||||
|
||||
|
@ -771,8 +767,6 @@ option {
|
|||
|
||||
.btn.button-default {
|
||||
min-height: 2em;
|
||||
padding-left: 0.5em;
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
|
||||
.new-status-notification {
|
||||
|
@ -786,11 +780,6 @@ option {
|
|||
.mobile-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.btn.button-default {
|
||||
padding-left: 0.7em;
|
||||
padding-right: 0.7em;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
|
|
|
@ -77,9 +77,7 @@ const getInstanceConfig = async ({ store }) => {
|
|||
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
|
||||
// don't override cookie if set
|
||||
if (!Cookies.get('userLanguage')) {
|
||||
const language = resolveLanguage(data.languages)
|
||||
store.dispatch('setOption', { name: 'interfaceLanguage', value: language })
|
||||
store.dispatch('setOption', { name: 'postLanguage', value: language })
|
||||
store.dispatch('setOption', { name: 'interfaceLanguage', value: resolveLanguage(data.languages) })
|
||||
}
|
||||
|
||||
if (vapidPublicKey) {
|
||||
|
@ -185,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'])
|
||||
}
|
||||
|
||||
|
@ -287,10 +279,6 @@ const getNodeInfo = async ({ store }) => {
|
|||
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
|
||||
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
|
||||
store.dispatch('setInstanceOption', { name: 'translationEnabled', value: features.includes('akkoma:machine_translation') })
|
||||
store.dispatch('setInstanceOption', { name: 'searchTypeMediaEnabled', value: features.includes('bnakkoma:search_type_media') })
|
||||
store.dispatch('setInstanceOption', { name: 'searchOptionFollowingEnabled', value: features.includes('bnakkoma:search_option_following') })
|
||||
store.dispatch('setInstanceOption', { name: 'searchOptionLocalEnabled', value: features.includes('bnakkoma:search_option_local') })
|
||||
store.dispatch('setInstanceOption', { name: 'opensearchProtocolSupported', value: features.includes('bnakkoma:opensearch_protocol') })
|
||||
|
||||
const uploadLimits = metadata.uploadLimits
|
||||
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
|
||||
|
@ -414,16 +402,6 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
|||
getTOS({ store })
|
||||
getStickers({ store })
|
||||
|
||||
// Create a link tag for OpenSearch and forget about it
|
||||
if (store.state.instance.opensearchProtocolSupported) {
|
||||
const node = document.createElement('link')
|
||||
node.setAttribute('rel', 'search')
|
||||
node.setAttribute('type', 'application/opensearchdescription+xml')
|
||||
node.setAttribute('href', '/opensearch.xml')
|
||||
node.setAttribute('title', store.state.instance.name)
|
||||
document.head.appendChild(node)
|
||||
}
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: routes(store),
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
white-space: pre-line;
|
||||
word-break: break-word;
|
||||
text-overflow: ellipsis;
|
||||
overflow: auto;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
&.-static {
|
||||
|
|
|
@ -155,7 +155,6 @@
|
|||
>
|
||||
<StillImage
|
||||
class="image"
|
||||
:loading="'lazy'"
|
||||
:referrerpolicy="referrerpolicy"
|
||||
:mimetype="attachment.mimetype"
|
||||
:src="attachment.large_thumb_url || attachment.url"
|
||||
|
@ -247,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';
|
||||
|
|
|
@ -61,11 +61,12 @@
|
|||
}
|
||||
|
||||
&-user-name {
|
||||
--emoji-size: 14px;
|
||||
}
|
||||
|
||||
&-user-name-value {
|
||||
font-weight: bold;
|
||||
img {
|
||||
object-fit: contain;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
&-user-name-value,
|
||||
|
|
|
@ -22,36 +22,31 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['update:modelValue'],
|
||||
props: [
|
||||
'modelValue',
|
||||
'indeterminate',
|
||||
'disabled'
|
||||
],
|
||||
emits: ['update:modelValue']
|
||||
]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
$checkbox-size: 1.2em;
|
||||
$padding-size: 0.5em;
|
||||
|
||||
.checkbox {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
min-height: $checkbox-size;
|
||||
padding-left: calc($checkbox-size + $padding-size);
|
||||
min-height: 1.2em;
|
||||
|
||||
&-indicator {
|
||||
position: relative;
|
||||
padding-left: calc($checkbox-size + $padding-size);
|
||||
margin-left: calc(($checkbox-size + $padding-size) * -1);
|
||||
padding-left: 1.2em;
|
||||
}
|
||||
|
||||
&-indicator::before {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
display: block;
|
||||
content: '✓';
|
||||
|
@ -97,6 +92,11 @@ $padding-size: 0.5em;
|
|||
color: $fallback--text;
|
||||
color: var(--inputText, $fallback--text);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
& > span {
|
||||
margin-left: .5em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -51,6 +51,7 @@ export default {
|
|||
ConfirmModal
|
||||
},
|
||||
data: () => ({
|
||||
searchBarHidden: true,
|
||||
supportsMask: window.CSS && window.CSS.supports && (
|
||||
window.CSS.supports('mask-size', 'contain') ||
|
||||
window.CSS.supports('-webkit-mask-size', 'contain') ||
|
||||
|
@ -76,7 +77,8 @@ export default {
|
|||
},
|
||||
logoBgStyle () {
|
||||
return Object.assign({
|
||||
'margin': `${this.$store.state.instance.logoMargin} 0`
|
||||
'margin': `${this.$store.state.instance.logoMargin} 0`,
|
||||
opacity: this.searchBarHidden ? 1 : 0
|
||||
}, this.enableMask ? {} : {
|
||||
'background-color': this.enableMask ? '' : 'transparent'
|
||||
})
|
||||
|
@ -118,6 +120,9 @@ export default {
|
|||
scrollToTop () {
|
||||
window.scrollTo(0, 0)
|
||||
},
|
||||
onSearchBarToggled (hidden) {
|
||||
this.searchBarHidden = hidden
|
||||
},
|
||||
openSettingsModal () {
|
||||
this.$store.dispatch('openSettingsModal')
|
||||
},
|
||||
|
|
|
@ -56,6 +56,13 @@
|
|||
.logo {
|
||||
grid-area: logo;
|
||||
position: relative;
|
||||
transition: opacity;
|
||||
transition-timing-function: ease-out;
|
||||
transition-duration: 100ms;
|
||||
|
||||
@media all and (min-width: 800px) {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mask {
|
||||
mask-repeat: no-repeat;
|
||||
|
|
|
@ -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
|
||||
|
@ -98,6 +98,7 @@
|
|||
<div class="item right actions">
|
||||
<search-bar
|
||||
v-if="currentUser || !privateMode"
|
||||
@toggled="onSearchBarToggled"
|
||||
@click.stop
|
||||
/>
|
||||
<div
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -43,10 +43,7 @@
|
|||
:class="{ highlighted: index === highlighted }"
|
||||
@click.stop.prevent="onClick($event, suggestion)"
|
||||
>
|
||||
<span
|
||||
v-if="!suggestion.mfm"
|
||||
class="image"
|
||||
>
|
||||
<span v-if="!suggestion.mfm" class="image">
|
||||
<img
|
||||
v-if="suggestion.img"
|
||||
:src="suggestion.img"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./emoji_reactions.js"></script>
|
||||
<script src="./emoji_reactions.js" ></script>
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'"
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -93,6 +93,9 @@ const MentionLink = {
|
|||
this.highlightType
|
||||
]
|
||||
},
|
||||
useAtIcon () {
|
||||
return this.mergedConfig.useAtIcon
|
||||
},
|
||||
isRemote () {
|
||||
return this.userName !== this.userNameFull
|
||||
},
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
class="mod-modal"
|
||||
:class="{ peek: modalPeeked }"
|
||||
:no-background="modalPeeked"
|
||||
@backdropClicked="closeModal"
|
||||
>
|
||||
<div class="mod-modal-panel panel">
|
||||
<div class="panel-heading">
|
||||
|
|
|
@ -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';
|
||||
|
@ -125,6 +125,20 @@
|
|||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
padding: 0;
|
||||
|
||||
&:first-child .menu-item {
|
||||
border-top-right-radius: $fallback--panelRadius;
|
||||
border-top-right-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
border-top-left-radius: $fallback--panelRadius;
|
||||
border-top-left-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
}
|
||||
|
||||
&:last-child .menu-item {
|
||||
border-bottom-right-radius: $fallback--panelRadius;
|
||||
border-bottom-right-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
border-bottom-left-radius: $fallback--panelRadius;
|
||||
border-bottom-left-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
}
|
||||
}
|
||||
|
||||
li:last-child {
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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"
|
||||
|
@ -152,6 +151,7 @@
|
|||
>
|
||||
<Timeago
|
||||
:time="notification.created_at"
|
||||
:with-direction="true"
|
||||
:auto-update="240"
|
||||
/>
|
||||
</router-link>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -105,12 +105,9 @@
|
|||
flex: 1;
|
||||
padding-left: 0.8em;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.heading-right, .notification-right {
|
||||
.timeago {
|
||||
display: inline-block;
|
||||
min-width: 6em;
|
||||
min-width: 3em;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -18,9 +18,7 @@
|
|||
:placeholder="$t('polls.option')"
|
||||
:maxlength="maxLength"
|
||||
@change="updatePollToParent"
|
||||
@keydown.enter.stop.prevent="
|
||||
$event.isComposing || $event.keyCode === 229 || nextOption(index)
|
||||
"
|
||||
@keydown.enter.stop.prevent="nextOption(index)"
|
||||
>
|
||||
</div>
|
||||
<button
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
@mouseenter="onMouseenter"
|
||||
@mouseleave="onMouseleave"
|
||||
>
|
||||
<div
|
||||
<button
|
||||
ref="trigger"
|
||||
class="button-unstyled popover-trigger-button"
|
||||
type="button"
|
||||
@click="onClick"
|
||||
>
|
||||
<slot name="trigger" />
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
v-if="!hidden"
|
||||
ref="content"
|
||||
|
|
|
@ -9,12 +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 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 {
|
||||
|
@ -63,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',
|
||||
|
@ -137,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 || ''
|
||||
|
@ -153,15 +138,7 @@ const PostStatusForm = {
|
|||
statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser)
|
||||
}
|
||||
|
||||
const {
|
||||
postContentType: contentType,
|
||||
postLanguage: defaultPostLanguage,
|
||||
sensitiveByDefault,
|
||||
sensitiveIfSubject,
|
||||
interfaceLanguage,
|
||||
alwaysShowSubjectInput,
|
||||
} = this.$store.getters.mergedConfig
|
||||
const postLanguage = defaultPostLanguage || interfaceToISOLanguage(interfaceLanguage)
|
||||
const { postContentType: contentType, sensitiveByDefault, sensitiveIfSubject, interfaceLanguage } = this.$store.getters.mergedConfig
|
||||
|
||||
let statusParams = {
|
||||
spoilerText: this.subject || '',
|
||||
|
@ -172,13 +149,12 @@ const PostStatusForm = {
|
|||
poll: {},
|
||||
mediaDescriptions: {},
|
||||
visibility: this.suggestedVisibility(),
|
||||
language: postLanguage,
|
||||
language: interfaceLanguage,
|
||||
contentType
|
||||
}
|
||||
|
||||
if (this.statusId || this.isRedraft) {
|
||||
const statusContentType = this.statusContentType || contentType
|
||||
const statusLanguage = this.statusLanguage || language
|
||||
statusParams = {
|
||||
spoilerText: this.subject || '',
|
||||
status: this.statusText || '',
|
||||
|
@ -188,7 +164,7 @@ const PostStatusForm = {
|
|||
poll: this.statusPoll || {},
|
||||
mediaDescriptions: this.statusMediaDescriptions || {},
|
||||
visibility: this.statusScope || this.suggestedVisibility(),
|
||||
language: this.statusLanguage || postLanguage,
|
||||
language: this.statusLanguage || interfaceLanguage,
|
||||
contentType: statusContentType
|
||||
}
|
||||
}
|
||||
|
@ -213,8 +189,8 @@ const PostStatusForm = {
|
|||
poll: draft.data.poll,
|
||||
mediaDescriptions: draft.data.mediaDescriptions,
|
||||
visibility: draft.data.visibility,
|
||||
contentType: draft.data.contentType,
|
||||
language: draft.data.language
|
||||
language: draft.data.language,
|
||||
contentType: draft.data.contentType
|
||||
}
|
||||
|
||||
if (draft.data.poll) {
|
||||
|
@ -223,10 +199,6 @@ const PostStatusForm = {
|
|||
}
|
||||
}
|
||||
|
||||
// When first loading the form, hide the subject (CW) field if it's disabled or doesn't have a starting value.
|
||||
// "disableSubject" seems to take priority over "alwaysShowSubjectInput".
|
||||
const showSubject = !this.disableSubject && (statusParams.spoilerText || alwaysShowSubjectInput)
|
||||
|
||||
return {
|
||||
dropFiles: [],
|
||||
uploadingFiles: false,
|
||||
|
@ -241,10 +213,7 @@ const PostStatusForm = {
|
|||
preview: null,
|
||||
previewLoading: false,
|
||||
emojiInputShown: false,
|
||||
idempotencyKey: '',
|
||||
activeEmojiInput: undefined,
|
||||
activeTextInput: undefined,
|
||||
subjectVisible: showSubject
|
||||
idempotencyKey: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -333,11 +302,13 @@ const PostStatusForm = {
|
|||
...mapState({
|
||||
mobileLayout: state => state.interface.mobileLayout
|
||||
}),
|
||||
isoLanguages () {
|
||||
return iso6391.getAllCodes();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'newStatus': {
|
||||
deep: true,
|
||||
flush: 'sync',
|
||||
handler () {
|
||||
this.statusChanged()
|
||||
}
|
||||
|
@ -703,33 +674,8 @@ const PostStatusForm = {
|
|||
this.$refs['emoji-input'].resize()
|
||||
},
|
||||
showEmojiPicker () {
|
||||
if (!this.activeEmojiInput || !this.activeTextInput)
|
||||
this.focusStatusInput()
|
||||
|
||||
this.$refs[this.activeTextInput].focus()
|
||||
this.$refs[this.activeEmojiInput].triggerShowPicker()
|
||||
},
|
||||
focusStatusInput() {
|
||||
this.activeEmojiInput = 'emoji-input'
|
||||
this.activeTextInput = 'textarea'
|
||||
},
|
||||
focusSubjectInput() {
|
||||
this.activeEmojiInput = 'subject-emoji-input'
|
||||
this.activeTextInput = 'subject-input'
|
||||
},
|
||||
toggleSubjectVisible() {
|
||||
// If hiding CW, then we need to clear the subject and reset focus
|
||||
if (this.subjectVisible)
|
||||
{
|
||||
this.focusStatusInput()
|
||||
|
||||
// "nsfw" property is normally set by the @change listener, but this bypasses it.
|
||||
// We need to clear it manually instead.
|
||||
this.newStatus.spoilerText = ''
|
||||
this.newStatus.nsfw = false
|
||||
}
|
||||
|
||||
this.subjectVisible = !this.subjectVisible
|
||||
this.$refs['textarea'].focus()
|
||||
this.$refs['emoji-input'].triggerShowPicker()
|
||||
},
|
||||
clearError () {
|
||||
this.error = null
|
||||
|
|
|
@ -118,16 +118,13 @@
|
|||
/>
|
||||
</div>
|
||||
<EmojiInput
|
||||
v-if="subjectVisible"
|
||||
ref="subject-emoji-input"
|
||||
v-if="!disableSubject && (newStatus.spoilerText || alwaysShowSubject)"
|
||||
v-model="newStatus.spoilerText"
|
||||
enable-emoji-picker
|
||||
hide-emoji-button
|
||||
:suggest="emojiSuggestor"
|
||||
class="form-control"
|
||||
>
|
||||
<input
|
||||
ref="subject-input"
|
||||
v-model="newStatus.spoilerText"
|
||||
type="text"
|
||||
:placeholder="$t('post_status.content_warning')"
|
||||
|
@ -135,7 +132,6 @@
|
|||
size="1"
|
||||
class="form-post-subject"
|
||||
@input="onSubjectInput"
|
||||
@focus="focusSubjectInput()"
|
||||
>
|
||||
</EmojiInput>
|
||||
<i18n-t
|
||||
|
@ -170,18 +166,13 @@
|
|||
cols="1"
|
||||
:disabled="posting && !optimisticPosting"
|
||||
class="form-post-body"
|
||||
:class="{ 'scrollable-form': !!maxHeight, '-has-subject': subjectVisible }"
|
||||
@keydown.exact.enter="
|
||||
$event.isComposing ||
|
||||
$event.keyCode === 229 ||
|
||||
(submitOnEnter && postStatus($event, newStatus))
|
||||
"
|
||||
:class="{ 'scrollable-form': !!maxHeight }"
|
||||
@keydown.exact.enter="submitOnEnter && postStatus($event, newStatus)"
|
||||
@keydown.meta.enter="postStatus($event, newStatus)"
|
||||
@keydown.ctrl.enter="!submitOnEnter && postStatus($event, newStatus)"
|
||||
@input="resize"
|
||||
@compositionupdate="resize"
|
||||
@paste="paste"
|
||||
@focus="focusStatusInput()"
|
||||
/>
|
||||
<p
|
||||
v-if="hasStatusLengthLimit"
|
||||
|
@ -194,7 +185,6 @@
|
|||
<div
|
||||
v-if="!disableScopeSelector"
|
||||
class="visibility-tray"
|
||||
:class="{ 'visibility-tray-edit': isEdit }"
|
||||
>
|
||||
<scope-selector
|
||||
v-if="!disableVisibilitySelector"
|
||||
|
@ -205,50 +195,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>
|
||||
|
@ -289,15 +276,6 @@
|
|||
>
|
||||
<FAIcon icon="poll-h" />
|
||||
</button>
|
||||
<button
|
||||
v-if="!disableSubject"
|
||||
class="spoiler-icon button-unstyled"
|
||||
:class="{ selected: subjectVisible }"
|
||||
:title="$t('post_status.toggle_content_warning')"
|
||||
@click="toggleSubjectVisible"
|
||||
>
|
||||
<FAIcon icon="eye-slash" />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
v-if="posting"
|
||||
|
@ -468,10 +446,6 @@
|
|||
align-items: baseline;
|
||||
}
|
||||
|
||||
.visibility-tray-edit {
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
.visibility-notice.edit-warning {
|
||||
> :first-child {
|
||||
margin-top: 0;
|
||||
|
@ -482,13 +456,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.format-selector-container {
|
||||
.format-selector {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.media-upload-icon, .poll-icon, .emoji-icon, .spoiler-icon {
|
||||
.media-upload-icon, .poll-icon, .emoji-icon {
|
||||
font-size: 1.85em;
|
||||
line-height: 1.1;
|
||||
flex: 1;
|
||||
|
@ -531,11 +499,6 @@
|
|||
|
||||
.poll-icon {
|
||||
order: 3;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.spoiler-icon {
|
||||
order: 4;
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
|
@ -588,11 +551,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
|
||||
|
@ -605,11 +563,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`
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./retweet_button.js"></script>
|
||||
<script src="./retweet_button.js" ></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
|
|
@ -1,54 +1,38 @@
|
|||
import FollowCard from '../follow_card/follow_card.vue'
|
||||
import Conversation from '../conversation/conversation.vue'
|
||||
import Status from '../status/status.vue'
|
||||
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
|
||||
import SearchFilters from './search_filters.vue'
|
||||
import map from 'lodash/map'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faCircleNotch,
|
||||
faSearch
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { uniqBy } from 'lodash'
|
||||
|
||||
library.add(
|
||||
faCircleNotch,
|
||||
faSearch
|
||||
)
|
||||
|
||||
const allSearchTypes = Object.freeze(['statuses', 'media', 'accounts', 'hashtags'])
|
||||
|
||||
const Search = {
|
||||
components: {
|
||||
FollowCard,
|
||||
Conversation,
|
||||
TabSwitcher,
|
||||
SearchFilters
|
||||
Status,
|
||||
TabSwitcher
|
||||
},
|
||||
props: [
|
||||
'query'
|
||||
],
|
||||
data () {
|
||||
return {
|
||||
queryCount: 0,
|
||||
loadedInitially: false,
|
||||
loading: Object.fromEntries(
|
||||
allSearchTypes.map((searchType) => [searchType, false])
|
||||
),
|
||||
loaded: false,
|
||||
loading: false,
|
||||
searchTerm: this.query || '',
|
||||
userIds: [],
|
||||
statuses: [],
|
||||
media: [],
|
||||
hashtags: [],
|
||||
allTabs: allSearchTypes,
|
||||
currentResultTab: 'statuses',
|
||||
hasUserSelectedTab: false,
|
||||
|
||||
statusesOffset: 0,
|
||||
lastStatusFetchCount: 0,
|
||||
mediaOffset: 0,
|
||||
lastMediaFetchCount: 0,
|
||||
lastQuery: '',
|
||||
filter: {}
|
||||
currenResultTab: 'statuses'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -61,20 +45,6 @@ const Search = {
|
|||
return this.statuses.filter(status =>
|
||||
allStatusesObject[status.id] && !allStatusesObject[status.id].deleted
|
||||
)
|
||||
},
|
||||
visibleMedia () {
|
||||
const allStatusesObject = this.$store.state.statuses.allStatusesObject
|
||||
|
||||
return this.media.filter(status =>
|
||||
allStatusesObject[status.id] && !allStatusesObject[status.id].deleted
|
||||
)
|
||||
},
|
||||
canSearchMediaPosts () {
|
||||
return this.$store.state.instance.searchTypeMediaEnabled === true
|
||||
},
|
||||
hasAtLeastOneResult () {
|
||||
return allSearchTypes
|
||||
.some((searchType) => this.getVisibleLength(searchType) > 0)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
@ -84,191 +54,55 @@ const Search = {
|
|||
query (newValue) {
|
||||
this.searchTerm = newValue
|
||||
this.search(newValue)
|
||||
},
|
||||
filter: {
|
||||
deep: true,
|
||||
handler () {
|
||||
this.lastQuery = "" // invalidate state
|
||||
this.search(this.searchTerm)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
newQuery (query) {
|
||||
if (this.lastQuery === query && !this.loading[this.currentResultTab]) {
|
||||
// Handle search retries
|
||||
this.lastQuery = "" // invalidate state
|
||||
this.search(query)
|
||||
} else {
|
||||
this.$router.push({ name: 'search', query: { query } })
|
||||
this.$refs.searchInput.focus()
|
||||
}
|
||||
this.$router.push({ name: 'search', query: { query } })
|
||||
this.$refs.searchInput.focus()
|
||||
},
|
||||
async search (query, searchType = null) {
|
||||
search (query) {
|
||||
if (!query) {
|
||||
for (const searchType of allSearchTypes) {
|
||||
this.loading[searchType] = false
|
||||
}
|
||||
this.loading = false
|
||||
return
|
||||
}
|
||||
|
||||
const localQueryCount = ++this.queryCount
|
||||
|
||||
const isNewSearch = this.lastQuery !== query
|
||||
this.loading = true
|
||||
this.userIds = []
|
||||
this.statuses = []
|
||||
this.hashtags = []
|
||||
this.$refs.searchInput.blur()
|
||||
if (isNewSearch) {
|
||||
this.userIds = []
|
||||
this.hashtags = []
|
||||
this.statuses = []
|
||||
this.media = []
|
||||
|
||||
this.statusesOffset = 0
|
||||
this.lastStatusFetchCount = 0
|
||||
this.mediaOffset = 0
|
||||
this.lastMediaFetchCount = 0
|
||||
}
|
||||
|
||||
let searchTypes = allSearchTypes
|
||||
if (searchType) {
|
||||
// Search only for `searchType` if it is provided
|
||||
searchTypes = [searchType]
|
||||
} else if (this.hasUserSelectedTab && this.currentResultTab !== 'statuses') {
|
||||
// Start the search from the tab that the user has selected
|
||||
// No need to sort if userPreferredTab === 'statuses'
|
||||
searchTypes = [
|
||||
this.currentResultTab,
|
||||
...allSearchTypes.filter((tab) => tab !== this.currentResultTab)
|
||||
]
|
||||
}
|
||||
|
||||
for (const searchType of searchTypes) {
|
||||
this.loading[searchType] = true
|
||||
}
|
||||
|
||||
let oldStatusesLength = this.statuses.length
|
||||
let oldMediaLength = this.media.length
|
||||
|
||||
let skipMediaSearch = !this.canSearchMediaPosts
|
||||
|
||||
for (const searchType of searchTypes) {
|
||||
try {
|
||||
if (searchType === 'media' && skipMediaSearch) {
|
||||
continue
|
||||
}
|
||||
|
||||
let searchOffset
|
||||
if (searchType === 'statuses') {
|
||||
searchOffset = this.statusesOffset
|
||||
} else if (searchType === 'media') {
|
||||
searchOffset = this.mediaOffset
|
||||
}
|
||||
|
||||
const data = await this.$store.dispatch('search', {
|
||||
q: query,
|
||||
resolve: true,
|
||||
offset: searchOffset,
|
||||
'type': searchType,
|
||||
following:
|
||||
'followingOnly' in this.filter && this.filter.followingOnly,
|
||||
local: 'localOnly' in this.filter && this.filter.localOnly
|
||||
})
|
||||
|
||||
if (localQueryCount !== this.queryCount) {
|
||||
// Query count differs, there should be a newer query
|
||||
return
|
||||
}
|
||||
|
||||
// Always append to old results. If new results are empty, this doesn't change anything
|
||||
this.userIds = this.userIds.concat(map(data.accounts, 'id'))
|
||||
this.statuses = uniqBy(this.statuses.concat(data.statuses), 'id')
|
||||
if ('media' in data) {
|
||||
this.media = uniqBy(this.media.concat(data.media), 'id')
|
||||
}
|
||||
this.hashtags = this.hashtags.concat(data.hashtags)
|
||||
|
||||
if (isNewSearch) {
|
||||
if (!this.hasUserSelectedTab) {
|
||||
this.currentResultTab = this.getFirstTabWithResults()
|
||||
}
|
||||
if (searchType === 'statuses' && data.statuses.length === 0) {
|
||||
// Safe to assume that there are no media posts
|
||||
skipMediaSearch = true
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
if (localQueryCount !== this.queryCount) {
|
||||
// Skip cleanups if there's a newer query
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.loadedInitially && this.hasAtLeastOneResult) {
|
||||
// Show results on the first meaningful response
|
||||
this.loadedInitially = true
|
||||
}
|
||||
this.loading[searchType] = false
|
||||
|
||||
if (searchType === 'statuses') {
|
||||
// Offset from whatever we already have
|
||||
this.statusesOffset = this.statuses.length
|
||||
// Because the amount of new statuses can actually be zero, compare to old length instead
|
||||
this.lastStatusFetchCount = this.statuses.length - oldStatusesLength
|
||||
} else if (searchType === 'media') {
|
||||
this.mediaOffset = this.media.length
|
||||
this.lastMediaFetchCount = this.media.length - oldMediaLength
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.lastQuery = query
|
||||
this.loadedInitially = true
|
||||
for (const searchType of allSearchTypes) {
|
||||
this.loading[searchType] = false
|
||||
}
|
||||
this.$store.dispatch('search', { q: query, resolve: true })
|
||||
.then(data => {
|
||||
this.loading = false
|
||||
this.userIds = map(data.accounts, 'id')
|
||||
this.statuses = data.statuses
|
||||
this.hashtags = data.hashtags
|
||||
this.currenResultTab = this.getActiveTab()
|
||||
this.loaded = true
|
||||
})
|
||||
},
|
||||
resultCount (tab) {
|
||||
const length = this.getVisibleLength(tab)
|
||||
|
||||
if (length === 0 || !this.loadedInitially) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (
|
||||
(tab === 'statuses' && this.lastStatusFetchCount !== 0) ||
|
||||
(tab === 'media' && this.lastMediaFetchCount !== 0)
|
||||
) {
|
||||
return ` (${length}+)`
|
||||
}
|
||||
|
||||
return ` (${length})`
|
||||
resultCount (tabName) {
|
||||
const length = this[tabName].length
|
||||
return length === 0 ? '' : ` (${length})`
|
||||
},
|
||||
onResultTabSwitch (key) {
|
||||
this.currentResultTab = key
|
||||
this.hasUserSelectedTab = true
|
||||
this.currenResultTab = key
|
||||
},
|
||||
getFirstTabWithResults () {
|
||||
for (const tab of allSearchTypes) {
|
||||
if (this.getVisibleLength(tab) > 0) {
|
||||
return tab
|
||||
}
|
||||
getActiveTab () {
|
||||
if (this.visibleStatuses.length > 0) {
|
||||
return 'statuses'
|
||||
} else if (this.users.length > 0) {
|
||||
return 'people'
|
||||
} else if (this.hashtags.length > 0) {
|
||||
return 'hashtags'
|
||||
}
|
||||
|
||||
return 'statuses'
|
||||
},
|
||||
lastHistoryRecord (hashtag) {
|
||||
return hashtag.history && hashtag.history[0]
|
||||
},
|
||||
getVisibleLength (tab) {
|
||||
if (tab === 'statuses') {
|
||||
return this.visibleStatuses.length
|
||||
} else if (tab === 'media') {
|
||||
return this.visibleMedia.length
|
||||
} else if (tab === 'accounts') {
|
||||
return this.users.length
|
||||
} else if (tab === 'hashtags') {
|
||||
return this.hashtags.length
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,6 @@
|
|||
<div class="title">
|
||||
{{ $t('nav.search') }}
|
||||
</div>
|
||||
<SearchFilters
|
||||
v-model="filter"
|
||||
/>
|
||||
</div>
|
||||
<div class="search-input-container">
|
||||
<input
|
||||
|
@ -14,9 +11,7 @@
|
|||
v-model="searchTerm"
|
||||
class="search-input"
|
||||
:placeholder="$t('nav.search')"
|
||||
@keydown.enter="
|
||||
$event.isComposing || $event.keyCode === 229 || newQuery(searchTerm)
|
||||
"
|
||||
@keyup.enter="newQuery(searchTerm)"
|
||||
>
|
||||
<button
|
||||
class="btn button-default search-button"
|
||||
|
@ -26,25 +21,30 @@
|
|||
<FAIcon icon="search" />
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="loadedInitially">
|
||||
<div
|
||||
v-if="loading"
|
||||
class="text-center loading-icon"
|
||||
>
|
||||
<FAIcon
|
||||
icon="circle-notch"
|
||||
spin
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="loaded">
|
||||
<div class="search-nav-heading">
|
||||
<tab-switcher
|
||||
ref="tabSwitcher"
|
||||
:on-switch="onResultTabSwitch"
|
||||
:active-tab="currentResultTab"
|
||||
:active-tab="currenResultTab"
|
||||
>
|
||||
<span
|
||||
key="statuses"
|
||||
:label="$t('user_card.statuses') + resultCount('statuses')"
|
||||
:label="$t('user_card.statuses') + resultCount('visibleStatuses')"
|
||||
/>
|
||||
<span
|
||||
v-if="canSearchMediaPosts"
|
||||
key="media"
|
||||
:label="$t('user_card.media') + resultCount('media')"
|
||||
/>
|
||||
<span
|
||||
key="accounts"
|
||||
:label="$t('search.people') + resultCount('accounts')"
|
||||
key="people"
|
||||
:label="$t('search.people') + resultCount('users')"
|
||||
/>
|
||||
<span
|
||||
key="hashtags"
|
||||
|
@ -54,97 +54,27 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div
|
||||
v-if="!Object.values(loading).includes(false) && !hasAtLeastOneResult"
|
||||
class="text-center loading-icon"
|
||||
>
|
||||
<FAIcon
|
||||
icon="circle-notch"
|
||||
spin
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="currentResultTab === 'statuses'">
|
||||
<Conversation
|
||||
<div v-if="currenResultTab === 'statuses'">
|
||||
<div
|
||||
v-if="visibleStatuses.length === 0 && !loading && loaded"
|
||||
class="search-result-heading"
|
||||
>
|
||||
<h4>{{ $t('search.no_results') }}</h4>
|
||||
</div>
|
||||
<Status
|
||||
v-for="status in visibleStatuses"
|
||||
:key="status.id"
|
||||
:collapsable="true"
|
||||
class="status-fadein"
|
||||
:status-id="status.id"
|
||||
:collapsable="false"
|
||||
:expandable="false"
|
||||
:compact="false"
|
||||
class="search-result"
|
||||
:statusoid="status"
|
||||
:no-heading="false"
|
||||
/>
|
||||
<button
|
||||
v-if="!loading['statuses'] && loadedInitially && lastStatusFetchCount > 0"
|
||||
class="more-statuses-button button-unstyled -link -fullwidth"
|
||||
@click.prevent="search(lastQuery, 'statuses')"
|
||||
>
|
||||
<div class="new-status-notification text-center">
|
||||
{{ $t('search.load_more') }}
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
v-else-if="loading['statuses'] && statusesOffset > 0"
|
||||
class="text-center loading-icon"
|
||||
>
|
||||
<FAIcon
|
||||
icon="circle-notch"
|
||||
spin
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
(visibleStatuses.length === 0 || lastStatusFetchCount === 0) &&
|
||||
!loading['statuses'] && loadedInitially
|
||||
"
|
||||
class="search-result-heading"
|
||||
>
|
||||
<h4>
|
||||
{{ visibleStatuses.length === 0 ? $t('search.no_results') : $t('search.no_more_results') }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="currentResultTab === 'media'">
|
||||
<Conversation
|
||||
v-for="media in visibleMedia"
|
||||
:key="media.id"
|
||||
:collapsable="true"
|
||||
class="status-fadein"
|
||||
:status-id="media.id"
|
||||
/>
|
||||
<button
|
||||
v-if="!loading['media'] && loadedInitially && lastMediaFetchCount > 0"
|
||||
class="more-statuses-button button-unstyled -link -fullwidth"
|
||||
@click.prevent="search(lastQuery, 'media')"
|
||||
>
|
||||
<div class="new-status-notification text-center">
|
||||
{{ $t('search.load_more') }}
|
||||
</div>
|
||||
</button>
|
||||
<div v-else-if="currenResultTab === 'people'">
|
||||
<div
|
||||
v-else-if="loading['media'] && mediaOffset > 0"
|
||||
class="text-center loading-icon"
|
||||
>
|
||||
<FAIcon
|
||||
icon="circle-notch"
|
||||
spin
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
(visibleMedia.length === 0 || lastMediaFetchCount === 0) &&
|
||||
!loading['media'] && loadedInitially
|
||||
"
|
||||
class="search-result-heading"
|
||||
>
|
||||
<h4>
|
||||
{{ visibleMedia.length === 0 ? $t('search.no_results') : $t('search.no_more_results') }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="currentResultTab === 'accounts'">
|
||||
<div
|
||||
v-if="users.length === 0 && !loading['accounts'] && loadedInitially"
|
||||
v-if="users.length === 0 && !loading && loaded"
|
||||
class="search-result-heading"
|
||||
>
|
||||
<h4>{{ $t('search.no_results') }}</h4>
|
||||
|
@ -156,9 +86,9 @@
|
|||
class="list-item search-result"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="currentResultTab === 'hashtags'">
|
||||
<div v-else-if="currenResultTab === 'hashtags'">
|
||||
<div
|
||||
v-if="hashtags.length === 0 && !loading['hashtags'] && loadedInitially"
|
||||
v-if="hashtags.length === 0 && !loading && loaded"
|
||||
class="search-result-heading"
|
||||
>
|
||||
<h4>{{ $t('search.no_results') }}</h4>
|
||||
|
@ -217,6 +147,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.search-result {
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid;
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
}
|
||||
|
||||
.search-result-footer {
|
||||
border-width: 1px 0 0 0;
|
||||
border-style: solid;
|
||||
|
@ -241,10 +178,6 @@
|
|||
|
||||
.search-button {
|
||||
margin-left: 0.5em;
|
||||
width: 3em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -256,12 +189,6 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& + & {
|
||||
border-top: 1px solid;
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
}
|
||||
|
||||
.hashtag {
|
||||
flex: 1 1 auto;
|
||||
color: $fallback--text;
|
||||
|
@ -269,12 +196,6 @@
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
padding: .5em 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.count {
|
||||
|
@ -289,8 +210,4 @@
|
|||
}
|
||||
}
|
||||
|
||||
.more-statuses-button {
|
||||
height: 3.5em;
|
||||
line-height: 3.5em;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
<template>
|
||||
<Popover
|
||||
v-if="showFilters"
|
||||
trigger="click"
|
||||
class="SearchFilters"
|
||||
placement="bottom"
|
||||
:bound-to="{ x: 'container' }"
|
||||
>
|
||||
<template #content>
|
||||
<div class="dropdown-menu">
|
||||
<button
|
||||
v-if="canSearchFollowing"
|
||||
class="button-default dropdown-item"
|
||||
@click="toggleFilter('followingOnly')"
|
||||
>
|
||||
<span
|
||||
class="menu-checkbox"
|
||||
:class="{ 'menu-checkbox-checked': currentFilter.followingOnly }"
|
||||
/>{{ $t('lists.following_only') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="canSearchLocal"
|
||||
class="button-default dropdown-item"
|
||||
@click="toggleFilter('localOnly')"
|
||||
>
|
||||
<span
|
||||
class="menu-checkbox"
|
||||
:class="{ 'menu-checkbox-checked': currentFilter.localOnly }"
|
||||
/>{{ $t('search.local_only') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template #trigger>
|
||||
<button class="filter-trigger-button button-unstyled">
|
||||
<FAIcon icon="filter" />
|
||||
</button>
|
||||
</template>
|
||||
</Popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Popover from '../popover/popover.vue'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faFilter } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faFilter
|
||||
)
|
||||
|
||||
export default {
|
||||
components: { Popover },
|
||||
props: [
|
||||
'modelValue'
|
||||
],
|
||||
emits: [
|
||||
'update:modelValue'
|
||||
],
|
||||
data () {
|
||||
return {
|
||||
currentFilter: {
|
||||
followingOnly: false,
|
||||
localOnly: false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showFilters () {
|
||||
return this.canSearchFollowing || this.canSearchLocal
|
||||
},
|
||||
isLoggedIn () {
|
||||
return !!this.$store.state.users.currentUser
|
||||
},
|
||||
canSearchFollowing () {
|
||||
return this.isLoggedIn &&
|
||||
this.$store.state.instance.searchOptionFollowingEnabled
|
||||
},
|
||||
canSearchLocal () {
|
||||
return this.$store.state.instance.searchOptionLocalEnabled
|
||||
},
|
||||
},
|
||||
created () {
|
||||
for (const filterName of Object.entries(this.currentFilter)) {
|
||||
if (this.modelValue && filterName in this.modelValue) {
|
||||
this.currentFilter[filterName] = this.modelValue[filterName]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleFilter (filterName) {
|
||||
if (filterName in this.currentFilter) {
|
||||
this.currentFilter[filterName] = !this.currentFilter[filterName]
|
||||
} else {
|
||||
this.currentFilter[filterName] = true
|
||||
}
|
||||
this.$emit('update:modelValue', this.currentFilter)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.SearchFilters {
|
||||
align-self: stretch;
|
||||
|
||||
> button {
|
||||
line-height: 100%;
|
||||
height: 100%;
|
||||
width: var(--__panel-heading-height-inner);
|
||||
text-align: center;
|
||||
|
||||
svg {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -10,12 +10,6 @@ library.add(
|
|||
)
|
||||
|
||||
const SearchBar = {
|
||||
mounted () {
|
||||
window.addEventListener('keydown', this.autoFocus)
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.autoFocus)
|
||||
},
|
||||
data: () => ({
|
||||
searchTerm: undefined,
|
||||
hidden: true,
|
||||
|
@ -42,38 +36,6 @@ const SearchBar = {
|
|||
this.$refs.searchInput.focus()
|
||||
}
|
||||
})
|
||||
},
|
||||
autoFocus (event) {
|
||||
if (
|
||||
event.target.tagName !== 'BODY' ||
|
||||
event.altKey ||
|
||||
event.ctrlKey ||
|
||||
event.isComposing ||
|
||||
event.metaKey ||
|
||||
event.repeat ||
|
||||
event.shiftKey ||
|
||||
// not very vue-esque, but as long as it works
|
||||
document.querySelector('.modal-view.modal-background.open')
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === '/') {
|
||||
if (this.hidden) {
|
||||
this.toggleHidden()
|
||||
} else {
|
||||
this.$refs.searchInput.focus()
|
||||
}
|
||||
event.preventDefault()
|
||||
} else if (event.key === 'Escape') {
|
||||
if (!this.hidden) {
|
||||
this.toggleHidden()
|
||||
}
|
||||
event.preventDefault()
|
||||
}
|
||||
},
|
||||
blur () {
|
||||
this.$refs.searchInput.blur()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,12 +24,7 @@
|
|||
class="search-bar-input"
|
||||
:placeholder="$t('nav.search')"
|
||||
type="text"
|
||||
@keydown.enter="
|
||||
$event.isComposing || $event.keyCode === 229 || find(searchTerm)
|
||||
"
|
||||
@keydown.escape="
|
||||
$event.isComposing || blur()
|
||||
"
|
||||
@keyup.enter="find(searchTerm)"
|
||||
>
|
||||
<button
|
||||
class="button-default search-button"
|
||||
|
@ -63,7 +58,8 @@
|
|||
|
||||
.SearchBar {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
align-items: baseline;
|
||||
vertical-align: baseline;
|
||||
justify-content: flex-end;
|
||||
|
||||
&.-expanded {
|
||||
|
@ -75,21 +71,13 @@
|
|||
height: 29px;
|
||||
}
|
||||
|
||||
.search-button {
|
||||
margin-left: 0.5em;
|
||||
width: 3em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-bar-input {
|
||||
flex: 1 0 auto;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.cancel-search {
|
||||
height: 100%;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.cancel-icon {
|
||||
|
|
|
@ -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')
|
||||
},
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
class="settings-modal"
|
||||
:class="{ peek: modalPeeked }"
|
||||
:no-background="modalPeeked"
|
||||
@backdropClicked="closeModal"
|
||||
>
|
||||
<div class="settings-modal-panel panel">
|
||||
<div class="panel-heading">
|
||||
|
@ -109,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,14 +4,12 @@ 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'
|
||||
import {
|
||||
faGlobe, faSync
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import iso6391 from 'iso-639-1'
|
||||
|
||||
library.add(
|
||||
faGlobe,
|
||||
|
@ -19,11 +17,6 @@ library.add(
|
|||
)
|
||||
|
||||
const GeneralTab = {
|
||||
setup() {
|
||||
const {postLanguageOptions} = usePostLanguageOptions()
|
||||
|
||||
return {postLanguageOptions}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
subjectLineOptions: ['email', 'noop', 'masto'].map(mode => ({
|
||||
|
@ -87,15 +80,6 @@ const GeneralTab = {
|
|||
label: this.$t(`post_status.content_type["${format}"]`)
|
||||
}))
|
||||
},
|
||||
postLanguages () {
|
||||
return iso6391.getLanguages(iso6391.getAllCodes())
|
||||
.map(lang => ({
|
||||
key: lang.code,
|
||||
value: lang.code,
|
||||
label: lang.nativeName,
|
||||
}))
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
},
|
||||
instanceSpecificPanelPresent () { return this.$store.state.instance.showInstanceSpecificPanel },
|
||||
instanceWallpaperUsed () {
|
||||
return this.$store.state.instance.background &&
|
||||
|
@ -134,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"
|
||||
|
@ -154,21 +146,6 @@
|
|||
{{ $t('settings.show_wider_shortcuts') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="displayPageBackgrounds">
|
||||
{{ $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') }}
|
||||
|
@ -506,6 +483,14 @@
|
|||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="useAtIcon"
|
||||
expert="1"
|
||||
>
|
||||
{{ $t('settings.use_at_icon') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="mentionLinkShowAvatar">
|
||||
{{ $t('settings.mention_link_show_avatar') }}
|
||||
|
@ -594,15 +579,6 @@
|
|||
{{ $t('settings.subject_line_behavior') }}
|
||||
</ChoiceSetting>
|
||||
</li>
|
||||
<li>
|
||||
<ChoiceSetting
|
||||
id="postLanguage"
|
||||
path="postLanguage"
|
||||
:options="postLanguages"
|
||||
>
|
||||
{{ $t('settings.post_status_language') }}
|
||||
</ChoiceSetting>
|
||||
</li>
|
||||
<li v-if="postFormats.length > 0">
|
||||
<ChoiceSetting
|
||||
id="postContentType"
|
||||
|
@ -612,15 +588,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>
|
||||
|
|
|
@ -33,7 +33,6 @@ const ProfileTab = {
|
|||
newName: this.$store.state.users.currentUser.name_unescaped,
|
||||
newBio: unescape(this.$store.state.users.currentUser.description),
|
||||
newLocked: this.$store.state.users.currentUser.locked,
|
||||
newPermitFollowback: this.$store.state.users.currentUser.permit_followback,
|
||||
newFields: this.$store.state.users.currentUser.fields.map(field => ({ name: field.name, value: field.value })),
|
||||
showRole: this.$store.state.users.currentUser.show_role,
|
||||
role: this.$store.state.users.currentUser.role,
|
||||
|
@ -130,15 +129,14 @@ 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,
|
||||
show_role: this.showRole,
|
||||
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 +185,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')"
|
||||
|
@ -257,19 +259,6 @@
|
|||
<BooleanSetting path="serverSide_locked">
|
||||
{{ $t('settings.lock_account_description') }}
|
||||
</BooleanSetting>
|
||||
<ul
|
||||
class="setting-list suboptions"
|
||||
:class="[{disabled: !serverSide_locked}]"
|
||||
>
|
||||
<li>
|
||||
<BooleanSetting
|
||||
path="serverSide_permitFollowback"
|
||||
:disabled="!serverSide_locked"
|
||||
>
|
||||
{{ $t('settings.permit_followback_description') }}
|
||||
</BooleanSetting>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<BooleanSetting path="serverSide_discoverable">
|
||||
|
|
|
@ -21,10 +21,8 @@
|
|||
</div>
|
||||
<div class="panel-body theme-preview-content">
|
||||
<div class="post">
|
||||
<div class="Avatar post-avatar">
|
||||
<div class="still-image avatar -better-shadow">
|
||||
( ͡° ͜ʖ ͡°)
|
||||
</div>
|
||||
<div class="avatar still-image">
|
||||
( ͡° ͜ʖ ͡°)
|
||||
</div>
|
||||
<div class="content">
|
||||
<h4>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue